//  (C) Copyright Gennadiy Rozental 2001-2003.
//  (C) Copyright Ullrich Koethe 2001.
//  Use, modification, and distribution are subject to 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: test_tools.hpp,v $
//
//  Version     : $Revision: 1.35.2.1 $
//
//  Description : contains definition for all test tools in test toolbox
// ***************************************************************************

#ifndef BOOST_TEST_TOOLS_HPP
#define BOOST_TEST_TOOLS_HPP

// Boost.Test
#include <boost/test/detail/unit_test_config.hpp>
#include <boost/test/unit_test_log.hpp>
#include <boost/test/detail/class_properties.hpp>
#include <boost/test/detail/wrap_stringstream.hpp>

// BOOST
#include <boost/cstdlib.hpp> // for boost::exit_success;
#include <boost/config.hpp>  // compilers workarounds
#include <boost/shared_ptr.hpp>

#include <stdexcept>        // for std::exception
#include <cstddef>          // for std::size_t
#include <memory>           // for std::auto_ptr
#include <string>           // for std::string

// ************************************************************************** //
// **************                    TOOL BOX                  ************** //
// ************************************************************************** //

#define BOOST_CHECKPOINT(message_)                                          \
    boost::test_toolbox::detail::checkpoint_impl(                           \
        boost::wrap_stringstream().ref() << message_, __FILE__, __LINE__)   \
/**/

#define BOOST_WARN(predicate)                                               \
    boost::test_toolbox::detail::warn_and_continue_impl((predicate),        \
        boost::wrap_stringstream().ref() << #predicate, __FILE__, __LINE__) \
/**/

#define BOOST_CHECK(predicate)                                              \
    boost::test_toolbox::detail::test_and_continue_impl((predicate),        \
        boost::wrap_stringstream().ref() << #predicate, __FILE__, __LINE__) \
/**/

#define BOOST_CHECK_EQUAL(left_, right_)                                                \
    boost::test_toolbox::detail::equal_and_continue_impl((left_), (right_),             \
        boost::wrap_stringstream().ref() << #left_ " == " #right_, __FILE__, __LINE__)
/**/

#define BOOST_CHECK_CLOSE(left_, right_, tolerance) \
    boost::test_toolbox::detail::compare_and_continue_impl((left_), (right_), (tolerance),\
                                                           #left_,  #right_, __FILE__, __LINE__)
/**/

#define BOOST_BITWISE_EQUAL(left_, right_) \
    boost::test_toolbox::detail::bitwise_equal_and_continue_impl((left_), (right_), \
        boost::wrap_stringstream().ref() << #left_ " =.= " #right_, __FILE__, __LINE__)
/**/

#define BOOST_REQUIRE(predicate) \
    boost::test_toolbox::detail::test_and_throw_impl((predicate), \
        boost::wrap_stringstream().ref() << #predicate, __FILE__, __LINE__)
/**/

#define BOOST_MESSAGE(message_) \
    boost::test_toolbox::detail::message_impl( \
        boost::wrap_stringstream().ref() << message_, __FILE__, __LINE__)
/**/

#define BOOST_WARN_MESSAGE(predicate, message_) \
    boost::test_toolbox::detail::warn_and_continue_impl((predicate), \
        boost::wrap_stringstream().ref() << message_, __FILE__, __LINE__,false)
/**/

#define BOOST_CHECK_MESSAGE(predicate, message_) \
    boost::test_toolbox::detail::test_and_continue_impl((predicate), \
        boost::wrap_stringstream().ref() << message_, __FILE__, __LINE__,false)
/**/

#define BOOST_REQUIRE_MESSAGE(predicate, message_) \
    boost::test_toolbox::detail::test_and_throw_impl((predicate), \
        boost::wrap_stringstream().ref() << message_, __FILE__, __LINE__,false)
/**/

#define BOOST_CHECK_PREDICATE( predicate, arg_list_size, arg_list ) \
    boost::test_toolbox::detail::test_and_continue_impl(predicate, BOOST_PLACE_PREDICATE_ARGS ## arg_list_size arg_list, \
        boost::wrap_stringstream().ref() << #predicate << "("\
        << BOOST_PRINT_PREDICATE_ARGS ## arg_list_size arg_list << ")", __FILE__, __LINE__)
/**/

#define BOOST_REQUIRE_PREDICATE( predicate, arg_list_size, arg_list ) \
    boost::test_toolbox::detail::test_and_throw_impl(predicate, BOOST_PLACE_PREDICATE_ARGS ## arg_list_size arg_list, \
        boost::wrap_stringstream().ref() << #predicate << "("\
        << BOOST_PRINT_PREDICATE_ARGS ## arg_list_size arg_list << ")", __FILE__, __LINE__)
/**/

#define BOOST_ERROR(message_) BOOST_CHECK_MESSAGE( false, message_ )

#define BOOST_FAIL(message_) BOOST_REQUIRE_MESSAGE( false, message_ )

#define BOOST_CHECK_THROW( statement, exception )                                               \
    try { statement; BOOST_ERROR( "exception "#exception" is expected" ); }                     \
    catch( exception const& ) {                                                                 \
        BOOST_CHECK_MESSAGE( true, "exception "#exception" is caught" );                        \
    }                                                                                           \
/**/

#define BOOST_CHECK_EXCEPTION( statement, exception, predicate )                                \
    try { statement; BOOST_ERROR( "exception "#exception" is expected" ); }                     \
    catch( exception const& ex ) {                                                              \
        BOOST_CHECK_MESSAGE( predicate( ex ), "incorrect exception "#exception" is caught" );   \
    }                                                                                           \
/**/

#define BOOST_IGNORE_CHECK( e ) true

#define BOOST_CHECK_NO_THROW( statement ) \
    try { statement; BOOST_CHECK_MESSAGE( true, "no exceptions was thrown by "#statement ); } \
    catch( ... ) { \
        BOOST_ERROR( "exception was thrown by "#statement ); \
    }
/**/

#define BOOST_CHECK_EQUAL_COLLECTIONS( left_begin_, left_end_, right_begin_ ) \
    boost::test_toolbox::detail::equal_and_continue_impl( (left_begin_), (left_end_), (right_begin_),\
        boost::wrap_stringstream().ref() << \
            "{" #left_begin_ ", " #left_end_ "}" " == {" #right_begin_ ", ...}", __FILE__, __LINE__)
/**/

#define BOOST_IS_DEFINED(symb) boost::test_toolbox::detail::is_defined_impl( #symb, BOOST_STRINGIZE(= symb) )

// ***************************** //
// helper macros

#define BOOST_PLACE_PREDICATE_ARGS1( first_ ) first_
#define BOOST_PLACE_PREDICATE_ARGS2( first_, second_ ) first_, second_

#define BOOST_PRINT_PREDICATE_ARGS1( first_ ) #first_
#define BOOST_PRINT_PREDICATE_ARGS2( first_, second_ ) #first_ << ", " << #second_

// ***************************** //
// depricated interface

#define BOOST_TEST(predicate)            BOOST_CHECK(predicate)
#define BOOST_CRITICAL_TEST(predicate)   BOOST_REQUIRE(predicate)
#define BOOST_CRITICAL_ERROR(message_)   BOOST_FAIL(message_)

#ifdef BOOST_MSVC
# pragma warning(push)
# pragma warning(disable: 4511) // copy constructor could not be generated
# pragma warning(disable: 4512) // assignment operator could not be generated
#endif

namespace boost {

namespace test_toolbox {

// ************************************************************************** //
// **************            extended_predicate_value          ************** //
// ************************************************************************** //

class extended_predicate_value {
public:
    // Constructor
    extended_predicate_value( bool predicate_value_ )
    : p_predicate_value( predicate_value_ ), p_message( new wrap_stringstream ) {}

    extended_predicate_value( extended_predicate_value const& rhs )
    : p_predicate_value( rhs.p_predicate_value.get() ), 
      p_message( rhs.p_message )                    {}

    bool        operator!() const                   { return !p_predicate_value.get(); }
    void        operator=( bool predicate_value_ )  { p_predicate_value.value = predicate_value_; }

    BOOST_READONLY_PROPERTY( bool, 1, (extended_predicate_value) )
                p_predicate_value;
    boost::shared_ptr<wrap_stringstream>
                p_message;
};

namespace detail {

using unit_test_framework::c_string_literal;

// ************************************************************************** //
// **************                test_tool_failed              ************** //
// ************************************************************************** //

// exception used to implement critical checks

struct test_tool_failed : public std::exception {
};

// ************************************************************************** //
// **************               log print helper               ************** //
// ************************************************************************** //

template<typename T>
struct print_log_value {
    void    operator()( std::ostream& ostr, T const& t )
    {
        ostr << t; // by default print the value
    }
};

//____________________________________________________________________________//

template<>
struct print_log_value<char> {
    void    operator()( std::ostream& ostr, char t );
};

//____________________________________________________________________________//

template<>
struct print_log_value<unsigned char> {
    void    operator()( std::ostream& ostr, unsigned char t );
};

//____________________________________________________________________________//

#define BOOST_TEST_DONT_PRINT_LOG_VALUE( the_type )                 \
namespace boost { namespace test_toolbox { namespace detail {       \
template<>                                                          \
struct print_log_value<the_type > {                                 \
    void operator()( std::ostream& ostr, the_type const& t ) {}     \
};                                                                  \
}}}                                                                 \
/**/

//____________________________________________________________________________//

template<typename T>
struct print_helper {
    explicit    print_helper( T const& t ) : m_t( t ) {}

    T const&    m_t;
};

//____________________________________________________________________________//

template<typename T>
inline std::ostream& 
operator<<( std::ostream& ostr, print_helper<T> const& ph )
{
    print_log_value<T>()( ostr, ph.m_t );

    return ostr;
}

//____________________________________________________________________________//

// ************************************************************************** //
// **************            TOOL BOX Implementation           ************** //
// ************************************************************************** //

void
checkpoint_impl( wrap_stringstream& message_, c_string_literal file_name_, std::size_t line_num_ );

//____________________________________________________________________________//

void
message_impl( wrap_stringstream& message_, c_string_literal file_name_, std::size_t line_num_ );

//____________________________________________________________________________//

// ************************************* //

void
warn_and_continue_impl( bool predicate_, wrap_stringstream& message_,
                        c_string_literal file_name_, std::size_t line_num_,
                        bool add_fail_pass_ = true );

//____________________________________________________________________________//

void
warn_and_continue_impl( extended_predicate_value const& v_, wrap_stringstream& message_,
                        c_string_literal file_name_, std::size_t line_num_,
                        bool add_fail_pass_ = true );

//____________________________________________________________________________//

// ************************************* //

bool  // return true if error detected
test_and_continue_impl( bool predicate_, wrap_stringstream& message_,
                        c_string_literal file_name_, std::size_t line_num_,
                        bool add_fail_pass_ = true,
                        unit_test_framework::log_level log_level_ = unit_test_framework::log_all_errors );
void
test_and_throw_impl   ( bool predicate_, wrap_stringstream& message_,
                        c_string_literal file_name_, std::size_t line_num_,
                        bool add_fail_pass_ = true,
                        unit_test_framework::log_level log_level_ = unit_test_framework::log_fatal_errors );

//____________________________________________________________________________//

bool
test_and_continue_impl( extended_predicate_value const& v_, wrap_stringstream& message_,
                        c_string_literal file_name_, std::size_t line_num_,
                        bool add_fail_pass_ = true,
                        unit_test_framework::log_level log_level_ = unit_test_framework::log_all_errors );

//____________________________________________________________________________//

// Borland bug workaround
#if BOOST_WORKAROUND(__BORLANDC__, <= 0x570)
inline bool
test_and_continue_impl( void* ptr, wrap_stringstream& message_,
                        c_string_literal file_name_, std::size_t line_num_,
                        bool add_fail_pass_ = true,
                        unit_test_framework::log_level log_level_ = unit_test_framework::log_all_errors )
{
    return test_and_continue_impl( !!ptr, message_, file_name_, line_num_, add_fail_pass_, log_level_ );
}
#endif

//____________________________________________________________________________//

void
test_and_throw_impl   ( extended_predicate_value const& v_, wrap_stringstream& message_,
                        c_string_literal file_name_, std::size_t line_num_,
                        bool add_fail_pass_ = true,
                        unit_test_framework::log_level log_level_ = unit_test_framework::log_fatal_errors );

//____________________________________________________________________________//

template<typename ArgType, typename Predicate>
inline bool
test_and_continue_impl( Predicate const& pred_, ArgType const& arg_,
                        wrap_stringstream& message_,
                        c_string_literal file_name_, std::size_t line_num_,
                        unit_test_framework::log_level log_level_ = unit_test_framework::log_all_errors )
{
    extended_predicate_value predicate( pred_( arg_ ) );

    if( !predicate ) {
        return test_and_continue_impl( predicate,
                                       wrap_stringstream().ref() << "test " << message_ << " failed for " 
                                                                 << print_helper<ArgType>( arg_ ),
                                       file_name_, line_num_, false, log_level_ );
    }

    return test_and_continue_impl( predicate, message_, file_name_, line_num_, true, log_level_ );
}

//____________________________________________________________________________//

template<typename ArgType, typename Predicate>
inline void
test_and_throw_impl   ( Predicate const& pred_, ArgType const& arg_,
                        wrap_stringstream& message_,
                        c_string_literal file_name_, std::size_t line_num_,
                        unit_test_framework::log_level log_level_ = unit_test_framework::log_fatal_errors )
{
    if( test_and_continue_impl( arg_, pred_, message_, file_name_, line_num_, log_level_ ) ) {
        throw test_tool_failed(); // error already reported by test_and_continue_impl
    }
}

//____________________________________________________________________________//

template<typename First, typename Second, typename Predicate>
inline bool
test_and_continue_impl( Predicate const& pred_, First const& first_, Second const& second_,
                        wrap_stringstream& message_,
                        c_string_literal file_name_, std::size_t line_num_,
                        unit_test_framework::log_level log_level_ = unit_test_framework::log_all_errors )
{
    extended_predicate_value predicate( pred_( first_, second_ ) );

    if( !predicate ) {
        return test_and_continue_impl( predicate,
            wrap_stringstream().ref() << "test " << message_ 
                                      << " failed for (" << print_helper<First>( first_ ) << ", " 
                                                         << print_helper<Second>( second_ ) << ")",
            file_name_, line_num_, false, log_level_ );
    }

    return test_and_continue_impl( predicate, message_, file_name_, line_num_, true, log_level_ );
}

//____________________________________________________________________________//

template<typename First, typename Second, typename Predicate>
inline void
test_and_throw_impl( First const& first_, Second const& second_, Predicate const& pred_,
                     wrap_stringstream& message_, c_string_literal file_name_, std::size_t line_num_,
                     unit_test_framework::log_level log_level_ = unit_test_framework::log_fatal_errors )
{
    if( test_and_continue_impl( first_, second_, pred_, message_, file_name_, line_num_, log_level_ ) ) {
        throw test_tool_failed(); // error already reported by test_and_continue_impl
    }
}

//____________________________________________________________________________//

// ************************************* //

bool
equal_and_continue_impl( c_string_literal left_, c_string_literal right_, wrap_stringstream& message_,
                         c_string_literal file_name_, std::size_t line_num_,
                         unit_test_framework::log_level log_level_ = unit_test_framework::log_all_errors );

//____________________________________________________________________________//

template <class Left, class Right>
inline bool
equal_and_continue_impl( Left const& left_, Right const& right_,
                         wrap_stringstream& message_, c_string_literal file_name_, std::size_t line_num_,
                         unit_test_framework::log_level log_level_ = unit_test_framework::log_all_errors,
                         std::size_t pos = (std::size_t)-1 )
{
    extended_predicate_value predicate( left_ == right_ );

    if( !predicate ) {
        wrap_stringstream error_message;
        error_message.ref() << "test " << message_ << " failed";

        if( pos != (std::size_t)-1 )
            error_message.ref() <<  " in a position " << pos;

        error_message.ref() << " [" 
                            << print_helper<Left>( left_ )   << " != " 
                            << print_helper<Right>( right_ ) << "]";

        return test_and_continue_impl( predicate, error_message, file_name_, line_num_, false, log_level_ );
    }

    return test_and_continue_impl( predicate, wrap_stringstream().ref() << message_, file_name_, line_num_, true, log_level_ );
    //----------------------------------------------^ this is added to prevent message_ corruption when reused by collection comparison
}

//____________________________________________________________________________//

template <class Left, class Right>
inline void
equal_and_continue_impl( Left left_begin_, Left left_end_, Right right_begin_,
                         wrap_stringstream& message_,
                         c_string_literal file_name_, std::size_t line_num_,
                         unit_test_framework::log_level log_level_ = unit_test_framework::log_all_errors )
{
    std::size_t pos = 0;
    for( ; left_begin_ != left_end_; ++left_begin_, ++right_begin_, ++pos )
        equal_and_continue_impl( *left_begin_, *right_begin_, message_, file_name_, line_num_, log_level_, pos );
}

//____________________________________________________________________________//

// ************************************* //

template<typename FPT, typename PersentType>
inline bool
compare_and_continue_impl( FPT left_, FPT right_, PersentType tolerance_,
                           c_string_literal left_text_, c_string_literal right_text_,
                           c_string_literal file_name_, std::size_t line_num_,
                           unit_test_framework::log_level log_level_ = unit_test_framework::log_all_errors )
{
    extended_predicate_value predicate( check_is_closed( left_, right_, tolerance_ ) );

    if( !predicate ) {
        return test_and_continue_impl( predicate,
            wrap_stringstream().ref() << "difference between " << left_text_ << "{" << print_helper<FPT>( left_ ) << "}" 
                                      << " and " << right_text_ << "{" << print_helper<FPT>( right_ ) << "}" 
                                      << " exceeds " << print_helper<PersentType>( tolerance_ ) << "%",
            file_name_, line_num_, false, log_level_ );
    }

    return test_and_continue_impl( predicate, 
        wrap_stringstream().ref() << "difference between " << left_text_ << "{" << print_helper<FPT>( left_ ) << "}" 
                                  << " and " << right_text_ << "{" << print_helper<FPT>( right_ ) << "}" 
                                  << " does not exceeds " << print_helper<PersentType>( tolerance_ ) << "%",
        file_name_, line_num_, true, log_level_ );
}

//____________________________________________________________________________//

template <class Left, class Right>
inline void
bitwise_equal_and_continue_impl( Left const& left_, Right const& right_,
                                 wrap_stringstream& message_, char const* file_name_, std::size_t line_num_,
                                 unit_test_framework::log_level log_level_ = unit_test_framework::log_all_errors )
{
    std::size_t left_bit_size  = sizeof(Left)*CHAR_BIT;
    std::size_t right_bit_size = sizeof(Right)*CHAR_BIT;

    static Left const L1( 1 );
    static Right const R1( 1 );

    if( left_bit_size != right_bit_size )
        warn_and_continue_impl( false, wrap_stringstream().ref() << message_ << ": operands bit sizes does not coinside", 
                                file_name_, line_num_, false );

    std::size_t total_bits = left_bit_size < right_bit_size ? left_bit_size : right_bit_size;

    for( std::size_t counter = 0; counter < total_bits; ++counter ) {
        bool predicate = ( left_ & ( L1 << counter ) ) == ( right_ & ( R1 << counter ) );

        test_and_continue_impl( predicate, wrap_stringstream().ref() << message_.str() << " in the position " << counter,
                                file_name_, line_num_, true, log_level_ );
    }
}

//____________________________________________________________________________//

// ************************************* //

bool
is_defined_impl( c_string_literal symbol_name_, c_string_literal symbol_value_ );

//____________________________________________________________________________//

} // namespace detail

// ************************************************************************** //
// **************               output_test_stream             ************** //
// ************************************************************************** //

// class to be used to simplify testing of ostream print functions

class output_test_stream : public 
#ifdef BOOST_NO_STRINGSTREAM
    std::ostrstream
#else
    std::ostringstream
#endif // BOOST_NO_STRINGSTREAM
{
    typedef extended_predicate_value                result_type;
    typedef unit_test_framework::c_string_literal   c_string_literal;
public:
    // Constructor
    explicit        output_test_stream( std::string const&  pattern_file_name = std::string(),
                                        bool                match_or_save     = true );
    explicit        output_test_stream( c_string_literal    pattern_file_name,
                                        bool                match_or_save     = true );

    // Destructor
    ~output_test_stream();

    // checking function
    result_type     is_empty( bool flush_stream_ = true );
    result_type     check_length( std::size_t length_, bool flush_stream_ = true );
    result_type     is_equal( c_string_literal arg_, bool flush_stream_ = true );
    result_type     is_equal( std::string const& arg_, bool flush_stream_ = true );
    result_type     is_equal( c_string_literal arg_, std::size_t n_, bool flush_stream_ = true );
    result_type     match_pattern( bool flush_stream_ = true );

    // helper function
    void            flush();
    std::size_t     length();

private:
    void            sync();

    struct Impl;
    boost::shared_ptr<Impl> m_pimpl;
};

} // namespace test_toolbox

} // namespace boost

#ifdef BOOST_MSVC
# pragma warning(default: 4511) // copy constructor could not be generated
# pragma warning(default: 4512) // assignment operator could not be generated
# pragma warning(pop)
#endif

// ***************************************************************************
//  Revision History :
//
//  $Log: test_tools.hpp,v $
//  Revision 1.35.2.1  2004/01/06 13:33:28  johnmaddock
//  merged changes from main branch
//
//  Revision 1.36  2004/01/05 11:56:25  johnmaddock
//  Borland specific workaround needs to be inline to prevent linker errors, and is unneeded for version 6 of the compiler.
//
//  Revision 1.35  2003/12/01 00:41:56  rogeeff
//  prerelease cleaning
//

// ***************************************************************************

#endif // BOOST_TEST_TOOLS_HPP
