| /* |
| * Created by Colton Wolkins on 2015-08-15. |
| * Copyright 2015 Martin Moene. 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_TAP_HPP_INCLUDED |
| #define TWOBLUECUBES_CATCH_REPORTER_TAP_HPP_INCLUDED |
| |
| |
| // Don't #include any Catch headers here - we can assume they are already |
| // included before this header. |
| // This is not good practice in general but is necessary in this case so this |
| // file can be distributed as a single header that works with the main |
| // Catch single header. |
| |
| #include <algorithm> |
| |
| namespace Catch { |
| |
| struct TAPReporter : StreamingReporterBase<TAPReporter> { |
| |
| using StreamingReporterBase::StreamingReporterBase; |
| |
| ~TAPReporter() override; |
| |
| static std::string getDescription() { |
| return "Reports test results in TAP format, suitable for test harnesses"; |
| } |
| |
| ReporterPreferences getPreferences() const override { |
| return m_reporterPrefs; |
| } |
| |
| void noMatchingTestCases( std::string const& spec ) override { |
| stream << "# No test cases matched '" << spec << "'" << std::endl; |
| } |
| |
| void assertionStarting( AssertionInfo const& ) override {} |
| |
| bool assertionEnded( AssertionStats const& _assertionStats ) override { |
| ++counter; |
| |
| stream << "# " << currentTestCaseInfo->name << std::endl; |
| AssertionPrinter printer( stream, _assertionStats, counter ); |
| printer.print(); |
| |
| stream << std::endl; |
| return true; |
| } |
| |
| void testRunEnded( TestRunStats const& _testRunStats ) override { |
| printTotals( _testRunStats.totals ); |
| stream << "\n" << std::endl; |
| StreamingReporterBase::testRunEnded( _testRunStats ); |
| } |
| |
| private: |
| std::size_t counter = 0; |
| class AssertionPrinter { |
| public: |
| AssertionPrinter& operator= ( AssertionPrinter const& ) = delete; |
| AssertionPrinter( AssertionPrinter const& ) = delete; |
| AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, std::size_t _counter ) |
| : stream( _stream ) |
| , result( _stats.assertionResult ) |
| , messages( _stats.infoMessages ) |
| , itMessage( _stats.infoMessages.begin() ) |
| , printInfoMessages( true ) |
| , counter(_counter) |
| {} |
| |
| void print() { |
| itMessage = messages.begin(); |
| |
| switch( result.getResultType() ) { |
| case ResultWas::Ok: |
| printResultType( passedString() ); |
| printOriginalExpression(); |
| printReconstructedExpression(); |
| if ( ! result.hasExpression() ) |
| printRemainingMessages( Colour::None ); |
| else |
| printRemainingMessages(); |
| break; |
| case ResultWas::ExpressionFailed: |
| if (result.isOk()) { |
| printResultType(passedString()); |
| } else { |
| printResultType(failedString()); |
| } |
| printOriginalExpression(); |
| printReconstructedExpression(); |
| if (result.isOk()) { |
| printIssue(" # TODO"); |
| } |
| printRemainingMessages(); |
| break; |
| case ResultWas::ThrewException: |
| printResultType( failedString() ); |
| printIssue( "unexpected exception with message:" ); |
| printMessage(); |
| printExpressionWas(); |
| printRemainingMessages(); |
| break; |
| case ResultWas::FatalErrorCondition: |
| printResultType( failedString() ); |
| printIssue( "fatal error condition with message:" ); |
| printMessage(); |
| printExpressionWas(); |
| printRemainingMessages(); |
| break; |
| case ResultWas::DidntThrowException: |
| printResultType( failedString() ); |
| printIssue( "expected exception, got none" ); |
| printExpressionWas(); |
| printRemainingMessages(); |
| break; |
| case ResultWas::Info: |
| printResultType( "info" ); |
| printMessage(); |
| printRemainingMessages(); |
| break; |
| case ResultWas::Warning: |
| printResultType( "warning" ); |
| printMessage(); |
| printRemainingMessages(); |
| break; |
| case ResultWas::ExplicitFailure: |
| printResultType( failedString() ); |
| printIssue( "explicitly" ); |
| printRemainingMessages( Colour::None ); |
| break; |
| // These cases are here to prevent compiler warnings |
| case ResultWas::Unknown: |
| case ResultWas::FailureBit: |
| case ResultWas::Exception: |
| printResultType( "** internal error **" ); |
| break; |
| } |
| } |
| |
| private: |
| static Colour::Code dimColour() { return Colour::FileName; } |
| |
| static const char* failedString() { return "not ok"; } |
| static const char* passedString() { return "ok"; } |
| |
| void printSourceInfo() const { |
| Colour colourGuard( dimColour() ); |
| stream << result.getSourceInfo() << ":"; |
| } |
| |
| void printResultType( std::string const& passOrFail ) const { |
| if( !passOrFail.empty() ) { |
| stream << passOrFail << ' ' << counter << " -"; |
| } |
| } |
| |
| void printIssue( std::string const& issue ) const { |
| stream << " " << issue; |
| } |
| |
| void printExpressionWas() { |
| if( result.hasExpression() ) { |
| stream << ";"; |
| { |
| Colour colour( dimColour() ); |
| stream << " expression was:"; |
| } |
| printOriginalExpression(); |
| } |
| } |
| |
| void printOriginalExpression() const { |
| if( result.hasExpression() ) { |
| stream << " " << result.getExpression(); |
| } |
| } |
| |
| void printReconstructedExpression() const { |
| if( result.hasExpandedExpression() ) { |
| { |
| Colour colour( dimColour() ); |
| stream << " for: "; |
| } |
| std::string expr = result.getExpandedExpression(); |
| std::replace( expr.begin(), expr.end(), '\n', ' '); |
| stream << expr; |
| } |
| } |
| |
| void printMessage() { |
| if ( itMessage != messages.end() ) { |
| stream << " '" << itMessage->message << "'"; |
| ++itMessage; |
| } |
| } |
| |
| void printRemainingMessages( Colour::Code colour = dimColour() ) { |
| if (itMessage == messages.end()) { |
| return; |
| } |
| |
| // using messages.end() directly (or auto) yields compilation error: |
| std::vector<MessageInfo>::const_iterator itEnd = messages.end(); |
| const std::size_t N = static_cast<std::size_t>( std::distance( itMessage, itEnd ) ); |
| |
| { |
| Colour colourGuard( colour ); |
| stream << " with " << pluralise( N, "message" ) << ":"; |
| } |
| |
| for(; itMessage != itEnd; ) { |
| // If this assertion is a warning ignore any INFO messages |
| if( printInfoMessages || itMessage->type != ResultWas::Info ) { |
| stream << " '" << itMessage->message << "'"; |
| if ( ++itMessage != itEnd ) { |
| Colour colourGuard( dimColour() ); |
| stream << " and"; |
| } |
| } |
| } |
| } |
| |
| private: |
| std::ostream& stream; |
| AssertionResult const& result; |
| std::vector<MessageInfo> messages; |
| std::vector<MessageInfo>::const_iterator itMessage; |
| bool printInfoMessages; |
| std::size_t counter; |
| }; |
| |
| void printTotals( const Totals& totals ) const { |
| if( totals.testCases.total() == 0 ) { |
| stream << "1..0 # Skipped: No tests ran."; |
| } else { |
| stream << "1.." << counter; |
| } |
| } |
| }; |
| |
| #ifdef CATCH_IMPL |
| TAPReporter::~TAPReporter() {} |
| #endif |
| |
| CATCH_REGISTER_REPORTER( "tap", TAPReporter ) |
| |
| } // end namespace Catch |
| |
| #endif // TWOBLUECUBES_CATCH_REPORTER_TAP_HPP_INCLUDED |