| /* |
| * 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 { |
| |
| class JunitReporter : public CumulativeReporterBase { |
| public: |
| JunitReporter( ReporterConfig const& _config ) |
| : CumulativeReporterBase( _config ), |
| xml( _config.stream() ) |
| {} |
| |
| ~JunitReporter(); |
| |
| 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*/ ) {} |
| |
| virtual ReporterPreferences getPreferences() const { |
| ReporterPreferences prefs; |
| prefs.shouldRedirectStdOut = true; |
| return prefs; |
| } |
| |
| virtual void testRunStarting( TestRunInfo const& runInfo ) { |
| CumulativeReporterBase::testRunStarting( runInfo ); |
| xml.startElement( "testsuites" ); |
| } |
| |
| virtual void testGroupStarting( GroupInfo const& groupInfo ) { |
| suiteTimer.start(); |
| stdOutForSuite.str(""); |
| stdErrForSuite.str(""); |
| unexpectedExceptions = 0; |
| CumulativeReporterBase::testGroupStarting( groupInfo ); |
| } |
| |
| virtual bool assertionEnded( AssertionStats const& assertionStats ) { |
| if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException ) |
| unexpectedExceptions++; |
| return CumulativeReporterBase::assertionEnded( assertionStats ); |
| } |
| |
| virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { |
| stdOutForSuite << testCaseStats.stdOut; |
| stdErrForSuite << testCaseStats.stdErr; |
| CumulativeReporterBase::testCaseEnded( testCaseStats ); |
| } |
| |
| virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { |
| double suiteTime = suiteTimer.getElapsedSeconds(); |
| CumulativeReporterBase::testGroupEnded( testGroupStats ); |
| writeGroup( *m_testGroups.back(), suiteTime ); |
| } |
| |
| virtual void testRunEndedCumulative() { |
| 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", "tbd" ); // !TBD |
| |
| // 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", 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 |