Catch/include/reporters/catch_reporter_junit.hpp
Martin Hořeňovský b71a06cf98 JUnit reporter outputs timestamps now
Also extended approval tests script to support the change
2017-01-16 20:21:43 +01:00

253 lines
10 KiB
C++

/*
* 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
#include "catch_reporter_bases.hpp"
#include "../internal/catch_tostring.h"
#include "../internal/catch_reporter_registrars.hpp"
#include "../internal/catch_xmlwriter.hpp"
#include <assert.h>
namespace Catch {
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");
#ifdef CATCH_PLATFORM_WINDOWS
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";
#ifdef CATCH_PLATFORM_WINDOWS
std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);
#else
std::strftime(timeStamp, timeStampSize, fmt, timeInfo);
#endif
return std::string(timeStamp);
}
}
class JunitReporter : public CumulativeReporterBase {
public:
JunitReporter( ReporterConfig const& _config )
: CumulativeReporterBase( _config ),
xml( _config.stream() )
{
m_reporterPrefs.shouldRedirectStdOut = true;
}
virtual ~JunitReporter() CATCH_OVERRIDE;
static std::string getDescription() {
return "Reports test results in an XML format that looks like Ant's junitreport target";
}
virtual void noMatchingTestCases( std::string const& /*spec*/ ) CATCH_OVERRIDE {}
virtual void testRunStarting( TestRunInfo const& runInfo ) CATCH_OVERRIDE {
CumulativeReporterBase::testRunStarting( runInfo );
xml.startElement( "testsuites" );
}
virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE {
suiteTimer.start();
stdOutForSuite.str("");
stdErrForSuite.str("");
unexpectedExceptions = 0;
CumulativeReporterBase::testGroupStarting( groupInfo );
}
virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE {
if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException )
unexpectedExceptions++;
return CumulativeReporterBase::assertionEnded( assertionStats );
}
virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE {
stdOutForSuite << testCaseStats.stdOut;
stdErrForSuite << testCaseStats.stdErr;
CumulativeReporterBase::testCaseEnded( testCaseStats );
}
virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE {
double suiteTime = suiteTimer.getElapsedSeconds();
CumulativeReporterBase::testGroupEnded( testGroupStats );
writeGroup( *m_testGroups.back(), suiteTime );
}
virtual void testRunEndedCumulative() CATCH_OVERRIDE {
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 );
xml.writeAttribute( "timestamp", getCurrentTimestamp() );
// 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 );
}
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;
if( className.empty() ) {
if( rootSection.childSections.empty() )
className = "global";
}
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() )
name = rootName + "/" + name;
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 );
}
xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) );
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:
case ResultWas::FatalErrorCondition:
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;
}
XmlWriter::ScopedElement e = xml.scopedElement( elementName );
xml.writeAttribute( "message", result.getExpandedExpression() );
xml.writeAttribute( "type", result.getTestMacroName() );
std::ostringstream oss;
if( !result.getMessage().empty() )
oss << result.getMessage() << "\n";
for( std::vector<MessageInfo>::const_iterator
it = stats.infoMessages.begin(),
itEnd = stats.infoMessages.end();
it != itEnd;
++it )
if( it->type == ResultWas::Info )
oss << it->message << "\n";
oss << "at " << result.getSourceInfo();
xml.writeText( oss.str(), false );
}
}
XmlWriter xml;
Timer suiteTimer;
std::ostringstream stdOutForSuite;
std::ostringstream stdErrForSuite;
unsigned int unexpectedExceptions;
};
INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter )
} // end namespace Catch
#endif // TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED