// Copyright Catch2 Authors // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE.txt or copy at // https://www.boost.org/LICENSE_1_0.txt) // SPDX-License-Identifier: BSL-1.0 #ifndef CATCH_TEST_CASE_TRACKER_HPP_INCLUDED #define CATCH_TEST_CASE_TRACKER_HPP_INCLUDED #include #include #include #include #include namespace Catch { namespace TestCaseTracking { struct NameAndLocation { std::string name; SourceLineInfo location; NameAndLocation( std::string&& _name, SourceLineInfo const& _location ); friend bool operator==(NameAndLocation const& lhs, NameAndLocation const& rhs) { // This is a very cheap check that should have a very high hit rate. // If we get to SourceLineInfo::operator==, we will redo it, but the // cost of repeating is trivial at that point (we will be paying // multiple strcmp/memcmps at that point). if ( lhs.location.line != rhs.location.line ) { return false; } return lhs.name == rhs.name && lhs.location == rhs.location; } friend bool operator!=(NameAndLocation const& lhs, NameAndLocation const& rhs) { return !( lhs == rhs ); } }; /** * This is a variant of `NameAndLocation` that does not own the name string * * This avoids extra allocations when trying to locate a tracker by its * name and location, as long as we make sure that trackers only keep * around the owning variant. */ struct NameAndLocationRef { StringRef name; SourceLineInfo location; constexpr NameAndLocationRef( StringRef name_, SourceLineInfo location_ ): name( name_ ), location( location_ ) {} friend bool operator==( NameAndLocation const& lhs, NameAndLocationRef const& rhs ) { // This is a very cheap check that should have a very high hit rate. // If we get to SourceLineInfo::operator==, we will redo it, but the // cost of repeating is trivial at that point (we will be paying // multiple strcmp/memcmps at that point). if ( lhs.location.line != rhs.location.line ) { return false; } return StringRef( lhs.name ) == rhs.name && lhs.location == rhs.location; } friend bool operator==( NameAndLocationRef const& lhs, NameAndLocation const& rhs ) { return rhs == lhs; } }; class ITracker; using ITrackerPtr = Catch::Detail::unique_ptr; class ITracker { NameAndLocation m_nameAndLocation; using Children = std::vector; protected: enum CycleState { NotStarted, Executing, ExecutingChildren, NeedsAnotherRun, CompletedSuccessfully, Failed }; ITracker* m_parent = nullptr; Children m_children; CycleState m_runState = NotStarted; public: ITracker( NameAndLocation&& nameAndLoc, ITracker* parent ): m_nameAndLocation( CATCH_MOVE(nameAndLoc) ), m_parent( parent ) {} // static queries NameAndLocation const& nameAndLocation() const { return m_nameAndLocation; } ITracker* parent() const { return m_parent; } virtual ~ITracker(); // = default // dynamic queries //! Returns true if tracker run to completion (successfully or not) virtual bool isComplete() const = 0; //! Returns true if tracker run to completion successfully bool isSuccessfullyCompleted() const { return m_runState == CompletedSuccessfully; } //! Returns true if tracker has started but hasn't been completed bool isOpen() const; //! Returns true iff tracker has started bool hasStarted() const; // actions virtual void close() = 0; // Successfully complete virtual void fail() = 0; void markAsNeedingAnotherRun(); //! Register a nested ITracker void addChild( ITrackerPtr&& child ); /** * Returns ptr to specific child if register with this tracker. * * Returns nullptr if not found. */ ITracker* findChild( NameAndLocationRef const& nameAndLocation ); //! Have any children been added? bool hasChildren() const { return !m_children.empty(); } //! Marks tracker as executing a child, doing se recursively up the tree void openChild(); /** * Returns true if the instance is a section tracker * * Subclasses should override to true if they are, replaces RTTI * for internal debug checks. */ virtual bool isSectionTracker() const; /** * Returns true if the instance is a generator tracker * * Subclasses should override to true if they are, replaces RTTI * for internal debug checks. */ virtual bool isGeneratorTracker() const; }; class TrackerContext { enum RunState { NotStarted, Executing, CompletedCycle }; ITrackerPtr m_rootTracker; ITracker* m_currentTracker = nullptr; RunState m_runState = NotStarted; public: ITracker& startRun(); void startCycle() { m_currentTracker = m_rootTracker.get(); m_runState = Executing; } void completeCycle(); bool completedCycle() const; ITracker& currentTracker() { return *m_currentTracker; } void setCurrentTracker( ITracker* tracker ); }; class TrackerBase : public ITracker { protected: TrackerContext& m_ctx; public: TrackerBase( NameAndLocation&& nameAndLocation, TrackerContext& ctx, ITracker* parent ); bool isComplete() const override; void open(); void close() override; void fail() override; private: void moveToParent(); void moveToThis(); }; class SectionTracker : public TrackerBase { std::vector m_filters; // Note that lifetime-wise we piggy back off the name stored in the `ITracker` parent`. // Currently it allocates owns the name, so this is safe. If it is later refactored // to not own the name, the name still has to outlive the `ITracker` parent, so // this should still be safe. StringRef m_trimmed_name; public: SectionTracker( NameAndLocation&& nameAndLocation, TrackerContext& ctx, ITracker* parent ); bool isSectionTracker() const override; bool isComplete() const override; static SectionTracker& acquire( TrackerContext& ctx, NameAndLocationRef const& nameAndLocation ); void tryOpen(); void addInitialFilters( std::vector const& filters ); void addNextFilters( std::vector const& filters ); //! Returns filters active in this tracker std::vector const& getFilters() const { return m_filters; } //! Returns whitespace-trimmed name of the tracked section StringRef trimmedName() const; }; } // namespace TestCaseTracking using TestCaseTracking::ITracker; using TestCaseTracking::TrackerContext; using TestCaseTracking::SectionTracker; } // namespace Catch #endif // CATCH_TEST_CASE_TRACKER_HPP_INCLUDED