1 /*
2  *  Created by Phil Nash on 23/7/2013
3  *  Copyright 2013 Two Blue Cubes Ltd. All rights reserved.
4  *
5  *  Distributed under the Boost Software License, Version 1.0. (See accompanying
6  *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7  */
8 #ifndef TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED
9 #define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED
10 
11 #include "catch_compiler_capabilities.h"
12 #include "catch_ptr.hpp"
13 
14 #include <algorithm>
15 #include <string>
16 #include <assert.h>
17 #include <vector>
18 #include <stdexcept>
19 
20 CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS
21 
22 namespace Catch {
23 namespace TestCaseTracking {
24 
25     struct NameAndLocation {
26         std::string name;
27         SourceLineInfo location;
28 
NameAndLocationCatch::TestCaseTracking::NameAndLocation29         NameAndLocation( std::string const& _name, SourceLineInfo const& _location )
30         :   name( _name ),
31             location( _location )
32         {}
33     };
34 
35     struct ITracker : SharedImpl<> {
36         virtual ~ITracker();
37 
38         // static queries
39         virtual NameAndLocation const& nameAndLocation() const = 0;
40 
41         // dynamic queries
42         virtual bool isComplete() const = 0; // Successfully completed or failed
43         virtual bool isSuccessfullyCompleted() const = 0;
44         virtual bool isOpen() const = 0; // Started but not complete
45         virtual bool hasChildren() const = 0;
46 
47         virtual ITracker& parent() = 0;
48 
49         // actions
50         virtual void close() = 0; // Successfully complete
51         virtual void fail() = 0;
52         virtual void markAsNeedingAnotherRun() = 0;
53 
54         virtual void addChild( Ptr<ITracker> const& child ) = 0;
55         virtual ITracker* findChild( NameAndLocation const& nameAndLocation ) = 0;
56         virtual void openChild() = 0;
57 
58         // Debug/ checking
59         virtual bool isSectionTracker() const = 0;
60         virtual bool isIndexTracker() const = 0;
61     };
62 
63     class  TrackerContext {
64 
65         enum RunState {
66             NotStarted,
67             Executing,
68             CompletedCycle
69         };
70 
71         Ptr<ITracker> m_rootTracker;
72         ITracker* m_currentTracker;
73         RunState m_runState;
74 
75     public:
76 
instance()77         static TrackerContext& instance() {
78             static TrackerContext s_instance;
79             return s_instance;
80         }
81 
TrackerContext()82         TrackerContext()
83         :   m_currentTracker( CATCH_NULL ),
84             m_runState( NotStarted )
85         {}
86 
87 
88         ITracker& startRun();
89 
endRun()90         void endRun() {
91             m_rootTracker.reset();
92             m_currentTracker = CATCH_NULL;
93             m_runState = NotStarted;
94         }
95 
startCycle()96         void startCycle() {
97             m_currentTracker = m_rootTracker.get();
98             m_runState = Executing;
99         }
completeCycle()100         void completeCycle() {
101             m_runState = CompletedCycle;
102         }
103 
completedCycle() const104         bool completedCycle() const {
105             return m_runState == CompletedCycle;
106         }
currentTracker()107         ITracker& currentTracker() {
108             return *m_currentTracker;
109         }
setCurrentTracker(ITracker * tracker)110         void setCurrentTracker( ITracker* tracker ) {
111             m_currentTracker = tracker;
112         }
113     };
114 
115     class TrackerBase : public ITracker {
116     protected:
117         enum CycleState {
118             NotStarted,
119             Executing,
120             ExecutingChildren,
121             NeedsAnotherRun,
122             CompletedSuccessfully,
123             Failed
124         };
125         class TrackerHasName {
126             NameAndLocation m_nameAndLocation;
127         public:
TrackerHasName(NameAndLocation const & nameAndLocation)128             TrackerHasName( NameAndLocation const& nameAndLocation ) : m_nameAndLocation( nameAndLocation ) {}
operator ()(Ptr<ITracker> const & tracker)129             bool operator ()( Ptr<ITracker> const& tracker ) {
130                 return
131                     tracker->nameAndLocation().name == m_nameAndLocation.name &&
132                     tracker->nameAndLocation().location == m_nameAndLocation.location;
133             }
134         };
135         typedef std::vector<Ptr<ITracker> > Children;
136         NameAndLocation m_nameAndLocation;
137         TrackerContext& m_ctx;
138         ITracker* m_parent;
139         Children m_children;
140         CycleState m_runState;
141     public:
TrackerBase(NameAndLocation const & nameAndLocation,TrackerContext & ctx,ITracker * parent)142         TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent )
143         :   m_nameAndLocation( nameAndLocation ),
144             m_ctx( ctx ),
145             m_parent( parent ),
146             m_runState( NotStarted )
147         {}
148         virtual ~TrackerBase();
149 
nameAndLocation() const150         virtual NameAndLocation const& nameAndLocation() const CATCH_OVERRIDE {
151             return m_nameAndLocation;
152         }
isComplete() const153         virtual bool isComplete() const CATCH_OVERRIDE {
154             return m_runState == CompletedSuccessfully || m_runState == Failed;
155         }
isSuccessfullyCompleted() const156         virtual bool isSuccessfullyCompleted() const CATCH_OVERRIDE {
157             return m_runState == CompletedSuccessfully;
158         }
isOpen() const159         virtual bool isOpen() const CATCH_OVERRIDE {
160             return m_runState != NotStarted && !isComplete();
161         }
hasChildren() const162         virtual bool hasChildren() const CATCH_OVERRIDE {
163             return !m_children.empty();
164         }
165 
166 
addChild(Ptr<ITracker> const & child)167         virtual void addChild( Ptr<ITracker> const& child ) CATCH_OVERRIDE {
168             m_children.push_back( child );
169         }
170 
findChild(NameAndLocation const & nameAndLocation)171         virtual ITracker* findChild( NameAndLocation const& nameAndLocation ) CATCH_OVERRIDE {
172             Children::const_iterator it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( nameAndLocation ) );
173             return( it != m_children.end() )
174                 ? it->get()
175                 : CATCH_NULL;
176         }
parent()177         virtual ITracker& parent() CATCH_OVERRIDE {
178             assert( m_parent ); // Should always be non-null except for root
179             return *m_parent;
180         }
181 
openChild()182         virtual void openChild() CATCH_OVERRIDE {
183             if( m_runState != ExecutingChildren ) {
184                 m_runState = ExecutingChildren;
185                 if( m_parent )
186                     m_parent->openChild();
187             }
188         }
189 
isSectionTracker() const190         virtual bool isSectionTracker() const CATCH_OVERRIDE { return false; }
isIndexTracker() const191         virtual bool isIndexTracker() const CATCH_OVERRIDE { return false; }
192 
open()193         void open() {
194             m_runState = Executing;
195             moveToThis();
196             if( m_parent )
197                 m_parent->openChild();
198         }
199 
close()200         virtual void close() CATCH_OVERRIDE {
201 
202             // Close any still open children (e.g. generators)
203             while( &m_ctx.currentTracker() != this )
204                 m_ctx.currentTracker().close();
205 
206             switch( m_runState ) {
207                 case NotStarted:
208                 case CompletedSuccessfully:
209                 case Failed:
210                     throw std::logic_error( "Illogical state" );
211 
212                 case NeedsAnotherRun:
213                     break;;
214 
215                 case Executing:
216                     m_runState = CompletedSuccessfully;
217                     break;
218                 case ExecutingChildren:
219                     if( m_children.empty() || m_children.back()->isComplete() )
220                         m_runState = CompletedSuccessfully;
221                     break;
222 
223                 default:
224                     throw std::logic_error( "Unexpected state" );
225             }
226             moveToParent();
227             m_ctx.completeCycle();
228         }
fail()229         virtual void fail() CATCH_OVERRIDE {
230             m_runState = Failed;
231             if( m_parent )
232                 m_parent->markAsNeedingAnotherRun();
233             moveToParent();
234             m_ctx.completeCycle();
235         }
markAsNeedingAnotherRun()236         virtual void markAsNeedingAnotherRun() CATCH_OVERRIDE {
237             m_runState = NeedsAnotherRun;
238         }
239     private:
moveToParent()240         void moveToParent() {
241             assert( m_parent );
242             m_ctx.setCurrentTracker( m_parent );
243         }
moveToThis()244         void moveToThis() {
245             m_ctx.setCurrentTracker( this );
246         }
247     };
248 
249     class SectionTracker : public TrackerBase {
250         std::vector<std::string> m_filters;
251     public:
SectionTracker(NameAndLocation const & nameAndLocation,TrackerContext & ctx,ITracker * parent)252         SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent )
253         :   TrackerBase( nameAndLocation, ctx, parent )
254         {
255             if( parent ) {
256                 while( !parent->isSectionTracker() )
257                     parent = &parent->parent();
258 
259                 SectionTracker& parentSection = static_cast<SectionTracker&>( *parent );
260                 addNextFilters( parentSection.m_filters );
261             }
262         }
263         virtual ~SectionTracker();
264 
isSectionTracker() const265         virtual bool isSectionTracker() const CATCH_OVERRIDE { return true; }
266 
acquire(TrackerContext & ctx,NameAndLocation const & nameAndLocation)267         static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) {
268             SectionTracker* section = CATCH_NULL;
269 
270             ITracker& currentTracker = ctx.currentTracker();
271             if( ITracker* childTracker = currentTracker.findChild( nameAndLocation ) ) {
272                 assert( childTracker );
273                 assert( childTracker->isSectionTracker() );
274                 section = static_cast<SectionTracker*>( childTracker );
275             }
276             else {
277                 section = new SectionTracker( nameAndLocation, ctx, &currentTracker );
278                 currentTracker.addChild( section );
279             }
280             if( !ctx.completedCycle() )
281                 section->tryOpen();
282             return *section;
283         }
284 
tryOpen()285         void tryOpen() {
286             if( !isComplete() && (m_filters.empty() || m_filters[0].empty() ||  m_filters[0] == m_nameAndLocation.name ) )
287                 open();
288         }
289 
addInitialFilters(std::vector<std::string> const & filters)290         void addInitialFilters( std::vector<std::string> const& filters ) {
291             if( !filters.empty() ) {
292                 m_filters.push_back(""); // Root - should never be consulted
293                 m_filters.push_back(""); // Test Case - not a section filter
294                 m_filters.insert( m_filters.end(), filters.begin(), filters.end() );
295             }
296         }
addNextFilters(std::vector<std::string> const & filters)297         void addNextFilters( std::vector<std::string> const& filters ) {
298             if( filters.size() > 1 )
299                 m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() );
300         }
301     };
302 
303     class IndexTracker : public TrackerBase {
304         int m_size;
305         int m_index;
306     public:
IndexTracker(NameAndLocation const & nameAndLocation,TrackerContext & ctx,ITracker * parent,int size)307         IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size )
308         :   TrackerBase( nameAndLocation, ctx, parent ),
309             m_size( size ),
310             m_index( -1 )
311         {}
312         virtual ~IndexTracker();
313 
isIndexTracker() const314         virtual bool isIndexTracker() const CATCH_OVERRIDE { return true; }
315 
acquire(TrackerContext & ctx,NameAndLocation const & nameAndLocation,int size)316         static IndexTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ) {
317             IndexTracker* tracker = CATCH_NULL;
318 
319             ITracker& currentTracker = ctx.currentTracker();
320             if( ITracker* childTracker = currentTracker.findChild( nameAndLocation ) ) {
321                 assert( childTracker );
322                 assert( childTracker->isIndexTracker() );
323                 tracker = static_cast<IndexTracker*>( childTracker );
324             }
325             else {
326                 tracker = new IndexTracker( nameAndLocation, ctx, &currentTracker, size );
327                 currentTracker.addChild( tracker );
328             }
329 
330             if( !ctx.completedCycle() && !tracker->isComplete() ) {
331                 if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun )
332                     tracker->moveNext();
333                 tracker->open();
334             }
335 
336             return *tracker;
337         }
338 
index() const339         int index() const { return m_index; }
340 
moveNext()341         void moveNext() {
342             m_index++;
343             m_children.clear();
344         }
345 
close()346         virtual void close() CATCH_OVERRIDE {
347             TrackerBase::close();
348             if( m_runState == CompletedSuccessfully && m_index < m_size-1 )
349                 m_runState = Executing;
350         }
351     };
352 
startRun()353     inline ITracker& TrackerContext::startRun() {
354         m_rootTracker = new SectionTracker( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, CATCH_NULL );
355         m_currentTracker = CATCH_NULL;
356         m_runState = Executing;
357         return *m_rootTracker;
358     }
359 
360 } // namespace TestCaseTracking
361 
362 using TestCaseTracking::ITracker;
363 using TestCaseTracking::TrackerContext;
364 using TestCaseTracking::SectionTracker;
365 using TestCaseTracking::IndexTracker;
366 
367 } // namespace Catch
368 
369 CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS
370 
371 #endif // TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED
372