catch_reporter_junit.cpp 11 KB

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