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