mirror of
https://github.com/catchorg/Catch2.git
synced 2024-11-14 01:39:54 +01:00
56e1075613
ReusableStringStream holds a std::ostringstream internally, but only exposes the ostream interface. It caches a pool of ostringstreams in a vector which is currently global, but will be made thread-local. Altogether this should enable both runtime and compile-time benefits. although more work is needed to realise the compile time opportunities.
337 lines
12 KiB
C++
337 lines
12 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 {
|
|
|
|
StreamRedirect::StreamRedirect(std::ostream& stream, std::string& targetString)
|
|
: m_stream(stream),
|
|
m_prevBuf(stream.rdbuf()),
|
|
m_targetString(targetString) {
|
|
stream.rdbuf(m_oss.get().rdbuf());
|
|
}
|
|
|
|
StreamRedirect::~StreamRedirect() {
|
|
m_targetString += m_oss.str();
|
|
m_stream.rdbuf(m_prevBuf);
|
|
}
|
|
|
|
StdErrRedirect::StdErrRedirect(std::string & targetString)
|
|
:m_cerrBuf(cerr().rdbuf()), m_clogBuf(clog().rdbuf()),
|
|
m_targetString(targetString) {
|
|
cerr().rdbuf(m_oss.get().rdbuf());
|
|
clog().rdbuf(m_oss.get().rdbuf());
|
|
}
|
|
|
|
StdErrRedirect::~StdErrRedirect() {
|
|
m_targetString += m_oss.str();
|
|
cerr().rdbuf(m_cerrBuf);
|
|
clog().rdbuf(m_clogBuf);
|
|
}
|
|
|
|
RunContext::RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter)
|
|
: m_runInfo(_config->name()),
|
|
m_context(getCurrentMutableContext()),
|
|
m_config(_config),
|
|
m_reporter(std::move(reporter)),
|
|
m_lastAssertionInfo{ "", SourceLineInfo("",0), "", ResultDisposition::Normal }
|
|
{
|
|
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;
|
|
|
|
TestCaseInfo 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::assertionStarting(AssertionInfo const& info) {
|
|
m_reporter->assertionStarting( info );
|
|
}
|
|
void RunContext::assertionEnded(AssertionResult const & result) {
|
|
if (result.getResultType() == ResultWas::Ok) {
|
|
m_totals.assertions.passed++;
|
|
} else if (!result.isOk()) {
|
|
if( m_activeTestCase->getTestCaseInfo().okToFail() )
|
|
m_totals.assertions.failedButOk++;
|
|
else
|
|
m_totals.assertions.failed++;
|
|
}
|
|
|
|
// 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
|
|
m_lastAssertionInfo = { "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}", m_lastAssertionInfo.resultDisposition };
|
|
m_lastResult = result;
|
|
}
|
|
|
|
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);
|
|
|
|
getResultCapture().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_totals.assertions.passed == (m_prevPassed + 1);
|
|
}
|
|
|
|
void RunContext::assertionPassed() {
|
|
++m_totals.assertions.passed;
|
|
m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}";
|
|
m_lastAssertionInfo.macroName = "";
|
|
}
|
|
|
|
void RunContext::assertionRun() {
|
|
m_prevPassed = m_totals.assertions.passed;
|
|
}
|
|
|
|
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;
|
|
try {
|
|
m_lastAssertionInfo = { "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal };
|
|
|
|
seedRng(*m_config);
|
|
|
|
Timer timer;
|
|
timer.start();
|
|
if (m_reporter->getPreferences().shouldRedirectStdOut) {
|
|
StreamRedirect coutRedir(cout(), redirectedCout);
|
|
StdErrRedirect errRedir(redirectedCerr);
|
|
invokeActiveTestCase();
|
|
} else {
|
|
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) {
|
|
AssertionHandler
|
|
( m_lastAssertionInfo.macroName,
|
|
m_lastAssertionInfo.lineInfo,
|
|
m_lastAssertionInfo.capturedExpression,
|
|
m_lastAssertionInfo.resultDisposition ).useActiveException();
|
|
}
|
|
}
|
|
m_testCaseTracker->close();
|
|
handleUnfinishedSections();
|
|
m_messages.clear();
|
|
|
|
Counts assertions = m_totals.assertions - prevAssertions;
|
|
bool missingAssertions = testForMissingAssertions(assertions);
|
|
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();
|
|
}
|
|
|
|
IResultCapture& getResultCapture() {
|
|
if (auto* capture = getCurrentContext().getResultCapture())
|
|
return *capture;
|
|
else
|
|
CATCH_INTERNAL_ERROR("No result capture instance");
|
|
}
|
|
}
|