Internal linkage for generator trackers

This commit is contained in:
Martin Hořeňovský 2023-03-16 16:30:06 +01:00
parent 72f3ce4db5
commit 3c8fb6bbb2
No known key found for this signature in database
GPG Key ID: DE48307B8B0D381A
1 changed files with 127 additions and 119 deletions

View File

@ -26,135 +26,143 @@
namespace Catch { namespace Catch {
namespace Generators { namespace Generators {
struct GeneratorTracker : TestCaseTracking::TrackerBase, IGeneratorTracker { namespace {
GeneratorBasePtr m_generator; struct GeneratorTracker : TestCaseTracking::TrackerBase,
IGeneratorTracker {
GeneratorBasePtr m_generator;
GeneratorTracker( TestCaseTracking::NameAndLocation&& nameAndLocation, TrackerContext& ctx, ITracker* parent ) GeneratorTracker(
: TrackerBase( CATCH_MOVE(nameAndLocation), ctx, parent ) TestCaseTracking::NameAndLocation&& nameAndLocation,
{} TrackerContext& ctx,
~GeneratorTracker() override; ITracker* parent ):
TrackerBase( CATCH_MOVE( nameAndLocation ), ctx, parent ) {}
~GeneratorTracker() override;
static GeneratorTracker* acquire( TrackerContext& ctx, TestCaseTracking::NameAndLocationRef const& nameAndLocation ) { static GeneratorTracker*
GeneratorTracker* tracker; acquire( TrackerContext& ctx,
TestCaseTracking::NameAndLocationRef const&
nameAndLocation ) {
GeneratorTracker* tracker;
ITracker& currentTracker = ctx.currentTracker(); ITracker& currentTracker = ctx.currentTracker();
// Under specific circumstances, the generator we want // Under specific circumstances, the generator we want
// to acquire is also the current tracker. If this is // to acquire is also the current tracker. If this is
// the case, we have to avoid looking through current // the case, we have to avoid looking through current
// tracker's children, and instead return the current // tracker's children, and instead return the current
// tracker. // tracker.
// A case where this check is important is e.g. // A case where this check is important is e.g.
// for (int i = 0; i < 5; ++i) { // for (int i = 0; i < 5; ++i) {
// int n = GENERATE(1, 2); // int n = GENERATE(1, 2);
// } // }
// //
// without it, the code above creates 5 nested generators. // without it, the code above creates 5 nested generators.
if ( currentTracker.nameAndLocation() == nameAndLocation ) { if ( currentTracker.nameAndLocation() == nameAndLocation ) {
auto thisTracker = auto thisTracker = currentTracker.parent()->findChild(
currentTracker.parent()->findChild( nameAndLocation ); nameAndLocation );
assert( thisTracker ); assert( thisTracker );
assert( thisTracker->isGeneratorTracker() ); assert( thisTracker->isGeneratorTracker() );
tracker = static_cast<GeneratorTracker*>( thisTracker ); tracker = static_cast<GeneratorTracker*>( thisTracker );
} else if ( ITracker* childTracker = } else if ( ITracker* childTracker =
currentTracker.findChild( nameAndLocation ) ) { currentTracker.findChild(
assert( childTracker ); nameAndLocation ) ) {
assert( childTracker->isGeneratorTracker() ); assert( childTracker );
tracker = static_cast<GeneratorTracker*>( childTracker ); assert( childTracker->isGeneratorTracker() );
} else { tracker =
return nullptr; static_cast<GeneratorTracker*>( childTracker );
} else {
return nullptr;
}
if ( !tracker->isComplete() ) { tracker->open(); }
return tracker;
} }
if( !tracker->isComplete() ) { // TrackerBase interface
tracker->open(); bool isGeneratorTracker() const override { return true; }
auto hasGenerator() const -> bool override {
return !!m_generator;
} }
void close() override {
return tracker; TrackerBase::close();
} // If a generator has a child (it is followed by a section)
// and none of its children have started, then we must wait
// TrackerBase interface // until later to start consuming its values.
bool isGeneratorTracker() const override { return true; } // This catches cases where `GENERATE` is placed between two
auto hasGenerator() const -> bool override { // `SECTION`s.
return !!m_generator; // **The check for m_children.empty cannot be removed**.
} // doing so would break `GENERATE` _not_ followed by
void close() override { // `SECTION`s.
TrackerBase::close(); const bool should_wait_for_child = [&]() {
// If a generator has a child (it is followed by a section) // No children -> nobody to wait for
// and none of its children have started, then we must wait if ( m_children.empty() ) { return false; }
// until later to start consuming its values. // If at least one child started executing, don't wait
// This catches cases where `GENERATE` is placed between two if ( std::find_if(
// `SECTION`s. m_children.begin(),
// **The check for m_children.empty cannot be removed**. m_children.end(),
// doing so would break `GENERATE` _not_ followed by `SECTION`s. []( TestCaseTracking::ITrackerPtr const&
const bool should_wait_for_child = [&]() { tracker ) {
// No children -> nobody to wait for return tracker->hasStarted();
if ( m_children.empty() ) { } ) != m_children.end() ) {
return false; return false;
}
// If at least one child started executing, don't wait
if ( std::find_if(
m_children.begin(),
m_children.end(),
[]( TestCaseTracking::ITrackerPtr const& tracker ) {
return tracker->hasStarted();
} ) != m_children.end() ) {
return false;
}
// No children have started. We need to check if they _can_
// start, and thus we should wait for them, or they cannot
// start (due to filters), and we shouldn't wait for them
ITracker* parent = m_parent;
// This is safe: there is always at least one section
// tracker in a test case tracking tree
while ( !parent->isSectionTracker() ) {
parent = parent->parent();
}
assert( parent &&
"Missing root (test case) level section" );
auto const& parentSection =
static_cast<SectionTracker const&>( *parent );
auto const& filters = parentSection.getFilters();
// No filters -> no restrictions on running sections
if ( filters.empty() ) {
return true;
}
for ( auto const& child : m_children ) {
if ( child->isSectionTracker() &&
std::find(
filters.begin(),
filters.end(),
static_cast<SectionTracker const&>( *child )
.trimmedName() ) != filters.end() ) {
return true;
} }
// No children have started. We need to check if they
// _can_ start, and thus we should wait for them, or
// they cannot start (due to filters), and we shouldn't
// wait for them
ITracker* parent = m_parent;
// This is safe: there is always at least one section
// tracker in a test case tracking tree
while ( !parent->isSectionTracker() ) {
parent = parent->parent();
}
assert( parent &&
"Missing root (test case) level section" );
auto const& parentSection =
static_cast<SectionTracker const&>( *parent );
auto const& filters = parentSection.getFilters();
// No filters -> no restrictions on running sections
if ( filters.empty() ) { return true; }
for ( auto const& child : m_children ) {
if ( child->isSectionTracker() &&
std::find( filters.begin(),
filters.end(),
static_cast<SectionTracker const&>(
*child )
.trimmedName() ) !=
filters.end() ) {
return true;
}
}
return false;
}();
// This check is a bit tricky, because m_generator->next()
// has a side-effect, where it consumes generator's current
// value, but we do not want to invoke the side-effect if
// this generator is still waiting for any child to start.
assert( m_generator && "Tracker without generator" );
if ( should_wait_for_child ||
( m_runState == CompletedSuccessfully &&
m_generator->countedNext() ) ) {
m_children.clear();
m_runState = Executing;
} }
return false;
}();
// This check is a bit tricky, because m_generator->next()
// has a side-effect, where it consumes generator's current
// value, but we do not want to invoke the side-effect if
// this generator is still waiting for any child to start.
assert( m_generator && "Tracker without generator" );
if ( should_wait_for_child ||
( m_runState == CompletedSuccessfully &&
m_generator->countedNext() ) ) {
m_children.clear();
m_runState = Executing;
} }
}
// IGeneratorTracker interface // IGeneratorTracker interface
auto getGenerator() const -> GeneratorBasePtr const& override { auto getGenerator() const -> GeneratorBasePtr const& override {
return m_generator; return m_generator;
} }
void setGenerator( GeneratorBasePtr&& generator ) override { void setGenerator( GeneratorBasePtr&& generator ) override {
m_generator = CATCH_MOVE( generator ); m_generator = CATCH_MOVE( generator );
} }
}; };
GeneratorTracker::~GeneratorTracker() = default; GeneratorTracker::~GeneratorTracker() = default;
} // namespace
} }
RunContext::RunContext(IConfig const* _config, IEventListenerPtr&& reporter) RunContext::RunContext(IConfig const* _config, IEventListenerPtr&& reporter)