@@ -169,13 +169,18 @@ namespace Catch {
169169 // This is used only for CHECKED_IF/CHECKED_ELSE, and making
170170 // it thread-local fits that usage better than shared between threads.
171171 static thread_local bool g_lastAssertionPassed = false ;
172+ // Should we clear message scopes before sending off the messages to
173+ // reporter? Set in `assertionPassedFastPath` to avoid doing the full
174+ // clear there, as it currently has to happen under mutex.
175+ static thread_local bool g_clearMessageScopes = false ;
176+ // TODO: explain that this is used per thread for handling assertions, only makes sense per thread
177+ static thread_local SourceLineInfo g_lastKnownLineInfo (" DummyLocation" , static_cast <size_t >(-1 ));;
172178 }
173179
174180 RunContext::RunContext (IConfig const * _config, IEventListenerPtr&& reporter)
175181 : m_runInfo(_config->name ()),
176182 m_config(_config),
177183 m_reporter(CATCH_MOVE (reporter)),
178- m_lastKnownLineInfo(" DummyLocation" , static_cast <size_t >(-1 )),
179184 m_outputRedirect( makeOutputRedirect( m_reporter->getPreferences ().shouldRedirectStdOut ) ),
180185 m_abortAfterXFailedAssertions( m_config->abortAfter () ),
181186 m_reportAssertionStarting( m_reporter->getPreferences ().shouldReportAllAssertionStarts ),
@@ -187,10 +192,12 @@ namespace Catch {
187192 }
188193
189194 RunContext::~RunContext () {
195+ m_totals.assertions = m_atomicAssertionCount.toCounts ();
190196 m_reporter->testRunEnded (TestRunStats (m_runInfo, m_totals, aborting ()));
191197 }
192198
193199 Totals RunContext::runTest (TestCaseHandle const & testCase) {
200+ m_totals.assertions = m_atomicAssertionCount.toCounts ();
194201 const Totals prevTotals = m_totals;
195202
196203 auto const & testInfo = testCase.getTestCaseInfo ();
@@ -245,6 +252,7 @@ namespace Catch {
245252
246253 m_reporter->testCasePartialStarting (testInfo, testRuns);
247254
255+ m_totals.assertions = m_atomicAssertionCount.toCounts ();
248256 const auto beforeRunTotals = m_totals;
249257 runCurrentTest ();
250258 std::string oneRunCout = m_outputRedirect->getStdout ();
@@ -253,6 +261,7 @@ namespace Catch {
253261 redirectedCout += oneRunCout;
254262 redirectedCerr += oneRunCerr;
255263
264+ m_totals.assertions = m_atomicAssertionCount.toCounts ();
256265 const auto singleRunTotals = m_totals.delta (beforeRunTotals);
257266 auto statsForOneRun = TestCaseStats (testInfo, singleRunTotals, CATCH_MOVE (oneRunCout), CATCH_MOVE (oneRunCerr), aborting ());
258267 m_reporter->testCasePartialEnded (statsForOneRun, testRuns);
@@ -282,35 +291,35 @@ namespace Catch {
282291
283292
284293 void RunContext::assertionEnded (AssertionResult&& result) {
285- // These could be atomicized (AtomicTotals for assertions?)
286- // FIXME: grab the lineInfo here for paths that end calling into assertionEnded...
287- m_lastKnownLineInfo = result.m_info .lineInfo ;
294+ Detail::g_lastKnownLineInfo = result.m_info .lineInfo ;
288295 if (result.getResultType () == ResultWas::Ok) {
289- m_totals. assertions .passed ++;
296+ m_atomicAssertionCount .passed ++;
290297 Detail::g_lastAssertionPassed = true ;
291298 } else if (result.getResultType () == ResultWas::ExplicitSkip) {
292- m_totals. assertions .skipped ++;
299+ m_atomicAssertionCount .skipped ++;
293300 Detail::g_lastAssertionPassed = true ;
294301 } else if (!result.succeeded ()) {
295302 Detail::g_lastAssertionPassed = false ;
296303 if (result.isOk ()) {
297304 }
298305 else if ( m_activeTestCase->getTestCaseInfo ().okToFail () ) // Read from a shared state established before the threads could start, this is fine
299- m_totals. assertions .failedButOk ++;
306+ m_atomicAssertionCount .failedButOk ++;
300307 else
301- m_totals. assertions .failed ++;
308+ m_atomicAssertionCount .failed ++;
302309 }
303310 else {
304311 Detail::g_lastAssertionPassed = true ;
305312 }
306313
307314 // From here, we are touching shared state and need mutex.
315+ std::lock_guard<std::mutex> lock ( m_assertionMutex );
308316 {
309- if ( m_clearMessageScopes ) {
317+ if ( Detail::g_clearMessageScopes ) {
310318 m_messageScopes.clear ();
311- m_clearMessageScopes = false ;
319+ Detail::g_clearMessageScopes = false ;
312320 }
313321 auto _ = scopedDeactivate ( *m_outputRedirect );
322+ m_totals.assertions = m_atomicAssertionCount.toCounts ();
314323 m_reporter->assertionEnded ( AssertionStats ( result, m_messages, m_totals ) );
315324 }
316325
@@ -325,7 +334,7 @@ namespace Catch {
325334
326335 void RunContext::notifyAssertionStarted ( AssertionInfo const & info ) {
327336 if (m_reportAssertionStarting) {
328- // TODO: Mutex here
337+ std::lock_guard<std::mutex> lock ( m_assertionMutex );
329338 auto _ = scopedDeactivate ( *m_outputRedirect );
330339 m_reporter->assertionStarting ( info );
331340 }
@@ -344,13 +353,16 @@ namespace Catch {
344353 m_activeSections.push_back (§ionTracker);
345354
346355 SectionInfo sectionInfo ( sectionLineInfo, static_cast <std::string>(sectionName) );
347- m_lastKnownLineInfo = sectionLineInfo;
356+ Detail::g_lastKnownLineInfo = sectionLineInfo;
348357
349358 {
350359 auto _ = scopedDeactivate ( *m_outputRedirect );
351360 m_reporter->sectionStarting ( sectionInfo );
352361 }
353362
363+ // FIXME: Do we need to change this, or do we rely on the update in assertion ended?
364+ // We probably need to update the assertion totals here, due to the fast path not updating
365+ m_totals.assertions = m_atomicAssertionCount.toCounts ();
354366 assertions = m_totals.assertions ;
355367
356368 return true ;
@@ -363,7 +375,7 @@ namespace Catch {
363375 m_trackerContext,
364376 TestCaseTracking::NameAndLocationRef (
365377 generatorName, lineInfo ) );
366- m_lastKnownLineInfo = lineInfo;
378+ Detail::g_lastKnownLineInfo = lineInfo;
367379 return tracker;
368380 }
369381
@@ -395,12 +407,13 @@ namespace Catch {
395407 return false ;
396408 if (m_trackerContext.currentTracker ().hasChildren ())
397409 return false ;
398- m_totals. assertions .failed ++;
410+ m_atomicAssertionCount .failed ++;
399411 assertions.failed ++;
400412 return true ;
401413 }
402414
403415 void RunContext::sectionEnded (SectionEndInfo&& endInfo) {
416+ m_totals.assertions = m_atomicAssertionCount.toCounts ();
404417 Counts assertions = m_totals.assertions - endInfo.prevAssertions ;
405418 bool missingAssertions = testForMissingAssertions (assertions);
406419
@@ -476,6 +489,10 @@ namespace Catch {
476489 }
477490
478491 const AssertionResult * RunContext::getLastResult () const {
492+ // m_lastResult is updated inside the assertion slow-path, so it needs to be mutexed as well
493+ // TODO: m_lastResult is not updated in the fast path, is there a point in support it at all?
494+ // TODO: also shouldn't it be a thread-local, since assertions are thread-owned until reporter?
495+ std::lock_guard<std::mutex> _ ( m_assertionMutex );
479496 return &(*m_lastResult);
480497 }
481498
@@ -487,13 +504,20 @@ namespace Catch {
487504 // TODO: What do we do here about threads? Give up? :-D Or maybe slap a mutex and hope for the best?
488505
489506
490- // TODO: scoped deactivate here? Just give up and do best effort?
491- // the deactivation can break things further, OTOH so can the
492- // capture
493- auto _ = scopedDeactivate ( *m_outputRedirect );
507+ {
508+ // Lock before touching the outputs, but we can unlock while preparing
509+ // the fake assertion and stuff.
510+ // TODO: Should we unlock? Trying to let other threads run while fatal error
511+ // is happening is... ill-advised.
512+ std::lock_guard<std::mutex> lock ( m_assertionMutex );
513+ // TODO: scoped deactivate here? Just give up and do best effort?
514+ // the deactivation can break things further, OTOH so can the
515+ // capture
516+ auto _ = scopedDeactivate ( *m_outputRedirect );
494517
495- // First notify reporter that bad things happened
496- m_reporter->fatalErrorEncountered ( message );
518+ // First notify reporter that bad things happened
519+ m_reporter->fatalErrorEncountered ( message );
520+ }
497521
498522 // Don't rebuild the result -- the stringification itself can cause more fatal errors
499523 // Instead, fake a result data.
@@ -504,6 +528,9 @@ namespace Catch {
504528
505529 assertionEnded (CATCH_MOVE (result) );
506530
531+
532+ // TODO: Should we keep granular locks here, or take a single lock at the start?
533+ std::lock_guard<std::mutex> lock ( m_assertionMutex );
507534 // Best effort cleanup for sections that have not been destructed yet
508535 // Since this is a fatal error, we have not had and won't have the opportunity to destruct them properly
509536 while (!m_activeSections.empty ()) {
@@ -533,6 +560,7 @@ namespace Catch {
533560 std::string (),
534561 false ));
535562 m_totals.testCases .failed ++;
563+ m_totals.assertions = m_atomicAssertionCount.toCounts ();
536564 m_reporter->testRunEnded (TestRunStats (m_runInfo, m_totals, false ));
537565 }
538566
@@ -542,24 +570,25 @@ namespace Catch {
542570
543571 void RunContext::assertionPassedFastPath (SourceLineInfo lineInfo) {
544572 // We want to save the line info for better experience with unexpected assertions
545- m_lastKnownLineInfo = lineInfo;
546- ++m_totals. assertions .passed ;
573+ Detail::g_lastKnownLineInfo = lineInfo;
574+ ++m_atomicAssertionCount .passed ;
547575 Detail::g_lastAssertionPassed = true ;
548- m_clearMessageScopes = true ;
576+ Detail::g_clearMessageScopes = true ;
549577 }
550578
551579 bool RunContext::aborting () const {
552- return m_totals. assertions .failed >= m_abortAfterXFailedAssertions;
580+ return m_atomicAssertionCount .failed >= m_abortAfterXFailedAssertions;
553581 }
554582
555583 void RunContext::runCurrentTest () {
556584 auto const & testCaseInfo = m_activeTestCase->getTestCaseInfo ();
557585 SectionInfo testCaseSection (testCaseInfo.lineInfo , testCaseInfo.name );
558586 m_reporter->sectionStarting (testCaseSection);
587+ m_totals.assertions = m_atomicAssertionCount.toCounts ();
559588 Counts prevAssertions = m_totals.assertions ;
560589 double duration = 0 ;
561590 m_shouldReportUnexpected = true ;
562- m_lastKnownLineInfo = testCaseInfo.lineInfo ;
591+ Detail::g_lastKnownLineInfo = testCaseInfo.lineInfo ;
563592
564593 Timer timer;
565594 CATCH_TRY {
@@ -583,6 +612,7 @@ namespace Catch {
583612 dummyReaction );
584613 }
585614 }
615+ m_totals.assertions = m_atomicAssertionCount.toCounts ();
586616 Counts assertions = m_totals.assertions - prevAssertions;
587617 bool missingAssertions = testForMissingAssertions (assertions);
588618
@@ -651,7 +681,7 @@ namespace Catch {
651681 ITransientExpression const *expr,
652682 bool negated ) {
653683
654- m_lastKnownLineInfo = info.lineInfo ; // line info will be atomic -> potentially non-locking on non-msvc platforms, based on target arch (MSVC just sucks :shrug:)
684+ Detail::g_lastKnownLineInfo = info.lineInfo ;
655685 AssertionResultData data ( resultType, LazyExpression ( negated ) );
656686
657687 AssertionResult assertionResult{ info, CATCH_MOVE ( data ) };
@@ -666,7 +696,7 @@ namespace Catch {
666696 std::string&& message,
667697 AssertionReaction& reaction
668698 ) {
669- m_lastKnownLineInfo = info.lineInfo ;
699+ Detail::g_lastKnownLineInfo = info.lineInfo ;
670700
671701 AssertionResultData data ( resultType, LazyExpression ( false ) );
672702 data.message = CATCH_MOVE ( message );
@@ -697,7 +727,7 @@ namespace Catch {
697727 std::string&& message,
698728 AssertionReaction& reaction
699729 ) {
700- m_lastKnownLineInfo = info.lineInfo ;
730+ Detail::g_lastKnownLineInfo = info.lineInfo ;
701731
702732 AssertionResultData data ( ResultWas::ThrewException, LazyExpression ( false ) );
703733 data.message = CATCH_MOVE (message);
@@ -715,11 +745,12 @@ namespace Catch {
715745
716746 AssertionInfo RunContext::makeDummyAssertionInfo () {
717747 const bool testCaseJustStarted =
718- m_lastKnownLineInfo == m_activeTestCase->getTestCaseInfo ().lineInfo ;
748+ Detail::g_lastKnownLineInfo ==
749+ m_activeTestCase->getTestCaseInfo ().lineInfo ;
719750
720751 return AssertionInfo{
721752 testCaseJustStarted ? " TEST_CASE" _sr : StringRef (),
722- m_lastKnownLineInfo ,
753+ Detail::g_lastKnownLineInfo ,
723754 testCaseJustStarted ? StringRef () : " {Unknown expression after the reported line}" _sr,
724755 ResultDisposition::Normal
725756 };
@@ -729,7 +760,7 @@ namespace Catch {
729760 AssertionInfo const & info
730761 ) {
731762 using namespace std ::string_literals;
732- m_lastKnownLineInfo = info.lineInfo ;
763+ Detail::g_lastKnownLineInfo = info.lineInfo ;
733764
734765 AssertionResultData data ( ResultWas::ThrewException, LazyExpression ( false ) );
735766 data.message = " Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE" s;
0 commit comments