mirror of
https://github.com/catchorg/Catch2.git
synced 2024-11-17 03:02:24 +01:00
JunitReporter reimplemented using the new IStreamingReporter interface
* created new AccumulatingReporterBase class for accumulating test results hierarchically and store them for a single processResults() call after all tests have been executed; sections are currently not handled, since their usage are optional and/or could be nested arbitrarily, which would result in overly complex code, IMHO * JunitReporter reimplemented on top of this new AccumulatingReporterBase class * added support for tracking time spend in each test case, each test group, and overall tests to the base "*Stats" classes; this enables each reporter (derived from IStreamingReporter interface) to report the timings; for now only the JunitReporter takes advantage of that.
This commit is contained in:
parent
b80c3b043a
commit
d4fa900b84
@ -58,11 +58,10 @@ namespace Catch {
|
|||||||
TestGroupStats::~TestGroupStats() {}
|
TestGroupStats::~TestGroupStats() {}
|
||||||
TestRunStats::~TestRunStats() {}
|
TestRunStats::~TestRunStats() {}
|
||||||
ThreadedSectionInfo::~ThreadedSectionInfo() {}
|
ThreadedSectionInfo::~ThreadedSectionInfo() {}
|
||||||
TestGroupNode::~TestGroupNode() {}
|
|
||||||
TestRunNode::~TestRunNode() {}
|
|
||||||
|
|
||||||
BasicReporter::~BasicReporter() {}
|
BasicReporter::~BasicReporter() {}
|
||||||
StreamingReporterBase::~StreamingReporterBase() {}
|
StreamingReporterBase::~StreamingReporterBase() {}
|
||||||
|
AccumulatingReporterBase::~AccumulatingReporterBase() {}
|
||||||
ConsoleReporter::~ConsoleReporter() {}
|
ConsoleReporter::~ConsoleReporter() {}
|
||||||
IRunner::~IRunner() {}
|
IRunner::~IRunner() {}
|
||||||
IMutableContext::~IMutableContext() {}
|
IMutableContext::~IMutableContext() {}
|
||||||
@ -86,7 +85,7 @@ namespace Catch {
|
|||||||
|
|
||||||
INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( "basic", BasicReporter )
|
INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( "basic", BasicReporter )
|
||||||
INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( "xml", XmlReporter )
|
INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( "xml", XmlReporter )
|
||||||
INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( "junit", JunitReporter )
|
INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter )
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
#include "catch_message.h"
|
#include "catch_message.h"
|
||||||
#include "catch_option.hpp"
|
#include "catch_option.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <map>
|
#include <map>
|
||||||
@ -47,10 +48,11 @@ namespace Catch
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct TestRunInfo {
|
struct TestRunInfo {
|
||||||
TestRunInfo( std::string const& _name ) : name( _name ) {}
|
TestRunInfo( std::string const& _name = "" ) : name( _name ) {}
|
||||||
std::string name;
|
std::string name;
|
||||||
};
|
};
|
||||||
struct GroupInfo {
|
struct GroupInfo {
|
||||||
|
GroupInfo() : groupIndex( 0 ), groupsCounts( 0 ) {}
|
||||||
GroupInfo( std::string const& _name,
|
GroupInfo( std::string const& _name,
|
||||||
std::size_t _groupIndex,
|
std::size_t _groupIndex,
|
||||||
std::size_t _groupsCount )
|
std::size_t _groupsCount )
|
||||||
@ -65,6 +67,7 @@ namespace Catch
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct SectionInfo {
|
struct SectionInfo {
|
||||||
|
SectionInfo() {}
|
||||||
SectionInfo( std::string const& _name,
|
SectionInfo( std::string const& _name,
|
||||||
std::string const& _description,
|
std::string const& _description,
|
||||||
SourceLineInfo const& _lineInfo )
|
SourceLineInfo const& _lineInfo )
|
||||||
@ -113,6 +116,7 @@ namespace Catch
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct SectionStats {
|
struct SectionStats {
|
||||||
|
SectionStats() : missingAssertions( false ) {}
|
||||||
SectionStats( SectionInfo const& _sectionInfo,
|
SectionStats( SectionInfo const& _sectionInfo,
|
||||||
Counts const& _assertions,
|
Counts const& _assertions,
|
||||||
bool _missingAssertions )
|
bool _missingAssertions )
|
||||||
@ -128,14 +132,20 @@ namespace Catch
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct TestCaseStats {
|
struct TestCaseStats {
|
||||||
|
TestCaseStats()
|
||||||
|
: missingAssertions( false ),
|
||||||
|
aborting( false)
|
||||||
|
{}
|
||||||
TestCaseStats( TestCaseInfo const& _testInfo,
|
TestCaseStats( TestCaseInfo const& _testInfo,
|
||||||
Totals const& _totals,
|
Totals const& _totals,
|
||||||
|
double _timeSecs,
|
||||||
std::string const& _stdOut,
|
std::string const& _stdOut,
|
||||||
std::string const& _stdErr,
|
std::string const& _stdErr,
|
||||||
bool _missingAssertions,
|
bool _missingAssertions,
|
||||||
bool _aborting )
|
bool _aborting )
|
||||||
: testInfo( _testInfo ),
|
: testInfo( _testInfo ),
|
||||||
totals( _totals ),
|
totals( _totals ),
|
||||||
|
timeSecs( _timeSecs ),
|
||||||
stdOut( _stdOut ),
|
stdOut( _stdOut ),
|
||||||
stdErr( _stdErr ),
|
stdErr( _stdErr ),
|
||||||
missingAssertions( _missingAssertions ),
|
missingAssertions( _missingAssertions ),
|
||||||
@ -145,6 +155,7 @@ namespace Catch
|
|||||||
|
|
||||||
TestCaseInfo testInfo;
|
TestCaseInfo testInfo;
|
||||||
Totals totals;
|
Totals totals;
|
||||||
|
double timeSecs;
|
||||||
std::string stdOut;
|
std::string stdOut;
|
||||||
std::string stdErr;
|
std::string stdErr;
|
||||||
bool missingAssertions;
|
bool missingAssertions;
|
||||||
@ -152,41 +163,51 @@ namespace Catch
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct TestGroupStats {
|
struct TestGroupStats {
|
||||||
|
TestGroupStats()
|
||||||
|
: aborting( false )
|
||||||
|
{}
|
||||||
|
|
||||||
TestGroupStats( GroupInfo const& _groupInfo,
|
TestGroupStats( GroupInfo const& _groupInfo,
|
||||||
Totals const& _totals,
|
Totals const& _totals,
|
||||||
|
double _timeSecs,
|
||||||
bool _aborting )
|
bool _aborting )
|
||||||
: groupInfo( _groupInfo ),
|
: groupInfo( _groupInfo ),
|
||||||
totals( _totals ),
|
totals( _totals ),
|
||||||
|
timeSecs( _timeSecs ),
|
||||||
aborting( _aborting )
|
aborting( _aborting )
|
||||||
{}
|
{}
|
||||||
TestGroupStats( GroupInfo const& _groupInfo )
|
TestGroupStats( GroupInfo const& _groupInfo )
|
||||||
: groupInfo( _groupInfo ),
|
: groupInfo( _groupInfo ),
|
||||||
|
timeSecs( 0.0 ),
|
||||||
aborting( false )
|
aborting( false )
|
||||||
{}
|
{}
|
||||||
virtual ~TestGroupStats();
|
virtual ~TestGroupStats();
|
||||||
|
|
||||||
GroupInfo groupInfo;
|
GroupInfo groupInfo;
|
||||||
Totals totals;
|
Totals totals;
|
||||||
|
double timeSecs;
|
||||||
bool aborting;
|
bool aborting;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TestRunStats {
|
struct TestRunStats {
|
||||||
|
TestRunStats()
|
||||||
|
: timeSecs( 0.0 ),
|
||||||
|
aborting( false )
|
||||||
|
{}
|
||||||
TestRunStats( TestRunInfo const& _runInfo,
|
TestRunStats( TestRunInfo const& _runInfo,
|
||||||
Totals const& _totals,
|
Totals const& _totals,
|
||||||
|
double _timeSecs,
|
||||||
bool _aborting )
|
bool _aborting )
|
||||||
: runInfo( _runInfo ),
|
: runInfo( _runInfo ),
|
||||||
totals( _totals ),
|
totals( _totals ),
|
||||||
|
timeSecs( _timeSecs ),
|
||||||
aborting( _aborting )
|
aborting( _aborting )
|
||||||
{}
|
{}
|
||||||
TestRunStats( TestRunStats const& _other )
|
|
||||||
: runInfo( _other.runInfo ),
|
|
||||||
totals( _other.totals ),
|
|
||||||
aborting( _other.aborting )
|
|
||||||
{}
|
|
||||||
virtual ~TestRunStats();
|
virtual ~TestRunStats();
|
||||||
|
|
||||||
TestRunInfo runInfo;
|
TestRunInfo runInfo;
|
||||||
Totals totals;
|
Totals totals;
|
||||||
|
double timeSecs;
|
||||||
bool aborting;
|
bool aborting;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -276,19 +297,81 @@ namespace Catch
|
|||||||
std::vector<Ptr<ThreadedSectionInfo> > m_rootSections;
|
std::vector<Ptr<ThreadedSectionInfo> > m_rootSections;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TestGroupNode : TestGroupStats {
|
struct AccumulatingReporterBase : SharedImpl<IStreamingReporter> {
|
||||||
TestGroupNode( TestGroupStats const& _stats ) : TestGroupStats( _stats ) {}
|
protected:
|
||||||
// TestGroupNode( GroupInfo const& _info ) : TestGroupStats( _stats ) {}
|
struct AccumTestCaseStats {
|
||||||
~TestGroupNode();
|
TestCaseStats testCase;
|
||||||
|
std::vector<AssertionStats> tests;
|
||||||
|
};
|
||||||
|
struct AccumTestGroupStats {
|
||||||
|
TestGroupStats testGroup;
|
||||||
|
std::vector<AccumTestCaseStats> testCases;
|
||||||
|
};
|
||||||
|
struct AccumTestRunStats {
|
||||||
|
TestRunStats testRun;
|
||||||
|
std::vector<AccumTestGroupStats> testGroups;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TestRunNode : TestRunStats {
|
public:
|
||||||
|
virtual ~AccumulatingReporterBase();
|
||||||
|
|
||||||
TestRunNode( TestRunStats const& _stats ) : TestRunStats( _stats ) {}
|
virtual void testRunStarting( TestRunInfo const& testRunInfo ) {
|
||||||
~TestRunNode();
|
(void)testRunInfo;
|
||||||
|
assert( m_accumGroups.empty() );
|
||||||
|
assert( m_accumCases.empty() );
|
||||||
|
assert( m_accumTests.empty() );
|
||||||
|
}
|
||||||
|
virtual void testGroupStarting( GroupInfo const& groupInfo ) {
|
||||||
|
(void)groupInfo;
|
||||||
|
assert( m_accumCases.empty() );
|
||||||
|
assert( m_accumTests.empty() );
|
||||||
|
}
|
||||||
|
virtual void testCaseStarting( TestCaseInfo const& testInfo ) {
|
||||||
|
(void)testInfo;
|
||||||
|
assert( m_accumTests.empty() );
|
||||||
|
}
|
||||||
|
virtual void sectionStarting( SectionInfo const& sectionInfo ) {
|
||||||
|
(void)sectionInfo;
|
||||||
|
}
|
||||||
|
virtual void assertionStarting( AssertionInfo const& assertionInfo ) {
|
||||||
|
(void)assertionInfo;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<TestGroupNode> groups;
|
virtual void assertionEnded( AssertionStats const& assertionStats ) {
|
||||||
|
m_accumTests.push_back( assertionStats );
|
||||||
|
}
|
||||||
|
virtual void sectionEnded( SectionStats const& sectionStats ) {
|
||||||
|
(void)sectionStats;
|
||||||
|
}
|
||||||
|
virtual void testCaseEnded( TestCaseStats const& testCaseStats ) {
|
||||||
|
AccumTestCaseStats stats;
|
||||||
|
stats.testCase = testCaseStats;
|
||||||
|
std::swap(stats.tests, m_accumTests);
|
||||||
|
m_accumCases.push_back( stats );
|
||||||
|
}
|
||||||
|
virtual void testGroupEnded( TestGroupStats const& testGroupStats ) {
|
||||||
|
assert( m_accumTests.empty() );
|
||||||
|
AccumTestGroupStats stats;
|
||||||
|
stats.testGroup = testGroupStats;
|
||||||
|
std::swap(stats.testCases, m_accumCases);
|
||||||
|
m_accumGroups.push_back( stats );
|
||||||
|
}
|
||||||
|
virtual void testRunEnded( TestRunStats const& testRunStats ) {
|
||||||
|
assert( m_accumTests.empty() );
|
||||||
|
assert( m_accumCases.empty() );
|
||||||
|
AccumTestRunStats stats;
|
||||||
|
stats.testRun = testRunStats;
|
||||||
|
std::swap(stats.testGroups, m_accumGroups);
|
||||||
|
|
||||||
|
processResults( stats );
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void processResults( AccumTestRunStats const& testRun ) = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<AccumTestGroupStats> m_accumGroups;
|
||||||
|
std::vector<AccumTestCaseStats> m_accumCases;
|
||||||
|
std::vector<AssertionStats> m_accumTests;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
@ -327,8 +410,9 @@ namespace Catch
|
|||||||
};
|
};
|
||||||
|
|
||||||
inline std::string trim( std::string const& str ) {
|
inline std::string trim( std::string const& str ) {
|
||||||
std::string::size_type start = str.find_first_not_of( "\n\r\t " );
|
const char* const stripAway = "\n\r\t ";
|
||||||
std::string::size_type end = str.find_last_not_of( "\n\r\t " );
|
std::string::size_type start = str.find_first_not_of( stripAway );
|
||||||
|
std::string::size_type end = str.find_last_not_of( stripAway );
|
||||||
|
|
||||||
return start != std::string::npos ? str.substr( start, 1+end-start ) : "";
|
return start != std::string::npos ? str.substr( start, 1+end-start ) : "";
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,20 @@
|
|||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
// C++11 specific start
|
||||||
|
#include <chrono>
|
||||||
|
// C++11 specific end
|
||||||
|
|
||||||
namespace Catch {
|
namespace Catch {
|
||||||
|
|
||||||
|
// C++11 specific start
|
||||||
|
typedef decltype(std::chrono::high_resolution_clock::now()) time_point;
|
||||||
|
static inline time_point time_now() { return std::chrono::high_resolution_clock::now(); }
|
||||||
|
static inline double time_diff_secs(time_point const& end, time_point const& start) {
|
||||||
|
return (1e-6 * std::chrono::duration_cast<std::chrono::microseconds>(end - start).count());
|
||||||
|
}
|
||||||
|
// C++11 specific end
|
||||||
|
|
||||||
class StreamRedirect {
|
class StreamRedirect {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -64,7 +76,9 @@ namespace Catch {
|
|||||||
m_reporter( reporter ),
|
m_reporter( reporter ),
|
||||||
m_prevRunner( &m_context.getRunner() ),
|
m_prevRunner( &m_context.getRunner() ),
|
||||||
m_prevResultCapture( &m_context.getResultCapture() ),
|
m_prevResultCapture( &m_context.getResultCapture() ),
|
||||||
m_prevConfig( m_context.getConfig() )
|
m_prevConfig( m_context.getConfig() ),
|
||||||
|
m_startRun( time_now() ),
|
||||||
|
m_startGroup( m_startRun )
|
||||||
{
|
{
|
||||||
m_context.setRunner( this );
|
m_context.setRunner( this );
|
||||||
m_context.setConfig( m_config );
|
m_context.setConfig( m_config );
|
||||||
@ -73,7 +87,8 @@ namespace Catch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
virtual ~RunContext() {
|
virtual ~RunContext() {
|
||||||
m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) );
|
double timing = time_diff_secs( time_now() , m_startRun );
|
||||||
|
m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, timing, aborting() ) );
|
||||||
m_context.setRunner( m_prevRunner );
|
m_context.setRunner( m_prevRunner );
|
||||||
m_context.setConfig( NULL );
|
m_context.setConfig( NULL );
|
||||||
m_context.setResultCapture( m_prevResultCapture );
|
m_context.setResultCapture( m_prevResultCapture );
|
||||||
@ -82,9 +97,11 @@ namespace Catch {
|
|||||||
|
|
||||||
void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) {
|
void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) {
|
||||||
m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) );
|
m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) );
|
||||||
|
m_startGroup = time_now();
|
||||||
}
|
}
|
||||||
void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) {
|
void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) {
|
||||||
m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) );
|
double timing = time_diff_secs( time_now() , m_startGroup );
|
||||||
|
m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, timing, aborting() ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
Totals runMatching( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) {
|
Totals runMatching( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) {
|
||||||
@ -116,6 +133,7 @@ namespace Catch {
|
|||||||
|
|
||||||
m_runningTest = new RunningTest( testCase );
|
m_runningTest = new RunningTest( testCase );
|
||||||
|
|
||||||
|
time_point timeStart = time_now();
|
||||||
do {
|
do {
|
||||||
do {
|
do {
|
||||||
runCurrentTest( redirectedCout, redirectedCerr );
|
runCurrentTest( redirectedCout, redirectedCerr );
|
||||||
@ -123,6 +141,7 @@ namespace Catch {
|
|||||||
while( m_runningTest->hasUntestedSections() && !aborting() );
|
while( m_runningTest->hasUntestedSections() && !aborting() );
|
||||||
}
|
}
|
||||||
while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() );
|
while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() );
|
||||||
|
time_point timeEnd = time_now();
|
||||||
|
|
||||||
Totals deltaTotals = m_totals.delta( prevTotals );
|
Totals deltaTotals = m_totals.delta( prevTotals );
|
||||||
bool missingAssertions = false;
|
bool missingAssertions = false;
|
||||||
@ -136,6 +155,7 @@ namespace Catch {
|
|||||||
|
|
||||||
m_reporter->testCaseEnded( TestCaseStats( testInfo,
|
m_reporter->testCaseEnded( TestCaseStats( testInfo,
|
||||||
deltaTotals,
|
deltaTotals,
|
||||||
|
time_diff_secs( timeEnd, timeStart ),
|
||||||
redirectedCout,
|
redirectedCout,
|
||||||
redirectedCerr,
|
redirectedCerr,
|
||||||
missingAssertions,
|
missingAssertions,
|
||||||
@ -323,6 +343,8 @@ namespace Catch {
|
|||||||
Ptr<IConfig const> m_prevConfig;
|
Ptr<IConfig const> m_prevConfig;
|
||||||
AssertionInfo m_lastAssertionInfo;
|
AssertionInfo m_lastAssertionInfo;
|
||||||
std::vector<UnfinishedSections> m_unfinishedSections;
|
std::vector<UnfinishedSections> m_unfinishedSections;
|
||||||
|
time_point m_startRun;
|
||||||
|
time_point m_startGroup;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // end namespace Catch
|
} // end namespace Catch
|
||||||
|
@ -24,6 +24,7 @@ namespace Catch {
|
|||||||
struct ITestCase;
|
struct ITestCase;
|
||||||
|
|
||||||
struct TestCaseInfo {
|
struct TestCaseInfo {
|
||||||
|
TestCaseInfo() : isHidden( false ) {}
|
||||||
TestCaseInfo( std::string const& _name,
|
TestCaseInfo( std::string const& _name,
|
||||||
std::string const& _className,
|
std::string const& _className,
|
||||||
std::string const& _description,
|
std::string const& _description,
|
||||||
@ -31,8 +32,6 @@ namespace Catch {
|
|||||||
bool _isHidden,
|
bool _isHidden,
|
||||||
SourceLineInfo const& _lineInfo );
|
SourceLineInfo const& _lineInfo );
|
||||||
|
|
||||||
TestCaseInfo( TestCaseInfo const& other );
|
|
||||||
|
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string className;
|
std::string className;
|
||||||
std::string description;
|
std::string description;
|
||||||
|
@ -51,16 +51,6 @@ namespace Catch {
|
|||||||
tagsAsString = oss.str();
|
tagsAsString = oss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
TestCaseInfo::TestCaseInfo( TestCaseInfo const& other )
|
|
||||||
: name( other.name ),
|
|
||||||
className( other.className ),
|
|
||||||
description( other.description ),
|
|
||||||
tags( other.tags ),
|
|
||||||
tagsAsString( other.tagsAsString ),
|
|
||||||
lineInfo( other.lineInfo ),
|
|
||||||
isHidden( other.isHidden )
|
|
||||||
{}
|
|
||||||
|
|
||||||
TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {}
|
TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {}
|
||||||
|
|
||||||
TestCase::TestCase( TestCase const& other )
|
TestCase::TestCase( TestCase const& other )
|
||||||
|
@ -17,243 +17,152 @@
|
|||||||
|
|
||||||
namespace Catch {
|
namespace Catch {
|
||||||
|
|
||||||
class JunitReporter : public SharedImpl<IReporter> {
|
class JunitReporter : public SharedImpl<AccumulatingReporterBase> {
|
||||||
|
|
||||||
// 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 {
|
|
||||||
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_startTime( time_now() ), m_endTime( m_startTime )
|
|
||||||
{}
|
|
||||||
|
|
||||||
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<TestStats> m_testStats;
|
|
||||||
std::vector<TestCaseStats> m_sections;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Stats {
|
|
||||||
|
|
||||||
Stats( const std::string& name = std::string() )
|
|
||||||
: m_testsCount( 0 ),
|
|
||||||
m_failuresCount( 0 ),
|
|
||||||
m_disabledCount( 0 ),
|
|
||||||
m_errorsCount( 0 ),
|
|
||||||
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;
|
|
||||||
std::string m_name;
|
|
||||||
time_point m_startTime, m_endTime;
|
|
||||||
std::vector<TestCaseStats> m_testCaseStats;
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
JunitReporter( ReporterConfig const& config )
|
JunitReporter( ReporterConfig const& config ) : m_config( config ) {}
|
||||||
: m_config( config ),
|
|
||||||
m_testSuiteStats( "AllTests" ),
|
|
||||||
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
|
virtual ReporterPreferences getPreferences() const {
|
||||||
|
ReporterPreferences prefs;
|
||||||
virtual bool shouldRedirectStdout() const {
|
prefs.shouldRedirectStdOut = true;
|
||||||
return true;
|
return prefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void StartTesting(){}
|
virtual void noMatchingTestCases( std::string const& spec ) {
|
||||||
|
(void)spec;
|
||||||
virtual void StartGroup( const std::string& groupName ) {
|
|
||||||
if( groupName.empty() )
|
|
||||||
m_statsForSuites.push_back( Stats( m_config.fullConfig()->name() ) );
|
|
||||||
else
|
|
||||||
m_statsForSuites.push_back( Stats( groupName ) );
|
|
||||||
m_currentStats = &m_statsForSuites.back();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void EndGroup( const std::string&, const Totals& totals ) {
|
virtual void processResults( AccumTestRunStats const& stats ) {
|
||||||
m_currentStats->m_testsCount = totals.assertions.total();
|
XmlWriter xml( m_config.stream() );
|
||||||
m_currentStats->m_endTime = time_now();
|
OutputTestSuites( xml, stats );
|
||||||
m_currentStats = &m_testSuiteStats;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void StartSection( const std::string&, const std::string& ){}
|
private:
|
||||||
|
static void OutputTestSuites( XmlWriter& xml, AccumTestRunStats const& stats ) {
|
||||||
|
xml.startElement( "testsuites" );
|
||||||
|
|
||||||
virtual void NoAssertionsInSection( const std::string& ) {}
|
xml.writeAttribute( "time", stats.testRun.timeSecs );
|
||||||
virtual void NoAssertionsInTestCase( const std::string& ) {}
|
|
||||||
|
|
||||||
virtual void EndSection( const std::string&, const Counts& ) {}
|
std::vector<AccumTestGroupStats>::const_iterator it = stats.testGroups.begin();
|
||||||
|
std::vector<AccumTestGroupStats>::const_iterator itEnd = stats.testGroups.end();
|
||||||
|
|
||||||
virtual void StartTestCase( const Catch::TestCaseInfo& testInfo ) {
|
std::ostringstream stdErr, stdOut;
|
||||||
m_currentStats->m_testCaseStats.push_back( TestCaseStats( testInfo.className, testInfo.name ) );
|
for( ; it != itEnd; ++it ) {
|
||||||
m_currentTestCaseStats.push_back( &m_currentStats->m_testCaseStats.back() );
|
OutputTestSuite( xml, *it);
|
||||||
|
CollectErrAndOutMessages( *it, stdErr, stdOut );
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void Result( const Catch::AssertionResult& assertionResult ) {
|
OutputTextIfNotEmpty( xml, "system-out", stdOut.str() );
|
||||||
if( assertionResult.getResultType() != ResultWas::Ok || m_config.fullConfig()->includeSuccessfulResults() ) {
|
OutputTextIfNotEmpty( xml, "system-err", stdErr.str() );
|
||||||
TestCaseStats& testCaseStats = m_currentStats->m_testCaseStats.back();
|
}
|
||||||
TestStats stats;
|
|
||||||
std::ostringstream oss;
|
|
||||||
if( !assertionResult.getMessage().empty() )
|
|
||||||
oss << assertionResult.getMessage() << " at ";
|
|
||||||
oss << assertionResult.getSourceInfo();
|
|
||||||
stats.m_content = oss.str();
|
|
||||||
stats.m_message = assertionResult.getExpandedExpression();
|
|
||||||
stats.m_resultType = assertionResult.getTestMacroName();
|
|
||||||
|
|
||||||
switch( assertionResult.getResultType() ) {
|
static void OutputTestSuite( XmlWriter& xml, AccumTestGroupStats const& stats ) {
|
||||||
case ResultWas::ThrewException:
|
size_t errors = 0, failures = 0;
|
||||||
stats.m_element = "error";
|
CountErrorAndFailures(stats, errors, failures);
|
||||||
m_currentStats->m_errorsCount++;
|
|
||||||
break;
|
XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" );
|
||||||
case ResultWas::Info:
|
xml.writeAttribute( "name", stats.testGroup.groupInfo.name );
|
||||||
stats.m_element = "info"; // !TBD ?
|
xml.writeAttribute( "errors", errors );
|
||||||
break;
|
xml.writeAttribute( "failures", failures );
|
||||||
case ResultWas::Warning:
|
xml.writeAttribute( "tests", stats.testCases.size() );
|
||||||
stats.m_element = "warning"; // !TBD ?
|
xml.writeAttribute( "hostname", "tbd" );
|
||||||
break;
|
xml.writeAttribute( "time", stats.testGroup.timeSecs );
|
||||||
|
xml.writeAttribute( "timestamp", "tbd" );
|
||||||
|
|
||||||
|
std::vector<AccumTestCaseStats>::const_iterator it2 = stats.testCases.begin();
|
||||||
|
std::vector<AccumTestCaseStats>::const_iterator it2End = stats.testCases.end();
|
||||||
|
for(; it2 != it2End; ++it2 ) {
|
||||||
|
OutputTestCase( xml, *it2 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OutputTestCase( XmlWriter& xml, AccumTestCaseStats const& stats ) {
|
||||||
|
XmlWriter::ScopedElement e = xml.scopedElement( "testcase" );
|
||||||
|
xml.writeAttribute( "classname", stats.testCase.testInfo.className );
|
||||||
|
xml.writeAttribute( "name", stats.testCase.testInfo.name );
|
||||||
|
xml.writeAttribute( "time", stats.testCase.timeSecs );
|
||||||
|
|
||||||
|
std::vector<AssertionStats>::const_iterator it = stats.tests.begin();
|
||||||
|
std::vector<AssertionStats>::const_iterator itEnd = stats.tests.end();
|
||||||
|
for( ; it != itEnd; ++it ) {
|
||||||
|
OutputTestResult( xml, *it );
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputTextIfNotEmpty( xml, "system-out", stats.testCase.stdOut );
|
||||||
|
OutputTextIfNotEmpty( xml, "system-err", stats.testCase.stdErr );
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string GetResultTag( AssertionStats const& test ) {
|
||||||
|
switch(test.assertionResult.getResultType()) {
|
||||||
|
case ResultWas::Ok: return "success";
|
||||||
|
case ResultWas::ThrewException: return "error";
|
||||||
|
case ResultWas::Info: return "info";
|
||||||
|
case ResultWas::Warning: return "warning";
|
||||||
case ResultWas::ExplicitFailure:
|
case ResultWas::ExplicitFailure:
|
||||||
stats.m_element = "failure";
|
|
||||||
m_currentStats->m_failuresCount++;
|
|
||||||
break;
|
|
||||||
case ResultWas::ExpressionFailed:
|
case ResultWas::ExpressionFailed:
|
||||||
stats.m_element = "failure";
|
case ResultWas::DidntThrowException: return "failure";
|
||||||
m_currentStats->m_failuresCount++;
|
|
||||||
break;
|
|
||||||
case ResultWas::Ok:
|
|
||||||
stats.m_element = "success";
|
|
||||||
break;
|
|
||||||
case ResultWas::DidntThrowException:
|
|
||||||
stats.m_element = "failure";
|
|
||||||
m_currentStats->m_failuresCount++;
|
|
||||||
break;
|
|
||||||
case ResultWas::Unknown:
|
case ResultWas::Unknown:
|
||||||
case ResultWas::FailureBit:
|
case ResultWas::FailureBit:
|
||||||
case ResultWas::Exception:
|
case ResultWas::Exception:
|
||||||
stats.m_element = "* internal error *";
|
default: return "* internal error *";
|
||||||
break;
|
|
||||||
}
|
|
||||||
testCaseStats.m_testStats.push_back( stats );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void EndTestCase( const Catch::TestCaseInfo&, const Totals&, const std::string& stdOut, const std::string& stdErr ) {
|
static void OutputTestResult( XmlWriter& xml, AssertionStats const& test ) {
|
||||||
m_currentTestCaseStats.pop_back();
|
std::string tag = GetResultTag(test);
|
||||||
assert( m_currentTestCaseStats.empty() );
|
if( tag != "success" ) {
|
||||||
TestCaseStats& testCaseStats = m_currentStats->m_testCaseStats.back();
|
XmlWriter::ScopedElement e = xml.scopedElement( tag );
|
||||||
testCaseStats.m_stdOut = stdOut;
|
|
||||||
testCaseStats.m_stdErr = stdErr;
|
xml.writeAttribute( "message", test.assertionResult.getExpandedExpression() );
|
||||||
testCaseStats.m_endTime = time_now();
|
xml.writeAttribute( "type", test.assertionResult.getTestMacroName() );
|
||||||
if( !stdOut.empty() )
|
|
||||||
m_stdOut << stdOut << "\n";
|
std::ostringstream oss;
|
||||||
if( !stdErr.empty() )
|
if( !test.assertionResult.getMessage().empty() ) {
|
||||||
m_stdErr << stdErr << "\n";
|
oss << test.assertionResult.getMessage() << " at ";
|
||||||
|
}
|
||||||
|
oss << test.assertionResult.getSourceInfo();
|
||||||
|
xml.writeText( oss.str() );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void Aborted() {
|
static void OutputTextIfNotEmpty( XmlWriter& xml, std::string const& elementName, std::string const& text ) {
|
||||||
// !TBD
|
std::string trimmed = trim( text );
|
||||||
|
if( !trimmed.empty() ) {
|
||||||
|
xml.scopedElement( elementName ).writeText( trimmed, false );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void EndTesting( const Totals& ) {
|
static void CountErrorAndFailures(AccumTestGroupStats const& stats, size_t& outErrors, size_t& outFailures) {
|
||||||
m_testSuiteStats.m_endTime = time_now();
|
std::vector<AccumTestCaseStats>::const_iterator it = stats.testCases.begin();
|
||||||
|
std::vector<AccumTestCaseStats>::const_iterator itEnd = stats.testCases.end();
|
||||||
XmlWriter xml( m_config.stream() );
|
|
||||||
|
|
||||||
if( m_statsForSuites.size() > 0 )
|
|
||||||
xml.startElement( "testsuites" );
|
|
||||||
|
|
||||||
std::vector<Stats>::const_iterator it = m_statsForSuites.begin();
|
|
||||||
std::vector<Stats>::const_iterator itEnd = m_statsForSuites.end();
|
|
||||||
|
|
||||||
for( ; it != itEnd; ++it ) {
|
for( ; it != itEnd; ++it ) {
|
||||||
XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" );
|
std::vector<AssertionStats>::const_iterator it2 = it->tests.begin();
|
||||||
xml.writeAttribute( "name", it->m_name );
|
std::vector<AssertionStats>::const_iterator it2End = it->tests.end();
|
||||||
xml.writeAttribute( "errors", it->m_errorsCount );
|
for( ; it2 != it2End; ++it2 ) {
|
||||||
xml.writeAttribute( "failures", it->m_failuresCount );
|
std::string tag = GetResultTag(*it2);
|
||||||
xml.writeAttribute( "tests", it->m_testsCount );
|
if( tag == "error" ) { ++outErrors; }
|
||||||
xml.writeAttribute( "hostname", "tbd" );
|
if( tag == "failure" ) { ++outFailures; }
|
||||||
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 );
|
static void CollectErrAndOutMessages(AccumTestGroupStats const& stats, std::ostream& outErr, std::ostream& outOut) {
|
||||||
xml.scopedElement( "system-err" ).writeText( trim( m_stdErr.str() ), false );
|
std::vector<AccumTestCaseStats>::const_iterator it = stats.testCases.begin();
|
||||||
}
|
std::vector<AccumTestCaseStats>::const_iterator itEnd = stats.testCases.end();
|
||||||
|
|
||||||
void OutputTestCases( XmlWriter& xml, const Stats& stats ) {
|
|
||||||
std::vector<TestCaseStats>::const_iterator it = stats.m_testCaseStats.begin();
|
|
||||||
std::vector<TestCaseStats>::const_iterator itEnd = stats.m_testCaseStats.end();
|
|
||||||
for( ; it != itEnd; ++it ) {
|
for( ; it != itEnd; ++it ) {
|
||||||
XmlWriter::ScopedElement e = xml.scopedElement( "testcase" );
|
std::string err = trim( it->testCase.stdErr );
|
||||||
xml.writeAttribute( "classname", it->m_className );
|
if( !err.empty() ) { outErr << err << std::endl; }
|
||||||
xml.writeAttribute( "name", it->m_name );
|
std::string out = trim( it->testCase.stdOut );
|
||||||
xml.writeAttribute( "time", time_diff(it->m_endTime, it->m_startTime) );
|
if( !out.empty() ) { outOut << out << std::endl; }
|
||||||
|
|
||||||
OutputTestResult( xml, *it );
|
|
||||||
|
|
||||||
std::string stdOut = trim( it->m_stdOut );
|
|
||||||
if( !stdOut.empty() )
|
|
||||||
xml.scopedElement( "system-out" ).writeText( stdOut, false );
|
|
||||||
std::string stdErr = trim( it->m_stdErr );
|
|
||||||
if( !stdErr.empty() )
|
|
||||||
xml.scopedElement( "system-err" ).writeText( stdErr, false );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OutputTestResult( XmlWriter& xml, const TestCaseStats& stats ) {
|
|
||||||
std::vector<TestStats>::const_iterator it = stats.m_testStats.begin();
|
|
||||||
std::vector<TestStats>::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() )
|
|
||||||
xml.writeText( it->m_content );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ReporterConfig m_config;
|
ReporterConfig m_config;
|
||||||
|
|
||||||
Stats m_testSuiteStats;
|
|
||||||
Stats* m_currentStats;
|
|
||||||
std::vector<Stats> m_statsForSuites;
|
|
||||||
std::vector<const TestCaseStats*> m_currentTestCaseStats;
|
|
||||||
std::ostringstream m_stdOut;
|
|
||||||
std::ostringstream m_stdErr;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // end namespace Catch
|
} // end namespace Catch
|
||||||
|
Loading…
Reference in New Issue
Block a user