/* * Created by Phil on 5/12/2012. * Copyright 2012 Two Blue Cubes Ltd. All rights reserved. * * 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) */ #ifndef TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED #define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED #include "../internal/catch_interfaces_reporter.h" #include "../internal/catch_reporter_registrars.hpp" #include "../internal/catch_console_colour.hpp" namespace Catch { struct ConsoleReporter : StreamingReporterBase { ConsoleReporter( ReporterConfig const& _config ) : StreamingReporterBase( _config ), m_headerPrinted( false ), m_atLeastOneTestCasePrinted( false ) {} virtual ~ConsoleReporter(); static std::string getDescription() { return "Reports test results as plain lines of text"; } virtual ReporterPreferences getPreferences() const { ReporterPreferences prefs; prefs.shouldRedirectStdOut = false; return prefs; } virtual void noMatchingTestCases( std::string const& spec ) { stream << "No test cases matched '" << spec << "'" << std::endl; } virtual void assertionStarting( AssertionInfo const& ) { } virtual void assertionEnded( AssertionStats const& _assertionStats ) { AssertionResult const& result = _assertionStats.assertionResult; // Drop out if result was successful and we're not printing those if( !m_config->includeSuccessfulResults() && result.isOk() ) return; lazyPrint(); AssertionPrinter printer( stream, _assertionStats ); printer.print(); stream << std::endl; } virtual void sectionStarting( SectionInfo const& _sectionInfo ) { m_headerPrinted = false; StreamingReporterBase::sectionStarting( _sectionInfo ); } virtual void sectionEnded( SectionStats const& _sectionStats ) { if( _sectionStats.missingAssertions ) { lazyPrint(); Colour colour( Colour::ResultError ); stream << "\nNo assertions in section, '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; } m_headerPrinted = false; StreamingReporterBase::sectionEnded( _sectionStats ); } virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) { if( _testCaseStats.missingAssertions ) { lazyPrint(); Colour colour( Colour::ResultError ); stream << "\nNo assertions in test case, '" << _testCaseStats.testInfo.name << "'\n" << std::endl; } if( m_config->showTimings() && (_testCaseStats.timeSecs >= m_config->timingsThreshold()) ) { Colour colour( Colour::SecondaryText ); stream << "Time spent in test case '" << _testCaseStats.testInfo.name << "': [timing: " << _testCaseStats.timeSecs << " secs]." << std::endl; } StreamingReporterBase::testCaseEnded( _testCaseStats ); m_headerPrinted = false; } virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) { if( !unusedGroupInfo ) { printSummaryDivider(); stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; printTotals( _testGroupStats.totals ); stream << "\n" << std::endl; } if( m_config->showTimings() && (_testGroupStats.timeSecs >= m_config->timingsThreshold()) ) { Colour colour( Colour::SecondaryText ); stream << "Time spent in test group '" << _testGroupStats.groupInfo.name << "': [timing: " << _testGroupStats.timeSecs << " secs]." << std::endl; } StreamingReporterBase::testGroupEnded( _testGroupStats ); } virtual void testRunEnded( TestRunStats const& _testRunStats ) { if( m_atLeastOneTestCasePrinted ) printTotalsDivider(); printTotals( _testRunStats.totals ); if( m_config->showTimings() ) { stream << " [timing: " << _testRunStats.timeSecs << " secs.]"; } stream << "\n" << std::endl; StreamingReporterBase::testRunEnded( _testRunStats ); } private: class AssertionPrinter { void operator= ( AssertionPrinter const& ); public: AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats ) : stream( _stream ), stats( _stats ), result( _stats.assertionResult ), colour( Colour::None ), message( result.getMessage() ), messages( _stats.infoMessages ) { switch( result.getResultType() ) { case ResultWas::Ok: colour = Colour::Success; passOrFail = "PASSED"; //if( result.hasMessage() ) if( _stats.infoMessages.size() == 1 ) messageLabel = "with message"; if( _stats.infoMessages.size() > 1 ) messageLabel = "with messages"; break; case ResultWas::ExpressionFailed: if( result.isOk() ) { colour = Colour::Success; passOrFail = "FAILED - but was ok"; } else { colour = Colour::Error; passOrFail = "FAILED"; } if( _stats.infoMessages.size() == 1 ) messageLabel = "with message"; if( _stats.infoMessages.size() > 1 ) messageLabel = "with messages"; break; case ResultWas::ThrewException: colour = Colour::Error; passOrFail = "FAILED"; messageLabel = "due to unexpected exception with message"; break; case ResultWas::DidntThrowException: colour = Colour::Error; passOrFail = "FAILED"; messageLabel = "because no exception was thrown where one was expected"; break; case ResultWas::Info: messageLabel = "info"; break; case ResultWas::Warning: messageLabel = "warning"; break; case ResultWas::ExplicitFailure: passOrFail = "FAILED"; colour = Colour::Error; if( _stats.infoMessages.size() == 1 ) messageLabel = "explicitly with message"; if( _stats.infoMessages.size() > 1 ) messageLabel = "explicitly with messages"; break; // These cases are here to prevent compiler warnings case ResultWas::Unknown: case ResultWas::FailureBit: case ResultWas::Exception: passOrFail = "** internal error **"; colour = Colour::Error; break; } } void print() const { printSourceInfo(); if( stats.totals.assertions.total() > 0 ) { if( result.isOk() ) stream << "\n"; printResultType(); printOriginalExpression(); printReconstructedExpression(); } else { stream << "\n"; } printMessage(); } private: void printResultType() const { if( !passOrFail.empty() ) { Colour colourGuard( colour ); stream << passOrFail << ":\n"; } } void printOriginalExpression() const { if( result.hasExpression() ) { Colour colourGuard( Colour::OriginalExpression ); stream << " "; stream << result.getExpressionInMacro(); stream << "\n"; } } void printReconstructedExpression() const { if( result.hasExpandedExpression() ) { stream << "with expansion:\n"; Colour colourGuard( Colour::ReconstructedExpression ); stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << "\n"; } } void printMessage() const { if( !messageLabel.empty() ) stream << messageLabel << ":" << "\n"; for( std::vector::const_iterator it = messages.begin(), itEnd = messages.end(); it != itEnd; ++it ) { stream << Text( it->message, TextAttributes().setIndent(2) ) << "\n"; } } void printSourceInfo() const { Colour colourGuard( Colour::FileName ); stream << result.getSourceInfo() << ": "; } std::ostream& stream; AssertionStats const& stats; AssertionResult const& result; Colour::Code colour; std::string passOrFail; std::string messageLabel; std::string message; std::vector messages; }; void lazyPrint() { if( testRunInfo ) lazyPrintRunInfo(); if( unusedGroupInfo ) lazyPrintGroupInfo(); if( !m_headerPrinted ) { printTestCaseAndSectionHeader(); m_headerPrinted = true; } m_atLeastOneTestCasePrinted = true; } void lazyPrintRunInfo() { stream << "\n" << getTildes() << "\n"; Colour colour( Colour::SecondaryText ); stream << testRunInfo->name << " is a Catch v" << libraryVersion.majorVersion << "." << libraryVersion.minorVersion << " b" << libraryVersion.buildNumber; if( libraryVersion.branchName != "master" ) stream << " (" << libraryVersion.branchName << ")"; stream << " host application.\n" << "Run with -? for options\n\n"; testRunInfo.reset(); } void lazyPrintGroupInfo() { if( !unusedGroupInfo->name.empty() && unusedGroupInfo->groupsCounts > 1 ) { printClosedHeader( "Group: " + unusedGroupInfo->name ); unusedGroupInfo.reset(); } } void printTestCaseAndSectionHeader() { printOpenHeader( unusedTestCaseInfo->name ); if( currentSectionInfo ) { Colour colourGuard( Colour::Headers ); std::vector sections; for( ThreadedSectionInfo* section = currentSectionInfo.get(); section; section = section->parent ) sections.push_back( section ); // Sections if( !sections.empty() ) { typedef std::vector::const_reverse_iterator It; for( It it = sections.rbegin(), itEnd = sections.rend(); it != itEnd; ++it ) printHeaderString( (*it)->name, 2 ); } } SourceLineInfo lineInfo = currentSectionInfo ? currentSectionInfo->lineInfo : unusedTestCaseInfo->lineInfo; if( !lineInfo.empty() ){ stream << getDashes() << "\n"; Colour colourGuard( Colour::FileName ); stream << lineInfo << "\n"; } stream << getDots() << "\n" << std::endl; } void printClosedHeader( std::string const& _name ) { printOpenHeader( _name ); stream << getDots() << "\n"; } void printOpenHeader( std::string const& _name ) { stream << getDashes() << "\n"; { Colour colourGuard( Colour::Headers ); printHeaderString( _name ); } } // if string has a : in first line will set indent to follow it on // subsequent lines void printHeaderString( std::string const& _string, std::size_t indent = 0 ) { std::size_t i = _string.find( ": " ); if( i != std::string::npos ) i+=2; else i = 0; stream << Text( _string, TextAttributes() .setIndent( indent+i) .setInitialIndent( indent ) ) << "\n"; } void printTotals( const Totals& totals ) { if( totals.assertions.total() == 0 ) { stream << "No tests ran"; } else if( totals.assertions.failed ) { Colour colour( Colour::ResultError ); printCounts( "test case", totals.testCases ); if( totals.testCases.failed > 0 ) { stream << " ("; printCounts( "assertion", totals.assertions ); stream << ")"; } } else { Colour colour( Colour::ResultSuccess ); stream << "All tests passed (" << pluralise( totals.assertions.passed, "assertion" ) << " in " << pluralise( totals.testCases.passed, "test case" ) << ")"; } } void printCounts( std::string const& label, Counts const& counts ) { if( counts.total() == 1 ) { stream << "1 " << label << " - "; if( counts.failed ) stream << "failed"; else stream << "passed"; } else { stream << counts.total() << " " << label << "s "; if( counts.passed ) { if( counts.failed ) stream << "- " << counts.failed << " failed"; else if( counts.passed == 2 ) stream << "- both passed"; else stream << "- all passed"; } else { if( counts.failed == 2 ) stream << "- both failed"; else stream << "- all failed"; } } } void printTotalsDivider() { stream << getDoubleDashes() << "\n"; } void printSummaryDivider() { stream << getDashes() << "\n"; } static std::string const& getDashes() { static const std::string dashes( CATCH_CONFIG_CONSOLE_WIDTH-1, '-' ); return dashes; } static std::string const& getDots() { static const std::string dots( CATCH_CONFIG_CONSOLE_WIDTH-1, '.' ); return dots; } static std::string const& getDoubleDashes() { static const std::string doubleDashes( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' ); return doubleDashes; } static std::string const& getTildes() { static const std::string dots( CATCH_CONFIG_CONSOLE_WIDTH-1, '~' ); return dots; } private: bool m_headerPrinted; bool m_atLeastOneTestCasePrinted; }; INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) } // end namespace Catch #endif // TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED