You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
220 lines
8.5 KiB
220 lines
8.5 KiB
/* |
|
* Created by Phil Nash on 19th December 2014 |
|
* Copyright 2014 Two Blue Cubes Ltd. 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_TEAMCITY_HPP_INCLUDED |
|
#define TWOBLUECUBES_CATCH_REPORTER_TEAMCITY_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 <cstring> |
|
|
|
#ifdef __clang__ |
|
# pragma clang diagnostic push |
|
# pragma clang diagnostic ignored "-Wpadded" |
|
#endif |
|
|
|
namespace Catch { |
|
|
|
struct TeamCityReporter : StreamingReporterBase<TeamCityReporter> { |
|
TeamCityReporter( ReporterConfig const& _config ) |
|
: StreamingReporterBase( _config ) |
|
{ |
|
m_reporterPrefs.shouldRedirectStdOut = true; |
|
} |
|
|
|
static std::string escape( std::string const& str ) { |
|
std::string escaped = str; |
|
replaceInPlace( escaped, "|", "||" ); |
|
replaceInPlace( escaped, "'", "|'" ); |
|
replaceInPlace( escaped, "\n", "|n" ); |
|
replaceInPlace( escaped, "\r", "|r" ); |
|
replaceInPlace( escaped, "[", "|[" ); |
|
replaceInPlace( escaped, "]", "|]" ); |
|
return escaped; |
|
} |
|
~TeamCityReporter() override; |
|
|
|
static std::string getDescription() { |
|
return "Reports test results as TeamCity service messages"; |
|
} |
|
|
|
void skipTest( TestCaseInfo const& /* testInfo */ ) override { |
|
} |
|
|
|
void noMatchingTestCases( std::string const& /* spec */ ) override {} |
|
|
|
void testGroupStarting( GroupInfo const& groupInfo ) override { |
|
StreamingReporterBase::testGroupStarting( groupInfo ); |
|
stream << "##teamcity[testSuiteStarted name='" |
|
<< escape( groupInfo.name ) << "']\n"; |
|
} |
|
void testGroupEnded( TestGroupStats const& testGroupStats ) override { |
|
StreamingReporterBase::testGroupEnded( testGroupStats ); |
|
stream << "##teamcity[testSuiteFinished name='" |
|
<< escape( testGroupStats.groupInfo.name ) << "']\n"; |
|
} |
|
|
|
|
|
void assertionStarting( AssertionInfo const& ) override {} |
|
|
|
bool assertionEnded( AssertionStats const& assertionStats ) override { |
|
AssertionResult const& result = assertionStats.assertionResult; |
|
if( !result.isOk() ) { |
|
|
|
ReusableStringStream msg; |
|
if( !m_headerPrintedForThisSection ) |
|
printSectionHeader( msg.get() ); |
|
m_headerPrintedForThisSection = true; |
|
|
|
msg << result.getSourceInfo() << "\n"; |
|
|
|
switch( result.getResultType() ) { |
|
case ResultWas::ExpressionFailed: |
|
msg << "expression failed"; |
|
break; |
|
case ResultWas::ThrewException: |
|
msg << "unexpected exception"; |
|
break; |
|
case ResultWas::FatalErrorCondition: |
|
msg << "fatal error condition"; |
|
break; |
|
case ResultWas::DidntThrowException: |
|
msg << "no exception was thrown where one was expected"; |
|
break; |
|
case ResultWas::ExplicitFailure: |
|
msg << "explicit failure"; |
|
break; |
|
|
|
// We shouldn't get here because of the isOk() test |
|
case ResultWas::Ok: |
|
case ResultWas::Info: |
|
case ResultWas::Warning: |
|
CATCH_ERROR( "Internal error in TeamCity reporter" ); |
|
// These cases are here to prevent compiler warnings |
|
case ResultWas::Unknown: |
|
case ResultWas::FailureBit: |
|
case ResultWas::Exception: |
|
CATCH_ERROR( "Not implemented" ); |
|
} |
|
if( assertionStats.infoMessages.size() == 1 ) |
|
msg << " with message:"; |
|
if( assertionStats.infoMessages.size() > 1 ) |
|
msg << " with messages:"; |
|
for( auto const& messageInfo : assertionStats.infoMessages ) |
|
msg << "\n \"" << messageInfo.message << "\""; |
|
|
|
|
|
if( result.hasExpression() ) { |
|
msg << |
|
"\n " << result.getExpressionInMacro() << "\n" |
|
"with expansion:\n" << |
|
" " << result.getExpandedExpression() << "\n"; |
|
} |
|
|
|
if( currentTestCaseInfo->okToFail() ) { |
|
msg << "- failure ignore as test marked as 'ok to fail'\n"; |
|
stream << "##teamcity[testIgnored" |
|
<< " name='" << escape( currentTestCaseInfo->name )<< "'" |
|
<< " message='" << escape( msg.str() ) << "'" |
|
<< "]\n"; |
|
} |
|
else { |
|
stream << "##teamcity[testFailed" |
|
<< " name='" << escape( currentTestCaseInfo->name )<< "'" |
|
<< " message='" << escape( msg.str() ) << "'" |
|
<< "]\n"; |
|
} |
|
} |
|
stream.flush(); |
|
return true; |
|
} |
|
|
|
void sectionStarting( SectionInfo const& sectionInfo ) override { |
|
m_headerPrintedForThisSection = false; |
|
StreamingReporterBase::sectionStarting( sectionInfo ); |
|
} |
|
|
|
void testCaseStarting( TestCaseInfo const& testInfo ) override { |
|
m_testTimer.start(); |
|
StreamingReporterBase::testCaseStarting( testInfo ); |
|
stream << "##teamcity[testStarted name='" |
|
<< escape( testInfo.name ) << "']\n"; |
|
stream.flush(); |
|
} |
|
|
|
void testCaseEnded( TestCaseStats const& testCaseStats ) override { |
|
StreamingReporterBase::testCaseEnded( testCaseStats ); |
|
if( !testCaseStats.stdOut.empty() ) |
|
stream << "##teamcity[testStdOut name='" |
|
<< escape( testCaseStats.testInfo.name ) |
|
<< "' out='" << escape( testCaseStats.stdOut ) << "']\n"; |
|
if( !testCaseStats.stdErr.empty() ) |
|
stream << "##teamcity[testStdErr name='" |
|
<< escape( testCaseStats.testInfo.name ) |
|
<< "' out='" << escape( testCaseStats.stdErr ) << "']\n"; |
|
stream << "##teamcity[testFinished name='" |
|
<< escape( testCaseStats.testInfo.name ) << "' duration='" |
|
<< m_testTimer.getElapsedMilliseconds() << "']\n"; |
|
stream.flush(); |
|
} |
|
|
|
private: |
|
void printSectionHeader( std::ostream& os ) { |
|
assert( !m_sectionStack.empty() ); |
|
|
|
if( m_sectionStack.size() > 1 ) { |
|
os << getLineOfChars<'-'>() << "\n"; |
|
|
|
std::vector<SectionInfo>::const_iterator |
|
it = m_sectionStack.begin()+1, // Skip first section (test case) |
|
itEnd = m_sectionStack.end(); |
|
for( ; it != itEnd; ++it ) |
|
printHeaderString( os, it->name ); |
|
os << getLineOfChars<'-'>() << "\n"; |
|
} |
|
|
|
SourceLineInfo lineInfo = m_sectionStack.front().lineInfo; |
|
|
|
if( !lineInfo.empty() ) |
|
os << lineInfo << "\n"; |
|
os << getLineOfChars<'.'>() << "\n\n"; |
|
} |
|
|
|
// if string has a : in first line will set indent to follow it on |
|
// subsequent lines |
|
static void printHeaderString( std::ostream& os, std::string const& _string, std::size_t indent = 0 ) { |
|
std::size_t i = _string.find( ": " ); |
|
if( i != std::string::npos ) |
|
i+=2; |
|
else |
|
i = 0; |
|
os << Column( _string ) |
|
.indent( indent+i) |
|
.initialIndent( indent ) << "\n"; |
|
} |
|
private: |
|
bool m_headerPrintedForThisSection = false; |
|
Timer m_testTimer; |
|
}; |
|
|
|
#ifdef CATCH_IMPL |
|
TeamCityReporter::~TeamCityReporter() {} |
|
#endif |
|
|
|
CATCH_REGISTER_REPORTER( "teamcity", TeamCityReporter ) |
|
|
|
} // end namespace Catch |
|
|
|
#ifdef __clang__ |
|
# pragma clang diagnostic pop |
|
#endif |
|
|
|
#endif // TWOBLUECUBES_CATCH_REPORTER_TEAMCITY_HPP_INCLUDED
|
|
|