diff --git a/include/reporters/catch_reporter_tap.hpp b/include/reporters/catch_reporter_tap.hpp new file mode 100644 index 00000000..b06e82d2 --- /dev/null +++ b/include/reporters/catch_reporter_tap.hpp @@ -0,0 +1,309 @@ +/* + * Created by Martin Moene on 2013-12-05. + * Copyright 2012 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 + +#include "catch_reporter_bases.hpp" +#include + +#include "../internal/catch_reporter_registrars.hpp" +#include "../internal/catch_console_colour.hpp" + +namespace Catch { + + struct TAPReporter : StreamingReporterBase { + + TAPReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ) + {} + + virtual ~TAPReporter(); + + static std::string getDescription() { + return "Reports test results in TAP format, suitable for test harneses"; + } + + 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; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages, ++counter ); + printer.print(); + stream << " # " << currentTestCaseInfo->name ; + + stream << std::endl; + return true; + } + + virtual void testRunEnded( TestRunStats const& _testRunStats ) { + printTotals( _testRunStats.totals ); + stream << "\n" << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + size_t counter = 0; + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages, size_t counter = 0 ) + : stream( _stream ) + , stats( _stats ) + , result( _stats.assertionResult ) + , messages( _stats.infoMessages ) + , itMessage( _stats.infoMessages.begin() ) + , printInfoMessages( _printInfoMessages ) + , counter(counter) + {} + + void print() { + //printSourceInfo(); + + itMessage = messages.begin(); + + switch( result.getResultType() ) { + case ResultWas::Ok: + printResultType( Colour::ResultSuccess, passedString() ); + printOriginalExpression(); + //printReconstructedExpression(); + if ( ! result.hasExpression() ) + printRemainingMessages( Colour::None ); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + //if( result.isOk() ) + // printResultType( Colour::ResultSuccess, failedString() + std::string( " # TODO" ) ); + //else + printResultType( Colour::Error, failedString() ); + printOriginalExpression(); + //printReconstructedExpression(); + if( result.isOk() ) + printIssue( " # TODO" ); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType( Colour::Error, failedString() ); + printIssue( "unexpected exception with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType( Colour::Error, failedString() ); + printIssue( "fatal error condition with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType( Colour::Error, failedString() ); + printIssue( "expected exception, got none" ); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType( Colour::None, "info" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType( Colour::None, "warning" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType( Colour::Error, 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( Colour::Error, "** internal error **" ); + break; + } + } + + private: + // Colour::LightGrey + + 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( Colour::FileName ); + stream << result.getSourceInfo() << ":"; + } + + void printResultType( Colour::Code colour, std::string passOrFail ) const { + if( !passOrFail.empty() ) { + { + //Colour colourGuard( colour ); + stream << passOrFail << " " << counter; + } + stream << " -"; + } + } + + void printIssue( std::string 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 yields compilation error: + std::vector::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast( 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; + AssertionStats const& stats; + AssertionResult const& result; + std::vector messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; + size_t counter; + }; + + // Colour, message variants: + // - white: No tests ran. + // - red: Failed [both/all] N test cases, failed [both/all] M assertions. + // - white: Passed [both/all] N test cases (no assertions). + // - red: Failed N tests cases, failed M assertions. + // - green: Passed [both/all] N tests cases with M assertions. + + std::string bothOrAll( std::size_t count ) const { + return count == 1 ? "" : count == 2 ? "both " : "all " ; + } + + void printTotals( const Totals& totals ) const { + if( totals.testCases.total() == 0 ) { + stream << "1..0 # Skipped: No tests ran."; + //stream << "No tests ran."; + } + else { + stream << "1.." << counter; + } + //else if( totals.testCases.failed == totals.testCases.total() ) { + // Colour colour( Colour::ResultError ); + // const std::string qualify_assertions_failed = + // totals.assertions.failed == totals.assertions.total() ? + // bothOrAll( totals.assertions.failed ) : ""; + // stream << + // "Failed " << bothOrAll( totals.testCases.failed ) + // << pluralise( totals.testCases.failed, "test case" ) << ", " + // "failed " << qualify_assertions_failed << + // pluralise( totals.assertions.failed, "assertion" ) << "."; + //} + //else if( totals.assertions.total() == 0 ) { + // stream << + // "Passed " << bothOrAll( totals.testCases.total() ) + // << pluralise( totals.testCases.total(), "test case" ) + // << " (no assertions)."; + //} + //else if( totals.assertions.failed ) { + // Colour colour( Colour::ResultError ); + // stream << + // "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " + // "failed " << pluralise( totals.assertions.failed, "assertion" ) << "."; + //} + //else { + // Colour colour( Colour::ResultSuccess ); + // stream << + // "Passed " << bothOrAll( totals.testCases.passed ) + // << pluralise( totals.testCases.passed, "test case" ) << + // " with " << pluralise( totals.assertions.passed, "assertion" ) << "."; + //} + } + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "tap", TAPReporter ) + +} // end namespace Catch + +#endif // TWOBLUECUBES_CATCH_REPORTER_TAP_HPP_INCLUDED