• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Created by Phil Nash on 19th December 2014
3  *  Copyright 2014 Two Blue Cubes Ltd. 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_TEAMCITY_HPP_INCLUDED
9 #define TWOBLUECUBES_CATCH_REPORTER_TEAMCITY_HPP_INCLUDED
10 
11 // Don't #include any Catch headers here - we can assume they are already
12 // included before this header.
13 // This is not good practice in general but is necessary in this case so this
14 // file can be distributed as a single header that works with the main
15 // Catch single header.
16 
17 #include <cstring>
18 
19 #ifdef __clang__
20 #   pragma clang diagnostic push
21 #   pragma clang diagnostic ignored "-Wpadded"
22 #endif
23 
24 namespace Catch {
25 
26     struct TeamCityReporter : StreamingReporterBase<TeamCityReporter> {
TeamCityReporterCatch::TeamCityReporter27         TeamCityReporter( ReporterConfig const& _config )
28         :   StreamingReporterBase( _config )
29         {
30             m_reporterPrefs.shouldRedirectStdOut = true;
31         }
32 
escapeCatch::TeamCityReporter33         static std::string escape( std::string const& str ) {
34             std::string escaped = str;
35             replaceInPlace( escaped, "|", "||" );
36             replaceInPlace( escaped, "'", "|'" );
37             replaceInPlace( escaped, "\n", "|n" );
38             replaceInPlace( escaped, "\r", "|r" );
39             replaceInPlace( escaped, "[", "|[" );
40             replaceInPlace( escaped, "]", "|]" );
41             return escaped;
42         }
43         ~TeamCityReporter() override;
44 
getDescriptionCatch::TeamCityReporter45         static std::string getDescription() {
46             return "Reports test results as TeamCity service messages";
47         }
48 
skipTestCatch::TeamCityReporter49         void skipTest( TestCaseInfo const& /* testInfo */ ) override {
50         }
51 
noMatchingTestCasesCatch::TeamCityReporter52         void noMatchingTestCases( std::string const& /* spec */ ) override {}
53 
testGroupStartingCatch::TeamCityReporter54         void testGroupStarting( GroupInfo const& groupInfo ) override {
55             StreamingReporterBase::testGroupStarting( groupInfo );
56             stream << "##teamcity[testSuiteStarted name='"
57                 << escape( groupInfo.name ) << "']\n";
58         }
testGroupEndedCatch::TeamCityReporter59         void testGroupEnded( TestGroupStats const& testGroupStats ) override {
60             StreamingReporterBase::testGroupEnded( testGroupStats );
61             stream << "##teamcity[testSuiteFinished name='"
62                 << escape( testGroupStats.groupInfo.name ) << "']\n";
63         }
64 
65 
assertionStartingCatch::TeamCityReporter66         void assertionStarting( AssertionInfo const& ) override {}
67 
assertionEndedCatch::TeamCityReporter68         bool assertionEnded( AssertionStats const& assertionStats ) override {
69             AssertionResult const& result = assertionStats.assertionResult;
70             if( !result.isOk() ) {
71 
72                 ReusableStringStream msg;
73                 if( !m_headerPrintedForThisSection )
74                     printSectionHeader( msg.get() );
75                 m_headerPrintedForThisSection = true;
76 
77                 msg << result.getSourceInfo() << "\n";
78 
79                 switch( result.getResultType() ) {
80                     case ResultWas::ExpressionFailed:
81                         msg << "expression failed";
82                         break;
83                     case ResultWas::ThrewException:
84                         msg << "unexpected exception";
85                         break;
86                     case ResultWas::FatalErrorCondition:
87                         msg << "fatal error condition";
88                         break;
89                     case ResultWas::DidntThrowException:
90                         msg << "no exception was thrown where one was expected";
91                         break;
92                     case ResultWas::ExplicitFailure:
93                         msg << "explicit failure";
94                         break;
95 
96                     // We shouldn't get here because of the isOk() test
97                     case ResultWas::Ok:
98                     case ResultWas::Info:
99                     case ResultWas::Warning:
100                         CATCH_ERROR( "Internal error in TeamCity reporter" );
101                     // These cases are here to prevent compiler warnings
102                     case ResultWas::Unknown:
103                     case ResultWas::FailureBit:
104                     case ResultWas::Exception:
105                         CATCH_ERROR( "Not implemented" );
106                 }
107                 if( assertionStats.infoMessages.size() == 1 )
108                     msg << " with message:";
109                 if( assertionStats.infoMessages.size() > 1 )
110                     msg << " with messages:";
111                 for( auto const& messageInfo : assertionStats.infoMessages )
112                     msg << "\n  \"" << messageInfo.message << "\"";
113 
114 
115                 if( result.hasExpression() ) {
116                     msg <<
117                         "\n  " << result.getExpressionInMacro() << "\n"
118                         "with expansion:\n" <<
119                         "  " << result.getExpandedExpression() << "\n";
120                 }
121 
122                 if( currentTestCaseInfo->okToFail() ) {
123                     msg << "- failure ignore as test marked as 'ok to fail'\n";
124                     stream << "##teamcity[testIgnored"
125                            << " name='" << escape( currentTestCaseInfo->name )<< "'"
126                            << " message='" << escape( msg.str() ) << "'"
127                            << "]\n";
128                 }
129                 else {
130                     stream << "##teamcity[testFailed"
131                            << " name='" << escape( currentTestCaseInfo->name )<< "'"
132                            << " message='" << escape( msg.str() ) << "'"
133                            << "]\n";
134                 }
135             }
136             stream.flush();
137             return true;
138         }
139 
sectionStartingCatch::TeamCityReporter140         void sectionStarting( SectionInfo const& sectionInfo ) override {
141             m_headerPrintedForThisSection = false;
142             StreamingReporterBase::sectionStarting( sectionInfo );
143         }
144 
testCaseStartingCatch::TeamCityReporter145         void testCaseStarting( TestCaseInfo const& testInfo ) override {
146             m_testTimer.start();
147             StreamingReporterBase::testCaseStarting( testInfo );
148             stream << "##teamcity[testStarted name='"
149                 << escape( testInfo.name ) << "']\n";
150             stream.flush();
151         }
152 
testCaseEndedCatch::TeamCityReporter153         void testCaseEnded( TestCaseStats const& testCaseStats ) override {
154             StreamingReporterBase::testCaseEnded( testCaseStats );
155             if( !testCaseStats.stdOut.empty() )
156                 stream << "##teamcity[testStdOut name='"
157                     << escape( testCaseStats.testInfo.name )
158                     << "' out='" << escape( testCaseStats.stdOut ) << "']\n";
159             if( !testCaseStats.stdErr.empty() )
160                 stream << "##teamcity[testStdErr name='"
161                     << escape( testCaseStats.testInfo.name )
162                     << "' out='" << escape( testCaseStats.stdErr ) << "']\n";
163             stream << "##teamcity[testFinished name='"
164                     << escape( testCaseStats.testInfo.name ) << "' duration='"
165                     << m_testTimer.getElapsedMilliseconds() << "']\n";
166             stream.flush();
167         }
168 
169     private:
printSectionHeaderCatch::TeamCityReporter170         void printSectionHeader( std::ostream& os ) {
171             assert( !m_sectionStack.empty() );
172 
173             if( m_sectionStack.size() > 1 ) {
174                 os << getLineOfChars<'-'>() << "\n";
175 
176                 std::vector<SectionInfo>::const_iterator
177                 it = m_sectionStack.begin()+1, // Skip first section (test case)
178                 itEnd = m_sectionStack.end();
179                 for( ; it != itEnd; ++it )
180                     printHeaderString( os, it->name );
181                 os << getLineOfChars<'-'>() << "\n";
182             }
183 
184             SourceLineInfo lineInfo = m_sectionStack.front().lineInfo;
185 
186             if( !lineInfo.empty() )
187                 os << lineInfo << "\n";
188             os << getLineOfChars<'.'>() << "\n\n";
189         }
190 
191         // if string has a : in first line will set indent to follow it on
192         // subsequent lines
printHeaderStringCatch::TeamCityReporter193         static void printHeaderString( std::ostream& os, std::string const& _string, std::size_t indent = 0 ) {
194             std::size_t i = _string.find( ": " );
195             if( i != std::string::npos )
196                 i+=2;
197             else
198                 i = 0;
199             os << Column( _string )
200                            .indent( indent+i)
201                            .initialIndent( indent ) << "\n";
202         }
203     private:
204         bool m_headerPrintedForThisSection = false;
205         Timer m_testTimer;
206     };
207 
208 #ifdef CATCH_IMPL
~TeamCityReporter()209     TeamCityReporter::~TeamCityReporter() {}
210 #endif
211 
212     CATCH_REGISTER_REPORTER( "teamcity", TeamCityReporter )
213 
214 } // end namespace Catch
215 
216 #ifdef __clang__
217 #   pragma clang diagnostic pop
218 #endif
219 
220 #endif // TWOBLUECUBES_CATCH_REPORTER_TEAMCITY_HPP_INCLUDED
221