Home | History | Annotate | Download | only in reporters
      1 /*
      2  *  Created by Phil on 26/11/2010.
      3  *  Copyright 2010 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 
      9 #include "catch_reporter_bases.hpp"
     10 
     11 #include "catch_reporter_junit.h"
     12 
     13 #include "../internal/catch_tostring.h"
     14 #include "../internal/catch_reporter_registrars.hpp"
     15 
     16 #include <cassert>
     17 #include <sstream>
     18 #include <ctime>
     19 #include <algorithm>
     20 
     21 namespace Catch {
     22 
     23     namespace {
     24         std::string getCurrentTimestamp() {
     25             // Beware, this is not reentrant because of backward compatibility issues
     26             // Also, UTC only, again because of backward compatibility (%z is C++11)
     27             time_t rawtime;
     28             std::time(&rawtime);
     29             auto const timeStampSize = sizeof("2017-01-16T17:06:45Z");
     30 
     31 #ifdef _MSC_VER
     32             std::tm timeInfo = {};
     33             gmtime_s(&timeInfo, &rawtime);
     34 #else
     35             std::tm* timeInfo;
     36             timeInfo = std::gmtime(&rawtime);
     37 #endif
     38 
     39             char timeStamp[timeStampSize];
     40             const char * const fmt = "%Y-%m-%dT%H:%M:%SZ";
     41 
     42 #ifdef _MSC_VER
     43             std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);
     44 #else
     45             std::strftime(timeStamp, timeStampSize, fmt, timeInfo);
     46 #endif
     47             return std::string(timeStamp);
     48         }
     49 
     50         std::string fileNameTag(const std::vector<std::string> &tags) {
     51             auto it = std::find_if(begin(tags),
     52                                    end(tags),
     53                                    [] (std::string const& tag) {return tag.front() == '#'; });
     54             if (it != tags.end())
     55                 return it->substr(1);
     56             return std::string();
     57         }
     58     } // anonymous namespace
     59 
     60     JunitReporter::JunitReporter( ReporterConfig const& _config )
     61         :   CumulativeReporterBase( _config ),
     62             xml( _config.stream() )
     63         {
     64             m_reporterPrefs.shouldRedirectStdOut = true;
     65             m_reporterPrefs.shouldReportAllAssertions = true;
     66         }
     67 
     68     JunitReporter::~JunitReporter() {}
     69 
     70     std::string JunitReporter::getDescription() {
     71         return "Reports test results in an XML format that looks like Ant's junitreport target";
     72     }
     73 
     74     void JunitReporter::noMatchingTestCases( std::string const& /*spec*/ ) {}
     75 
     76     void JunitReporter::testRunStarting( TestRunInfo const& runInfo )  {
     77         CumulativeReporterBase::testRunStarting( runInfo );
     78         xml.startElement( "testsuites" );
     79         if( m_config->rngSeed() != 0 ) {
     80             xml.startElement( "properties" );
     81             xml.scopedElement( "property" )
     82                 .writeAttribute( "name", "random-seed" )
     83                 .writeAttribute( "value", m_config->rngSeed() );
     84             xml.endElement();
     85         }
     86     }
     87 
     88     void JunitReporter::testGroupStarting( GroupInfo const& groupInfo ) {
     89         suiteTimer.start();
     90         stdOutForSuite.clear();
     91         stdErrForSuite.clear();
     92         unexpectedExceptions = 0;
     93         CumulativeReporterBase::testGroupStarting( groupInfo );
     94     }
     95 
     96     void JunitReporter::testCaseStarting( TestCaseInfo const& testCaseInfo ) {
     97         m_okToFail = testCaseInfo.okToFail();
     98     }
     99 
    100     bool JunitReporter::assertionEnded( AssertionStats const& assertionStats ) {
    101         if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail )
    102             unexpectedExceptions++;
    103         return CumulativeReporterBase::assertionEnded( assertionStats );
    104     }
    105 
    106     void JunitReporter::testCaseEnded( TestCaseStats const& testCaseStats ) {
    107         stdOutForSuite += testCaseStats.stdOut;
    108         stdErrForSuite += testCaseStats.stdErr;
    109         CumulativeReporterBase::testCaseEnded( testCaseStats );
    110     }
    111 
    112     void JunitReporter::testGroupEnded( TestGroupStats const& testGroupStats ) {
    113         double suiteTime = suiteTimer.getElapsedSeconds();
    114         CumulativeReporterBase::testGroupEnded( testGroupStats );
    115         writeGroup( *m_testGroups.back(), suiteTime );
    116     }
    117 
    118     void JunitReporter::testRunEndedCumulative() {
    119         xml.endElement();
    120     }
    121 
    122     void JunitReporter::writeGroup( TestGroupNode const& groupNode, double suiteTime ) {
    123         XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" );
    124         TestGroupStats const& stats = groupNode.value;
    125         xml.writeAttribute( "name", stats.groupInfo.name );
    126         xml.writeAttribute( "errors", unexpectedExceptions );
    127         xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions );
    128         xml.writeAttribute( "tests", stats.totals.assertions.total() );
    129         xml.writeAttribute( "hostname", "tbd" ); // !TBD
    130         if( m_config->showDurations() == ShowDurations::Never )
    131             xml.writeAttribute( "time", "" );
    132         else
    133             xml.writeAttribute( "time", suiteTime );
    134         xml.writeAttribute( "timestamp", getCurrentTimestamp() );
    135 
    136         // Write test cases
    137         for( auto const& child : groupNode.children )
    138             writeTestCase( *child );
    139 
    140         xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), false );
    141         xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite ), false );
    142     }
    143 
    144     void JunitReporter::writeTestCase( TestCaseNode const& testCaseNode ) {
    145         TestCaseStats const& stats = testCaseNode.value;
    146 
    147         // All test cases have exactly one section - which represents the
    148         // test case itself. That section may have 0-n nested sections
    149         assert( testCaseNode.children.size() == 1 );
    150         SectionNode const& rootSection = *testCaseNode.children.front();
    151 
    152         std::string className = stats.testInfo.className;
    153 
    154         if( className.empty() ) {
    155             className = fileNameTag(stats.testInfo.tags);
    156             if ( className.empty() )
    157                 className = "global";
    158         }
    159 
    160         if ( !m_config->name().empty() )
    161             className = m_config->name() + "." + className;
    162 
    163         writeSection( className, "", rootSection );
    164     }
    165 
    166     void JunitReporter::writeSection(  std::string const& className,
    167                         std::string const& rootName,
    168                         SectionNode const& sectionNode ) {
    169         std::string name = trim( sectionNode.stats.sectionInfo.name );
    170         if( !rootName.empty() )
    171             name = rootName + '/' + name;
    172 
    173         if( !sectionNode.assertions.empty() ||
    174             !sectionNode.stdOut.empty() ||
    175             !sectionNode.stdErr.empty() ) {
    176             XmlWriter::ScopedElement e = xml.scopedElement( "testcase" );
    177             if( className.empty() ) {
    178                 xml.writeAttribute( "classname", name );
    179                 xml.writeAttribute( "name", "root" );
    180             }
    181             else {
    182                 xml.writeAttribute( "classname", className );
    183                 xml.writeAttribute( "name", name );
    184             }
    185             xml.writeAttribute( "time", ::Catch::Detail::stringify( sectionNode.stats.durationInSeconds ) );
    186 
    187             writeAssertions( sectionNode );
    188 
    189             if( !sectionNode.stdOut.empty() )
    190                 xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false );
    191             if( !sectionNode.stdErr.empty() )
    192                 xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false );
    193         }
    194         for( auto const& childNode : sectionNode.childSections )
    195             if( className.empty() )
    196                 writeSection( name, "", *childNode );
    197             else
    198                 writeSection( className, name, *childNode );
    199     }
    200 
    201     void JunitReporter::writeAssertions( SectionNode const& sectionNode ) {
    202         for( auto const& assertion : sectionNode.assertions )
    203             writeAssertion( assertion );
    204     }
    205 
    206     void JunitReporter::writeAssertion( AssertionStats const& stats ) {
    207         AssertionResult const& result = stats.assertionResult;
    208         if( !result.isOk() ) {
    209             std::string elementName;
    210             switch( result.getResultType() ) {
    211                 case ResultWas::ThrewException:
    212                 case ResultWas::FatalErrorCondition:
    213                     elementName = "error";
    214                     break;
    215                 case ResultWas::ExplicitFailure:
    216                     elementName = "failure";
    217                     break;
    218                 case ResultWas::ExpressionFailed:
    219                     elementName = "failure";
    220                     break;
    221                 case ResultWas::DidntThrowException:
    222                     elementName = "failure";
    223                     break;
    224 
    225                 // We should never see these here:
    226                 case ResultWas::Info:
    227                 case ResultWas::Warning:
    228                 case ResultWas::Ok:
    229                 case ResultWas::Unknown:
    230                 case ResultWas::FailureBit:
    231                 case ResultWas::Exception:
    232                     elementName = "internalError";
    233                     break;
    234             }
    235 
    236             XmlWriter::ScopedElement e = xml.scopedElement( elementName );
    237 
    238             xml.writeAttribute( "message", result.getExpandedExpression() );
    239             xml.writeAttribute( "type", result.getTestMacroName() );
    240 
    241             ReusableStringStream rss;
    242             if( !result.getMessage().empty() )
    243                 rss << result.getMessage() << '\n';
    244             for( auto const& msg : stats.infoMessages )
    245                 if( msg.type == ResultWas::Info )
    246                     rss << msg.message << '\n';
    247 
    248             rss << "at " << result.getSourceInfo();
    249             xml.writeText( rss.str(), false );
    250         }
    251     }
    252 
    253     CATCH_REGISTER_REPORTER( "junit", JunitReporter )
    254 
    255 } // end namespace Catch
    256