Home | History | Annotate | Download | only in catch2
      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> {
     27         TeamCityReporter( ReporterConfig const& _config )
     28         :   StreamingReporterBase( _config )
     29         {
     30             m_reporterPrefs.shouldRedirectStdOut = true;
     31         }
     32 
     33         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 
     45         static std::string getDescription() {
     46             return "Reports test results as TeamCity service messages";
     47         }
     48 
     49         void skipTest( TestCaseInfo const& /* testInfo */ ) override {
     50         }
     51 
     52         void noMatchingTestCases( std::string const& /* spec */ ) override {}
     53 
     54         void testGroupStarting( GroupInfo const& groupInfo ) override {
     55             StreamingReporterBase::testGroupStarting( groupInfo );
     56             stream << "##teamcity[testSuiteStarted name='"
     57                 << escape( groupInfo.name ) << "']\n";
     58         }
     59         void testGroupEnded( TestGroupStats const& testGroupStats ) override {
     60             StreamingReporterBase::testGroupEnded( testGroupStats );
     61             stream << "##teamcity[testSuiteFinished name='"
     62                 << escape( testGroupStats.groupInfo.name ) << "']\n";
     63         }
     64 
     65 
     66         void assertionStarting( AssertionInfo const& ) override {}
     67 
     68         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 
    140         void sectionStarting( SectionInfo const& sectionInfo ) override {
    141             m_headerPrintedForThisSection = false;
    142             StreamingReporterBase::sectionStarting( sectionInfo );
    143         }
    144 
    145         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 
    153         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:
    170         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
    193         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
    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