diff options
| author | Martin Hořeňovský | 2024-08-14 12:05:21 +0200 |
|---|---|---|
| committer | Martin Hořeňovský | 2024-08-14 12:05:21 +0200 |
| commit | 31588bb4f56b638dd5afc28d3ebff9b9dcefb88d (patch) | |
| tree | b5ab431071a20481be60d6faa4c0edf3238b43f9 /extras/catch_amalgamated.cpp | |
| parent | f24569a1b4af70352d454ca46eb3be45a0ba51ef (diff) | |
| download | catch3-3.7.0.tar.gz | |
v3.7.0v3.7.0
Diffstat (limited to 'extras/catch_amalgamated.cpp')
| -rw-r--r-- | extras/catch_amalgamated.cpp | 477 |
1 files changed, 348 insertions, 129 deletions
diff --git a/extras/catch_amalgamated.cpp b/extras/catch_amalgamated.cpp index c080ad19..4178e328 100644 --- a/extras/catch_amalgamated.cpp +++ b/extras/catch_amalgamated.cpp @@ -6,8 +6,8 @@ // SPDX-License-Identifier: BSL-1.0 -// Catch v3.6.0 -// Generated: 2024-05-05 20:53:27.562886 +// Catch v3.7.0 +// Generated: 2024-08-14 12:04:53.604337 // ---------------------------------------------------------- // This file is an amalgamation of multiple different files. // You probably shouldn't edit it directly. @@ -128,7 +128,13 @@ namespace Catch { namespace Catch { namespace Benchmark { namespace Detail { + struct do_nothing { + void operator()() const {} + }; + BenchmarkFunction::callable::~callable() = default; + BenchmarkFunction::BenchmarkFunction(): + f( new model<do_nothing>{ {} } ){} } // namespace Detail } // namespace Benchmark } // namespace Catch @@ -1040,6 +1046,7 @@ namespace Catch { m_messages.back().message += " := "; start = pos; } + break; default:; // noop } } @@ -2273,7 +2280,7 @@ namespace Catch { } Version const& libraryVersion() { - static Version version( 3, 6, 0, "", 0 ); + static Version version( 3, 7, 0, "", 0 ); return version; } @@ -4808,138 +4815,328 @@ namespace Catch { #include <cstdio> #include <cstring> +#include <iosfwd> #include <sstream> -#if defined(CATCH_CONFIG_NEW_CAPTURE) - #if defined(_MSC_VER) - #include <io.h> //_dup and _dup2 - #define dup _dup - #define dup2 _dup2 - #define fileno _fileno - #else - #include <unistd.h> // dup and dup2 - #endif +#if defined( CATCH_CONFIG_NEW_CAPTURE ) +# if defined( _MSC_VER ) +# include <io.h> //_dup and _dup2 +# define dup _dup +# define dup2 _dup2 +# define fileno _fileno +# else +# include <unistd.h> // dup and dup2 +# endif #endif - namespace Catch { - RedirectedStream::RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ) - : m_originalStream( originalStream ), - m_redirectionStream( redirectionStream ), - m_prevBuf( m_originalStream.rdbuf() ) - { - m_originalStream.rdbuf( m_redirectionStream.rdbuf() ); - } + namespace { + //! A no-op implementation, used if no reporter wants output + //! redirection. + class NoopRedirect : public OutputRedirect { + void activateImpl() override {} + void deactivateImpl() override {} + std::string getStdout() override { return {}; } + std::string getStderr() override { return {}; } + void clearBuffers() override {} + }; - RedirectedStream::~RedirectedStream() { - m_originalStream.rdbuf( m_prevBuf ); - } + /** + * Redirects specific stream's rdbuf with another's. + * + * Redirection can be stopped and started on-demand, assumes + * that the underlying stream's rdbuf aren't changed by other + * users. + */ + class RedirectedStreamNew { + std::ostream& m_originalStream; + std::ostream& m_redirectionStream; + std::streambuf* m_prevBuf; - RedirectedStdOut::RedirectedStdOut() : m_cout( Catch::cout(), m_rss.get() ) {} - auto RedirectedStdOut::str() const -> std::string { return m_rss.str(); } + public: + RedirectedStreamNew( std::ostream& originalStream, + std::ostream& redirectionStream ): + m_originalStream( originalStream ), + m_redirectionStream( redirectionStream ), + m_prevBuf( m_originalStream.rdbuf() ) {} - RedirectedStdErr::RedirectedStdErr() - : m_cerr( Catch::cerr(), m_rss.get() ), - m_clog( Catch::clog(), m_rss.get() ) - {} - auto RedirectedStdErr::str() const -> std::string { return m_rss.str(); } + void startRedirect() { + m_originalStream.rdbuf( m_redirectionStream.rdbuf() ); + } + void stopRedirect() { m_originalStream.rdbuf( m_prevBuf ); } + }; - RedirectedStreams::RedirectedStreams(std::string& redirectedCout, std::string& redirectedCerr) - : m_redirectedCout(redirectedCout), - m_redirectedCerr(redirectedCerr) - {} + /** + * Redirects the `std::cout`, `std::cerr`, `std::clog` streams, + * but does not touch the actual `stdout`/`stderr` file descriptors. + */ + class StreamRedirect : public OutputRedirect { + ReusableStringStream m_redirectedOut, m_redirectedErr; + RedirectedStreamNew m_cout, m_cerr, m_clog; - RedirectedStreams::~RedirectedStreams() { - m_redirectedCout += m_redirectedStdOut.str(); - m_redirectedCerr += m_redirectedStdErr.str(); - } + public: + StreamRedirect(): + m_cout( Catch::cout(), m_redirectedOut.get() ), + m_cerr( Catch::cerr(), m_redirectedErr.get() ), + m_clog( Catch::clog(), m_redirectedErr.get() ) {} + + void activateImpl() override { + m_cout.startRedirect(); + m_cerr.startRedirect(); + m_clog.startRedirect(); + } + void deactivateImpl() override { + m_cout.stopRedirect(); + m_cerr.stopRedirect(); + m_clog.stopRedirect(); + } + std::string getStdout() override { return m_redirectedOut.str(); } + std::string getStderr() override { return m_redirectedErr.str(); } + void clearBuffers() override { + m_redirectedOut.str( "" ); + m_redirectedErr.str( "" ); + } + }; -#if defined(CATCH_CONFIG_NEW_CAPTURE) +#if defined( CATCH_CONFIG_NEW_CAPTURE ) -#if defined(_MSC_VER) - TempFile::TempFile() { - if (tmpnam_s(m_buffer)) { - CATCH_RUNTIME_ERROR("Could not get a temp filename"); - } - if (fopen_s(&m_file, m_buffer, "w+")) { - char buffer[100]; - if (strerror_s(buffer, errno)) { - CATCH_RUNTIME_ERROR("Could not translate errno to a string"); + // Windows's implementation of std::tmpfile is terrible (it tries + // to create a file inside system folder, thus requiring elevated + // privileges for the binary), so we have to use tmpnam(_s) and + // create the file ourselves there. + class TempFile { + public: + TempFile( TempFile const& ) = delete; + TempFile& operator=( TempFile const& ) = delete; + TempFile( TempFile&& ) = delete; + TempFile& operator=( TempFile&& ) = delete; + +# if defined( _MSC_VER ) + TempFile() { + if ( tmpnam_s( m_buffer ) ) { + CATCH_RUNTIME_ERROR( "Could not get a temp filename" ); + } + if ( fopen_s( &m_file, m_buffer, "wb+" ) ) { + char buffer[100]; + if ( strerror_s( buffer, errno ) ) { + CATCH_RUNTIME_ERROR( + "Could not translate errno to a string" ); + } + CATCH_RUNTIME_ERROR( "Could not open the temp file: '" + << m_buffer + << "' because: " << buffer ); + } + } +# else + TempFile() { + m_file = std::tmpfile(); + if ( !m_file ) { + CATCH_RUNTIME_ERROR( "Could not create a temp file." ); + } } - CATCH_RUNTIME_ERROR("Could not open the temp file: '" << m_buffer << "' because: " << buffer); +# endif + + ~TempFile() { + // TBD: What to do about errors here? + std::fclose( m_file ); + // We manually create the file on Windows only, on Linux + // it will be autodeleted +# if defined( _MSC_VER ) + std::remove( m_buffer ); +# endif + } + + std::FILE* getFile() { return m_file; } + std::string getContents() { + ReusableStringStream sstr; + constexpr long buffer_size = 100; + char buffer[buffer_size + 1] = {}; + long current_pos = ftell( m_file ); + CATCH_ENFORCE( current_pos >= 0, + "ftell failed, errno: " << errno ); + std::rewind( m_file ); + while ( current_pos > 0 ) { + auto read_characters = + std::fread( buffer, + 1, + std::min( buffer_size, current_pos ), + m_file ); + buffer[read_characters] = '\0'; + sstr << buffer; + current_pos -= static_cast<long>( read_characters ); + } + return sstr.str(); + } + + void clear() { std::rewind( m_file ); } + + private: + std::FILE* m_file = nullptr; + char m_buffer[L_tmpnam] = { 0 }; + }; + + /** + * Redirects the actual `stdout`/`stderr` file descriptors. + * + * Works by replacing the file descriptors numbered 1 and 2 + * with an open temporary file. + */ + class FileRedirect : public OutputRedirect { + TempFile m_outFile, m_errFile; + int m_originalOut = -1; + int m_originalErr = -1; + + // Flushes cout/cerr/clog streams and stdout/stderr FDs + void flushEverything() { + Catch::cout() << std::flush; + fflush( stdout ); + // Since we support overriding these streams, we flush cerr + // even though std::cerr is unbuffered + Catch::cerr() << std::flush; + Catch::clog() << std::flush; + fflush( stderr ); + } + + public: + FileRedirect(): + m_originalOut( dup( fileno( stdout ) ) ), + m_originalErr( dup( fileno( stderr ) ) ) { + CATCH_ENFORCE( m_originalOut >= 0, "Could not dup stdout" ); + CATCH_ENFORCE( m_originalErr >= 0, "Could not dup stderr" ); + } + + std::string getStdout() override { return m_outFile.getContents(); } + std::string getStderr() override { return m_errFile.getContents(); } + void clearBuffers() override { + m_outFile.clear(); + m_errFile.clear(); + } + + void activateImpl() override { + // We flush before starting redirect, to ensure that we do + // not capture the end of message sent before activation. + flushEverything(); + + int ret; + ret = dup2( fileno( m_outFile.getFile() ), fileno( stdout ) ); + CATCH_ENFORCE( ret >= 0, + "dup2 to stdout has failed, errno: " << errno ); + ret = dup2( fileno( m_errFile.getFile() ), fileno( stderr ) ); + CATCH_ENFORCE( ret >= 0, + "dup2 to stderr has failed, errno: " << errno ); + } + void deactivateImpl() override { + // We flush before ending redirect, to ensure that we + // capture all messages sent while the redirect was active. + flushEverything(); + + int ret; + ret = dup2( m_originalOut, fileno( stdout ) ); + CATCH_ENFORCE( + ret >= 0, + "dup2 of original stdout has failed, errno: " << errno ); + ret = dup2( m_originalErr, fileno( stderr ) ); + CATCH_ENFORCE( + ret >= 0, + "dup2 of original stderr has failed, errno: " << errno ); + } + }; + +#endif // CATCH_CONFIG_NEW_CAPTURE + + } // end namespace + + bool isRedirectAvailable( OutputRedirect::Kind kind ) { + switch ( kind ) { + // These two are always available + case OutputRedirect::None: + case OutputRedirect::Streams: + return true; +#if defined( CATCH_CONFIG_NEW_CAPTURE ) + case OutputRedirect::FileDescriptors: + return true; +#endif + default: + return false; } } + + Detail::unique_ptr<OutputRedirect> makeOutputRedirect( bool actual ) { + if ( actual ) { + // TODO: Clean this up later +#if defined( CATCH_CONFIG_NEW_CAPTURE ) + return Detail::make_unique<FileRedirect>(); #else - TempFile::TempFile() { - m_file = std::tmpfile(); - if (!m_file) { - CATCH_RUNTIME_ERROR("Could not create a temp file."); + return Detail::make_unique<StreamRedirect>(); +#endif + } else { + return Detail::make_unique<NoopRedirect>(); } } -#endif + RedirectGuard scopedActivate( OutputRedirect& redirectImpl ) { + return RedirectGuard( true, redirectImpl ); + } - TempFile::~TempFile() { - // TBD: What to do about errors here? - std::fclose(m_file); - // We manually create the file on Windows only, on Linux - // it will be autodeleted -#if defined(_MSC_VER) - std::remove(m_buffer); -#endif + RedirectGuard scopedDeactivate( OutputRedirect& redirectImpl ) { + return RedirectGuard( false, redirectImpl ); } + OutputRedirect::~OutputRedirect() = default; - FILE* TempFile::getFile() { - return m_file; - } + RedirectGuard::RedirectGuard( bool activate, OutputRedirect& redirectImpl ): + m_redirect( &redirectImpl ), + m_activate( activate ), + m_previouslyActive( redirectImpl.isActive() ) { - std::string TempFile::getContents() { - std::stringstream sstr; - char buffer[100] = {}; - std::rewind(m_file); - while (std::fgets(buffer, sizeof(buffer), m_file)) { - sstr << buffer; - } - return sstr.str(); - } + // Skip cases where there is no actual state change. + if ( m_activate == m_previouslyActive ) { return; } - OutputRedirect::OutputRedirect(std::string& stdout_dest, std::string& stderr_dest) : - m_originalStdout(dup(1)), - m_originalStderr(dup(2)), - m_stdoutDest(stdout_dest), - m_stderrDest(stderr_dest) { - dup2(fileno(m_stdoutFile.getFile()), 1); - dup2(fileno(m_stderrFile.getFile()), 2); + if ( m_activate ) { + m_redirect->activate(); + } else { + m_redirect->deactivate(); + } } - OutputRedirect::~OutputRedirect() { - Catch::cout() << std::flush; - fflush(stdout); - // Since we support overriding these streams, we flush cerr - // even though std::cerr is unbuffered - Catch::cerr() << std::flush; - Catch::clog() << std::flush; - fflush(stderr); + RedirectGuard::~RedirectGuard() noexcept( false ) { + if ( m_moved ) { return; } + // Skip cases where there is no actual state change. + if ( m_activate == m_previouslyActive ) { return; } - dup2(m_originalStdout, 1); - dup2(m_originalStderr, 2); + if ( m_activate ) { + m_redirect->deactivate(); + } else { + m_redirect->activate(); + } + } - m_stdoutDest += m_stdoutFile.getContents(); - m_stderrDest += m_stderrFile.getContents(); + RedirectGuard::RedirectGuard( RedirectGuard&& rhs ) noexcept: + m_redirect( rhs.m_redirect ), + m_activate( rhs.m_activate ), + m_previouslyActive( rhs.m_previouslyActive ), + m_moved( false ) { + rhs.m_moved = true; } -#endif // CATCH_CONFIG_NEW_CAPTURE + RedirectGuard& RedirectGuard::operator=( RedirectGuard&& rhs ) noexcept { + m_redirect = rhs.m_redirect; + m_activate = rhs.m_activate; + m_previouslyActive = rhs.m_previouslyActive; + m_moved = false; + rhs.m_moved = true; + return *this; + } } // namespace Catch -#if defined(CATCH_CONFIG_NEW_CAPTURE) - #if defined(_MSC_VER) - #undef dup - #undef dup2 - #undef fileno - #endif +#if defined( CATCH_CONFIG_NEW_CAPTURE ) +# if defined( _MSC_VER ) +# undef dup +# undef dup2 +# undef fileno +# endif #endif @@ -5573,6 +5770,7 @@ namespace Catch { m_config(_config), m_reporter(CATCH_MOVE(reporter)), m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal }, + m_outputRedirect( makeOutputRedirect( m_reporter->getPreferences().shouldRedirectStdOut ) ), m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ) { getCurrentMutableContext().setResultCapture( this ); @@ -5588,6 +5786,7 @@ namespace Catch { auto const& testInfo = testCase.getTestCaseInfo(); m_reporter->testCaseStarting(testInfo); + testCase.prepareTestCase(); m_activeTestCase = &testCase; @@ -5638,15 +5837,17 @@ namespace Catch { m_reporter->testCasePartialStarting(testInfo, testRuns); const auto beforeRunTotals = m_totals; - std::string oneRunCout, oneRunCerr; - runCurrentTest(oneRunCout, oneRunCerr); + runCurrentTest(); + std::string oneRunCout = m_outputRedirect->getStdout(); + std::string oneRunCerr = m_outputRedirect->getStderr(); + m_outputRedirect->clearBuffers(); redirectedCout += oneRunCout; redirectedCerr += oneRunCerr; const auto singleRunTotals = m_totals.delta(beforeRunTotals); auto statsForOneRun = TestCaseStats(testInfo, singleRunTotals, CATCH_MOVE(oneRunCout), CATCH_MOVE(oneRunCerr), aborting()); - m_reporter->testCasePartialEnded(statsForOneRun, testRuns); + ++testRuns; } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting()); @@ -5657,6 +5858,7 @@ namespace Catch { deltaTotals.testCases.failed++; } m_totals.testCases += deltaTotals.testCases; + testCase.tearDownTestCase(); m_reporter->testCaseEnded(TestCaseStats(testInfo, deltaTotals, CATCH_MOVE(redirectedCout), @@ -5690,7 +5892,10 @@ namespace Catch { m_lastAssertionPassed = true; } - m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals)); + { + auto _ = scopedDeactivate( *m_outputRedirect ); + m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ); + } if ( result.getResultType() != ResultWas::Warning ) { m_messageScopes.clear(); @@ -5707,6 +5912,7 @@ namespace Catch { } void RunContext::notifyAssertionStarted( AssertionInfo const& info ) { + auto _ = scopedDeactivate( *m_outputRedirect ); m_reporter->assertionStarting( info ); } @@ -5725,7 +5931,10 @@ namespace Catch { SectionInfo sectionInfo( sectionLineInfo, static_cast<std::string>(sectionName) ); m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; - m_reporter->sectionStarting(sectionInfo); + { + auto _ = scopedDeactivate( *m_outputRedirect ); + m_reporter->sectionStarting( sectionInfo ); + } assertions = m_totals.assertions; @@ -5785,7 +5994,15 @@ namespace Catch { m_activeSections.pop_back(); } - m_reporter->sectionEnded(SectionStats(CATCH_MOVE(endInfo.sectionInfo), assertions, endInfo.durationInSeconds, missingAssertions)); + { + auto _ = scopedDeactivate( *m_outputRedirect ); + m_reporter->sectionEnded( + SectionStats( CATCH_MOVE( endInfo.sectionInfo ), + assertions, + endInfo.durationInSeconds, + missingAssertions ) ); + } + m_messages.clear(); m_messageScopes.clear(); } @@ -5802,15 +6019,19 @@ namespace Catch { } void RunContext::benchmarkPreparing( StringRef name ) { - m_reporter->benchmarkPreparing(name); + auto _ = scopedDeactivate( *m_outputRedirect ); + m_reporter->benchmarkPreparing( name ); } void RunContext::benchmarkStarting( BenchmarkInfo const& info ) { + auto _ = scopedDeactivate( *m_outputRedirect ); m_reporter->benchmarkStarting( info ); } void RunContext::benchmarkEnded( BenchmarkStats<> const& stats ) { + auto _ = scopedDeactivate( *m_outputRedirect ); m_reporter->benchmarkEnded( stats ); } void RunContext::benchmarkFailed( StringRef error ) { + auto _ = scopedDeactivate( *m_outputRedirect ); m_reporter->benchmarkFailed( error ); } @@ -5841,8 +6062,13 @@ namespace Catch { } void RunContext::handleFatalErrorCondition( StringRef message ) { + // TODO: scoped deactivate here? Just give up and do best effort? + // the deactivation can break things further, OTOH so can the + // capture + auto _ = scopedDeactivate( *m_outputRedirect ); + // First notify reporter that bad things happened - m_reporter->fatalErrorEncountered(message); + m_reporter->fatalErrorEncountered( message ); // Don't rebuild the result -- the stringification itself can cause more fatal errors // Instead, fake a result data. @@ -5869,7 +6095,7 @@ namespace Catch { Counts assertions; assertions.failed = 1; SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, 0, false); - m_reporter->sectionEnded(testCaseSectionStats); + m_reporter->sectionEnded( testCaseSectionStats ); auto const& testInfo = m_activeTestCase->getTestCaseInfo(); @@ -5900,7 +6126,7 @@ namespace Catch { return m_totals.assertions.failed >= static_cast<std::size_t>(m_config->abortAfter()); } - void RunContext::runCurrentTest(std::string & redirectedCout, std::string & redirectedCerr) { + void RunContext::runCurrentTest() { auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); m_reporter->sectionStarting(testCaseSection); @@ -5911,18 +6137,8 @@ namespace Catch { Timer timer; CATCH_TRY { - if (m_reporter->getPreferences().shouldRedirectStdOut) { -#if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) - RedirectedStreams redirectedStreams(redirectedCout, redirectedCerr); - - timer.start(); - invokeActiveTestCase(); -#else - OutputRedirect r(redirectedCout, redirectedCerr); - timer.start(); - invokeActiveTestCase(); -#endif - } else { + { + auto _ = scopedActivate( *m_outputRedirect ); timer.start(); invokeActiveTestCase(); } @@ -5967,11 +6183,12 @@ namespace Catch { void RunContext::handleUnfinishedSections() { // If sections ended prematurely due to an exception we stored their // infos here so we can tear them down outside the unwind process. - for (auto it = m_unfinishedSections.rbegin(), - itEnd = m_unfinishedSections.rend(); - it != itEnd; - ++it) - sectionEnded(CATCH_MOVE(*it)); + for ( auto it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it ) { + sectionEnded( CATCH_MOVE( *it ) ); + } m_unfinishedSections.clear(); } @@ -6898,6 +7115,8 @@ namespace Catch { #include <iterator> namespace Catch { + void ITestInvoker::prepareTestCase() {} + void ITestInvoker::tearDownTestCase() {} ITestInvoker::~ITestInvoker() = default; namespace { @@ -10333,7 +10552,7 @@ namespace Catch { xml( m_stream ) { m_preferences.shouldRedirectStdOut = true; - m_preferences.shouldReportAllAssertions = true; + m_preferences.shouldReportAllAssertions = false; m_shouldStoreSuccesfulAssertions = false; } @@ -10443,7 +10662,7 @@ namespace Catch { if( !rootName.empty() ) name = rootName + '/' + name; - if( sectionNode.hasAnyAssertions() + if ( sectionNode.stats.assertions.total() > 0 || !sectionNode.stdOut.empty() || !sectionNode.stdErr.empty() ) { XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); |