/* * catch_reporter_junit.hpp * Catch * * 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 "internal/catch_capture.hpp" #include "internal/catch_reporter_registry.hpp" #include "internal/catch_xmlwriter.hpp" #include namespace Catch { class JunitReporter : public Catch::ITestReporter { struct Indenter { Indenter& operator ++() { m_indent += "\t"; return *this; } Indenter& operator --() { m_indent = m_indent.substr( 0, m_indent.length()-1 ); return *this; } friend std::ostream& operator << ( std::ostream& os, const Indenter& indent ) { os << indent.m_indent; return os; } std::string m_indent; }; struct TestStats { std::string element; std::string resultType; std::string message; std::string content; }; struct TestCaseStats { TestCaseStats( const std::string& name = std::string() ) : name( name ) { } double timeInSeconds; std::string status; std::string className; std::string name; std::vector testStats; }; struct Stats { Stats( const std::string& name = std::string() ) : testsCount( 0 ), failuresCount( 0 ), disabledCount( 0 ), errorsCount( 0 ), timeInSeconds( 0 ), name( name ) { } std::size_t testsCount; std::size_t failuresCount; std::size_t disabledCount; std::size_t errorsCount; double timeInSeconds; std::string name; std::vector testCaseStats; }; public: /////////////////////////////////////////////////////////////////////////// JunitReporter( const ReporterConfig& config = ReporterConfig() ) : m_config( config ), m_testSuiteStats( "AllTests" ), m_currentStats( &m_testSuiteStats ) { } /////////////////////////////////////////////////////////////////////////// static std::string getDescription() { return "Reports test results in an XML format that looks like Ant's junitreport target"; } private: // ITestReporter /////////////////////////////////////////////////////////////////////////// virtual void StartTesting() { } /////////////////////////////////////////////////////////////////////////// virtual void StartGroup( const std::string& groupName ) { // m_config.stream() << "\t\n"; // if( !groupName.empty() ) { m_statsForSuites.push_back( Stats( groupName ) ); m_currentStats = &m_statsForSuites.back(); } } /////////////////////////////////////////////////////////////////////////// virtual void EndGroup( const std::string& groupName, std::size_t succeeded, std::size_t failed ) { // m_config.stream() << "\t\n"; (groupName, succeeded, failed); m_currentStats = &m_testSuiteStats; } virtual void StartSection( const std::string& sectionName, const std::string description ){(sectionName,description);} virtual void EndSection( const std::string& sectionName, std::size_t succeeded, std::size_t failed ){(sectionName, succeeded, failed);} /////////////////////////////////////////////////////////////////////////// virtual void StartTestCase( const Catch::TestCaseInfo& testInfo ) { // m_config.stream() << "\t\t\n"; // m_currentTestSuccess = true; m_currentStats->testCaseStats.push_back( TestCaseStats( testInfo.getName() ) ); } /////////////////////////////////////////////////////////////////////////// virtual void Result( const Catch::ResultInfo& resultInfo ) { if( !resultInfo.ok() || m_config.includeSuccessfulResults() ) { TestCaseStats& testCaseStats = m_currentStats->testCaseStats.back(); TestStats stats; std::ostringstream oss; if( !resultInfo.getMessage().empty() ) { oss << resultInfo.getMessage() << " at "; } oss << resultInfo.getFilename() << ":" << resultInfo.getLine(); stats.content = oss.str(); stats.message = resultInfo.getExpandedExpression(); stats.resultType = resultInfo.getTestMacroName(); switch( resultInfo.getResultType() ) { case ResultWas::ThrewException: stats.element = "error"; break; case ResultWas::Info: stats.element = "info"; // !TBD ? break; case ResultWas::Warning: stats.element = "warning"; // !TBD ? break; case ResultWas::ExplicitFailure: stats.element = "failure"; break; case ResultWas::ExpressionFailed: stats.element = "failure"; break; case ResultWas::Ok: stats.element = "success"; break; default: stats.element = "unknown"; break; } testCaseStats.testStats.push_back( stats ); } } /////////////////////////////////////////////////////////////////////////// virtual void EndTestCase( const Catch::TestCaseInfo&, const std::string& stdOut, const std::string& stdErr ) { if( !stdOut.empty() ) m_stdOut << stdOut << "\n"; if( !stdErr.empty() ) m_stdErr << stdErr << "\n"; } static std::string trim( const std::string& str ) { std::string::size_type start = str.find_first_not_of( "\n\r\t " ); std::string::size_type end = str.find_last_not_of( "\n\r\t " ); return start < end ? str.substr( start, 1+end-start ) : ""; } /////////////////////////////////////////////////////////////////////////// virtual void EndTesting( std::size_t /* succeeded */, std::size_t /* failed */ ) { std::ostream& str = m_config.stream(); { XmlWriter xml( str ); if( m_statsForSuites.size() > 0 ) xml.startElement( "testsuites" ); std::vector::const_iterator it = m_statsForSuites.begin(); std::vector::const_iterator itEnd = m_statsForSuites.end(); for(; it != itEnd; ++it ) { XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); xml.writeAttribute( "name", it->name ); OutputTestCases( xml, *it ); } xml.scopedElement( "system-out" ).writeText( trim( m_stdOut.str() ) ); xml.scopedElement( "system-err" ).writeText( trim( m_stdOut.str() ) ); } } /////////////////////////////////////////////////////////////////////////// void OutputTestCases( XmlWriter& xml, const Stats& stats ) { std::vector::const_iterator it = stats.testCaseStats.begin(); std::vector::const_iterator itEnd = stats.testCaseStats.end(); for(; it != itEnd; ++it ) { xml.writeBlankLine(); xml.writeComment( "Test case" ); XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); xml.writeAttribute( "classname", it->className ); xml.writeAttribute( "name", it->name ); xml.writeAttribute( "time", "tbd" ); OutputTestResult( xml, *it ); } } /////////////////////////////////////////////////////////////////////////// void OutputTestResult( XmlWriter& xml, const TestCaseStats& stats ) { std::vector::const_iterator it = stats.testStats.begin(); std::vector::const_iterator itEnd = stats.testStats.end(); for(; it != itEnd; ++it ) { if( it->element != "success" ) { XmlWriter::ScopedElement e = xml.scopedElement( it->element ); xml.writeAttribute( "message", it->message ); xml.writeAttribute( "type", it->resultType ); if( !it->content.empty() ) xml.writeText( it->content ); } } } private: const ReporterConfig& m_config; bool m_currentTestSuccess; Stats m_testSuiteStats; Stats* m_currentStats; std::vector m_statsForSuites; std::ostringstream m_stdOut; std::ostringstream m_stdErr; Indenter m_indent; }; } // end namespace Catch #endif // TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED