//  (C) Copyright Gennadiy Rozental 2001-2015.
//  (C) Copyright Beman Dawes 2001.
//  Distributed under the Boost Software License, Version 1.0.
//  (See accompanying file LICENSE_1_0.txt or copy at
//  http://www.boost.org/LICENSE_1_0.txt)


//  See http://www.boost.org/libs/test for the library home page.
//
//  File        : $RCSfile$
//
//  Version     : $Revision$
//
//  Description : tests an ability of Unit Test Framework to catch all kinds
//  of test errors in a user code and properly report it.
// ***************************************************************************

// Boost.Test
#define BOOST_TEST_MAIN
#include <boost/test/unit_test.hpp>
#include <boost/test/tools/output_test_stream.hpp>
#include <boost/test/unit_test_log.hpp>
#include <boost/test/unit_test_suite.hpp>
#include <boost/test/framework.hpp>
#include <boost/test/tree/test_unit.hpp>
#include <boost/test/unit_test_parameters.hpp>
#include <boost/test/output/compiler_log_formatter.hpp>
#include <boost/test/results_reporter.hpp>
// STL
#include <iostream>
#include <stdexcept>

using namespace boost::unit_test;
using namespace boost::test_tools;

#if defined(__GNUC__) || defined(__SUNPRO_CC) || defined(__DECCXX_VER)
#define LIMITED_TEST
#endif

namespace {

struct this_test_log_formatter : public boost::unit_test::output::compiler_log_formatter
{
    void    print_prefix( std::ostream& output, boost::unit_test::const_string, std::size_t line )
    {
        output << line << ": ";
    }

    void test_unit_finish( std::ostream& output, test_unit const& tu, unsigned long )
    {
        output << "Leaving test " << tu.p_type_name << " \"" << tu.p_name << "\"" << std::endl;
    }

};

//____________________________________________________________________________//

char const* log_level_name[] = {
    "log_successful_tests",
    "log_test_suites",
    "log_messages",
    "log_warnings",
    "log_all_errors",
    "log_cpp_exception_errors",
    "log_system_errors",
    "log_fatal_errors",
    "log_nothing"
};

enum error_type_enum {
    et_begin,
    et_none = et_begin,
    et_message,
    et_warning,
    et_user,
    et_cpp_exception,
#ifdef LIMITED_TEST
    et_fatal_user,
#else
    et_system,
    et_fatal_user,
    et_fatal_system,
#endif
    et_end
} error_type;

char const* error_type_name[] = {
    "no error", "user message", "user warning",
    "user non-fatal error", "cpp exception", " system error",
    "user fatal error", "system fatal error"
};

int divide_by_zero = 0;

void error_on_demand()
{
    switch( error_type ) {
    case et_none:
        BOOST_CHECK_MESSAGE( divide_by_zero == 0, "no error" );
        break;

    case et_message:
        BOOST_TEST_MESSAGE( "message" );
        break;

    case et_warning:
        BOOST_WARN_MESSAGE( divide_by_zero != 0, "warning" );
        break;

    case et_user:
        BOOST_ERROR( "non-fatal error" );
        break;

    case et_fatal_user:
        BOOST_FAIL( "fatal error" );

        BOOST_ERROR( "Should never reach this code!" );
        break;

    case et_cpp_exception:
        BOOST_TEST_CHECKPOINT( "error_on_demand() throw runtime_error" );
        throw std::runtime_error( "test std::runtime error what() message" );

#ifndef LIMITED_TEST
    case et_system:
        BOOST_TEST_CHECKPOINT( "error_on_demand() divide by zero" );
        divide_by_zero = 1 / divide_by_zero;
        break;

    case et_fatal_system:
        BOOST_TEST_CHECKPOINT( "write to an invalid address" );
        {
            int* p = 0;
            *p = 0;

            BOOST_ERROR( "Should never reach this code!" );
        }
        break;
#endif
    default:
        BOOST_ERROR( "Should never reach this code!" );
    }
    return;
}

}  // local namespace

//____________________________________________________________________________//

BOOST_AUTO_TEST_CASE( test_errors_handling )
{
#define PATTERN_FILE_NAME "errors-handling-test.pattern"
    std::string pattern_file_name(
        framework::master_test_suite().argc <= 1
            ? (runtime_config::save_pattern() ? PATTERN_FILE_NAME : "./baseline-outputs/" PATTERN_FILE_NAME)
            : framework::master_test_suite().argv[1] );

#ifdef LIMITED_TEST
    pattern_file_name += "2";
#endif

    output_test_stream test_output( pattern_file_name, !runtime_config::save_pattern() );

    test_case* test = BOOST_TEST_CASE( &error_on_demand );
    test->p_default_status.value = test_unit::RS_ENABLED;
    framework::finalize_setup_phase( test->p_id );

    // for each log level
    for( log_level level = log_successful_tests;
         level          <= log_nothing;
         level           = static_cast<log_level>(level+1) )
    {
        // for each error type
        for( error_type = et_begin;
             error_type != et_end;
             error_type = static_cast<error_type_enum>(error_type+1) )
        {
            test_output << "\n===========================\n"
                        << "log level: "       << log_level_name[level] << ';'
                        << " error type: "     << error_type_name[error_type] << ";\n" << std::endl;

            unit_test_log.set_stream( test_output );
            unit_test_log.set_formatter( new this_test_log_formatter );
            unit_test_log.set_threshold_level( level );
            framework::run( test );

            unit_test_log.set_stream( std::cout );
            unit_test_log.set_format( runtime_config::get<output_format>( runtime_config::LOG_FORMAT ) );

            log_level ll = runtime_config::get<log_level>( runtime_config::LOG_LEVEL );
            unit_test_log.set_threshold_level( ll != invalid_log_level? ll : log_all_errors );

            BOOST_CHECK( test_output.match_pattern() );
        }
    }
}

//____________________________________________________________________________//

// EOF

