catch2/src/catch2/internal/catch_test_case_tracker.hpp
Martin Hořeňovský 43f02027e4
Avoid allocations when looking for trackers
Now we delay allocating owning `NameAndLocation` instances until
we construct a new tracker (because a tracker's lifetime can be
significantly different from the underlying tracked-thing's name).

This saves 4239 allocations (436948 -> 432709) when running
`./tests/SelfTest -o /dev/null`, at some cost to code clarity
due to introducing a new ref type, `NameAndLocationRef`.
2023-01-29 10:14:20 +01:00

228 lines
6.5 KiB
C++

// 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 <catch2/internal/catch_source_line_info.hpp>
#include <catch2/internal/catch_unique_ptr.hpp>
#include <catch2/internal/catch_stringref.hpp>
#include <string>
#include <vector>
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) {
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 rhs ) {
return StringRef( lhs.name ) == rhs.name &&
lhs.location == rhs.location;
}
friend bool operator==( NameAndLocationRef lhs,
NameAndLocation const& rhs ) {
return rhs == lhs;
}
};
class ITracker;
using ITrackerPtr = Catch::Detail::unique_ptr<ITracker>;
class ITracker {
NameAndLocation m_nameAndLocation;
using Children = std::vector<ITrackerPtr>;
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 succesfully
bool isSuccessfullyCompleted() const;
//! 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 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 endRun();
void startCycle();
void completeCycle();
bool completedCycle() const;
ITracker& 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<StringRef> m_filters;
std::string 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 nameAndLocation );
void tryOpen();
void addInitialFilters( std::vector<std::string> const& filters );
void addNextFilters( std::vector<StringRef> const& filters );
//! Returns filters active in this tracker
std::vector<StringRef> const& getFilters() const;
//! 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