123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595 |
- #include "catch_run_context.h"
- #include "catch_compiler_capabilities.h"
- #include "catch_context.h"
- #include "catch_enforce.h"
- #include "catch_random_number_generator.h"
- #include "catch_stream.h"
- #include "catch_output_redirect.h"
- #include <cassert>
- #include <algorithm>
- #include <sstream>
- namespace Catch {
- namespace Generators {
- struct GeneratorTracker : TestCaseTracking::TrackerBase, IGeneratorTracker {
- GeneratorBasePtr m_generator;
- GeneratorTracker( TestCaseTracking::NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent )
- : TrackerBase( nameAndLocation, ctx, parent )
- {}
- ~GeneratorTracker();
- static GeneratorTracker& acquire( TrackerContext& ctx, TestCaseTracking::NameAndLocation const& nameAndLocation ) {
- std::shared_ptr<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 = std::static_pointer_cast<GeneratorTracker>(thisTracker);
- } else if ( TestCaseTracking::ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) {
- assert( childTracker );
- assert( childTracker->isGeneratorTracker() );
- tracker = std::static_pointer_cast<GeneratorTracker>( childTracker );
- } else {
- tracker = std::make_shared<GeneratorTracker>( nameAndLocation, ctx, ¤tTracker );
- currentTracker.addChild( tracker );
- }
- 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 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
- auto* 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&>( *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&>( *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.
- if ( should_wait_for_child ||
- ( m_runState == CompletedSuccessfully &&
- m_generator->next() ) ) {
- m_children.clear();
- m_runState = Executing;
- }
- }
- // IGeneratorTracker interface
- auto getGenerator() const -> GeneratorBasePtr const& override {
- return m_generator;
- }
- void setGenerator( GeneratorBasePtr&& generator ) override {
- m_generator = std::move( generator );
- }
- };
- GeneratorTracker::~GeneratorTracker() {}
- }
- RunContext::RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter)
- : m_runInfo(_config->name()),
- m_context(getCurrentMutableContext()),
- m_config(_config),
- m_reporter(std::move(reporter)),
- m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal },
- m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions )
- {
- m_context.setRunner(this);
- m_context.setConfig(m_config);
- m_context.setResultCapture(this);
- m_reporter->testRunStarting(m_runInfo);
- }
- RunContext::~RunContext() {
- m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting()));
- }
- void RunContext::testGroupStarting(std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount) {
- m_reporter->testGroupStarting(GroupInfo(testSpec, groupIndex, groupsCount));
- }
- void RunContext::testGroupEnded(std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount) {
- m_reporter->testGroupEnded(TestGroupStats(GroupInfo(testSpec, groupIndex, groupsCount), totals, aborting()));
- }
- Totals RunContext::runTest(TestCase const& testCase) {
- 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());
- do {
- m_trackerContext.startCycle();
- m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(testInfo.name, testInfo.lineInfo));
- runCurrentTest(redirectedCout, redirectedCerr);
- } 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;
- }
- IConfigPtr RunContext::config() const {
- return m_config;
- }
- IStreamingReporter& RunContext::reporter() const {
- return *m_reporter;
- }
- void RunContext::assertionEnded(AssertionResult const & result) {
- if (result.getResultType() == ResultWas::Ok) {
- m_totals.assertions.passed++;
- m_lastAssertionPassed = true;
- } else if (!result.isOk()) {
- m_lastAssertionPassed = false;
- if( m_activeTestCase->getTestCaseInfo().okToFail() )
- m_totals.assertions.failedButOk++;
- else
- m_totals.assertions.failed++;
- }
- else {
- m_lastAssertionPassed = true;
- }
- // We have no use for the return value (whether messages should be cleared), because messages were made scoped
- // and should be let to clear themselves out.
- static_cast<void>(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::NameAndLocation(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;
- }
- auto RunContext::acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& {
- using namespace Generators;
- GeneratorTracker& tracker = GeneratorTracker::acquire(m_trackerContext,
- TestCaseTracking::NameAndLocation( static_cast<std::string>(generatorName), lineInfo ) );
- m_lastAssertionInfo.lineInfo = lineInfo;
- return tracker;
- }
- 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 const & 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(endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions));
- m_messages.clear();
- m_messageScopes.clear();
- }
- void RunContext::sectionEndedEarly(SectionEndInfo const & endInfo) {
- if (m_unfinishedSections.empty())
- m_activeSections.back()->fail();
- else
- m_activeSections.back()->close();
- m_activeSections.pop_back();
- m_unfinishedSections.push_back(endInfo);
- }
- #if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)
- void RunContext::benchmarkPreparing(std::string const& 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(std::string const & error) {
- m_reporter->benchmarkFailed(error);
- }
- #endif // CATCH_CONFIG_ENABLE_BENCHMARKING
- 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 const& builder ) {
- m_messageScopes.emplace_back( 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, 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(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++;
- testGroupEnded(std::string(), m_totals, 1, 1);
- 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 };
- seedRng(*m_config);
- 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_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(testCaseSection, assertions, duration, missingAssertions);
- m_reporter->sectionEnded(testCaseSectionStats);
- }
- void RunContext::invokeActiveTestCase() {
- FatalConditionHandlerGuard _(&m_fatalConditionhandler);
- 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(*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, data };
- assertionResult.m_resultData.lazyExpression.m_transientExpression = expr;
- assertionEnded( assertionResult );
- }
- void RunContext::handleMessage(
- AssertionInfo const& info,
- ResultWas::OfType resultType,
- StringRef const& 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, data };
- assertionEnded( assertionResult );
- if( !assertionResult.isOk() )
- populateReaction( reaction );
- }
- 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, 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, 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, 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) {
- if (config.rngSeed() != 0) {
- std::srand(config.rngSeed());
- rng().seed(config.rngSeed());
- }
- }
- unsigned int rngSeed() {
- return getCurrentContext().getConfig()->rngSeed();
- }
- }
|