A simple structure used to represent a test suite section.
struct Section
{
Catch::Counts assertions;
std::vector<Section> sections;
Section* parent{ nullptr };
std::string name;
double duration{ 0.0 };
bool printed{ false };
};
A simple structure used to represent a test suite.
struct Suite
{
Catch::Counts assertions;
std::vector<Section> sections;
std::string name;
decltype(std::chrono::high_resolution_clock::now()) start{ std::chrono::high_resolution_clock::now() };
decltype(std::chrono::high_resolution_clock::now()) end;
[[nodiscard]] std::string filename() const
{
return std::format( "{}.html", boost::algorithm::replace_all_copy( name, " ", "-" ) );
}
[[nodiscard]] std::chrono::nanoseconds duration() const
{
return end - start;
}
};
Over-ridden implementation of the testRunStarting method.
void testRunStarting( const Catch::TestRunInfo& info ) override
{
StreamingReporterBase::testRunStarting( info );
suites.reserve( 16 );
seed = Catch::getSeed();
start = std::chrono::high_resolution_clock::now();
startTime = std::chrono::system_clock::now();
}
Over-ridden implementation of the testCaseStarting method.
void testCaseStarting( const Catch::TestCaseInfo& info ) override
{
StreamingReporterBase::testCaseStarting( info );
currentSection = nullptr;
suites.push_back( phr::create<phr::Suite>( info.name ) );
std::println( "\033[1;34m{}\033[0m", info.name );
}
Over-ridden implementation of the sectionStarting method.
void sectionStarting( const Catch::SectionInfo& info ) override
{
StreamingReporterBase::sectionStarting( info );
auto test = phr::create<phr::Section>( info.name );
test.parent = currentSection;
if ( test.name == suites.back().name ) return;
const auto add = [this]( std::vector<phr::Section>& sections, phr::Section&& sec )
{
if ( auto iter = ranges::find_if( sections, [&sec]( const auto& tc ) { return tc.name == sec.name; } );
iter == ranges::end( sections ) )
{
sections.push_back( std::move( sec ) );
currentSection = §ions.back();
}
else
{
currentSection = iter.base();
}
};
add( currentSection ? currentSection->sections : suites.back().sections, std::move( test ) );
}
Over-ridden implementation of the sectionEnded method.
void sectionEnded( const Catch::SectionStats& stats ) override
{
StreamingReporterBase::sectionEnded( stats );
if ( !currentSection ) return;
if ( const auto sec = phr::create<phr::Section>( stats.sectionInfo.name ); sec.name == suites.back().name ) return;
currentSection->assertions.passed += stats.assertions.passed;
currentSection->assertions.failed += stats.assertions.failed;
currentSection->assertions.failedButOk += stats.assertions.failedButOk;
currentSection->assertions.skipped += stats.assertions.skipped;
currentSection->duration += stats.durationInSeconds;
const auto indent = []( const phr::Section& sec ) -> std::string
{
auto indent = std::string{};
indent.reserve( 16 );
indent.append( 2, ' ' );
auto root = sec.parent;
while ( root )
{
indent.append( 2, ' ' );
root = root->parent;
}
return indent;
};
const auto style = []( const phr::Section& sec ) -> std::string
{
if ( sec.assertions.allPassed() ) return "\033[36m";
return "\033[33m";
};
const auto symbol = [] ( const phr::Section& sec ) -> std::string
{
return sec.assertions.allPassed() ? "✅" : "❌";
};
auto messages = std::vector<std::string>{};
messages.reserve( 8 );
if ( !currentSection->printed )
{
messages.push_back( std::format( "{}{}{} {}\033[0m", indent( *currentSection ), style( *currentSection ), symbol( *currentSection ), currentSection->name ) );
currentSection->printed = true;
}
auto root = currentSection->parent;
while ( root && !root->printed )
{
messages.push_back( std::format( "{}{}{} {}", indent( *root ), style( *root ), symbol( *root ), root->name ) );
root->printed = true;
root = root->parent;
}
for ( const auto& msg : messages | ranges::views::reverse ) std::println( "{}", msg );
currentSection = currentSection->parent;
}
Over-ridden implementation of the testCaseEnded method.
void testCaseEnded( const Catch::TestCaseStats& stats ) override
{
currentSection = nullptr;
if ( !stats.testInfo ) return;
auto& suite = suites.back();
suite.assertions.passed += stats.totals.assertions.passed;
suite.assertions.failed += stats.totals.assertions.failed;
suite.assertions.failedButOk += stats.totals.assertions.failedButOk;
suite.assertions.skipped += stats.totals.assertions.skipped;
suite.end = std::chrono::high_resolution_clock::now();
std::println( "\033[1;34m{} \033[0m\033[1m({} seconds)\033[0m", suite.name, std::chrono::duration_cast<std::chrono::duration<double>>( suite.duration() ).count() );
std::println( "\033[1;34m Assertions - \033[0m\033[1;32mPassed: {}; \033[0m\033[1;31mFailed: {}, \033[0m\033[1;33mFailedOk: {}, \033[0m\033[1;35mSkipped: {}, \033[0m\033[1mTotal: {}\033[0m",
suite.assertions.passed, suite.assertions.failed, suite.assertions.failedButOk,
suite.assertions.skipped, suite.assertions.total() );
}
Over-ridden implementation of the testRunEnded method.
void testRunEnded( const Catch::TestRunStats& stats ) override
{
StreamingReporterBase::testRunEnded( stats );
std::println( "\033[1;34m{}\033[0m", stats.runInfo.name.data() );
std::println( "\033[1;34m Test Cases - \033[0m\033[1;32mPassed: {}; \033[0m\033[1;31mFailed: {}, \033[0m\033[1;33mFailedOk: {}, \033[0m\033[1;35mSkipped: {}, \033[0m\033[1mTotal: {}\033[0m",
stats.totals.testCases.passed, stats.totals.testCases.failed, stats.totals.testCases.failedButOk,
stats.totals.testCases.skipped, stats.totals.testCases.total() );
std::println( "\033[1;34m Assertions - \033[0m\033[1;32mPassed: {}; \033[0m\033[1;31mFailed: {}, \033[0m\033[1;33mFailedOk: {}, \033[0m\033[1;35mSkipped: {}; \033[0m\033[1mTotal: {}\033[0m",
stats.totals.assertions.passed, stats.totals.assertions.failed, stats.totals.assertions.failedButOk,
stats.totals.assertions.skipped, stats.totals.assertions.total() );
std::println( "\033[1;34m Duration - \033[0m\033[1m{} \033[0m\033[1;34mseconds\033[0m", std::chrono::duration_cast<std::chrono::duration<double>>( std::chrono::high_resolution_clock::now() - start ).count() );
phr::generate( suites, "test-results", startTime );
}