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 
26         ~TAPReporter() override;
27 
getDescriptionCatch::TAPReporter28         static std::string getDescription() {
29             return "Reports test results in TAP format, suitable for test harnesses";
30         }
31 
getPreferencesCatch::TAPReporter32         ReporterPreferences getPreferences() const override {
33             return m_reporterPrefs;
34         }
35 
noMatchingTestCasesCatch::TAPReporter36         void noMatchingTestCases( std::string const& spec ) override {
37             stream << "# No test cases matched '" << spec << "'" << std::endl;
38         }
39 
assertionStartingCatch::TAPReporter40         void assertionStarting( AssertionInfo const& ) override {}
41 
assertionEndedCatch::TAPReporter42         bool assertionEnded( AssertionStats const& _assertionStats ) override {
43             ++counter;
44 
45             stream << "# " << currentTestCaseInfo->name << std::endl;
46             AssertionPrinter printer( stream, _assertionStats, counter );
47             printer.print();
48 
49             stream << std::endl;
50             return true;
51         }
52 
testRunEndedCatch::TAPReporter53         void testRunEnded( TestRunStats const& _testRunStats ) override {
54             printTotals( _testRunStats.totals );
55             stream << "\n" << std::endl;
56             StreamingReporterBase::testRunEnded( _testRunStats );
57         }
58 
59     private:
60         std::size_t counter = 0;
61         class AssertionPrinter {
62         public:
63             AssertionPrinter& operator= ( AssertionPrinter const& ) = delete;
64             AssertionPrinter( AssertionPrinter const& ) = delete;
AssertionPrinter(std::ostream & _stream,AssertionStats const & _stats,std::size_t _counter)65             AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, std::size_t _counter )
66             : stream( _stream )
67             , result( _stats.assertionResult )
68             , messages( _stats.infoMessages )
69             , itMessage( _stats.infoMessages.begin() )
70             , printInfoMessages( true )
71             , counter(_counter)
72             {}
73 
print()74             void print() {
75                 itMessage = messages.begin();
76 
77                 switch( result.getResultType() ) {
78                     case ResultWas::Ok:
79                         printResultType( passedString() );
80                         printOriginalExpression();
81                         printReconstructedExpression();
82                         if ( ! result.hasExpression() )
83                             printRemainingMessages( Colour::None );
84                         else
85                             printRemainingMessages();
86                         break;
87                     case ResultWas::ExpressionFailed:
88                         if (result.isOk()) {
89                             printResultType(passedString());
90                         } else {
91                             printResultType(failedString());
92                         }
93                         printOriginalExpression();
94                         printReconstructedExpression();
95                         if (result.isOk()) {
96                             printIssue(" # TODO");
97                         }
98                         printRemainingMessages();
99                         break;
100                     case ResultWas::ThrewException:
101                         printResultType( failedString() );
102                         printIssue( "unexpected exception with message:" );
103                         printMessage();
104                         printExpressionWas();
105                         printRemainingMessages();
106                         break;
107                     case ResultWas::FatalErrorCondition:
108                         printResultType( failedString() );
109                         printIssue( "fatal error condition with message:" );
110                         printMessage();
111                         printExpressionWas();
112                         printRemainingMessages();
113                         break;
114                     case ResultWas::DidntThrowException:
115                         printResultType( failedString() );
116                         printIssue( "expected exception, got none" );
117                         printExpressionWas();
118                         printRemainingMessages();
119                         break;
120                     case ResultWas::Info:
121                         printResultType( "info" );
122                         printMessage();
123                         printRemainingMessages();
124                         break;
125                     case ResultWas::Warning:
126                         printResultType( "warning" );
127                         printMessage();
128                         printRemainingMessages();
129                         break;
130                     case ResultWas::ExplicitFailure:
131                         printResultType( failedString() );
132                         printIssue( "explicitly" );
133                         printRemainingMessages( Colour::None );
134                         break;
135                     // These cases are here to prevent compiler warnings
136                     case ResultWas::Unknown:
137                     case ResultWas::FailureBit:
138                     case ResultWas::Exception:
139                         printResultType( "** internal error **" );
140                         break;
141                 }
142             }
143 
144         private:
dimColour()145             static Colour::Code dimColour() { return Colour::FileName; }
146 
failedString()147             static const char* failedString() { return "not ok"; }
passedString()148             static const char* passedString() { return "ok"; }
149 
printSourceInfo() const150             void printSourceInfo() const {
151                 Colour colourGuard( dimColour() );
152                 stream << result.getSourceInfo() << ":";
153             }
154 
printResultType(std::string const & passOrFail) const155             void printResultType( std::string const& passOrFail ) const {
156                 if( !passOrFail.empty() ) {
157                     stream << passOrFail << ' ' << counter << " -";
158                 }
159             }
160 
printIssue(std::string const & issue) const161             void printIssue( std::string const& issue ) const {
162                 stream << " " << issue;
163             }
164 
printExpressionWas()165             void printExpressionWas() {
166                 if( result.hasExpression() ) {
167                     stream << ";";
168                     {
169                         Colour colour( dimColour() );
170                         stream << " expression was:";
171                     }
172                     printOriginalExpression();
173                 }
174             }
175 
printOriginalExpression() const176             void printOriginalExpression() const {
177                 if( result.hasExpression() ) {
178                     stream << " " << result.getExpression();
179                 }
180             }
181 
printReconstructedExpression() const182             void printReconstructedExpression() const {
183                 if( result.hasExpandedExpression() ) {
184                     {
185                         Colour colour( dimColour() );
186                         stream << " for: ";
187                     }
188                     std::string expr = result.getExpandedExpression();
189                     std::replace( expr.begin(), expr.end(), '\n', ' ');
190                     stream << expr;
191                 }
192             }
193 
printMessage()194             void printMessage() {
195                 if ( itMessage != messages.end() ) {
196                     stream << " '" << itMessage->message << "'";
197                     ++itMessage;
198                 }
199             }
200 
printRemainingMessages(Colour::Code colour=dimColour ())201             void printRemainingMessages( Colour::Code colour = dimColour() ) {
202                 if (itMessage == messages.end()) {
203                     return;
204                 }
205 
206                 // using messages.end() directly (or auto) yields compilation error:
207                 std::vector<MessageInfo>::const_iterator itEnd = messages.end();
208                 const std::size_t 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                 for(; 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                     }
224                 }
225             }
226 
227         private:
228             std::ostream& stream;
229             AssertionResult const& result;
230             std::vector<MessageInfo> messages;
231             std::vector<MessageInfo>::const_iterator itMessage;
232             bool printInfoMessages;
233             std::size_t counter;
234         };
235 
printTotalsCatch::TAPReporter236         void printTotals( const Totals& totals ) const {
237             if( totals.testCases.total() == 0 ) {
238                 stream << "1..0 # Skipped: No tests ran.";
239             } else {
240                 stream << "1.." << counter;
241             }
242         }
243     };
244 
245 #ifdef CATCH_IMPL
~TAPReporter()246     TAPReporter::~TAPReporter() {}
247 #endif
248 
249     CATCH_REGISTER_REPORTER( "tap", TAPReporter )
250 
251 } // end namespace Catch
252 
253 #endif // TWOBLUECUBES_CATCH_REPORTER_TAP_HPP_INCLUDED
254