| /* |
| * 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 bool 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 false; |
| |
| lazyPrint(); |
| |
| AssertionPrinter printer( stream, _assertionStats ); |
| printer.print(); |
| stream << std::endl; |
| return true; |
| } |
| |
| 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; |
| } |
| 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; |
| } |
| StreamingReporterBase::testGroupEnded( _testGroupStats ); |
| } |
| virtual void testRunEnded( TestRunStats const& _testRunStats ) { |
| if( m_atLeastOneTestCasePrinted ) |
| printTotalsDivider(); |
| printTotals( _testRunStats.totals ); |
| 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<MessageInfo>::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<MessageInfo> 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<ThreadedSectionInfo*> sections; |
| for( ThreadedSectionInfo* section = currentSectionInfo.get(); |
| section; |
| section = section->parent ) |
| sections.push_back( section ); |
| |
| // Sections |
| if( !sections.empty() ) { |
| typedef std::vector<ThreadedSectionInfo*>::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 |