text: /*
   *  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( ReporterConfig const& config ):
              StreamingReporterBase( config ) {
              m_reporterPrefs.shouldReportAllAssertions = true;
          }
  
          ~TAPReporter() override;
  
          static std::string getDescription() {
              return "Reports test results in TAP format, suitable for test harnesses";
          }
  
          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;
                  }
  
                  const auto itEnd = messages.cend();
                  const auto N = static_cast<std::size_t>( std::distance( itMessage, itEnd ) );
  
                  {
                      Colour colourGuard( colour );
                      stream << " with " << pluralise( N, "message" ) << ":";
                  }
  
                  while( 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";
                          }
                          continue;
                      }
                      ++itMessage;
                  }
              }
  
          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 {
              stream << "1.." << totals.assertions.total();
              if( totals.testCases.total() == 0 ) {
                  stream << " # Skipped: No tests ran.";
              }
          }
      };
  
  #ifdef CATCH_IMPL
      TAPReporter::~TAPReporter() {}
  #endif
  
      CATCH_REGISTER_REPORTER( "tap", TAPReporter )
  
  } // end namespace Catch
  
  #endif // TWOBLUECUBES_CATCH_REPORTER_TAP_HPP_INCLUDED