From b80c3b043ac6956623a767de952e57f04517b2dd Mon Sep 17 00:00:00 2001 From: Kosta Date: Mon, 22 Jul 2013 20:49:54 +0200 Subject: [PATCH] added timing support to jUnit XML reporter Implemented support for report test timings in the jUnit XML reporter. The implementation uses the new C++11 chrono functionality, but all this has been encapsulated in only 3 lines at the top of the JunitReporter class, which should allow for an easy adjustment for non-C++11-compilers... --- include/reporters/catch_reporter_junit.hpp | 87 ++++++++++++---------- 1 file changed, 48 insertions(+), 39 deletions(-) diff --git a/include/reporters/catch_reporter_junit.hpp b/include/reporters/catch_reporter_junit.hpp index 62b97a42..30343251 100644 --- a/include/reporters/catch_reporter_junit.hpp +++ b/include/reporters/catch_reporter_junit.hpp @@ -18,52 +18,58 @@ namespace Catch { class JunitReporter : public SharedImpl { - + + // C++11 specific start => needs to be adjusted for pre-C++11-compilers + typedef decltype(std::chrono::high_resolution_clock::now()) time_point; + static time_point time_now() { return std::chrono::high_resolution_clock::now(); } + static double time_diff(const time_point& end, const time_point& start) { return (1e-6 * std::chrono::duration_cast(end - start).count()); } + // C++11 specific end + struct TestStats { std::string m_element; std::string m_resultType; std::string m_message; std::string m_content; }; - + struct TestCaseStats { - + TestCaseStats( const std::string& className, const std::string& name ) : m_className( className ), - m_name( name ) + m_name( name ), + m_startTime( time_now() ), m_endTime( m_startTime ) {} - - double m_timeInSeconds; + std::string m_status; std::string m_className; std::string m_name; std::string m_stdOut; std::string m_stdErr; + time_point m_startTime, m_endTime; std::vector m_testStats; std::vector m_sections; }; - + struct Stats { - + Stats( const std::string& name = std::string() ) : m_testsCount( 0 ), m_failuresCount( 0 ), m_disabledCount( 0 ), m_errorsCount( 0 ), - m_timeInSeconds( 0 ), - m_name( name ) + m_name( name ), + m_startTime( time_now() ), m_endTime( m_startTime ) {} - + std::size_t m_testsCount; std::size_t m_failuresCount; std::size_t m_disabledCount; std::size_t m_errorsCount; - double m_timeInSeconds; std::string m_name; - + time_point m_startTime, m_endTime; std::vector m_testCaseStats; }; - + public: JunitReporter( ReporterConfig const& config ) : m_config( config ), @@ -71,19 +77,19 @@ namespace Catch { m_currentStats( &m_testSuiteStats ) {} virtual ~JunitReporter(); - + static std::string getDescription() { return "Reports test results in an XML format that looks like Ant's junitreport target"; } - + private: // IReporter - + virtual bool shouldRedirectStdout() const { return true; - } + } virtual void StartTesting(){} - + virtual void StartGroup( const std::string& groupName ) { if( groupName.empty() ) m_statsForSuites.push_back( Stats( m_config.fullConfig()->name() ) ); @@ -94,21 +100,22 @@ namespace Catch { virtual void EndGroup( const std::string&, const Totals& totals ) { m_currentStats->m_testsCount = totals.assertions.total(); + m_currentStats->m_endTime = time_now(); m_currentStats = &m_testSuiteStats; } - + virtual void StartSection( const std::string&, const std::string& ){} virtual void NoAssertionsInSection( const std::string& ) {} virtual void NoAssertionsInTestCase( const std::string& ) {} virtual void EndSection( const std::string&, const Counts& ) {} - + virtual void StartTestCase( const Catch::TestCaseInfo& testInfo ) { m_currentStats->m_testCaseStats.push_back( TestCaseStats( testInfo.className, testInfo.name ) ); m_currentTestCaseStats.push_back( &m_currentStats->m_testCaseStats.back() ); } - + virtual void Result( const Catch::AssertionResult& assertionResult ) { if( assertionResult.getResultType() != ResultWas::Ok || m_config.fullConfig()->includeSuccessfulResults() ) { TestCaseStats& testCaseStats = m_currentStats->m_testCaseStats.back(); @@ -153,35 +160,38 @@ namespace Catch { stats.m_element = "* internal error *"; break; } - testCaseStats.m_testStats.push_back( stats ); + testCaseStats.m_testStats.push_back( stats ); } } - + virtual void EndTestCase( const Catch::TestCaseInfo&, const Totals&, const std::string& stdOut, const std::string& stdErr ) { m_currentTestCaseStats.pop_back(); assert( m_currentTestCaseStats.empty() ); TestCaseStats& testCaseStats = m_currentStats->m_testCaseStats.back(); testCaseStats.m_stdOut = stdOut; testCaseStats.m_stdErr = stdErr; + testCaseStats.m_endTime = time_now(); if( !stdOut.empty() ) m_stdOut << stdOut << "\n"; if( !stdErr.empty() ) m_stdErr << stdErr << "\n"; - } + } virtual void Aborted() { // !TBD } virtual void EndTesting( const Totals& ) { + m_testSuiteStats.m_endTime = time_now(); + XmlWriter xml( m_config.stream() ); - + 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->m_name ); @@ -189,25 +199,24 @@ namespace Catch { xml.writeAttribute( "failures", it->m_failuresCount ); xml.writeAttribute( "tests", it->m_testsCount ); xml.writeAttribute( "hostname", "tbd" ); - xml.writeAttribute( "time", "tbd" ); + xml.writeAttribute( "time", time_diff(it->m_endTime, it->m_startTime) ); xml.writeAttribute( "timestamp", "tbd" ); - + OutputTestCases( xml, *it ); } - + xml.scopedElement( "system-out" ).writeText( trim( m_stdOut.str() ), false ); xml.scopedElement( "system-err" ).writeText( trim( m_stdErr.str() ), false ); } - + void OutputTestCases( XmlWriter& xml, const Stats& stats ) { std::vector::const_iterator it = stats.m_testCaseStats.begin(); std::vector::const_iterator itEnd = stats.m_testCaseStats.end(); for(; it != itEnd; ++it ) { - XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); xml.writeAttribute( "classname", it->m_className ); xml.writeAttribute( "name", it->m_name ); - xml.writeAttribute( "time", "tbd" ); + xml.writeAttribute( "time", time_diff(it->m_endTime, it->m_startTime) ); OutputTestResult( xml, *it ); @@ -220,14 +229,14 @@ namespace Catch { } } - + void OutputTestResult( XmlWriter& xml, const TestCaseStats& stats ) { std::vector::const_iterator it = stats.m_testStats.begin(); std::vector::const_iterator itEnd = stats.m_testStats.end(); for(; it != itEnd; ++it ) { if( it->m_element != "success" ) { XmlWriter::ScopedElement e = xml.scopedElement( it->m_element ); - + xml.writeAttribute( "message", it->m_message ); xml.writeAttribute( "type", it->m_resultType ); if( !it->m_content.empty() ) @@ -235,10 +244,10 @@ namespace Catch { } } } - + private: ReporterConfig m_config; - + Stats m_testSuiteStats; Stats* m_currentStats; std::vector m_statsForSuites; @@ -246,7 +255,7 @@ namespace Catch { std::ostringstream m_stdOut; std::ostringstream m_stdErr; }; - + } // end namespace Catch #endif // TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED