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