static_assert( __cplusplus > 2020'99 ); #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Alepha::Hydrogen::Testing ::detail:: printDebugging_m { inline namespace exports { enum class OutputMode { All, Relaxed }; template< OutputMode outputMode, typename T > void streamDebugging( std::ostream &stream, const T &witness, const T &expected ); template< OutputMode outputMode, typename T > void printDebugging( const T &witness, const T &expected ); template< OutputMode outputMode, typename T > auto streamAdaptValue( const T &v ); } using namespace std::literals::string_literals; template< OutputMode outputMode, typename T > void streamValue( const T &v, std::ostream &oss ) { if constexpr( false ) ; // To keep the rest of the clauses regular else if constexpr( std::is_same_v< std::uint8_t, std::decay_t< T > > ) { oss << std::hex << std::setw( 2 ) << std::setfill( '0' ) << int( v ); } else if constexpr( std::is_same_v< bool, std::decay_t< T > > ) { oss << std::boolalpha << v; } else if constexpr( std::is_same_v< std::string, std::decay_t< T > > ) { oss << "(String with " << v.size() << " chars)"; oss << '\n' << R"(""")" << '\n'; for( const char ch: v ) { if( ch == '\n' ) oss << "\n"; else if( std::isalnum( ch ) or std::ispunct( ch ) or ( ch == ' ' ) ) oss << ch; else oss << "<\\0x" << std::hex << std::setw( 2 ) << std::setfill( '0' ) << unsigned( ch ) << '>'; } oss << '\n' << R"(""")"; } else if constexpr( Meta::is_ostreamable_v< T > ) { oss << v; } else if constexpr( Meta::is_optional_v< T > ) { if( not v.has_value() ) oss << ""; else streamValue< outputMode >( v.value(), oss ); } else if constexpr( Meta::is_sequence_v< T > ) { if constexpr( outputMode == OutputMode::Relaxed and not Meta::is_ostreamable_v< typename T::value_type > ) { oss << ""; } else { oss << Meta::sequence_kind_v< T > << "(" << v.size() << " elements):\n{" << std::endl; int index= 0; for( const auto &elem: v ) { oss << "\t" << index++ << ": "; streamValue< outputMode >( elem, oss ); oss << "," << std::endl; } oss << "}" << std::endl; } } else if constexpr( Meta::is_pair_v< T > ) { const auto &[ first, second ]= v; streamValue< outputMode >( std::tie( first, second ), oss ); } else if constexpr( Meta::is_tuple_v< T > ) { oss << '['; template_for( v ) <=[&oss, first= true]( const auto &elem ) mutable { if( not first ) oss << ", "; first= false; oss << std::endl; streamValue< outputMode >( elem, oss ); }; oss << std::endl << ']' << std::endl; } else if constexpr( std::is_same_v< T, TotalOrder > ) { if( false ) ; // For alignment else if( v == TotalOrder::less ) oss << "less"; else if( v == TotalOrder::equal ) oss << "equal"; else if( v == TotalOrder::greater ) oss << "greater"; else throw std::logic_error( "Impossible `TotalOrder` condition." ); } else { if constexpr( outputMode == OutputMode::Relaxed ) { oss << ""; } else static_assert( dependent_value< false, T >, "One of the types used in the testing table does not support stringification." ); } } template< OutputMode outputMode, typename T > struct Adaptor { const T &element; friend std::ostream & operator << ( std::ostream &os, const Adaptor &a ) { streamValue< outputMode >( a.element, os ); return os; } }; template< OutputMode outputMode, typename T > auto exports::streamAdaptValue( const T &v ) { return Adaptor< outputMode, std::decay_t< T > >{ v }; } inline void streamDebuggingForStrings( std::ostream &stream, const std::string &witness, const std::string &expected ) { const std::size_t amount= std::min( witness.size(), expected.size() ); if( witness.size() != expected.size() ) { stream << "Witness string size did not match the expected string size. Only mismatches found in the first " << amount << " characters will be printed." << std::endl; } for( int i= 0; i < amount; ++i ) { if( witness.at( i ) == expected.at( i ) ) continue; stream << "Mismatch at index: " << i << std::endl; stream << "witness: " << witness.at( i ) << std::endl; stream << "expected: " << expected.at( i ) << std::endl; } } template< OutputMode outputMode, typename T > void exports::streamDebugging( std::ostream &stream, const T &witness, const T &expected ) { if constexpr( std::is_same_v< std::string, std::decay_t< T > > ) { streamDebuggingForStrings( stream, witness, expected ); } else if constexpr( Meta::is_sequence_v< T > ) { if constexpr( std::is_same_v< std::string, typename T::value_type > ) { if( witness.size() == expected.size() ) for( std::size_t i= 0; i < witness.size(); ++i ) { if( witness.at( i ) != expected.at( i ) ) streamDebuggingForStrings( stream, witness.at( i ), expected.at( i ) ); } } else { if( witness.size() != expected.size() ) { stream << "Witness sequence size of " << witness.size() << " did not match the expected sequence size of " << expected.size() << std::endl; } auto next= std::make_pair( begin( witness ), begin( expected ) ); bool first= true; while( next.first != end( witness ) and next.second != end( expected ) ) { if( not first ) { stream << "Mismatch at witness index " << std::distance( begin( witness ), next.first ) << " and " << "expected index " << std::distance( begin( expected ), next.second ) << std::endl; ++next.first; ++next.second; } first= false; next= std::mismatch( next.first, end( witness ), next.second, end( expected ) ); } } } stream << std::endl << "computed: " << streamAdaptValue< outputMode >( witness ) << std::endl << "expected: " << streamAdaptValue< outputMode >( expected ) << std::endl << std::endl; } template< OutputMode outputMode, typename T > void exports::printDebugging( const T &witness, const T &expected ) { streamDebugging( std::cout, witness, expected ); } } namespace Alepha::Hydrogen::Testing::inline exports::inline printDebugging_m { using namespace detail::printDebugging_m::exports; }