/* * Created by Phil on 22/10/2010. * Copyright 2010 Two Blue Cubes Ltd. All rights reserved. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ #ifndef TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED #define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED #include "catch_interfaces_runner.h" #include "catch_interfaces_reporter.h" #include "catch_interfaces_exception.h" #include "catch_config.hpp" #include "catch_test_registry.hpp" #include "catch_test_case_info.h" #include "catch_capture.hpp" #include "catch_totals.hpp" #include "catch_running_test.hpp" #include "catch_test_spec.h" #include #include // C++11 specific start #include // C++11 specific end 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(end - start).count()); } // C++11 specific end class StreamRedirect { public: StreamRedirect( std::ostream& stream, std::string& targetString ) : m_stream( stream ), m_prevBuf( stream.rdbuf() ), m_targetString( targetString ) { stream.rdbuf( m_oss.rdbuf() ); } ~StreamRedirect() { m_targetString += m_oss.str(); m_stream.rdbuf( m_prevBuf ); } private: std::ostream& m_stream; std::streambuf* m_prevBuf; std::ostringstream m_oss; std::string& m_targetString; }; /////////////////////////////////////////////////////////////////////////// class RunContext : public IResultCapture, public IRunner { RunContext( RunContext const& ); void operator =( RunContext const& ); public: explicit RunContext( Ptr const& config, Ptr const& reporter ) : m_runInfo( config->name() ), m_context( getCurrentMutableContext() ), m_runningTest( NULL ), m_config( config ), m_reporter( reporter ), m_prevRunner( &m_context.getRunner() ), m_prevResultCapture( &m_context.getResultCapture() ), m_prevConfig( m_context.getConfig() ), m_startRun( time_now() ), m_startGroup( m_startRun ) { m_context.setRunner( this ); m_context.setConfig( m_config ); m_context.setResultCapture( this ); m_reporter->testRunStarting( m_runInfo ); } virtual ~RunContext() { 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.setConfig( NULL ); m_context.setResultCapture( m_prevResultCapture ); m_context.setConfig( m_prevConfig ); } void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t 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 ) { 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 ) { std::vector matchingTests = getRegistryHub().getTestCaseRegistry().getMatchingTestCases( testSpec ); Totals totals; testGroupStarting( testSpec, groupIndex, groupsCount ); std::vector::const_iterator it = matchingTests.begin(); std::vector::const_iterator itEnd = matchingTests.end(); for(; it != itEnd; ++it ) totals += runTest( *it ); testGroupEnded( testSpec, totals, groupIndex, groupsCount ); return totals; } Totals runTest( TestCase const& testCase ) { Totals prevTotals = m_totals; std::string redirectedCout; std::string redirectedCerr; TestCaseInfo testInfo = testCase.getTestCaseInfo(); m_reporter->testCaseStarting( testInfo ); m_runningTest = new RunningTest( testCase ); time_point timeStart = time_now(); do { do { runCurrentTest( redirectedCout, redirectedCerr ); } while( m_runningTest->hasUntestedSections() && !aborting() ); } while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); time_point timeEnd = time_now(); 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, time_diff_secs( timeEnd, timeStart ), redirectedCout, redirectedCerr, missingAssertions, aborting() ) ); delete m_runningTest; m_runningTest = NULL; return deltaTotals; } Ptr config() const { return m_config; } private: // IResultCapture virtual void acceptMessage( MessageBuilder const& messageBuilder ) { m_messages.push_back( messageBuilder.build() ); } virtual ResultAction::Value acceptExpression( ExpressionResultBuilder const& assertionResult, AssertionInfo const& assertionInfo ) { m_lastAssertionInfo = assertionInfo; return actOnCurrentResult( assertionResult.buildResult( assertionInfo ) ); } virtual void assertionEnded( AssertionResult const& result ) { if( result.getResultType() == ResultWas::Ok ) { m_totals.assertions.passed++; } else if( !result.isOk() ) { m_totals.assertions.failed++; } m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ); // Reset working state m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition ); m_messages.clear(); } virtual bool sectionStarted ( SectionInfo const& sectionInfo, Counts& assertions ) { std::ostringstream oss; oss << sectionInfo.name << "@" << sectionInfo.lineInfo; if( !m_runningTest->addSection( oss.str() ) ) return false; m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; m_reporter->sectionStarting( sectionInfo ); assertions = m_totals.assertions; return true; } virtual void sectionEnded( SectionInfo const& info, Counts const& prevAssertions ) { if( std::uncaught_exception() ) { m_unfinishedSections.push_back( UnfinishedSections( info, prevAssertions ) ); return; } Counts assertions = m_totals.assertions - prevAssertions; bool missingAssertions = false; if( assertions.total() == 0 && m_config->warnAboutMissingAssertions() && !m_runningTest->isBranchSection() ) { m_totals.assertions.failed++; assertions.failed++; missingAssertions = true; } m_runningTest->endSection( info.name, false ); m_reporter->sectionEnded( SectionStats( info, assertions, missingAssertions ) ); m_messages.clear(); } virtual void pushScopedMessage( ScopedMessageBuilder const& _builder ) { m_messages.push_back( _builder.build() ); } virtual void popScopedMessage( ScopedMessageBuilder const& _builder ) { m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), _builder ), m_messages.end() ); } virtual bool shouldDebugBreak() const { return m_config->shouldDebugBreak(); } virtual std::string getCurrentTestName() const { return m_runningTest ? m_runningTest->getTestCase().getTestCaseInfo().name : ""; } virtual const AssertionResult* getLastResult() const { return &m_lastResult; } public: // !TBD We need to do this another way! bool aborting() const { return m_totals.assertions.failed == static_cast( m_config->abortAfter() ); } private: ResultAction::Value actOnCurrentResult( AssertionResult const& result ) { m_lastResult = result; assertionEnded( m_lastResult ); ResultAction::Value action = ResultAction::None; if( !m_lastResult.isOk() ) { action = ResultAction::Failed; if( shouldDebugBreak() ) action = (ResultAction::Value)( action | ResultAction::Debug ); if( aborting() ) action = (ResultAction::Value)( action | ResultAction::Abort ); } return action; } void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) { try { m_lastAssertionInfo = AssertionInfo( "TEST_CASE", m_runningTest->getTestCase().getTestCaseInfo().lineInfo, "", ResultDisposition::Normal ); m_runningTest->reset(); if( m_reporter->getPreferences().shouldRedirectStdOut ) { StreamRedirect coutRedir( std::cout, redirectedCout ); StreamRedirect cerrRedir( std::cerr, redirectedCerr ); m_runningTest->getTestCase().invoke(); } else { m_runningTest->getTestCase().invoke(); } m_runningTest->ranToCompletion(); } catch( TestFailureException& ) { // This just means the test was aborted due to failure } catch(...) { ExpressionResultBuilder exResult( ResultWas::ThrewException ); exResult << translateActiveException(); actOnCurrentResult( exResult.buildResult( m_lastAssertionInfo ) ); } for( std::vector::const_iterator it = m_unfinishedSections.begin(), itEnd = m_unfinishedSections.end(); it != itEnd; ++it ) sectionEnded( it->info, it->prevAssertions ); m_unfinishedSections.clear(); m_messages.clear(); } private: struct UnfinishedSections { UnfinishedSections( SectionInfo const& _info, Counts const& _prevAssertions ) : info( _info ), prevAssertions( _prevAssertions ) {} SectionInfo info; Counts prevAssertions; }; TestRunInfo m_runInfo; IMutableContext& m_context; RunningTest* m_runningTest; AssertionResult m_lastResult; Ptr m_config; Totals m_totals; Ptr m_reporter; std::vector m_messages; IRunner* m_prevRunner; IResultCapture* m_prevResultCapture; Ptr m_prevConfig; AssertionInfo m_lastAssertionInfo; std::vector m_unfinishedSections; time_point m_startRun; time_point m_startGroup; }; } // end namespace Catch #endif // TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED