home readme diff tree note docs

0commit ed67eed87393f9e03cc3bdd9739ebe668c6220ab
1Author: Mason Wright <mason@lakefox.net>
2Date:   Sun Nov 2 14:10:05 2025 -0700
3
4    Doc changes and catch2 fix
5    
6    Catch2 had a weird utf-8 issue.
7    I am working on switching to groff style docs so they can be viewed anwhere
8
9diff --git a/docs/content/adding/adding-css-properties.3 b/docs/content/adding/adding-css-properties.3
10old mode 100644
11new mode 100755
12diff --git a/docs/content/features/supported-css-selectors.1 b/docs/content/features/supported-css-selectors.1
13old mode 100644
14new mode 100755
15diff --git a/docs/manpage/grim.3 b/docs/manpage/grim.3
16old mode 100644
17new mode 100755
18diff --git a/docs/manpage/grim.html b/docs/manpage/grim.html
19old mode 100644
20new mode 100755
21diff --git a/include/catch_amalgamated.cpp b/include/catch_amalgamated.cpp
22old mode 100755
23new mode 100644
24index 6aa6788..892d637
25--- a/include/catch_amalgamated.cpp
26+++ b/include/catch_amalgamated.cpp
27@@ -6,8 +6,8 @@
28 
29 // SPDX-License-Identifier: BSL-1.0
30 
31-//  Catch v3.5.4
32-//  Generated: 2024-04-10 12:03:46.281848
33+//  Catch v3.11.0
34+//  Generated: 2025-09-30 10:49:12.549018
35 //  ----------------------------------------------------------
36 //  This file is an amalgamation of multiple different files.
37 //  You probably shouldn't edit it directly.
38@@ -128,7 +128,13 @@ namespace Catch {
39 namespace Catch {
40     namespace Benchmark {
41         namespace Detail {
42+            struct do_nothing {
43+                void operator()() const {}
44+            };
45+
46             BenchmarkFunction::callable::~callable() = default;
47+            BenchmarkFunction::BenchmarkFunction():
48+                f( new model<do_nothing>{ {} } ){}
49         } // namespace Detail
50     } // namespace Benchmark
51 } // namespace Catch
52@@ -326,7 +332,7 @@ namespace Catch {
53                                              double diff = b - m;
54                                              return a + diff * diff;
55                                          } ) /
56-                        ( last - first );
57+                        static_cast<double>( last - first );
58                     return std::sqrt( variance );
59                 }
60 
61@@ -361,7 +367,7 @@ namespace Catch {
62                                               double* first,
63                                               double* last ) {
64                 auto count = last - first;
65-                double idx = (count - 1) * k / static_cast<double>(q);
66+                double idx = static_cast<double>((count - 1) * k) / static_cast<double>(q);
67                 int j = static_cast<int>(idx);
68                 double g = idx - j;
69                 std::nth_element(first, first + j, last);
70@@ -464,10 +470,10 @@ namespace Catch {
71 
72                 double accel = sum_cubes / ( 6 * std::pow( sum_squares, 1.5 ) );
73                 long n = static_cast<long>( resample.size() );
74-                double prob_n =
75+                double prob_n = static_cast<double>(
76                     std::count_if( resample.begin(),
77                                    resample.end(),
78-                                   [point]( double x ) { return x < point; } ) /
79+                                   [point]( double x ) { return x < point; } )) /
80                     static_cast<double>( n );
81                 // degenerate case with uniform samples
82                 if ( Catch::Detail::directCompare( prob_n, 0. ) ) {
83@@ -621,7 +627,7 @@ std::string StringMaker<Catch::Approx>::convert(Catch::Approx const& value) {
84 
85 namespace Catch {
86 
87-    AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const & _lazyExpression):
88+    AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const& _lazyExpression):
89         lazyExpression(_lazyExpression),
90         resultType(_resultType) {}
91 
92@@ -819,6 +825,8 @@ namespace Catch {
93             m_data.reporterSpecifications.push_back( std::move( *parsed ) );
94         }
95 
96+        // Reading bazel env vars can change some parts of the config data,
97+        // so we have to process the bazel env before acting on the config.
98         if ( enableBazelEnvSupport() ) {
99             readBazelEnvVars();
100         }
101@@ -883,6 +891,8 @@ namespace Catch {
102 
103     bool Config::showHelp() const { return m_data.showHelp; }
104 
105+    std::string const& Config::getExitGuardFilePath() const { return m_data.prematureExitGuardFilePath; }
106+
107     // IConfig interface
108     bool Config::allowThrows() const                   { return !m_data.noThrow; }
109     StringRef Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; }
110@@ -944,6 +954,26 @@ namespace Catch {
111                 m_data.shardCount = bazelShardOptions->shardCount;
112             }
113         }
114+
115+        const auto bazelExitGuardFile = Detail::getEnv( "TEST_PREMATURE_EXIT_FILE" );
116+        if (bazelExitGuardFile) {
117+            m_data.prematureExitGuardFilePath = bazelExitGuardFile;
118+        }
119+
120+        const auto bazelRandomSeed = Detail::getEnv( "TEST_RANDOM_SEED" );
121+        if ( bazelRandomSeed ) {
122+            auto parsedSeed = parseUInt( bazelRandomSeed, 0 );
123+            if ( !parsedSeed ) {
124+                // Currently we handle issues with parsing other Bazel Env
125+                // options by warning and ignoring the issue. So we do the
126+                // same for random seed option.
127+                Catch::cerr()
128+                    << "Warning: could not parse 'TEST_RANDOM_SEED' ('"
129+                    << bazelRandomSeed << "') as proper seed.\n";
130+            } else {
131+                m_data.rngSeed = *parsedSeed;
132+            }
133+        }
134     }
135 
136 } // end namespace Catch
137@@ -969,28 +999,26 @@ namespace Catch {
138 
139 
140     ScopedMessage::ScopedMessage( MessageBuilder&& builder ):
141-        m_info( CATCH_MOVE(builder.m_info) ) {
142-        m_info.message = builder.m_stream.str();
143-        getResultCapture().pushScopedMessage( m_info );
144+        m_messageId( builder.m_info.sequence ) {
145+        MessageInfo info( CATCH_MOVE( builder.m_info ) );
146+        info.message = builder.m_stream.str();
147+        IResultCapture::pushScopedMessage( CATCH_MOVE( info ) );
148     }
149 
150     ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept:
151-        m_info( CATCH_MOVE( old.m_info ) ) {
152+        m_messageId( old.m_messageId ) {
153         old.m_moved = true;
154     }
155 
156     ScopedMessage::~ScopedMessage() {
157-        if ( !uncaught_exceptions() && !m_moved ){
158-            getResultCapture().popScopedMessage(m_info);
159-        }
160+        if ( !m_moved ) { IResultCapture::popScopedMessage( m_messageId ); }
161     }
162 
163 
164     Capturer::Capturer( StringRef macroName,
165                         SourceLineInfo const& lineInfo,
166                         ResultWas::OfType resultType,
167-                        StringRef names ):
168-        m_resultCapture( getResultCapture() ) {
169+                        StringRef names ) {
170         auto trimmed = [&] (size_t start, size_t end) {
171             while (names[start] == ',' || isspace(static_cast<unsigned char>(names[start]))) {
172                 ++start;
173@@ -1036,30 +1064,30 @@ namespace Catch {
174             case ',':
175                 if (start != pos && openings.empty()) {
176                     m_messages.emplace_back(macroName, lineInfo, resultType);
177-                    m_messages.back().message = static_cast<std::string>(trimmed(start, pos));
178-                    m_messages.back().message += " := ";
179+                    m_messages.back().message += trimmed(start, pos);
180+                    m_messages.back().message += " := "_sr;
181                     start = pos;
182                 }
183+                break;
184             default:; // noop
185             }
186         }
187         assert(openings.empty() && "Mismatched openings");
188         m_messages.emplace_back(macroName, lineInfo, resultType);
189-        m_messages.back().message = static_cast<std::string>(trimmed(start, names.size() - 1));
190-        m_messages.back().message += " := ";
191+        m_messages.back().message += trimmed(start, names.size() - 1);
192+        m_messages.back().message += " := "_sr;
193     }
194     Capturer::~Capturer() {
195-        if ( !uncaught_exceptions() ){
196-            assert( m_captured == m_messages.size() );
197-            for( size_t i = 0; i < m_captured; ++i  )
198-                m_resultCapture.popScopedMessage( m_messages[i] );
199+        assert( m_captured == m_messages.size() );
200+        for (auto const& message : m_messages) {
201+            IResultCapture::popScopedMessage( message.sequence );
202         }
203     }
204 
205     void Capturer::captureValue( size_t index, std::string const& value ) {
206         assert( index < m_messages.size() );
207         m_messages[index].message += value;
208-        m_resultCapture.pushScopedMessage( m_messages[index] );
209+        IResultCapture::pushScopedMessage( CATCH_MOVE( m_messages[index] ) );
210         m_captured++;
211     }
212 
213@@ -1143,7 +1171,6 @@ namespace Catch {
214     }
215     void cleanUp() {
216         cleanupSingletons();
217-        cleanUpContext();
218     }
219     std::string translateActiveException() {
220         return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException();
221@@ -1154,8 +1181,9 @@ namespace Catch {
222 
223 
224 
225-#include <algorithm>
226 #include <cassert>
227+#include <cstdio>
228+#include <cstdlib>
229 #include <exception>
230 #include <iomanip>
231 #include <set>
232@@ -1163,8 +1191,6 @@ namespace Catch {
233 namespace Catch {
234 
235     namespace {
236-        const int MaxExitCode = 255;
237-
238         IEventListenerPtr createReporter(std::string const& reporterName, ReporterConfig&& config) {
239             auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, CATCH_MOVE(config));
240             CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << '\'');
241@@ -1272,6 +1298,50 @@ namespace Catch {
242             }
243         }
244 
245+        // Creates empty file at path. The path must be writable, we do not
246+        // try to create directories in path because that's hard in C++14.
247+        void setUpGuardFile( std::string const& guardFilePath ) {
248+            if ( !guardFilePath.empty() ) {
249+#if defined( _MSC_VER )
250+                std::FILE* file = nullptr;
251+                if ( fopen_s( &file, guardFilePath.c_str(), "w" ) ) {
252+                    char msgBuffer[100];
253+                    const auto err = errno;
254+                    std::string errMsg;
255+                    if ( !strerror_s( msgBuffer, err ) ) {
256+                        errMsg = msgBuffer;
257+                    } else {
258+                        errMsg = "Could not translate errno to a string";
259+                    }
260+
261+#else
262+                std::FILE* file = std::fopen( guardFilePath.c_str(), "w" );
263+                if ( !file ) {
264+                    const auto err = errno;
265+                    const char* errMsg = std::strerror( err );
266+#endif
267+
268+                    CATCH_RUNTIME_ERROR( "Could not open the exit guard file '"
269+                                         << guardFilePath << "' because '"
270+                                         << errMsg << "' (" << err << ')' );
271+                }
272+                const int ret = std::fclose( file );
273+                CATCH_ENFORCE(
274+                    ret == 0,
275+                    "Error when closing the exit guard file: " << ret );
276+            }
277+        }
278+
279+        // Removes file at path. Assuming we created it in setUpGuardFile.
280+        void tearDownGuardFile( std::string const& guardFilePath ) {
281+            if ( !guardFilePath.empty() ) {
282+                const int ret = std::remove( guardFilePath.c_str() );
283+                CATCH_ENFORCE(
284+                    ret == 0,
285+                    "Error when removing the exit guard file: " << ret );
286+            }
287+        }
288+
289     } // anon namespace
290 
291     Session::Session() {
292@@ -1327,8 +1397,7 @@ namespace Catch {
293     }
294 
295     int Session::applyCommandLine( int argc, char const * const * argv ) {
296-        if( m_startupExceptions )
297-            return 1;
298+        if ( m_startupExceptions ) { return UnspecifiedErrorExitCode; }
299 
300         auto result = m_cli.parse( Clara::Args( argc, argv ) );
301 
302@@ -1344,7 +1413,7 @@ namespace Catch {
303                 << TextFlow::Column( result.errorMessage() ).indent( 2 )
304                 << "\n\n";
305             errStream->stream() << "Run with -? for usage\n\n" << std::flush;
306-            return MaxExitCode;
307+            return UnspecifiedErrorExitCode;
308         }
309 
310         if( m_configData.showHelp )
311@@ -1391,6 +1460,7 @@ namespace Catch {
312             static_cast<void>(std::getchar());
313         }
314         int exitCode = runInternal();
315+
316         if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) {
317             Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << '\n' << std::flush;
318             static_cast<void>(std::getchar());
319@@ -1414,8 +1484,7 @@ namespace Catch {
320     }
321 
322     int Session::runInternal() {
323-        if( m_startupExceptions )
324-            return 1;
325+        if ( m_startupExceptions ) { return UnspecifiedErrorExitCode; }
326 
327         if (m_configData.showHelp || m_configData.libIdentify) {
328             return 0;
329@@ -1426,12 +1495,16 @@ namespace Catch {
330                           << ") must be greater than the shard index ("
331                           << m_configData.shardIndex << ")\n"
332                           << std::flush;
333-            return 1;
334+            return UnspecifiedErrorExitCode;
335         }
336 
337         CATCH_TRY {
338             config(); // Force config to be constructed
339 
340+            // We need to retrieve potential Bazel config with the full Config
341+            // constructor, so we have to create the guard file after it is created.
342+            setUpGuardFile( m_config->getExitGuardFilePath() );
343+
344             seedRng( *m_config );
345 
346             if (m_configData.filenamesAsTags) {
347@@ -1449,7 +1522,7 @@ namespace Catch {
348                 for ( auto const& spec : invalidSpecs ) {
349                     reporter->reportInvalidTestSpec( spec );
350                 }
351-                return 1;
352+                return InvalidTestSpecExitCode;
353             }
354 
355 
356@@ -1461,31 +1534,34 @@ namespace Catch {
357             TestGroup tests { CATCH_MOVE(reporter), m_config.get() };
358             auto const totals = tests.execute();
359 
360+            // If we got here, running the tests finished normally-enough.
361+            // They might've failed, but that would've been reported elsewhere.
362+            tearDownGuardFile( m_config->getExitGuardFilePath() );
363+
364             if ( tests.hadUnmatchedTestSpecs()
365                 && m_config->warnAboutUnmatchedTestSpecs() ) {
366-                return 3;
367+                return UnmatchedTestSpecExitCode;
368             }
369 
370             if ( totals.testCases.total() == 0
371                 && !m_config->zeroTestsCountAsSuccess() ) {
372-                return 2;
373+                return NoTestsRunExitCode;
374             }
375 
376             if ( totals.testCases.total() > 0 &&
377                  totals.testCases.total() == totals.testCases.skipped
378                 && !m_config->zeroTestsCountAsSuccess() ) {
379-                return 4;
380+                return AllTestsSkippedExitCode;
381             }
382 
383-            // Note that on unices only the lower 8 bits are usually used, clamping
384-            // the return value to 255 prevents false negative when some multiple
385-            // of 256 tests has failed
386-            return (std::min) (MaxExitCode, static_cast<int>(totals.assertions.failed));
387+            if ( totals.assertions.failed ) { return TestFailureExitCode; }
388+            return 0;
389+
390         }
391 #if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
392         catch( std::exception& ex ) {
393             Catch::cerr() << ex.what() << '\n' << std::flush;
394-            return MaxExitCode;
395+            return UnspecifiedErrorExitCode;
396         }
397 #endif
398     }
399@@ -1521,26 +1597,26 @@ namespace Catch {
400         static_assert(sizeof(TestCaseProperties) == sizeof(TCP_underlying_type),
401                       "The size of the TestCaseProperties is different from the assumed size");
402 
403-        TestCaseProperties operator|(TestCaseProperties lhs, TestCaseProperties rhs) {
404+        constexpr TestCaseProperties operator|(TestCaseProperties lhs, TestCaseProperties rhs) {
405             return static_cast<TestCaseProperties>(
406                 static_cast<TCP_underlying_type>(lhs) | static_cast<TCP_underlying_type>(rhs)
407             );
408         }
409 
410-        TestCaseProperties& operator|=(TestCaseProperties& lhs, TestCaseProperties rhs) {
411+        constexpr TestCaseProperties& operator|=(TestCaseProperties& lhs, TestCaseProperties rhs) {
412             lhs = static_cast<TestCaseProperties>(
413                 static_cast<TCP_underlying_type>(lhs) | static_cast<TCP_underlying_type>(rhs)
414             );
415             return lhs;
416         }
417 
418-        TestCaseProperties operator&(TestCaseProperties lhs, TestCaseProperties rhs) {
419+        constexpr TestCaseProperties operator&(TestCaseProperties lhs, TestCaseProperties rhs) {
420             return static_cast<TestCaseProperties>(
421                 static_cast<TCP_underlying_type>(lhs) & static_cast<TCP_underlying_type>(rhs)
422             );
423         }
424 
425-        bool applies(TestCaseProperties tcp) {
426+        constexpr bool applies(TestCaseProperties tcp) {
427             static_assert(static_cast<TCP_underlying_type>(TestCaseProperties::None) == 0,
428                           "TestCaseProperties::None must be equal to 0");
429             return tcp != TestCaseProperties::None;
430@@ -1579,7 +1655,7 @@ namespace Catch {
431             return "Anonymous test case " + std::to_string(++counter);
432         }
433 
434-        StringRef extractFilenamePart(StringRef filename) {
435+        constexpr StringRef extractFilenamePart(StringRef filename) {
436             size_t lastDot = filename.size();
437             while (lastDot > 0 && filename[lastDot - 1] != '.') {
438                 --lastDot;
439@@ -1597,7 +1673,7 @@ namespace Catch {
440         }
441 
442         // Returns the upper bound on size of extra tags ([#file]+[.])
443-        size_t sizeOfExtraTags(StringRef filepath) {
444+        constexpr size_t sizeOfExtraTags(StringRef filepath) {
445             // [.] is 3, [#] is another 3
446             const size_t extras = 3 + 3;
447             return extractFilenamePart(filepath).size() + extras;
448@@ -1758,10 +1834,6 @@ namespace Catch {
449         return lhs.tags < rhs.tags;
450     }
451 
452-    TestCaseInfo const& TestCaseHandle::getTestCaseInfo() const {
453-        return *m_info;
454-    }
455-
456 } // end namespace Catch
457 
458 
459@@ -1902,7 +1974,7 @@ namespace Catch {
460 
461     namespace {
462         static auto getCurrentNanosecondsSinceEpoch() -> uint64_t {
463-            return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
464+            return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
465         }
466     } // end unnamed namespace
467 
468@@ -1919,7 +1991,7 @@ namespace Catch {
469         return static_cast<unsigned int>(getElapsedMicroseconds()/1000);
470     }
471     auto Timer::getElapsedSeconds() const -> double {
472-        return getElapsedMicroseconds()/1000000.0;
473+        return static_cast<double>(getElapsedMicroseconds())/1000000.0;
474     }
475 
476 
477@@ -1928,7 +2000,6 @@ namespace Catch {
478 
479 
480 
481-#include <cmath>
482 #include <iomanip>
483 
484 namespace Catch {
485@@ -1939,7 +2010,10 @@ namespace Detail {
486         const int hexThreshold = 255;
487 
488         struct Endianness {
489-            enum Arch { Big, Little };
490+            enum Arch : uint8_t {
491+                Big,
492+                Little
493+            };
494 
495             static Arch which() {
496                 int one = 1;
497@@ -1975,35 +2049,35 @@ namespace Detail {
498         std::string ret;
499         // This is enough for the "don't escape invisibles" case, and a good
500         // lower bound on the "escape invisibles" case.
501-        ret.reserve(string.size() + 2);
502+        ret.reserve( string.size() + 2 );
503 
504-        if (!escapeInvisibles) {
505+        if ( !escapeInvisibles ) {
506             ret += '"';
507             ret += string;
508             ret += '"';
509             return ret;
510         }
511 
512+        size_t last_start = 0;
513+        auto write_to = [&]( size_t idx ) {
514+            if ( last_start < idx ) {
515+                ret += string.substr( last_start, idx - last_start );
516+            }
517+            last_start = idx + 1;
518+        };
519+
520         ret += '"';
521-        for (char c : string) {
522-            switch (c) {
523-            case '\r':
524-                ret.append("\\r");
525-                break;
526-            case '\n':
527-                ret.append("\\n");
528-                break;
529-            case '\t':
530-                ret.append("\\t");
531-                break;
532-            case '\f':
533-                ret.append("\\f");
534-                break;
535-            default:
536-                ret.push_back(c);
537-                break;
538+        for ( size_t i = 0; i < string.size(); ++i ) {
539+            const char c = string[i];
540+            if ( c == '\r' || c == '\n' || c == '\t' || c == '\f' ) {
541+                write_to( i );
542+                if ( c == '\r' ) { ret.append( "\\r" ); }
543+                if ( c == '\n' ) { ret.append( "\\n" ); }
544+                if ( c == '\t' ) { ret.append( "\\t" ); }
545+                if ( c == '\f' ) { ret.append( "\\f" ); }
546             }
547         }
548+        write_to( string.size() );
549         ret += '"';
550 
551         return ret;
552@@ -2028,6 +2102,13 @@ namespace Detail {
553              rss << std::setw(2) << static_cast<unsigned>(bytes[i]);
554        return rss.str();
555     }
556+
557+    std::string makeExceptionHappenedString() {
558+        return "{ stringification failed with an exception: \"" +
559+               translateActiveException() + "\" }";
560+
561+    }
562+
563 } // end Detail namespace
564 
565 
566@@ -2156,13 +2237,13 @@ std::string StringMaker<unsigned char>::convert(unsigned char value) {
567     return ::Catch::Detail::stringify(static_cast<char>(value));
568 }
569 
570-int StringMaker<float>::precision = 5;
571+int StringMaker<float>::precision = std::numeric_limits<float>::max_digits10;
572 
573 std::string StringMaker<float>::convert(float value) {
574     return Detail::fpToString(value, precision) + 'f';
575 }
576 
577-int StringMaker<double>::precision = 10;
578+int StringMaker<double>::precision = std::numeric_limits<double>::max_digits10;
579 
580 std::string StringMaker<double>::convert(double value) {
581     return Detail::fpToString(value, precision);
582@@ -2273,7 +2354,7 @@ namespace Catch {
583     }
584 
585     Version const& libraryVersion() {
586-        static Version version( 3, 5, 4, "", 0 );
587+        static Version version( 3, 11, 0, "", 0 );
588         return version;
589     }
590 
591@@ -2361,8 +2442,14 @@ namespace Catch {
592 
593 
594 namespace Catch {
595+    namespace Detail {
596+        void missingCaptureInstance() {
597+            CATCH_INTERNAL_ERROR( "No result capture instance" );
598+        }
599+    } // namespace Detail
600+
601     IResultCapture::~IResultCapture() = default;
602-}
603+} // namespace Catch
604 
605 
606 
607@@ -2529,8 +2616,8 @@ namespace Catch {
608     void AssertionHandler::handleExpr( ITransientExpression const& expr ) {
609         m_resultCapture.handleExpr( m_assertionInfo, expr, m_reaction );
610     }
611-    void AssertionHandler::handleMessage(ResultWas::OfType resultType, StringRef message) {
612-        m_resultCapture.handleMessage( m_assertionInfo, resultType, message, m_reaction );
613+    void AssertionHandler::handleMessage(ResultWas::OfType resultType, std::string&& message) {
614+        m_resultCapture.handleMessage( m_assertionInfo, resultType, CATCH_MOVE(message), m_reaction );
615     }
616 
617     auto AssertionHandler::allowThrows() const -> bool {
618@@ -2676,7 +2763,7 @@ namespace Catch {
619                                 { TokenType::Argument,
620                                   next.substr( delimiterPos + 1, next.size() ) } );
621                         } else {
622-                            if ( next[1] != '-' && next.size() > 2 ) {
623+                            if ( next.size() > 1 && next[1] != '-' && next.size() > 2 ) {
624                                 // Combined short args, e.g. "-ab" for "-a -b"
625                                 for ( size_t i = 1; i < next.size(); ++i ) {
626                                     m_tokenBuffer.push_back(
627@@ -3355,6 +3442,9 @@ namespace Catch {
628             | Opt( config.allowZeroTests )
629                 ["--allow-running-no-tests"]
630                 ( "Treat 'No tests run' as a success" )
631+            | Opt( config.prematureExitGuardFilePath, "path" )
632+                ["--premature-exit-guard-file"]
633+                ( "create a file before running tests and delete it during clean exit" )
634             | Arg( config.testsOrTags, "test name|pattern|tags" )
635                 ( "which test or tests to use" );
636 
637@@ -3509,7 +3599,11 @@ namespace {
638 #endif // Windows/ ANSI/ None
639 
640 
641-#if defined( CATCH_PLATFORM_LINUX ) || defined( CATCH_PLATFORM_MAC )
642+#if defined( CATCH_PLATFORM_LINUX ) \
643+ || defined( CATCH_PLATFORM_MAC ) \
644+ || defined( __GLIBC__ ) \
645+ || defined( __FreeBSD__ ) \
646+ || defined( CATCH_PLATFORM_QNX )
647 #    define CATCH_INTERNAL_HAS_ISATTY
648 #    include <unistd.h>
649 #endif
650@@ -3633,28 +3727,12 @@ namespace Catch {
651 
652 namespace Catch {
653 
654-    Context* Context::currentContext = nullptr;
655-
656-    void cleanUpContext() {
657-        delete Context::currentContext;
658-        Context::currentContext = nullptr;
659-    }
660-    void Context::createContext() {
661-        currentContext = new Context();
662-    }
663+    Context Context::currentContext;
664 
665     Context& getCurrentMutableContext() {
666-        if ( !Context::currentContext ) { Context::createContext(); }
667-        // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn)
668-        return *Context::currentContext;
669+        return Context::currentContext;
670     }
671 
672-    void Context::setResultCapture( IResultCapture* resultCapture ) {
673-        m_resultCapture = resultCapture;
674-    }
675-
676-    void Context::setConfig( IConfig const* config ) { m_config = config; }
677-
678     SimplePcg32& sharedRng() {
679         static SimplePcg32 s_rng;
680         return s_rng;
681@@ -3757,7 +3835,7 @@ namespace Catch {
682         #endif
683     } // namespace Catch
684 
685-#elif defined(CATCH_PLATFORM_LINUX)
686+#elif defined(CATCH_PLATFORM_LINUX) || defined(CATCH_PLATFORM_QNX)
687     #include <fstream>
688     #include <string>
689 
690@@ -3989,10 +4067,10 @@ namespace Catch {
691         // To avoid having to handle TFE explicitly everywhere, we just
692         // rethrow it so that it goes back up the caller.
693         catch( TestFailureException& ) {
694-            std::rethrow_exception(std::current_exception());
695+            return "{ nested assertion failed }";
696         }
697         catch( TestSkipException& ) {
698-            std::rethrow_exception(std::current_exception());
699+            return "{ nested SKIP() called }";
700         }
701         catch( std::exception const& ex ) {
702             return ex.what();
703@@ -4084,30 +4162,34 @@ namespace Catch {
704     // There is no 1-1 mapping between signals and windows exceptions.
705     // Windows can easily distinguish between SO and SigSegV,
706     // but SigInt, SigTerm, etc are handled differently.
707-    static SignalDefs signalDefs[] = {
708+    static constexpr SignalDefs signalDefs[] = {
709         { EXCEPTION_ILLEGAL_INSTRUCTION,  "SIGILL - Illegal instruction signal" },
710         { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" },
711         { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" },
712         { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" },
713     };
714 
715+    // Since we do not support multiple instantiations, we put these
716+    // into global variables and rely on cleaning them up in outlined
717+    // constructors/destructors
718+    static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr;
719+
720+
721     static LONG CALLBACK topLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) {
722         for (auto const& def : signalDefs) {
723             if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) {
724                 reportFatal(def.name);
725             }
726         }
727-        // If its not an exception we care about, pass it along.
728+        // If a filter was previously registered, invoke it
729+        if (previousTopLevelExceptionFilter) {
730+            return previousTopLevelExceptionFilter(ExceptionInfo);
731+        }
732+        // Otherwise, pass along all exceptions.
733         // This stops us from eating debugger breaks etc.
734         return EXCEPTION_CONTINUE_SEARCH;
735     }
736 
737-    // Since we do not support multiple instantiations, we put these
738-    // into global variables and rely on cleaning them up in outlined
739-    // constructors/destructors
740-    static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr;
741-
742-
743     // For MSVC, we reserve part of the stack memory for handling
744     // memory overflow structured exception.
745     FatalConditionHandler::FatalConditionHandler() {
746@@ -4155,7 +4237,7 @@ namespace Catch {
747         const char* name;
748     };
749 
750-    static SignalDefs signalDefs[] = {
751+    static constexpr SignalDefs signalDefs[] = {
752         { SIGINT,  "SIGINT - Terminal interrupt signal" },
753         { SIGILL,  "SIGILL - Illegal instruction signal" },
754         { SIGFPE,  "SIGFPE - Floating point error signal" },
755@@ -4319,8 +4401,6 @@ namespace Catch {
756 
757 #include <cstdio>
758 #include <fstream>
759-#include <sstream>
760-#include <vector>
761 
762 namespace Catch {
763 
764@@ -4461,6 +4541,33 @@ namespace Detail {
765 
766 
767 namespace Catch {
768+
769+    namespace {
770+        static bool needsEscape( char c ) {
771+            return c == '"' || c == '\\' || c == '\b' || c == '\f' ||
772+                   c == '\n' || c == '\r' || c == '\t';
773+        }
774+
775+        static Catch::StringRef makeEscapeStringRef( char c ) {
776+            if ( c == '"' ) {
777+                return "\\\""_sr;
778+            } else if ( c == '\\' ) {
779+                return "\\\\"_sr;
780+            } else if ( c == '\b' ) {
781+                return "\\b"_sr;
782+            } else if ( c == '\f' ) {
783+                return "\\f"_sr;
784+            } else if ( c == '\n' ) {
785+                return "\\n"_sr;
786+            } else if ( c == '\r' ) {
787+                return "\\r"_sr;
788+            } else if ( c == '\t' ) {
789+                return "\\t"_sr;
790+            }
791+            Catch::Detail::Unreachable();
792+        }
793+    } // namespace
794+
795     void JsonUtils::indent( std::ostream& os, std::uint64_t level ) {
796         for ( std::uint64_t i = 0; i < level; ++i ) {
797             os << "  ";
798@@ -4570,30 +4677,19 @@ namespace Catch {
799 
800     void JsonValueWriter::writeImpl( Catch::StringRef value, bool quote ) {
801         if ( quote ) { m_os << '"'; }
802-        for (char c : value) {
803-            // Escape list taken from https://www.json.org/json-en.html,
804-            // string definition.
805-            // Note that while forward slash _can_ be escaped, it does
806-            // not have to be, if JSON is not further embedded somewhere
807-            // where forward slash is meaningful.
808-            if ( c == '"' ) {
809-                m_os << "\\\"";
810-            } else if ( c == '\\' ) {
811-                m_os << "\\\\";
812-            } else if ( c == '\b' ) {
813-                m_os << "\\b";
814-            } else if ( c == '\f' ) {
815-                m_os << "\\f";
816-            } else if ( c == '\n' ) {
817-                m_os << "\\n";
818-            } else if ( c == '\r' ) {
819-                m_os << "\\r";
820-            } else if ( c == '\t' ) {
821-                m_os << "\\t";
822-            } else {
823-                m_os << c;
824+        size_t current_start = 0;
825+        for ( size_t i = 0; i < value.size(); ++i ) {
826+            if ( needsEscape( value[i] ) ) {
827+                if ( current_start < i ) {
828+                    m_os << value.substr( current_start, i - current_start );
829+                }
830+                m_os << makeEscapeStringRef( value[i] );
831+                current_start = i + 1;
832             }
833         }
834+        if ( current_start < value.size() ) {
835+            m_os << value.substr( current_start, value.size() - current_start );
836+        }
837         if ( quote ) { m_os << '"'; }
838     }
839 
840@@ -4761,7 +4857,7 @@ namespace Catch {
841 namespace Catch {
842     CATCH_INTERNAL_START_WARNINGS_SUPPRESSION
843     CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS
844-    static LeakDetector leakDetector;
845+    static const LeakDetector leakDetector;
846     CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
847 }
848 
849@@ -4790,17 +4886,18 @@ int main (int argc, char * argv[]) {
850 
851 namespace Catch {
852 
853-    MessageInfo::MessageInfo(   StringRef _macroName,
854-                                SourceLineInfo const& _lineInfo,
855-                                ResultWas::OfType _type )
856+    MessageInfo::MessageInfo( StringRef _macroName,
857+                              SourceLineInfo const& _lineInfo,
858+                              ResultWas::OfType _type )
859     :   macroName( _macroName ),
860         lineInfo( _lineInfo ),
861         type( _type ),
862         sequence( ++globalCount )
863     {}
864 
865-    // This may need protecting if threading support is added
866-    unsigned int MessageInfo::globalCount = 0;
867+    // Messages are owned by their individual threads, so the counter should be thread-local as well.
868+    // Alternative consideration: atomic, so threads don't share IDs and things are easier to debug.
869+    thread_local unsigned int MessageInfo::globalCount = 0;
870 
871 } // end namespace Catch
872 
873@@ -4808,138 +4905,328 @@ namespace Catch {
874 
875 #include <cstdio>
876 #include <cstring>
877+#include <iosfwd>
878 #include <sstream>
879 
880-#if defined(CATCH_CONFIG_NEW_CAPTURE)
881-    #if defined(_MSC_VER)
882-    #include <io.h>      //_dup and _dup2
883-    #define dup _dup
884-    #define dup2 _dup2
885-    #define fileno _fileno
886-    #else
887-    #include <unistd.h>  // dup and dup2
888-    #endif
889+#if defined( CATCH_CONFIG_NEW_CAPTURE )
890+#    if defined( _MSC_VER )
891+#        include <io.h> //_dup and _dup2
892+#        define dup _dup
893+#        define dup2 _dup2
894+#        define fileno _fileno
895+#    else
896+#        include <unistd.h> // dup and dup2
897+#    endif
898 #endif
899 
900-
901 namespace Catch {
902 
903-    RedirectedStream::RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream )
904-    :   m_originalStream( originalStream ),
905-        m_redirectionStream( redirectionStream ),
906-        m_prevBuf( m_originalStream.rdbuf() )
907-    {
908-        m_originalStream.rdbuf( m_redirectionStream.rdbuf() );
909-    }
910+    namespace {
911+        //! A no-op implementation, used if no reporter wants output
912+        //! redirection.
913+        class NoopRedirect : public OutputRedirect {
914+            void activateImpl() override {}
915+            void deactivateImpl() override {}
916+            std::string getStdout() override { return {}; }
917+            std::string getStderr() override { return {}; }
918+            void clearBuffers() override {}
919+        };
920 
921-    RedirectedStream::~RedirectedStream() {
922-        m_originalStream.rdbuf( m_prevBuf );
923-    }
924+        /**
925+         * Redirects specific stream's rdbuf with another's.
926+         *
927+         * Redirection can be stopped and started on-demand, assumes
928+         * that the underlying stream's rdbuf aren't changed by other
929+         * users.
930+         */
931+        class RedirectedStreamNew {
932+            std::ostream& m_originalStream;
933+            std::ostream& m_redirectionStream;
934+            std::streambuf* m_prevBuf;
935 
936-    RedirectedStdOut::RedirectedStdOut() : m_cout( Catch::cout(), m_rss.get() ) {}
937-    auto RedirectedStdOut::str() const -> std::string { return m_rss.str(); }
938+        public:
939+            RedirectedStreamNew( std::ostream& originalStream,
940+                                 std::ostream& redirectionStream ):
941+                m_originalStream( originalStream ),
942+                m_redirectionStream( redirectionStream ),
943+                m_prevBuf( m_originalStream.rdbuf() ) {}
944 
945-    RedirectedStdErr::RedirectedStdErr()
946-    :   m_cerr( Catch::cerr(), m_rss.get() ),
947-        m_clog( Catch::clog(), m_rss.get() )
948-    {}
949-    auto RedirectedStdErr::str() const -> std::string { return m_rss.str(); }
950+            void startRedirect() {
951+                m_originalStream.rdbuf( m_redirectionStream.rdbuf() );
952+            }
953+            void stopRedirect() { m_originalStream.rdbuf( m_prevBuf ); }
954+        };
955 
956-    RedirectedStreams::RedirectedStreams(std::string& redirectedCout, std::string& redirectedCerr)
957-    :   m_redirectedCout(redirectedCout),
958-        m_redirectedCerr(redirectedCerr)
959-    {}
960+        /**
961+         * Redirects the `std::cout`, `std::cerr`, `std::clog` streams,
962+         * but does not touch the actual `stdout`/`stderr` file descriptors.
963+         */
964+        class StreamRedirect : public OutputRedirect {
965+            ReusableStringStream m_redirectedOut, m_redirectedErr;
966+            RedirectedStreamNew m_cout, m_cerr, m_clog;
967 
968-    RedirectedStreams::~RedirectedStreams() {
969-        m_redirectedCout += m_redirectedStdOut.str();
970-        m_redirectedCerr += m_redirectedStdErr.str();
971-    }
972+        public:
973+            StreamRedirect():
974+                m_cout( Catch::cout(), m_redirectedOut.get() ),
975+                m_cerr( Catch::cerr(), m_redirectedErr.get() ),
976+                m_clog( Catch::clog(), m_redirectedErr.get() ) {}
977+
978+            void activateImpl() override {
979+                m_cout.startRedirect();
980+                m_cerr.startRedirect();
981+                m_clog.startRedirect();
982+            }
983+            void deactivateImpl() override {
984+                m_cout.stopRedirect();
985+                m_cerr.stopRedirect();
986+                m_clog.stopRedirect();
987+            }
988+            std::string getStdout() override { return m_redirectedOut.str(); }
989+            std::string getStderr() override { return m_redirectedErr.str(); }
990+            void clearBuffers() override {
991+                m_redirectedOut.str( "" );
992+                m_redirectedErr.str( "" );
993+            }
994+        };
995 
996-#if defined(CATCH_CONFIG_NEW_CAPTURE)
997+#if defined( CATCH_CONFIG_NEW_CAPTURE )
998 
999-#if defined(_MSC_VER)
1000-    TempFile::TempFile() {
1001-        if (tmpnam_s(m_buffer)) {
1002-            CATCH_RUNTIME_ERROR("Could not get a temp filename");
1003-        }
1004-        if (fopen_s(&m_file, m_buffer, "w+")) {
1005-            char buffer[100];
1006-            if (strerror_s(buffer, errno)) {
1007-                CATCH_RUNTIME_ERROR("Could not translate errno to a string");
1008+        // Windows's implementation of std::tmpfile is terrible (it tries
1009+        // to create a file inside system folder, thus requiring elevated
1010+        // privileges for the binary), so we have to use tmpnam(_s) and
1011+        // create the file ourselves there.
1012+        class TempFile {
1013+        public:
1014+            TempFile( TempFile const& ) = delete;
1015+            TempFile& operator=( TempFile const& ) = delete;
1016+            TempFile( TempFile&& ) = delete;
1017+            TempFile& operator=( TempFile&& ) = delete;
1018+
1019+#    if defined( _MSC_VER )
1020+            TempFile() {
1021+                if ( tmpnam_s( m_buffer ) ) {
1022+                    CATCH_RUNTIME_ERROR( "Could not get a temp filename" );
1023+                }
1024+                if ( fopen_s( &m_file, m_buffer, "wb+" ) ) {
1025+                    char buffer[100];
1026+                    if ( strerror_s( buffer, errno ) ) {
1027+                        CATCH_RUNTIME_ERROR(
1028+                            "Could not translate errno to a string" );
1029+                    }
1030+                    CATCH_RUNTIME_ERROR( "Could not open the temp file: '"
1031+                                         << m_buffer
1032+                                         << "' because: " << buffer );
1033+                }
1034+            }
1035+#    else
1036+            TempFile() {
1037+                m_file = std::tmpfile();
1038+                if ( !m_file ) {
1039+                    CATCH_RUNTIME_ERROR( "Could not create a temp file." );
1040+                }
1041+            }
1042+#    endif
1043+
1044+            ~TempFile() {
1045+                // TBD: What to do about errors here?
1046+                std::fclose( m_file );
1047+                // We manually create the file on Windows only, on Linux
1048+                // it will be autodeleted
1049+#    if defined( _MSC_VER )
1050+                std::remove( m_buffer );
1051+#    endif
1052+            }
1053+
1054+            std::FILE* getFile() { return m_file; }
1055+            std::string getContents() {
1056+                ReusableStringStream sstr;
1057+                constexpr long buffer_size = 100;
1058+                char buffer[buffer_size + 1] = {};
1059+                long current_pos = ftell( m_file );
1060+                CATCH_ENFORCE( current_pos >= 0,
1061+                               "ftell failed, errno: " << errno );
1062+                std::rewind( m_file );
1063+                while ( current_pos > 0 ) {
1064+                    auto read_characters =
1065+                        std::fread( buffer,
1066+                                    1,
1067+                                    std::min( buffer_size, current_pos ),
1068+                                    m_file );
1069+                    buffer[read_characters] = '\0';
1070+                    sstr << buffer;
1071+                    current_pos -= static_cast<long>( read_characters );
1072+                }
1073+                return sstr.str();
1074+            }
1075+
1076+            void clear() { std::rewind( m_file ); }
1077+
1078+        private:
1079+            std::FILE* m_file = nullptr;
1080+            char m_buffer[L_tmpnam] = { 0 };
1081+        };
1082+
1083+        /**
1084+         * Redirects the actual `stdout`/`stderr` file descriptors.
1085+         *
1086+         * Works by replacing the file descriptors numbered 1 and 2
1087+         * with an open temporary file.
1088+         */
1089+        class FileRedirect : public OutputRedirect {
1090+            TempFile m_outFile, m_errFile;
1091+            int m_originalOut = -1;
1092+            int m_originalErr = -1;
1093+
1094+            // Flushes cout/cerr/clog streams and stdout/stderr FDs
1095+            void flushEverything() {
1096+                Catch::cout() << std::flush;
1097+                fflush( stdout );
1098+                // Since we support overriding these streams, we flush cerr
1099+                // even though std::cerr is unbuffered
1100+                Catch::cerr() << std::flush;
1101+                Catch::clog() << std::flush;
1102+                fflush( stderr );
1103             }
1104-            CATCH_RUNTIME_ERROR("Could not open the temp file: '" << m_buffer << "' because: " << buffer);
1105+
1106+        public:
1107+            FileRedirect():
1108+                m_originalOut( dup( fileno( stdout ) ) ),
1109+                m_originalErr( dup( fileno( stderr ) ) ) {
1110+                CATCH_ENFORCE( m_originalOut >= 0, "Could not dup stdout" );
1111+                CATCH_ENFORCE( m_originalErr >= 0, "Could not dup stderr" );
1112+            }
1113+
1114+            std::string getStdout() override { return m_outFile.getContents(); }
1115+            std::string getStderr() override { return m_errFile.getContents(); }
1116+            void clearBuffers() override {
1117+                m_outFile.clear();
1118+                m_errFile.clear();
1119+            }
1120+
1121+            void activateImpl() override {
1122+                // We flush before starting redirect, to ensure that we do
1123+                // not capture the end of message sent before activation.
1124+                flushEverything();
1125+
1126+                int ret;
1127+                ret = dup2( fileno( m_outFile.getFile() ), fileno( stdout ) );
1128+                CATCH_ENFORCE( ret >= 0,
1129+                               "dup2 to stdout has failed, errno: " << errno );
1130+                ret = dup2( fileno( m_errFile.getFile() ), fileno( stderr ) );
1131+                CATCH_ENFORCE( ret >= 0,
1132+                               "dup2 to stderr has failed, errno: " << errno );
1133+            }
1134+            void deactivateImpl() override {
1135+                // We flush before ending redirect, to ensure that we
1136+                // capture all messages sent while the redirect was active.
1137+                flushEverything();
1138+
1139+                int ret;
1140+                ret = dup2( m_originalOut, fileno( stdout ) );
1141+                CATCH_ENFORCE(
1142+                    ret >= 0,
1143+                    "dup2 of original stdout has failed, errno: " << errno );
1144+                ret = dup2( m_originalErr, fileno( stderr ) );
1145+                CATCH_ENFORCE(
1146+                    ret >= 0,
1147+                    "dup2 of original stderr has failed, errno: " << errno );
1148+            }
1149+        };
1150+
1151+#endif // CATCH_CONFIG_NEW_CAPTURE
1152+
1153+    } // end namespace
1154+
1155+    bool isRedirectAvailable( OutputRedirect::Kind kind ) {
1156+        switch ( kind ) {
1157+        // These two are always available
1158+        case OutputRedirect::None:
1159+        case OutputRedirect::Streams:
1160+            return true;
1161+#if defined( CATCH_CONFIG_NEW_CAPTURE )
1162+        case OutputRedirect::FileDescriptors:
1163+            return true;
1164+#endif
1165+        default:
1166+            return false;
1167         }
1168     }
1169+
1170+    Detail::unique_ptr<OutputRedirect> makeOutputRedirect( bool actual ) {
1171+        if ( actual ) {
1172+            // TODO: Clean this up later
1173+#if defined( CATCH_CONFIG_NEW_CAPTURE )
1174+            return Detail::make_unique<FileRedirect>();
1175 #else
1176-    TempFile::TempFile() {
1177-        m_file = std::tmpfile();
1178-        if (!m_file) {
1179-            CATCH_RUNTIME_ERROR("Could not create a temp file.");
1180+            return Detail::make_unique<StreamRedirect>();
1181+#endif
1182+        } else {
1183+            return Detail::make_unique<NoopRedirect>();
1184         }
1185     }
1186 
1187-#endif
1188+    RedirectGuard scopedActivate( OutputRedirect& redirectImpl ) {
1189+        return RedirectGuard( true, redirectImpl );
1190+    }
1191 
1192-    TempFile::~TempFile() {
1193-         // TBD: What to do about errors here?
1194-         std::fclose(m_file);
1195-         // We manually create the file on Windows only, on Linux
1196-         // it will be autodeleted
1197-#if defined(_MSC_VER)
1198-         std::remove(m_buffer);
1199-#endif
1200+    RedirectGuard scopedDeactivate( OutputRedirect& redirectImpl ) {
1201+        return RedirectGuard( false, redirectImpl );
1202     }
1203 
1204+    OutputRedirect::~OutputRedirect() = default;
1205 
1206-    FILE* TempFile::getFile() {
1207-        return m_file;
1208-    }
1209+    RedirectGuard::RedirectGuard( bool activate, OutputRedirect& redirectImpl ):
1210+        m_redirect( &redirectImpl ),
1211+        m_activate( activate ),
1212+        m_previouslyActive( redirectImpl.isActive() ) {
1213 
1214-    std::string TempFile::getContents() {
1215-        std::stringstream sstr;
1216-        char buffer[100] = {};
1217-        std::rewind(m_file);
1218-        while (std::fgets(buffer, sizeof(buffer), m_file)) {
1219-            sstr << buffer;
1220-        }
1221-        return sstr.str();
1222-    }
1223+        // Skip cases where there is no actual state change.
1224+        if ( m_activate == m_previouslyActive ) { return; }
1225 
1226-    OutputRedirect::OutputRedirect(std::string& stdout_dest, std::string& stderr_dest) :
1227-        m_originalStdout(dup(1)),
1228-        m_originalStderr(dup(2)),
1229-        m_stdoutDest(stdout_dest),
1230-        m_stderrDest(stderr_dest) {
1231-        dup2(fileno(m_stdoutFile.getFile()), 1);
1232-        dup2(fileno(m_stderrFile.getFile()), 2);
1233+        if ( m_activate ) {
1234+            m_redirect->activate();
1235+        } else {
1236+            m_redirect->deactivate();
1237+        }
1238     }
1239 
1240-    OutputRedirect::~OutputRedirect() {
1241-        Catch::cout() << std::flush;
1242-        fflush(stdout);
1243-        // Since we support overriding these streams, we flush cerr
1244-        // even though std::cerr is unbuffered
1245-        Catch::cerr() << std::flush;
1246-        Catch::clog() << std::flush;
1247-        fflush(stderr);
1248+    RedirectGuard::~RedirectGuard() noexcept( false ) {
1249+        if ( m_moved ) { return; }
1250+        // Skip cases where there is no actual state change.
1251+        if ( m_activate == m_previouslyActive ) { return; }
1252 
1253-        dup2(m_originalStdout, 1);
1254-        dup2(m_originalStderr, 2);
1255+        if ( m_activate ) {
1256+            m_redirect->deactivate();
1257+        } else {
1258+            m_redirect->activate();
1259+        }
1260+    }
1261 
1262-        m_stdoutDest += m_stdoutFile.getContents();
1263-        m_stderrDest += m_stderrFile.getContents();
1264+    RedirectGuard::RedirectGuard( RedirectGuard&& rhs ) noexcept:
1265+        m_redirect( rhs.m_redirect ),
1266+        m_activate( rhs.m_activate ),
1267+        m_previouslyActive( rhs.m_previouslyActive ),
1268+        m_moved( false ) {
1269+        rhs.m_moved = true;
1270     }
1271 
1272-#endif // CATCH_CONFIG_NEW_CAPTURE
1273+    RedirectGuard& RedirectGuard::operator=( RedirectGuard&& rhs ) noexcept {
1274+        m_redirect = rhs.m_redirect;
1275+        m_activate = rhs.m_activate;
1276+        m_previouslyActive = rhs.m_previouslyActive;
1277+        m_moved = false;
1278+        rhs.m_moved = true;
1279+        return *this;
1280+    }
1281 
1282 } // namespace Catch
1283 
1284-#if defined(CATCH_CONFIG_NEW_CAPTURE)
1285-    #if defined(_MSC_VER)
1286-    #undef dup
1287-    #undef dup2
1288-    #undef fileno
1289-    #endif
1290+#if defined( CATCH_CONFIG_NEW_CAPTURE )
1291+#    if defined( _MSC_VER )
1292+#        undef dup
1293+#        undef dup2
1294+#        undef fileno
1295+#    endif
1296 #endif
1297 
1298 
1299@@ -5022,6 +5309,12 @@ namespace Catch {
1300 
1301 
1302 
1303+#if defined( __clang__ )
1304+#    define CATCH2_CLANG_NO_SANITIZE_INTEGER \
1305+        __attribute__( ( no_sanitize( "unsigned-integer-overflow" ) ) )
1306+#else
1307+#    define CATCH2_CLANG_NO_SANITIZE_INTEGER
1308+#endif
1309 namespace Catch {
1310 
1311 namespace {
1312@@ -5031,6 +5324,7 @@ namespace {
1313 #pragma warning(disable:4146) // we negate uint32 during the rotate
1314 #endif
1315         // Safe rotr implementation thanks to John Regehr
1316+        CATCH2_CLANG_NO_SANITIZE_INTEGER
1317         uint32_t rotate_right(uint32_t val, uint32_t count) {
1318             const uint32_t mask = 31;
1319             count &= mask;
1320@@ -5064,10 +5358,11 @@ namespace {
1321         }
1322     }
1323 
1324+    CATCH2_CLANG_NO_SANITIZE_INTEGER
1325     SimplePcg32::result_type SimplePcg32::operator()() {
1326         // prepare the output value
1327         const uint32_t xorshifted = static_cast<uint32_t>(((m_state >> 18u) ^ m_state) >> 27u);
1328-        const auto output = rotate_right(xorshifted, m_state >> 59u);
1329+        const auto output = rotate_right(xorshifted, static_cast<uint32_t>(m_state >> 59u));
1330 
1331         // advance state
1332         m_state = m_state * 6364136223846793005ULL + s_inc;
1333@@ -5350,28 +5645,9 @@ ReporterSpec::ReporterSpec(
1334 
1335 
1336 
1337-namespace Catch {
1338-
1339-    bool isOk( ResultWas::OfType resultType ) {
1340-        return ( resultType & ResultWas::FailureBit ) == 0;
1341-    }
1342-    bool isJustInfo( int flags ) {
1343-        return flags == ResultWas::Info;
1344-    }
1345-
1346-    ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) {
1347-        return static_cast<ResultDisposition::Flags>( static_cast<int>( lhs ) | static_cast<int>( rhs ) );
1348-    }
1349-
1350-    bool shouldContinueOnFailure( int flags )    { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; }
1351-    bool shouldSuppressFailure( int flags )      { return ( flags & ResultDisposition::SuppressFail ) != 0; }
1352-
1353-} // end namespace Catch
1354-
1355-
1356-
1357 #include <cstdio>
1358 #include <sstream>
1359+#include <tuple>
1360 #include <vector>
1361 
1362 namespace Catch {
1363@@ -5381,34 +5657,40 @@ namespace Catch {
1364         std::vector<Detail::unique_ptr<std::ostringstream>> m_streams;
1365         std::vector<std::size_t> m_unused;
1366         std::ostringstream m_referenceStream; // Used for copy state/ flags from
1367+        Detail::Mutex m_mutex;
1368 
1369-        auto add() -> std::size_t {
1370+        auto add() -> std::pair<std::size_t, std::ostringstream*> {
1371+            Detail::LockGuard _( m_mutex );
1372             if( m_unused.empty() ) {
1373                 m_streams.push_back( Detail::make_unique<std::ostringstream>() );
1374-                return m_streams.size()-1;
1375+                return { m_streams.size()-1, m_streams.back().get() };
1376             }
1377             else {
1378                 auto index = m_unused.back();
1379                 m_unused.pop_back();
1380-                return index;
1381+                return { index, m_streams[index].get() };
1382             }
1383         }
1384 
1385-        void release( std::size_t index ) {
1386-            m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state
1387-            m_unused.push_back(index);
1388+        void release( std::size_t index, std::ostream* originalPtr ) {
1389+            assert( originalPtr );
1390+            originalPtr->copyfmt( m_referenceStream );  // Restore initial flags and other state
1391+
1392+            Detail::LockGuard _( m_mutex );
1393+            assert( originalPtr == m_streams[index].get() && "Mismatch between release index and stream ptr" );
1394+            m_unused.push_back( index );
1395         }
1396     };
1397 
1398-    ReusableStringStream::ReusableStringStream()
1399-    :   m_index( Singleton<StringStreams>::getMutable().add() ),
1400-        m_oss( Singleton<StringStreams>::getMutable().m_streams[m_index].get() )
1401-    {}
1402+    ReusableStringStream::ReusableStringStream() {
1403+        std::tie( m_index, m_oss ) =
1404+            Singleton<StringStreams>::getMutable().add();
1405+    }
1406 
1407     ReusableStringStream::~ReusableStringStream() {
1408         static_cast<std::ostringstream*>( m_oss )->str("");
1409         m_oss->clear();
1410-        Singleton<StringStreams>::getMutable().release( m_index );
1411+        Singleton<StringStreams>::getMutable().release( m_index, m_oss );
1412     }
1413 
1414     std::string ReusableStringStream::str() const {
1415@@ -5568,26 +5850,69 @@ namespace Catch {
1416         } // namespace
1417     }
1418 
1419+    namespace Detail {
1420+        // Assertions are owned by the thread that is executing them.
1421+        // This allows for lock-free progress in common cases where we
1422+        // do not need to send the assertion events to the reporter.
1423+        // This also implies that messages are owned by their respective
1424+        // threads, and should not be shared across different threads.
1425+        //
1426+        // This implies that various pieces of metadata referring to last
1427+        // assertion result/source location/message handling, etc
1428+        // should also be thread local. For now we just use naked globals
1429+        // below, in the future we will want to allocate piece of memory
1430+        // from heap, to avoid consuming too much thread-local storage.
1431+
1432+        // This is used for the "if" part of CHECKED_IF/CHECKED_ELSE
1433+        static thread_local bool g_lastAssertionPassed = false;
1434+
1435+        // This is the source location for last encountered macro. It is
1436+        // used to provide the users with more precise location of error
1437+        // when an unexpected exception/fatal error happens.
1438+        static thread_local SourceLineInfo g_lastKnownLineInfo("DummyLocation", static_cast<size_t>(-1));
1439+
1440+        // Should we clear message scopes before sending off the messages to
1441+        // reporter? Set in `assertionPassedFastPath` to avoid doing the full
1442+        // clear there for performance reasons.
1443+        static thread_local bool g_clearMessageScopes = false;
1444+
1445+        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION
1446+        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS
1447+        // Actual messages to be provided to the reporter
1448+        static thread_local std::vector<MessageInfo> g_messages;
1449+
1450+        // Owners for the UNSCOPED_X information macro
1451+        static thread_local std::vector<ScopedMessage> g_messageScopes;
1452+        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
1453+
1454+    } // namespace Detail
1455+
1456     RunContext::RunContext(IConfig const* _config, IEventListenerPtr&& reporter)
1457     :   m_runInfo(_config->name()),
1458         m_config(_config),
1459         m_reporter(CATCH_MOVE(reporter)),
1460-        m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal },
1461-        m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions )
1462+        m_outputRedirect( makeOutputRedirect( m_reporter->getPreferences().shouldRedirectStdOut ) ),
1463+        m_abortAfterXFailedAssertions( m_config->abortAfter() ),
1464+        m_reportAssertionStarting( m_reporter->getPreferences().shouldReportAllAssertionStarts ),
1465+        m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ),
1466+        m_shouldDebugBreak( m_config->shouldDebugBreak() )
1467     {
1468         getCurrentMutableContext().setResultCapture( this );
1469         m_reporter->testRunStarting(m_runInfo);
1470     }
1471 
1472     RunContext::~RunContext() {
1473+        updateTotalsFromAtomics();
1474         m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting()));
1475     }
1476 
1477     Totals RunContext::runTest(TestCaseHandle const& testCase) {
1478+        updateTotalsFromAtomics();
1479         const Totals prevTotals = m_totals;
1480 
1481         auto const& testInfo = testCase.getTestCaseInfo();
1482         m_reporter->testCaseStarting(testInfo);
1483+        testCase.prepareTestCase();
1484         m_activeTestCase = &testCase;
1485 
1486 
1487@@ -5637,16 +5962,20 @@ namespace Catch {
1488 
1489             m_reporter->testCasePartialStarting(testInfo, testRuns);
1490 
1491+            updateTotalsFromAtomics();
1492             const auto beforeRunTotals = m_totals;
1493-            std::string oneRunCout, oneRunCerr;
1494-            runCurrentTest(oneRunCout, oneRunCerr);
1495+            runCurrentTest();
1496+            std::string oneRunCout = m_outputRedirect->getStdout();
1497+            std::string oneRunCerr = m_outputRedirect->getStderr();
1498+            m_outputRedirect->clearBuffers();
1499             redirectedCout += oneRunCout;
1500             redirectedCerr += oneRunCerr;
1501 
1502+            updateTotalsFromAtomics();
1503             const auto singleRunTotals = m_totals.delta(beforeRunTotals);
1504             auto statsForOneRun = TestCaseStats(testInfo, singleRunTotals, CATCH_MOVE(oneRunCout), CATCH_MOVE(oneRunCerr), aborting());
1505-
1506             m_reporter->testCasePartialEnded(statsForOneRun, testRuns);
1507+
1508             ++testRuns;
1509         } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting());
1510 
1511@@ -5657,6 +5986,7 @@ namespace Catch {
1512             deltaTotals.testCases.failed++;
1513         }
1514         m_totals.testCases += deltaTotals.testCases;
1515+        testCase.tearDownTestCase();
1516         m_reporter->testCaseEnded(TestCaseStats(testInfo,
1517                                   deltaTotals,
1518                                   CATCH_MOVE(redirectedCout),
1519@@ -5671,43 +6001,54 @@ namespace Catch {
1520 
1521 
1522     void RunContext::assertionEnded(AssertionResult&& result) {
1523+        Detail::g_lastKnownLineInfo = result.m_info.lineInfo;
1524         if (result.getResultType() == ResultWas::Ok) {
1525-            m_totals.assertions.passed++;
1526-            m_lastAssertionPassed = true;
1527+            m_atomicAssertionCount.passed++;
1528+            Detail::g_lastAssertionPassed = true;
1529         } else if (result.getResultType() == ResultWas::ExplicitSkip) {
1530-            m_totals.assertions.skipped++;
1531-            m_lastAssertionPassed = true;
1532+            m_atomicAssertionCount.skipped++;
1533+            Detail::g_lastAssertionPassed = true;
1534         } else if (!result.succeeded()) {
1535-            m_lastAssertionPassed = false;
1536+            Detail::g_lastAssertionPassed = false;
1537             if (result.isOk()) {
1538             }
1539-            else if( m_activeTestCase->getTestCaseInfo().okToFail() )
1540-                m_totals.assertions.failedButOk++;
1541+            else if( m_activeTestCase->getTestCaseInfo().okToFail() ) // Read from a shared state established before the threads could start, this is fine
1542+                m_atomicAssertionCount.failedButOk++;
1543             else
1544-                m_totals.assertions.failed++;
1545+                m_atomicAssertionCount.failed++;
1546         }
1547         else {
1548-            m_lastAssertionPassed = true;
1549+            Detail::g_lastAssertionPassed = true;
1550+        }
1551+
1552+        if ( Detail::g_clearMessageScopes ) {
1553+            Detail::g_messageScopes.clear();
1554+            Detail::g_clearMessageScopes = false;
1555         }
1556 
1557-        m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals));
1558+        // From here, we are touching shared state and need mutex.
1559+        Detail::LockGuard lock( m_assertionMutex );
1560+        {
1561+            auto _ = scopedDeactivate( *m_outputRedirect );
1562+            updateTotalsFromAtomics();
1563+            m_reporter->assertionEnded( AssertionStats( result, Detail::g_messages, m_totals ) );
1564+        }
1565 
1566         if ( result.getResultType() != ResultWas::Warning ) {
1567-            m_messageScopes.clear();
1568+            Detail::g_messageScopes.clear();
1569         }
1570 
1571         // Reset working state. assertion info will be reset after
1572         // populateReaction is run if it is needed
1573         m_lastResult = CATCH_MOVE( result );
1574     }
1575-    void RunContext::resetAssertionInfo() {
1576-        m_lastAssertionInfo.macroName = StringRef();
1577-        m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr;
1578-        m_lastAssertionInfo.resultDisposition = ResultDisposition::Normal;
1579-    }
1580 
1581     void RunContext::notifyAssertionStarted( AssertionInfo const& info ) {
1582-        m_reporter->assertionStarting( info );
1583+        if (m_reportAssertionStarting) {
1584+            Detail::LockGuard lock( m_assertionMutex );
1585+            auto _ = scopedDeactivate( *m_outputRedirect );
1586+            m_reporter->assertionStarting( info );
1587+        }
1588     }
1589 
1590     bool RunContext::sectionStarted( StringRef sectionName,
1591@@ -5723,10 +6064,14 @@ namespace Catch {
1592         m_activeSections.push_back(&sectionTracker);
1593 
1594         SectionInfo sectionInfo( sectionLineInfo, static_cast<std::string>(sectionName) );
1595-        m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo;
1596+        Detail::g_lastKnownLineInfo = sectionLineInfo;
1597 
1598-        m_reporter->sectionStarting(sectionInfo);
1599+        {
1600+            auto _ = scopedDeactivate( *m_outputRedirect );
1601+            m_reporter->sectionStarting( sectionInfo );
1602+        }
1603 
1604+        updateTotalsFromAtomics();
1605         assertions = m_totals.assertions;
1606 
1607         return true;
1608@@ -5734,12 +6079,11 @@ namespace Catch {
1609     IGeneratorTracker*
1610     RunContext::acquireGeneratorTracker( StringRef generatorName,
1611                                          SourceLineInfo const& lineInfo ) {
1612-        using namespace Generators;
1613-        GeneratorTracker* tracker = GeneratorTracker::acquire(
1614+        auto* tracker = Generators::GeneratorTracker::acquire(
1615             m_trackerContext,
1616             TestCaseTracking::NameAndLocationRef(
1617                  generatorName, lineInfo ) );
1618-        m_lastAssertionInfo.lineInfo = lineInfo;
1619+        Detail::g_lastKnownLineInfo = lineInfo;
1620         return tracker;
1621     }
1622 
1623@@ -5752,7 +6096,7 @@ namespace Catch {
1624         auto& currentTracker = m_trackerContext.currentTracker();
1625         assert(
1626             currentTracker.nameAndLocation() != nameAndLoc &&
1627-            "Trying to create tracker for a genreator that already has one" );
1628+            "Trying to create tracker for a generator that already has one" );
1629 
1630         auto newTracker = Catch::Detail::make_unique<Generators::GeneratorTracker>(
1631             CATCH_MOVE(nameAndLoc), m_trackerContext, &currentTracker );
1632@@ -5771,12 +6115,13 @@ namespace Catch {
1633             return false;
1634         if (m_trackerContext.currentTracker().hasChildren())
1635             return false;
1636-        m_totals.assertions.failed++;
1637+        m_atomicAssertionCount.failed++;
1638         assertions.failed++;
1639         return true;
1640     }
1641 
1642     void RunContext::sectionEnded(SectionEndInfo&& endInfo) {
1643+        updateTotalsFromAtomics();
1644         Counts assertions = m_totals.assertions - endInfo.prevAssertions;
1645         bool missingAssertions = testForMissingAssertions(assertions);
1646 
1647@@ -5785,9 +6130,14 @@ namespace Catch {
1648             m_activeSections.pop_back();
1649         }
1650 
1651-        m_reporter->sectionEnded(SectionStats(CATCH_MOVE(endInfo.sectionInfo), assertions, endInfo.durationInSeconds, missingAssertions));
1652-        m_messages.clear();
1653-        m_messageScopes.clear();
1654+        {
1655+            auto _ = scopedDeactivate( *m_outputRedirect );
1656+            m_reporter->sectionEnded(
1657+                SectionStats( CATCH_MOVE( endInfo.sectionInfo ),
1658+                              assertions,
1659+                              endInfo.durationInSeconds,
1660+                              missingAssertions ) );
1661+        }
1662     }
1663 
1664     void RunContext::sectionEndedEarly(SectionEndInfo&& endInfo) {
1665@@ -5802,30 +6152,22 @@ namespace Catch {
1666     }
1667 
1668     void RunContext::benchmarkPreparing( StringRef name ) {
1669-        m_reporter->benchmarkPreparing(name);
1670+        auto _ = scopedDeactivate( *m_outputRedirect );
1671+        m_reporter->benchmarkPreparing( name );
1672     }
1673     void RunContext::benchmarkStarting( BenchmarkInfo const& info ) {
1674+        auto _ = scopedDeactivate( *m_outputRedirect );
1675         m_reporter->benchmarkStarting( info );
1676     }
1677     void RunContext::benchmarkEnded( BenchmarkStats<> const& stats ) {
1678+        auto _ = scopedDeactivate( *m_outputRedirect );
1679         m_reporter->benchmarkEnded( stats );
1680     }
1681     void RunContext::benchmarkFailed( StringRef error ) {
1682+        auto _ = scopedDeactivate( *m_outputRedirect );
1683         m_reporter->benchmarkFailed( error );
1684     }
1685 
1686-    void RunContext::pushScopedMessage(MessageInfo const & message) {
1687-        m_messages.push_back(message);
1688-    }
1689-
1690-    void RunContext::popScopedMessage(MessageInfo const & message) {
1691-        m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end());
1692-    }
1693-
1694-    void RunContext::emplaceUnscopedMessage( MessageBuilder&& builder ) {
1695-        m_messageScopes.emplace_back( CATCH_MOVE(builder) );
1696-    }
1697-
1698     std::string RunContext::getCurrentTestName() const {
1699         return m_activeTestCase
1700             ? m_activeTestCase->getTestCaseInfo().name
1701@@ -5833,6 +6175,18 @@ namespace Catch {
1702     }
1703 
1704     const AssertionResult * RunContext::getLastResult() const {
1705+        // m_lastResult is updated inside the assertion slow-path, under
1706+        // a mutex, so the read needs to happen under mutex as well.
1707+
1708+        // TBD: The last result only makes sense if it is a thread-local
1709+        //      thing, because the answer is different per thread, like
1710+        //      last line info, whether last assertion passed, and so on.
1711+        //
1712+        //      However, the last result was also never updated in the
1713+        //      assertion fast path, so it was always somewhat broken,
1714+        //      and since IResultCapture::getLastResult is deprecated,
1715+        //      we will leave it as is, until it is finally removed.
1716+        Detail::LockGuard _( m_assertionMutex );
1717         return &(*m_lastResult);
1718     }
1719 
1720@@ -5841,18 +6195,46 @@ namespace Catch {
1721     }
1722 
1723     void RunContext::handleFatalErrorCondition( StringRef message ) {
1724-        // First notify reporter that bad things happened
1725-        m_reporter->fatalErrorEncountered(message);
1726+        // We lock only when touching the reporters directly, to avoid
1727+        // deadlocks when we call into other functions that also want
1728+        // to lock the mutex before touching reporters.
1729+        //
1730+        // This does mean that we allow other threads to run while handling
1731+        // a fatal error, but this is all a best effort attempt anyway.
1732+        {
1733+            Detail::LockGuard lock( m_assertionMutex );
1734+            // TODO: scoped deactivate here? Just give up and do best effort?
1735+            //       the deactivation can break things further, OTOH so can the
1736+            //       capture
1737+            auto _ = scopedDeactivate( *m_outputRedirect );
1738+
1739+            // First notify reporter that bad things happened
1740+            m_reporter->fatalErrorEncountered( message );
1741+        }
1742 
1743         // Don't rebuild the result -- the stringification itself can cause more fatal errors
1744         // Instead, fake a result data.
1745         AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } );
1746         tempResult.message = static_cast<std::string>(message);
1747-        AssertionResult result(m_lastAssertionInfo, CATCH_MOVE(tempResult));
1748+        AssertionResult result( makeDummyAssertionInfo(),
1749+                                CATCH_MOVE( tempResult ) );
1750 
1751         assertionEnded(CATCH_MOVE(result) );
1752-        resetAssertionInfo();
1753 
1754+
1755+        // At this point we touch sections/test cases from this thread
1756+        // to try and end them. Technically that is not supported when
1757+        // using multiple threads, but the worst thing that can happen
1758+        // is that the process aborts harder :-D
1759+        Detail::LockGuard lock( m_assertionMutex );
1760+
1761+        // Best effort cleanup for sections that have not been destructed yet
1762+        // Since this is a fatal error, we have not had and won't have the opportunity to destruct them properly
1763+        while (!m_activeSections.empty()) {
1764+            auto const& nl = m_activeSections.back()->nameAndLocation();
1765+            SectionEndInfo endInfo{ SectionInfo(nl.location, nl.name), {}, 0.0 };
1766+            sectionEndedEarly(CATCH_MOVE(endInfo));
1767+        }
1768         handleUnfinishedSections();
1769 
1770         // Recreate section for test case (as we will lose the one that was in scope)
1771@@ -5862,7 +6244,7 @@ namespace Catch {
1772         Counts assertions;
1773         assertions.failed = 1;
1774         SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, 0, false);
1775-        m_reporter->sectionEnded(testCaseSectionStats);
1776+        m_reporter->sectionEnded( testCaseSectionStats );
1777 
1778         auto const& testInfo = m_activeTestCase->getTestCaseInfo();
1779 
1780@@ -5875,47 +6257,49 @@ namespace Catch {
1781                                   std::string(),
1782                                   false));
1783         m_totals.testCases.failed++;
1784+        updateTotalsFromAtomics();
1785         m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false));
1786     }
1787 
1788     bool RunContext::lastAssertionPassed() {
1789-         return m_lastAssertionPassed;
1790+        return Detail::g_lastAssertionPassed;
1791     }
1792 
1793-    void RunContext::assertionPassed() {
1794-        m_lastAssertionPassed = true;
1795-        ++m_totals.assertions.passed;
1796-        resetAssertionInfo();
1797-        m_messageScopes.clear();
1798+    void RunContext::assertionPassedFastPath(SourceLineInfo lineInfo) {
1799+        // We want to save the line info for better experience with unexpected assertions
1800+        Detail::g_lastKnownLineInfo = lineInfo;
1801+        ++m_atomicAssertionCount.passed;
1802+        Detail::g_lastAssertionPassed = true;
1803+        Detail::g_clearMessageScopes = true;
1804+    }
1805+
1806+    void RunContext::updateTotalsFromAtomics() {
1807+        m_totals.assertions = Counts{
1808+            m_atomicAssertionCount.passed,
1809+            m_atomicAssertionCount.failed,
1810+            m_atomicAssertionCount.failedButOk,
1811+            m_atomicAssertionCount.skipped,
1812+        };
1813     }
1814 
1815     bool RunContext::aborting() const {
1816-        return m_totals.assertions.failed >= static_cast<std::size_t>(m_config->abortAfter());
1817+        return m_atomicAssertionCount.failed >= m_abortAfterXFailedAssertions;
1818     }
1819 
1820-    void RunContext::runCurrentTest(std::string & redirectedCout, std::string & redirectedCerr) {
1821+    void RunContext::runCurrentTest() {
1822         auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo();
1823         SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name);
1824         m_reporter->sectionStarting(testCaseSection);
1825+        updateTotalsFromAtomics();
1826         Counts prevAssertions = m_totals.assertions;
1827         double duration = 0;
1828         m_shouldReportUnexpected = true;
1829-        m_lastAssertionInfo = { "TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal };
1830+        Detail::g_lastKnownLineInfo = testCaseInfo.lineInfo;
1831 
1832         Timer timer;
1833         CATCH_TRY {
1834-            if (m_reporter->getPreferences().shouldRedirectStdOut) {
1835-#if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT)
1836-                RedirectedStreams redirectedStreams(redirectedCout, redirectedCerr);
1837-
1838-                timer.start();
1839-                invokeActiveTestCase();
1840-#else
1841-                OutputRedirect r(redirectedCout, redirectedCerr);
1842-                timer.start();
1843-                invokeActiveTestCase();
1844-#endif
1845-            } else {
1846+            {
1847+                auto _ = scopedActivate( *m_outputRedirect );
1848                 timer.start();
1849                 invokeActiveTestCase();
1850             }
1851@@ -5927,18 +6311,23 @@ namespace Catch {
1852         } CATCH_CATCH_ALL {
1853             // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions
1854             // are reported without translation at the point of origin.
1855-            if( m_shouldReportUnexpected ) {
1856+            if ( m_shouldReportUnexpected ) {
1857                 AssertionReaction dummyReaction;
1858-                handleUnexpectedInflightException( m_lastAssertionInfo, translateActiveException(), dummyReaction );
1859+                handleUnexpectedInflightException( makeDummyAssertionInfo(),
1860+                                                   translateActiveException(),
1861+                                                   dummyReaction );
1862             }
1863         }
1864+        updateTotalsFromAtomics();
1865         Counts assertions = m_totals.assertions - prevAssertions;
1866         bool missingAssertions = testForMissingAssertions(assertions);
1867 
1868         m_testCaseTracker->close();
1869         handleUnfinishedSections();
1870-        m_messages.clear();
1871-        m_messageScopes.clear();
1872+        Detail::g_messageScopes.clear();
1873+        // TBD: At this point, m_messages should be empty. Do we want to
1874+        //      assert that this is true, or keep the defensive clear call?
1875+        Detail::g_messages.clear();
1876 
1877         SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, duration, missingAssertions);
1878         m_reporter->sectionEnded(testCaseSectionStats);
1879@@ -5960,11 +6349,12 @@ namespace Catch {
1880     void RunContext::handleUnfinishedSections() {
1881         // If sections ended prematurely due to an exception we stored their
1882         // infos here so we can tear them down outside the unwind process.
1883-        for (auto it = m_unfinishedSections.rbegin(),
1884-             itEnd = m_unfinishedSections.rend();
1885-             it != itEnd;
1886-             ++it)
1887-            sectionEnded(CATCH_MOVE(*it));
1888+        for ( auto it = m_unfinishedSections.rbegin(),
1889+                   itEnd = m_unfinishedSections.rend();
1890+              it != itEnd;
1891+              ++it ) {
1892+            sectionEnded( CATCH_MOVE( *it ) );
1893+        }
1894         m_unfinishedSections.clear();
1895     }
1896 
1897@@ -5978,7 +6368,7 @@ namespace Catch {
1898 
1899         if( result ) {
1900             if (!m_includeSuccessfulResults) {
1901-                assertionPassed();
1902+                assertionPassedFastPath(info.lineInfo);
1903             }
1904             else {
1905                 reportExpr(info, ResultWas::Ok, &expr, negated);
1906@@ -5986,9 +6376,9 @@ namespace Catch {
1907         }
1908         else {
1909             reportExpr(info, ResultWas::ExpressionFailed, &expr, negated );
1910-            populateReaction( reaction );
1911+            populateReaction(
1912+                reaction, info.resultDisposition & ResultDisposition::Normal );
1913         }
1914-        resetAssertionInfo();
1915     }
1916     void RunContext::reportExpr(
1917             AssertionInfo const &info,
1918@@ -5996,7 +6386,7 @@ namespace Catch {
1919             ITransientExpression const *expr,
1920             bool negated ) {
1921 
1922-        m_lastAssertionInfo = info;
1923+        Detail::g_lastKnownLineInfo = info.lineInfo;
1924         AssertionResultData data( resultType, LazyExpression( negated ) );
1925 
1926         AssertionResult assertionResult{ info, CATCH_MOVE( data ) };
1927@@ -6008,27 +6398,28 @@ namespace Catch {
1928     void RunContext::handleMessage(
1929             AssertionInfo const& info,
1930             ResultWas::OfType resultType,
1931-            StringRef message,
1932+            std::string&& message,
1933             AssertionReaction& reaction
1934     ) {
1935-        m_lastAssertionInfo = info;
1936+        Detail::g_lastKnownLineInfo = info.lineInfo;
1937 
1938         AssertionResultData data( resultType, LazyExpression( false ) );
1939-        data.message = static_cast<std::string>(message);
1940-        AssertionResult assertionResult{ m_lastAssertionInfo,
1941+        data.message = CATCH_MOVE( message );
1942+        AssertionResult assertionResult{ info,
1943                                          CATCH_MOVE( data ) };
1944 
1945         const auto isOk = assertionResult.isOk();
1946         assertionEnded( CATCH_MOVE(assertionResult) );
1947         if ( !isOk ) {
1948-            populateReaction( reaction );
1949+            populateReaction(
1950+                reaction, info.resultDisposition & ResultDisposition::Normal );
1951         } else if ( resultType == ResultWas::ExplicitSkip ) {
1952             // TODO: Need to handle this explicitly, as ExplicitSkip is
1953             // considered "OK"
1954             reaction.shouldSkip = true;
1955         }
1956-        resetAssertionInfo();
1957     }
1958+
1959     void RunContext::handleUnexpectedExceptionNotThrown(
1960             AssertionInfo const& info,
1961             AssertionReaction& reaction
1962@@ -6041,55 +6432,88 @@ namespace Catch {
1963             std::string&& message,
1964             AssertionReaction& reaction
1965     ) {
1966-        m_lastAssertionInfo = info;
1967+        Detail::g_lastKnownLineInfo = info.lineInfo;
1968 
1969         AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) );
1970         data.message = CATCH_MOVE(message);
1971         AssertionResult assertionResult{ info, CATCH_MOVE(data) };
1972         assertionEnded( CATCH_MOVE(assertionResult) );
1973-        populateReaction( reaction );
1974-        resetAssertionInfo();
1975+        populateReaction( reaction,
1976+                          info.resultDisposition & ResultDisposition::Normal );
1977     }
1978 
1979-    void RunContext::populateReaction( AssertionReaction& reaction ) {
1980-        reaction.shouldDebugBreak = m_config->shouldDebugBreak();
1981-        reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal);
1982+    void RunContext::populateReaction( AssertionReaction& reaction,
1983+                                       bool has_normal_disposition ) {
1984+        reaction.shouldDebugBreak = m_shouldDebugBreak;
1985+        reaction.shouldThrow = aborting() || has_normal_disposition;
1986+    }
1987+
1988+    AssertionInfo RunContext::makeDummyAssertionInfo() {
1989+        const bool testCaseJustStarted =
1990+            Detail::g_lastKnownLineInfo ==
1991+            m_activeTestCase->getTestCaseInfo().lineInfo;
1992+
1993+        return AssertionInfo{
1994+            testCaseJustStarted ? "TEST_CASE"_sr : StringRef(),
1995+            Detail::g_lastKnownLineInfo,
1996+            testCaseJustStarted ? StringRef() : "{Unknown expression after the reported line}"_sr,
1997+            ResultDisposition::Normal
1998+        };
1999     }
2000 
2001     void RunContext::handleIncomplete(
2002             AssertionInfo const& info
2003     ) {
2004         using namespace std::string_literals;
2005-        m_lastAssertionInfo = info;
2006+        Detail::g_lastKnownLineInfo = info.lineInfo;
2007 
2008         AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) );
2009         data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"s;
2010         AssertionResult assertionResult{ info, CATCH_MOVE( data ) };
2011         assertionEnded( CATCH_MOVE(assertionResult) );
2012-        resetAssertionInfo();
2013     }
2014+
2015     void RunContext::handleNonExpr(
2016             AssertionInfo const &info,
2017             ResultWas::OfType resultType,
2018             AssertionReaction &reaction
2019     ) {
2020-        m_lastAssertionInfo = info;
2021-
2022         AssertionResultData data( resultType, LazyExpression( false ) );
2023         AssertionResult assertionResult{ info, CATCH_MOVE( data ) };
2024 
2025         const auto isOk = assertionResult.isOk();
2026+        if ( isOk && !m_includeSuccessfulResults ) {
2027+            assertionPassedFastPath( info.lineInfo );
2028+            return;
2029+        }
2030+
2031         assertionEnded( CATCH_MOVE(assertionResult) );
2032-        if ( !isOk ) { populateReaction( reaction ); }
2033-        resetAssertionInfo();
2034+        if ( !isOk ) {
2035+            populateReaction(
2036+                reaction, info.resultDisposition & ResultDisposition::Normal );
2037+        }
2038     }
2039 
2040+    void IResultCapture::pushScopedMessage( MessageInfo&& message ) {
2041+        Detail::g_messages.push_back( CATCH_MOVE( message ) );
2042+    }
2043 
2044-    IResultCapture& getResultCapture() {
2045-        if (auto* capture = getCurrentContext().getResultCapture())
2046-            return *capture;
2047-        else
2048-            CATCH_INTERNAL_ERROR("No result capture instance");
2049+    void IResultCapture::popScopedMessage( unsigned int messageId ) {
2050+        // Note: On average, it would probably be better to look for the message
2051+        //       backwards. However, we do not expect to have to deal with more
2052+        //       messages than low single digits, so the optimization is tiny,
2053+        //       and we would have to hand-write the loop to avoid terrible
2054+        //       codegen of reverse iterators in debug mode.
2055+        Detail::g_messages.erase( std::find_if( Detail::g_messages.begin(),
2056+                                                Detail::g_messages.end(),
2057+                                                [=]( MessageInfo const& msg ) {
2058+                                                    return msg.sequence ==
2059+                                                           messageId;
2060+                                                } ) );
2061+    }
2062+
2063+    void IResultCapture::emplaceUnscopedMessage( MessageBuilder&& builder ) {
2064+        Detail::g_messageScopes.emplace_back( CATCH_MOVE( builder ) );
2065     }
2066 
2067     void seedRng(IConfig const& config) {
2068@@ -6315,7 +6739,7 @@ namespace Catch {
2069         std::string origStr = CATCH_MOVE(str);
2070         str.clear();
2071         // There is at least one replacement, so reserve with the best guess
2072-        // we can make without actually counting the number of occurences.
2073+        // we can make without actually counting the number of occurrences.
2074         str.reserve(origStr.size() - replaceThis.size() + withThis.size());
2075         do {
2076             str.append(origStr, copyBegin, i-copyBegin );
2077@@ -6361,7 +6785,6 @@ namespace Catch {
2078 #include <algorithm>
2079 #include <ostream>
2080 #include <cstring>
2081-#include <cstdint>
2082 
2083 namespace Catch {
2084     StringRef::StringRef( char const* rawChars ) noexcept
2085@@ -6891,6 +7314,8 @@ namespace Catch {
2086 #include <iterator>
2087 
2088 namespace Catch {
2089+    void ITestInvoker::prepareTestCase() {}
2090+    void ITestInvoker::tearDownTestCase() {}
2091     ITestInvoker::~ITestInvoker() = default;
2092 
2093     namespace {
2094@@ -6927,7 +7352,7 @@ namespace Catch {
2095             TestType m_testAsFunction;
2096 
2097         public:
2098-            TestInvokerAsFunction( TestType testAsFunction ) noexcept:
2099+            constexpr TestInvokerAsFunction( TestType testAsFunction ) noexcept:
2100                 m_testAsFunction( testAsFunction ) {}
2101 
2102             void invoke() const override { m_testAsFunction(); }
2103@@ -7207,117 +7632,228 @@ namespace {
2104         return std::memchr( chars, c, sizeof( chars ) - 1 ) != nullptr;
2105     }
2106 
2107-    bool isBoundary( std::string const& line, size_t at ) {
2108-        assert( at > 0 );
2109-        assert( at <= line.size() );
2110-
2111-        return at == line.size() ||
2112-               ( isWhitespace( line[at] ) && !isWhitespace( line[at - 1] ) ) ||
2113-               isBreakableBefore( line[at] ) ||
2114-               isBreakableAfter( line[at - 1] );
2115-    }
2116-
2117 } // namespace
2118 
2119 namespace Catch {
2120     namespace TextFlow {
2121+        void AnsiSkippingString::preprocessString() {
2122+            for ( auto it = m_string.begin(); it != m_string.end(); ) {
2123+                // try to read through an ansi sequence
2124+                while ( it != m_string.end() && *it == '\033' &&
2125+                        it + 1 != m_string.end() && *( it + 1 ) == '[' ) {
2126+                    auto cursor = it + 2;
2127+                    while ( cursor != m_string.end() &&
2128+                            ( isdigit( *cursor ) || *cursor == ';' ) ) {
2129+                        ++cursor;
2130+                    }
2131+                    if ( cursor == m_string.end() || *cursor != 'm' ) {
2132+                        break;
2133+                    }
2134+                    // 'm' -> 0xff
2135+                    *cursor = AnsiSkippingString::sentinel;
2136+                    // if we've read an ansi sequence, set the iterator and
2137+                    // return to the top of the loop
2138+                    it = cursor + 1;
2139+                }
2140+                if ( it != m_string.end() ) {
2141+                    ++m_size;
2142+                    ++it;
2143+                }
2144+            }
2145+        }
2146+
2147+        AnsiSkippingString::AnsiSkippingString( std::string const& text ):
2148+            m_string( text ) {
2149+            preprocessString();
2150+        }
2151+
2152+        AnsiSkippingString::AnsiSkippingString( std::string&& text ):
2153+            m_string( CATCH_MOVE( text ) ) {
2154+            preprocessString();
2155+        }
2156+
2157+        AnsiSkippingString::const_iterator AnsiSkippingString::begin() const {
2158+            return const_iterator( m_string );
2159+        }
2160+
2161+        AnsiSkippingString::const_iterator AnsiSkippingString::end() const {
2162+            return const_iterator( m_string, const_iterator::EndTag{} );
2163+        }
2164+
2165+        std::string AnsiSkippingString::substring( const_iterator begin,
2166+                                                   const_iterator end ) const {
2167+            // There's one caveat here to an otherwise simple substring: when
2168+            // making a begin iterator we might have skipped ansi sequences at
2169+            // the start. If `begin` here is a begin iterator, skipped over
2170+            // initial ansi sequences, we'll use the true beginning of the
2171+            // string. Lastly: We need to transform any chars we replaced with
2172+            // 0xff back to 'm'
2173+            auto str = std::string( begin == this->begin() ? m_string.begin()
2174+                                                           : begin.m_it,
2175+                                    end.m_it );
2176+            std::transform( str.begin(), str.end(), str.begin(), []( char c ) {
2177+                return c == AnsiSkippingString::sentinel ? 'm' : c;
2178+            } );
2179+            return str;
2180+        }
2181+
2182+        void AnsiSkippingString::const_iterator::tryParseAnsiEscapes() {
2183+            // check if we've landed on an ansi sequence, and if so read through
2184+            // it
2185+            while ( m_it != m_string->end() && *m_it == '\033' &&
2186+                    m_it + 1 != m_string->end() &&  *( m_it + 1 ) == '[' ) {
2187+                auto cursor = m_it + 2;
2188+                while ( cursor != m_string->end() &&
2189+                        ( isdigit( *cursor ) || *cursor == ';' ) ) {
2190+                    ++cursor;
2191+                }
2192+                if ( cursor == m_string->end() ||
2193+                     *cursor != AnsiSkippingString::sentinel ) {
2194+                    break;
2195+                }
2196+                // if we've read an ansi sequence, set the iterator and
2197+                // return to the top of the loop
2198+                m_it = cursor + 1;
2199+            }
2200+        }
2201+
2202+        void AnsiSkippingString::const_iterator::advance() {
2203+            assert( m_it != m_string->end() );
2204+            m_it++;
2205+            tryParseAnsiEscapes();
2206+        }
2207+
2208+        void AnsiSkippingString::const_iterator::unadvance() {
2209+            assert( m_it != m_string->begin() );
2210+            m_it--;
2211+            // if *m_it is 0xff, scan back to the \033 and then m_it-- once more
2212+            // (and repeat check)
2213+            while ( *m_it == AnsiSkippingString::sentinel ) {
2214+                while ( *m_it != '\033' ) {
2215+                    assert( m_it != m_string->begin() );
2216+                    m_it--;
2217+                }
2218+                // if this happens, we must have been a begin iterator that had
2219+                // skipped over ansi sequences at the start of a string
2220+                assert( m_it != m_string->begin() );
2221+                assert( *m_it == '\033' );
2222+                m_it--;
2223+            }
2224+        }
2225+
2226+        static bool isBoundary( AnsiSkippingString const& line,
2227+                                AnsiSkippingString::const_iterator it ) {
2228+            return it == line.end() ||
2229+                   ( isWhitespace( *it ) &&
2230+                     !isWhitespace( *it.oneBefore() ) ) ||
2231+                   isBreakableBefore( *it ) ||
2232+                   isBreakableAfter( *it.oneBefore() );
2233+        }
2234 
2235         void Column::const_iterator::calcLength() {
2236             m_addHyphen = false;
2237             m_parsedTo = m_lineStart;
2238+            AnsiSkippingString const& current_line = m_column.m_string;
2239 
2240-            std::string const& current_line = m_column.m_string;
2241-            if ( current_line[m_lineStart] == '\n' ) {
2242-                ++m_parsedTo;
2243+            if ( m_parsedTo == current_line.end() ) {
2244+                m_lineEnd = m_parsedTo;
2245+                return;
2246             }
2247 
2248+            assert( m_lineStart != current_line.end() );
2249+            if ( *m_lineStart == '\n' ) { ++m_parsedTo; }
2250+
2251             const auto maxLineLength = m_column.m_width - indentSize();
2252-            const auto maxParseTo = std::min(current_line.size(), m_lineStart + maxLineLength);
2253-            while ( m_parsedTo < maxParseTo &&
2254-                    current_line[m_parsedTo] != '\n' ) {
2255+            std::size_t lineLength = 0;
2256+            while ( m_parsedTo != current_line.end() &&
2257+                    lineLength < maxLineLength && *m_parsedTo != '\n' ) {
2258                 ++m_parsedTo;
2259+                ++lineLength;
2260             }
2261 
2262             // If we encountered a newline before the column is filled,
2263             // then we linebreak at the newline and consider this line
2264             // finished.
2265-            if ( m_parsedTo < m_lineStart + maxLineLength ) {
2266-                m_lineLength = m_parsedTo - m_lineStart;
2267+            if ( lineLength < maxLineLength ) {
2268+                m_lineEnd = m_parsedTo;
2269             } else {
2270                 // Look for a natural linebreak boundary in the column
2271                 // (We look from the end, so that the first found boundary is
2272                 // the right one)
2273-                size_t newLineLength = maxLineLength;
2274-                while ( newLineLength > 0 && !isBoundary( current_line, m_lineStart + newLineLength ) ) {
2275-                    --newLineLength;
2276+                m_lineEnd = m_parsedTo;
2277+                while ( lineLength > 0 &&
2278+                        !isBoundary( current_line, m_lineEnd ) ) {
2279+                    --lineLength;
2280+                    --m_lineEnd;
2281                 }
2282-                while ( newLineLength > 0 &&
2283-                        isWhitespace( current_line[m_lineStart + newLineLength - 1] ) ) {
2284-                    --newLineLength;
2285+                while ( lineLength > 0 &&
2286+                        isWhitespace( *m_lineEnd.oneBefore() ) ) {
2287+                    --lineLength;
2288+                    --m_lineEnd;
2289                 }
2290 
2291-                // If we found one, then that is where we linebreak
2292-                if ( newLineLength > 0 ) {
2293-                    m_lineLength = newLineLength;
2294-                } else {
2295-                    // Otherwise we have to split text with a hyphen
2296+                // If we found one, then that is where we linebreak, otherwise
2297+                // we have to split text with a hyphen
2298+                if ( lineLength == 0 ) {
2299                     m_addHyphen = true;
2300-                    m_lineLength = maxLineLength - 1;
2301+                    m_lineEnd = m_parsedTo.oneBefore();
2302                 }
2303             }
2304         }
2305 
2306         size_t Column::const_iterator::indentSize() const {
2307-            auto initial =
2308-                m_lineStart == 0 ? m_column.m_initialIndent : std::string::npos;
2309+            auto initial = m_lineStart == m_column.m_string.begin()
2310+                               ? m_column.m_initialIndent
2311+                               : std::string::npos;
2312             return initial == std::string::npos ? m_column.m_indent : initial;
2313         }
2314 
2315-        std::string
2316-        Column::const_iterator::addIndentAndSuffix( size_t position,
2317-                                              size_t length ) const {
2318+        std::string Column::const_iterator::addIndentAndSuffix(
2319+            AnsiSkippingString::const_iterator start,
2320+            AnsiSkippingString::const_iterator end ) const {
2321             std::string ret;
2322             const auto desired_indent = indentSize();
2323-            ret.reserve( desired_indent + length + m_addHyphen );
2324+            // ret.reserve( desired_indent + (end - start) + m_addHyphen );
2325             ret.append( desired_indent, ' ' );
2326-            ret.append( m_column.m_string, position, length );
2327-            if ( m_addHyphen ) {
2328-                ret.push_back( '-' );
2329-            }
2330+            // ret.append( start, end );
2331+            ret += m_column.m_string.substring( start, end );
2332+            if ( m_addHyphen ) { ret.push_back( '-' ); }
2333 
2334             return ret;
2335         }
2336 
2337-        Column::const_iterator::const_iterator( Column const& column ): m_column( column ) {
2338+        Column::const_iterator::const_iterator( Column const& column ):
2339+            m_column( column ),
2340+            m_lineStart( column.m_string.begin() ),
2341+            m_lineEnd( column.m_string.begin() ),
2342+            m_parsedTo( column.m_string.begin() ) {
2343             assert( m_column.m_width > m_column.m_indent );
2344             assert( m_column.m_initialIndent == std::string::npos ||
2345                     m_column.m_width > m_column.m_initialIndent );
2346             calcLength();
2347-            if ( m_lineLength == 0 ) {
2348-                m_lineStart = m_column.m_string.size();
2349+            if ( m_lineStart == m_lineEnd ) {
2350+                m_lineStart = m_column.m_string.end();
2351             }
2352         }
2353 
2354         std::string Column::const_iterator::operator*() const {
2355             assert( m_lineStart <= m_parsedTo );
2356-            return addIndentAndSuffix( m_lineStart, m_lineLength );
2357+            return addIndentAndSuffix( m_lineStart, m_lineEnd );
2358         }
2359 
2360         Column::const_iterator& Column::const_iterator::operator++() {
2361-            m_lineStart += m_lineLength;
2362-            std::string const& current_line = m_column.m_string;
2363-            if ( m_lineStart < current_line.size() && current_line[m_lineStart] == '\n' ) {
2364-                m_lineStart += 1;
2365+            m_lineStart = m_lineEnd;
2366+            AnsiSkippingString const& current_line = m_column.m_string;
2367+            if ( m_lineStart != current_line.end() && *m_lineStart == '\n' ) {
2368+                m_lineStart++;
2369             } else {
2370-                while ( m_lineStart < current_line.size() &&
2371-                        isWhitespace( current_line[m_lineStart] ) ) {
2372+                while ( m_lineStart != current_line.end() &&
2373+                        isWhitespace( *m_lineStart ) ) {
2374                     ++m_lineStart;
2375                 }
2376             }
2377 
2378-            if ( m_lineStart != current_line.size() ) {
2379-                calcLength();
2380-            }
2381+            if ( m_lineStart != current_line.end() ) { calcLength(); }
2382             return *this;
2383         }
2384 
2385@@ -7414,25 +7950,25 @@ namespace Catch {
2386             return os;
2387         }
2388 
2389-        Columns operator+(Column const& lhs, Column const& rhs) {
2390+        Columns operator+( Column const& lhs, Column const& rhs ) {
2391             Columns cols;
2392             cols += lhs;
2393             cols += rhs;
2394             return cols;
2395         }
2396-        Columns operator+(Column&& lhs, Column&& rhs) {
2397+        Columns operator+( Column&& lhs, Column&& rhs ) {
2398             Columns cols;
2399             cols += CATCH_MOVE( lhs );
2400             cols += CATCH_MOVE( rhs );
2401             return cols;
2402         }
2403 
2404-        Columns& operator+=(Columns& lhs, Column const& rhs) {
2405+        Columns& operator+=( Columns& lhs, Column const& rhs ) {
2406             lhs.m_columns.push_back( rhs );
2407             return lhs;
2408         }
2409-        Columns& operator+=(Columns& lhs, Column&& rhs) {
2410-            lhs.m_columns.push_back( CATCH_MOVE(rhs) );
2411+        Columns& operator+=( Columns& lhs, Column&& rhs ) {
2412+            lhs.m_columns.push_back( CATCH_MOVE( rhs ) );
2413             return lhs;
2414         }
2415         Columns operator+( Columns const& lhs, Column const& rhs ) {
2416@@ -7545,134 +8081,130 @@ namespace {
2417 
2418     void hexEscapeChar(std::ostream& os, unsigned char c) {
2419         std::ios_base::fmtflags f(os.flags());
2420-        os << "\\x"
2421+        os << "\\x"_sr
2422             << std::uppercase << std::hex << std::setfill('0') << std::setw(2)
2423             << static_cast<int>(c);
2424         os.flags(f);
2425     }
2426 
2427-    bool shouldNewline(XmlFormatting fmt) {
2428+    constexpr bool shouldNewline(XmlFormatting fmt) {
2429         return !!(static_cast<std::underlying_type_t<XmlFormatting>>(fmt & XmlFormatting::Newline));
2430     }
2431 
2432-    bool shouldIndent(XmlFormatting fmt) {
2433+    constexpr bool shouldIndent(XmlFormatting fmt) {
2434         return !!(static_cast<std::underlying_type_t<XmlFormatting>>(fmt & XmlFormatting::Indent));
2435     }
2436 
2437 } // anonymous namespace
2438 
2439-    XmlFormatting operator | (XmlFormatting lhs, XmlFormatting rhs) {
2440-        return static_cast<XmlFormatting>(
2441-            static_cast<std::underlying_type_t<XmlFormatting>>(lhs) |
2442-            static_cast<std::underlying_type_t<XmlFormatting>>(rhs)
2443-        );
2444-    }
2445-
2446-    XmlFormatting operator & (XmlFormatting lhs, XmlFormatting rhs) {
2447-        return static_cast<XmlFormatting>(
2448-            static_cast<std::underlying_type_t<XmlFormatting>>(lhs) &
2449-            static_cast<std::underlying_type_t<XmlFormatting>>(rhs)
2450-        );
2451-    }
2452-
2453-
2454-    XmlEncode::XmlEncode( StringRef str, ForWhat forWhat )
2455-    :   m_str( str ),
2456-        m_forWhat( forWhat )
2457-    {}
2458-
2459     void XmlEncode::encodeTo( std::ostream& os ) const {
2460         // Apostrophe escaping not necessary if we always use " to write attributes
2461         // (see: http://www.w3.org/TR/xml/#syntax)
2462+        size_t last_start = 0;
2463+        auto write_to = [&]( size_t idx ) {
2464+            if ( last_start < idx ) {
2465+                os << m_str.substr( last_start, idx - last_start );
2466+            }
2467+            last_start = idx + 1;
2468+        };
2469 
2470-        for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) {
2471-            unsigned char c = static_cast<unsigned char>(m_str[idx]);
2472-            switch (c) {
2473-            case '<':   os << "&lt;"; break;
2474-            case '&':   os << "&amp;"; break;
2475+        for ( std::size_t idx = 0; idx < m_str.size(); ++idx ) {
2476+            unsigned char c = static_cast<unsigned char>( m_str[idx] );
2477+            switch ( c ) {
2478+            case '<':
2479+                write_to( idx );
2480+                os << "&lt;"_sr;
2481+                break;
2482+            case '&':
2483+                write_to( idx );
2484+                os << "&amp;"_sr;
2485+                break;
2486 
2487             case '>':
2488                 // See: http://www.w3.org/TR/xml/#syntax
2489-                if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']')
2490-                    os << "&gt;";
2491-                else
2492-                    os << c;
2493+                if ( idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']' ) {
2494+                    write_to( idx );
2495+                    os << "&gt;"_sr;
2496+                }
2497                 break;
2498 
2499             case '\"':
2500-                if (m_forWhat == ForAttributes)
2501-                    os << "&quot;";
2502-                else
2503-                    os << c;
2504+                if ( m_forWhat == ForAttributes ) {
2505+                    write_to( idx );
2506+                    os << "&quot;"_sr;
2507+                }
2508                 break;
2509 
2510             default:
2511                 // Check for control characters and invalid utf-8
2512 
2513                 // Escape control characters in standard ascii
2514-                // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0
2515-                if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) {
2516-                    hexEscapeChar(os, c);
2517+                // see
2518+                // http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0
2519+                if ( c < 0x09 || ( c > 0x0D && c < 0x20 ) || c == 0x7F ) {
2520+                    write_to( idx );
2521+                    hexEscapeChar( os, c );
2522                     break;
2523                 }
2524 
2525                 // Plain ASCII: Write it to stream
2526-                if (c < 0x7F) {
2527-                    os << c;
2528+                if ( c < 0x7F ) {
2529                     break;
2530                 }
2531 
2532                 // UTF-8 territory
2533-                // Check if the encoding is valid and if it is not, hex escape bytes.
2534-                // Important: We do not check the exact decoded values for validity, only the encoding format
2535-                // First check that this bytes is a valid lead byte:
2536-                // This means that it is not encoded as 1111 1XXX
2537+                // Check if the encoding is valid and if it is not, hex escape
2538+                // bytes. Important: We do not check the exact decoded values for
2539+                // validity, only the encoding format First check that this bytes is
2540+                // a valid lead byte: This means that it is not encoded as 1111 1XXX
2541                 // Or as 10XX XXXX
2542-                if (c <  0xC0 ||
2543-                    c >= 0xF8) {
2544-                    hexEscapeChar(os, c);
2545+                if ( c < 0xC0 || c >= 0xF8 ) {
2546+                    write_to( idx );
2547+                    hexEscapeChar( os, c );
2548                     break;
2549                 }
2550 
2551-                auto encBytes = trailingBytes(c);
2552-                // Are there enough bytes left to avoid accessing out-of-bounds memory?
2553-                if (idx + encBytes - 1 >= m_str.size()) {
2554-                    hexEscapeChar(os, c);
2555+                auto encBytes = trailingBytes( c );
2556+                // Are there enough bytes left to avoid accessing out-of-bounds
2557+                // memory?
2558+                if ( idx + encBytes - 1 >= m_str.size() ) {
2559+                    write_to( idx );
2560+                    hexEscapeChar( os, c );
2561                     break;
2562                 }
2563                 // The header is valid, check data
2564                 // The next encBytes bytes must together be a valid utf-8
2565-                // This means: bitpattern 10XX XXXX and the extracted value is sane (ish)
2566+                // This means: bitpattern 10XX XXXX and the extracted value is sane
2567+                // (ish)
2568                 bool valid = true;
2569-                uint32_t value = headerValue(c);
2570-                for (std::size_t n = 1; n < encBytes; ++n) {
2571-                    unsigned char nc = static_cast<unsigned char>(m_str[idx + n]);
2572-                    valid &= ((nc & 0xC0) == 0x80);
2573-                    value = (value << 6) | (nc & 0x3F);
2574+                uint32_t value = headerValue( c );
2575+                for ( std::size_t n = 1; n < encBytes; ++n ) {
2576+                    unsigned char nc = static_cast<unsigned char>( m_str[idx + n] );
2577+                    valid &= ( ( nc & 0xC0 ) == 0x80 );
2578+                    value = ( value << 6 ) | ( nc & 0x3F );
2579                 }
2580 
2581                 if (
2582                     // Wrong bit pattern of following bytes
2583-                    (!valid) ||
2584+                    ( !valid ) ||
2585                     // Overlong encodings
2586-                    (value < 0x80) ||
2587-                    (0x80 <= value && value < 0x800   && encBytes > 2) ||
2588-                    (0x800 < value && value < 0x10000 && encBytes > 3) ||
2589+                    ( value < 0x80 ) ||
2590+                    ( 0x80 <= value && value < 0x800 && encBytes > 2 ) ||
2591+                    ( 0x800 < value && value < 0x10000 && encBytes > 3 ) ||
2592                     // Encoded value out of range
2593-                    (value >= 0x110000)
2594-                    ) {
2595-                    hexEscapeChar(os, c);
2596+                    ( value >= 0x110000 ) ) {
2597+                    write_to( idx );
2598+                    hexEscapeChar( os, c );
2599                     break;
2600                 }
2601 
2602                 // If we got here, this is in fact a valid(ish) utf-8 sequence
2603-                for (std::size_t n = 0; n < encBytes; ++n) {
2604-                    os << m_str[idx + n];
2605-                }
2606                 idx += encBytes - 1;
2607                 break;
2608             }
2609         }
2610+
2611+        write_to( m_str.size() );
2612     }
2613 
2614     std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) {
2615@@ -8077,7 +8609,7 @@ namespace Detail {
2616 
2617     std::string WithinRelMatcher::describe() const {
2618         Catch::ReusableStringStream sstr;
2619-        sstr << "and " << m_target << " are within " << m_epsilon * 100. << "% of each other";
2620+        sstr << "and " << ::Catch::Detail::stringify(m_target) << " are within " << m_epsilon * 100. << "% of each other";
2621         return sstr.str();
2622     }
2623 
2624@@ -8583,7 +9115,8 @@ private:
2625                          << m_config->testSpec()
2626                          << '\n';
2627             }
2628-            m_stream << "RNG seed: " << getSeed() << '\n';
2629+            m_stream << "RNG seed: " << getSeed() << '\n'
2630+                     << std::flush;
2631         }
2632 
2633         void CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) {
2634@@ -8817,7 +9350,7 @@ struct RowBreak {};
2635 struct OutputFlush {};
2636 
2637 class Duration {
2638-    enum class Unit {
2639+    enum class Unit : uint8_t {
2640         Auto,
2641         Nanoseconds,
2642         Microseconds,
2643@@ -8889,7 +9422,10 @@ public:
2644 };
2645 } // end anon namespace
2646 
2647-enum class Justification { Left, Right };
2648+enum class Justification : uint8_t {
2649+    Left,
2650+    Right
2651+};
2652 
2653 struct ColumnInfo {
2654     std::string name;
2655@@ -9001,7 +9537,10 @@ ConsoleReporter::ConsoleReporter(ReporterConfig&& config):
2656                 { "est run time high mean  high std dev", 14, Justification::Right }
2657             };
2658         }
2659-    }())) {}
2660+    }())) {
2661+    m_preferences.shouldReportAllAssertionStarts = false;
2662+}
2663+
2664 ConsoleReporter::~ConsoleReporter() = default;
2665 
2666 std::string ConsoleReporter::getDescription() {
2667@@ -9016,8 +9555,6 @@ void ConsoleReporter::reportInvalidTestSpec( StringRef arg ) {
2668     m_stream << "Invalid Filter: " << arg << '\n';
2669 }
2670 
2671-void ConsoleReporter::assertionStarting(AssertionInfo const&) {}
2672-
2673 void ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) {
2674     AssertionResult const& result = _assertionStats.assertionResult;
2675 
2676@@ -9129,7 +9666,8 @@ void ConsoleReporter::testRunStarting(TestRunInfo const& _testRunInfo) {
2677         m_stream << m_colour->guardColour( Colour::BrightYellow ) << "Filters: "
2678                  << m_config->testSpec() << '\n';
2679     }
2680-    m_stream << "Randomness seeded to: " << getSeed() << '\n';
2681+    m_stream << "Randomness seeded to: " << getSeed() << '\n'
2682+             << std::flush;
2683 }
2684 
2685 void ConsoleReporter::lazyPrint() {
2686@@ -9457,6 +9995,7 @@ namespace Catch {
2687 
2688 #include <algorithm>
2689 #include <cfloat>
2690+#include <cmath>
2691 #include <cstdio>
2692 #include <ostream>
2693 #include <iomanip>
2694@@ -9620,9 +10159,29 @@ namespace Catch {
2695             out << "All available tags:\n";
2696         }
2697 
2698+        // minimum whitespace to pad tag counts, possibly overwritten below
2699+        size_t maxTagCountLen = 2;
2700+
2701+        // determine necessary padding for tag count column
2702+        if ( ! tags.empty() ) {
2703+            const auto maxTagCount =
2704+                std::max_element( tags.begin(),
2705+                                  tags.end(),
2706+                                  []( auto const& lhs, auto const& rhs ) {
2707+                                      return lhs.count < rhs.count;
2708+                                  } )
2709+                    ->count;
2710+            
2711+            // more padding necessary for 3+ digits
2712+            if (maxTagCount >= 100) {
2713+                auto numDigits = 1 + std::floor( std::log10( maxTagCount ) );
2714+                maxTagCountLen = static_cast<size_t>( numDigits );
2715+            }
2716+        }
2717+
2718         for ( auto const& tagCount : tags ) {
2719             ReusableStringStream rss;
2720-            rss << "  " << std::setw( 2 ) << tagCount.count << "  ";
2721+            rss << "  " << std::setw( maxTagCountLen ) << tagCount.count << "  ";
2722             auto str = rss.str();
2723             auto wrapper = TextFlow::Column( tagCount.all() )
2724                                .initialIndent( 0 )
2725@@ -9820,6 +10379,8 @@ namespace Catch {
2726         //      not, but for machine-parseable reporters I think the answer
2727         //      should be yes.
2728         m_preferences.shouldReportAllAssertions = true;
2729+        // We only handle assertions when they end
2730+        m_preferences.shouldReportAllAssertionStarts = false;
2731 
2732         m_objectWriters.emplace( m_stream );
2733         m_writers.emplace( Writer::Object );
2734@@ -10049,7 +10610,6 @@ namespace Catch {
2735         endObject();
2736     }
2737 
2738-    void JsonReporter::assertionStarting( AssertionInfo const& /*assertionInfo*/ ) {}
2739     void JsonReporter::assertionEnded( AssertionStats const& assertionStats ) {
2740         // TODO: There is lot of different things to handle here, but
2741         //       we can fill it in later, after we show that the basic
2742@@ -10215,7 +10775,8 @@ namespace Catch {
2743             xml( m_stream )
2744         {
2745             m_preferences.shouldRedirectStdOut = true;
2746-            m_preferences.shouldReportAllAssertions = true;
2747+            m_preferences.shouldReportAllAssertions = false;
2748+            m_preferences.shouldReportAllAssertionStarts = false;
2749             m_shouldStoreSuccesfulAssertions = false;
2750         }
2751 
2752@@ -10325,7 +10886,7 @@ namespace Catch {
2753         if( !rootName.empty() )
2754             name = rootName + '/' + name;
2755 
2756-        if( sectionNode.hasAnyAssertions()
2757+        if ( sectionNode.stats.assertions.total() > 0
2758            || !sectionNode.stdOut.empty()
2759            || !sectionNode.stdErr.empty() ) {
2760             XmlWriter::ScopedElement e = xml.scopedElement( "testcase" );
2761@@ -10446,6 +11007,8 @@ namespace Catch {
2762             reporterish.getPreferences().shouldRedirectStdOut;
2763         m_preferences.shouldReportAllAssertions |=
2764             reporterish.getPreferences().shouldReportAllAssertions;
2765+        m_preferences.shouldReportAllAssertionStarts |=
2766+            reporterish.getPreferences().shouldReportAllAssertionStarts;
2767     }
2768 
2769     void MultiReporter::addListener( IEventListenerPtr&& listener ) {
2770@@ -10713,9 +11276,9 @@ namespace Catch {
2771         if (!rootName.empty())
2772             name = rootName + '/' + name;
2773 
2774-        if ( sectionNode.hasAnyAssertions()
2775+        if ( sectionNode.stats.assertions.total() > 0
2776             || !sectionNode.stdOut.empty()
2777-            ||  !sectionNode.stdErr.empty() ) {
2778+            || !sectionNode.stdErr.empty() ) {
2779             XmlWriter::ScopedElement e = xml.scopedElement("testCase");
2780             xml.writeAttribute("name"_sr, name);
2781             xml.writeAttribute("duration"_sr, static_cast<long>(sectionNode.stats.durationInSeconds * 1000));
2782@@ -11003,7 +11566,8 @@ namespace Catch {
2783         if ( m_config->testSpec().hasFilters() ) {
2784             m_stream << "# filters: " << m_config->testSpec() << '\n';
2785         }
2786-        m_stream << "# rng-seed: " << m_config->rngSeed() << '\n';
2787+        m_stream << "# rng-seed: " << m_config->rngSeed() << '\n'
2788+                 << std::flush;
2789     }
2790 
2791     void TAPReporter::noMatchingTestCases( StringRef unmatchedSpec ) {
2792@@ -11217,6 +11781,7 @@ namespace Catch {
2793     {
2794         m_preferences.shouldRedirectStdOut = true;
2795         m_preferences.shouldReportAllAssertions = true;
2796+        m_preferences.shouldReportAllAssertionStarts = false;
2797     }
2798 
2799     XmlReporter::~XmlReporter() = default;
2800@@ -11273,8 +11838,6 @@ namespace Catch {
2801         }
2802     }
2803 
2804-    void XmlReporter::assertionStarting( AssertionInfo const& ) { }
2805-
2806     void XmlReporter::assertionEnded( AssertionStats const& assertionStats ) {
2807 
2808         AssertionResult const& result = assertionStats.assertionResult;
2809diff --git a/include/catch_amalgamated.hpp b/include/catch_amalgamated.hpp
2810old mode 100755
2811new mode 100644
2812index 7e75a5d..d5eb1bb
2813--- a/include/catch_amalgamated.hpp
2814+++ b/include/catch_amalgamated.hpp
2815@@ -6,8 +6,8 @@
2816 
2817 // SPDX-License-Identifier: BSL-1.0
2818 
2819-//  Catch v3.5.4
2820-//  Generated: 2024-04-10 12:03:45.785902
2821+//  Catch v3.11.0
2822+//  Generated: 2025-09-30 10:49:11.225746
2823 //  ----------------------------------------------------------
2824 //  This file is an amalgamation of multiple different files.
2825 //  You probably shouldn't edit it directly.
2826@@ -101,6 +101,9 @@
2827 #elif defined(linux) || defined(__linux) || defined(__linux__)
2828 #  define CATCH_PLATFORM_LINUX
2829 
2830+#elif defined(__QNX__)
2831+#  define CATCH_PLATFORM_QNX
2832+
2833 #elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__)
2834 #  define CATCH_PLATFORM_WINDOWS
2835 
2836@@ -150,7 +153,7 @@
2837 #    define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \
2838          _Pragma( "GCC diagnostic ignored \"-Wshadow\"" )
2839 
2840-#    define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__)
2841+#    define CATCH_INTERNAL_CONFIG_USE_BUILTIN_CONSTANT_P
2842 
2843 #endif
2844 
2845@@ -174,35 +177,13 @@
2846 // clang-cl defines _MSC_VER as well as __clang__, which could cause the
2847 // start/stop internal suppression macros to be double defined.
2848 #if defined(__clang__) && !defined(_MSC_VER)
2849-
2850+#    define CATCH_INTERNAL_CONFIG_USE_BUILTIN_CONSTANT_P
2851 #    define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" )
2852 #    define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION  _Pragma( "clang diagnostic pop" )
2853-
2854 #endif // __clang__ && !_MSC_VER
2855 
2856 #if defined(__clang__)
2857 
2858-// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug
2859-// which results in calls to destructors being emitted for each temporary,
2860-// without a matching initialization. In practice, this can result in something
2861-// like `std::string::~string` being called on an uninitialized value.
2862-//
2863-// For example, this code will likely segfault under IBM XL:
2864-// ```
2865-// REQUIRE(std::string("12") + "34" == "1234")
2866-// ```
2867-//
2868-// Similarly, NVHPC's implementation of `__builtin_constant_p` has a bug which
2869-// results in calls to the immediately evaluated lambda expressions to be
2870-// reported as unevaluated lambdas.
2871-// https://developer.nvidia.com/nvidia_bug/3321845.
2872-//
2873-// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented.
2874-#  if !defined(__ibmxl__) && !defined(__CUDACC__) && !defined( __NVCOMPILER )
2875-#    define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */
2876-#  endif
2877-
2878-
2879 #    define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
2880          _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \
2881          _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"")
2882@@ -213,8 +194,16 @@
2883 #    define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \
2884          _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" )
2885 
2886-#    define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \
2887-         _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" )
2888+#    if (__clang_major__ >= 20)
2889+#        define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \
2890+             _Pragma( "clang diagnostic ignored \"-Wvariadic-macro-arguments-omitted\"" )
2891+#    elif (__clang_major__ == 19)
2892+#        define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \
2893+	         _Pragma( "clang diagnostic ignored \"-Wc++20-extensions\"" )
2894+#    else
2895+#        define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS
2896+             _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" )
2897+#    endif
2898 
2899 #    define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \
2900          _Pragma( "clang diagnostic ignored \"-Wunused-template\"" )
2901@@ -227,6 +216,27 @@
2902 
2903 #endif // __clang__
2904 
2905+// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug
2906+// which results in calls to destructors being emitted for each temporary,
2907+// without a matching initialization. In practice, this can result in something
2908+// like `std::string::~string` being called on an uninitialized value.
2909+//
2910+// For example, this code will likely segfault under IBM XL:
2911+// ```
2912+// REQUIRE(std::string("12") + "34" == "1234")
2913+// ```
2914+//
2915+// Similarly, NVHPC's implementation of `__builtin_constant_p` has a bug which
2916+// results in calls to the immediately evaluated lambda expressions to be
2917+// reported as unevaluated lambdas.
2918+// https://developer.nvidia.com/nvidia_bug/3321845.
2919+//
2920+// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented.
2921+#if defined( __ibmxl__ ) || defined( __CUDACC__ ) || defined( __NVCOMPILER )
2922+#    define CATCH_INTERNAL_CONFIG_NO_USE_BUILTIN_CONSTANT_P
2923+#endif
2924+
2925+
2926 
2927 ////////////////////////////////////////////////////////////////////////////////
2928 // We know some environments not to support full POSIX signals
2929@@ -301,13 +311,17 @@
2930 #    endif
2931 
2932 // Universal Windows platform does not support SEH
2933-// Or console colours (or console at all...)
2934-#  if defined(CATCH_PLATFORM_WINDOWS_UWP)
2935-#    define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32
2936-#  else
2937+#  if !defined(CATCH_PLATFORM_WINDOWS_UWP)
2938 #    define CATCH_INTERNAL_CONFIG_WINDOWS_SEH
2939 #  endif
2940 
2941+// Only some Windows platform families support the console
2942+#  if defined(WINAPI_FAMILY_PARTITION)
2943+#    if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
2944+#      define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32
2945+#    endif
2946+#  endif
2947+
2948 // MSVC traditional preprocessor needs some workaround for __VA_ARGS__
2949 // _MSVC_TRADITIONAL == 0 means new conformant preprocessor
2950 // _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor
2951@@ -450,6 +464,22 @@
2952 #endif
2953 
2954 
2955+// The goal of this macro is to avoid evaluation of the arguments, but
2956+// still have the compiler warn on problems inside...
2957+#if defined( CATCH_INTERNAL_CONFIG_USE_BUILTIN_CONSTANT_P ) && \
2958+    !defined( CATCH_INTERNAL_CONFIG_NO_USE_BUILTIN_CONSTANT_P ) && !defined(CATCH_CONFIG_USE_BUILTIN_CONSTANT_P)
2959+#define CATCH_CONFIG_USE_BUILTIN_CONSTANT_P
2960+#endif
2961+
2962+#if defined( CATCH_CONFIG_USE_BUILTIN_CONSTANT_P ) && \
2963+    !defined( CATCH_CONFIG_NO_USE_BUILTIN_CONSTANT_P )
2964+#    define CATCH_INTERNAL_IGNORE_BUT_WARN( ... )                                              \
2965+        (void)__builtin_constant_p( __VA_ARGS__ ) /* NOLINT(cppcoreguidelines-pro-type-vararg, \
2966+                                                     hicpp-vararg) */
2967+#else
2968+#    define CATCH_INTERNAL_IGNORE_BUT_WARN( ... )
2969+#endif
2970+
2971 // Even if we do not think the compiler has that warning, we still have
2972 // to provide a macro that can be used by the code.
2973 #if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION)
2974@@ -486,13 +516,6 @@
2975 #    define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS
2976 #endif
2977 
2978-
2979-// The goal of this macro is to avoid evaluation of the arguments, but
2980-// still have the compiler warn on problems inside...
2981-#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN)
2982-#   define CATCH_INTERNAL_IGNORE_BUT_WARN(...)
2983-#endif
2984-
2985 #if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10)
2986 #   undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS
2987 #elif defined(__clang__) && (__clang_major__ < 5)
2988@@ -548,31 +571,27 @@ namespace Catch {
2989         IConfig const* m_config = nullptr;
2990         IResultCapture* m_resultCapture = nullptr;
2991 
2992-        CATCH_EXPORT static Context* currentContext;
2993+        CATCH_EXPORT static Context currentContext;
2994         friend Context& getCurrentMutableContext();
2995         friend Context const& getCurrentContext();
2996-        static void createContext();
2997-        friend void cleanUpContext();
2998 
2999     public:
3000-        IResultCapture* getResultCapture() const { return m_resultCapture; }
3001-        IConfig const* getConfig() const { return m_config; }
3002-        void setResultCapture( IResultCapture* resultCapture );
3003-        void setConfig( IConfig const* config );
3004+        constexpr IResultCapture* getResultCapture() const {
3005+            return m_resultCapture;
3006+        }
3007+        constexpr IConfig const* getConfig() const { return m_config; }
3008+        constexpr void setResultCapture( IResultCapture* resultCapture ) {
3009+            m_resultCapture = resultCapture;
3010+        }
3011+        constexpr void setConfig( IConfig const* config ) { m_config = config; }
3012     };
3013 
3014     Context& getCurrentMutableContext();
3015 
3016     inline Context const& getCurrentContext() {
3017-        // We duplicate the logic from `getCurrentMutableContext` here,
3018-        // to avoid paying the call overhead in debug mode.
3019-        if ( !Context::currentContext ) { Context::createContext(); }
3020-        // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn)
3021-        return *Context::currentContext;
3022+        return Context::currentContext;
3023     }
3024 
3025-    void cleanUpContext();
3026-
3027     class SimplePcg32;
3028     SimplePcg32& sharedRng();
3029 }
3030@@ -669,7 +688,6 @@ namespace Catch {
3031 #define CATCH_INTERFACES_CAPTURE_HPP_INCLUDED
3032 
3033 #include <string>
3034-#include <chrono>
3035 
3036 
3037 
3038@@ -819,8 +837,10 @@ namespace Catch {
3039 
3040     }; };
3041 
3042-    bool isOk( ResultWas::OfType resultType );
3043-    bool isJustInfo( int flags );
3044+    constexpr bool isOk( ResultWas::OfType resultType ) {
3045+        return ( resultType & ResultWas::FailureBit ) == 0;
3046+    }
3047+    constexpr bool isJustInfo( int flags ) { return flags == ResultWas::Info; }
3048 
3049 
3050     // ResultDisposition::Flags enum
3051@@ -832,11 +852,18 @@ namespace Catch {
3052         SuppressFail = 0x08         // Failures are reported but do not fail the test
3053     }; };
3054 
3055-    ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs );
3056+    constexpr ResultDisposition::Flags operator|( ResultDisposition::Flags lhs,
3057+                                        ResultDisposition::Flags rhs ) {
3058+        return static_cast<ResultDisposition::Flags>( static_cast<int>( lhs ) |
3059+                                                      static_cast<int>( rhs ) );
3060+    }
3061 
3062-    bool shouldContinueOnFailure( int flags );
3063-    inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; }
3064-    bool shouldSuppressFailure( int flags );
3065+    constexpr bool isFalseTest( int flags ) {
3066+        return ( flags & ResultDisposition::FalseTest ) != 0;
3067+    }
3068+    constexpr bool shouldSuppressFailure( int flags ) {
3069+        return ( flags & ResultDisposition::SuppressFail ) != 0;
3070+    }
3071 
3072 } // end namespace Catch
3073 
3074@@ -929,7 +956,7 @@ namespace Detail {
3075         }
3076 
3077         explicit operator bool() const {
3078-            return m_ptr;
3079+            return m_ptr != nullptr;
3080         }
3081 
3082         friend void swap(unique_ptr& lhs, unique_ptr& rhs) {
3083@@ -1040,10 +1067,9 @@ namespace Catch {
3084         virtual void benchmarkEnded( BenchmarkStats<> const& stats ) = 0;
3085         virtual void benchmarkFailed( StringRef error ) = 0;
3086 
3087-        virtual void pushScopedMessage( MessageInfo const& message ) = 0;
3088-        virtual void popScopedMessage( MessageInfo const& message ) = 0;
3089-
3090-        virtual void emplaceUnscopedMessage( MessageBuilder&& builder ) = 0;
3091+        static void pushScopedMessage( MessageInfo&& message );
3092+        static void popScopedMessage( unsigned int messageId );
3093+        static void emplaceUnscopedMessage( MessageBuilder&& builder );
3094 
3095         virtual void handleFatalErrorCondition( StringRef message ) = 0;
3096 
3097@@ -1054,7 +1080,7 @@ namespace Catch {
3098         virtual void handleMessage
3099                 (   AssertionInfo const& info,
3100                     ResultWas::OfType resultType,
3101-                    StringRef message,
3102+                    std::string&& message,
3103                     AssertionReaction& reaction ) = 0;
3104         virtual void handleUnexpectedExceptionNotThrown
3105                 (   AssertionInfo const& info,
3106@@ -1071,9 +1097,7 @@ namespace Catch {
3107                     AssertionReaction &reaction ) = 0;
3108 
3109 
3110-
3111         virtual bool lastAssertionPassed() = 0;
3112-        virtual void assertionPassed() = 0;
3113 
3114         // Deprecated, do not use:
3115         virtual std::string getCurrentTestName() const = 0;
3116@@ -1081,7 +1105,18 @@ namespace Catch {
3117         virtual void exceptionEarlyReported() = 0;
3118     };
3119 
3120-    IResultCapture& getResultCapture();
3121+    namespace Detail {
3122+        [[noreturn]]
3123+        void missingCaptureInstance();
3124+    }
3125+    inline IResultCapture& getResultCapture() {
3126+        if (auto* capture = getCurrentContext().getResultCapture()) {
3127+            return *capture;
3128+        } else {
3129+            Detail::missingCaptureInstance();
3130+        }
3131+    }
3132+
3133 }
3134 
3135 #endif // CATCH_INTERFACES_CAPTURE_HPP_INCLUDED
3136@@ -1100,6 +1135,7 @@ namespace Catch {
3137 
3138         //! Deriving classes become noncopyable and nonmovable
3139         class NonCopyable {
3140+        public:
3141             NonCopyable( NonCopyable const& ) = delete;
3142             NonCopyable( NonCopyable&& ) = delete;
3143             NonCopyable& operator=( NonCopyable const& ) = delete;
3144@@ -1115,7 +1151,6 @@ namespace Catch {
3145 #endif // CATCH_NONCOPYABLE_HPP_INCLUDED
3146 
3147 #include <chrono>
3148-#include <iosfwd>
3149 #include <string>
3150 #include <vector>
3151 
3152@@ -1302,7 +1337,7 @@ namespace Catch {
3153             int high_mild = 0;      // 1.5 to 3 times IQR above Q3
3154             int high_severe = 0;    // more than 3 times IQR above Q3
3155 
3156-            int total() const {
3157+            constexpr int total() const {
3158                 return low_severe + low_mild + high_mild + high_severe;
3159             }
3160         };
3161@@ -1570,8 +1605,7 @@ namespace Catch {
3162     namespace Benchmark {
3163         namespace Detail {
3164             template <typename T, typename U>
3165-            struct is_related
3166-                : std::is_same<std::decay_t<T>, std::decay_t<U>> {};
3167+            static constexpr bool is_related_v = std::is_same<std::decay_t<T>, std::decay_t<U>>::value;
3168 
3169             /// We need to reinvent std::function because every piece of code that might add overhead
3170             /// in a measurement context needs to have consistent performance characteristics so that we
3171@@ -1584,22 +1618,17 @@ namespace Catch {
3172             private:
3173                 struct callable {
3174                     virtual void call(Chronometer meter) const = 0;
3175-                    virtual Catch::Detail::unique_ptr<callable> clone() const = 0;
3176                     virtual ~callable(); // = default;
3177 
3178                     callable() = default;
3179-                    callable(callable const&) = default;
3180-                    callable& operator=(callable const&) = default;
3181+                    callable(callable&&) = default;
3182+                    callable& operator=(callable&&) = default;
3183                 };
3184                 template <typename Fun>
3185                 struct model : public callable {
3186                     model(Fun&& fun_) : fun(CATCH_MOVE(fun_)) {}
3187                     model(Fun const& fun_) : fun(fun_) {}
3188 
3189-                    Catch::Detail::unique_ptr<callable> clone() const override {
3190-                        return Catch::Detail::make_unique<model<Fun>>( *this );
3191-                    }
3192-
3193                     void call(Chronometer meter) const override {
3194                         call(meter, is_callable<Fun(Chronometer)>());
3195                     }
3196@@ -1613,37 +1642,23 @@ namespace Catch {
3197                     Fun fun;
3198                 };
3199 
3200-                struct do_nothing { void operator()() const {} };
3201-
3202-                template <typename T>
3203-                BenchmarkFunction(model<T>* c) : f(c) {}
3204-
3205             public:
3206-                BenchmarkFunction()
3207-                    : f(new model<do_nothing>{ {} }) {}
3208+                BenchmarkFunction();
3209 
3210                 template <typename Fun,
3211-                    std::enable_if_t<!is_related<Fun, BenchmarkFunction>::value, int> = 0>
3212+                    std::enable_if_t<!is_related_v<Fun, BenchmarkFunction>, int> = 0>
3213                     BenchmarkFunction(Fun&& fun)
3214                     : f(new model<std::decay_t<Fun>>(CATCH_FORWARD(fun))) {}
3215 
3216                 BenchmarkFunction( BenchmarkFunction&& that ) noexcept:
3217                     f( CATCH_MOVE( that.f ) ) {}
3218 
3219-                BenchmarkFunction(BenchmarkFunction const& that)
3220-                    : f(that.f->clone()) {}
3221-
3222                 BenchmarkFunction&
3223                 operator=( BenchmarkFunction&& that ) noexcept {
3224                     f = CATCH_MOVE( that.f );
3225                     return *this;
3226                 }
3227 
3228-                BenchmarkFunction& operator=(BenchmarkFunction const& that) {
3229-                    f = that.f->clone();
3230-                    return *this;
3231-                }
3232-
3233                 void operator()(Chronometer meter) const { f->call(meter); }
3234 
3235             private:
3236@@ -1757,8 +1772,6 @@ namespace Catch {
3237 #define CATCH_TIMING_HPP_INCLUDED
3238 
3239 
3240-#include <type_traits>
3241-
3242 namespace Catch {
3243     namespace Benchmark {
3244         template <typename Result>
3245@@ -1780,7 +1793,7 @@ namespace Catch {
3246             template <typename Clock, typename Fun, typename... Args>
3247             TimingOf<Fun, Args...> measure(Fun&& fun, Args&&... args) {
3248                 auto start = Clock::now();
3249-                auto&& r = Detail::complete_invoke(fun, CATCH_FORWARD(args)...);
3250+                auto&& r = Detail::complete_invoke(CATCH_FORWARD(fun), CATCH_FORWARD(args)...);
3251                 auto end = Clock::now();
3252                 auto delta = end - start;
3253                 return { delta, CATCH_FORWARD(r), 1 };
3254@@ -1946,15 +1959,17 @@ namespace Catch {
3255         namespace Detail {
3256             template <typename Clock>
3257             std::vector<double> resolution(int k) {
3258-                std::vector<TimePoint<Clock>> times;
3259-                times.reserve(static_cast<size_t>(k + 1));
3260-                for ( int i = 0; i < k + 1; ++i ) {
3261-                    times.push_back( Clock::now() );
3262+                const size_t points = static_cast<size_t>( k + 1 );
3263+                // To avoid overhead from the branch inside vector::push_back,
3264+                // we allocate them all and then overwrite.
3265+                std::vector<TimePoint<Clock>> times(points);
3266+                for ( auto& time : times ) {
3267+                    time = Clock::now();
3268                 }
3269 
3270                 std::vector<double> deltas;
3271                 deltas.reserve(static_cast<size_t>(k));
3272-                for ( size_t idx = 1; idx < times.size(); ++idx ) {
3273+                for ( size_t idx = 1; idx < points; ++idx ) {
3274                     deltas.push_back( static_cast<double>(
3275                         ( times[idx] - times[idx - 1] ).count() ) );
3276                 }
3277@@ -2103,12 +2118,12 @@ namespace Catch {
3278                 : fun(CATCH_MOVE(func)), name(CATCH_MOVE(benchmarkName)) {}
3279 
3280             template <typename Clock>
3281-            ExecutionPlan prepare(const IConfig &cfg, Environment env) const {
3282+            ExecutionPlan prepare(const IConfig &cfg, Environment env) {
3283                 auto min_time = env.clock_resolution.mean * Detail::minimum_ticks;
3284                 auto run_time = std::max(min_time, std::chrono::duration_cast<decltype(min_time)>(cfg.benchmarkWarmupTime()));
3285                 auto&& test = Detail::run_for_at_least<Clock>(std::chrono::duration_cast<IDuration>(run_time), 1, fun);
3286                 int new_iters = static_cast<int>(std::ceil(min_time * test.iterations / test.elapsed));
3287-                return { new_iters, test.elapsed / test.iterations * new_iters * cfg.benchmarkSamples(), fun, std::chrono::duration_cast<FDuration>(cfg.benchmarkWarmupTime()), Detail::warmup_iterations };
3288+                return { new_iters, test.elapsed / test.iterations * new_iters * cfg.benchmarkSamples(), CATCH_MOVE(fun), std::chrono::duration_cast<FDuration>(cfg.benchmarkWarmupTime()), Detail::warmup_iterations };
3289             }
3290 
3291             template <typename Clock = default_clock>
3292@@ -2144,9 +2159,7 @@ namespace Catch {
3293                     auto analysis = Detail::analyse(*cfg, samples.data(), samples.data() + samples.size());
3294                     BenchmarkStats<> stats{ CATCH_MOVE(info), CATCH_MOVE(analysis.samples), analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance };
3295                     getResultCapture().benchmarkEnded(stats);
3296-                } CATCH_CATCH_ANON (TestFailureException const&) {
3297-                    getResultCapture().benchmarkFailed("Benchmark failed due to failed assertion"_sr);
3298-                } CATCH_CATCH_ALL{
3299+                } CATCH_CATCH_ALL {
3300                     getResultCapture().benchmarkFailed(translateActiveException());
3301                     // We let the exception go further up so that the
3302                     // test case is marked as failed.
3303@@ -2155,7 +2168,7 @@ namespace Catch {
3304             }
3305 
3306             // sets lambda to be used in fun *and* executes benchmark!
3307-            template <typename Fun, std::enable_if_t<!Detail::is_related<Fun, Benchmark>::value, int> = 0>
3308+            template <typename Fun, std::enable_if_t<!Detail::is_related_v<Fun, Benchmark>, int> = 0>
3309                 Benchmark & operator=(Fun func) {
3310                 auto const* cfg = getCurrentContext().getConfig();
3311                 if (!cfg->skipBenchmarks()) {
3312@@ -2484,18 +2497,14 @@ namespace Catch {
3313           return rawMemoryToString( &object, sizeof(object) );
3314         }
3315 
3316-        template<typename T>
3317-        class IsStreamInsertable {
3318-            template<typename Stream, typename U>
3319-            static auto test(int)
3320-                -> decltype(std::declval<Stream&>() << std::declval<U>(), std::true_type());
3321-
3322-            template<typename, typename>
3323-            static auto test(...)->std::false_type;
3324+        template<typename T,typename = void>
3325+        static constexpr bool IsStreamInsertable_v = false;
3326 
3327-        public:
3328-            static const bool value = decltype(test<std::ostream, const T&>(0))::value;
3329-        };
3330+        template <typename T>
3331+        static constexpr bool IsStreamInsertable_v<
3332+            T,
3333+            decltype( void( std::declval<std::ostream&>() << std::declval<T>() ) )> =
3334+            true;
3335 
3336         template<typename E>
3337         std::string convertUnknownEnumToString( E e );
3338@@ -2540,7 +2549,7 @@ namespace Catch {
3339     struct StringMaker {
3340         template <typename Fake = T>
3341         static
3342-        std::enable_if_t<::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>
3343+        std::enable_if_t<::Catch::Detail::IsStreamInsertable_v<Fake>, std::string>
3344             convert(const Fake& value) {
3345                 ReusableStringStream rss;
3346                 // NB: call using the function-like syntax to avoid ambiguity with
3347@@ -2551,7 +2560,7 @@ namespace Catch {
3348 
3349         template <typename Fake = T>
3350         static
3351-        std::enable_if_t<!::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>
3352+        std::enable_if_t<!::Catch::Detail::IsStreamInsertable_v<Fake>, std::string>
3353             convert( const Fake& value ) {
3354 #if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER)
3355             return Detail::convertUnstreamable(value);
3356@@ -2563,11 +2572,17 @@ namespace Catch {
3357 
3358     namespace Detail {
3359 
3360+        std::string makeExceptionHappenedString();
3361+
3362         // This function dispatches all stringification requests inside of Catch.
3363         // Should be preferably called fully qualified, like ::Catch::Detail::stringify
3364         template <typename T>
3365-        std::string stringify(const T& e) {
3366-            return ::Catch::StringMaker<std::remove_cv_t<std::remove_reference_t<T>>>::convert(e);
3367+        std::string stringify( const T& e ) {
3368+            CATCH_TRY {
3369+                return ::Catch::StringMaker<
3370+                    std::remove_cv_t<std::remove_reference_t<T>>>::convert( e );
3371+            }
3372+            CATCH_CATCH_ALL { return makeExceptionHappenedString(); }
3373         }
3374 
3375         template<typename E>
3376@@ -2943,7 +2958,7 @@ namespace Catch {
3377     }
3378 
3379     template<typename R>
3380-    struct StringMaker<R, std::enable_if_t<is_range<R>::value && !::Catch::Detail::IsStreamInsertable<R>::value>> {
3381+    struct StringMaker<R, std::enable_if_t<is_range<R>::value && !::Catch::Detail::IsStreamInsertable_v<R>>> {
3382         static std::string convert( R const& range ) {
3383             return rangeToString( range );
3384         }
3385@@ -3050,13 +3065,19 @@ struct ratio_string<std::milli> {
3386     template<typename Duration>
3387     struct StringMaker<std::chrono::time_point<std::chrono::system_clock, Duration>> {
3388         static std::string convert(std::chrono::time_point<std::chrono::system_clock, Duration> const& time_point) {
3389-            auto converted = std::chrono::system_clock::to_time_t(time_point);
3390+            const auto systemish = std::chrono::time_point_cast<
3391+                std::chrono::system_clock::duration>( time_point );
3392+            const auto as_time_t = std::chrono::system_clock::to_time_t( systemish );
3393 
3394 #ifdef _MSC_VER
3395             std::tm timeInfo = {};
3396-            gmtime_s(&timeInfo, &converted);
3397+            const auto err = gmtime_s( &timeInfo, &as_time_t );
3398+            if ( err ) {
3399+                return "gmtime from provided timepoint has failed. This "
3400+                       "happens e.g. with pre-1970 dates using Microsoft libc";
3401+            }
3402 #else
3403-            std::tm* timeInfo = std::gmtime(&converted);
3404+            std::tm* timeInfo = std::gmtime( &as_time_t );
3405 #endif
3406 
3407             auto const timeStampSize = sizeof("2017-01-16T17:06:45Z");
3408@@ -3284,13 +3305,13 @@ namespace Catch {
3409         ITransientExpression const* m_transientExpression = nullptr;
3410         bool m_isNegated;
3411     public:
3412-        LazyExpression( bool isNegated ):
3413+        constexpr LazyExpression( bool isNegated ):
3414             m_isNegated(isNegated)
3415         {}
3416-        LazyExpression(LazyExpression const& other) = default;
3417+        constexpr LazyExpression(LazyExpression const& other) = default;
3418         LazyExpression& operator = ( LazyExpression const& ) = delete;
3419 
3420-        explicit operator bool() const {
3421+        constexpr explicit operator bool() const {
3422             return m_transientExpression != nullptr;
3423         }
3424 
3425@@ -3347,6 +3368,18 @@ namespace Catch {
3426 #endif // CATCH_ASSERTION_RESULT_HPP_INCLUDED
3427 
3428 
3429+#ifndef CATCH_CASE_SENSITIVE_HPP_INCLUDED
3430+#define CATCH_CASE_SENSITIVE_HPP_INCLUDED
3431+
3432+namespace Catch {
3433+
3434+    enum class CaseSensitive { Yes, No };
3435+
3436+} // namespace Catch
3437+
3438+#endif // CATCH_CASE_SENSITIVE_HPP_INCLUDED
3439+
3440+
3441 #ifndef CATCH_CONFIG_HPP_INCLUDED
3442 #define CATCH_CONFIG_HPP_INCLUDED
3443 
3444@@ -3366,18 +3399,6 @@ namespace Catch {
3445 #define CATCH_WILDCARD_PATTERN_HPP_INCLUDED
3446 
3447 
3448-
3449-#ifndef CATCH_CASE_SENSITIVE_HPP_INCLUDED
3450-#define CATCH_CASE_SENSITIVE_HPP_INCLUDED
3451-
3452-namespace Catch {
3453-
3454-    enum class CaseSensitive { Yes, No };
3455-
3456-} // namespace Catch
3457-
3458-#endif // CATCH_CASE_SENSITIVE_HPP_INCLUDED
3459-
3460 #include <string>
3461 
3462 namespace Catch
3463@@ -3776,7 +3797,7 @@ namespace Catch {
3464         WarnAbout::What warnings = WarnAbout::Nothing;
3465         ShowDurations showDurations = ShowDurations::DefaultForReporter;
3466         double minDuration = -1;
3467-        TestRunOrder runOrder = TestRunOrder::Declared;
3468+        TestRunOrder runOrder = TestRunOrder::Randomized;
3469         ColourMode defaultColourMode = ColourMode::PlatformDefault;
3470         WaitForKeypress::When waitForKeypress = WaitForKeypress::Never;
3471 
3472@@ -3787,6 +3808,8 @@ namespace Catch {
3473 
3474         std::vector<std::string> testsOrTags;
3475         std::vector<std::string> sectionsToRun;
3476+
3477+        std::string prematureExitGuardFilePath;
3478     };
3479 
3480 
3481@@ -3814,6 +3837,8 @@ namespace Catch {
3482 
3483         bool showHelp() const;
3484 
3485+        std::string const& getExitGuardFilePath() const;
3486+
3487         // IConfig interface
3488         bool allowThrows() const override;
3489         StringRef name() const override;
3490@@ -3922,6 +3947,19 @@ namespace Catch {
3491 #define CATCH_MESSAGE_INFO_HPP_INCLUDED
3492 
3493 
3494+
3495+#ifndef CATCH_DEPRECATION_MACRO_HPP_INCLUDED
3496+#define CATCH_DEPRECATION_MACRO_HPP_INCLUDED
3497+
3498+
3499+#if !defined( CATCH_CONFIG_NO_DEPRECATION_ANNOTATIONS )
3500+#    define DEPRECATED( msg ) [[deprecated( msg )]]
3501+#else
3502+#    define DEPRECATED( msg )
3503+#endif
3504+
3505+#endif // CATCH_DEPRECATION_MACRO_HPP_INCLUDED
3506+
3507 #include <string>
3508 
3509 namespace Catch {
3510@@ -3935,16 +3973,19 @@ namespace Catch {
3511         std::string message;
3512         SourceLineInfo lineInfo;
3513         ResultWas::OfType type;
3514+        // The "ID" of the message, used to know when to remove it from reporter context.
3515         unsigned int sequence;
3516 
3517+        DEPRECATED( "Explicitly use the 'sequence' member instead" )
3518         bool operator == (MessageInfo const& other) const {
3519             return sequence == other.sequence;
3520         }
3521+        DEPRECATED( "Explicitly use the 'sequence' member instead" )
3522         bool operator < (MessageInfo const& other) const {
3523             return sequence < other.sequence;
3524         }
3525     private:
3526-        static unsigned int globalCount;
3527+        static thread_local unsigned int globalCount;
3528     };
3529 
3530 } // end namespace Catch
3531@@ -3992,13 +4033,12 @@ namespace Catch {
3532         ScopedMessage( ScopedMessage&& old ) noexcept;
3533         ~ScopedMessage();
3534 
3535-        MessageInfo m_info;
3536+        unsigned int m_messageId;
3537         bool m_moved = false;
3538     };
3539 
3540     class Capturer {
3541         std::vector<MessageInfo> m_messages;
3542-        IResultCapture& m_resultCapture;
3543         size_t m_captured = 0;
3544     public:
3545         Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names );
3546@@ -4029,7 +4069,7 @@ namespace Catch {
3547     do { \
3548         Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition ); \
3549         catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \
3550-        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
3551+        catchAssertionHandler.complete(); \
3552     } while( false )
3553 
3554 ///////////////////////////////////////////////////////////////////////////////
3555@@ -4046,7 +4086,7 @@ namespace Catch {
3556 
3557 ///////////////////////////////////////////////////////////////////////////////
3558 #define INTERNAL_CATCH_UNSCOPED_INFO( macroName, log ) \
3559-    Catch::getResultCapture().emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log )
3560+    Catch::IResultCapture::emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log )
3561 
3562 
3563 #if defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE)
3564@@ -4221,15 +4261,13 @@ namespace Catch {
3565             };
3566 
3567             template <typename F, typename = void>
3568-            struct is_unary_function : std::false_type {};
3569+            static constexpr bool is_unary_function_v = false;
3570 
3571             template <typename F>
3572-            struct is_unary_function<
3573+            static constexpr bool is_unary_function_v<
3574                 F,
3575-                Catch::Detail::void_t<decltype(
3576-                    std::declval<F>()( fake_arg() ) )
3577-                >
3578-            > : std::true_type {};
3579+                Catch::Detail::void_t<decltype( std::declval<F>()(
3580+                    fake_arg() ) )>> = true;
3581 
3582             // Traits for extracting arg and return type of lambdas (for single
3583             // argument lambdas)
3584@@ -4662,14 +4700,14 @@ namespace Catch {
3585 
3586                 template <typename T,
3587                           typename = typename std::enable_if_t<
3588-                              !Detail::is_unary_function<T>::value>>
3589+                              !Detail::is_unary_function_v<T>>>
3590                 ParserRefImpl( T& ref, StringRef hint ):
3591                     m_ref( std::make_shared<BoundValueRef<T>>( ref ) ),
3592                     m_hint( hint ) {}
3593 
3594                 template <typename LambdaT,
3595                           typename = typename std::enable_if_t<
3596-                              Detail::is_unary_function<LambdaT>::value>>
3597+                              Detail::is_unary_function_v<LambdaT>>>
3598                 ParserRefImpl( LambdaT const& ref, StringRef hint ):
3599                     m_ref( std::make_shared<BoundLambda<LambdaT>>( ref ) ),
3600                     m_hint( hint ) {}
3601@@ -4736,7 +4774,7 @@ namespace Catch {
3602 
3603             template <typename LambdaT,
3604                       typename = typename std::enable_if_t<
3605-                          Detail::is_unary_function<LambdaT>::value>>
3606+                          Detail::is_unary_function_v<LambdaT>>>
3607             Opt( LambdaT const& ref, StringRef hint ):
3608                 ParserRefImpl( ref, hint ) {}
3609 
3610@@ -4746,7 +4784,7 @@ namespace Catch {
3611 
3612             template <typename T,
3613                       typename = typename std::enable_if_t<
3614-                          !Detail::is_unary_function<T>::value>>
3615+                          !Detail::is_unary_function_v<T>>>
3616             Opt( T& ref, StringRef hint ):
3617                 ParserRefImpl( ref, hint ) {}
3618 
3619@@ -4916,6 +4954,14 @@ namespace Catch {
3620 
3621 namespace Catch {
3622 
3623+    // TODO: Use C++17 `inline` variables
3624+    constexpr int UnspecifiedErrorExitCode = 1;
3625+    constexpr int NoTestsRunExitCode = 2;
3626+    constexpr int UnmatchedTestSpecExitCode = 3;
3627+    constexpr int AllTestsSkippedExitCode = 4;
3628+    constexpr int InvalidTestSpecExitCode = 5;
3629+    constexpr int TestFailureExitCode = 42;
3630+
3631     class Session : Detail::NonCopyable {
3632     public:
3633 
3634@@ -5000,7 +5046,7 @@ namespace Catch {
3635 #define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \
3636     CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \
3637     CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
3638-    namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \
3639+    namespace{ const Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \
3640     CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
3641 
3642 #endif // CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED
3643@@ -5177,7 +5223,7 @@ namespace Detail {
3644  *    when the compiler handles `ExprLhs<T> == b`, it also tries to resolve
3645  *    the overload set for `b == ExprLhs<T>`.
3646  *
3647- * To accomodate these use cases, decomposer ended up rather complex.
3648+ * To accommodate these use cases, decomposer ended up rather complex.
3649  *
3650  * 1) These types are handled by adding SFINAE overloads to our comparison
3651  *    operators, checking whether `T == U` are comparable with the given
3652@@ -5210,10 +5256,11 @@ namespace Detail {
3653  *
3654  * 3) If a type has no linkage, we also cannot capture it by reference.
3655  *    The solution is once again to capture them by value. We handle
3656- *    the common cases by using `std::is_arithmetic` as the default
3657- *    for `Catch::capture_by_value`, but that is only a some-effort
3658- *    heuristic. But as with 2), users can specialize `capture_by_value`
3659- *    for their own types as needed.
3660+ *    the common cases by using `std::is_arithmetic` and `std::is_enum`
3661+ *    as the default for `Catch::capture_by_value`, but that is only a
3662+ *    some-effort heuristic. These combine to capture all possible bitfield
3663+ *    bases, and also some trait-like types. As with 2), users can
3664+ *    specialize `capture_by_value` for their own types as needed.
3665  *
3666  * 4) To support C++20 and make the SFINAE on our decomposing operators
3667  *    work, the SFINAE has to happen in return type, rather than in
3668@@ -5242,9 +5289,11 @@ namespace Detail {
3669 #ifdef __clang__
3670 #  pragma clang diagnostic push
3671 #  pragma clang diagnostic ignored "-Wsign-compare"
3672+#  pragma clang diagnostic ignored "-Wnon-virtual-dtor"
3673 #elif defined __GNUC__
3674 #  pragma GCC diagnostic push
3675 #  pragma GCC diagnostic ignored "-Wsign-compare"
3676+#  pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
3677 #endif
3678 
3679 #if defined(CATCH_CPP20_OR_GREATER) && __has_include(<compare>)
3680@@ -5263,13 +5312,22 @@ namespace Catch {
3681         using RemoveCVRef_t = std::remove_cv_t<std::remove_reference_t<T>>;
3682     }
3683 
3684-    // Note: There is nothing that stops us from extending this,
3685-    //       e.g. to `std::is_scalar`, but the more encompassing
3686-    //       traits are usually also more expensive. For now we
3687-    //       keep this as it used to be and it can be changed later.
3688+    // Note: This is about as much as we can currently reasonably support.
3689+    //       In an ideal world, we could capture by value small trivially
3690+    //       copyable types, but the actual `std::is_trivially_copyable`
3691+    //       trait is a huge mess with standard-violating results on
3692+    //       GCC and Clang, which are unlikely to be fixed soon due to ABI
3693+    //       concerns.
3694+    //       `std::is_scalar` also causes issues due to the `is_pointer`
3695+    //       component, which causes ambiguity issues with (references-to)
3696+    //       function pointer. If those are resolved, we still need to
3697+    //       disambiguate the overload set for arrays, through explicit
3698+    //       overload for references to sized arrays.
3699     template <typename T>
3700     struct capture_by_value
3701-        : std::integral_constant<bool, std::is_arithmetic<T>{}> {};
3702+        : std::integral_constant<bool,
3703+                                 std::is_arithmetic<T>::value ||
3704+                                     std::is_enum<T>::value> {};
3705 
3706 #if defined( CATCH_CONFIG_CPP20_COMPARE_OVERLOADS )
3707     template <>
3708@@ -5287,10 +5345,13 @@ namespace Catch {
3709         bool m_isBinaryExpression;
3710         bool m_result;
3711 
3712+    protected:
3713+        ~ITransientExpression() = default;
3714+
3715     public:
3716         constexpr auto isBinaryExpression() const -> bool { return m_isBinaryExpression; }
3717         constexpr auto getResult() const -> bool { return m_result; }
3718-        //! This function **has** to be overriden by the derived class.
3719+        //! This function **has** to be overridden by the derived class.
3720         virtual void streamReconstructedExpression( std::ostream& os ) const;
3721 
3722         constexpr ITransientExpression( bool isBinaryExpression, bool result )
3723@@ -5298,17 +5359,13 @@ namespace Catch {
3724             m_result( result )
3725         {}
3726 
3727-        ITransientExpression() = default;
3728-        ITransientExpression(ITransientExpression const&) = default;
3729-        ITransientExpression& operator=(ITransientExpression const&) = default;
3730+        constexpr ITransientExpression( ITransientExpression const& ) = default;
3731+        constexpr ITransientExpression& operator=( ITransientExpression const& ) = default;
3732 
3733         friend std::ostream& operator<<(std::ostream& out, ITransientExpression const& expr) {
3734             expr.streamReconstructedExpression(out);
3735             return out;
3736         }
3737-
3738-    protected:
3739-        ~ITransientExpression() = default;
3740     };
3741 
3742     void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs );
3743@@ -5617,12 +5674,12 @@ namespace Catch {
3744 
3745 
3746         template<typename T>
3747-        void handleExpr( ExprLhs<T> const& expr ) {
3748+        constexpr void handleExpr( ExprLhs<T> const& expr ) {
3749             handleExpr( expr.makeUnaryExpr() );
3750         }
3751         void handleExpr( ITransientExpression const& expr );
3752 
3753-        void handleMessage(ResultWas::OfType resultType, StringRef message);
3754+        void handleMessage(ResultWas::OfType resultType, std::string&& message);
3755 
3756         void handleExceptionThrownAsExpected();
3757         void handleUnexpectedExceptionNotThrown();
3758@@ -5678,8 +5735,6 @@ namespace Catch {
3759 
3760 #endif
3761 
3762-#define INTERNAL_CATCH_REACT( handler ) handler.complete();
3763-
3764 ///////////////////////////////////////////////////////////////////////////////
3765 #define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \
3766     do { /* NOLINT(bugprone-infinite-loop) */ \
3767@@ -5692,7 +5747,7 @@ namespace Catch {
3768             catchAssertionHandler.handleExpr( Catch::Decomposer() <= __VA_ARGS__ ); /* NOLINT(bugprone-chained-comparison) */ \
3769             CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \
3770         } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \
3771-        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
3772+        catchAssertionHandler.complete(); \
3773     } while( (void)0, (false) && static_cast<const bool&>( !!(__VA_ARGS__) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look
3774     // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&.
3775 
3776@@ -5720,7 +5775,7 @@ namespace Catch {
3777         catch( ... ) { \
3778             catchAssertionHandler.handleUnexpectedInflightException(); \
3779         } \
3780-        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
3781+        catchAssertionHandler.complete(); \
3782     } while( false )
3783 
3784 ///////////////////////////////////////////////////////////////////////////////
3785@@ -5741,7 +5796,7 @@ namespace Catch {
3786             } \
3787         else \
3788             catchAssertionHandler.handleThrowingCallSkipped(); \
3789-        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
3790+        catchAssertionHandler.complete(); \
3791     } while( false )
3792 
3793 ///////////////////////////////////////////////////////////////////////////////
3794@@ -5765,7 +5820,7 @@ namespace Catch {
3795             } \
3796         else \
3797             catchAssertionHandler.handleThrowingCallSkipped(); \
3798-        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
3799+        catchAssertionHandler.complete(); \
3800     } while( false )
3801 
3802 
3803@@ -5789,7 +5844,7 @@ namespace Catch {
3804             } \
3805         else \
3806             catchAssertionHandler.handleThrowingCallSkipped(); \
3807-        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
3808+        catchAssertionHandler.complete(); \
3809     } while( false )
3810 
3811 #endif // CATCH_CONFIG_DISABLE
3812@@ -5897,7 +5952,7 @@ namespace Catch {
3813 #else
3814 
3815 // These section definitions imply that at most one section at one level
3816-// will be intered (because only one section's __LINE__ can be equal to
3817+// will be entered (because only one section's __LINE__ can be equal to
3818 // the dummy `catchInternalSectionHint` variable from `TEST_CASE`).
3819 
3820 namespace Catch {
3821@@ -5951,6 +6006,8 @@ namespace Catch {
3822 
3823     class ITestInvoker {
3824     public:
3825+        virtual void prepareTestCase();
3826+        virtual void tearDownTestCase();
3827         virtual void invoke() const = 0;
3828         virtual ~ITestInvoker(); // = default
3829     };
3830@@ -5988,7 +6045,8 @@ template<typename C>
3831 class TestInvokerAsMethod : public ITestInvoker {
3832     void (C::*m_testAsMethod)();
3833 public:
3834-    TestInvokerAsMethod( void (C::*testAsMethod)() ) noexcept : m_testAsMethod( testAsMethod ) {}
3835+    constexpr TestInvokerAsMethod( void ( C::*testAsMethod )() ) noexcept:
3836+        m_testAsMethod( testAsMethod ) {}
3837 
3838     void invoke() const override {
3839         C obj;
3840@@ -6003,6 +6061,34 @@ Detail::unique_ptr<ITestInvoker> makeTestInvoker( void (C::*testAsMethod)() ) {
3841     return Detail::make_unique<TestInvokerAsMethod<C>>( testAsMethod );
3842 }
3843 
3844+template <typename C>
3845+class TestInvokerFixture : public ITestInvoker {
3846+    void ( C::*m_testAsMethod )() const;
3847+    Detail::unique_ptr<C> m_fixture = nullptr;
3848+
3849+public:
3850+    constexpr TestInvokerFixture( void ( C::*testAsMethod )() const ) noexcept:
3851+        m_testAsMethod( testAsMethod ) {}
3852+
3853+    void prepareTestCase() override {
3854+        m_fixture = Detail::make_unique<C>();
3855+    }
3856+
3857+    void tearDownTestCase() override {
3858+        m_fixture.reset();
3859+    }
3860+
3861+    void invoke() const override {
3862+        auto* f = m_fixture.get();
3863+        ( f->*m_testAsMethod )();
3864+    }
3865+};
3866+
3867+template<typename C>
3868+Detail::unique_ptr<ITestInvoker> makeTestInvokerFixture( void ( C::*testAsMethod )() const ) {
3869+    return Detail::make_unique<TestInvokerFixture<C>>( testAsMethod );
3870+}
3871+
3872 struct NameAndTags {
3873     constexpr NameAndTags( StringRef name_ = StringRef(),
3874                            StringRef tags_ = StringRef() ) noexcept:
3875@@ -6099,6 +6185,26 @@ static int catchInternalSectionHint = 0;
3876     #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \
3877         INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), ClassName, __VA_ARGS__ )
3878 
3879+    ///////////////////////////////////////////////////////////////////////////////
3880+    #define INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE2( TestName, ClassName, ... )      \
3881+        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                             \
3882+        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                              \
3883+        CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS                      \
3884+        namespace {                                                           \
3885+            struct TestName : INTERNAL_CATCH_REMOVE_PARENS( ClassName ) {     \
3886+                void test() const;                                            \
3887+            };                                                                \
3888+            const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( \
3889+                Catch::makeTestInvokerFixture( &TestName::test ),                    \
3890+                CATCH_INTERNAL_LINEINFO,                                      \
3891+                #ClassName##_catch_sr,                                        \
3892+                Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */             \
3893+        }                                                                     \
3894+        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION                              \
3895+        void TestName::test() const
3896+    #define INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE( ClassName, ... )    \
3897+        INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), ClassName, __VA_ARGS__ )
3898+
3899 
3900     ///////////////////////////////////////////////////////////////////////////////
3901     #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \
3902@@ -6129,6 +6235,57 @@ static int catchInternalSectionHint = 0;
3903 #endif // CATCH_TEST_REGISTRY_HPP_INCLUDED
3904 
3905 
3906+#ifndef CATCH_UNREACHABLE_HPP_INCLUDED
3907+#define CATCH_UNREACHABLE_HPP_INCLUDED
3908+
3909+/**\file
3910+ * Polyfill `std::unreachable`
3911+ *
3912+ * We need something like `std::unreachable` to tell the compiler that
3913+ * some macros, e.g. `FAIL` or `SKIP`, do not continue execution in normal
3914+ * manner, and should handle it as such, e.g. not warn if there is no return
3915+ * from non-void function after a `FAIL` or `SKIP`.
3916+ */
3917+
3918+#include <exception>
3919+
3920+#if defined( __cpp_lib_unreachable ) && __cpp_lib_unreachable > 202202L
3921+#    include <utility>
3922+namespace Catch {
3923+    namespace Detail {
3924+        using Unreachable = std::unreachable;
3925+    }
3926+} // namespace Catch
3927+
3928+#else // vv If we do not have std::unreachable, we implement something similar
3929+
3930+namespace Catch {
3931+    namespace Detail {
3932+
3933+        [[noreturn]]
3934+        inline void Unreachable() noexcept {
3935+#    if defined( NDEBUG )
3936+#        if defined( _MSC_VER ) && !defined( __clang__ )
3937+            __assume( false );
3938+#        elif defined( __GNUC__ )
3939+            __builtin_unreachable();
3940+#        else // vv platform without known optimization hint
3941+            std::terminate();
3942+#        endif
3943+#    else  // ^^ NDEBUG
3944+            // For non-release builds, we prefer termination on bug over UB
3945+            std::terminate();
3946+#    endif //
3947+        }
3948+
3949+    } // namespace Detail
3950+} // end namespace Catch
3951+
3952+#endif
3953+
3954+#endif // CATCH_UNREACHABLE_HPP_INCLUDED
3955+
3956+
3957 // All of our user-facing macros support configuration toggle, that
3958 // forces them to be defined prefixed with CATCH_. We also like to
3959 // support another toggle that can minimize (disable) their implementation.
3960@@ -6156,13 +6313,20 @@ static int catchInternalSectionHint = 0;
3961   #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )
3962   #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )
3963   #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
3964+  #define CATCH_TEST_CASE_PERSISTENT_FIXTURE( className, ... ) INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE( className, __VA_ARGS__ )
3965   #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )
3966   #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
3967   #define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ )
3968-  #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ )
3969+  #define CATCH_FAIL( ... ) do { \
3970+           INTERNAL_CATCH_MSG("CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ );  \
3971+           Catch::Detail::Unreachable(); \
3972+         } while ( false )
3973   #define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
3974   #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
3975-  #define CATCH_SKIP( ... ) INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ )
3976+  #define CATCH_SKIP( ... ) do { \
3977+           INTERNAL_CATCH_MSG( "CATCH_SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ); \
3978+           Catch::Detail::Unreachable(); \
3979+         } while (false)
3980 
3981 
3982   #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE)
3983@@ -6210,6 +6374,7 @@ static int catchInternalSectionHint = 0;
3984   #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ))
3985   #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ))
3986   #define CATCH_METHOD_AS_TEST_CASE( method, ... )
3987+  #define CATCH_TEST_CASE_PERSISTENT_FIXTURE( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ))
3988   #define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0)
3989   #define CATCH_SECTION( ... )
3990   #define CATCH_DYNAMIC_SECTION( ... )
3991@@ -6255,13 +6420,20 @@ static int catchInternalSectionHint = 0;
3992   #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )
3993   #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )
3994   #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
3995+  #define TEST_CASE_PERSISTENT_FIXTURE( className, ... ) INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE( className, __VA_ARGS__ )
3996   #define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )
3997   #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
3998   #define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ )
3999-  #define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ )
4000+  #define FAIL( ... ) do { \
4001+           INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ); \
4002+           Catch::Detail::Unreachable(); \
4003+         } while (false)
4004   #define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
4005   #define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
4006-  #define SKIP( ... ) INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ )
4007+  #define SKIP( ... ) do { \
4008+           INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ); \
4009+           Catch::Detail::Unreachable(); \
4010+         } while (false)
4011 
4012 
4013   #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE)
4014@@ -6308,6 +6480,7 @@ static int catchInternalSectionHint = 0;
4015   #define TEST_CASE( ... )  INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), __VA_ARGS__)
4016   #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ))
4017   #define METHOD_AS_TEST_CASE( method, ... )
4018+  #define TEST_CASE_PERSISTENT_FIXTURE( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), __VA_ARGS__)
4019   #define REGISTER_TEST_CASE( Function, ... ) (void)(0)
4020   #define SECTION( ... )
4021   #define DYNAMIC_SECTION( ... )
4022@@ -6354,6 +6527,15 @@ static int catchInternalSectionHint = 0;
4023 #endif
4024 
4025 
4026+namespace Catch {
4027+    namespace Detail {
4028+        template <int N>
4029+        struct priority_tag : priority_tag<N - 1> {};
4030+        template <>
4031+        struct priority_tag<0> {};
4032+    }
4033+}
4034+
4035 #define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__
4036 #define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__)))
4037 #define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__)))
4038@@ -6413,10 +6595,10 @@ static int catchInternalSectionHint = 0;
4039 #define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name)
4040 
4041 #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
4042-#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>())
4043+#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>(Catch::Detail::priority_tag<1>{}))
4044 #define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))
4045 #else
4046-#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>()))
4047+#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>(Catch::Detail::priority_tag<1>{})))
4048 #define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)))
4049 #endif
4050 
4051@@ -6439,11 +6621,11 @@ static int catchInternalSectionHint = 0;
4052 
4053 #define INTERNAL_CATCH_TYPE_GEN\
4054     template<typename...> struct TypeList {};\
4055-    template<typename...Ts>\
4056-    constexpr auto get_wrapper() noexcept -> TypeList<Ts...> { return {}; }\
4057+    template<typename... Ts>\
4058+    constexpr auto get_wrapper(Catch::Detail::priority_tag<1>) noexcept -> TypeList<Ts...> { return {}; }\
4059     template<template<typename...> class...> struct TemplateTypeList{};\
4060     template<template<typename...> class...Cs>\
4061-    constexpr auto get_wrapper() noexcept -> TemplateTypeList<Cs...> { return {}; }\
4062+    constexpr auto get_wrapper(Catch::Detail::priority_tag<1>) noexcept -> TemplateTypeList<Cs...> { return {}; }\
4063     template<typename...>\
4064     struct append;\
4065     template<typename...>\
4066@@ -6473,10 +6655,10 @@ static int catchInternalSectionHint = 0;
4067 #define INTERNAL_CATCH_NTTP_1(signature, ...)\
4068     template<INTERNAL_CATCH_REMOVE_PARENS(signature)> struct Nttp{};\
4069     template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\
4070-    constexpr auto get_wrapper() noexcept -> Nttp<__VA_ARGS__> { return {}; } \
4071+    constexpr auto get_wrapper(Catch::Detail::priority_tag<0>) noexcept -> Nttp<__VA_ARGS__> { return {}; } \
4072     template<template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...> struct NttpTemplateTypeList{};\
4073     template<template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...Cs>\
4074-    constexpr auto get_wrapper() noexcept -> NttpTemplateTypeList<Cs...> { return {}; } \
4075+    constexpr auto get_wrapper(Catch::Detail::priority_tag<0>) noexcept -> NttpTemplateTypeList<Cs...> { return {}; } \
4076     \
4077     template< template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class Container, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class List, INTERNAL_CATCH_REMOVE_PARENS(signature)>\
4078     struct rewrap<NttpTemplateTypeList<Container>, List<__VA_ARGS__>> { using type = TypeList<Container<__VA_ARGS__>>; };\
4079@@ -6501,13 +6683,14 @@ static int catchInternalSectionHint = 0;
4080     template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\
4081     static void TestName()
4082 
4083-#define INTERNAL_CATCH_NTTP_REGISTER0(TestFunc, signature)\
4084-    template<typename Type>\
4085-    void reg_test(TypeList<Type>, Catch::NameAndTags nameAndTags)\
4086+#define INTERNAL_CATCH_TYPES_REGISTER(TestFunc)\
4087+    template<typename... Ts>\
4088+    void reg_test(TypeList<Ts...>, Catch::NameAndTags nameAndTags)\
4089     {\
4090-        Catch::AutoReg( Catch::makeTestInvoker(&TestFunc<Type>), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\
4091+        Catch::AutoReg( Catch::makeTestInvoker(&TestFunc<Ts...>), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\
4092     }
4093 
4094+#define INTERNAL_CATCH_NTTP_REGISTER0(TestFunc, signature, ...)
4095 #define INTERNAL_CATCH_NTTP_REGISTER(TestFunc, signature, ...)\
4096     template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\
4097     void reg_test(Nttp<__VA_ARGS__>, Catch::NameAndTags nameAndTags)\
4098@@ -6556,7 +6739,7 @@ static int catchInternalSectionHint = 0;
4099 #define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__)
4100 #define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__)
4101 #define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__)
4102-#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__)
4103+#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_TYPES_REGISTER(TestFunc) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__)
4104 #define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__)
4105 #define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__)
4106 #define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG,INTERNAL_CATCH_REMOVE_PARENS_10_ARG,INTERNAL_CATCH_REMOVE_PARENS_9_ARG,INTERNAL_CATCH_REMOVE_PARENS_8_ARG,INTERNAL_CATCH_REMOVE_PARENS_7_ARG,INTERNAL_CATCH_REMOVE_PARENS_6_ARG,INTERNAL_CATCH_REMOVE_PARENS_5_ARG,INTERNAL_CATCH_REMOVE_PARENS_4_ARG,INTERNAL_CATCH_REMOVE_PARENS_3_ARG,INTERNAL_CATCH_REMOVE_PARENS_2_ARG,INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__)
4107@@ -6566,7 +6749,7 @@ static int catchInternalSectionHint = 0;
4108 #define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__))
4109 #define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__))
4110 #define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__))
4111-#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__))
4112+#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_TYPES_REGISTER(TestFunc) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__))
4113 #define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__))
4114 #define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__))
4115 #define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG,INTERNAL_CATCH_REMOVE_PARENS_10_ARG,INTERNAL_CATCH_REMOVE_PARENS_9_ARG,INTERNAL_CATCH_REMOVE_PARENS_8_ARG,INTERNAL_CATCH_REMOVE_PARENS_7_ARG,INTERNAL_CATCH_REMOVE_PARENS_6_ARG,INTERNAL_CATCH_REMOVE_PARENS_5_ARG,INTERNAL_CATCH_REMOVE_PARENS_4_ARG,INTERNAL_CATCH_REMOVE_PARENS_3_ARG,INTERNAL_CATCH_REMOVE_PARENS_2_ARG,INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__))
4116@@ -6695,11 +6878,11 @@ static int catchInternalSectionHint = 0;
4117                     constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\
4118                     constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\
4119                     constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\
4120-                    (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFuncName<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + std::string(types_list[index % num_types]) + '>', Tags } ), index++)... };/* NOLINT */\
4121+                    (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFuncName<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + types_list[index % num_types] + '>', Tags } ), index++)... };/* NOLINT */\
4122                 }                                                     \
4123             };                                                        \
4124-            static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \
4125-                using TestInit = typename create<TestName, decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS(TmplTypes)>()), TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(INTERNAL_CATCH_REMOVE_PARENS(TypesList))>>::type; \
4126+            static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \
4127+                using TestInit = typename create<TestName, decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS(TmplTypes)>(Catch::Detail::priority_tag<1>{})), TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(INTERNAL_CATCH_REMOVE_PARENS(TypesList))>>::type; \
4128                 TestInit t;                                           \
4129                 t.reg_tests();                                        \
4130                 return 0;                                             \
4131@@ -6744,7 +6927,7 @@ static int catchInternalSectionHint = 0;
4132                 (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " INTERNAL_CATCH_STRINGIZE(TmplList) " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */\
4133             }                                                     \
4134         };\
4135-        static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \
4136+        static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \
4137                 using TestInit = typename convert<TestName, TmplList>::type; \
4138                 TestInit t;                                           \
4139                 t.reg_tests();                                        \
4140@@ -6780,7 +6963,7 @@ static int catchInternalSectionHint = 0;
4141                     (void)expander{(reg_test(Types{}, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index]), Tags } ), index++)... };/* NOLINT */ \
4142                 }\
4143             };\
4144-            static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\
4145+            static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\
4146                 TestNameClass<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(__VA_ARGS__)>();\
4147                 return 0;\
4148         }();\
4149@@ -6827,11 +7010,11 @@ static int catchInternalSectionHint = 0;
4150                     constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\
4151                     constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\
4152                     constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\
4153-                    (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + std::string(types_list[index % num_types]) + '>', Tags } ), index++)... };/* NOLINT */ \
4154+                    (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + types_list[index % num_types] + '>', Tags } ), index++)... };/* NOLINT */ \
4155                 }\
4156             };\
4157-            static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\
4158-                using TestInit = typename create<TestNameClass, decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS(TmplTypes)>()), TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(INTERNAL_CATCH_REMOVE_PARENS(TypesList))>>::type;\
4159+            static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\
4160+                using TestInit = typename create<TestNameClass, decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS(TmplTypes)>(Catch::Detail::priority_tag<1>{})), TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(INTERNAL_CATCH_REMOVE_PARENS(TypesList))>>::type;\
4161                 TestInit t;\
4162                 t.reg_tests();\
4163                 return 0;\
4164@@ -6879,7 +7062,7 @@ static int catchInternalSectionHint = 0;
4165                     (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName##_catch_sr, Catch::NameAndTags{ Name " - " INTERNAL_CATCH_STRINGIZE(TmplList) " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */ \
4166                 }\
4167             };\
4168-            static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\
4169+            static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\
4170                 using TestInit = typename convert<TestNameClass, TmplList>::type;\
4171                 TestInit t;\
4172                 t.reg_tests();\
4173@@ -7098,14 +7281,24 @@ namespace Catch {
4174         TestCaseInfo* m_info;
4175         ITestInvoker* m_invoker;
4176     public:
4177-        TestCaseHandle(TestCaseInfo* info, ITestInvoker* invoker) :
4178+        constexpr TestCaseHandle(TestCaseInfo* info, ITestInvoker* invoker) :
4179             m_info(info), m_invoker(invoker) {}
4180 
4181+        void prepareTestCase() const {
4182+            m_invoker->prepareTestCase();
4183+        }
4184+
4185+        void tearDownTestCase() const {
4186+            m_invoker->tearDownTestCase();
4187+        }
4188+
4189         void invoke() const {
4190             m_invoker->invoke();
4191         }
4192 
4193-        TestCaseInfo const& getTestCaseInfo() const;
4194+        constexpr TestCaseInfo const& getTestCaseInfo() const {
4195+            return *m_info;
4196+        }
4197     };
4198 
4199     Detail::unique_ptr<TestCaseInfo>
4200@@ -7121,6 +7314,22 @@ namespace Catch {
4201 #endif // CATCH_TEST_CASE_INFO_HPP_INCLUDED
4202 
4203 
4204+#ifndef CATCH_TEST_RUN_INFO_HPP_INCLUDED
4205+#define CATCH_TEST_RUN_INFO_HPP_INCLUDED
4206+
4207+
4208+namespace Catch {
4209+
4210+    struct TestRunInfo {
4211+        constexpr TestRunInfo(StringRef _name) : name(_name) {}
4212+        StringRef name;
4213+    };
4214+
4215+} // end namespace Catch
4216+
4217+#endif // CATCH_TEST_RUN_INFO_HPP_INCLUDED
4218+
4219+
4220 #ifndef CATCH_TRANSLATE_EXCEPTION_HPP_INCLUDED
4221 #define CATCH_TRANSLATE_EXCEPTION_HPP_INCLUDED
4222 
4223@@ -7168,7 +7377,7 @@ namespace Catch {
4224         class ExceptionTranslator : public IExceptionTranslator {
4225         public:
4226 
4227-            ExceptionTranslator( std::string(*translateFunction)( T const& ) )
4228+            constexpr ExceptionTranslator( std::string(*translateFunction)( T const& ) )
4229             : m_translateFunction( translateFunction )
4230             {}
4231 
4232@@ -7208,7 +7417,7 @@ namespace Catch {
4233     static std::string translatorName( signature ); \
4234     CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \
4235     CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
4236-    namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \
4237+    namespace{ const Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \
4238     CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \
4239     static std::string translatorName( signature )
4240 
4241@@ -7269,8 +7478,8 @@ namespace Catch {
4242 #define CATCH_VERSION_MACROS_HPP_INCLUDED
4243 
4244 #define CATCH_VERSION_MAJOR 3
4245-#define CATCH_VERSION_MINOR 5
4246-#define CATCH_VERSION_PATCH 4
4247+#define CATCH_VERSION_MINOR 11
4248+#define CATCH_VERSION_PATCH 0
4249 
4250 #endif // CATCH_VERSION_MACROS_HPP_INCLUDED
4251 
4252@@ -7311,7 +7520,7 @@ namespace Catch {
4253             m_msg(msg)
4254         {}
4255 
4256-        const char* what() const noexcept override final;
4257+        const char* what() const noexcept final;
4258     };
4259 
4260 } // end namespace Catch
4261@@ -7685,8 +7894,9 @@ namespace Generators {
4262     class FilterGenerator final : public IGenerator<T> {
4263         GeneratorWrapper<T> m_generator;
4264         Predicate m_predicate;
4265+        static_assert(!std::is_reference<Predicate>::value, "This would most likely result in a dangling reference");
4266     public:
4267-        template <typename P = Predicate>
4268+        template <typename P>
4269         FilterGenerator(P&& pred, GeneratorWrapper<T>&& generator):
4270             m_generator(CATCH_MOVE(generator)),
4271             m_predicate(CATCH_FORWARD(pred))
4272@@ -7718,7 +7928,7 @@ namespace Generators {
4273 
4274     template <typename T, typename Predicate>
4275     GeneratorWrapper<T> filter(Predicate&& pred, GeneratorWrapper<T>&& generator) {
4276-        return GeneratorWrapper<T>(Catch::Detail::make_unique<FilterGenerator<T, Predicate>>(CATCH_FORWARD(pred), CATCH_MOVE(generator)));
4277+        return GeneratorWrapper<T>(Catch::Detail::make_unique<FilterGenerator<T, typename std::remove_reference<Predicate>::type>>(CATCH_FORWARD(pred), CATCH_MOVE(generator)));
4278     }
4279 
4280     template <typename T>
4281@@ -7949,7 +8159,10 @@ namespace Catch {
4282 //       it, and it provides an escape hatch to the users who need it.
4283 #if defined( __SIZEOF_INT128__ )
4284 #    define CATCH_CONFIG_INTERNAL_UINT128
4285-#elif defined( _MSC_VER ) && ( defined( _WIN64 ) || defined( _M_ARM64 ) )
4286+// Unlike GCC, MSVC does not polyfill umul as mulh + mul pair on ARM machines.
4287+// Currently we do not bother doing this ourselves, but we could if it became
4288+// important for perf.
4289+#elif defined( _MSC_VER ) && defined( _M_X64 )
4290 #    define CATCH_CONFIG_INTERNAL_MSVC_UMUL128
4291 #endif
4292 
4293@@ -7964,7 +8177,6 @@ namespace Catch {
4294     !defined( CATCH_CONFIG_MSVC_UMUL128 )
4295 #    define CATCH_CONFIG_MSVC_UMUL128
4296 #    include <intrin.h>
4297-#    pragma intrinsic( _umul128 )
4298 #endif
4299 
4300 
4301@@ -7995,7 +8207,7 @@ namespace Catch {
4302         struct ExtendedMultResult {
4303             T upper;
4304             T lower;
4305-            bool operator==( ExtendedMultResult const& rhs ) const {
4306+            constexpr bool operator==( ExtendedMultResult const& rhs ) const {
4307                 return upper == rhs.upper && lower == rhs.lower;
4308             }
4309         };
4310@@ -8113,6 +8325,7 @@ namespace Catch {
4311          * get by simple casting ([0, ..., INT_MAX, INT_MIN, ..., -1])
4312          */
4313         template <typename OriginalType, typename UnsignedType>
4314+        constexpr
4315         std::enable_if_t<std::is_signed<OriginalType>::value, UnsignedType>
4316         transposeToNaturalOrder( UnsignedType in ) {
4317             static_assert(
4318@@ -8133,6 +8346,7 @@ namespace Catch {
4319 
4320         template <typename OriginalType,
4321                   typename UnsignedType>
4322+        constexpr
4323         std::enable_if_t<std::is_unsigned<OriginalType>::value, UnsignedType>
4324             transposeToNaturalOrder(UnsignedType in) {
4325             static_assert(
4326@@ -8184,24 +8398,24 @@ class uniform_integer_distribution {
4327     // distribution will be reused many times and this is an optimization.
4328     UnsignedIntegerType m_rejection_threshold = 0;
4329 
4330-    UnsignedIntegerType computeDistance(IntegerType a, IntegerType b) const {
4331+    static constexpr UnsignedIntegerType computeDistance(IntegerType a, IntegerType b) {
4332         // This overflows and returns 0 if a == 0 and b == TYPE_MAX.
4333         // We handle that later when generating the number.
4334         return transposeTo(b) - transposeTo(a) + 1;
4335     }
4336 
4337-    static UnsignedIntegerType computeRejectionThreshold(UnsignedIntegerType ab_distance) {
4338+    static constexpr UnsignedIntegerType computeRejectionThreshold(UnsignedIntegerType ab_distance) {
4339         // distance == 0 means that we will return all possible values from
4340         // the type's range, and that we shouldn't reject anything.
4341         if ( ab_distance == 0 ) { return 0; }
4342         return ( ~ab_distance + 1 ) % ab_distance;
4343     }
4344 
4345-    static UnsignedIntegerType transposeTo(IntegerType in) {
4346+    static constexpr UnsignedIntegerType transposeTo(IntegerType in) {
4347         return Detail::transposeToNaturalOrder<IntegerType>(
4348             static_cast<UnsignedIntegerType>( in ) );
4349     }
4350-    static IntegerType transposeBack(UnsignedIntegerType in) {
4351+    static constexpr IntegerType transposeBack(UnsignedIntegerType in) {
4352         return static_cast<IntegerType>(
4353             Detail::transposeToNaturalOrder<IntegerType>(in) );
4354     }
4355@@ -8209,7 +8423,7 @@ class uniform_integer_distribution {
4356 public:
4357     using result_type = IntegerType;
4358 
4359-    uniform_integer_distribution( IntegerType a, IntegerType b ):
4360+    constexpr uniform_integer_distribution( IntegerType a, IntegerType b ):
4361         m_a( transposeTo(a) ),
4362         m_ab_distance( computeDistance(a, b) ),
4363         m_rejection_threshold( computeRejectionThreshold(m_ab_distance) ) {
4364@@ -8217,7 +8431,7 @@ public:
4365     }
4366 
4367     template <typename Generator>
4368-    result_type operator()( Generator& g ) {
4369+    constexpr result_type operator()( Generator& g ) {
4370         // All possible values of result_type are valid.
4371         if ( m_ab_distance == 0 ) {
4372             return transposeBack( Detail::fillBitsFrom<UnsignedIntegerType>( g ) );
4373@@ -8235,8 +8449,8 @@ public:
4374         return transposeBack(m_a + emul.upper);
4375     }
4376 
4377-    result_type a() const { return transposeBack(m_a); }
4378-    result_type b() const { return transposeBack(m_ab_distance + m_a - 1); }
4379+    constexpr result_type a() const { return transposeBack(m_a); }
4380+    constexpr result_type b() const { return transposeBack(m_ab_distance + m_a - 1); }
4381 };
4382 
4383 } // end namespace Catch
4384@@ -8649,7 +8863,7 @@ public:
4385 
4386 template <typename InputIterator,
4387           typename InputSentinel,
4388-          typename ResultType = typename std::iterator_traits<InputIterator>::value_type>
4389+          typename ResultType = std::remove_const_t<typename std::iterator_traits<InputIterator>::value_type>>
4390 GeneratorWrapper<ResultType> from_range(InputIterator from, InputSentinel to) {
4391     return GeneratorWrapper<ResultType>(Catch::Detail::make_unique<IteratorGenerator<ResultType>>(from, to));
4392 }
4393@@ -8694,26 +8908,9 @@ auto from_range(Container const& cnt) {
4394 #define CATCH_INTERFACES_REPORTER_HPP_INCLUDED
4395 
4396 
4397-
4398-#ifndef CATCH_TEST_RUN_INFO_HPP_INCLUDED
4399-#define CATCH_TEST_RUN_INFO_HPP_INCLUDED
4400-
4401-
4402-namespace Catch {
4403-
4404-    struct TestRunInfo {
4405-        constexpr TestRunInfo(StringRef _name) : name(_name) {}
4406-        StringRef name;
4407-    };
4408-
4409-} // end namespace Catch
4410-
4411-#endif // CATCH_TEST_RUN_INFO_HPP_INCLUDED
4412-
4413 #include <map>
4414 #include <string>
4415 #include <vector>
4416-#include <iosfwd>
4417 
4418 namespace Catch {
4419 
4420@@ -8809,6 +9006,11 @@ namespace Catch {
4421         //! Catch2 should call `Reporter::assertionEnded` even for passing
4422         //! assertions
4423         bool shouldReportAllAssertions = false;
4424+        //! Catch2 should call `Reporter::assertionStarting` for all assertions
4425+        // Defaults to true for backwards compatibility, but none of our current
4426+        // reporters actually want this, and it enables a fast path in assertion
4427+        // handling.
4428+        bool shouldReportAllAssertionStarts = true;
4429     };
4430 
4431     /**
4432@@ -9331,6 +9533,17 @@ namespace Catch {
4433     bool isDebuggerActive();
4434 }
4435 
4436+#if !defined( CATCH_TRAP ) && defined( __clang__ ) && defined( __has_builtin )
4437+#    if __has_builtin( __builtin_debugtrap )
4438+#        define CATCH_TRAP() __builtin_debugtrap()
4439+#    endif
4440+#endif
4441+
4442+#if !defined( CATCH_TRAP ) && defined( _MSC_VER )
4443+#    define CATCH_TRAP() __debugbreak()
4444+#endif
4445+
4446+#if !defined(CATCH_TRAP) // If we couldn't use compiler-specific impl from above, we get into platform-specific options
4447 #ifdef CATCH_PLATFORM_MAC
4448 
4449     #if defined(__i386__) || defined(__x86_64__)
4450@@ -9355,7 +9568,7 @@ namespace Catch {
4451         #define CATCH_TRAP()  __asm__(".inst 0xde01")
4452     #endif
4453 
4454-#elif defined(CATCH_PLATFORM_LINUX)
4455+#elif defined(CATCH_PLATFORM_LINUX) || defined(CATCH_PLATFORM_QNX)
4456     // If we can use inline assembler, do it because this allows us to break
4457     // directly at the location of the failing check instead of breaking inside
4458     // raise() called from it, i.e. one stack frame below.
4459@@ -9366,15 +9579,15 @@ namespace Catch {
4460 
4461         #define CATCH_TRAP() raise(SIGTRAP)
4462     #endif
4463-#elif defined(_MSC_VER)
4464-    #define CATCH_TRAP() __debugbreak()
4465 #elif defined(__MINGW32__)
4466     extern "C" __declspec(dllimport) void __stdcall DebugBreak();
4467     #define CATCH_TRAP() DebugBreak()
4468 #endif
4469+#endif // ^^ CATCH_TRAP is not defined yet, so we define it
4470+
4471 
4472-#ifndef CATCH_BREAK_INTO_DEBUGGER
4473-    #ifdef CATCH_TRAP
4474+#if !defined(CATCH_BREAK_INTO_DEBUGGER)
4475+    #if defined(CATCH_TRAP)
4476         #define CATCH_BREAK_INTO_DEBUGGER() []{ if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } }()
4477     #else
4478         #define CATCH_BREAK_INTO_DEBUGGER() []{}()
4479@@ -9388,7 +9601,7 @@ namespace Catch {
4480 #define CATCH_ENFORCE_HPP_INCLUDED
4481 
4482 
4483-#include <exception>
4484+#include <exception> // for `std::exception` in no-exception configuration
4485 
4486 namespace Catch {
4487 #if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
4488@@ -9484,7 +9697,6 @@ namespace Catch {
4489 #define CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED
4490 
4491 
4492-#include <vector>
4493 #include <string>
4494 
4495 namespace Catch {
4496@@ -9684,8 +9896,8 @@ namespace Detail {
4497 #ifndef CATCH_IS_PERMUTATION_HPP_INCLUDED
4498 #define CATCH_IS_PERMUTATION_HPP_INCLUDED
4499 
4500-#include <algorithm>
4501 #include <iterator>
4502+#include <type_traits>
4503 
4504 namespace Catch {
4505     namespace Detail {
4506@@ -9694,6 +9906,7 @@ namespace Catch {
4507                   typename Sentinel,
4508                   typename T,
4509                   typename Comparator>
4510+        constexpr
4511         ForwardIter find_sentinel( ForwardIter start,
4512                                    Sentinel sentinel,
4513                                    T const& value,
4514@@ -9709,6 +9922,7 @@ namespace Catch {
4515                   typename Sentinel,
4516                   typename T,
4517                   typename Comparator>
4518+        constexpr
4519         std::ptrdiff_t count_sentinel( ForwardIter start,
4520                                        Sentinel sentinel,
4521                                        T const& value,
4522@@ -9722,6 +9936,7 @@ namespace Catch {
4523         }
4524 
4525         template <typename ForwardIter, typename Sentinel>
4526+        constexpr
4527         std::enable_if_t<!std::is_same<ForwardIter, Sentinel>::value,
4528                          std::ptrdiff_t>
4529         sentinel_distance( ForwardIter iter, const Sentinel sentinel ) {
4530@@ -9734,8 +9949,8 @@ namespace Catch {
4531         }
4532 
4533         template <typename ForwardIter>
4534-        std::ptrdiff_t sentinel_distance( ForwardIter first,
4535-                                          ForwardIter last ) {
4536+        constexpr std::ptrdiff_t sentinel_distance( ForwardIter first,
4537+                                                    ForwardIter last ) {
4538             return std::distance( first, last );
4539         }
4540 
4541@@ -9744,11 +9959,11 @@ namespace Catch {
4542                   typename ForwardIter2,
4543                   typename Sentinel2,
4544                   typename Comparator>
4545-        bool check_element_counts( ForwardIter1 first_1,
4546-                                   const Sentinel1 end_1,
4547-                                   ForwardIter2 first_2,
4548-                                   const Sentinel2 end_2,
4549-                                   Comparator cmp ) {
4550+        constexpr bool check_element_counts( ForwardIter1 first_1,
4551+                                             const Sentinel1 end_1,
4552+                                             ForwardIter2 first_2,
4553+                                             const Sentinel2 end_2,
4554+                                             Comparator cmp ) {
4555             auto cursor = first_1;
4556             while ( cursor != end_1 ) {
4557                 if ( find_sentinel( first_1, cursor, *cursor, cmp ) ==
4558@@ -9778,11 +9993,11 @@ namespace Catch {
4559                   typename ForwardIter2,
4560                   typename Sentinel2,
4561                   typename Comparator>
4562-        bool is_permutation( ForwardIter1 first_1,
4563-                             const Sentinel1 end_1,
4564-                             ForwardIter2 first_2,
4565-                             const Sentinel2 end_2,
4566-                             Comparator cmp ) {
4567+        constexpr bool is_permutation( ForwardIter1 first_1,
4568+                                       const Sentinel1 end_1,
4569+                                       ForwardIter2 first_2,
4570+                                       const Sentinel2 end_2,
4571+                                       Comparator cmp ) {
4572             // TODO: no optimization for stronger iterators, because we would also have to constrain on sentinel vs not sentinel types
4573             // TODO: Comparator has to be "both sides", e.g. a == b => b == a
4574             // This skips shared prefix of the two ranges
4575@@ -9819,8 +10034,6 @@ namespace Catch {
4576 
4577 
4578 #include <iosfwd>
4579-#include <cstddef>
4580-#include <ostream>
4581 #include <string>
4582 
4583 namespace Catch {
4584@@ -10029,106 +10242,67 @@ namespace Catch {
4585 #define CATCH_OUTPUT_REDIRECT_HPP_INCLUDED
4586 
4587 
4588-#include <cstdio>
4589-#include <iosfwd>
4590+#include <cassert>
4591 #include <string>
4592 
4593 namespace Catch {
4594 
4595-    class RedirectedStream {
4596-        std::ostream& m_originalStream;
4597-        std::ostream& m_redirectionStream;
4598-        std::streambuf* m_prevBuf;
4599-
4600-    public:
4601-        RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream );
4602-        ~RedirectedStream();
4603-    };
4604-
4605-    class RedirectedStdOut {
4606-        ReusableStringStream m_rss;
4607-        RedirectedStream m_cout;
4608-    public:
4609-        RedirectedStdOut();
4610-        auto str() const -> std::string;
4611-    };
4612-
4613-    // StdErr has two constituent streams in C++, std::cerr and std::clog
4614-    // This means that we need to redirect 2 streams into 1 to keep proper
4615-    // order of writes
4616-    class RedirectedStdErr {
4617-        ReusableStringStream m_rss;
4618-        RedirectedStream m_cerr;
4619-        RedirectedStream m_clog;
4620+    class OutputRedirect {
4621+        bool m_redirectActive = false;
4622+        virtual void activateImpl() = 0;
4623+        virtual void deactivateImpl() = 0;
4624     public:
4625-        RedirectedStdErr();
4626-        auto str() const -> std::string;
4627-    };
4628+        enum Kind {
4629+            //! No redirect (noop implementation)
4630+            None,
4631+            //! Redirect std::cout/std::cerr/std::clog streams internally
4632+            Streams,
4633+            //! Redirect the stdout/stderr file descriptors into files
4634+            FileDescriptors,
4635+        };
4636 
4637-    class RedirectedStreams {
4638-    public:
4639-        RedirectedStreams(RedirectedStreams const&) = delete;
4640-        RedirectedStreams& operator=(RedirectedStreams const&) = delete;
4641-        RedirectedStreams(RedirectedStreams&&) = delete;
4642-        RedirectedStreams& operator=(RedirectedStreams&&) = delete;
4643+        virtual ~OutputRedirect(); // = default;
4644 
4645-        RedirectedStreams(std::string& redirectedCout, std::string& redirectedCerr);
4646-        ~RedirectedStreams();
4647-    private:
4648-        std::string& m_redirectedCout;
4649-        std::string& m_redirectedCerr;
4650-        RedirectedStdOut m_redirectedStdOut;
4651-        RedirectedStdErr m_redirectedStdErr;
4652+        // TODO: Do we want to check that redirect is not active before retrieving the output?
4653+        virtual std::string getStdout() = 0;
4654+        virtual std::string getStderr() = 0;
4655+        virtual void clearBuffers() = 0;
4656+        bool isActive() const { return m_redirectActive; }
4657+        void activate() {
4658+            assert( !m_redirectActive && "redirect is already active" );
4659+            activateImpl();
4660+            m_redirectActive = true;
4661+        }
4662+        void deactivate() {
4663+            assert( m_redirectActive && "redirect is not active" );
4664+            deactivateImpl();
4665+            m_redirectActive = false;
4666+        }
4667     };
4668 
4669-#if defined(CATCH_CONFIG_NEW_CAPTURE)
4670-
4671-    // Windows's implementation of std::tmpfile is terrible (it tries
4672-    // to create a file inside system folder, thus requiring elevated
4673-    // privileges for the binary), so we have to use tmpnam(_s) and
4674-    // create the file ourselves there.
4675-    class TempFile {
4676-    public:
4677-        TempFile(TempFile const&) = delete;
4678-        TempFile& operator=(TempFile const&) = delete;
4679-        TempFile(TempFile&&) = delete;
4680-        TempFile& operator=(TempFile&&) = delete;
4681-
4682-        TempFile();
4683-        ~TempFile();
4684-
4685-        std::FILE* getFile();
4686-        std::string getContents();
4687-
4688-    private:
4689-        std::FILE* m_file = nullptr;
4690-    #if defined(_MSC_VER)
4691-        char m_buffer[L_tmpnam] = { 0 };
4692-    #endif
4693-    };
4694+    bool isRedirectAvailable( OutputRedirect::Kind kind);
4695+    Detail::unique_ptr<OutputRedirect> makeOutputRedirect( bool actual );
4696 
4697+    class RedirectGuard {
4698+        OutputRedirect* m_redirect;
4699+        bool m_activate;
4700+        bool m_previouslyActive;
4701+        bool m_moved = false;
4702 
4703-    class OutputRedirect {
4704     public:
4705-        OutputRedirect(OutputRedirect const&) = delete;
4706-        OutputRedirect& operator=(OutputRedirect const&) = delete;
4707-        OutputRedirect(OutputRedirect&&) = delete;
4708-        OutputRedirect& operator=(OutputRedirect&&) = delete;
4709+        RedirectGuard( bool activate, OutputRedirect& redirectImpl );
4710+        ~RedirectGuard() noexcept( false );
4711 
4712+        RedirectGuard( RedirectGuard const& ) = delete;
4713+        RedirectGuard& operator=( RedirectGuard const& ) = delete;
4714 
4715-        OutputRedirect(std::string& stdout_dest, std::string& stderr_dest);
4716-        ~OutputRedirect();
4717-
4718-    private:
4719-        int m_originalStdout = -1;
4720-        int m_originalStderr = -1;
4721-        TempFile m_stdoutFile;
4722-        TempFile m_stderrFile;
4723-        std::string& m_stdoutDest;
4724-        std::string& m_stderrDest;
4725+        // C++14 needs move-able guards to return them from functions
4726+        RedirectGuard( RedirectGuard&& rhs ) noexcept;
4727+        RedirectGuard& operator=( RedirectGuard&& rhs ) noexcept;
4728     };
4729 
4730-#endif
4731+    RedirectGuard scopedActivate( OutputRedirect& redirectImpl );
4732+    RedirectGuard scopedDeactivate( OutputRedirect& redirectImpl );
4733 
4734 } // end namespace Catch
4735 
4736@@ -10443,6 +10617,48 @@ using TestCaseTracking::SectionTracker;
4737 
4738 #endif // CATCH_TEST_CASE_TRACKER_HPP_INCLUDED
4739 
4740+
4741+#ifndef CATCH_THREAD_SUPPORT_HPP_INCLUDED
4742+#define CATCH_THREAD_SUPPORT_HPP_INCLUDED
4743+
4744+
4745+#if defined( CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS )
4746+#    include <atomic>
4747+#    include <mutex>
4748+#endif
4749+
4750+
4751+namespace Catch {
4752+    namespace Detail {
4753+#if defined( CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS )
4754+        using Mutex = std::mutex;
4755+        using LockGuard = std::lock_guard<std::mutex>;
4756+        struct AtomicCounts {
4757+            std::atomic<std::uint64_t> passed = 0;
4758+            std::atomic<std::uint64_t> failed = 0;
4759+            std::atomic<std::uint64_t> failedButOk = 0;
4760+            std::atomic<std::uint64_t> skipped = 0;
4761+        };
4762+#else // ^^ Use actual mutex, lock and atomics
4763+      // vv Dummy implementations for single-thread performance
4764+
4765+        struct Mutex {
4766+            void lock() {}
4767+            void unlock() {}
4768+        };
4769+
4770+        struct LockGuard {
4771+            LockGuard( Mutex ) {}
4772+        };
4773+
4774+        using AtomicCounts = Counts;
4775+#endif
4776+
4777+    } // namespace Detail
4778+} // namespace Catch
4779+
4780+#endif // CATCH_THREAD_SUPPORT_HPP_INCLUDED
4781+
4782 #include <string>
4783 
4784 namespace Catch {
4785@@ -10451,6 +10667,7 @@ namespace Catch {
4786     class IConfig;
4787     class IEventListener;
4788     using IEventListenerPtr = Detail::unique_ptr<IEventListener>;
4789+    class OutputRedirect;
4790 
4791     ///////////////////////////////////////////////////////////////////////////
4792 
4793@@ -10476,7 +10693,7 @@ namespace Catch {
4794         void handleMessage
4795                 (   AssertionInfo const& info,
4796                     ResultWas::OfType resultType,
4797-                    StringRef message,
4798+                    std::string&& message,
4799                     AssertionReaction& reaction ) override;
4800         void handleUnexpectedExceptionNotThrown
4801                 (   AssertionInfo const& info,
4802@@ -10514,11 +10731,6 @@ namespace Catch {
4803         void benchmarkEnded( BenchmarkStats<> const& stats ) override;
4804         void benchmarkFailed( StringRef error ) override;
4805 
4806-        void pushScopedMessage( MessageInfo const& message ) override;
4807-        void popScopedMessage( MessageInfo const& message ) override;
4808-
4809-        void emplaceUnscopedMessage( MessageBuilder&& builder ) override;
4810-
4811         std::string getCurrentTestName() const override;
4812 
4813         const AssertionResult* getLastResult() const override;
4814@@ -10529,18 +10741,18 @@ namespace Catch {
4815 
4816         bool lastAssertionPassed() override;
4817 
4818-        void assertionPassed() override;
4819-
4820     public:
4821         // !TBD We need to do this another way!
4822         bool aborting() const;
4823 
4824     private:
4825+        void assertionPassedFastPath( SourceLineInfo lineInfo );
4826+        // Update the non-thread-safe m_totals from the atomic assertion counts.
4827+        void updateTotalsFromAtomics();
4828 
4829-        void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr );
4830+        void runCurrentTest();
4831         void invokeActiveTestCase();
4832 
4833-        void resetAssertionInfo();
4834         bool testForMissingAssertions( Counts& assertions );
4835 
4836         void assertionEnded( AssertionResult&& result );
4837@@ -10550,30 +10762,39 @@ namespace Catch {
4838                     ITransientExpression const *expr,
4839                     bool negated );
4840 
4841-        void populateReaction( AssertionReaction& reaction );
4842+        void populateReaction( AssertionReaction& reaction, bool has_normal_disposition );
4843+
4844+        // Creates dummy info for unexpected exceptions/fatal errors,
4845+        // where we do not have the access to one, but we still need
4846+        // to send one to the reporters.
4847+        AssertionInfo makeDummyAssertionInfo();
4848 
4849     private:
4850 
4851         void handleUnfinishedSections();
4852-
4853+        mutable Detail::Mutex m_assertionMutex;
4854         TestRunInfo m_runInfo;
4855         TestCaseHandle const* m_activeTestCase = nullptr;
4856         ITracker* m_testCaseTracker = nullptr;
4857         Optional<AssertionResult> m_lastResult;
4858-
4859         IConfig const* m_config;
4860         Totals m_totals;
4861+        Detail::AtomicCounts m_atomicAssertionCount;
4862         IEventListenerPtr m_reporter;
4863-        std::vector<MessageInfo> m_messages;
4864-        std::vector<ScopedMessage> m_messageScopes; /* Keeps owners of so-called unscoped messages. */
4865-        AssertionInfo m_lastAssertionInfo;
4866         std::vector<SectionEndInfo> m_unfinishedSections;
4867         std::vector<ITracker*> m_activeSections;
4868         TrackerContext m_trackerContext;
4869+        Detail::unique_ptr<OutputRedirect> m_outputRedirect;
4870         FatalConditionHandler m_fatalConditionhandler;
4871-        bool m_lastAssertionPassed = false;
4872+        // Caches m_config->abortAfter() to avoid vptr calls/allow inlining
4873+        size_t m_abortAfterXFailedAssertions;
4874         bool m_shouldReportUnexpected = true;
4875+        // Caches whether `assertionStarting` events should be sent to the reporter.
4876+        bool m_reportAssertionStarting;
4877+        // Caches whether `assertionEnded` events for successful assertions should be sent to the reporter
4878         bool m_includeSuccessfulResults;
4879+        // Caches m_config->shouldDebugBreak() to avoid vptr calls/allow inlining
4880+        bool m_shouldDebugBreak;
4881     };
4882 
4883     void seedRng(IConfig const& config);
4884@@ -10587,7 +10808,6 @@ namespace Catch {
4885 #define CATCH_SHARDING_HPP_INCLUDED
4886 
4887 #include <cassert>
4888-#include <cmath>
4889 #include <algorithm>
4890 
4891 namespace Catch {
4892@@ -10944,6 +11164,107 @@ namespace Catch {
4893 
4894         class Columns;
4895 
4896+        /**
4897+         * Abstraction for a string with ansi escape sequences that
4898+         * automatically skips over escapes when iterating. Only graphical
4899+         * escape sequences are considered.
4900+         *
4901+         * Internal representation:
4902+         * An escape sequence looks like \033[39;49m
4903+         * We need bidirectional iteration and the unbound length of escape
4904+         * sequences poses a problem for operator-- To make this work we'll
4905+         * replace the last `m` with a 0xff (this is a codepoint that won't have
4906+         * any utf-8 meaning).
4907+         */
4908+        class AnsiSkippingString {
4909+            std::string m_string;
4910+            std::size_t m_size = 0;
4911+
4912+            // perform 0xff replacement and calculate m_size
4913+            void preprocessString();
4914+
4915+        public:
4916+            class const_iterator;
4917+            using iterator = const_iterator;
4918+            // note: must be u-suffixed or this will cause a "truncation of
4919+            // constant value" warning on MSVC
4920+            static constexpr char sentinel = static_cast<char>( 0xffu );
4921+
4922+            explicit AnsiSkippingString( std::string const& text );
4923+            explicit AnsiSkippingString( std::string&& text );
4924+
4925+            const_iterator begin() const;
4926+            const_iterator end() const;
4927+
4928+            size_t size() const { return m_size; }
4929+
4930+            std::string substring( const_iterator begin,
4931+                                   const_iterator end ) const;
4932+        };
4933+
4934+        class AnsiSkippingString::const_iterator {
4935+            friend AnsiSkippingString;
4936+            struct EndTag {};
4937+
4938+            const std::string* m_string;
4939+            std::string::const_iterator m_it;
4940+
4941+            explicit const_iterator( const std::string& string, EndTag ):
4942+                m_string( &string ), m_it( string.end() ) {}
4943+
4944+            void tryParseAnsiEscapes();
4945+            void advance();
4946+            void unadvance();
4947+
4948+        public:
4949+            using difference_type = std::ptrdiff_t;
4950+            using value_type = char;
4951+            using pointer = value_type*;
4952+            using reference = value_type&;
4953+            using iterator_category = std::bidirectional_iterator_tag;
4954+
4955+            explicit const_iterator( const std::string& string ):
4956+                m_string( &string ), m_it( string.begin() ) {
4957+                tryParseAnsiEscapes();
4958+            }
4959+
4960+            char operator*() const { return *m_it; }
4961+
4962+            const_iterator& operator++() {
4963+                advance();
4964+                return *this;
4965+            }
4966+            const_iterator operator++( int ) {
4967+                iterator prev( *this );
4968+                operator++();
4969+                return prev;
4970+            }
4971+            const_iterator& operator--() {
4972+                unadvance();
4973+                return *this;
4974+            }
4975+            const_iterator operator--( int ) {
4976+                iterator prev( *this );
4977+                operator--();
4978+                return prev;
4979+            }
4980+
4981+            bool operator==( const_iterator const& other ) const {
4982+                return m_it == other.m_it;
4983+            }
4984+            bool operator!=( const_iterator const& other ) const {
4985+                return !operator==( other );
4986+            }
4987+            bool operator<=( const_iterator const& other ) const {
4988+                return m_it <= other.m_it;
4989+            }
4990+
4991+            const_iterator oneBefore() const {
4992+                auto it = *this;
4993+                return --it;
4994+            }
4995+        };
4996+
4997         /**
4998          * Represents a column of text with specific width and indentation
4999          *
5000@@ -10953,10 +11274,11 @@ namespace Catch {
5001          */
5002         class Column {
5003             // String to be written out
5004-            std::string m_string;
5005+            AnsiSkippingString m_string;
5006             // Width of the column for linebreaking
5007             size_t m_width = CATCH_CONFIG_CONSOLE_WIDTH - 1;
5008-            // Indentation of other lines (including first if initial indent is unset)
5009+            // Indentation of other lines (including first if initial indent is
5010+            // unset)
5011             size_t m_indent = 0;
5012             // Indentation of the first line
5013             size_t m_initialIndent = std::string::npos;
5014@@ -10971,16 +11293,19 @@ namespace Catch {
5015 
5016                 Column const& m_column;
5017                 // Where does the current line start?
5018-                size_t m_lineStart = 0;
5019+                AnsiSkippingString::const_iterator m_lineStart;
5020                 // How long should the current line be?
5021-                size_t m_lineLength = 0;
5022+                AnsiSkippingString::const_iterator m_lineEnd;
5023                 // How far have we checked the string to iterate?
5024-                size_t m_parsedTo = 0;
5025+                AnsiSkippingString::const_iterator m_parsedTo;
5026                 // Should a '-' be appended to the line?
5027                 bool m_addHyphen = false;
5028 
5029                 const_iterator( Column const& column, EndTag ):
5030-                    m_column( column ), m_lineStart( m_column.m_string.size() ) {}
5031+                    m_column( column ),
5032+                    m_lineStart( m_column.m_string.end() ),
5033+                    m_lineEnd( column.m_string.end() ),
5034+                    m_parsedTo( column.m_string.end() ) {}
5035 
5036                 // Calculates the length of the current line
5037                 void calcLength();
5038@@ -10990,8 +11315,9 @@ namespace Catch {
5039 
5040                 // Creates an indented and (optionally) suffixed string from
5041                 // current iterator position, indentation and length.
5042-                std::string addIndentAndSuffix( size_t position,
5043-                                                size_t length ) const;
5044+                std::string addIndentAndSuffix(
5045+                    AnsiSkippingString::const_iterator start,
5046+                    AnsiSkippingString::const_iterator end ) const;
5047 
5048             public:
5049                 using difference_type = std::ptrdiff_t;
5050@@ -11008,7 +11334,8 @@ namespace Catch {
5051                 const_iterator operator++( int );
5052 
5053                 bool operator==( const_iterator const& other ) const {
5054-                    return m_lineStart == other.m_lineStart && &m_column == &other.m_column;
5055+                    return m_lineStart == other.m_lineStart &&
5056+                           &m_column == &other.m_column;
5057                 }
5058                 bool operator!=( const_iterator const& other ) const {
5059                     return !operator==( other );
5060@@ -11018,7 +11345,7 @@ namespace Catch {
5061 
5062             explicit Column( std::string const& text ): m_string( text ) {}
5063             explicit Column( std::string&& text ):
5064-                m_string( CATCH_MOVE(text)) {}
5065+                m_string( CATCH_MOVE( text ) ) {}
5066 
5067             Column& width( size_t newWidth ) & {
5068                 assert( newWidth > 0 );
5069@@ -11049,7 +11376,9 @@ namespace Catch {
5070 
5071             size_t width() const { return m_width; }
5072             const_iterator begin() const { return const_iterator( *this ); }
5073-            const_iterator end() const { return { *this, const_iterator::EndTag{} }; }
5074+            const_iterator end() const {
5075+                return { *this, const_iterator::EndTag{} };
5076+            }
5077 
5078             friend std::ostream& operator<<( std::ostream& os,
5079                                              Column const& col );
5080@@ -11151,16 +11480,25 @@ namespace Catch {
5081 
5082 #include <iosfwd>
5083 #include <vector>
5084+#include <cstdint>
5085 
5086 namespace Catch {
5087-    enum class XmlFormatting {
5088+    enum class XmlFormatting : std::uint8_t {
5089         None = 0x00,
5090         Indent = 0x01,
5091         Newline = 0x02,
5092     };
5093 
5094-    XmlFormatting operator | (XmlFormatting lhs, XmlFormatting rhs);
5095-    XmlFormatting operator & (XmlFormatting lhs, XmlFormatting rhs);
5096+    constexpr XmlFormatting operator|( XmlFormatting lhs, XmlFormatting rhs ) {
5097+        return static_cast<XmlFormatting>( static_cast<std::uint8_t>( lhs ) |
5098+                                           static_cast<std::uint8_t>( rhs ) );
5099+    }
5100+
5101+    constexpr XmlFormatting operator&( XmlFormatting lhs, XmlFormatting rhs ) {
5102+        return static_cast<XmlFormatting>( static_cast<std::uint8_t>( lhs ) &
5103+                                           static_cast<std::uint8_t>( rhs ) );
5104+    }
5105+
5106 
5107     /**
5108      * Helper for XML-encoding text (escaping angle brackets, quotes, etc)
5109@@ -11172,7 +11510,9 @@ namespace Catch {
5110     public:
5111         enum ForWhat { ForTextNodes, ForAttributes };
5112 
5113-        XmlEncode( StringRef str, ForWhat forWhat = ForTextNodes );
5114+        constexpr XmlEncode( StringRef str, ForWhat forWhat = ForTextNodes ):
5115+            m_str( str ), m_forWhat( forWhat ) {}
5116+
5117 
5118         void encodeTo( std::ostream& os ) const;
5119 
5120@@ -11320,12 +11660,22 @@ namespace Catch {
5121 
5122 namespace Catch {
5123 
5124+#ifdef __clang__
5125+#    pragma clang diagnostic push
5126+#    pragma clang diagnostic ignored "-Wsign-compare"
5127+#    pragma clang diagnostic ignored "-Wnon-virtual-dtor"
5128+#elif defined __GNUC__
5129+#    pragma GCC diagnostic push
5130+#    pragma GCC diagnostic ignored "-Wsign-compare"
5131+#    pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
5132+#endif
5133+
5134     template<typename ArgT, typename MatcherT>
5135     class MatchExpr : public ITransientExpression {
5136         ArgT && m_arg;
5137         MatcherT const& m_matcher;
5138     public:
5139-        MatchExpr( ArgT && arg, MatcherT const& matcher )
5140+        constexpr MatchExpr( ArgT && arg, MatcherT const& matcher )
5141         :   ITransientExpression{ true, matcher.match( arg ) }, // not forwarding arg here on purpose
5142             m_arg( CATCH_FORWARD(arg) ),
5143             m_matcher( matcher )
5144@@ -11338,6 +11688,13 @@ namespace Catch {
5145         }
5146     };
5147 
5148+#ifdef __clang__
5149+#    pragma clang diagnostic pop
5150+#elif defined __GNUC__
5151+#    pragma GCC diagnostic pop
5152+#endif
5153+
5154+
5155     namespace Matchers {
5156         template <typename ArgT>
5157         class MatcherBase;
5158@@ -11348,7 +11705,8 @@ namespace Catch {
5159     void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher );
5160 
5161     template<typename ArgT, typename MatcherT>
5162-    auto makeMatchExpr( ArgT && arg, MatcherT const& matcher ) -> MatchExpr<ArgT, MatcherT> {
5163+    constexpr MatchExpr<ArgT, MatcherT>
5164+    makeMatchExpr( ArgT&& arg, MatcherT const& matcher ) {
5165         return MatchExpr<ArgT, MatcherT>( CATCH_FORWARD(arg), matcher );
5166     }
5167 
5168@@ -11362,7 +11720,7 @@ namespace Catch {
5169         INTERNAL_CATCH_TRY { \
5170             catchAssertionHandler.handleExpr( Catch::makeMatchExpr( arg, matcher ) ); \
5171         } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \
5172-        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
5173+        catchAssertionHandler.complete(); \
5174     } while( false )
5175 
5176 
5177@@ -11372,7 +11730,10 @@ namespace Catch {
5178         Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(exceptionType) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \
5179         if( catchAssertionHandler.allowThrows() ) \
5180             try { \
5181+                CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \
5182+                CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \
5183                 static_cast<void>(__VA_ARGS__ ); \
5184+                CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \
5185                 catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \
5186             } \
5187             catch( exceptionType const& ex ) { \
5188@@ -11383,7 +11744,7 @@ namespace Catch {
5189             } \
5190         else \
5191             catchAssertionHandler.handleThrowingCallSkipped(); \
5192-        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
5193+        catchAssertionHandler.complete(); \
5194     } while( false )
5195 
5196 
5197@@ -11669,19 +12030,19 @@ namespace Matchers {
5198         }
5199 
5200         template<typename T>
5201-        using is_generic_matcher = std::is_base_of<
5202+        static constexpr bool is_generic_matcher_v = std::is_base_of<
5203             Catch::Matchers::MatcherGenericBase,
5204             std::remove_cv_t<std::remove_reference_t<T>>
5205-        >;
5206+        >::value;
5207 
5208         template<typename... Ts>
5209-        using are_generic_matchers = Catch::Detail::conjunction<is_generic_matcher<Ts>...>;
5210+        static constexpr bool are_generic_matchers_v = Catch::Detail::conjunction<std::integral_constant<bool,is_generic_matcher_v<Ts>>...>::value;
5211 
5212         template<typename T>
5213-        using is_matcher = std::is_base_of<
5214+        static constexpr bool is_matcher_v = std::is_base_of<
5215             Catch::Matchers::MatcherUntypedBase,
5216             std::remove_cv_t<std::remove_reference_t<T>>
5217-        >;
5218+        >::value;
5219 
5220 
5221         template<std::size_t N, typename Arg>
5222@@ -11754,7 +12115,7 @@ namespace Matchers {
5223 
5224             //! Avoids type nesting for `GenericAllOf && some matcher` case
5225             template<typename MatcherRHS>
5226-            friend std::enable_if_t<is_matcher<MatcherRHS>::value,
5227+            friend std::enable_if_t<is_matcher_v<MatcherRHS>,
5228             MatchAllOfGeneric<MatcherTs..., MatcherRHS>> operator && (
5229                     MatchAllOfGeneric<MatcherTs...>&& lhs,
5230                     MatcherRHS const& rhs) {
5231@@ -11763,7 +12124,7 @@ namespace Matchers {
5232 
5233             //! Avoids type nesting for `some matcher && GenericAllOf` case
5234             template<typename MatcherLHS>
5235-            friend std::enable_if_t<is_matcher<MatcherLHS>::value,
5236+            friend std::enable_if_t<is_matcher_v<MatcherLHS>,
5237             MatchAllOfGeneric<MatcherLHS, MatcherTs...>> operator && (
5238                     MatcherLHS const& lhs,
5239                     MatchAllOfGeneric<MatcherTs...>&& rhs) {
5240@@ -11808,7 +12169,7 @@ namespace Matchers {
5241 
5242             //! Avoids type nesting for `GenericAnyOf || some matcher` case
5243             template<typename MatcherRHS>
5244-            friend std::enable_if_t<is_matcher<MatcherRHS>::value,
5245+            friend std::enable_if_t<is_matcher_v<MatcherRHS>,
5246             MatchAnyOfGeneric<MatcherTs..., MatcherRHS>> operator || (
5247                     MatchAnyOfGeneric<MatcherTs...>&& lhs,
5248                     MatcherRHS const& rhs) {
5249@@ -11817,7 +12178,7 @@ namespace Matchers {
5250 
5251             //! Avoids type nesting for `some matcher || GenericAnyOf` case
5252             template<typename MatcherLHS>
5253-            friend std::enable_if_t<is_matcher<MatcherLHS>::value,
5254+            friend std::enable_if_t<is_matcher_v<MatcherLHS>,
5255             MatchAnyOfGeneric<MatcherLHS, MatcherTs...>> operator || (
5256                 MatcherLHS const& lhs,
5257                 MatchAnyOfGeneric<MatcherTs...>&& rhs) {
5258@@ -11857,20 +12218,20 @@ namespace Matchers {
5259 
5260     // compose only generic matchers
5261     template<typename MatcherLHS, typename MatcherRHS>
5262-    std::enable_if_t<Detail::are_generic_matchers<MatcherLHS, MatcherRHS>::value, Detail::MatchAllOfGeneric<MatcherLHS, MatcherRHS>>
5263+    std::enable_if_t<Detail::are_generic_matchers_v<MatcherLHS, MatcherRHS>, Detail::MatchAllOfGeneric<MatcherLHS, MatcherRHS>>
5264         operator && (MatcherLHS const& lhs, MatcherRHS const& rhs) {
5265         return { lhs, rhs };
5266     }
5267 
5268     template<typename MatcherLHS, typename MatcherRHS>
5269-    std::enable_if_t<Detail::are_generic_matchers<MatcherLHS, MatcherRHS>::value, Detail::MatchAnyOfGeneric<MatcherLHS, MatcherRHS>>
5270+    std::enable_if_t<Detail::are_generic_matchers_v<MatcherLHS, MatcherRHS>, Detail::MatchAnyOfGeneric<MatcherLHS, MatcherRHS>>
5271         operator || (MatcherLHS const& lhs, MatcherRHS const& rhs) {
5272         return { lhs, rhs };
5273     }
5274 
5275     //! Wrap provided generic matcher in generic negator
5276     template<typename MatcherT>
5277-    std::enable_if_t<Detail::is_generic_matcher<MatcherT>::value, Detail::MatchNotOfGeneric<MatcherT>>
5278+    std::enable_if_t<Detail::is_generic_matcher_v<MatcherT>, Detail::MatchNotOfGeneric<MatcherT>>
5279         operator ! (MatcherT const& matcher) {
5280         return Detail::MatchNotOfGeneric<MatcherT>{matcher};
5281     }
5282@@ -11878,25 +12239,25 @@ namespace Matchers {
5283 
5284     // compose mixed generic and non-generic matchers
5285     template<typename MatcherLHS, typename ArgRHS>
5286-    std::enable_if_t<Detail::is_generic_matcher<MatcherLHS>::value, Detail::MatchAllOfGeneric<MatcherLHS, MatcherBase<ArgRHS>>>
5287+    std::enable_if_t<Detail::is_generic_matcher_v<MatcherLHS>, Detail::MatchAllOfGeneric<MatcherLHS, MatcherBase<ArgRHS>>>
5288         operator && (MatcherLHS const& lhs, MatcherBase<ArgRHS> const& rhs) {
5289         return { lhs, rhs };
5290     }
5291 
5292     template<typename ArgLHS, typename MatcherRHS>
5293-    std::enable_if_t<Detail::is_generic_matcher<MatcherRHS>::value, Detail::MatchAllOfGeneric<MatcherBase<ArgLHS>, MatcherRHS>>
5294+    std::enable_if_t<Detail::is_generic_matcher_v<MatcherRHS>, Detail::MatchAllOfGeneric<MatcherBase<ArgLHS>, MatcherRHS>>
5295         operator && (MatcherBase<ArgLHS> const& lhs, MatcherRHS const& rhs) {
5296         return { lhs, rhs };
5297     }
5298 
5299     template<typename MatcherLHS, typename ArgRHS>
5300-    std::enable_if_t<Detail::is_generic_matcher<MatcherLHS>::value, Detail::MatchAnyOfGeneric<MatcherLHS, MatcherBase<ArgRHS>>>
5301+    std::enable_if_t<Detail::is_generic_matcher_v<MatcherLHS>, Detail::MatchAnyOfGeneric<MatcherLHS, MatcherBase<ArgRHS>>>
5302         operator || (MatcherLHS const& lhs, MatcherBase<ArgRHS> const& rhs) {
5303         return { lhs, rhs };
5304     }
5305 
5306     template<typename ArgLHS, typename MatcherRHS>
5307-    std::enable_if_t<Detail::is_generic_matcher<MatcherRHS>::value, Detail::MatchAnyOfGeneric<MatcherBase<ArgLHS>, MatcherRHS>>
5308+    std::enable_if_t<Detail::is_generic_matcher_v<MatcherRHS>, Detail::MatchAnyOfGeneric<MatcherBase<ArgLHS>, MatcherRHS>>
5309         operator || (MatcherBase<ArgLHS> const& lhs, MatcherRHS const& rhs) {
5310         return { lhs, rhs };
5311     }
5312@@ -11973,7 +12334,7 @@ namespace Catch {
5313         //! Creates a matcher that accepts ranges/containers with specific size
5314         HasSizeMatcher SizeIs(std::size_t sz);
5315         template <typename Matcher>
5316-        std::enable_if_t<Detail::is_matcher<Matcher>::value,
5317+        std::enable_if_t<Detail::is_matcher_v<Matcher>,
5318         SizeMatchesMatcher<Matcher>> SizeIs(Matcher&& m) {
5319             return SizeMatchesMatcher<Matcher>{CATCH_FORWARD(m)};
5320         }
5321@@ -11988,8 +12349,8 @@ namespace Catch {
5322 #define CATCH_MATCHERS_CONTAINS_HPP_INCLUDED
5323 
5324 
5325-#include <algorithm>
5326 #include <functional>
5327+#include <type_traits>
5328 
5329 namespace Catch {
5330     namespace Matchers {
5331@@ -12051,14 +12412,14 @@ namespace Catch {
5332          * Uses `std::equal_to` to do the comparison
5333          */
5334         template <typename T>
5335-        std::enable_if_t<!Detail::is_matcher<T>::value,
5336+        std::enable_if_t<!Detail::is_matcher_v<T>,
5337         ContainsElementMatcher<T, std::equal_to<>>> Contains(T&& elem) {
5338             return { CATCH_FORWARD(elem), std::equal_to<>{} };
5339         }
5340 
5341         //! Creates a matcher that checks whether a range contains element matching a matcher
5342         template <typename Matcher>
5343-        std::enable_if_t<Detail::is_matcher<Matcher>::value,
5344+        std::enable_if_t<Detail::is_matcher_v<Matcher>,
5345         ContainsMatcherMatcher<Matcher>> Contains(Matcher&& matcher) {
5346             return { CATCH_FORWARD(matcher) };
5347         }
5348@@ -12435,8 +12796,7 @@ namespace Catch {
5349 #define CATCH_MATCHERS_RANGE_EQUALS_HPP_INCLUDED
5350 
5351 
5352-#include <algorithm>
5353-#include <utility>
5354+#include <functional>
5355 
5356 namespace Catch {
5357     namespace Matchers {
5358@@ -12452,12 +12812,14 @@ namespace Catch {
5359 
5360         public:
5361             template <typename TargetRangeLike2, typename Equality2>
5362+            constexpr
5363             RangeEqualsMatcher( TargetRangeLike2&& range,
5364                                 Equality2&& predicate ):
5365                 m_desired( CATCH_FORWARD( range ) ),
5366                 m_predicate( CATCH_FORWARD( predicate ) ) {}
5367 
5368             template <typename RangeLike>
5369+            constexpr
5370             bool match( RangeLike&& rng ) const {
5371                 auto rng_start = begin( rng );
5372                 const auto rng_end = end( rng );
5373@@ -12490,12 +12852,14 @@ namespace Catch {
5374 
5375         public:
5376             template <typename TargetRangeLike2, typename Equality2>
5377+            constexpr
5378             UnorderedRangeEqualsMatcher( TargetRangeLike2&& range,
5379                                          Equality2&& predicate ):
5380                 m_desired( CATCH_FORWARD( range ) ),
5381                 m_predicate( CATCH_FORWARD( predicate ) ) {}
5382 
5383             template <typename RangeLike>
5384+            constexpr
5385             bool match( RangeLike&& rng ) const {
5386                 using std::begin;
5387                 using std::end;
5388@@ -12516,51 +12880,64 @@ namespace Catch {
5389          * Creates a matcher that checks if all elements in a range are equal
5390          * to all elements in another range.
5391          *
5392-         * Uses `std::equal_to` to do the comparison
5393+         * Uses the provided predicate `predicate` to do the comparisons
5394+         * (defaulting to `std::equal_to`)
5395          */
5396-        template <typename RangeLike>
5397-        std::enable_if_t<!Detail::is_matcher<RangeLike>::value,
5398-                         RangeEqualsMatcher<RangeLike, std::equal_to<>>>
5399-        RangeEquals( RangeLike&& range ) {
5400-            return { CATCH_FORWARD( range ), std::equal_to<>{} };
5401+        template <typename RangeLike,
5402+                  typename Equality = decltype( std::equal_to<>{} )>
5403+        constexpr
5404+        RangeEqualsMatcher<RangeLike, Equality>
5405+        RangeEquals( RangeLike&& range,
5406+                     Equality&& predicate = std::equal_to<>{} ) {
5407+            return { CATCH_FORWARD( range ), CATCH_FORWARD( predicate ) };
5408         }
5409 
5410         /**
5411          * Creates a matcher that checks if all elements in a range are equal
5412-         * to all elements in another range.
5413+         * to all elements in an initializer list.
5414          *
5415-         * Uses to provided predicate `predicate` to do the comparisons
5416+         * Uses the provided predicate `predicate` to do the comparisons
5417+         * (defaulting to `std::equal_to`)
5418          */
5419-        template <typename RangeLike, typename Equality>
5420-        RangeEqualsMatcher<RangeLike, Equality>
5421-        RangeEquals( RangeLike&& range, Equality&& predicate ) {
5422-            return { CATCH_FORWARD( range ), CATCH_FORWARD( predicate ) };
5423+        template <typename T,
5424+                  typename Equality = decltype( std::equal_to<>{} )>
5425+        constexpr
5426+        RangeEqualsMatcher<std::initializer_list<T>, Equality>
5427+        RangeEquals( std::initializer_list<T> range,
5428+                     Equality&& predicate = std::equal_to<>{} ) {
5429+            return { range, CATCH_FORWARD( predicate ) };
5430         }
5431 
5432         /**
5433          * Creates a matcher that checks if all elements in a range are equal
5434-         * to all elements in another range, in some permutation
5435+         * to all elements in another range, in some permutation.
5436          *
5437-         * Uses `std::equal_to` to do the comparison
5438+         * Uses the provided predicate `predicate` to do the comparisons
5439+         * (defaulting to `std::equal_to`)
5440          */
5441-        template <typename RangeLike>
5442-        std::enable_if_t<
5443-            !Detail::is_matcher<RangeLike>::value,
5444-            UnorderedRangeEqualsMatcher<RangeLike, std::equal_to<>>>
5445-        UnorderedRangeEquals( RangeLike&& range ) {
5446-            return { CATCH_FORWARD( range ), std::equal_to<>{} };
5447+        template <typename RangeLike,
5448+                  typename Equality = decltype( std::equal_to<>{} )>
5449+        constexpr
5450+        UnorderedRangeEqualsMatcher<RangeLike, Equality>
5451+        UnorderedRangeEquals( RangeLike&& range,
5452+                              Equality&& predicate = std::equal_to<>{} ) {
5453+            return { CATCH_FORWARD( range ), CATCH_FORWARD( predicate ) };
5454         }
5455 
5456         /**
5457          * Creates a matcher that checks if all elements in a range are equal
5458-         * to all elements in another range, in some permutation.
5459+         * to all elements in an initializer list, in some permutation.
5460          *
5461-         * Uses to provided predicate `predicate` to do the comparisons
5462+         * Uses the provided predicate `predicate` to do the comparisons
5463+         * (defaulting to `std::equal_to`)
5464          */
5465-        template <typename RangeLike, typename Equality>
5466-        UnorderedRangeEqualsMatcher<RangeLike, Equality>
5467-        UnorderedRangeEquals( RangeLike&& range, Equality&& predicate ) {
5468-            return { CATCH_FORWARD( range ), CATCH_FORWARD( predicate ) };
5469+        template <typename T,
5470+                  typename Equality = decltype( std::equal_to<>{} )>
5471+        constexpr
5472+        UnorderedRangeEqualsMatcher<std::initializer_list<T>, Equality>
5473+        UnorderedRangeEquals( std::initializer_list<T> range,
5474+                              Equality&& predicate = std::equal_to<>{} ) {
5475+            return { range, CATCH_FORWARD( predicate ) };
5476         }
5477     } // namespace Matchers
5478 } // namespace Catch
5479@@ -13003,9 +13380,11 @@ namespace Catch {
5480     public:
5481         // GCC5 compat: we cannot use inherited constructor, because it
5482         //              doesn't implement backport of P0136
5483-        AutomakeReporter(ReporterConfig&& _config):
5484-            StreamingReporterBase(CATCH_MOVE(_config))
5485-        {}
5486+        AutomakeReporter( ReporterConfig&& _config ):
5487+            StreamingReporterBase( CATCH_MOVE( _config ) ) {
5488+            m_preferences.shouldReportAllAssertionStarts = false;
5489+        }
5490+
5491         ~AutomakeReporter() override;
5492 
5493         static std::string getDescription() {
5494@@ -13032,7 +13411,10 @@ namespace Catch {
5495 
5496     class CompactReporter final : public StreamingReporterBase {
5497     public:
5498-        using StreamingReporterBase::StreamingReporterBase;
5499+        CompactReporter( ReporterConfig&& _config ):
5500+            StreamingReporterBase( CATCH_MOVE( _config ) ) {
5501+            m_preferences.shouldReportAllAssertionStarts = false;
5502+        }
5503 
5504         ~CompactReporter() override;
5505 
5506@@ -13074,8 +13456,6 @@ namespace Catch {
5507         void noMatchingTestCases( StringRef unmatchedSpec ) override;
5508         void reportInvalidTestSpec( StringRef arg ) override;
5509 
5510-        void assertionStarting(AssertionInfo const&) override;
5511-
5512         void assertionEnded(AssertionStats const& _assertionStats) override;
5513 
5514         void sectionStarting(SectionInfo const& _sectionInfo) override;
5515@@ -13429,7 +13809,6 @@ namespace Catch {
5516         void sectionStarting( SectionInfo const& sectionInfo ) override;
5517         void sectionEnded( SectionStats const& sectionStats ) override;
5518 
5519-        void assertionStarting( AssertionInfo const& assertionInfo ) override;
5520         void assertionEnded( AssertionStats const& assertionStats ) override;
5521 
5522         //void testRunEndedCumulative() override;
5523@@ -13556,6 +13935,11 @@ namespace Catch {
5524         void updatePreferences(IEventListener const& reporterish);
5525 
5526     public:
5527+        MultiReporter( IConfig const* config ):
5528+            IEventListener( config ) {
5529+            m_preferences.shouldReportAllAssertionStarts = false;
5530+        }
5531+
5532         using IEventListener::IEventListener;
5533 
5534         void addListener( IEventListenerPtr&& listener );
5535@@ -13691,22 +14075,24 @@ namespace Catch {
5536 
5537 #if !defined(CATCH_CONFIG_DISABLE)
5538 
5539-#    define CATCH_REGISTER_REPORTER( name, reporterType )                      \
5540-        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                              \
5541-        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                               \
5542-        namespace {                                                            \
5543-            Catch::ReporterRegistrar<reporterType> INTERNAL_CATCH_UNIQUE_NAME( \
5544-                catch_internal_RegistrarFor )( name );                         \
5545-        }                                                                      \
5546+#    define CATCH_REGISTER_REPORTER( name, reporterType )                  \
5547+        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                          \
5548+        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                           \
5549+        namespace {                                                        \
5550+            const Catch::ReporterRegistrar<reporterType>                   \
5551+                INTERNAL_CATCH_UNIQUE_NAME( catch_internal_RegistrarFor )( \
5552+                    name );                                                \
5553+        }                                                                  \
5554         CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
5555 
5556-#    define CATCH_REGISTER_LISTENER( listenerType )                            \
5557-        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                              \
5558-        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                               \
5559-        namespace {                                                            \
5560-            Catch::ListenerRegistrar<listenerType> INTERNAL_CATCH_UNIQUE_NAME( \
5561-                catch_internal_RegistrarFor )( #listenerType##_catch_sr );     \
5562-        }                                                                      \
5563+#    define CATCH_REGISTER_LISTENER( listenerType )                        \
5564+        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                          \
5565+        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                           \
5566+        namespace {                                                        \
5567+            const Catch::ListenerRegistrar<listenerType>                   \
5568+                INTERNAL_CATCH_UNIQUE_NAME( catch_internal_RegistrarFor )( \
5569+                    #listenerType##_catch_sr );                            \
5570+        }                                                                  \
5571         CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
5572 
5573 #else // CATCH_CONFIG_DISABLE
5574@@ -13732,7 +14118,8 @@ namespace Catch {
5575         : CumulativeReporterBase(CATCH_MOVE(config))
5576         , xml(m_stream) {
5577             m_preferences.shouldRedirectStdOut = true;
5578-            m_preferences.shouldReportAllAssertions = true;
5579+            m_preferences.shouldReportAllAssertions = false;
5580+            m_preferences.shouldReportAllAssertionStarts = false;
5581             m_shouldStoreSuccesfulAssertions = false;
5582         }
5583 
5584@@ -13781,6 +14168,7 @@ namespace Catch {
5585         TAPReporter( ReporterConfig&& config ):
5586             StreamingReporterBase( CATCH_MOVE(config) ) {
5587             m_preferences.shouldReportAllAssertions = true;
5588+            m_preferences.shouldReportAllAssertionStarts = false;
5589         }
5590 
5591         static std::string getDescription() {
5592@@ -13824,6 +14212,7 @@ namespace Catch {
5593         :   StreamingReporterBase( CATCH_MOVE(_config) )
5594         {
5595             m_preferences.shouldRedirectStdOut = true;
5596+            m_preferences.shouldReportAllAssertionStarts = false;
5597         }
5598 
5599         ~TeamCityReporter() override;
5600@@ -13891,8 +14280,6 @@ namespace Catch {
5601 
5602         void sectionStarting(SectionInfo const& sectionInfo) override;
5603 
5604-        void assertionStarting(AssertionInfo const&) override;
5605-
5606         void assertionEnded(AssertionStats const& assertionStats) override;
5607 
5608         void sectionEnded(SectionStats const& sectionStats) override;