mirror of
https://github.com/catchorg/Catch2
synced 2026-02-24 04:32:12 +00:00
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`.
680 lines
26 KiB
C++
680 lines
26 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_generatortracker.hpp>
|
|
#include <catch2/interfaces/catch_interfaces_config.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 <cassert>
|
|
#include <algorithm>
|
|
|
|
namespace Catch {
|
|
|
|
namespace Generators {
|
|
struct GeneratorTracker : TestCaseTracking::TrackerBase, IGeneratorTracker {
|
|
GeneratorBasePtr m_generator;
|
|
|
|
GeneratorTracker( TestCaseTracking::NameAndLocation&& nameAndLocation, TrackerContext& ctx, ITracker* parent )
|
|
: TrackerBase( CATCH_MOVE(nameAndLocation), ctx, parent )
|
|
{}
|
|
~GeneratorTracker() override;
|
|
|
|
static GeneratorTracker* acquire( TrackerContext& ctx, TestCaseTracking::NameAndLocationRef 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() &&
|
|
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;
|
|
}
|
|
}
|
|
|
|
// IGeneratorTracker interface
|
|
auto getGenerator() const -> GeneratorBasePtr const& override {
|
|
return m_generator;
|
|
}
|
|
void setGenerator( GeneratorBasePtr&& generator ) override {
|
|
m_generator = CATCH_MOVE( generator );
|
|
}
|
|
};
|
|
GeneratorTracker::~GeneratorTracker() = default;
|
|
}
|
|
|
|
RunContext::RunContext(IConfig const* _config, IEventListenerPtr&& reporter)
|
|
: m_runInfo(_config->name()),
|
|
m_context(getCurrentMutableContext()),
|
|
m_config(_config),
|
|
m_reporter(CATCH_MOVE(reporter)),
|
|
m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal },
|
|
m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions )
|
|
{
|
|
m_context.setResultCapture(this);
|
|
m_reporter->testRunStarting(m_runInfo);
|
|
}
|
|
|
|
RunContext::~RunContext() {
|
|
m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting()));
|
|
}
|
|
|
|
Totals RunContext::runTest(TestCaseHandle const& testCase) {
|
|
const Totals prevTotals = m_totals;
|
|
|
|
std::string redirectedCout;
|
|
std::string redirectedCerr;
|
|
|
|
auto const& testInfo = testCase.getTestCaseInfo();
|
|
|
|
m_reporter->testCaseStarting(testInfo);
|
|
|
|
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;
|
|
do {
|
|
m_trackerContext.startCycle();
|
|
m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocationRef(testInfo.name, testInfo.lineInfo));
|
|
|
|
m_reporter->testCasePartialStarting(testInfo, testRuns);
|
|
|
|
const auto beforeRunTotals = m_totals;
|
|
std::string oneRunCout, oneRunCerr;
|
|
runCurrentTest(oneRunCout, oneRunCerr);
|
|
redirectedCout += oneRunCout;
|
|
redirectedCerr += oneRunCerr;
|
|
|
|
const auto singleRunTotals = m_totals.delta(beforeRunTotals);
|
|
auto statsForOneRun = TestCaseStats(testInfo, singleRunTotals, oneRunCout, 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;
|
|
m_reporter->testCaseEnded(TestCaseStats(testInfo,
|
|
deltaTotals,
|
|
redirectedCout,
|
|
redirectedCerr,
|
|
aborting()));
|
|
|
|
m_activeTestCase = nullptr;
|
|
m_testCaseTracker = nullptr;
|
|
|
|
return deltaTotals;
|
|
}
|
|
|
|
|
|
void RunContext::assertionEnded(AssertionResult const & result) {
|
|
if (result.getResultType() == ResultWas::Ok) {
|
|
m_totals.assertions.passed++;
|
|
m_lastAssertionPassed = true;
|
|
} else if (result.getResultType() == ResultWas::ExplicitSkip) {
|
|
m_totals.assertions.skipped++;
|
|
m_lastAssertionPassed = true;
|
|
} else if (!result.succeeded()) {
|
|
m_lastAssertionPassed = false;
|
|
if (result.isOk()) {
|
|
}
|
|
else if( m_activeTestCase->getTestCaseInfo().okToFail() )
|
|
m_totals.assertions.failedButOk++;
|
|
else
|
|
m_totals.assertions.failed++;
|
|
}
|
|
else {
|
|
m_lastAssertionPassed = true;
|
|
}
|
|
|
|
m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals));
|
|
|
|
if (result.getResultType() != ResultWas::Warning)
|
|
m_messageScopes.clear();
|
|
|
|
// Reset working state
|
|
resetAssertionInfo();
|
|
m_lastResult = result;
|
|
}
|
|
void RunContext::resetAssertionInfo() {
|
|
m_lastAssertionInfo.macroName = StringRef();
|
|
m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr;
|
|
}
|
|
|
|
bool RunContext::sectionStarted(SectionInfo const & sectionInfo, Counts & assertions) {
|
|
ITracker& sectionTracker = SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocationRef(sectionInfo.name, sectionInfo.lineInfo));
|
|
|
|
if (!sectionTracker.isOpen())
|
|
return false;
|
|
m_activeSections.push_back(§ionTracker);
|
|
|
|
m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo;
|
|
|
|
m_reporter->sectionStarting(sectionInfo);
|
|
|
|
assertions = m_totals.assertions;
|
|
|
|
return true;
|
|
}
|
|
IGeneratorTracker*
|
|
RunContext::acquireGeneratorTracker( StringRef generatorName,
|
|
SourceLineInfo const& lineInfo ) {
|
|
using namespace Generators;
|
|
GeneratorTracker* tracker = GeneratorTracker::acquire(
|
|
m_trackerContext,
|
|
TestCaseTracking::NameAndLocationRef(
|
|
generatorName, lineInfo ) );
|
|
m_lastAssertionInfo.lineInfo = 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 genreator 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 ) );
|
|
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_totals.assertions.failed++;
|
|
assertions.failed++;
|
|
return true;
|
|
}
|
|
|
|
void RunContext::sectionEnded(SectionEndInfo&& endInfo) {
|
|
Counts assertions = m_totals.assertions - endInfo.prevAssertions;
|
|
bool missingAssertions = testForMissingAssertions(assertions);
|
|
|
|
if (!m_activeSections.empty()) {
|
|
m_activeSections.back()->close();
|
|
m_activeSections.pop_back();
|
|
}
|
|
|
|
m_reporter->sectionEnded(SectionStats(CATCH_MOVE(endInfo.sectionInfo), assertions, endInfo.durationInSeconds, missingAssertions));
|
|
m_messages.clear();
|
|
m_messageScopes.clear();
|
|
}
|
|
|
|
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 ) {
|
|
m_reporter->benchmarkPreparing(name);
|
|
}
|
|
void RunContext::benchmarkStarting( BenchmarkInfo const& info ) {
|
|
m_reporter->benchmarkStarting( info );
|
|
}
|
|
void RunContext::benchmarkEnded( BenchmarkStats<> const& stats ) {
|
|
m_reporter->benchmarkEnded( stats );
|
|
}
|
|
void RunContext::benchmarkFailed( StringRef error ) {
|
|
m_reporter->benchmarkFailed( error );
|
|
}
|
|
|
|
void RunContext::pushScopedMessage(MessageInfo const & message) {
|
|
m_messages.push_back(message);
|
|
}
|
|
|
|
void RunContext::popScopedMessage(MessageInfo const & message) {
|
|
m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end());
|
|
}
|
|
|
|
void RunContext::emplaceUnscopedMessage( MessageBuilder&& builder ) {
|
|
m_messageScopes.emplace_back( CATCH_MOVE(builder) );
|
|
}
|
|
|
|
std::string RunContext::getCurrentTestName() const {
|
|
return m_activeTestCase
|
|
? m_activeTestCase->getTestCaseInfo().name
|
|
: std::string();
|
|
}
|
|
|
|
const AssertionResult * RunContext::getLastResult() const {
|
|
return &(*m_lastResult);
|
|
}
|
|
|
|
void RunContext::exceptionEarlyReported() {
|
|
m_shouldReportUnexpected = false;
|
|
}
|
|
|
|
void RunContext::handleFatalErrorCondition( StringRef message ) {
|
|
// 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(m_lastAssertionInfo, CATCH_MOVE(tempResult));
|
|
|
|
assertionEnded(result);
|
|
|
|
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++;
|
|
m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false));
|
|
}
|
|
|
|
bool RunContext::lastAssertionPassed() {
|
|
return m_lastAssertionPassed;
|
|
}
|
|
|
|
void RunContext::assertionPassed() {
|
|
m_lastAssertionPassed = true;
|
|
++m_totals.assertions.passed;
|
|
resetAssertionInfo();
|
|
m_messageScopes.clear();
|
|
}
|
|
|
|
bool RunContext::aborting() const {
|
|
return m_totals.assertions.failed >= static_cast<std::size_t>(m_config->abortAfter());
|
|
}
|
|
|
|
void RunContext::runCurrentTest(std::string & redirectedCout, std::string & redirectedCerr) {
|
|
auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo();
|
|
SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name);
|
|
m_reporter->sectionStarting(testCaseSection);
|
|
Counts prevAssertions = m_totals.assertions;
|
|
double duration = 0;
|
|
m_shouldReportUnexpected = true;
|
|
m_lastAssertionInfo = { "TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal };
|
|
|
|
Timer timer;
|
|
CATCH_TRY {
|
|
if (m_reporter->getPreferences().shouldRedirectStdOut) {
|
|
#if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT)
|
|
RedirectedStreams redirectedStreams(redirectedCout, redirectedCerr);
|
|
|
|
timer.start();
|
|
invokeActiveTestCase();
|
|
#else
|
|
OutputRedirect r(redirectedCout, redirectedCerr);
|
|
timer.start();
|
|
invokeActiveTestCase();
|
|
#endif
|
|
} else {
|
|
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( m_lastAssertionInfo, translateActiveException(), dummyReaction );
|
|
}
|
|
}
|
|
Counts assertions = m_totals.assertions - prevAssertions;
|
|
bool missingAssertions = testForMissingAssertions(assertions);
|
|
|
|
m_testCaseTracker->close();
|
|
handleUnfinishedSections();
|
|
m_messages.clear();
|
|
m_messageScopes.clear();
|
|
|
|
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
|
|
) {
|
|
m_reporter->assertionStarting( info );
|
|
|
|
bool negated = isFalseTest( info.resultDisposition );
|
|
bool result = expr.getResult() != negated;
|
|
|
|
if( result ) {
|
|
if (!m_includeSuccessfulResults) {
|
|
assertionPassed();
|
|
}
|
|
else {
|
|
reportExpr(info, ResultWas::Ok, &expr, negated);
|
|
}
|
|
}
|
|
else {
|
|
reportExpr(info, ResultWas::ExpressionFailed, &expr, negated );
|
|
populateReaction( reaction );
|
|
}
|
|
}
|
|
void RunContext::reportExpr(
|
|
AssertionInfo const &info,
|
|
ResultWas::OfType resultType,
|
|
ITransientExpression const *expr,
|
|
bool negated ) {
|
|
|
|
m_lastAssertionInfo = info;
|
|
AssertionResultData data( resultType, LazyExpression( negated ) );
|
|
|
|
AssertionResult assertionResult{ info, CATCH_MOVE( data ) };
|
|
assertionResult.m_resultData.lazyExpression.m_transientExpression = expr;
|
|
|
|
assertionEnded( assertionResult );
|
|
}
|
|
|
|
void RunContext::handleMessage(
|
|
AssertionInfo const& info,
|
|
ResultWas::OfType resultType,
|
|
StringRef message,
|
|
AssertionReaction& reaction
|
|
) {
|
|
m_reporter->assertionStarting( info );
|
|
|
|
m_lastAssertionInfo = info;
|
|
|
|
AssertionResultData data( resultType, LazyExpression( false ) );
|
|
data.message = static_cast<std::string>(message);
|
|
AssertionResult assertionResult{ m_lastAssertionInfo,
|
|
CATCH_MOVE( data ) };
|
|
assertionEnded( assertionResult );
|
|
if ( !assertionResult.isOk() ) {
|
|
populateReaction( reaction );
|
|
} 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 const& message,
|
|
AssertionReaction& reaction
|
|
) {
|
|
m_lastAssertionInfo = info;
|
|
|
|
AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) );
|
|
data.message = message;
|
|
AssertionResult assertionResult{ info, CATCH_MOVE(data) };
|
|
assertionEnded( assertionResult );
|
|
populateReaction( reaction );
|
|
}
|
|
|
|
void RunContext::populateReaction( AssertionReaction& reaction ) {
|
|
reaction.shouldDebugBreak = m_config->shouldDebugBreak();
|
|
reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal);
|
|
}
|
|
|
|
void RunContext::handleIncomplete(
|
|
AssertionInfo const& info
|
|
) {
|
|
m_lastAssertionInfo = info;
|
|
|
|
AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) );
|
|
data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE";
|
|
AssertionResult assertionResult{ info, CATCH_MOVE( data ) };
|
|
assertionEnded( assertionResult );
|
|
}
|
|
void RunContext::handleNonExpr(
|
|
AssertionInfo const &info,
|
|
ResultWas::OfType resultType,
|
|
AssertionReaction &reaction
|
|
) {
|
|
m_lastAssertionInfo = info;
|
|
|
|
AssertionResultData data( resultType, LazyExpression( false ) );
|
|
AssertionResult assertionResult{ info, CATCH_MOVE( data ) };
|
|
assertionEnded( assertionResult );
|
|
|
|
if( !assertionResult.isOk() )
|
|
populateReaction( reaction );
|
|
}
|
|
|
|
|
|
IResultCapture& getResultCapture() {
|
|
if (auto* capture = getCurrentContext().getResultCapture())
|
|
return *capture;
|
|
else
|
|
CATCH_INTERNAL_ERROR("No result capture instance");
|
|
}
|
|
|
|
void seedRng(IConfig const& config) {
|
|
sharedRng().seed(config.rngSeed());
|
|
}
|
|
|
|
unsigned int rngSeed() {
|
|
return getCurrentContext().getConfig()->rngSeed();
|
|
}
|
|
|
|
}
|