mirror of
https://github.com/catchorg/Catch2.git
synced 2024-11-17 03:02:24 +01:00
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:
parent
1d3dd71774
commit
b80c3b043a
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user