mirror of
https://github.com/catchorg/Catch2
synced 2026-02-23 16:22:10 +00:00
920 lines
38 KiB
C++
920 lines
38 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
|
|
#include <catch2/internal/catch_run_context.hpp>
|
|
|
|
#include <catch2/catch_user_config.hpp>
|
|
#include <catch2/interfaces/catch_interfaces_config.hpp>
|
|
#include <catch2/interfaces/catch_interfaces_generatortracker.hpp>
|
|
#include <catch2/interfaces/catch_interfaces_reporter.hpp>
|
|
#include <catch2/internal/catch_compiler_capabilities.hpp>
|
|
#include <catch2/internal/catch_context.hpp>
|
|
#include <catch2/internal/catch_enforce.hpp>
|
|
#include <catch2/internal/catch_fatal_condition_handler.hpp>
|
|
#include <catch2/internal/catch_random_number_generator.hpp>
|
|
#include <catch2/catch_timer.hpp>
|
|
#include <catch2/internal/catch_output_redirect.hpp>
|
|
#include <catch2/internal/catch_assertion_handler.hpp>
|
|
#include <catch2/internal/catch_test_failure_exception.hpp>
|
|
#include <catch2/internal/catch_thread_local.hpp>
|
|
#include <catch2/internal/catch_unreachable.hpp>
|
|
#include <catch2/internal/catch_result_type.hpp>
|
|
#include <catch2/catch_test_macros.hpp>
|
|
|
|
#include <cassert>
|
|
#include <algorithm>
|
|
|
|
namespace Catch {
|
|
|
|
namespace Generators {
|
|
namespace {
|
|
struct GeneratorTracker final : TestCaseTracking::TrackerBase,
|
|
IGeneratorTracker {
|
|
GeneratorBasePtr m_generator;
|
|
|
|
GeneratorTracker(
|
|
TestCaseTracking::NameAndLocation&& nameAndLocation,
|
|
TrackerContext& ctx,
|
|
ITracker* parent ):
|
|
TrackerBase( CATCH_MOVE( nameAndLocation ), ctx, parent ) {}
|
|
|
|
static GeneratorTracker*
|
|
acquire( TrackerContext& ctx,
|
|
TestCaseTracking::NameAndLocationRef const&
|
|
nameAndLocation ) {
|
|
GeneratorTracker* tracker;
|
|
|
|
ITracker& currentTracker = ctx.currentTracker();
|
|
// Under specific circumstances, the generator we want
|
|
// to acquire is also the current tracker. If this is
|
|
// the case, we have to avoid looking through current
|
|
// tracker's children, and instead return the current
|
|
// tracker.
|
|
// A case where this check is important is e.g.
|
|
// for (int i = 0; i < 5; ++i) {
|
|
// int n = GENERATE(1, 2);
|
|
// }
|
|
//
|
|
// without it, the code above creates 5 nested generators.
|
|
if ( currentTracker.nameAndLocation() == nameAndLocation ) {
|
|
auto thisTracker = currentTracker.parent()->findChild(
|
|
nameAndLocation );
|
|
assert( thisTracker );
|
|
assert( thisTracker->isGeneratorTracker() );
|
|
tracker = static_cast<GeneratorTracker*>( thisTracker );
|
|
} else if ( ITracker* childTracker =
|
|
currentTracker.findChild(
|
|
nameAndLocation ) ) {
|
|
assert( childTracker );
|
|
assert( childTracker->isGeneratorTracker() );
|
|
tracker =
|
|
static_cast<GeneratorTracker*>( childTracker );
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
|
|
if ( !tracker->isComplete() ) { tracker->open(); }
|
|
|
|
return tracker;
|
|
}
|
|
|
|
// TrackerBase interface
|
|
bool isGeneratorTracker() const override { return true; }
|
|
auto hasGenerator() const -> bool override {
|
|
return !!m_generator;
|
|
}
|
|
void close() override {
|
|
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
|
|
// until later to start consuming its values.
|
|
// This catches cases where `GENERATE` is placed between two
|
|
// `SECTION`s.
|
|
// **The check for m_children.empty cannot be removed**.
|
|
// doing so would break `GENERATE` _not_ followed by
|
|
// `SECTION`s.
|
|
const bool should_wait_for_child = [&]() {
|
|
// No children -> nobody to wait for
|
|
if ( m_children.empty() ) { 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() &&
|
|
static_cast<SectionTracker const&>( *child )
|
|
.trimmedName() == filters[0] ) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
// IGeneratorTracker interface
|
|
auto getGenerator() const -> GeneratorBasePtr const& override {
|
|
return m_generator;
|
|
}
|
|
void setGenerator( GeneratorBasePtr&& generator ) override {
|
|
m_generator = CATCH_MOVE( generator );
|
|
}
|
|
};
|
|
} // namespace
|
|
}
|
|
|
|
namespace Detail {
|
|
// Assertions are owned by the thread that is executing them.
|
|
// This allows for lock-free progress in common cases where we
|
|
// do not need to send the assertion events to the reporter.
|
|
// This also implies that messages are owned by their respective
|
|
// threads, and should not be shared across different threads.
|
|
//
|
|
// This implies that various pieces of metadata referring to last
|
|
// assertion result/source location/message handling, etc
|
|
// should also be thread local. For now we just use naked globals
|
|
// below, in the future we will want to allocate piece of memory
|
|
// from heap, to avoid consuming too much thread-local storage.
|
|
//
|
|
// Note that we also don't want non-trivial the thread-local variables
|
|
// below be initialized for every thread, only for those that touch
|
|
// Catch2. To make this work with both GCC/Clang and MSVC, we have to
|
|
// make them thread-local magic statics. (Class-level statics have the
|
|
// desired semantics on GCC, but not on MSVC).
|
|
|
|
// This is used for the "if" part of CHECKED_IF/CHECKED_ELSE
|
|
static CATCH_INTERNAL_THREAD_LOCAL bool g_lastAssertionPassed = false;
|
|
|
|
// This is the source location for last encountered macro. It is
|
|
// used to provide the users with more precise location of error
|
|
// when an unexpected exception/fatal error happens.
|
|
static CATCH_INTERNAL_THREAD_LOCAL SourceLineInfo
|
|
g_lastKnownLineInfo( "DummyLocation", static_cast<size_t>( -1 ) );
|
|
|
|
// Should we clear message scopes before sending off the messages to
|
|
// reporter? Set in `assertionPassedFastPath` to avoid doing the full
|
|
// clear there for performance reasons.
|
|
static CATCH_INTERNAL_THREAD_LOCAL bool g_clearMessageScopes = false;
|
|
|
|
|
|
// Holds the data for both scoped and unscoped messages together,
|
|
// to avoid issues where their lifetimes start in wrong order,
|
|
// and then are destroyed in wrong order.
|
|
class MessageHolder {
|
|
// The actual message vector passed to the reporters
|
|
std::vector<MessageInfo> messages;
|
|
// IDs of messages from UNSCOPED_X macros, which we have to
|
|
// remove manually.
|
|
std::vector<unsigned int> unscoped_ids;
|
|
|
|
public:
|
|
// We do not need to special-case the unscoped messages when
|
|
// we only keep around the raw msg ids.
|
|
~MessageHolder() = default;
|
|
|
|
void addUnscopedMessage( MessageInfo&& info ) {
|
|
repairUnscopedMessageInvariant();
|
|
unscoped_ids.push_back( info.sequence );
|
|
messages.push_back( CATCH_MOVE( info ) );
|
|
}
|
|
|
|
void addUnscopedMessage(MessageBuilder&& builder) {
|
|
MessageInfo info( CATCH_MOVE( builder.m_info ) );
|
|
info.message = builder.m_stream.str();
|
|
addUnscopedMessage( CATCH_MOVE( info ) );
|
|
}
|
|
|
|
void addScopedMessage(MessageInfo&& info) {
|
|
messages.push_back( CATCH_MOVE( info ) );
|
|
}
|
|
|
|
std::vector<MessageInfo> const& getMessages() const {
|
|
return messages;
|
|
}
|
|
|
|
void removeMessage( unsigned int messageId ) {
|
|
// Note: On average, it would probably be better to look for
|
|
// the message backwards. However, we do not expect to have
|
|
// to deal with more messages than low single digits, so
|
|
// the improvement is tiny, and we would have to hand-write
|
|
// the loop to avoid terrible codegen of reverse iterators
|
|
// in debug mode.
|
|
auto iter =
|
|
std::find_if( messages.begin(),
|
|
messages.end(),
|
|
[messageId]( MessageInfo const& msg ) {
|
|
return msg.sequence == messageId;
|
|
} );
|
|
assert( iter != messages.end() &&
|
|
"Trying to remove non-existent message." );
|
|
messages.erase( iter );
|
|
}
|
|
|
|
void removeUnscopedMessages() {
|
|
for ( const auto messageId : unscoped_ids ) {
|
|
removeMessage( messageId );
|
|
}
|
|
unscoped_ids.clear();
|
|
g_clearMessageScopes = false;
|
|
}
|
|
|
|
void repairUnscopedMessageInvariant() {
|
|
if ( g_clearMessageScopes ) { removeUnscopedMessages(); }
|
|
g_clearMessageScopes = false;
|
|
}
|
|
};
|
|
|
|
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION
|
|
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS
|
|
static MessageHolder& g_messageHolder() {
|
|
static CATCH_INTERNAL_THREAD_LOCAL MessageHolder value;
|
|
return value;
|
|
}
|
|
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
|
|
|
|
} // namespace Detail
|
|
|
|
RunContext::RunContext(IConfig const* _config, IEventListenerPtr&& reporter)
|
|
: m_runInfo(_config->name()),
|
|
m_config(_config),
|
|
m_reporter(CATCH_MOVE(reporter)),
|
|
m_outputRedirect( makeOutputRedirect( m_reporter->getPreferences().shouldRedirectStdOut ) ),
|
|
m_abortAfterXFailedAssertions( m_config->abortAfter() ),
|
|
m_reportAssertionStarting( m_reporter->getPreferences().shouldReportAllAssertionStarts ),
|
|
m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ),
|
|
m_shouldDebugBreak( m_config->shouldDebugBreak() )
|
|
{
|
|
getCurrentMutableContext().setResultCapture( this );
|
|
m_reporter->testRunStarting(m_runInfo);
|
|
|
|
// TODO: HACK!
|
|
// We need to make sure the underlying cache is initialized
|
|
// while we are guaranteed to be running in a single thread,
|
|
// because the initialization is not thread-safe.
|
|
ReusableStringStream rss;
|
|
(void)rss;
|
|
}
|
|
|
|
RunContext::~RunContext() {
|
|
updateTotalsFromAtomics();
|
|
m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting()));
|
|
}
|
|
|
|
Totals RunContext::runTest(TestCaseHandle const& testCase) {
|
|
updateTotalsFromAtomics();
|
|
const Totals prevTotals = m_totals;
|
|
|
|
auto const& testInfo = testCase.getTestCaseInfo();
|
|
m_reporter->testCaseStarting(testInfo);
|
|
testCase.prepareTestCase();
|
|
m_activeTestCase = &testCase;
|
|
|
|
|
|
ITracker& rootTracker = m_trackerContext.startRun();
|
|
assert(rootTracker.isSectionTracker());
|
|
static_cast<SectionTracker&>(rootTracker).addInitialFilters(m_config->getSectionsToRun());
|
|
|
|
// We intentionally only seed the internal RNG once per test case,
|
|
// before it is first invoked. The reason for that is a complex
|
|
// interplay of generator/section implementation details and the
|
|
// Random*Generator types.
|
|
//
|
|
// The issue boils down to us needing to seed the Random*Generators
|
|
// with different seed each, so that they return different sequences
|
|
// of random numbers. We do this by giving them a number from the
|
|
// shared RNG instance as their seed.
|
|
//
|
|
// However, this runs into an issue if the reseeding happens each
|
|
// time the test case is entered (as opposed to first time only),
|
|
// because multiple generators could get the same seed, e.g. in
|
|
// ```cpp
|
|
// TEST_CASE() {
|
|
// auto i = GENERATE(take(10, random(0, 100));
|
|
// SECTION("A") {
|
|
// auto j = GENERATE(take(10, random(0, 100));
|
|
// }
|
|
// SECTION("B") {
|
|
// auto k = GENERATE(take(10, random(0, 100));
|
|
// }
|
|
// }
|
|
// ```
|
|
// `i` and `j` would properly return values from different sequences,
|
|
// but `i` and `k` would return the same sequence, because their seed
|
|
// would be the same.
|
|
// (The reason their seeds would be the same is that the generator
|
|
// for k would be initialized when the test case is entered the second
|
|
// time, after the shared RNG instance was reset to the same value
|
|
// it had when the generator for i was initialized.)
|
|
seedRng( *m_config );
|
|
|
|
uint64_t testRuns = 0;
|
|
std::string redirectedCout;
|
|
std::string redirectedCerr;
|
|
do {
|
|
m_trackerContext.startCycle();
|
|
m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocationRef(testInfo.name, testInfo.lineInfo));
|
|
|
|
m_reporter->testCasePartialStarting(testInfo, testRuns);
|
|
|
|
updateTotalsFromAtomics();
|
|
const auto beforeRunTotals = m_totals;
|
|
runCurrentTest();
|
|
std::string oneRunCout = m_outputRedirect->getStdout();
|
|
std::string oneRunCerr = m_outputRedirect->getStderr();
|
|
m_outputRedirect->clearBuffers();
|
|
redirectedCout += oneRunCout;
|
|
redirectedCerr += oneRunCerr;
|
|
|
|
updateTotalsFromAtomics();
|
|
const auto singleRunTotals = m_totals.delta(beforeRunTotals);
|
|
auto statsForOneRun = TestCaseStats(testInfo, singleRunTotals, CATCH_MOVE(oneRunCout), CATCH_MOVE(oneRunCerr), aborting());
|
|
m_reporter->testCasePartialEnded(statsForOneRun, testRuns);
|
|
|
|
++testRuns;
|
|
} while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting());
|
|
|
|
Totals deltaTotals = m_totals.delta(prevTotals);
|
|
if (testInfo.expectedToFail() && deltaTotals.testCases.passed > 0) {
|
|
deltaTotals.assertions.failed++;
|
|
deltaTotals.testCases.passed--;
|
|
deltaTotals.testCases.failed++;
|
|
}
|
|
m_totals.testCases += deltaTotals.testCases;
|
|
testCase.tearDownTestCase();
|
|
m_reporter->testCaseEnded(TestCaseStats(testInfo,
|
|
deltaTotals,
|
|
CATCH_MOVE(redirectedCout),
|
|
CATCH_MOVE(redirectedCerr),
|
|
aborting()));
|
|
|
|
m_activeTestCase = nullptr;
|
|
m_testCaseTracker = nullptr;
|
|
|
|
return deltaTotals;
|
|
}
|
|
|
|
|
|
void RunContext::assertionEnded(AssertionResult&& result) {
|
|
Detail::g_lastKnownLineInfo = result.m_info.lineInfo;
|
|
if (result.getResultType() == ResultWas::Ok) {
|
|
m_atomicAssertionCount.passed++;
|
|
Detail::g_lastAssertionPassed = true;
|
|
} else if (result.getResultType() == ResultWas::ExplicitSkip) {
|
|
m_atomicAssertionCount.skipped++;
|
|
Detail::g_lastAssertionPassed = true;
|
|
} else if (!result.succeeded()) {
|
|
Detail::g_lastAssertionPassed = false;
|
|
if (result.isOk()) {
|
|
}
|
|
else if( m_activeTestCase->getTestCaseInfo().okToFail() ) // Read from a shared state established before the threads could start, this is fine
|
|
m_atomicAssertionCount.failedButOk++;
|
|
else
|
|
m_atomicAssertionCount.failed++;
|
|
}
|
|
else {
|
|
Detail::g_lastAssertionPassed = true;
|
|
}
|
|
|
|
auto& msgHolder = Detail::g_messageHolder();
|
|
msgHolder.repairUnscopedMessageInvariant();
|
|
|
|
// From here, we are touching shared state and need mutex.
|
|
Detail::LockGuard lock( m_assertionMutex );
|
|
{
|
|
auto _ = scopedDeactivate( *m_outputRedirect );
|
|
updateTotalsFromAtomics();
|
|
m_reporter->assertionEnded( AssertionStats( result, msgHolder.getMessages(), m_totals ) );
|
|
}
|
|
|
|
if ( result.getResultType() != ResultWas::Warning ) {
|
|
msgHolder.removeUnscopedMessages();
|
|
}
|
|
|
|
// Reset working state. assertion info will be reset after
|
|
// populateReaction is run if it is needed
|
|
m_lastResult = CATCH_MOVE( result );
|
|
}
|
|
|
|
void RunContext::notifyAssertionStarted( AssertionInfo const& info ) {
|
|
if (m_reportAssertionStarting) {
|
|
Detail::LockGuard lock( m_assertionMutex );
|
|
auto _ = scopedDeactivate( *m_outputRedirect );
|
|
m_reporter->assertionStarting( info );
|
|
}
|
|
}
|
|
|
|
bool RunContext::sectionStarted( StringRef sectionName,
|
|
SourceLineInfo const& sectionLineInfo,
|
|
Counts& assertions ) {
|
|
ITracker& sectionTracker =
|
|
SectionTracker::acquire( m_trackerContext,
|
|
TestCaseTracking::NameAndLocationRef(
|
|
sectionName, sectionLineInfo ) );
|
|
|
|
if (!sectionTracker.isOpen())
|
|
return false;
|
|
m_activeSections.push_back(§ionTracker);
|
|
|
|
SectionInfo sectionInfo( sectionLineInfo, static_cast<std::string>(sectionName) );
|
|
Detail::g_lastKnownLineInfo = sectionLineInfo;
|
|
|
|
{
|
|
auto _ = scopedDeactivate( *m_outputRedirect );
|
|
m_reporter->sectionStarting( sectionInfo );
|
|
}
|
|
|
|
updateTotalsFromAtomics();
|
|
assertions = m_totals.assertions;
|
|
|
|
return true;
|
|
}
|
|
IGeneratorTracker*
|
|
RunContext::acquireGeneratorTracker( StringRef generatorName,
|
|
SourceLineInfo const& lineInfo ) {
|
|
auto* tracker = Generators::GeneratorTracker::acquire(
|
|
m_trackerContext,
|
|
TestCaseTracking::NameAndLocationRef(
|
|
generatorName, lineInfo ) );
|
|
Detail::g_lastKnownLineInfo = lineInfo;
|
|
return tracker;
|
|
}
|
|
|
|
IGeneratorTracker* RunContext::createGeneratorTracker(
|
|
StringRef generatorName,
|
|
SourceLineInfo lineInfo,
|
|
Generators::GeneratorBasePtr&& generator ) {
|
|
|
|
auto nameAndLoc = TestCaseTracking::NameAndLocation( static_cast<std::string>( generatorName ), lineInfo );
|
|
auto& currentTracker = m_trackerContext.currentTracker();
|
|
assert(
|
|
currentTracker.nameAndLocation() != nameAndLoc &&
|
|
"Trying to create tracker for a generator that already has one" );
|
|
|
|
auto newTracker = Catch::Detail::make_unique<Generators::GeneratorTracker>(
|
|
CATCH_MOVE(nameAndLoc), m_trackerContext, ¤tTracker );
|
|
auto ret = newTracker.get();
|
|
currentTracker.addChild( CATCH_MOVE( newTracker ) );
|
|
|
|
ret->setGenerator( CATCH_MOVE( generator ) );
|
|
if ( m_config->warnAboutInfiniteGenerators() &&
|
|
!ret->getGenerator()->isFinite() ) {
|
|
// TBD: Would it be better to expand this macro inline?
|
|
FAIL( "GENERATE() would run infinitely" );
|
|
}
|
|
ret->open();
|
|
return ret;
|
|
}
|
|
|
|
bool RunContext::testForMissingAssertions(Counts& assertions) {
|
|
if (assertions.total() != 0)
|
|
return false;
|
|
if (!m_config->warnAboutMissingAssertions())
|
|
return false;
|
|
if (m_trackerContext.currentTracker().hasChildren())
|
|
return false;
|
|
m_atomicAssertionCount.failed++;
|
|
assertions.failed++;
|
|
return true;
|
|
}
|
|
|
|
void RunContext::sectionEnded(SectionEndInfo&& endInfo) {
|
|
updateTotalsFromAtomics();
|
|
Counts assertions = m_totals.assertions - endInfo.prevAssertions;
|
|
bool missingAssertions = testForMissingAssertions(assertions);
|
|
|
|
if (!m_activeSections.empty()) {
|
|
m_activeSections.back()->close();
|
|
m_activeSections.pop_back();
|
|
}
|
|
|
|
{
|
|
auto _ = scopedDeactivate( *m_outputRedirect );
|
|
m_reporter->sectionEnded(
|
|
SectionStats( CATCH_MOVE( endInfo.sectionInfo ),
|
|
assertions,
|
|
endInfo.durationInSeconds,
|
|
missingAssertions ) );
|
|
}
|
|
}
|
|
|
|
void RunContext::sectionEndedEarly(SectionEndInfo&& endInfo) {
|
|
if ( m_unfinishedSections.empty() ) {
|
|
m_activeSections.back()->fail();
|
|
} else {
|
|
m_activeSections.back()->close();
|
|
}
|
|
m_activeSections.pop_back();
|
|
|
|
m_unfinishedSections.push_back(CATCH_MOVE(endInfo));
|
|
}
|
|
|
|
void RunContext::benchmarkPreparing( StringRef name ) {
|
|
auto _ = scopedDeactivate( *m_outputRedirect );
|
|
m_reporter->benchmarkPreparing( name );
|
|
}
|
|
void RunContext::benchmarkStarting( BenchmarkInfo const& info ) {
|
|
auto _ = scopedDeactivate( *m_outputRedirect );
|
|
m_reporter->benchmarkStarting( info );
|
|
}
|
|
void RunContext::benchmarkEnded( BenchmarkStats<> const& stats ) {
|
|
auto _ = scopedDeactivate( *m_outputRedirect );
|
|
m_reporter->benchmarkEnded( stats );
|
|
}
|
|
void RunContext::benchmarkFailed( StringRef error ) {
|
|
auto _ = scopedDeactivate( *m_outputRedirect );
|
|
m_reporter->benchmarkFailed( error );
|
|
}
|
|
|
|
std::string RunContext::getCurrentTestName() const {
|
|
return m_activeTestCase
|
|
? m_activeTestCase->getTestCaseInfo().name
|
|
: std::string();
|
|
}
|
|
|
|
const AssertionResult * RunContext::getLastResult() const {
|
|
// m_lastResult is updated inside the assertion slow-path, under
|
|
// a mutex, so the read needs to happen under mutex as well.
|
|
|
|
// TBD: The last result only makes sense if it is a thread-local
|
|
// thing, because the answer is different per thread, like
|
|
// last line info, whether last assertion passed, and so on.
|
|
//
|
|
// However, the last result was also never updated in the
|
|
// assertion fast path, so it was always somewhat broken,
|
|
// and since IResultCapture::getLastResult is deprecated,
|
|
// we will leave it as is, until it is finally removed.
|
|
Detail::LockGuard _( m_assertionMutex );
|
|
return &(*m_lastResult);
|
|
}
|
|
|
|
void RunContext::exceptionEarlyReported() {
|
|
m_shouldReportUnexpected = false;
|
|
}
|
|
|
|
void RunContext::handleFatalErrorCondition( StringRef message ) {
|
|
// We lock only when touching the reporters directly, to avoid
|
|
// deadlocks when we call into other functions that also want
|
|
// to lock the mutex before touching reporters.
|
|
//
|
|
// This does mean that we allow other threads to run while handling
|
|
// a fatal error, but this is all a best effort attempt anyway.
|
|
{
|
|
Detail::LockGuard lock( m_assertionMutex );
|
|
// TODO: scoped deactivate here? Just give up and do best effort?
|
|
// the deactivation can break things further, OTOH so can the
|
|
// capture
|
|
auto _ = scopedDeactivate( *m_outputRedirect );
|
|
|
|
// First notify reporter that bad things happened
|
|
m_reporter->fatalErrorEncountered( message );
|
|
}
|
|
|
|
// Don't rebuild the result -- the stringification itself can cause more fatal errors
|
|
// Instead, fake a result data.
|
|
AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } );
|
|
tempResult.message = static_cast<std::string>(message);
|
|
AssertionResult result( makeDummyAssertionInfo(),
|
|
CATCH_MOVE( tempResult ) );
|
|
|
|
assertionEnded(CATCH_MOVE(result) );
|
|
|
|
|
|
// At this point we touch sections/test cases from this thread
|
|
// to try and end them. Technically that is not supported when
|
|
// using multiple threads, but the worst thing that can happen
|
|
// is that the process aborts harder :-D
|
|
Detail::LockGuard lock( m_assertionMutex );
|
|
|
|
// Best effort cleanup for sections that have not been destructed yet
|
|
// Since this is a fatal error, we have not had and won't have the opportunity to destruct them properly
|
|
while (!m_activeSections.empty()) {
|
|
auto const& nl = m_activeSections.back()->nameAndLocation();
|
|
SectionEndInfo endInfo{ SectionInfo(nl.location, nl.name), {}, 0.0 };
|
|
sectionEndedEarly(CATCH_MOVE(endInfo));
|
|
}
|
|
handleUnfinishedSections();
|
|
|
|
// Recreate section for test case (as we will lose the one that was in scope)
|
|
auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo();
|
|
SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name);
|
|
|
|
Counts assertions;
|
|
assertions.failed = 1;
|
|
SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, 0, false);
|
|
m_reporter->sectionEnded( testCaseSectionStats );
|
|
|
|
auto const& testInfo = m_activeTestCase->getTestCaseInfo();
|
|
|
|
Totals deltaTotals;
|
|
deltaTotals.testCases.failed = 1;
|
|
deltaTotals.assertions.failed = 1;
|
|
m_reporter->testCaseEnded(TestCaseStats(testInfo,
|
|
deltaTotals,
|
|
std::string(),
|
|
std::string(),
|
|
false));
|
|
m_totals.testCases.failed++;
|
|
updateTotalsFromAtomics();
|
|
m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false));
|
|
}
|
|
|
|
bool RunContext::lastAssertionPassed() {
|
|
return Detail::g_lastAssertionPassed;
|
|
}
|
|
|
|
void RunContext::assertionPassedFastPath(SourceLineInfo lineInfo) {
|
|
// We want to save the line info for better experience with unexpected assertions
|
|
Detail::g_lastKnownLineInfo = lineInfo;
|
|
++m_atomicAssertionCount.passed;
|
|
Detail::g_lastAssertionPassed = true;
|
|
Detail::g_clearMessageScopes = true;
|
|
}
|
|
|
|
void RunContext::updateTotalsFromAtomics() {
|
|
m_totals.assertions = Counts{
|
|
m_atomicAssertionCount.passed,
|
|
m_atomicAssertionCount.failed,
|
|
m_atomicAssertionCount.failedButOk,
|
|
m_atomicAssertionCount.skipped,
|
|
};
|
|
}
|
|
|
|
bool RunContext::aborting() const {
|
|
return m_atomicAssertionCount.failed >= m_abortAfterXFailedAssertions;
|
|
}
|
|
|
|
void RunContext::runCurrentTest() {
|
|
auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo();
|
|
SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name);
|
|
m_reporter->sectionStarting(testCaseSection);
|
|
updateTotalsFromAtomics();
|
|
Counts prevAssertions = m_totals.assertions;
|
|
double duration = 0;
|
|
m_shouldReportUnexpected = true;
|
|
Detail::g_lastKnownLineInfo = testCaseInfo.lineInfo;
|
|
|
|
Timer timer;
|
|
CATCH_TRY {
|
|
{
|
|
auto _ = scopedActivate( *m_outputRedirect );
|
|
timer.start();
|
|
invokeActiveTestCase();
|
|
}
|
|
duration = timer.getElapsedSeconds();
|
|
} CATCH_CATCH_ANON (TestFailureException&) {
|
|
// This just means the test was aborted due to failure
|
|
} CATCH_CATCH_ANON (TestSkipException&) {
|
|
// This just means the test was explicitly skipped
|
|
} CATCH_CATCH_ALL {
|
|
// Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions
|
|
// are reported without translation at the point of origin.
|
|
if ( m_shouldReportUnexpected ) {
|
|
AssertionReaction dummyReaction;
|
|
handleUnexpectedInflightException( makeDummyAssertionInfo(),
|
|
translateActiveException(),
|
|
dummyReaction );
|
|
}
|
|
}
|
|
updateTotalsFromAtomics();
|
|
Counts assertions = m_totals.assertions - prevAssertions;
|
|
bool missingAssertions = testForMissingAssertions(assertions);
|
|
|
|
m_testCaseTracker->close();
|
|
handleUnfinishedSections();
|
|
auto& msgHolder = Detail::g_messageHolder();
|
|
msgHolder.removeUnscopedMessages();
|
|
assert( msgHolder.getMessages().empty() &&
|
|
"There should be no leftover messages after the test ends" );
|
|
|
|
SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, duration, missingAssertions);
|
|
m_reporter->sectionEnded(testCaseSectionStats);
|
|
}
|
|
|
|
void RunContext::invokeActiveTestCase() {
|
|
// We need to engage a handler for signals/structured exceptions
|
|
// before running the tests themselves, or the binary can crash
|
|
// without failed test being reported.
|
|
FatalConditionHandlerGuard _(&m_fatalConditionhandler);
|
|
// We keep having issue where some compilers warn about an unused
|
|
// variable, even though the type has non-trivial constructor and
|
|
// destructor. This is annoying and ugly, but it makes them stfu.
|
|
(void)_;
|
|
|
|
m_activeTestCase->invoke();
|
|
}
|
|
|
|
void RunContext::handleUnfinishedSections() {
|
|
// If sections ended prematurely due to an exception we stored their
|
|
// infos here so we can tear them down outside the unwind process.
|
|
for ( auto it = m_unfinishedSections.rbegin(),
|
|
itEnd = m_unfinishedSections.rend();
|
|
it != itEnd;
|
|
++it ) {
|
|
sectionEnded( CATCH_MOVE( *it ) );
|
|
}
|
|
m_unfinishedSections.clear();
|
|
}
|
|
|
|
void RunContext::handleExpr(
|
|
AssertionInfo const& info,
|
|
ITransientExpression const& expr,
|
|
AssertionReaction& reaction
|
|
) {
|
|
bool negated = isFalseTest( info.resultDisposition );
|
|
bool result = expr.getResult() != negated;
|
|
|
|
if( result ) {
|
|
if (!m_includeSuccessfulResults) {
|
|
assertionPassedFastPath(info.lineInfo);
|
|
}
|
|
else {
|
|
reportExpr(info, ResultWas::Ok, &expr, negated);
|
|
}
|
|
}
|
|
else {
|
|
reportExpr(info, ResultWas::ExpressionFailed, &expr, negated );
|
|
populateReaction(
|
|
reaction, info.resultDisposition & ResultDisposition::Normal );
|
|
}
|
|
}
|
|
void RunContext::reportExpr(
|
|
AssertionInfo const &info,
|
|
ResultWas::OfType resultType,
|
|
ITransientExpression const *expr,
|
|
bool negated ) {
|
|
|
|
Detail::g_lastKnownLineInfo = info.lineInfo;
|
|
AssertionResultData data( resultType, LazyExpression( negated ) );
|
|
|
|
AssertionResult assertionResult{ info, CATCH_MOVE( data ) };
|
|
assertionResult.m_resultData.lazyExpression.m_transientExpression = expr;
|
|
|
|
assertionEnded( CATCH_MOVE(assertionResult) );
|
|
}
|
|
|
|
void RunContext::handleMessage(
|
|
AssertionInfo const& info,
|
|
ResultWas::OfType resultType,
|
|
std::string&& message,
|
|
AssertionReaction& reaction
|
|
) {
|
|
Detail::g_lastKnownLineInfo = info.lineInfo;
|
|
|
|
AssertionResultData data( resultType, LazyExpression( false ) );
|
|
data.message = CATCH_MOVE( message );
|
|
AssertionResult assertionResult{ info,
|
|
CATCH_MOVE( data ) };
|
|
|
|
const auto isOk = assertionResult.isOk();
|
|
assertionEnded( CATCH_MOVE(assertionResult) );
|
|
if ( !isOk ) {
|
|
populateReaction(
|
|
reaction, info.resultDisposition & ResultDisposition::Normal );
|
|
} else if ( resultType == ResultWas::ExplicitSkip ) {
|
|
// TODO: Need to handle this explicitly, as ExplicitSkip is
|
|
// considered "OK"
|
|
reaction.shouldSkip = true;
|
|
}
|
|
}
|
|
|
|
void RunContext::handleUnexpectedExceptionNotThrown(
|
|
AssertionInfo const& info,
|
|
AssertionReaction& reaction
|
|
) {
|
|
handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction);
|
|
}
|
|
|
|
void RunContext::handleUnexpectedInflightException(
|
|
AssertionInfo const& info,
|
|
std::string&& message,
|
|
AssertionReaction& reaction
|
|
) {
|
|
Detail::g_lastKnownLineInfo = info.lineInfo;
|
|
|
|
AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) );
|
|
data.message = CATCH_MOVE(message);
|
|
AssertionResult assertionResult{ info, CATCH_MOVE(data) };
|
|
assertionEnded( CATCH_MOVE(assertionResult) );
|
|
populateReaction( reaction,
|
|
info.resultDisposition & ResultDisposition::Normal );
|
|
}
|
|
|
|
void RunContext::populateReaction( AssertionReaction& reaction,
|
|
bool has_normal_disposition ) {
|
|
reaction.shouldDebugBreak = m_shouldDebugBreak;
|
|
reaction.shouldThrow = aborting() || has_normal_disposition;
|
|
}
|
|
|
|
AssertionInfo RunContext::makeDummyAssertionInfo() {
|
|
const bool testCaseJustStarted =
|
|
Detail::g_lastKnownLineInfo ==
|
|
m_activeTestCase->getTestCaseInfo().lineInfo;
|
|
|
|
return AssertionInfo{
|
|
testCaseJustStarted ? "TEST_CASE"_sr : StringRef(),
|
|
Detail::g_lastKnownLineInfo,
|
|
testCaseJustStarted ? StringRef() : "{Unknown expression after the reported line}"_sr,
|
|
ResultDisposition::Normal
|
|
};
|
|
}
|
|
|
|
void RunContext::handleIncomplete(
|
|
AssertionInfo const& info
|
|
) {
|
|
using namespace std::string_literals;
|
|
Detail::g_lastKnownLineInfo = info.lineInfo;
|
|
|
|
AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) );
|
|
data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"s;
|
|
AssertionResult assertionResult{ info, CATCH_MOVE( data ) };
|
|
assertionEnded( CATCH_MOVE(assertionResult) );
|
|
}
|
|
|
|
void RunContext::handleNonExpr(
|
|
AssertionInfo const &info,
|
|
ResultWas::OfType resultType,
|
|
AssertionReaction &reaction
|
|
) {
|
|
AssertionResultData data( resultType, LazyExpression( false ) );
|
|
AssertionResult assertionResult{ info, CATCH_MOVE( data ) };
|
|
|
|
const auto isOk = assertionResult.isOk();
|
|
if ( isOk && !m_includeSuccessfulResults ) {
|
|
assertionPassedFastPath( info.lineInfo );
|
|
return;
|
|
}
|
|
|
|
assertionEnded( CATCH_MOVE(assertionResult) );
|
|
if ( !isOk ) {
|
|
populateReaction(
|
|
reaction, info.resultDisposition & ResultDisposition::Normal );
|
|
}
|
|
}
|
|
|
|
void IResultCapture::pushScopedMessage( MessageInfo&& message ) {
|
|
Detail::g_messageHolder().addScopedMessage( CATCH_MOVE( message ) );
|
|
}
|
|
|
|
void IResultCapture::popScopedMessage( unsigned int messageId ) {
|
|
Detail::g_messageHolder().removeMessage( messageId );
|
|
}
|
|
|
|
void IResultCapture::emplaceUnscopedMessage( MessageBuilder&& builder ) {
|
|
Detail::g_messageHolder().addUnscopedMessage( CATCH_MOVE( builder ) );
|
|
}
|
|
|
|
void IResultCapture::addUnscopedMessage( MessageInfo&& message ) {
|
|
Detail::g_messageHolder().addUnscopedMessage( CATCH_MOVE( message ) );
|
|
}
|
|
|
|
void seedRng(IConfig const& config) {
|
|
sharedRng().seed(config.rngSeed());
|
|
}
|
|
|
|
unsigned int rngSeed() {
|
|
return getCurrentContext().getConfig()->rngSeed();
|
|
}
|
|
|
|
}
|