// 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 #pragma once #include #include #include #include #include #include #include namespace Catch { struct PathFilter; 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_ CATCH_ATTR_LIFETIMEBOUND, 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; virtual bool isFilteredImpl() const = 0; protected: enum CycleState { NotStarted, Executing, ExecutingChildren, NeedsAnotherRun, CompletedSuccessfully, Failed }; ITracker* m_parent = nullptr; Children m_children; CycleState m_runState = NotStarted; // Members for path filtering std::vector const* m_filterRef = nullptr; // Note: There are 2 dummy section trackers (root, test-case) before // the first "real" section tracker can be encountered. We start // the default tracker at -2, so that the first "real" section // tracker overflows to index 0. // Nesting depth of this tracker, used to decide which new-style filter applies. size_t m_allTrackerDepth = static_cast( -2 ); // Nesting depth of sections (inc. this tracker), used for old-style filters. // Must be updated by the section tracker on its own. size_t m_sectionOnlyDepth = static_cast( -2 ); // Transitory: Remove once we remove backwards compatibility with old-style (v3.x) filters bool m_newStyleFilters = false; public: ITracker( NameAndLocation&& nameAndLoc, ITracker* 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; void setFilters( std::vector const* filters, bool newStyleFilters ) { m_filterRef = filters; m_newStyleFilters = newStyleFilters; } // 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; /** * Returns true if the concrete tracker instance has a filter that applies to it. */ bool isFiltered() const { // Fast path: are there even filters for tracker in this position? const size_t filter_depth = m_newStyleFilters ? m_allTrackerDepth : m_sectionOnlyDepth; if ( m_filterRef->size() <= filter_depth ) { return false; } // Slow path: If there are filters, ask the concrete tracker. // This handles things like match-all filters for that tracker. return isFilteredImpl(); } }; 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 final : public TrackerBase { // 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; bool isFilteredImpl() const override; 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(); //! Returns whitespace-trimmed name of the tracked section StringRef trimmedName() const; }; } // namespace TestCaseTracking using TestCaseTracking::ITracker; using TestCaseTracking::TrackerContext; using TestCaseTracking::SectionTracker; } // namespace Catch