/* * 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 #ifdef __clang__ # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wpadded" #endif namespace Catch { struct TeamCityReporter : StreamingReporterBase { 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::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; 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