/* * Created by Phil on 1/10/2015. * Copyright 2015 Two Blue Cubes Ltd * * 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) */ #include "internal/catch_suppress_warnings.h" #include "internal/catch_compiler_capabilities.h" #include "internal/catch_ptr.hpp" #ifdef __clang__ //# pragma clang diagnostic ignored "-Wpadded" //# pragma clang diagnostic ignored "-Wc++98-compat" #endif #include #include #include namespace Catch { struct IPartTracker : SharedImpl<> { virtual ~IPartTracker() {} // static queries virtual std::string name() const = 0; // dynamic queries virtual bool isComplete() const = 0; // Successfully completed or failed virtual bool isSuccessfullyCompleted() const = 0; virtual bool isOpen() const = 0; // Started but not complete virtual IPartTracker& parent() = 0; // actions virtual void close() = 0; // Successfully complete virtual void fail() = 0; virtual void markAsNeedingAnotherRun() = 0; virtual void addChild( Ptr const& child ) = 0; virtual IPartTracker* findChild( std::string const& name ) = 0; virtual void openChild() = 0; }; class TrackerContext { enum RunState { NotStarted, Executing, CompletedCycle }; Ptr m_rootPart; IPartTracker* m_currentPart; RunState m_runState; public: static TrackerContext& instance() { static TrackerContext s_instance; return s_instance; } TrackerContext() : m_currentPart( CATCH_NULL ), m_runState( NotStarted ) {} IPartTracker& startRun(); void endRun() { m_rootPart.reset(); m_currentPart = CATCH_NULL; m_runState = NotStarted; } void startCycle() { m_currentPart = m_rootPart.get(); m_runState = Executing; } void completeCycle() { m_runState = CompletedCycle; } bool completedCycle() const { return m_runState == CompletedCycle; } IPartTracker& currentPart() { return *m_currentPart; } void setCurrentPart( IPartTracker* part ) { m_currentPart = part; } IPartTracker* findPart( std::string const& name ) { return m_currentPart->findChild( name ); } }; class PartTrackerBase : public IPartTracker { protected: enum RunState { NotStarted, Executing, ExecutingChildren, NeedsAnotherRun, CompletedSuccessfully, Failed }; class TrackerHasName { std::string m_name; public: TrackerHasName( std::string const& name ) : m_name( name ) {} bool operator ()( Ptr const& tracker ) { return tracker->name() == m_name; } }; typedef std::vector > Children; std::string m_name; TrackerContext& m_ctx; IPartTracker* m_parent; Children m_children; RunState m_runState; public: PartTrackerBase( std::string const& name, TrackerContext& ctx, IPartTracker* parent ) : m_name( name ), m_ctx( ctx ), m_parent( parent ), m_runState( NotStarted ) {} virtual std::string name() const CATCH_OVERRIDE { return m_name; } virtual bool isComplete() const CATCH_OVERRIDE { return m_runState == CompletedSuccessfully || m_runState == Failed; } virtual bool isSuccessfullyCompleted() const CATCH_OVERRIDE { return m_runState == CompletedSuccessfully; } virtual bool isOpen() const CATCH_OVERRIDE { return m_runState != NotStarted && !isComplete(); } virtual void addChild( Ptr const& child ) CATCH_OVERRIDE { m_children.push_back( child ); size_t childCount = m_children.size(); } virtual IPartTracker* findChild( std::string const& name ) CATCH_OVERRIDE { Children::const_iterator it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( name ) ); return( it != m_children.end() ) ? it->get() : CATCH_NULL; } virtual IPartTracker& parent() CATCH_OVERRIDE { assert( m_parent ); // Should always be non-null except for root return *m_parent; } virtual void openChild() CATCH_OVERRIDE { if( m_runState != ExecutingChildren ) { m_runState = ExecutingChildren; if( m_parent ) m_parent->openChild(); } } void open() { m_runState = Executing; moveToThis(); if( m_parent ) m_parent->openChild(); } virtual void close() CATCH_OVERRIDE { // Close any still open children (e.g. generators) while( &m_ctx.currentPart() != this ) m_ctx.currentPart().close(); switch( m_runState ) { case CompletedSuccessfully: case Failed: assert(false); // Shouldn't really get here case NeedsAnotherRun: return; case Executing: m_runState = CompletedSuccessfully; break; case ExecutingChildren: if( m_children.empty() || m_children.back()->isComplete() ) m_runState = CompletedSuccessfully; break; default: throw std::logic_error( "Unexpected state" ); } moveToParent(); m_ctx.completeCycle(); } virtual void fail() CATCH_OVERRIDE { m_runState = Failed; if( m_parent ) m_parent->markAsNeedingAnotherRun(); moveToParent(); m_ctx.completeCycle(); } virtual void markAsNeedingAnotherRun() CATCH_OVERRIDE { m_runState = NeedsAnotherRun; } private: void moveToParent() { assert( m_parent ); m_ctx.setCurrentPart( m_parent ); } void moveToThis() { m_ctx.setCurrentPart( this ); } }; class SectionTracker : public PartTrackerBase { public: SectionTracker( std::string const& name, TrackerContext& ctx, IPartTracker* parent ) : PartTrackerBase( name, ctx, parent ) {} static SectionTracker& acquire( TrackerContext& ctx, std::string const& name ) { SectionTracker* section = CATCH_NULL; IPartTracker& currentPart = ctx.currentPart(); if( IPartTracker* part = currentPart.findChild( name ) ) { section = dynamic_cast( part ); assert( section ); } else { section = new SectionTracker( name, ctx, ¤tPart ); currentPart.addChild( section ); } if( !ctx.completedCycle() && !section->isComplete() ) { section->open(); } return *section; } }; class IndexTracker : public PartTrackerBase { int m_size; int m_index; public: IndexTracker( std::string const& name, TrackerContext& ctx, IPartTracker* parent, int size ) : PartTrackerBase( name, ctx, parent ), m_size( size ), m_index( -1 ) {} static IndexTracker& acquire( TrackerContext& ctx, std::string const& name, int size ) { IndexTracker* tracker = CATCH_NULL; IPartTracker& currentPart = ctx.currentPart(); if( IPartTracker* part = currentPart.findChild( name ) ) { tracker = dynamic_cast( part ); assert( tracker ); } else { tracker = new IndexTracker( name, ctx, ¤tPart, size ); currentPart.addChild( tracker ); } if( !ctx.completedCycle() && !tracker->isComplete() ) { if( tracker->m_runState != ExecutingChildren ) tracker->moveNext(); tracker->open(); } return *tracker; } int index() const { return m_index; } void moveNext() { m_index++; m_children.clear(); } virtual void close() CATCH_OVERRIDE { PartTrackerBase::close(); if( m_runState == CompletedSuccessfully ) if( m_index < m_size-1 ) m_runState = Executing; } }; IPartTracker& TrackerContext::startRun() { m_rootPart = new SectionTracker( "{root}", *this, CATCH_NULL ); m_currentPart = CATCH_NULL; m_runState = Executing; return *m_rootPart; } class LocalContext { public: TrackerContext& operator()() const { return TrackerContext::instance(); } }; } // namespace Catch inline Catch::TrackerContext& C_A_T_C_H_Context() { return Catch::TrackerContext::instance(); } // ------------------- #include "catch.hpp" using namespace Catch; inline void testCase( Catch::LocalContext const& C_A_T_C_H_Context ) { // REQUIRE( C_A_T_C_H_Context().i() == 42 ); } TEST_CASE( "PartTracker" ) { TrackerContext ctx; ctx.startRun(); ctx.startCycle(); IPartTracker& testCase = SectionTracker::acquire( ctx, "Testcase" ); REQUIRE( testCase.isOpen() ); IPartTracker& s1 = SectionTracker::acquire( ctx, "S1" ); REQUIRE( s1.isOpen() ); SECTION( "successfully close one section" ) { s1.close(); REQUIRE( s1.isSuccessfullyCompleted() ); REQUIRE( testCase.isComplete() == false ); testCase.close(); REQUIRE( ctx.completedCycle() ); REQUIRE( testCase.isSuccessfullyCompleted() ); } SECTION( "fail one section" ) { s1.fail(); REQUIRE( s1.isComplete() ); REQUIRE( s1.isSuccessfullyCompleted() == false ); REQUIRE( testCase.isComplete() == false ); testCase.close(); REQUIRE( ctx.completedCycle() ); REQUIRE( testCase.isSuccessfullyCompleted() == false ); SECTION( "re-enter after failed section" ) { ctx.startCycle(); IPartTracker& testCase2 = SectionTracker::acquire( ctx, "Testcase" ); REQUIRE( testCase2.isOpen() ); IPartTracker& s1b = SectionTracker::acquire( ctx, "S1" ); REQUIRE( s1b.isOpen() == false ); testCase2.close(); REQUIRE( ctx.completedCycle() ); REQUIRE( testCase.isComplete() ); REQUIRE( testCase.isSuccessfullyCompleted() ); } SECTION( "re-enter after failed section and find next section" ) { ctx.startCycle(); IPartTracker& testCase2 = SectionTracker::acquire( ctx, "Testcase" ); REQUIRE( testCase2.isOpen() ); IPartTracker& s1b = SectionTracker::acquire( ctx, "S1" ); REQUIRE( s1b.isOpen() == false ); IPartTracker& s2 = SectionTracker::acquire( ctx, "S2" ); REQUIRE( s2.isOpen() ); s2.close(); REQUIRE( ctx.completedCycle() ); testCase2.close(); REQUIRE( testCase.isComplete() ); REQUIRE( testCase.isSuccessfullyCompleted() ); } } SECTION( "successfully close one section, then find another" ) { s1.close(); IPartTracker& s2 = SectionTracker::acquire( ctx, "S2" ); REQUIRE( s2.isOpen() == false ); testCase.close(); REQUIRE( testCase.isComplete() == false ); SECTION( "Re-enter - skips S1 and enters S2" ) { ctx.startCycle(); IPartTracker& testCase2 = SectionTracker::acquire( ctx, "Testcase" ); REQUIRE( testCase2.isOpen() ); IPartTracker& s1b = SectionTracker::acquire( ctx, "S1" ); REQUIRE( s1b.isOpen() == false ); IPartTracker& s2b = SectionTracker::acquire( ctx, "S2" ); REQUIRE( s2b.isOpen() ); REQUIRE( ctx.completedCycle() == false ); SECTION ("Successfully close S2") { s2b.close(); REQUIRE( ctx.completedCycle() ); REQUIRE( s2b.isSuccessfullyCompleted() ); REQUIRE( testCase2.isComplete() == false ); testCase2.close(); REQUIRE( testCase2.isSuccessfullyCompleted() ); } SECTION ("fail S2") { s2b.fail(); REQUIRE( ctx.completedCycle() ); REQUIRE( s2b.isComplete() ); REQUIRE( s2b.isSuccessfullyCompleted() == false ); testCase2.close(); // REQUIRE( testCase2.isComplete() ); REQUIRE( testCase2.isSuccessfullyCompleted() == false ); // Need a final cycle ctx.startCycle(); IPartTracker& testCase3 = SectionTracker::acquire( ctx, "Testcase" ); REQUIRE( testCase3.isOpen() ); IPartTracker& s1c = SectionTracker::acquire( ctx, "S1" ); REQUIRE( s1c.isOpen() == false ); IPartTracker& s2c = SectionTracker::acquire( ctx, "S2" ); REQUIRE( s2c.isOpen() == false ); testCase3.close(); REQUIRE( testCase3.isSuccessfullyCompleted() ); } } } SECTION( "open a nested section" ) { IPartTracker& s2 = SectionTracker::acquire( ctx, "S2" ); REQUIRE( s2.isOpen() ); s2.close(); REQUIRE( s2.isSuccessfullyCompleted() ); REQUIRE( s1.isSuccessfullyCompleted() == false ); s1.close(); REQUIRE( s1.isSuccessfullyCompleted() ); REQUIRE( testCase.isSuccessfullyCompleted() == false ); testCase.close(); REQUIRE( testCase.isSuccessfullyCompleted() ); } SECTION( "start a generator" ) { IndexTracker& g1 = IndexTracker::acquire( ctx, "G1", 2 ); REQUIRE( g1.isOpen() ); REQUIRE( g1.index() == 0 ); REQUIRE( g1.isSuccessfullyCompleted() == false ); REQUIRE( s1.isSuccessfullyCompleted() == false ); SECTION( "close outer section" ) { s1.close(); REQUIRE( s1.isSuccessfullyCompleted() == false ); testCase.close(); REQUIRE( testCase.isSuccessfullyCompleted() == false ); SECTION( "Re-enter for second generation" ) { ctx.startCycle(); IPartTracker& testCase2 = SectionTracker::acquire( ctx, "Testcase" ); REQUIRE( testCase2.isOpen() ); IPartTracker& s1b = SectionTracker::acquire( ctx, "S1" ); REQUIRE( s1b.isOpen() ); IndexTracker& g1b = IndexTracker::acquire( ctx, "G1", 2 ); REQUIRE( g1b.isOpen() ); REQUIRE( g1b.index() == 1 ); REQUIRE( s1.isSuccessfullyCompleted() == false ); s1b.close(); REQUIRE( s1b.isSuccessfullyCompleted() ); REQUIRE( g1b.isSuccessfullyCompleted() ); testCase2.close(); REQUIRE( testCase2.isSuccessfullyCompleted() ); } } SECTION( "Start a new inner section" ) { IPartTracker& s2 = SectionTracker::acquire( ctx, "S2" ); REQUIRE( s2.isOpen() ); s2.close(); REQUIRE( s2.isSuccessfullyCompleted() ); s1.close(); REQUIRE( s1.isSuccessfullyCompleted() == false ); testCase.close(); REQUIRE( testCase.isSuccessfullyCompleted() == false ); SECTION( "Re-enter for second generation" ) { ctx.startCycle(); IPartTracker& testCase2 = SectionTracker::acquire( ctx, "Testcase" ); REQUIRE( testCase2.isOpen() ); IPartTracker& s1b = SectionTracker::acquire( ctx, "S1" ); REQUIRE( s1b.isOpen() ); // generator - next value IndexTracker& g1b = IndexTracker::acquire( ctx, "G1", 2 ); REQUIRE( g1b.isOpen() ); REQUIRE( g1b.index() == 1 ); // inner section again IPartTracker& s2b = SectionTracker::acquire( ctx, "S2" ); REQUIRE( s2b.isOpen() ); s2b.close(); REQUIRE( s2b.isSuccessfullyCompleted() ); s1b.close(); REQUIRE( s1b.isSuccessfullyCompleted() ); REQUIRE( g1b.isSuccessfullyCompleted() ); testCase2.close(); REQUIRE( testCase2.isSuccessfullyCompleted() ); } } // !TBD" // nested generator // two sections within a generator } }