Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorPhil Nash2014-12-23 18:54:08 +0000
committerPhil Nash2014-12-23 18:54:08 +0000
commit1cbc4f2c9c4e8bb0bb5a34ccaa43217963efdd3e (patch)
treefa7fe74463f1177b730695a808c4400f76885c09
parent92f0836fd3d683d4237068d6dd602805ad888aee (diff)
parent6e996956103a390046525a88487cbfe739625c45 (diff)
downloadcatch3-1cbc4f2c9c4e8bb0bb5a34ccaa43217963efdd3e.tar.gz
Merge branch 'develop' of https://github.com/SeanCline/Catch into develop
-rw-r--r--docs/build-systems.md45
-rw-r--r--include/internal/catch_impl.hpp2
-rw-r--r--include/reporters/catch_reporter_xml.hpp170
3 files changed, 151 insertions, 66 deletions
diff --git a/docs/build-systems.md b/docs/build-systems.md
index 335b2062..b001dc19 100644
--- a/docs/build-systems.md
+++ b/docs/build-systems.md
@@ -1,5 +1,50 @@
# Integration with build systems
+Build Systems may refer to low-level tools, like CMake, or larger systems that run on servers, like Jenkins or TeamCity. This page will talk about both.
+
+# Continuous Integration systems
+
+Probably the most important aspect to using Catch with a build server is the use of different reporters. Catch comes bundled with three reporters that should cover the majority of build servers out there - although adding more for better integration with some is always a possibility (as has been done with TeamCity).
+
+Two of these reporters are built in (XML and JUnit) and the third (TeamCity) is included as a separate header. It's possible that the other two may be split out in the future too - as that would make the core of Catch smaller for those that don't need them.
+
+## XML Reporter
+```-r xml```
+
+The XML Reporter writes in an XML format that is specific to Catch.
+
+The advantage of this format is that it corresponds well to the way Catch works (especially the more unusual features, such as nested sections) and is a fully streaming format - that is it writes output as it goes, without having to store up all its results before it can start writing.
+
+The disadvantage is that, being specific to Catch, no existing build servers understand the format natively. It can be used as input to an XSLT transformation that could covert it to, say, HTML - although this loses the streaming advantage, of course.
+
+## JUnit Reporter
+```-r junit```
+
+The JUnit Reporter writes in an XML format that mimics the JUnit ANT schema.
+
+The advantage of this format is that the JUnit Ant schema is widely understood by most build servers and so can usually be consumed with no additional work.
+
+The disadvantage is that this schema was designed to correspond to how JUnit works - and there is a significant mismatch with how Catch works. Additionally the format is not streamable (because opening elements hold counts of failed and passing tests as attributes) - so the whole test run must complete before it can be written.
+
+## TeamCity Reporter
+```-r teamcity```
+
+The TeamCity Reporter writes TeamCity service messages to stdout. In order to be able to use this reporter an additional header must also be included.
+
+```catch_reporter_teamcity.hpp``` can be found in the ```include\reporters``` directory. It should be included in the same file that ```#define```s ```CATCH_CONFIG_MAIN``` or ```CATCH_CONFIG_RUNNER```. The ```#include``` should be placed after ```#include```ing Catch itself.
+
+e.g.:
+
+```
+#define CATCH_CONFIG_MAIN
+#include "catch.hpp"
+#include "catch_reporter_teamcity.hpp"
+```
+
+Being specific to TeamCity this is the best reporter to use with it - but it is completely unsuitable for any other purpose. It is a streaming format (it writes as it goes) - although test results don't appear in the TeamCity interface until the completion of a suite (usually the whole test run).
+
+# Low-level tools
+
## CMake
You can use the following CMake script to automatically fetch Catch from github and configure it as an external project:
diff --git a/include/internal/catch_impl.hpp b/include/internal/catch_impl.hpp
index cc03ae08..820b4975 100644
--- a/include/internal/catch_impl.hpp
+++ b/include/internal/catch_impl.hpp
@@ -88,8 +88,6 @@ namespace Catch {
Matchers::Impl::StdString::EndsWith::~EndsWith() {}
void Config::dummy() {}
-
- INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( "xml", XmlReporter )
}
#ifdef __clang__
diff --git a/include/reporters/catch_reporter_xml.hpp b/include/reporters/catch_reporter_xml.hpp
index 4687471a..1b7c0199 100644
--- a/include/reporters/catch_reporter_xml.hpp
+++ b/include/reporters/catch_reporter_xml.hpp
@@ -13,83 +13,93 @@
#include "../internal/catch_capture.hpp"
#include "../internal/catch_reporter_registrars.hpp"
#include "../internal/catch_xmlwriter.hpp"
+#include "../internal/catch_timer.h"
namespace Catch {
- class XmlReporter : public SharedImpl<IReporter> {
+ class XmlReporter : public StreamingReporterBase {
public:
- XmlReporter( ReporterConfig const& config ) : m_config( config ), m_sectionDepth( 0 ) {}
+ XmlReporter( ReporterConfig const& _config )
+ : StreamingReporterBase( _config ),
+ m_sectionDepth( 0 )
+ {}
+ virtual ~XmlReporter();
+
static std::string getDescription() {
return "Reports test results as an XML document";
}
- virtual ~XmlReporter();
-
- private: // IReporter
- virtual bool shouldRedirectStdout() const {
- return true;
+ public: // StreamingReporterBase
+ virtual ReporterPreferences getPreferences() const {
+ ReporterPreferences prefs;
+ prefs.shouldRedirectStdOut = true;
+ return prefs;
}
- virtual void StartTesting() {
- m_xml.setStream( m_config.stream() );
- m_xml.startElement( "Catch" );
- if( !m_config.fullConfig()->name().empty() )
- m_xml.writeAttribute( "name", m_config.fullConfig()->name() );
+ virtual void noMatchingTestCases( std::string const& s ) {
+ StreamingReporterBase::noMatchingTestCases( s );
}
- virtual void EndTesting( const Totals& totals ) {
- m_xml.scopedElement( "OverallResults" )
- .writeAttribute( "successes", totals.assertions.passed )
- .writeAttribute( "failures", totals.assertions.failed )
- .writeAttribute( "expectedFailures", totals.assertions.failedButOk );
- m_xml.endElement();
+ virtual void testRunStarting( TestRunInfo const& testInfo ) {
+ StreamingReporterBase::testRunStarting( testInfo );
+ m_xml.setStream( stream );
+ m_xml.startElement( "Catch" );
+ if( !m_config->name().empty() )
+ m_xml.writeAttribute( "name", m_config->name() );
}
- virtual void StartGroup( const std::string& groupName ) {
+ virtual void testGroupStarting( GroupInfo const& groupInfo ) {
+ StreamingReporterBase::testGroupStarting( groupInfo );
m_xml.startElement( "Group" )
- .writeAttribute( "name", groupName );
+ .writeAttribute( "name", groupInfo.name );
}
- virtual void EndGroup( const std::string&, const Totals& totals ) {
- m_xml.scopedElement( "OverallResults" )
- .writeAttribute( "successes", totals.assertions.passed )
- .writeAttribute( "failures", totals.assertions.failed )
- .writeAttribute( "expectedFailures", totals.assertions.failedButOk );
- m_xml.endElement();
+ virtual void testCaseStarting( TestCaseInfo const& testInfo ) {
+ StreamingReporterBase::testCaseStarting(testInfo);
+ m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) );
+
+ if ( m_config->showDurations() == ShowDurations::Always )
+ m_testCaseTimer.start();
}
- virtual void StartSection( const std::string& sectionName, const std::string& description ) {
+ virtual void sectionStarting( SectionInfo const& sectionInfo ) {
+ StreamingReporterBase::sectionStarting( sectionInfo );
if( m_sectionDepth++ > 0 ) {
m_xml.startElement( "Section" )
- .writeAttribute( "name", trim( sectionName ) )
- .writeAttribute( "description", description );
+ .writeAttribute( "name", trim( sectionInfo.name ) )
+ .writeAttribute( "description", sectionInfo.description );
}
}
- virtual void NoAssertionsInSection( const std::string& ) {}
- virtual void NoAssertionsInTestCase( const std::string& ) {}
- virtual void EndSection( const std::string& /*sectionName*/, const Counts& assertions ) {
- if( --m_sectionDepth > 0 ) {
- m_xml.scopedElement( "OverallResults" )
- .writeAttribute( "successes", assertions.passed )
- .writeAttribute( "failures", assertions.failed )
- .writeAttribute( "expectedFailures", assertions.failedButOk );
- m_xml.endElement();
+ virtual void assertionStarting( AssertionInfo const& ) { }
+
+ virtual bool assertionEnded( AssertionStats const& assertionStats ) {
+ const AssertionResult& assertionResult = assertionStats.assertionResult;
+
+ // Print any info messages in <Info> tags.
+ if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) {
+ for( std::vector<MessageInfo>::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end();
+ it != itEnd;
+ ++it ) {
+ if( it->type == ResultWas::Info ) {
+ m_xml.scopedElement( "Info" )
+ .writeText( it->message );
+ } else if ( it->type == ResultWas::Warning ) {
+ m_xml.scopedElement( "Warning" )
+ .writeText( it->message );
+ }
+ }
}
- }
-
- virtual void StartTestCase( const Catch::TestCaseInfo& testInfo ) {
- m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) );
- m_currentTestSuccess = true;
- }
- virtual void Result( const Catch::AssertionResult& assertionResult ) {
- if( !m_config.fullConfig()->includeSuccessfulResults() && assertionResult.getResultType() == ResultWas::Ok )
- return;
+ // Drop out if result was successful but we're not printing them.
+ if( !m_config->includeSuccessfulResults() && isOk(assertionResult.getResultType()) )
+ return true;
+ // Print the expression if there is one.
if( assertionResult.hasExpression() ) {
m_xml.startElement( "Expression" )
.writeAttribute( "success", assertionResult.succeeded() )
+ .writeAttribute( "type", assertionResult.getTestMacroName() )
.writeAttribute( "filename", assertionResult.getSourceInfo().file )
.writeAttribute( "line", assertionResult.getSourceInfo().line );
@@ -97,23 +107,23 @@ namespace Catch {
.writeText( assertionResult.getExpression() );
m_xml.scopedElement( "Expanded" )
.writeText( assertionResult.getExpandedExpression() );
- m_currentTestSuccess &= assertionResult.succeeded();
}
+ // And... Print a result applicable to each result type.
switch( assertionResult.getResultType() ) {
+ default:
+ break;
case ResultWas::ThrewException:
m_xml.scopedElement( "Exception" )
.writeAttribute( "filename", assertionResult.getSourceInfo().file )
.writeAttribute( "line", assertionResult.getSourceInfo().line )
.writeText( assertionResult.getMessage() );
- m_currentTestSuccess = false;
break;
case ResultWas::FatalErrorCondition:
m_xml.scopedElement( "Fatal Error Condition" )
.writeAttribute( "filename", assertionResult.getSourceInfo().file )
.writeAttribute( "line", assertionResult.getSourceInfo().line )
.writeText( assertionResult.getMessage() );
- m_currentTestSuccess = false;
break;
case ResultWas::Info:
m_xml.scopedElement( "Info" )
@@ -126,36 +136,68 @@ namespace Catch {
case ResultWas::ExplicitFailure:
m_xml.scopedElement( "Failure" )
.writeText( assertionResult.getMessage() );
- m_currentTestSuccess = false;
- break;
- case ResultWas::Unknown:
- case ResultWas::Ok:
- case ResultWas::FailureBit:
- case ResultWas::ExpressionFailed:
- case ResultWas::Exception:
- case ResultWas::DidntThrowException:
break;
}
+
if( assertionResult.hasExpression() )
m_xml.endElement();
+
+ return true;
+ }
+
+ virtual void sectionEnded( SectionStats const& sectionStats ) {
+ StreamingReporterBase::sectionEnded( sectionStats );
+ if( --m_sectionDepth > 0 ) {
+ XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" );
+ e.writeAttribute( "successes", sectionStats.assertions.passed );
+ e.writeAttribute( "failures", sectionStats.assertions.failed );
+ e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk );
+
+ if ( m_config->showDurations() == ShowDurations::Always )
+ e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds );
+
+ m_xml.endElement();
+ }
}
- virtual void Aborted() {
- // !TBD
+ virtual void testCaseEnded( TestCaseStats const& testCaseStats ) {
+ StreamingReporterBase::testCaseEnded( testCaseStats );
+ XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" );
+ e.writeAttribute( "success", testCaseStats.totals.assertions.allPassed() );
+
+ if ( m_config->showDurations() == ShowDurations::Always )
+ e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() );
+
+ m_xml.endElement();
}
- virtual void EndTestCase( const Catch::TestCaseInfo&, const Totals&, const std::string&, const std::string& ) {
- m_xml.scopedElement( "OverallResult" ).writeAttribute( "success", m_currentTestSuccess );
+ virtual void testGroupEnded( TestGroupStats const& testGroupStats ) {
+ StreamingReporterBase::testGroupEnded( testGroupStats );
+ // TODO: Check testGroupStats.aborting and act accordingly.
+ m_xml.scopedElement( "OverallResults" )
+ .writeAttribute( "successes", testGroupStats.totals.assertions.passed )
+ .writeAttribute( "failures", testGroupStats.totals.assertions.failed )
+ .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk );
+ m_xml.endElement();
+ }
+
+ virtual void testRunEnded( TestRunStats const& testRunStats ) {
+ StreamingReporterBase::testRunEnded( testRunStats );
+ m_xml.scopedElement( "OverallResults" )
+ .writeAttribute( "successes", testRunStats.totals.assertions.passed )
+ .writeAttribute( "failures", testRunStats.totals.assertions.failed )
+ .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk );
m_xml.endElement();
}
private:
- ReporterConfig m_config;
- bool m_currentTestSuccess;
+ Timer m_testCaseTimer;
XmlWriter m_xml;
int m_sectionDepth;
};
+ INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter )
+
} // end namespace Catch
#endif // TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED