diff --git a/projects/SelfTest/PartTrackerTests.cpp b/projects/SelfTest/PartTrackerTests.cpp new file mode 100644 index 00000000..6323f376 --- /dev/null +++ b/projects/SelfTest/PartTrackerTests.cpp @@ -0,0 +1,422 @@ +/* + * 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 IContainerPart; + + struct ITrackedPart : SharedImpl<> { + virtual ~ITrackedPart() {} + virtual std::string name() const = 0; + virtual bool isUnstarted() const = 0; + virtual bool isCompleteOrFailed() const = 0; + virtual bool isComplete() const = 0; + virtual bool isOpen() const = 0; + + virtual IContainerPart& parent() = 0; + + virtual void close() = 0; + virtual void fail() = 0; + }; + + struct IContainerPart : ITrackedPart { + virtual void addChild( Ptr const& child ) = 0; + virtual ITrackedPart* findChild( std::string const& name ) = 0; + virtual void openChild() = 0; + virtual void childFailed() = 0; + }; + + + class TrackerContext { + + enum RunState { + NotStarted, + Executing, + CompletedCycle + }; + + Ptr m_rootPartTracker; + IContainerPart* m_currentPart; + RunState m_runState; + + public: + + static TrackerContext& instance() { + static TrackerContext s_instance; + return s_instance; + } + + TrackerContext() + : m_currentPart( CATCH_NULL ), + m_runState( NotStarted ) + {} + + int i() { + return 42; + } + + IContainerPart& startRun(); + + void endRun() { + m_rootPartTracker.reset(); + m_currentPart = CATCH_NULL; + m_runState = NotStarted; + } + + void startCycle() { + m_currentPart = m_rootPartTracker.get(); + m_runState = Executing; + } + void completeCycle() { + m_runState = CompletedCycle; + } + + bool completedCycle() const { + return m_runState == CompletedCycle; + } + + IContainerPart& currentPart() { + return *m_currentPart; + } + void setCurrentPart( IContainerPart* part ) { + m_currentPart = part; + } + + ITrackedPart* findPart( std::string const& name ) { + return m_currentPart->findChild( name ); + } + + + private: + }; + + + class PartTracker : public IContainerPart { + enum RunState { + NotStarted, + Executing, + ExecutingChildren, + CompletedSuccessfully, + Failed, + ChildFailed + }; + 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; + IContainerPart* m_parent; + Children m_children; + RunState m_runState; + public: + PartTracker( std::string const& name, TrackerContext& ctx, IContainerPart* 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 isCompleteOrFailed() const CATCH_OVERRIDE { + return m_runState == CompletedSuccessfully || m_runState == Failed; + } + virtual bool isComplete() const CATCH_OVERRIDE { + return m_runState == CompletedSuccessfully; + } + virtual bool isUnstarted() const CATCH_OVERRIDE { + return m_runState == NotStarted; + } + virtual bool isOpen() const CATCH_OVERRIDE { + return m_runState == Executing || m_runState == ExecutingChildren; + } + + + virtual void addChild( Ptr const& child ) CATCH_OVERRIDE { + m_children.push_back( child ); + size_t childCount = m_children.size(); + } + + virtual ITrackedPart* 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 IContainerPart& 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(); + } + } + virtual void childFailed() CATCH_OVERRIDE { + assert( m_runState == ExecutingChildren ); + m_runState = ChildFailed; + if( m_parent ) + m_parent->childFailed(); + } + void open() { + m_runState = Executing; + moveToThis(); + if( m_parent ) + m_parent->openChild(); + } + + virtual void close() CATCH_OVERRIDE { + switch( m_runState ) { + case Executing: + m_runState = CompletedSuccessfully; + break; + case ExecutingChildren: + if( !hasUnstartedChildren() ) + m_runState = CompletedSuccessfully; + break; + case ChildFailed: + m_runState = ExecutingChildren; + 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->childFailed(); + moveToParent(); + m_ctx.completeCycle(); + } + private: + void moveToParent() { + m_ctx.setCurrentPart( m_parent ); + } + void moveToThis() { + m_ctx.setCurrentPart( this ); + } + + bool hasUnstartedChildren() const { + return !m_children.empty() && m_children.back()->isUnstarted(); + } + }; + + IContainerPart& TrackerContext::startRun() { + m_rootPartTracker = new PartTracker( "{root}", *this, CATCH_NULL ); + m_currentPart = CATCH_NULL; + m_runState = Executing; + return *m_rootPartTracker; + } + + class LocalContext { + + public: + TrackerContext& operator()() const { + return TrackerContext::instance(); + } + }; + + class SectionPart : public PartTracker { + public: + SectionPart( std::string const& name, TrackerContext& ctx, IContainerPart* parent ) + : PartTracker( name, ctx, parent ) + {} + + static SectionPart& acquire( TrackerContext& ctx, std::string const& name ) { + SectionPart* section = CATCH_NULL; + + IContainerPart& currentPart = ctx.currentPart(); + if( ITrackedPart* part = currentPart.findChild( name ) ) { + section = dynamic_cast( part ); + assert( section ); + } + else { + section = new SectionPart( name, ctx, ¤tPart ); + currentPart.addChild( section ); + } + if( !ctx.completedCycle() && !section->isCompleteOrFailed() ) { + section->open(); + } + return *section; + } + }; + +} // 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(); + + SectionPart& testCase = SectionPart::acquire( ctx, "Testcase" ); + REQUIRE( testCase.isComplete() == false ); + + SectionPart& s1 = SectionPart::acquire( ctx, "S1" ); + REQUIRE( s1.isComplete() == false ); + + SECTION( "successfully close one section" ) { + s1.close(); + REQUIRE( s1.isComplete() == true ); + REQUIRE( testCase.isCompleteOrFailed() == false ); + + testCase.close(); + REQUIRE( testCase.isComplete() == true ); + + REQUIRE( ctx.completedCycle() == true ); + } + + SECTION( "fail one section" ) { + s1.fail(); + REQUIRE( s1.isComplete() == false ); + REQUIRE( s1.isCompleteOrFailed() == true ); + REQUIRE( testCase.isComplete() == false ); + REQUIRE( testCase.isCompleteOrFailed() == false ); + + testCase.close(); + REQUIRE( ctx.completedCycle() == true ); + REQUIRE( testCase.isComplete() == false ); + + SECTION( "re-enter after failed section" ) { + ctx.startCycle(); + SectionPart& testCase2 = SectionPart::acquire( ctx, "Testcase" ); + REQUIRE( testCase2.isComplete() == false ); + + SectionPart& s1b = SectionPart::acquire( ctx, "S1" ); + REQUIRE( s1b.isComplete() == false ); + + testCase2.close(); + REQUIRE( ctx.completedCycle() == true ); + REQUIRE( testCase.isComplete() == true ); + REQUIRE( testCase.isCompleteOrFailed() == true ); + } + SECTION( "re-enter after failed section and find next section" ) { + ctx.startCycle(); + SectionPart& testCase2 = SectionPart::acquire( ctx, "Testcase" ); + REQUIRE( testCase2.isComplete() == false ); + + SectionPart& s1b = SectionPart::acquire( ctx, "S1" ); + REQUIRE( s1b.isComplete() == false ); + + SectionPart& s2 = SectionPart::acquire( ctx, "S2" ); + REQUIRE( s2.isOpen() ); + s2.close(); + REQUIRE( ctx.completedCycle() == true ); + + testCase2.close(); + REQUIRE( testCase.isComplete() == true ); + REQUIRE( testCase.isCompleteOrFailed() == true ); + } + } + + SECTION( "successfully close one section, then find another" ) { + s1.close(); + REQUIRE( ctx.completedCycle() == true ); + + SectionPart& s2 = SectionPart::acquire( ctx, "S2" ); + REQUIRE( s2.isComplete() == false ); + REQUIRE( s2.isOpen() == false ); + + testCase.close(); + REQUIRE( testCase.isComplete() == false ); + + SECTION( "Re-enter - skip S1 and enter S2" ) { + ctx.startCycle(); + SectionPart& testCase2 = SectionPart::acquire( ctx, "Testcase" ); + REQUIRE( testCase2.isComplete() == false ); + + SectionPart& s1b = SectionPart::acquire( ctx, "S1" ); + REQUIRE( s1b.isComplete() == true ); + + SectionPart& s2b = SectionPart::acquire( ctx, "S2" ); + REQUIRE( s2b.isComplete() == false ); + REQUIRE( s2b.isOpen() ); + + REQUIRE( ctx.completedCycle() == false ); + + SECTION ("Successfully close S2") { + s2b.close(); + REQUIRE( ctx.completedCycle() == true ); + + REQUIRE( s2b.isComplete() == true ); + REQUIRE( testCase2.isCompleteOrFailed() == false ); + + testCase2.close(); + REQUIRE( testCase2.isComplete() == true ); + } + SECTION ("fail S2") { + s2b.fail(); + REQUIRE( ctx.completedCycle() == true ); + + REQUIRE( s2b.isComplete() == false ); + REQUIRE( s2b.isCompleteOrFailed() == true ); + REQUIRE( testCase2.isCompleteOrFailed() == false ); + + testCase2.close(); + REQUIRE( testCase2.isComplete() == false ); + } + } + } + + SECTION( "open a nested section" ) { + SectionPart& s2 = SectionPart::acquire( ctx, "S2" ); + REQUIRE( s2.isOpen() == true ); + + s2.close(); + REQUIRE( s2.isComplete() == true ); + REQUIRE( s1.isComplete() == false ); + + s1.close(); + REQUIRE( s1.isComplete() == true ); + REQUIRE( testCase.isComplete() == false ); + + testCase.close(); + REQUIRE( testCase.isComplete() == true ); + + } + +} diff --git a/projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj b/projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj index 81f826b6..5ec14365 100644 --- a/projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj +++ b/projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 26059AF21BD4B94C003D575C /* PartTrackerTests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26059AF11BD4B94C003D575C /* PartTrackerTests.cpp */; settings = {ASSET_TAGS = (); }; }; 263F7A4719B6FCBF009474C2 /* EnumToString.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 263F7A4619B6FCBF009474C2 /* EnumToString.cpp */; }; 263F7A4B19B6FE1E009474C2 /* ToStringPair.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 263F7A4819B6FE1E009474C2 /* ToStringPair.cpp */; }; 263F7A4C19B6FE1E009474C2 /* ToStringVector.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 263F7A4919B6FE1E009474C2 /* ToStringVector.cpp */; }; @@ -62,6 +63,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 26059AF11BD4B94C003D575C /* PartTrackerTests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PartTrackerTests.cpp; path = ../../../SelfTest/PartTrackerTests.cpp; sourceTree = ""; }; 261488FA184C81130041FBEB /* catch_test_spec.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = catch_test_spec.hpp; sourceTree = ""; }; 261488FC184D1DC10041FBEB /* catch_stream.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = catch_stream.h; sourceTree = ""; }; 261488FD184D21290041FBEB /* catch_section_info.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = catch_section_info.h; sourceTree = ""; }; @@ -212,6 +214,7 @@ 266E9AD317290E710061DAB2 /* Introspective Tests */ = { isa = PBXGroup; children = ( + 26059AF11BD4B94C003D575C /* PartTrackerTests.cpp */, 26948284179A9AB900ED166E /* SectionTrackerTests.cpp */, 26E1B7D119213BC900812682 /* CmdLineTests.cpp */, 26711C8D195D465C0033EDA2 /* TagAliasTests.cpp */, @@ -548,6 +551,7 @@ 4A6D0C3E149B3D9E00DB3EAA /* TestMain.cpp in Sources */, 4A6D0C3F149B3D9E00DB3EAA /* TrickyTests.cpp in Sources */, 263F7A4D19B6FE1E009474C2 /* ToStringWhich.cpp in Sources */, + 26059AF21BD4B94C003D575C /* PartTrackerTests.cpp in Sources */, 263F7A4B19B6FE1E009474C2 /* ToStringPair.cpp in Sources */, 4AEE032016142F910071E950 /* catch_common.cpp in Sources */, 263F7A4C19B6FE1E009474C2 /* ToStringVector.cpp in Sources */,