mirror of https://github.com/procxx/kepka.git
				
				
				
			
		
			
				
	
	
		
			466 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			466 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
#include "catch_run_context.h"
 | 
						|
#include "catch_context.h"
 | 
						|
#include "catch_enforce.h"
 | 
						|
#include "catch_random_number_generator.h"
 | 
						|
#include "catch_stream.h"
 | 
						|
 | 
						|
#include <cassert>
 | 
						|
#include <algorithm>
 | 
						|
#include <sstream>
 | 
						|
 | 
						|
namespace Catch {
 | 
						|
 | 
						|
    class RedirectedStream {
 | 
						|
        std::ostream& m_originalStream;
 | 
						|
        std::ostream& m_redirectionStream;
 | 
						|
        std::streambuf* m_prevBuf;
 | 
						|
 | 
						|
    public:
 | 
						|
        RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream )
 | 
						|
        :   m_originalStream( originalStream ),
 | 
						|
            m_redirectionStream( redirectionStream ),
 | 
						|
            m_prevBuf( m_originalStream.rdbuf() )
 | 
						|
        {
 | 
						|
            m_originalStream.rdbuf( m_redirectionStream.rdbuf() );
 | 
						|
        }
 | 
						|
        ~RedirectedStream() {
 | 
						|
            m_originalStream.rdbuf( m_prevBuf );
 | 
						|
        }
 | 
						|
    };
 | 
						|
 | 
						|
    class RedirectedStdOut {
 | 
						|
        ReusableStringStream m_rss;
 | 
						|
        RedirectedStream m_cout;
 | 
						|
    public:
 | 
						|
        RedirectedStdOut() : m_cout( Catch::cout(), m_rss.get() ) {}
 | 
						|
        auto str() const -> std::string { return m_rss.str(); }
 | 
						|
    };
 | 
						|
 | 
						|
    // StdErr has two constituent streams in C++, std::cerr and std::clog
 | 
						|
    // This means that we need to redirect 2 streams into 1 to keep proper
 | 
						|
    // order of writes
 | 
						|
    class RedirectedStdErr {
 | 
						|
        ReusableStringStream m_rss;
 | 
						|
        RedirectedStream m_cerr;
 | 
						|
        RedirectedStream m_clog;
 | 
						|
    public:
 | 
						|
        RedirectedStdErr()
 | 
						|
        :   m_cerr( Catch::cerr(), m_rss.get() ),
 | 
						|
            m_clog( Catch::clog(), m_rss.get() )
 | 
						|
        {}
 | 
						|
        auto str() const -> std::string { return m_rss.str(); }
 | 
						|
    };
 | 
						|
 | 
						|
 | 
						|
    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_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)));
 | 
						|
 | 
						|
        // 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;
 | 
						|
    }
 | 
						|
 | 
						|
    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();
 | 
						|
    }
 | 
						|
 | 
						|
    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);
 | 
						|
    }
 | 
						|
    void RunContext::benchmarkStarting( BenchmarkInfo const& info ) {
 | 
						|
        m_reporter->benchmarkStarting( info );
 | 
						|
    }
 | 
						|
    void RunContext::benchmarkEnded( BenchmarkStats const& stats ) {
 | 
						|
        m_reporter->benchmarkEnded( stats );
 | 
						|
    }
 | 
						|
 | 
						|
    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());
 | 
						|
    }
 | 
						|
 | 
						|
    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 = 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, testCaseInfo.description);
 | 
						|
 | 
						|
        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();
 | 
						|
    }
 | 
						|
 | 
						|
    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, testCaseInfo.description);
 | 
						|
        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;
 | 
						|
        try {
 | 
						|
            if (m_reporter->getPreferences().shouldRedirectStdOut) {
 | 
						|
                RedirectedStdOut redirectedStdOut;
 | 
						|
                RedirectedStdErr redirectedStdErr;
 | 
						|
                timer.start();
 | 
						|
                invokeActiveTestCase();
 | 
						|
                redirectedCout += redirectedStdOut.str();
 | 
						|
                redirectedCerr += redirectedStdErr.str();
 | 
						|
 | 
						|
            } else {
 | 
						|
                timer.start();
 | 
						|
                invokeActiveTestCase();
 | 
						|
            }
 | 
						|
            duration = timer.getElapsedSeconds();
 | 
						|
        } catch (TestFailureException&) {
 | 
						|
            // This just means the test was aborted due to failure
 | 
						|
        } catch (...) {
 | 
						|
            // 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();
 | 
						|
 | 
						|
        SectionStats testCaseSectionStats(testCaseSection, assertions, duration, missingAssertions);
 | 
						|
        m_reporter->sectionEnded(testCaseSectionStats);
 | 
						|
    }
 | 
						|
 | 
						|
    void RunContext::invokeActiveTestCase() {
 | 
						|
        FatalConditionHandler fatalConditionHandler; // Handle signals
 | 
						|
        m_activeTestCase->invoke();
 | 
						|
        fatalConditionHandler.reset();
 | 
						|
    }
 | 
						|
 | 
						|
    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 = 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");
 | 
						|
    }
 | 
						|
}
 |