2010-12-10 09:01:42 +01:00
|
|
|
/*
|
|
|
|
* Created by Phil on 26/11/2010.
|
|
|
|
* Copyright 2010 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_JUNIT_HPP_INCLUDED
|
|
|
|
#define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED
|
|
|
|
|
2013-12-03 19:52:41 +01:00
|
|
|
#include "catch_reporter_bases.hpp"
|
|
|
|
|
2014-04-23 07:51:58 +02:00
|
|
|
#include "../internal/catch_tostring.h"
|
2011-06-02 09:49:47 +02:00
|
|
|
#include "../internal/catch_reporter_registrars.hpp"
|
|
|
|
#include "../internal/catch_xmlwriter.hpp"
|
2010-12-10 09:01:42 +01:00
|
|
|
|
2012-12-02 01:05:51 +01:00
|
|
|
#include <assert.h>
|
|
|
|
|
2012-05-16 15:53:59 +02:00
|
|
|
namespace Catch {
|
|
|
|
|
2017-01-16 20:21:43 +01:00
|
|
|
namespace {
|
|
|
|
std::string getCurrentTimestamp() {
|
|
|
|
// Beware, this is not reentrant because of backward compatibility issues
|
|
|
|
// Also, UTC only, again because of backward compatibility (%z is C++11)
|
|
|
|
time_t rawtime;
|
|
|
|
std::time(&rawtime);
|
|
|
|
const size_t timeStampSize = sizeof("2017-01-16T17:06:45Z");
|
|
|
|
|
2017-02-06 01:43:53 +01:00
|
|
|
#ifdef _MSC_VER
|
2017-01-16 20:21:43 +01:00
|
|
|
std::tm timeInfo = {};
|
|
|
|
gmtime_s(&timeInfo, &rawtime);
|
|
|
|
#else
|
|
|
|
std::tm* timeInfo;
|
|
|
|
timeInfo = std::gmtime(&rawtime);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
char timeStamp[timeStampSize];
|
|
|
|
const char * const fmt = "%Y-%m-%dT%H:%M:%SZ";
|
|
|
|
|
2017-02-06 01:43:53 +01:00
|
|
|
#ifdef _MSC_VER
|
2017-01-16 20:21:43 +01:00
|
|
|
std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);
|
|
|
|
#else
|
|
|
|
std::strftime(timeStamp, timeStampSize, fmt, timeInfo);
|
|
|
|
#endif
|
|
|
|
return std::string(timeStamp);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2013-08-15 19:39:55 +02:00
|
|
|
class JunitReporter : public CumulativeReporterBase {
|
|
|
|
public:
|
|
|
|
JunitReporter( ReporterConfig const& _config )
|
|
|
|
: CumulativeReporterBase( _config ),
|
2017-04-11 16:47:42 +02:00
|
|
|
xml( _config.stream() ),
|
|
|
|
m_okToFail( false )
|
2015-08-07 09:20:56 +02:00
|
|
|
{
|
|
|
|
m_reporterPrefs.shouldRedirectStdOut = true;
|
|
|
|
}
|
2013-08-15 19:39:55 +02:00
|
|
|
|
2015-08-07 09:20:56 +02:00
|
|
|
virtual ~JunitReporter() CATCH_OVERRIDE;
|
2013-08-15 19:39:55 +02:00
|
|
|
|
|
|
|
static std::string getDescription() {
|
|
|
|
return "Reports test results in an XML format that looks like Ant's junitreport target";
|
|
|
|
}
|
|
|
|
|
2015-08-07 09:20:56 +02:00
|
|
|
virtual void noMatchingTestCases( std::string const& /*spec*/ ) CATCH_OVERRIDE {}
|
2013-08-15 19:39:55 +02:00
|
|
|
|
2015-08-07 09:20:56 +02:00
|
|
|
virtual void testRunStarting( TestRunInfo const& runInfo ) CATCH_OVERRIDE {
|
2013-08-15 19:39:55 +02:00
|
|
|
CumulativeReporterBase::testRunStarting( runInfo );
|
|
|
|
xml.startElement( "testsuites" );
|
|
|
|
}
|
|
|
|
|
2015-08-07 09:20:56 +02:00
|
|
|
virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE {
|
2013-08-15 19:39:55 +02:00
|
|
|
suiteTimer.start();
|
|
|
|
stdOutForSuite.str("");
|
|
|
|
stdErrForSuite.str("");
|
|
|
|
unexpectedExceptions = 0;
|
|
|
|
CumulativeReporterBase::testGroupStarting( groupInfo );
|
|
|
|
}
|
|
|
|
|
2017-04-11 16:47:42 +02:00
|
|
|
virtual void testCaseStarting( TestCaseInfo const& testCaseInfo ) CATCH_OVERRIDE {
|
|
|
|
m_okToFail = testCaseInfo.okToFail();
|
|
|
|
}
|
2015-08-07 09:20:56 +02:00
|
|
|
virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE {
|
2017-04-11 16:47:42 +02:00
|
|
|
if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail )
|
2013-08-15 19:39:55 +02:00
|
|
|
unexpectedExceptions++;
|
|
|
|
return CumulativeReporterBase::assertionEnded( assertionStats );
|
|
|
|
}
|
|
|
|
|
2015-08-07 09:20:56 +02:00
|
|
|
virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE {
|
2013-08-15 19:39:55 +02:00
|
|
|
stdOutForSuite << testCaseStats.stdOut;
|
|
|
|
stdErrForSuite << testCaseStats.stdErr;
|
|
|
|
CumulativeReporterBase::testCaseEnded( testCaseStats );
|
|
|
|
}
|
|
|
|
|
2015-08-07 09:20:56 +02:00
|
|
|
virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE {
|
2013-08-15 19:39:55 +02:00
|
|
|
double suiteTime = suiteTimer.getElapsedSeconds();
|
|
|
|
CumulativeReporterBase::testGroupEnded( testGroupStats );
|
|
|
|
writeGroup( *m_testGroups.back(), suiteTime );
|
|
|
|
}
|
|
|
|
|
2015-08-07 09:20:56 +02:00
|
|
|
virtual void testRunEndedCumulative() CATCH_OVERRIDE {
|
2013-08-15 19:39:55 +02:00
|
|
|
xml.endElement();
|
|
|
|
}
|
|
|
|
|
|
|
|
void writeGroup( TestGroupNode const& groupNode, double suiteTime ) {
|
|
|
|
XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" );
|
|
|
|
TestGroupStats const& stats = groupNode.value;
|
|
|
|
xml.writeAttribute( "name", stats.groupInfo.name );
|
|
|
|
xml.writeAttribute( "errors", unexpectedExceptions );
|
|
|
|
xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions );
|
|
|
|
xml.writeAttribute( "tests", stats.totals.assertions.total() );
|
|
|
|
xml.writeAttribute( "hostname", "tbd" ); // !TBD
|
|
|
|
if( m_config->showDurations() == ShowDurations::Never )
|
|
|
|
xml.writeAttribute( "time", "" );
|
|
|
|
else
|
|
|
|
xml.writeAttribute( "time", suiteTime );
|
2017-01-16 20:21:43 +01:00
|
|
|
xml.writeAttribute( "timestamp", getCurrentTimestamp() );
|
2013-08-15 19:39:55 +02:00
|
|
|
|
|
|
|
// Write test cases
|
|
|
|
for( TestGroupNode::ChildNodes::const_iterator
|
|
|
|
it = groupNode.children.begin(), itEnd = groupNode.children.end();
|
|
|
|
it != itEnd;
|
|
|
|
++it )
|
|
|
|
writeTestCase( **it );
|
|
|
|
|
|
|
|
xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false );
|
|
|
|
xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false );
|
|
|
|
}
|
|
|
|
|
2017-05-30 17:26:46 +02:00
|
|
|
static std::string fileNameTag( const std::set<std::string> &tags ) {
|
|
|
|
std::set<std::string>::const_iterator it = tags.lower_bound("#");
|
|
|
|
if( it != tags.end() && !it->empty() && it->front() == '#' )
|
|
|
|
return it->substr(1);
|
|
|
|
return std::string();
|
|
|
|
}
|
|
|
|
|
2013-08-15 19:39:55 +02:00
|
|
|
void writeTestCase( TestCaseNode const& testCaseNode ) {
|
|
|
|
TestCaseStats const& stats = testCaseNode.value;
|
|
|
|
|
|
|
|
// All test cases have exactly one section - which represents the
|
|
|
|
// test case itself. That section may have 0-n nested sections
|
|
|
|
assert( testCaseNode.children.size() == 1 );
|
|
|
|
SectionNode const& rootSection = *testCaseNode.children.front();
|
|
|
|
|
|
|
|
std::string className = stats.testInfo.className;
|
2015-11-04 19:01:28 +01:00
|
|
|
|
2013-08-15 19:39:55 +02:00
|
|
|
if( className.empty() ) {
|
2017-05-30 17:26:46 +02:00
|
|
|
className = fileNameTag(stats.testInfo.tags);
|
|
|
|
if ( className.empty() )
|
|
|
|
className = "global";
|
2013-08-15 19:39:55 +02:00
|
|
|
}
|
|
|
|
writeSection( className, "", rootSection );
|
|
|
|
}
|
|
|
|
|
|
|
|
void writeSection( std::string const& className,
|
|
|
|
std::string const& rootName,
|
|
|
|
SectionNode const& sectionNode ) {
|
|
|
|
std::string name = trim( sectionNode.stats.sectionInfo.name );
|
|
|
|
if( !rootName.empty() )
|
2017-01-29 23:07:15 +01:00
|
|
|
name = rootName + '/' + name;
|
2015-11-04 19:01:28 +01:00
|
|
|
|
2013-08-15 19:39:55 +02:00
|
|
|
if( !sectionNode.assertions.empty() ||
|
|
|
|
!sectionNode.stdOut.empty() ||
|
|
|
|
!sectionNode.stdErr.empty() ) {
|
|
|
|
XmlWriter::ScopedElement e = xml.scopedElement( "testcase" );
|
|
|
|
if( className.empty() ) {
|
|
|
|
xml.writeAttribute( "classname", name );
|
|
|
|
xml.writeAttribute( "name", "root" );
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
xml.writeAttribute( "classname", className );
|
|
|
|
xml.writeAttribute( "name", name );
|
|
|
|
}
|
2014-12-09 19:49:58 +01:00
|
|
|
xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) );
|
2013-08-15 19:39:55 +02:00
|
|
|
|
|
|
|
writeAssertions( sectionNode );
|
|
|
|
|
|
|
|
if( !sectionNode.stdOut.empty() )
|
|
|
|
xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false );
|
|
|
|
if( !sectionNode.stdErr.empty() )
|
|
|
|
xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false );
|
|
|
|
}
|
|
|
|
for( SectionNode::ChildSections::const_iterator
|
|
|
|
it = sectionNode.childSections.begin(),
|
|
|
|
itEnd = sectionNode.childSections.end();
|
|
|
|
it != itEnd;
|
|
|
|
++it )
|
|
|
|
if( className.empty() )
|
|
|
|
writeSection( name, "", **it );
|
|
|
|
else
|
|
|
|
writeSection( className, name, **it );
|
|
|
|
}
|
|
|
|
|
|
|
|
void writeAssertions( SectionNode const& sectionNode ) {
|
|
|
|
for( SectionNode::Assertions::const_iterator
|
|
|
|
it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end();
|
|
|
|
it != itEnd;
|
|
|
|
++it )
|
|
|
|
writeAssertion( *it );
|
|
|
|
}
|
|
|
|
void writeAssertion( AssertionStats const& stats ) {
|
|
|
|
AssertionResult const& result = stats.assertionResult;
|
|
|
|
if( !result.isOk() ) {
|
|
|
|
std::string elementName;
|
|
|
|
switch( result.getResultType() ) {
|
|
|
|
case ResultWas::ThrewException:
|
2014-08-22 09:07:39 +02:00
|
|
|
case ResultWas::FatalErrorCondition:
|
2013-08-15 19:39:55 +02:00
|
|
|
elementName = "error";
|
|
|
|
break;
|
|
|
|
case ResultWas::ExplicitFailure:
|
|
|
|
elementName = "failure";
|
|
|
|
break;
|
|
|
|
case ResultWas::ExpressionFailed:
|
|
|
|
elementName = "failure";
|
|
|
|
break;
|
|
|
|
case ResultWas::DidntThrowException:
|
|
|
|
elementName = "failure";
|
|
|
|
break;
|
|
|
|
|
|
|
|
// We should never see these here:
|
|
|
|
case ResultWas::Info:
|
|
|
|
case ResultWas::Warning:
|
|
|
|
case ResultWas::Ok:
|
|
|
|
case ResultWas::Unknown:
|
|
|
|
case ResultWas::FailureBit:
|
|
|
|
case ResultWas::Exception:
|
|
|
|
elementName = "internalError";
|
|
|
|
break;
|
|
|
|
}
|
2015-11-04 19:01:28 +01:00
|
|
|
|
2013-08-15 19:39:55 +02:00
|
|
|
XmlWriter::ScopedElement e = xml.scopedElement( elementName );
|
|
|
|
|
|
|
|
xml.writeAttribute( "message", result.getExpandedExpression() );
|
|
|
|
xml.writeAttribute( "type", result.getTestMacroName() );
|
|
|
|
|
|
|
|
std::ostringstream oss;
|
|
|
|
if( !result.getMessage().empty() )
|
2017-01-29 23:07:15 +01:00
|
|
|
oss << result.getMessage() << '\n';
|
2013-08-15 19:39:55 +02:00
|
|
|
for( std::vector<MessageInfo>::const_iterator
|
|
|
|
it = stats.infoMessages.begin(),
|
|
|
|
itEnd = stats.infoMessages.end();
|
|
|
|
it != itEnd;
|
|
|
|
++it )
|
|
|
|
if( it->type == ResultWas::Info )
|
2017-01-29 23:07:15 +01:00
|
|
|
oss << it->message << '\n';
|
2013-08-15 19:39:55 +02:00
|
|
|
|
|
|
|
oss << "at " << result.getSourceInfo();
|
|
|
|
xml.writeText( oss.str(), false );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
XmlWriter xml;
|
|
|
|
Timer suiteTimer;
|
|
|
|
std::ostringstream stdOutForSuite;
|
|
|
|
std::ostringstream stdErrForSuite;
|
|
|
|
unsigned int unexpectedExceptions;
|
2017-04-11 16:47:42 +02:00
|
|
|
bool m_okToFail;
|
2013-08-15 19:39:55 +02:00
|
|
|
};
|
|
|
|
|
2015-11-04 19:01:28 +01:00
|
|
|
INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter )
|
2013-08-15 19:39:55 +02:00
|
|
|
|
2010-12-10 09:01:42 +01:00
|
|
|
} // end namespace Catch
|
|
|
|
|
2010-12-28 15:42:46 +01:00
|
|
|
#endif // TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED
|