Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/include
diff options
context:
space:
mode:
authorColton Wolkins (Ogre)2015-10-15 00:54:37 -0600
committerMartin Hořeňovský2017-02-22 11:35:20 +0100
commita63ce953a0a8ecb8fc36d6a8e5ab007c68fa819d (patch)
tree034e318724dc081ac1d2f3ee6124422bb289971b /include
parentb753f05d745a1fb505d3ca0de3a131235727f19a (diff)
downloadcatch3-a63ce953a0a8ecb8fc36d6a8e5ab007c68fa819d.tar.gz
Add TAP reporter
This is a hackish attempt to add a TAP reporter (see philsquared/Catch#309 ) by following the TAP 12 specification <http://testanything.org/tap-specification.html>. I'm unsure how well I did in following the spec or with following good C++ guidelines. Comments are appreciated. Signed-off-by: Colton Wolkins (Ogre) <frostyfrog2@gmail.com>
Diffstat (limited to 'include')
-rw-r--r--include/reporters/catch_reporter_tap.hpp309
1 files changed, 309 insertions, 0 deletions
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 <algorithm>
+
+#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<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;
+ AssertionStats const& stats;
+ AssertionResult const& result;
+ std::vector<MessageInfo> messages;
+ std::vector<MessageInfo>::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