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...
This commit is contained in:
Kosta 2013-07-22 20:49:54 +02:00
parent 1d3dd71774
commit b80c3b043a

View File

@ -18,52 +18,58 @@
namespace Catch { namespace Catch {
class JunitReporter : public SharedImpl<IReporter> { class JunitReporter : public SharedImpl<IReporter> {
// 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<std::chrono::microseconds>(end - start).count()); }
// C++11 specific end
struct TestStats { struct TestStats {
std::string m_element; std::string m_element;
std::string m_resultType; std::string m_resultType;
std::string m_message; std::string m_message;
std::string m_content; std::string m_content;
}; };
struct TestCaseStats { struct TestCaseStats {
TestCaseStats( const std::string& className, const std::string& name ) TestCaseStats( const std::string& className, const std::string& name )
: m_className( className ), : 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_status;
std::string m_className; std::string m_className;
std::string m_name; std::string m_name;
std::string m_stdOut; std::string m_stdOut;
std::string m_stdErr; std::string m_stdErr;
time_point m_startTime, m_endTime;
std::vector<TestStats> m_testStats; std::vector<TestStats> m_testStats;
std::vector<TestCaseStats> m_sections; std::vector<TestCaseStats> m_sections;
}; };
struct Stats { struct Stats {
Stats( const std::string& name = std::string() ) Stats( const std::string& name = std::string() )
: m_testsCount( 0 ), : m_testsCount( 0 ),
m_failuresCount( 0 ), m_failuresCount( 0 ),
m_disabledCount( 0 ), m_disabledCount( 0 ),
m_errorsCount( 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_testsCount;
std::size_t m_failuresCount; std::size_t m_failuresCount;
std::size_t m_disabledCount; std::size_t m_disabledCount;
std::size_t m_errorsCount; std::size_t m_errorsCount;
double m_timeInSeconds;
std::string m_name; std::string m_name;
time_point m_startTime, m_endTime;
std::vector<TestCaseStats> m_testCaseStats; std::vector<TestCaseStats> m_testCaseStats;
}; };
public: public:
JunitReporter( ReporterConfig const& config ) JunitReporter( ReporterConfig const& config )
: m_config( config ), : m_config( config ),
@ -71,19 +77,19 @@ namespace Catch {
m_currentStats( &m_testSuiteStats ) m_currentStats( &m_testSuiteStats )
{} {}
virtual ~JunitReporter(); virtual ~JunitReporter();
static std::string getDescription() { static std::string getDescription() {
return "Reports test results in an XML format that looks like Ant's junitreport target"; return "Reports test results in an XML format that looks like Ant's junitreport target";
} }
private: // IReporter private: // IReporter
virtual bool shouldRedirectStdout() const { virtual bool shouldRedirectStdout() const {
return true; return true;
} }
virtual void StartTesting(){} virtual void StartTesting(){}
virtual void StartGroup( const std::string& groupName ) { virtual void StartGroup( const std::string& groupName ) {
if( groupName.empty() ) if( groupName.empty() )
m_statsForSuites.push_back( Stats( m_config.fullConfig()->name() ) ); m_statsForSuites.push_back( Stats( m_config.fullConfig()->name() ) );
@ -94,21 +100,22 @@ namespace Catch {
virtual void EndGroup( const std::string&, const Totals& totals ) { virtual void EndGroup( const std::string&, const Totals& totals ) {
m_currentStats->m_testsCount = totals.assertions.total(); m_currentStats->m_testsCount = totals.assertions.total();
m_currentStats->m_endTime = time_now();
m_currentStats = &m_testSuiteStats; m_currentStats = &m_testSuiteStats;
} }
virtual void StartSection( const std::string&, const std::string& ){} virtual void StartSection( const std::string&, const std::string& ){}
virtual void NoAssertionsInSection( const std::string& ) {} virtual void NoAssertionsInSection( const std::string& ) {}
virtual void NoAssertionsInTestCase( const std::string& ) {} virtual void NoAssertionsInTestCase( const std::string& ) {}
virtual void EndSection( const std::string&, const Counts& ) {} virtual void EndSection( const std::string&, const Counts& ) {}
virtual void StartTestCase( const Catch::TestCaseInfo& testInfo ) { virtual void StartTestCase( const Catch::TestCaseInfo& testInfo ) {
m_currentStats->m_testCaseStats.push_back( TestCaseStats( testInfo.className, testInfo.name ) ); m_currentStats->m_testCaseStats.push_back( TestCaseStats( testInfo.className, testInfo.name ) );
m_currentTestCaseStats.push_back( &m_currentStats->m_testCaseStats.back() ); m_currentTestCaseStats.push_back( &m_currentStats->m_testCaseStats.back() );
} }
virtual void Result( const Catch::AssertionResult& assertionResult ) { virtual void Result( const Catch::AssertionResult& assertionResult ) {
if( assertionResult.getResultType() != ResultWas::Ok || m_config.fullConfig()->includeSuccessfulResults() ) { if( assertionResult.getResultType() != ResultWas::Ok || m_config.fullConfig()->includeSuccessfulResults() ) {
TestCaseStats& testCaseStats = m_currentStats->m_testCaseStats.back(); TestCaseStats& testCaseStats = m_currentStats->m_testCaseStats.back();
@ -153,35 +160,38 @@ namespace Catch {
stats.m_element = "* internal error *"; stats.m_element = "* internal error *";
break; 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 ) { virtual void EndTestCase( const Catch::TestCaseInfo&, const Totals&, const std::string& stdOut, const std::string& stdErr ) {
m_currentTestCaseStats.pop_back(); m_currentTestCaseStats.pop_back();
assert( m_currentTestCaseStats.empty() ); assert( m_currentTestCaseStats.empty() );
TestCaseStats& testCaseStats = m_currentStats->m_testCaseStats.back(); TestCaseStats& testCaseStats = m_currentStats->m_testCaseStats.back();
testCaseStats.m_stdOut = stdOut; testCaseStats.m_stdOut = stdOut;
testCaseStats.m_stdErr = stdErr; testCaseStats.m_stdErr = stdErr;
testCaseStats.m_endTime = time_now();
if( !stdOut.empty() ) if( !stdOut.empty() )
m_stdOut << stdOut << "\n"; m_stdOut << stdOut << "\n";
if( !stdErr.empty() ) if( !stdErr.empty() )
m_stdErr << stdErr << "\n"; m_stdErr << stdErr << "\n";
} }
virtual void Aborted() { virtual void Aborted() {
// !TBD // !TBD
} }
virtual void EndTesting( const Totals& ) { virtual void EndTesting( const Totals& ) {
m_testSuiteStats.m_endTime = time_now();
XmlWriter xml( m_config.stream() ); XmlWriter xml( m_config.stream() );
if( m_statsForSuites.size() > 0 ) if( m_statsForSuites.size() > 0 )
xml.startElement( "testsuites" ); xml.startElement( "testsuites" );
std::vector<Stats>::const_iterator it = m_statsForSuites.begin(); std::vector<Stats>::const_iterator it = m_statsForSuites.begin();
std::vector<Stats>::const_iterator itEnd = m_statsForSuites.end(); std::vector<Stats>::const_iterator itEnd = m_statsForSuites.end();
for(; it != itEnd; ++it ) { for(; it != itEnd; ++it ) {
XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" );
xml.writeAttribute( "name", it->m_name ); xml.writeAttribute( "name", it->m_name );
@ -189,25 +199,24 @@ namespace Catch {
xml.writeAttribute( "failures", it->m_failuresCount ); xml.writeAttribute( "failures", it->m_failuresCount );
xml.writeAttribute( "tests", it->m_testsCount ); xml.writeAttribute( "tests", it->m_testsCount );
xml.writeAttribute( "hostname", "tbd" ); xml.writeAttribute( "hostname", "tbd" );
xml.writeAttribute( "time", "tbd" ); xml.writeAttribute( "time", time_diff(it->m_endTime, it->m_startTime) );
xml.writeAttribute( "timestamp", "tbd" ); xml.writeAttribute( "timestamp", "tbd" );
OutputTestCases( xml, *it ); OutputTestCases( xml, *it );
} }
xml.scopedElement( "system-out" ).writeText( trim( m_stdOut.str() ), false ); xml.scopedElement( "system-out" ).writeText( trim( m_stdOut.str() ), false );
xml.scopedElement( "system-err" ).writeText( trim( m_stdErr.str() ), false ); xml.scopedElement( "system-err" ).writeText( trim( m_stdErr.str() ), false );
} }
void OutputTestCases( XmlWriter& xml, const Stats& stats ) { void OutputTestCases( XmlWriter& xml, const Stats& stats ) {
std::vector<TestCaseStats>::const_iterator it = stats.m_testCaseStats.begin(); std::vector<TestCaseStats>::const_iterator it = stats.m_testCaseStats.begin();
std::vector<TestCaseStats>::const_iterator itEnd = stats.m_testCaseStats.end(); std::vector<TestCaseStats>::const_iterator itEnd = stats.m_testCaseStats.end();
for(; it != itEnd; ++it ) { for(; it != itEnd; ++it ) {
XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); XmlWriter::ScopedElement e = xml.scopedElement( "testcase" );
xml.writeAttribute( "classname", it->m_className ); xml.writeAttribute( "classname", it->m_className );
xml.writeAttribute( "name", it->m_name ); xml.writeAttribute( "name", it->m_name );
xml.writeAttribute( "time", "tbd" ); xml.writeAttribute( "time", time_diff(it->m_endTime, it->m_startTime) );
OutputTestResult( xml, *it ); OutputTestResult( xml, *it );
@ -220,14 +229,14 @@ namespace Catch {
} }
} }
void OutputTestResult( XmlWriter& xml, const TestCaseStats& stats ) { void OutputTestResult( XmlWriter& xml, const TestCaseStats& stats ) {
std::vector<TestStats>::const_iterator it = stats.m_testStats.begin(); std::vector<TestStats>::const_iterator it = stats.m_testStats.begin();
std::vector<TestStats>::const_iterator itEnd = stats.m_testStats.end(); std::vector<TestStats>::const_iterator itEnd = stats.m_testStats.end();
for(; it != itEnd; ++it ) { for(; it != itEnd; ++it ) {
if( it->m_element != "success" ) { if( it->m_element != "success" ) {
XmlWriter::ScopedElement e = xml.scopedElement( it->m_element ); XmlWriter::ScopedElement e = xml.scopedElement( it->m_element );
xml.writeAttribute( "message", it->m_message ); xml.writeAttribute( "message", it->m_message );
xml.writeAttribute( "type", it->m_resultType ); xml.writeAttribute( "type", it->m_resultType );
if( !it->m_content.empty() ) if( !it->m_content.empty() )
@ -235,10 +244,10 @@ namespace Catch {
} }
} }
} }
private: private:
ReporterConfig m_config; ReporterConfig m_config;
Stats m_testSuiteStats; Stats m_testSuiteStats;
Stats* m_currentStats; Stats* m_currentStats;
std::vector<Stats> m_statsForSuites; std::vector<Stats> m_statsForSuites;
@ -246,7 +255,7 @@ namespace Catch {
std::ostringstream m_stdOut; std::ostringstream m_stdOut;
std::ostringstream m_stdErr; std::ostringstream m_stdErr;
}; };
} // end namespace Catch } // end namespace Catch
#endif // TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED #endif // TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED