1 /*
2  *  Created by Martin on 19/07/2017
3  *
4  *  Distributed under the Boost Software License, Version 1.0. (See accompanying
5  *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  */
7 
8 #include "catch_test_case_tracker.h"
9 
10 #include "catch_enforce.h"
11 
12 #include <algorithm>
13 #include <assert.h>
14 #include <stdexcept>
15 #include <memory>
16 #include <sstream>
17 
18 #if defined(__clang__)
19 #    pragma clang diagnostic push
20 #    pragma clang diagnostic ignored "-Wexit-time-destructors"
21 #endif
22 
23 namespace Catch {
24 namespace TestCaseTracking {
25 
NameAndLocation(std::string const & _name,SourceLineInfo const & _location)26     NameAndLocation::NameAndLocation( std::string const& _name, SourceLineInfo const& _location )
27     :   name( _name ),
28         location( _location )
29     {}
30 
31 
32     ITracker::~ITracker() = default;
33 
34 
instance()35     TrackerContext& TrackerContext::instance() {
36         static TrackerContext s_instance;
37         return s_instance;
38     }
39 
startRun()40     ITracker& TrackerContext::startRun() {
41         m_rootTracker = std::make_shared<SectionTracker>( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, nullptr );
42         m_currentTracker = nullptr;
43         m_runState = Executing;
44         return *m_rootTracker;
45     }
46 
endRun()47     void TrackerContext::endRun() {
48         m_rootTracker.reset();
49         m_currentTracker = nullptr;
50         m_runState = NotStarted;
51     }
52 
startCycle()53     void TrackerContext::startCycle() {
54         m_currentTracker = m_rootTracker.get();
55         m_runState = Executing;
56     }
completeCycle()57     void TrackerContext::completeCycle() {
58         m_runState = CompletedCycle;
59     }
60 
completedCycle() const61     bool TrackerContext::completedCycle() const {
62         return m_runState == CompletedCycle;
63     }
currentTracker()64     ITracker& TrackerContext::currentTracker() {
65         return *m_currentTracker;
66     }
setCurrentTracker(ITracker * tracker)67     void TrackerContext::setCurrentTracker( ITracker* tracker ) {
68         m_currentTracker = tracker;
69     }
70 
71 
72 
TrackerHasName(NameAndLocation const & nameAndLocation)73     TrackerBase::TrackerHasName::TrackerHasName( NameAndLocation const& nameAndLocation ) : m_nameAndLocation( nameAndLocation ) {}
operator ()(ITrackerPtr const & tracker) const74     bool TrackerBase::TrackerHasName::operator ()( ITrackerPtr const& tracker ) const {
75         return
76             tracker->nameAndLocation().name == m_nameAndLocation.name &&
77             tracker->nameAndLocation().location == m_nameAndLocation.location;
78     }
79 
TrackerBase(NameAndLocation const & nameAndLocation,TrackerContext & ctx,ITracker * parent)80     TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent )
81     :   m_nameAndLocation( nameAndLocation ),
82         m_ctx( ctx ),
83         m_parent( parent )
84     {}
85 
nameAndLocation() const86     NameAndLocation const& TrackerBase::nameAndLocation() const {
87         return m_nameAndLocation;
88     }
isComplete() const89     bool TrackerBase::isComplete() const {
90         return m_runState == CompletedSuccessfully || m_runState == Failed;
91     }
isSuccessfullyCompleted() const92     bool TrackerBase::isSuccessfullyCompleted() const {
93         return m_runState == CompletedSuccessfully;
94     }
isOpen() const95     bool TrackerBase::isOpen() const {
96         return m_runState != NotStarted && !isComplete();
97     }
hasChildren() const98     bool TrackerBase::hasChildren() const {
99         return !m_children.empty();
100     }
101 
102 
addChild(ITrackerPtr const & child)103     void TrackerBase::addChild( ITrackerPtr const& child ) {
104         m_children.push_back( child );
105     }
106 
findChild(NameAndLocation const & nameAndLocation)107     ITrackerPtr TrackerBase::findChild( NameAndLocation const& nameAndLocation ) {
108         auto it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( nameAndLocation ) );
109         return( it != m_children.end() )
110             ? *it
111             : nullptr;
112     }
parent()113     ITracker& TrackerBase::parent() {
114         assert( m_parent ); // Should always be non-null except for root
115         return *m_parent;
116     }
117 
openChild()118     void TrackerBase::openChild() {
119         if( m_runState != ExecutingChildren ) {
120             m_runState = ExecutingChildren;
121             if( m_parent )
122                 m_parent->openChild();
123         }
124     }
125 
isSectionTracker() const126     bool TrackerBase::isSectionTracker() const { return false; }
isIndexTracker() const127     bool TrackerBase::isIndexTracker() const { return false; }
128 
open()129     void TrackerBase::open() {
130         m_runState = Executing;
131         moveToThis();
132         if( m_parent )
133             m_parent->openChild();
134     }
135 
close()136     void TrackerBase::close() {
137 
138         // Close any still open children (e.g. generators)
139         while( &m_ctx.currentTracker() != this )
140             m_ctx.currentTracker().close();
141 
142         switch( m_runState ) {
143             case NeedsAnotherRun:
144                 break;
145 
146             case Executing:
147                 m_runState = CompletedSuccessfully;
148                 break;
149             case ExecutingChildren:
150                 if( m_children.empty() || m_children.back()->isComplete() )
151                     m_runState = CompletedSuccessfully;
152                 break;
153 
154             case NotStarted:
155             case CompletedSuccessfully:
156             case Failed:
157                 CATCH_INTERNAL_ERROR( "Illogical state: " << m_runState );
158 
159             default:
160                 CATCH_INTERNAL_ERROR( "Unknown state: " << m_runState );
161         }
162         moveToParent();
163         m_ctx.completeCycle();
164     }
fail()165     void TrackerBase::fail() {
166         m_runState = Failed;
167         if( m_parent )
168             m_parent->markAsNeedingAnotherRun();
169         moveToParent();
170         m_ctx.completeCycle();
171     }
markAsNeedingAnotherRun()172     void TrackerBase::markAsNeedingAnotherRun() {
173         m_runState = NeedsAnotherRun;
174     }
175 
moveToParent()176     void TrackerBase::moveToParent() {
177         assert( m_parent );
178         m_ctx.setCurrentTracker( m_parent );
179     }
moveToThis()180     void TrackerBase::moveToThis() {
181         m_ctx.setCurrentTracker( this );
182     }
183 
SectionTracker(NameAndLocation const & nameAndLocation,TrackerContext & ctx,ITracker * parent)184     SectionTracker::SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent )
185     :   TrackerBase( nameAndLocation, ctx, parent )
186     {
187         if( parent ) {
188             while( !parent->isSectionTracker() )
189                 parent = &parent->parent();
190 
191             SectionTracker& parentSection = static_cast<SectionTracker&>( *parent );
192             addNextFilters( parentSection.m_filters );
193         }
194     }
195 
isSectionTracker() const196     bool SectionTracker::isSectionTracker() const { return true; }
197 
acquire(TrackerContext & ctx,NameAndLocation const & nameAndLocation)198     SectionTracker& SectionTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) {
199         std::shared_ptr<SectionTracker> section;
200 
201         ITracker& currentTracker = ctx.currentTracker();
202         if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) {
203             assert( childTracker );
204             assert( childTracker->isSectionTracker() );
205             section = std::static_pointer_cast<SectionTracker>( childTracker );
206         }
207         else {
208             section = std::make_shared<SectionTracker>( nameAndLocation, ctx, &currentTracker );
209             currentTracker.addChild( section );
210         }
211         if( !ctx.completedCycle() )
212             section->tryOpen();
213         return *section;
214     }
215 
tryOpen()216     void SectionTracker::tryOpen() {
217         if( !isComplete() && (m_filters.empty() || m_filters[0].empty() ||  m_filters[0] == m_nameAndLocation.name ) )
218             open();
219     }
220 
addInitialFilters(std::vector<std::string> const & filters)221     void SectionTracker::addInitialFilters( std::vector<std::string> const& filters ) {
222         if( !filters.empty() ) {
223             m_filters.push_back(""); // Root - should never be consulted
224             m_filters.push_back(""); // Test Case - not a section filter
225             m_filters.insert( m_filters.end(), filters.begin(), filters.end() );
226         }
227     }
addNextFilters(std::vector<std::string> const & filters)228     void SectionTracker::addNextFilters( std::vector<std::string> const& filters ) {
229         if( filters.size() > 1 )
230             m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() );
231     }
232 
IndexTracker(NameAndLocation const & nameAndLocation,TrackerContext & ctx,ITracker * parent,int size)233     IndexTracker::IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size )
234     :   TrackerBase( nameAndLocation, ctx, parent ),
235         m_size( size )
236     {}
237 
isIndexTracker() const238     bool IndexTracker::isIndexTracker() const { return true; }
239 
acquire(TrackerContext & ctx,NameAndLocation const & nameAndLocation,int size)240     IndexTracker& IndexTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ) {
241         std::shared_ptr<IndexTracker> tracker;
242 
243         ITracker& currentTracker = ctx.currentTracker();
244         if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) {
245             assert( childTracker );
246             assert( childTracker->isIndexTracker() );
247             tracker = std::static_pointer_cast<IndexTracker>( childTracker );
248         }
249         else {
250             tracker = std::make_shared<IndexTracker>( nameAndLocation, ctx, &currentTracker, size );
251             currentTracker.addChild( tracker );
252         }
253 
254         if( !ctx.completedCycle() && !tracker->isComplete() ) {
255             if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun )
256                 tracker->moveNext();
257             tracker->open();
258         }
259 
260         return *tracker;
261     }
262 
index() const263     int IndexTracker::index() const { return m_index; }
264 
moveNext()265     void IndexTracker::moveNext() {
266         m_index++;
267         m_children.clear();
268     }
269 
close()270     void IndexTracker::close() {
271         TrackerBase::close();
272         if( m_runState == CompletedSuccessfully && m_index < m_size-1 )
273             m_runState = Executing;
274     }
275 
276 } // namespace TestCaseTracking
277 
278 using TestCaseTracking::ITracker;
279 using TestCaseTracking::TrackerContext;
280 using TestCaseTracking::SectionTracker;
281 using TestCaseTracking::IndexTracker;
282 
283 } // namespace Catch
284 
285 #if defined(__clang__)
286 #    pragma clang diagnostic pop
287 #endif
288