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, ¤tTracker ); 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, ¤tTracker, 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