mirror of
https://github.com/catchorg/Catch2.git
synced 2025-08-01 12:55:40 +02:00
Completed CumulativeReporterBase and reimplemented JUnitReporter in terms of it
This commit is contained in:
@@ -116,7 +116,9 @@ namespace Catch {
|
||||
bool empty() const {
|
||||
return file.empty();
|
||||
}
|
||||
|
||||
bool operator == ( SourceLineInfo const& other ) const {
|
||||
return line == other.line && file == other.file;
|
||||
}
|
||||
std::string file;
|
||||
std::size_t line;
|
||||
};
|
||||
|
@@ -58,6 +58,8 @@ namespace Catch {
|
||||
TestCaseStats::~TestCaseStats() {}
|
||||
TestGroupStats::~TestGroupStats() {}
|
||||
TestRunStats::~TestRunStats() {}
|
||||
CumulativeReporterBase::SectionNode::~SectionNode() {}
|
||||
CumulativeReporterBase::~CumulativeReporterBase() {}
|
||||
|
||||
BasicReporter::~BasicReporter() {}
|
||||
StreamingReporterBase::~StreamingReporterBase() {}
|
||||
@@ -84,7 +86,7 @@ namespace Catch {
|
||||
|
||||
INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( "basic", BasicReporter )
|
||||
INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( "xml", XmlReporter )
|
||||
INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( "junit", JunitReporter )
|
||||
INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( "junit2", JunitReporter2 )
|
||||
|
||||
}
|
||||
|
||||
|
@@ -46,20 +46,6 @@ namespace Catch
|
||||
bool shouldRedirectStdOut;
|
||||
};
|
||||
|
||||
|
||||
template<typename T, typename ChildT=T>
|
||||
struct Node : SharedImpl<> {
|
||||
Node( T const& _value, Node* _parent = NULL )
|
||||
: value( _value ),
|
||||
parent( _parent )
|
||||
{}
|
||||
virtual ~Node() {}
|
||||
|
||||
T value;
|
||||
std::vector<Ptr<Node> > children;
|
||||
Node* parent;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct LazyStat : Option<T> {
|
||||
LazyStat() : used( false ) {}
|
||||
@@ -238,8 +224,6 @@ namespace Catch
|
||||
|
||||
struct StreamingReporterBase : SharedImpl<IStreamingReporter> {
|
||||
|
||||
typedef Ptr<Node<SectionInfo> > SectionInfoNode;
|
||||
|
||||
StreamingReporterBase( ReporterConfig const& _config )
|
||||
: m_config( _config.fullConfig() ),
|
||||
stream( _config.stream() )
|
||||
@@ -260,8 +244,7 @@ namespace Catch
|
||||
currentTestCaseInfo = _testInfo;
|
||||
}
|
||||
virtual void sectionStarting( SectionInfo const& _sectionInfo ) {
|
||||
Ptr<Node<SectionInfo> > sectionInfo = new Node<SectionInfo>( _sectionInfo );
|
||||
m_sectionStack.push_back( sectionInfo );
|
||||
m_sectionStack.push_back( _sectionInfo );
|
||||
}
|
||||
|
||||
virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) {
|
||||
@@ -281,30 +264,137 @@ namespace Catch
|
||||
}
|
||||
|
||||
Ptr<IConfig> m_config;
|
||||
std::ostream& stream;
|
||||
|
||||
LazyStat<TestRunInfo> currentTestRunInfo;
|
||||
LazyStat<GroupInfo> currentGroupInfo;
|
||||
LazyStat<TestCaseInfo> currentTestCaseInfo;
|
||||
std::ostream& stream;
|
||||
|
||||
std::vector<SectionInfoNode> m_sectionStack;
|
||||
std::vector<SectionInfo> m_sectionStack;
|
||||
};
|
||||
|
||||
struct CumulativeReporterBase : StreamingReporterBase {
|
||||
virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0;
|
||||
virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0;
|
||||
struct CumulativeReporterBase : SharedImpl<IStreamingReporter> {
|
||||
template<typename T, typename ChildNodeT>
|
||||
struct Node : SharedImpl<> {
|
||||
explicit Node( T const& _value ) : value( _value ) {}
|
||||
virtual ~Node() {}
|
||||
|
||||
virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0;
|
||||
virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0;
|
||||
typedef std::vector<Ptr<ChildNodeT> > ChildNodes;
|
||||
T value;
|
||||
ChildNodes children;
|
||||
};
|
||||
struct SectionNode : SharedImpl<> {
|
||||
explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {}
|
||||
virtual ~SectionNode();
|
||||
|
||||
virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0;
|
||||
bool operator == ( SectionNode const& other ) const {
|
||||
return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo;
|
||||
}
|
||||
bool operator == ( Ptr<SectionNode> const& other ) const {
|
||||
return operator==( *other );
|
||||
}
|
||||
|
||||
virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0;
|
||||
virtual void sectionEnded( SectionStats const& sectionStats ) = 0;
|
||||
virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0;
|
||||
virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0;
|
||||
virtual void testRunEnded( TestRunStats const& testRunStats ) = 0;
|
||||
SectionStats stats;
|
||||
typedef std::vector<Ptr<SectionNode> > ChildSections;
|
||||
typedef std::vector<AssertionStats> Assertions;
|
||||
ChildSections childSections;
|
||||
Assertions assertions;
|
||||
std::string stdOut;
|
||||
std::string stdErr;
|
||||
};
|
||||
friend bool operator == ( Ptr<SectionNode> const& node, SectionInfo const& other ) {
|
||||
return node->stats.sectionInfo.lineInfo == other.lineInfo;
|
||||
}
|
||||
|
||||
typedef Node<TestCaseStats, SectionNode> TestCaseNode;
|
||||
typedef Node<TestGroupStats, TestCaseNode> TestGroupNode;
|
||||
typedef Node<TestRunStats, TestGroupNode> TestRunNode;
|
||||
|
||||
CumulativeReporterBase( ReporterConfig const& _config )
|
||||
: m_config( _config.fullConfig() ),
|
||||
stream( _config.stream() )
|
||||
{}
|
||||
~CumulativeReporterBase();
|
||||
|
||||
virtual void testRunStarting( TestRunInfo const& ) {}
|
||||
virtual void testGroupStarting( GroupInfo const& ) {}
|
||||
|
||||
virtual void testCaseStarting( TestCaseInfo const& ) {}
|
||||
|
||||
virtual void sectionStarting( SectionInfo const& sectionInfo ) {
|
||||
SectionStats incompleteStats( sectionInfo, Counts(), 0, false );
|
||||
Ptr<SectionNode> node;
|
||||
if( m_sectionStack.empty() ) {
|
||||
if( !m_rootSection )
|
||||
m_rootSection = new SectionNode( incompleteStats );
|
||||
node = m_rootSection;
|
||||
}
|
||||
else {
|
||||
SectionNode& parentNode = *m_sectionStack.back();
|
||||
SectionNode::ChildSections::const_iterator it =
|
||||
std::find( parentNode.childSections.begin(), parentNode.childSections.end(), sectionInfo );
|
||||
if( it == parentNode.childSections.end() ) {
|
||||
node = new SectionNode( incompleteStats );
|
||||
parentNode.childSections.push_back( node );
|
||||
}
|
||||
else
|
||||
node = *it;
|
||||
}
|
||||
m_sectionStack.push_back( node );
|
||||
m_deepestSection = node;
|
||||
}
|
||||
|
||||
virtual void assertionStarting( AssertionInfo const& ) {}
|
||||
|
||||
virtual bool assertionEnded( AssertionStats const& assertionStats ) {
|
||||
assert( !m_sectionStack.empty() );
|
||||
SectionNode& sectionNode = *m_sectionStack.back();
|
||||
sectionNode.assertions.push_back( assertionStats );
|
||||
return true;
|
||||
}
|
||||
virtual void sectionEnded( SectionStats const& sectionStats ) {
|
||||
assert( !m_sectionStack.empty() );
|
||||
SectionNode& node = *m_sectionStack.back();
|
||||
node.stats = sectionStats;
|
||||
m_sectionStack.pop_back();
|
||||
}
|
||||
virtual void testCaseEnded( TestCaseStats const& testCaseStats ) {
|
||||
Ptr<TestCaseNode> node = new TestCaseNode( testCaseStats );
|
||||
assert( m_sectionStack.size() == 0 );
|
||||
node->children.push_back( m_rootSection );
|
||||
m_testCases.push_back( node );
|
||||
m_rootSection.reset();
|
||||
|
||||
assert( m_deepestSection );
|
||||
m_deepestSection->stdOut = testCaseStats.stdOut;
|
||||
m_deepestSection->stdErr = testCaseStats.stdErr;
|
||||
}
|
||||
virtual void testGroupEnded( TestGroupStats const& testGroupStats ) {
|
||||
Ptr<TestGroupNode> node = new TestGroupNode( testGroupStats );
|
||||
node->children.swap( m_testCases );
|
||||
m_testGroups.push_back( node );
|
||||
}
|
||||
virtual void testRunEnded( TestRunStats const& testRunStats ) {
|
||||
Ptr<TestRunNode> node = new TestRunNode( testRunStats );
|
||||
node->children.swap( m_testGroups );
|
||||
m_testRuns.push_back( node );
|
||||
testRunEnded();
|
||||
}
|
||||
virtual void testRunEnded() = 0;
|
||||
|
||||
Ptr<IConfig> m_config;
|
||||
std::ostream& stream;
|
||||
std::vector<AssertionStats> m_assertions;
|
||||
std::vector<std::vector<Ptr<SectionNode> > > m_sections;
|
||||
std::vector<Ptr<TestCaseNode> > m_testCases;
|
||||
std::vector<Ptr<TestGroupNode> > m_testGroups;
|
||||
|
||||
std::vector<Ptr<TestRunNode> > m_testRuns;
|
||||
|
||||
Ptr<SectionNode> m_rootSection;
|
||||
Ptr<SectionNode> m_deepestSection;
|
||||
std::vector<Ptr<SectionNode> > m_sectionStack;
|
||||
|
||||
std::vector<Ptr<Node<TestGroupStats> > > m_groups;
|
||||
};
|
||||
|
||||
// Deprecated
|
||||
|
@@ -127,7 +127,7 @@ namespace Catch {
|
||||
AutoReg::AutoReg( TestFunction function,
|
||||
SourceLineInfo const& lineInfo,
|
||||
NameAndDesc const& nameAndDesc ) {
|
||||
registerTestCase( new FreeFunctionTestCase( function ), "global", nameAndDesc, lineInfo );
|
||||
registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo );
|
||||
}
|
||||
|
||||
AutoReg::~AutoReg() {}
|
||||
|
@@ -174,9 +174,17 @@ inline std::string toString( unsigned int value ) {
|
||||
|
||||
inline std::string toString( const double value ) {
|
||||
std::ostringstream oss;
|
||||
oss << std::setprecision (std::numeric_limits<double>::digits10 + 1)
|
||||
oss << std::setprecision( 10 )
|
||||
<< std::fixed
|
||||
<< value;
|
||||
return oss.str();
|
||||
std::string d = oss.str();
|
||||
std::size_t i = d.find_last_not_of( '0' );
|
||||
if( i != std::string::npos && i != d.size()-1 ) {
|
||||
if( d[i] == '.' )
|
||||
i++;
|
||||
d = d.substr( 0, i+1 );
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
inline std::string toString( bool value ) {
|
||||
|
@@ -29,7 +29,6 @@ namespace Catch {
|
||||
ReporterPreferences prefs;
|
||||
prefs.shouldRedirectStdOut = false;
|
||||
return prefs;
|
||||
|
||||
}
|
||||
|
||||
virtual void noMatchingTestCases( std::string const& spec ) {
|
||||
@@ -267,14 +266,14 @@ namespace Catch {
|
||||
if( m_sectionStack.size() > 1 ) {
|
||||
Colour colourGuard( Colour::Headers );
|
||||
|
||||
std::vector<SectionInfoNode>::const_iterator
|
||||
std::vector<SectionInfo>::const_iterator
|
||||
it = m_sectionStack.begin()+1, // Skip first section (test case)
|
||||
itEnd = m_sectionStack.end();
|
||||
for( ; it != itEnd; ++it )
|
||||
printHeaderString( (*it)->value.name, 2 );
|
||||
printHeaderString( it->name, 2 );
|
||||
}
|
||||
|
||||
SourceLineInfo lineInfo = m_sectionStack.front()->value.lineInfo;
|
||||
SourceLineInfo lineInfo = m_sectionStack.front().lineInfo;
|
||||
|
||||
if( !lineInfo.empty() ){
|
||||
stream << getDashes() << "\n";
|
||||
|
@@ -17,7 +17,210 @@
|
||||
|
||||
namespace Catch {
|
||||
|
||||
class JunitReporter : public SharedImpl<IReporter> {
|
||||
class JunitReporter : public CumulativeReporterBase {
|
||||
public:
|
||||
JunitReporter( ReporterConfig const& _config )
|
||||
: CumulativeReporterBase( _config ),
|
||||
xml( _config.stream() )
|
||||
{}
|
||||
|
||||
~JunitReporter();
|
||||
|
||||
static std::string getDescription() {
|
||||
return "Reports test results in an XML format that looks like Ant's junitreport target";
|
||||
}
|
||||
|
||||
virtual void noMatchingTestCases( std::string const& /*spec*/ ) {}
|
||||
|
||||
virtual ReporterPreferences getPreferences() const {
|
||||
ReporterPreferences prefs;
|
||||
prefs.shouldRedirectStdOut = true;
|
||||
return prefs;
|
||||
}
|
||||
|
||||
virtual void testRunStarting( TestRunInfo const& runInfo ) {
|
||||
CumulativeReporterBase::testRunStarting( runInfo );
|
||||
xml.startElement( "testsuites" );
|
||||
}
|
||||
|
||||
virtual void testGroupStarting( GroupInfo const& groupInfo ) {
|
||||
suiteTimer.start();
|
||||
stdOutForSuite.str("");
|
||||
stdErrForSuite.str("");
|
||||
unexpectedExceptions = 0;
|
||||
CumulativeReporterBase::testGroupStarting( groupInfo );
|
||||
}
|
||||
|
||||
virtual bool assertionEnded( AssertionStats const& assertionStats ) {
|
||||
if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException )
|
||||
unexpectedExceptions++;
|
||||
return CumulativeReporterBase::assertionEnded( assertionStats );
|
||||
}
|
||||
|
||||
virtual void testCaseEnded( TestCaseStats const& testCaseStats ) {
|
||||
stdOutForSuite << testCaseStats.stdOut;
|
||||
stdErrForSuite << testCaseStats.stdErr;
|
||||
CumulativeReporterBase::testCaseEnded( testCaseStats );
|
||||
}
|
||||
|
||||
virtual void testGroupEnded( TestGroupStats const& testGroupStats ) {
|
||||
double suiteTime = suiteTimer.getElapsedSeconds();
|
||||
CumulativeReporterBase::testGroupEnded( testGroupStats );
|
||||
writeGroup( *m_testGroups.back(), suiteTime );
|
||||
}
|
||||
|
||||
virtual void testRunEnded() {
|
||||
xml.endElement();
|
||||
}
|
||||
|
||||
void writeGroup( TestGroupNode const& groupNode, double suiteTime ) {
|
||||
XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" );
|
||||
TestGroupStats const& stats = groupNode.value;
|
||||
xml.writeAttribute( "name", stats.groupInfo.name );
|
||||
xml.writeAttribute( "errors", unexpectedExceptions );
|
||||
xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions );
|
||||
xml.writeAttribute( "tests", stats.totals.assertions.total() );
|
||||
xml.writeAttribute( "hostname", "tbd" ); // !TBD
|
||||
if( m_config->showDurations() == ShowDurations::Never )
|
||||
xml.writeAttribute( "time", "" );
|
||||
else
|
||||
xml.writeAttribute( "time", suiteTime );
|
||||
xml.writeAttribute( "timestamp", "tbd" ); // !TBD
|
||||
|
||||
// Write test cases
|
||||
for( TestGroupNode::ChildNodes::const_iterator
|
||||
it = groupNode.children.begin(), itEnd = groupNode.children.end();
|
||||
it != itEnd;
|
||||
++it )
|
||||
writeTestCase( **it );
|
||||
|
||||
xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false );
|
||||
xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false );
|
||||
}
|
||||
|
||||
void writeTestCase( TestCaseNode const& testCaseNode ) {
|
||||
TestCaseStats const& stats = testCaseNode.value;
|
||||
|
||||
// All test cases have exactly one section - which represents the
|
||||
// test case itself. That section may have 0-n nested sections
|
||||
assert( testCaseNode.children.size() == 1 );
|
||||
SectionNode const& rootSection = *testCaseNode.children.front();
|
||||
|
||||
std::string className = stats.testInfo.className;
|
||||
|
||||
if( className.empty() ) {
|
||||
if( rootSection.childSections.empty() )
|
||||
className = "global";
|
||||
}
|
||||
writeSection( className, "", rootSection );
|
||||
}
|
||||
|
||||
void writeSection( std::string const& className,
|
||||
std::string const& rootName,
|
||||
SectionNode const& sectionNode ) {
|
||||
std::string name = trim( sectionNode.stats.sectionInfo.name );
|
||||
if( !rootName.empty() )
|
||||
name = rootName + "/" + name;
|
||||
|
||||
if( !sectionNode.assertions.empty() ||
|
||||
!sectionNode.stdOut.empty() ||
|
||||
!sectionNode.stdErr.empty() ) {
|
||||
XmlWriter::ScopedElement e = xml.scopedElement( "testcase" );
|
||||
if( className.empty() ) {
|
||||
xml.writeAttribute( "classname", name );
|
||||
xml.writeAttribute( "name", "root" );
|
||||
}
|
||||
else {
|
||||
xml.writeAttribute( "classname", className );
|
||||
xml.writeAttribute( "name", name );
|
||||
}
|
||||
xml.writeAttribute( "time", toString( sectionNode.stats.durationInSeconds ) );
|
||||
|
||||
writeAssertions( sectionNode );
|
||||
|
||||
if( !sectionNode.stdOut.empty() )
|
||||
xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false );
|
||||
if( !sectionNode.stdErr.empty() )
|
||||
xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false );
|
||||
}
|
||||
for( SectionNode::ChildSections::const_iterator
|
||||
it = sectionNode.childSections.begin(),
|
||||
itEnd = sectionNode.childSections.end();
|
||||
it != itEnd;
|
||||
++it )
|
||||
if( className.empty() )
|
||||
writeSection( name, "", **it );
|
||||
else
|
||||
writeSection( className, name, **it );
|
||||
}
|
||||
|
||||
void writeAssertions( SectionNode const& sectionNode ) {
|
||||
for( SectionNode::Assertions::const_iterator
|
||||
it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end();
|
||||
it != itEnd;
|
||||
++it )
|
||||
writeAssertion( *it );
|
||||
}
|
||||
void writeAssertion( AssertionStats const& stats ) {
|
||||
AssertionResult const& result = stats.assertionResult;
|
||||
if( !result.isOk() ) {
|
||||
std::string elementName;
|
||||
switch( result.getResultType() ) {
|
||||
case ResultWas::ThrewException:
|
||||
elementName = "error";
|
||||
break;
|
||||
case ResultWas::ExplicitFailure:
|
||||
elementName = "failure";
|
||||
break;
|
||||
case ResultWas::ExpressionFailed:
|
||||
elementName = "failure";
|
||||
break;
|
||||
case ResultWas::DidntThrowException:
|
||||
elementName = "failure";
|
||||
break;
|
||||
|
||||
// We should never see these here:
|
||||
case ResultWas::Info:
|
||||
case ResultWas::Warning:
|
||||
case ResultWas::Ok:
|
||||
case ResultWas::Unknown:
|
||||
case ResultWas::FailureBit:
|
||||
case ResultWas::Exception:
|
||||
elementName = "internalError";
|
||||
break;
|
||||
}
|
||||
|
||||
XmlWriter::ScopedElement e = xml.scopedElement( elementName );
|
||||
|
||||
xml.writeAttribute( "message", result.getExpandedExpression() );
|
||||
xml.writeAttribute( "type", result.getTestMacroName() );
|
||||
|
||||
std::ostringstream oss;
|
||||
if( !result.getMessage().empty() )
|
||||
oss << result.getMessage() << "\n";
|
||||
for( std::vector<MessageInfo>::const_iterator
|
||||
it = stats.infoMessages.begin(),
|
||||
itEnd = stats.infoMessages.end();
|
||||
it != itEnd;
|
||||
++it )
|
||||
if( it->type == ResultWas::Info )
|
||||
oss << it->message << "\n";
|
||||
|
||||
oss << "at " << result.getSourceInfo();
|
||||
xml.writeText( oss.str(), false );
|
||||
}
|
||||
}
|
||||
|
||||
XmlWriter xml;
|
||||
Timer suiteTimer;
|
||||
std::ostringstream stdOutForSuite;
|
||||
std::ostringstream stdErrForSuite;
|
||||
unsigned int unexpectedExceptions;
|
||||
};
|
||||
|
||||
INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter )
|
||||
|
||||
class JunitReporter2 : public SharedImpl<IReporter> {
|
||||
|
||||
struct TestStats {
|
||||
std::string m_element;
|
||||
@@ -65,12 +268,12 @@ namespace Catch {
|
||||
};
|
||||
|
||||
public:
|
||||
JunitReporter( ReporterConfig const& config )
|
||||
JunitReporter2( ReporterConfig const& config )
|
||||
: m_config( config ),
|
||||
m_testSuiteStats( "AllTests" ),
|
||||
m_currentStats( &m_testSuiteStats )
|
||||
{}
|
||||
virtual ~JunitReporter();
|
||||
// virtual ~JunitReporter2();
|
||||
|
||||
static std::string getDescription() {
|
||||
return "Reports test results in an XML format that looks like Ant's junitreport target";
|
||||
|
Reference in New Issue
Block a user