diff --git a/README.md b/README.md index b7dcefcb..d766bc65 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![catch logo](catch-logo-small.png) -*v1.0 build 6 (master branch)* +*v1.0 build 7 (master branch)* # New release with significant changes diff --git a/include/internal/catch_version.hpp b/include/internal/catch_version.hpp index b83e7532..c3fd6eb8 100644 --- a/include/internal/catch_version.hpp +++ b/include/internal/catch_version.hpp @@ -13,7 +13,7 @@ namespace Catch { // These numbers are maintained by a script - Version libraryVersion( 1, 0, 6, "master" ); + Version libraryVersion( 1, 0, 7, "master" ); } #endif // TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED diff --git a/single_include/catch.hpp b/single_include/catch.hpp index 552a09c2..3d66de79 100644 --- a/single_include/catch.hpp +++ b/single_include/catch.hpp @@ -1,6 +1,6 @@ /* - * CATCH v1.0 build 6 (master branch) - * Generated: 2013-07-25 08:17:49.210246 + * CATCH v1.0 build 7 (master branch) + * Generated: 2013-08-15 18:59:14.088519 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. @@ -208,7 +208,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; }; @@ -795,9 +797,17 @@ inline std::string toString( unsigned int value ) { inline std::string toString( const double value ) { std::ostringstream oss; - oss << std::setprecision (std::numeric_limits::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 ) { @@ -1408,7 +1418,7 @@ namespace Catch { virtual void assertionEnded( AssertionResult const& result ) = 0; virtual bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) = 0; - virtual void sectionEnded( SectionInfo const& name, Counts const& assertions ) = 0; + virtual void sectionEnded( SectionInfo const& name, Counts const& assertions, double _durationInSeconds ) = 0; virtual void pushScopedMessage( MessageInfo const& message ) = 0; virtual void popScopedMessage( MessageInfo const& message ) = 0; @@ -1952,6 +1962,23 @@ namespace Catch { namespace Catch { + struct Verbosity { enum Level { + NoOutput = 0, + Quiet, + Normal + }; }; + + struct WarnAbout { enum What { + Nothing = 0x00, + NoAssertions = 0x01 + }; }; + + struct ShowDurations { enum OrNot { + DefaultForReporter, + Always, + Never + }; }; + struct IConfig : IShared { virtual ~IConfig(); @@ -1963,6 +1990,7 @@ namespace Catch { virtual bool shouldDebugBreak() const = 0; virtual bool warnAboutMissingAssertions() const = 0; virtual int abortAfter() const = 0; + virtual ShowDurations::OrNot showDurations() const = 0; }; } @@ -2070,17 +2098,6 @@ namespace Catch { struct ConfigData { - struct Verbosity { enum Level { - NoOutput = 0, - Quiet, - Normal - }; }; - - struct WarnAbout { enum What { - Nothing = 0x00, - NoAssertions = 0x01 - }; }; - ConfigData() : listTests( false ), listTags( false ), @@ -2091,7 +2108,8 @@ namespace Catch { showHelp( false ), abortAfter( -1 ), verbosity( Verbosity::Normal ), - warnings( WarnAbout::Nothing ) + warnings( WarnAbout::Nothing ), + showDurations( ShowDurations::DefaultForReporter ) {} bool listTests; @@ -2107,6 +2125,7 @@ namespace Catch { Verbosity::Level verbosity; WarnAbout::What warnings; + ShowDurations::OrNot showDurations; std::string reporterName; std::string outputFilename; @@ -2209,7 +2228,8 @@ namespace Catch { virtual std::ostream& stream() const { return m_os; } virtual std::string name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } virtual bool includeSuccessfulResults() const { return m_data.showSuccessfulTests; } - virtual bool warnAboutMissingAssertions() const { return m_data.warnings & ConfigData::WarnAbout::NoAssertions; } + virtual bool warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; } + virtual ShowDurations::OrNot showDurations() const { return m_data.showDurations; } private: ConfigData m_data; @@ -2313,6 +2333,21 @@ namespace Catch bool shouldRedirectStdOut; }; + template + struct LazyStat : Option { + LazyStat() : used( false ) {} + LazyStat& operator=( T const& _value ) { + Option::operator=( _value ); + used = false; + return *this; + } + void reset() { + Option::reset(); + used = false; + } + bool used; + }; + struct TestRunInfo { TestRunInfo( std::string const& _name ) : name( _name ) {} std::string name; @@ -2345,17 +2380,6 @@ namespace Catch SourceLineInfo lineInfo; }; - struct ThreadedSectionInfo : SectionInfo, SharedImpl<> { - ThreadedSectionInfo( SectionInfo const& _sectionInfo, ThreadedSectionInfo* _parent = NULL ) - : SectionInfo( _sectionInfo ), - parent( _parent ) - {} - virtual ~ThreadedSectionInfo(); - - std::vector > children; - ThreadedSectionInfo* parent; - }; - struct AssertionStats { AssertionStats( AssertionResult const& _assertionResult, std::vector const& _infoMessages, @@ -2384,15 +2408,18 @@ namespace Catch struct SectionStats { SectionStats( SectionInfo const& _sectionInfo, Counts const& _assertions, + double _durationInSeconds, bool _missingAssertions ) : sectionInfo( _sectionInfo ), assertions( _assertions ), + durationInSeconds( _durationInSeconds ), missingAssertions( _missingAssertions ) {} virtual ~SectionStats(); SectionInfo sectionInfo; Counts assertions; + double durationInSeconds; bool missingAssertions; }; @@ -2401,13 +2428,11 @@ namespace Catch Totals const& _totals, std::string const& _stdOut, std::string const& _stdErr, - bool _missingAssertions, bool _aborting ) : testInfo( _testInfo ), totals( _totals ), stdOut( _stdOut ), stdErr( _stdErr ), - missingAssertions( _missingAssertions ), aborting( _aborting ) {} virtual ~TestCaseStats(); @@ -2416,7 +2441,6 @@ namespace Catch Totals totals; std::string stdOut; std::string stdErr; - bool missingAssertions; bool aborting; }; @@ -2496,68 +2520,167 @@ namespace Catch virtual void noMatchingTestCases( std::string const& ) {} virtual void testRunStarting( TestRunInfo const& _testRunInfo ) { - testRunInfo = _testRunInfo; + currentTestRunInfo = _testRunInfo; } virtual void testGroupStarting( GroupInfo const& _groupInfo ) { - unusedGroupInfo = _groupInfo; + currentGroupInfo = _groupInfo; } virtual void testCaseStarting( TestCaseInfo const& _testInfo ) { - unusedTestCaseInfo = _testInfo; + currentTestCaseInfo = _testInfo; } virtual void sectionStarting( SectionInfo const& _sectionInfo ) { - Ptr sectionInfo = new ThreadedSectionInfo( _sectionInfo ); - if( !currentSectionInfo ) { - currentSectionInfo = sectionInfo; - m_rootSections.push_back( currentSectionInfo ); - } - else { - currentSectionInfo->children.push_back( sectionInfo ); - sectionInfo->parent = currentSectionInfo.get(); - currentSectionInfo = sectionInfo; - } + m_sectionStack.push_back( _sectionInfo ); } virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) { - currentSectionInfo = currentSectionInfo->parent; + m_sectionStack.pop_back(); } virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) { - unusedTestCaseInfo.reset(); + currentTestCaseInfo.reset(); + assert( m_sectionStack.empty() ); } virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) { - unusedGroupInfo.reset(); + currentGroupInfo.reset(); } virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) { - currentSectionInfo.reset(); - unusedTestCaseInfo.reset(); - unusedGroupInfo.reset(); - testRunInfo.reset(); + currentTestCaseInfo.reset(); + currentGroupInfo.reset(); + currentTestRunInfo.reset(); } Ptr m_config; - Option testRunInfo; - Option unusedGroupInfo; - Option unusedTestCaseInfo; - Ptr currentSectionInfo; std::ostream& stream; - // !TBD: This should really go in the TestCaseStats class - std::vector > m_rootSections; + LazyStat currentTestRunInfo; + LazyStat currentGroupInfo; + LazyStat currentTestCaseInfo; + + std::vector m_sectionStack; }; - struct TestGroupNode : TestGroupStats { - TestGroupNode( TestGroupStats const& _stats ) : TestGroupStats( _stats ) {} -// TestGroupNode( GroupInfo const& _info ) : TestGroupStats( _stats ) {} - ~TestGroupNode(); + struct CumulativeReporterBase : SharedImpl { + template + struct Node : SharedImpl<> { + explicit Node( T const& _value ) : value( _value ) {} + virtual ~Node() {} - }; + typedef std::vector > ChildNodes; + T value; + ChildNodes children; + }; + struct SectionNode : SharedImpl<> { + explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {} + virtual ~SectionNode(); - struct TestRunNode : TestRunStats { + bool operator == ( SectionNode const& other ) const { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + bool operator == ( Ptr const& other ) const { + return operator==( *other ); + } - TestRunNode( TestRunStats const& _stats ) : TestRunStats( _stats ) {} - ~TestRunNode(); + SectionStats stats; + typedef std::vector > ChildSections; + typedef std::vector Assertions; + ChildSections childSections; + Assertions assertions; + std::string stdOut; + std::string stdErr; + }; + friend bool operator == ( Ptr const& node, SectionInfo const& other ) { + return node->stats.sectionInfo.lineInfo == other.lineInfo; + } + + typedef Node TestCaseNode; + typedef Node TestGroupNode; + typedef Node 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 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 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 node = new TestGroupNode( testGroupStats ); + node->children.swap( m_testCases ); + m_testGroups.push_back( node ); + } + virtual void testRunEnded( TestRunStats const& testRunStats ) { + Ptr node = new TestRunNode( testRunStats ); + node->children.swap( m_testGroups ); + m_testRuns.push_back( node ); + testRunEnded(); + } + virtual void testRunEnded() = 0; + + Ptr m_config; + std::ostream& stream; + std::vector m_assertions; + std::vector > > m_sections; + std::vector > m_testCases; + std::vector > m_testGroups; + + std::vector > m_testRuns; + + Ptr m_rootSection; + Ptr m_deepestSection; + std::vector > m_sectionStack; - std::vector groups; }; // Deprecated @@ -2595,8 +2718,9 @@ namespace Catch }; inline std::string trim( std::string const& str ) { - std::string::size_type start = str.find_first_not_of( "\n\r\t " ); - std::string::size_type end = str.find_last_not_of( "\n\r\t " ); + static char const* whitespaceChars = "\n\r\t "; + std::string::size_type start = str.find_first_not_of( whitespaceChars ); + std::string::size_type end = str.find_last_not_of( whitespaceChars ); return start != std::string::npos ? str.substr( start, 1+end-start ) : ""; } @@ -2788,6 +2912,31 @@ struct TestFailureException{}; // #included from: internal/catch_section.hpp #define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED +// #included from: catch_timer.h +#define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED + +#ifdef WIN32 +typedef unsigned long long uint64_t; +#else +#include +#endif + +namespace Catch { + + class Timer { + public: + Timer() : m_ticks( 0 ) {} + void start(); + unsigned int getElapsedNanoseconds() const; + unsigned int getElapsedMilliseconds() const; + double getElapsedSeconds() const; + + private: + uint64_t m_ticks; + }; + +} // namespace Catch + #include namespace Catch { @@ -2799,11 +2948,13 @@ namespace Catch { std::string const& description = "" ) : m_info( name, description, lineInfo ), m_sectionIncluded( getCurrentContext().getResultCapture().sectionStarted( m_info, m_assertions ) ) - {} + { + m_timer.start(); + } ~Section() { if( m_sectionIncluded ) - getCurrentContext().getResultCapture().sectionEnded( m_info, m_assertions ); + getCurrentContext().getResultCapture().sectionEnded( m_info, m_assertions, m_timer.getElapsedSeconds() ); } // This indicates whether the section should be executed or not @@ -2817,6 +2968,7 @@ namespace Catch { std::string m_name; Counts m_assertions; bool m_sectionIncluded; + Timer m_timer; }; } // end namespace Catch @@ -4208,14 +4360,19 @@ namespace Catch { inline void addWarning( ConfigData& config, std::string const& _warning ) { if( _warning == "NoAssertions" ) - config.warnings = (ConfigData::WarnAbout::What)( config.warnings | ConfigData::WarnAbout::NoAssertions ); + config.warnings = (WarnAbout::What)( config.warnings | WarnAbout::NoAssertions ); else throw std::runtime_error( "Unrecognised warning: '" + _warning + "'" ); } inline void setVerbosity( ConfigData& config, int level ) { // !TBD: accept strings? - config.verbosity = (ConfigData::Verbosity::Level)level; + config.verbosity = (Verbosity::Level)level; + } + inline void setShowDurations( ConfigData& config, bool _showDurations ) { + config.showDurations = _showDurations + ? ShowDurations::Always + : ShowDurations::Never; } inline Clara::CommandLine makeCommandLineParser() { @@ -4305,6 +4462,12 @@ namespace Catch { .describe( "which test or tests to use" ) .argName( "test name, pattern or tags" ); + cli.bind( &setShowDurations ) + .describe( "show test durations" ) + .shortOpt( "d") + .longOpt( "durations" ) + .argName( "durations" ); + return cli; } @@ -4802,20 +4965,11 @@ namespace Catch { while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); Totals deltaTotals = m_totals.delta( prevTotals ); - bool missingAssertions = false; - if( deltaTotals.assertions.total() == 0 && m_config->warnAboutMissingAssertions() ) { - m_totals.assertions.failed++; - deltaTotals = m_totals.delta( prevTotals ); - missingAssertions = true; - } - m_totals.testCases += deltaTotals.testCases; - m_reporter->testCaseEnded( TestCaseStats( testInfo, deltaTotals, redirectedCout, redirectedCerr, - missingAssertions, aborting() ) ); m_activeTestCase = NULL; @@ -4869,26 +5023,28 @@ namespace Catch { return true; } + bool testForMissingAssertions( Counts& assertions ) { + if( assertions.total() != 0 || + !m_config->warnAboutMissingAssertions() || + m_testCaseTracker->currentSectionHasChildren() ) + return false; + m_totals.assertions.failed++; + assertions.failed++; + return true; + } - virtual void sectionEnded( SectionInfo const& info, Counts const& prevAssertions ) { + virtual void sectionEnded( SectionInfo const& info, Counts const& prevAssertions, double _durationInSeconds ) { if( std::uncaught_exception() ) { - m_unfinishedSections.push_back( UnfinishedSections( info, prevAssertions ) ); + m_unfinishedSections.push_back( UnfinishedSections( info, prevAssertions, _durationInSeconds ) ); return; } Counts assertions = m_totals.assertions - prevAssertions; - bool missingAssertions = false; - if( assertions.total() == 0 && - m_config->warnAboutMissingAssertions() && - !m_testCaseTracker->currentSectionHasChildren() ) { - m_totals.assertions.failed++; - assertions.failed++; - missingAssertions = true; + bool missingAssertions = testForMissingAssertions( assertions ); - } m_testCaseTracker->leaveSection(); - m_reporter->sectionEnded( SectionStats( info, assertions, missingAssertions ) ); + m_reporter->sectionEnded( SectionStats( info, assertions, _durationInSeconds, missingAssertions ) ); m_messages.clear(); } @@ -4942,10 +5098,14 @@ namespace Catch { TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); SectionInfo testCaseSection( testCaseInfo.name, testCaseInfo.description, testCaseInfo.lineInfo ); m_reporter->sectionStarting( testCaseSection ); + Counts prevAssertions = m_totals.assertions; + double duration = 0; try { m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal ); TestCaseTracker::Guard guard( *m_testCaseTracker ); + Timer timer; + timer.start(); if( m_reporter->getPreferences().shouldRedirectStdOut ) { StreamRedirect coutRedir( std::cout, redirectedCout ); StreamRedirect cerrRedir( std::cerr, redirectedCerr ); @@ -4954,6 +5114,7 @@ namespace Catch { else { m_activeTestCase->invoke(); } + duration = timer.getElapsedSeconds(); } catch( TestFailureException& ) { // This just means the test was aborted due to failure @@ -4969,21 +5130,26 @@ namespace Catch { itEnd = m_unfinishedSections.end(); it != itEnd; ++it ) - sectionEnded( it->info, it->prevAssertions ); + sectionEnded( it->info, it->prevAssertions, it->durationInSeconds ); m_unfinishedSections.clear(); m_messages.clear(); - SectionStats testCaseSectionStats( testCaseSection, Counts(), 0 ); // !TBD + + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions( assertions ); + + SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions ); m_reporter->sectionEnded( testCaseSectionStats ); } private: struct UnfinishedSections { - UnfinishedSections( SectionInfo const& _info, Counts const& _prevAssertions ) - : info( _info ), prevAssertions( _prevAssertions ) + UnfinishedSections( SectionInfo const& _info, Counts const& _prevAssertions, double _durationInSeconds ) + : info( _info ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) {} SectionInfo info; Counts prevAssertions; + double durationInSeconds; }; TestRunInfo m_runInfo; @@ -5372,7 +5538,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() {} @@ -6181,7 +6347,7 @@ namespace Catch { namespace Catch { // These numbers are maintained by a script - Version libraryVersion( 1, 0, 6, "master" ); + Version libraryVersion( 1, 0, 7, "master" ); } // #included from: catch_text.hpp @@ -6384,8 +6550,6 @@ namespace Catch m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions ); } void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) { - if( testCaseStats.missingAssertions ) - m_legacyReporter->NoAssertionsInTestCase( testCaseStats.testInfo.name ); m_legacyReporter->EndTestCase ( testCaseStats.testInfo, testCaseStats.totals, @@ -6402,6 +6566,60 @@ namespace Catch } } +// #included from: catch_timer.hpp + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++11-long-long" +#endif + +#ifdef WIN32 +#include +#else +#include +#endif + +namespace Catch { + + namespace { +#ifdef WIN32 + uint64_t getCurrentTicks() { + static uint64_t hz=0, hzo=0; + if (!hz) { + QueryPerformanceFrequency((LARGE_INTEGER*)&hz); + QueryPerformanceCounter((LARGE_INTEGER*)&hzo); + } + uint64_t t; + QueryPerformanceCounter((LARGE_INTEGER*)&t); + return ((t-hzo)*1000000)/hz; + } +#else + uint64_t getCurrentTicks() { + timeval t; + gettimeofday(&t,NULL); + return (uint64_t)t.tv_sec * 1000000ull + (uint64_t)t.tv_usec; + } +#endif + } + + void Timer::start() { + m_ticks = getCurrentTicks(); + } + unsigned int Timer::getElapsedNanoseconds() const { + return (unsigned int)(getCurrentTicks() - m_ticks); + } + unsigned int Timer::getElapsedMilliseconds() const { + return (unsigned int)((getCurrentTicks() - m_ticks)/1000); + } + double Timer::getElapsedSeconds() const { + return (getCurrentTicks() - m_ticks)/1000000.0; + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif // #included from: ../reporters/catch_reporter_basic.hpp #define TWOBLUECUBES_CATCH_REPORTER_BASIC_HPP_INCLUDED @@ -7153,235 +7371,209 @@ namespace Catch { namespace Catch { - class JunitReporter : public SharedImpl { - - 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 ) - {} - - double m_timeInSeconds; - std::string m_status; - std::string m_className; - std::string m_name; - std::string m_stdOut; - std::string m_stdErr; - std::vector m_testStats; - std::vector m_sections; - }; - - struct Stats { - - Stats( const std::string& name = std::string() ) - : m_testsCount( 0 ), - m_failuresCount( 0 ), - m_disabledCount( 0 ), - m_errorsCount( 0 ), - m_timeInSeconds( 0 ), - m_name( name ) - {} - - std::size_t m_testsCount; - std::size_t m_failuresCount; - std::size_t m_disabledCount; - std::size_t m_errorsCount; - double m_timeInSeconds; - std::string m_name; - - std::vector m_testCaseStats; - }; - + class JunitReporter : public CumulativeReporterBase { public: - JunitReporter( ReporterConfig const& config ) - : m_config( config ), - m_testSuiteStats( "AllTests" ), - m_currentStats( &m_testSuiteStats ) + JunitReporter( ReporterConfig const& _config ) + : CumulativeReporterBase( _config ), + xml( _config.stream() ) {} - virtual ~JunitReporter(); + + ~JunitReporter(); static std::string getDescription() { return "Reports test results in an XML format that looks like Ant's junitreport target"; } - private: // IReporter + virtual void noMatchingTestCases( std::string const& /*spec*/ ) {} - virtual bool shouldRedirectStdout() const { - return true; + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = true; + return prefs; } - virtual void StartTesting(){} + virtual void testRunStarting( TestRunInfo const& runInfo ) { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + } - virtual void StartGroup( const std::string& groupName ) { - if( groupName.empty() ) - m_statsForSuites.push_back( Stats( m_config.fullConfig()->name() ) ); + 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 - m_statsForSuites.push_back( Stats( groupName ) ); - m_currentStats = &m_statsForSuites.back(); + 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 ); } - virtual void EndGroup( const std::string&, const Totals& totals ) { - m_currentStats->m_testsCount = totals.assertions.total(); - m_currentStats = &m_testSuiteStats; + 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 ); } - virtual void StartSection( const std::string&, const std::string& ){} + 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; - virtual void NoAssertionsInSection( const std::string& ) {} - virtual void NoAssertionsInTestCase( const std::string& ) {} + 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 ) ); - virtual void EndSection( const std::string&, const Counts& ) {} + writeAssertions( sectionNode ); - virtual void StartTestCase( const Catch::TestCaseInfo& testInfo ) { - m_currentStats->m_testCaseStats.push_back( TestCaseStats( testInfo.className, testInfo.name ) ); - m_currentTestCaseStats.push_back( &m_currentStats->m_testCaseStats.back() ); + 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 ); } - virtual void Result( const Catch::AssertionResult& assertionResult ) { - if( assertionResult.getResultType() != ResultWas::Ok || m_config.fullConfig()->includeSuccessfulResults() ) { - 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() ) { + 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: - stats.m_element = "error"; - m_currentStats->m_errorsCount++; - break; - case ResultWas::Info: - stats.m_element = "info"; // !TBD ? - break; - case ResultWas::Warning: - stats.m_element = "warning"; // !TBD ? + elementName = "error"; break; case ResultWas::ExplicitFailure: - stats.m_element = "failure"; - m_currentStats->m_failuresCount++; + elementName = "failure"; break; case ResultWas::ExpressionFailed: - stats.m_element = "failure"; - m_currentStats->m_failuresCount++; - break; - case ResultWas::Ok: - stats.m_element = "success"; + elementName = "failure"; break; case ResultWas::DidntThrowException: - stats.m_element = "failure"; - m_currentStats->m_failuresCount++; + 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: - stats.m_element = "* internal error *"; + elementName = "internalError"; break; } - testCaseStats.m_testStats.push_back( stats ); + + 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::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 ); } } - virtual void EndTestCase( const Catch::TestCaseInfo&, const Totals&, const std::string& stdOut, const std::string& stdErr ) { - m_currentTestCaseStats.pop_back(); - assert( m_currentTestCaseStats.empty() ); - TestCaseStats& testCaseStats = m_currentStats->m_testCaseStats.back(); - testCaseStats.m_stdOut = stdOut; - testCaseStats.m_stdErr = stdErr; - if( !stdOut.empty() ) - m_stdOut << stdOut << "\n"; - if( !stdErr.empty() ) - m_stdErr << stdErr << "\n"; - } - - virtual void Aborted() { - // !TBD - } - - virtual void EndTesting( const Totals& ) { - XmlWriter xml( m_config.stream() ); - - if( m_statsForSuites.size() > 0 ) - xml.startElement( "testsuites" ); - - std::vector::const_iterator it = m_statsForSuites.begin(); - std::vector::const_iterator itEnd = m_statsForSuites.end(); - - for(; it != itEnd; ++it ) { - XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); - xml.writeAttribute( "name", it->m_name ); - xml.writeAttribute( "errors", it->m_errorsCount ); - xml.writeAttribute( "failures", it->m_failuresCount ); - xml.writeAttribute( "tests", it->m_testsCount ); - xml.writeAttribute( "hostname", "tbd" ); - xml.writeAttribute( "time", "tbd" ); - xml.writeAttribute( "timestamp", "tbd" ); - - OutputTestCases( xml, *it ); - } - - xml.scopedElement( "system-out" ).writeText( trim( m_stdOut.str() ), false ); - xml.scopedElement( "system-err" ).writeText( trim( m_stdErr.str() ), false ); - } - - void OutputTestCases( XmlWriter& xml, const Stats& stats ) { - std::vector::const_iterator it = stats.m_testCaseStats.begin(); - std::vector::const_iterator itEnd = stats.m_testCaseStats.end(); - for(; it != itEnd; ++it ) { - - XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); - xml.writeAttribute( "classname", it->m_className ); - xml.writeAttribute( "name", it->m_name ); - xml.writeAttribute( "time", "tbd" ); - - 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::const_iterator it = stats.m_testStats.begin(); - std::vector::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: - ReporterConfig m_config; - - Stats m_testSuiteStats; - Stats* m_currentStats; - std::vector m_statsForSuites; - std::vector m_currentTestCaseStats; - std::ostringstream m_stdOut; - std::ostringstream m_stdErr; + XmlWriter xml; + Timer suiteTimer; + std::ostringstream stdOutForSuite; + std::ostringstream stdErrForSuite; + unsigned int unexpectedExceptions; }; + INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter ) + } // end namespace Catch // #included from: ../reporters/catch_reporter_console.hpp @@ -7404,7 +7596,6 @@ namespace Catch { ReporterPreferences prefs; prefs.shouldRedirectStdOut = false; return prefs; - } virtual void noMatchingTestCases( std::string const& spec ) { @@ -7437,24 +7628,24 @@ namespace Catch { if( _sectionStats.missingAssertions ) { lazyPrint(); Colour colour( Colour::ResultError ); - stream << "\nNo assertions in section, '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; + if( m_sectionStack.size() > 1 ) + stream << "\nNo assertions in section"; + else + stream << "\nNo assertions in test case"; + stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; } m_headerPrinted = false; + if( m_config->showDurations() == ShowDurations::Always ) + stream << "Completed in " << _sectionStats.durationInSeconds << "s" << std::endl; StreamingReporterBase::sectionEnded( _sectionStats ); } virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) { - - if( _testCaseStats.missingAssertions ) { - lazyPrint(); - Colour colour( Colour::ResultError ); - stream << "\nNo assertions in test case, '" << _testCaseStats.testInfo.name << "'\n" << std::endl; - } StreamingReporterBase::testCaseEnded( _testCaseStats ); m_headerPrinted = false; } virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) { - if( !unusedGroupInfo ) { + if( currentGroupInfo.used ) { printSummaryDivider(); stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; printTotals( _testGroupStats.totals ); @@ -7604,9 +7795,9 @@ namespace Catch { void lazyPrint() { - if( testRunInfo ) + if( !currentTestRunInfo.used ) lazyPrintRunInfo(); - if( unusedGroupInfo ) + if( !currentGroupInfo.used ) lazyPrintGroupInfo(); if( !m_headerPrinted ) { @@ -7618,7 +7809,7 @@ namespace Catch { void lazyPrintRunInfo() { stream << "\n" << getTildes() << "\n"; Colour colour( Colour::SecondaryText ); - stream << testRunInfo->name + stream << currentTestRunInfo->name << " is a Catch v" << libraryVersion.majorVersion << "." << libraryVersion.minorVersion << " b" << libraryVersion.buildNumber; @@ -7627,33 +7818,29 @@ namespace Catch { stream << " host application.\n" << "Run with -? for options\n\n"; - testRunInfo.reset(); + currentTestRunInfo.used = true; } void lazyPrintGroupInfo() { - if( !unusedGroupInfo->name.empty() && unusedGroupInfo->groupsCounts > 1 ) { - printClosedHeader( "Group: " + unusedGroupInfo->name ); - unusedGroupInfo.reset(); + if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) { + printClosedHeader( "Group: " + currentGroupInfo->name ); + currentGroupInfo.used = true; } } void printTestCaseAndSectionHeader() { - printOpenHeader( unusedTestCaseInfo->name ); - if( currentSectionInfo ) { - Colour colourGuard( Colour::Headers ); - std::vector sections; - for( ThreadedSectionInfo* section = currentSectionInfo.get(); - section; - section = section->parent ) - sections.push_back( section ); + assert( !m_sectionStack.empty() ); + printOpenHeader( currentTestCaseInfo->name ); - // Sections - std::vector::const_reverse_iterator - it = sections.rbegin(), itEnd = sections.rend(); - for( ++it; it != itEnd; ++it ) // Skip first section (test case) - printHeaderString( (*it)->name, 2 ); + if( m_sectionStack.size() > 1 ) { + Colour colourGuard( Colour::Headers ); + + std::vector::const_iterator + it = m_sectionStack.begin()+1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for( ; it != itEnd; ++it ) + printHeaderString( it->name, 2 ); } - SourceLineInfo lineInfo = currentSectionInfo - ? currentSectionInfo->lineInfo - : unusedTestCaseInfo->lineInfo; + + SourceLineInfo lineInfo = m_sectionStack.front().lineInfo; if( !lineInfo.empty() ){ stream << getDashes() << "\n"; @@ -7788,9 +7975,8 @@ namespace Catch { TestCaseStats::~TestCaseStats() {} TestGroupStats::~TestGroupStats() {} TestRunStats::~TestRunStats() {} - ThreadedSectionInfo::~ThreadedSectionInfo() {} - TestGroupNode::~TestGroupNode() {} - TestRunNode::~TestRunNode() {} + CumulativeReporterBase::SectionNode::~SectionNode() {} + CumulativeReporterBase::~CumulativeReporterBase() {} BasicReporter::~BasicReporter() {} StreamingReporterBase::~StreamingReporterBase() {} @@ -7817,8 +8003,6 @@ namespace Catch { INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( "basic", BasicReporter ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( "xml", XmlReporter ) - INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( "junit", JunitReporter ) - } #ifdef __clang__