1
0
forked from Alepha/Alepha
Files
Alepha/Testing/printDebugging.h

236 lines
6.8 KiB
C++

static_assert( __cplusplus > 2020'99 );
#pragma once
#include <Alepha/Alepha.h>
#include <cstdint>
#include <iostream>
#include <type_traits>
#include <iomanip>
#include <string>
#include <boost/core/demangle.hpp>
#include <boost/lexical_cast.hpp>
#include <Alepha/TotalOrder.h>
#include <Alepha/Console.h>
#include <Alepha/IOStreams/String.h>
#include <Alepha/Meta/product_type_decay.h>
#include <Alepha/Meta/sequence_kind.h>
#include <Alepha/Meta/is_vector.h>
#include <Alepha/Meta/is_optional.h>
#include <Alepha/Meta/is_streamable.h>
#include <Alepha/Meta/is_sequence.h>
#include <Alepha/Meta/is_product_type.h>
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 << "<EOL>\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 << "<noopt>";
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 << "<Unstreamable sequence of " << v.size() << " elements.>";
}
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 << "<Unstreamable object of type `" << typeid( v ).name() << "`>";
}
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;
}