1 /*
2  *  Created by Colton Wolkins on 2015-08-15.
3  *  Copyright 2015 Martin Moene. All rights reserved.
4  *
5  *  Distributed under the Boost Software License, Version 1.0. (See accompanying
6  *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7  */
8 #ifndef TWOBLUECUBES_CATCH_REPORTER_TAP_HPP_INCLUDED
9 #define TWOBLUECUBES_CATCH_REPORTER_TAP_HPP_INCLUDED
10 
11 
12 // Don't #include any Catch headers here - we can assume they are already
13 // included before this header.
14 // This is not good practice in general but is necessary in this case so this
15 // file can be distributed as a single header that works with the main
16 // Catch single header.
17 
18 #include <algorithm>
19 
20 namespace Catch {
21 
22     struct TAPReporter : StreamingReporterBase<TAPReporter> {
23 
24         using StreamingReporterBase::StreamingReporterBase;
25 
TAPReporterCatch::TAPReporter26         TAPReporter( ReporterConfig const& config ):
27             StreamingReporterBase( config ) {
28             m_reporterPrefs.shouldReportAllAssertions = true;
29         }
30 
31         ~TAPReporter() override;
32 
getDescriptionCatch::TAPReporter33         static std::string getDescription() {
34             return "Reports test results in TAP format, suitable for test harnesses";
35         }
36 
noMatchingTestCasesCatch::TAPReporter37         void noMatchingTestCases( std::string const& spec ) override {
38             stream << "# No test cases matched '" << spec << "'" << std::endl;
39         }
40 
assertionStartingCatch::TAPReporter41         void assertionStarting( AssertionInfo const& ) override {}
42 
assertionEndedCatch::TAPReporter43         bool assertionEnded( AssertionStats const& _assertionStats ) override {
44             ++counter;
45 
46             stream << "# " << currentTestCaseInfo->name << std::endl;
47             AssertionPrinter printer( stream, _assertionStats, counter );
48             printer.print();
49 
50             stream << std::endl;
51             return true;
52         }
53 
testRunEndedCatch::TAPReporter54         void testRunEnded( TestRunStats const& _testRunStats ) override {
55             printTotals( _testRunStats.totals );
56             stream << "\n" << std::endl;
57             StreamingReporterBase::testRunEnded( _testRunStats );
58         }
59 
60     private:
61         std::size_t counter = 0;
62         class AssertionPrinter {
63         public:
64             AssertionPrinter& operator= ( AssertionPrinter const& ) = delete;
65             AssertionPrinter( AssertionPrinter const& ) = delete;
AssertionPrinter(std::ostream & _stream,AssertionStats const & _stats,std::size_t _counter)66             AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, std::size_t _counter )
67             : stream( _stream )
68             , result( _stats.assertionResult )
69             , messages( _stats.infoMessages )
70             , itMessage( _stats.infoMessages.begin() )
71             , printInfoMessages( true )
72             , counter(_counter)
73             {}
74 
print()75             void print() {
76                 itMessage = messages.begin();
77 
78                 switch( result.getResultType() ) {
79                     case ResultWas::Ok:
80                         printResultType( passedString() );
81                         printOriginalExpression();
82                         printReconstructedExpression();
83                         if ( ! result.hasExpression() )
84                             printRemainingMessages( Colour::None );
85                         else
86                             printRemainingMessages();
87                         break;
88                     case ResultWas::ExpressionFailed:
89                         if (result.isOk()) {
90                             printResultType(passedString());
91                         } else {
92                             printResultType(failedString());
93                         }
94                         printOriginalExpression();
95                         printReconstructedExpression();
96                         if (result.isOk()) {
97                             printIssue(" # TODO");
98                         }
99                         printRemainingMessages();
100                         break;
101                     case ResultWas::ThrewException:
102                         printResultType( failedString() );
103                         printIssue( "unexpected exception with message:" );
104                         printMessage();
105                         printExpressionWas();
106                         printRemainingMessages();
107                         break;
108                     case ResultWas::FatalErrorCondition:
109                         printResultType( failedString() );
110                         printIssue( "fatal error condition with message:" );
111                         printMessage();
112                         printExpressionWas();
113                         printRemainingMessages();
114                         break;
115                     case ResultWas::DidntThrowException:
116                         printResultType( failedString() );
117                         printIssue( "expected exception, got none" );
118                         printExpressionWas();
119                         printRemainingMessages();
120                         break;
121                     case ResultWas::Info:
122                         printResultType( "info" );
123                         printMessage();
124                         printRemainingMessages();
125                         break;
126                     case ResultWas::Warning:
127                         printResultType( "warning" );
128                         printMessage();
129                         printRemainingMessages();
130                         break;
131                     case ResultWas::ExplicitFailure:
132                         printResultType( failedString() );
133                         printIssue( "explicitly" );
134                         printRemainingMessages( Colour::None );
135                         break;
136                     // These cases are here to prevent compiler warnings
137                     case ResultWas::Unknown:
138                     case ResultWas::FailureBit:
139                     case ResultWas::Exception:
140                         printResultType( "** internal error **" );
141                         break;
142                 }
143             }
144 
145         private:
dimColour()146             static Colour::Code dimColour() { return Colour::FileName; }
147 
failedString()148             static const char* failedString() { return "not ok"; }
passedString()149             static const char* passedString() { return "ok"; }
150 
printSourceInfo() const151             void printSourceInfo() const {
152                 Colour colourGuard( dimColour() );
153                 stream << result.getSourceInfo() << ":";
154             }
155 
printResultType(std::string const & passOrFail) const156             void printResultType( std::string const& passOrFail ) const {
157                 if( !passOrFail.empty() ) {
158                     stream << passOrFail << ' ' << counter << " -";
159                 }
160             }
161 
printIssue(std::string const & issue) const162             void printIssue( std::string const& issue ) const {
163                 stream << " " << issue;
164             }
165 
printExpressionWas()166             void printExpressionWas() {
167                 if( result.hasExpression() ) {
168                     stream << ";";
169                     {
170                         Colour colour( dimColour() );
171                         stream << " expression was:";
172                     }
173                     printOriginalExpression();
174                 }
175             }
176 
printOriginalExpression() const177             void printOriginalExpression() const {
178                 if( result.hasExpression() ) {
179                     stream << " " << result.getExpression();
180                 }
181             }
182 
printReconstructedExpression() const183             void printReconstructedExpression() const {
184                 if( result.hasExpandedExpression() ) {
185                     {
186                         Colour colour( dimColour() );
187                         stream << " for: ";
188                     }
189                     std::string expr = result.getExpandedExpression();
190                     std::replace( expr.begin(), expr.end(), '\n', ' ');
191                     stream << expr;
192                 }
193             }
194 
printMessage()195             void printMessage() {
196                 if ( itMessage != messages.end() ) {
197                     stream << " '" << itMessage->message << "'";
198                     ++itMessage;
199                 }
200             }
201 
printRemainingMessages(Colour::Code colour=dimColour ())202             void printRemainingMessages( Colour::Code colour = dimColour() ) {
203                 if (itMessage == messages.end()) {
204                     return;
205                 }
206 
207                 const auto itEnd = messages.cend();
208                 const auto N = static_cast<std::size_t>( std::distance( itMessage, itEnd ) );
209 
210                 {
211                     Colour colourGuard( colour );
212                     stream << " with " << pluralise( N, "message" ) << ":";
213                 }
214 
215                 while( itMessage != itEnd ) {
216                     // If this assertion is a warning ignore any INFO messages
217                     if( printInfoMessages || itMessage->type != ResultWas::Info ) {
218                         stream << " '" << itMessage->message << "'";
219                         if ( ++itMessage != itEnd ) {
220                             Colour colourGuard( dimColour() );
221                             stream << " and";
222                         }
223                         continue;
224                     }
225                     ++itMessage;
226                 }
227             }
228 
229         private:
230             std::ostream& stream;
231             AssertionResult const& result;
232             std::vector<MessageInfo> messages;
233             std::vector<MessageInfo>::const_iterator itMessage;
234             bool printInfoMessages;
235             std::size_t counter;
236         };
237 
printTotalsCatch::TAPReporter238         void printTotals( const Totals& totals ) const {
239             stream << "1.." << totals.assertions.total();
240             if( totals.testCases.total() == 0 ) {
241                 stream << " # Skipped: No tests ran.";
242             }
243         }
244     };
245 
246 #ifdef CATCH_IMPL
~TAPReporter()247     TAPReporter::~TAPReporter() {}
248 #endif
249 
250     CATCH_REGISTER_REPORTER( "tap", TAPReporter )
251 
252 } // end namespace Catch
253 
254 #endif // TWOBLUECUBES_CATCH_REPORTER_TAP_HPP_INCLUDED
255