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 <cassert>
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 
TrackerBase(NameAndLocation const & nameAndLocation,TrackerContext & ctx,ITracker * parent)72     TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent )
73     :   m_nameAndLocation( nameAndLocation ),
74         m_ctx( ctx ),
75         m_parent( parent )
76     {}
77 
nameAndLocation() const78     NameAndLocation const& TrackerBase::nameAndLocation() const {
79         return m_nameAndLocation;
80     }
isComplete() const81     bool TrackerBase::isComplete() const {
82         return m_runState == CompletedSuccessfully || m_runState == Failed;
83     }
isSuccessfullyCompleted() const84     bool TrackerBase::isSuccessfullyCompleted() const {
85         return m_runState == CompletedSuccessfully;
86     }
isOpen() const87     bool TrackerBase::isOpen() const {
88         return m_runState != NotStarted && !isComplete();
89     }
hasChildren() const90     bool TrackerBase::hasChildren() const {
91         return !m_children.empty();
92     }
93 
94 
addChild(ITrackerPtr const & child)95     void TrackerBase::addChild( ITrackerPtr const& child ) {
96         m_children.push_back( child );
97     }
98 
findChild(NameAndLocation const & nameAndLocation)99     ITrackerPtr TrackerBase::findChild( NameAndLocation const& nameAndLocation ) {
100         auto it = std::find_if( m_children.begin(), m_children.end(),
101             [&nameAndLocation]( ITrackerPtr const& tracker ){
102                 return
103                     tracker->nameAndLocation().location == nameAndLocation.location &&
104                     tracker->nameAndLocation().name == nameAndLocation.name;
105             } );
106         return( it != m_children.end() )
107             ? *it
108             : nullptr;
109     }
parent()110     ITracker& TrackerBase::parent() {
111         assert( m_parent ); // Should always be non-null except for root
112         return *m_parent;
113     }
114 
openChild()115     void TrackerBase::openChild() {
116         if( m_runState != ExecutingChildren ) {
117             m_runState = ExecutingChildren;
118             if( m_parent )
119                 m_parent->openChild();
120         }
121     }
122 
isSectionTracker() const123     bool TrackerBase::isSectionTracker() const { return false; }
isGeneratorTracker() const124     bool TrackerBase::isGeneratorTracker() const { return false; }
125 
open()126     void TrackerBase::open() {
127         m_runState = Executing;
128         moveToThis();
129         if( m_parent )
130             m_parent->openChild();
131     }
132 
close()133     void TrackerBase::close() {
134 
135         // Close any still open children (e.g. generators)
136         while( &m_ctx.currentTracker() != this )
137             m_ctx.currentTracker().close();
138 
139         switch( m_runState ) {
140             case NeedsAnotherRun:
141                 break;
142 
143             case Executing:
144                 m_runState = CompletedSuccessfully;
145                 break;
146             case ExecutingChildren:
147                 if( m_children.empty() || m_children.back()->isComplete() )
148                     m_runState = CompletedSuccessfully;
149                 break;
150 
151             case NotStarted:
152             case CompletedSuccessfully:
153             case Failed:
154                 CATCH_INTERNAL_ERROR( "Illogical state: " << m_runState );
155 
156             default:
157                 CATCH_INTERNAL_ERROR( "Unknown state: " << m_runState );
158         }
159         moveToParent();
160         m_ctx.completeCycle();
161     }
fail()162     void TrackerBase::fail() {
163         m_runState = Failed;
164         if( m_parent )
165             m_parent->markAsNeedingAnotherRun();
166         moveToParent();
167         m_ctx.completeCycle();
168     }
markAsNeedingAnotherRun()169     void TrackerBase::markAsNeedingAnotherRun() {
170         m_runState = NeedsAnotherRun;
171     }
172 
moveToParent()173     void TrackerBase::moveToParent() {
174         assert( m_parent );
175         m_ctx.setCurrentTracker( m_parent );
176     }
moveToThis()177     void TrackerBase::moveToThis() {
178         m_ctx.setCurrentTracker( this );
179     }
180 
SectionTracker(NameAndLocation const & nameAndLocation,TrackerContext & ctx,ITracker * parent)181     SectionTracker::SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent )
182     :   TrackerBase( nameAndLocation, ctx, parent )
183     {
184         if( parent ) {
185             while( !parent->isSectionTracker() )
186                 parent = &parent->parent();
187 
188             SectionTracker& parentSection = static_cast<SectionTracker&>( *parent );
189             addNextFilters( parentSection.m_filters );
190         }
191     }
192 
isComplete() const193     bool SectionTracker::isComplete() const {
194         bool complete = true;
195 
196         if ((m_filters.empty() || m_filters[0] == "") ||
197              std::find(m_filters.begin(), m_filters.end(),
198                        m_nameAndLocation.name) != m_filters.end())
199             complete = TrackerBase::isComplete();
200         return complete;
201 
202     }
203 
isSectionTracker() const204     bool SectionTracker::isSectionTracker() const { return true; }
205 
acquire(TrackerContext & ctx,NameAndLocation const & nameAndLocation)206     SectionTracker& SectionTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) {
207         std::shared_ptr<SectionTracker> section;
208 
209         ITracker& currentTracker = ctx.currentTracker();
210         if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) {
211             assert( childTracker );
212             assert( childTracker->isSectionTracker() );
213             section = std::static_pointer_cast<SectionTracker>( childTracker );
214         }
215         else {
216             section = std::make_shared<SectionTracker>( nameAndLocation, ctx, &currentTracker );
217             currentTracker.addChild( section );
218         }
219         if( !ctx.completedCycle() )
220             section->tryOpen();
221         return *section;
222     }
223 
tryOpen()224     void SectionTracker::tryOpen() {
225         if( !isComplete() && (m_filters.empty() || m_filters[0].empty() ||  m_filters[0] == m_nameAndLocation.name ) )
226             open();
227     }
228 
addInitialFilters(std::vector<std::string> const & filters)229     void SectionTracker::addInitialFilters( std::vector<std::string> const& filters ) {
230         if( !filters.empty() ) {
231             m_filters.push_back(""); // Root - should never be consulted
232             m_filters.push_back(""); // Test Case - not a section filter
233             m_filters.insert( m_filters.end(), filters.begin(), filters.end() );
234         }
235     }
addNextFilters(std::vector<std::string> const & filters)236     void SectionTracker::addNextFilters( std::vector<std::string> const& filters ) {
237         if( filters.size() > 1 )
238             m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() );
239     }
240 
241 } // namespace TestCaseTracking
242 
243 using TestCaseTracking::ITracker;
244 using TestCaseTracking::TrackerContext;
245 using TestCaseTracking::SectionTracker;
246 
247 } // namespace Catch
248 
249 #if defined(__clang__)
250 #    pragma clang diagnostic pop
251 #endif
252