diff options
| author | Leitwolf | 2026-10-24 19:03:40 +0300 |
|---|---|---|
| committer | GitHub | 2026-10-24 19:03:40 +0300 |
| commit | 4f1c759cffcb14bd6511e1d7c5fbace682e24f2d (patch) | |
| tree | 415fe5d48a5b48650bba3747beaf245d727c4f61 /src/catch2/catch_session.cpp | |
| parent | 83c6fc3ac13abe83fa39f7b5f8559ccaf4412233 (diff) | |
| parent | 622188f9c08e4af35941c146979883ed4882410e (diff) | |
| download | catch3-4f1c759cffcb14bd6511e1d7c5fbace682e24f2d.tar.gz | |
Merge pull request #15 from Progmasoft/feature/catch3-runner
Replace Catch2's runner with the Catch3 runner
Diffstat (limited to 'src/catch2/catch_session.cpp')
| -rw-r--r-- | src/catch2/catch_session.cpp | 488 |
1 files changed, 12 insertions, 476 deletions
diff --git a/src/catch2/catch_session.cpp b/src/catch2/catch_session.cpp index 34fd2809..e40df626 100644 --- a/src/catch2/catch_session.cpp +++ b/src/catch2/catch_session.cpp @@ -6,517 +6,53 @@ // SPDX-License-Identifier: BSL-1.0 #include <catch2/catch_session.hpp> -#include <catch2/internal/catch_console_colour.hpp> -#include <catch2/internal/catch_enforce.hpp> -#include <catch2/internal/catch_list.hpp> #include <catch2/internal/catch_context.hpp> -#include <catch2/internal/catch_run_context.hpp> -#include <catch2/catch_test_spec.hpp> -#include <catch2/catch_version.hpp> -#include <catch2/internal/catch_startup_exception_registry.hpp> -#include <catch2/internal/catch_sharding.hpp> -#include <catch2/internal/catch_test_case_registry_impl.hpp> -#include <catch2/internal/catch_textflow.hpp> +#include <catch2/internal/catch_enforce.hpp> #include <catch2/internal/catch_windows_h_proxy.hpp> -#include <catch2/reporters/catch_reporter_multi.hpp> -#include <catch2/internal/catch_reporter_registry.hpp> -#include <catch2/interfaces/catch_interfaces_reporter_factory.hpp> #include <catch2/interfaces/catch_interfaces_registry_hub.hpp> -#include <catch2/internal/catch_move_and_forward.hpp> -#include <catch2/internal/catch_stdstreams.hpp> -#include <catch2/internal/catch_istream.hpp> - -#include <cassert> -#include <cerrno> -#include <cstdio> -#include <cstdlib> -#include <cstring> -#include <exception> -#include <iomanip> -#if defined( _WIN32 ) -# include <fcntl.h> -# include <io.h> -# include <sys/stat.h> -#else -# include <fcntl.h> -# include <sys/stat.h> -# include <unistd.h> -#endif +#include <Progmasoft/Catch3/Runner.hpp> namespace Catch { - namespace { - IEventListenerPtr createReporter(std::string const& reporterName, ReporterConfig&& config) { - auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, CATCH_MOVE(config)); - CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << '\''); - - return reporter; - } - - IEventListenerPtr prepareReporters(Config const* config) { - if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty() - && config->getProcessedReporterSpecs().size() == 1) { - auto const& spec = config->getProcessedReporterSpecs()[0]; - return createReporter( - spec.name, - ReporterConfig( config, - makeStream( spec.outputFilename ), - spec.colourMode, - spec.verbosity, - spec.customOptions ) ); - } - - auto multi = Detail::make_unique<MultiReporter>(config); - - auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); - for (auto const& listener : listeners) { - multi->addListener(listener->create(config)); - } - - for ( auto const& reporterSpec : config->getProcessedReporterSpecs() ) { - multi->addReporter( createReporter( - reporterSpec.name, - ReporterConfig( config, - makeStream( reporterSpec.outputFilename ), - reporterSpec.colourMode, - reporterSpec.verbosity, - reporterSpec.customOptions ) ) ); - } - - return multi; - } - - class TestGroup { - public: - explicit TestGroup(IEventListenerPtr&& reporter, Config const* config): - m_reporter(reporter.get()), - m_config{config}, - m_context{config, CATCH_MOVE(reporter)} { - - assert( m_config->testSpec().getInvalidSpecs().empty() && - "Invalid test specs should be handled before running tests" ); - - // Note that we are working with pointers into this vector - // of sorted test cases, so sorting/unique-ing the pointers - // keeps the user-specified order. - auto const& allTestCases = getAllTestCasesSorted(*m_config); - auto const& testSpec = m_config->testSpec(); - m_tests.reserve( allTestCases.size() ); - if ( !testSpec.hasFilters() ) { - for ( auto const& test : allTestCases ) { - if ( !test.getTestCaseInfo().isHidden() ) { - m_tests.push_back( &test ); - } - } - } else { - m_matches = testSpec.matchesByFilter( allTestCases, *m_config ); - for ( auto const& match : m_matches ) { - m_tests.insert( m_tests.end(), - match.tests.begin(), - match.tests.end() ); - } - std::sort( m_tests.begin(), m_tests.end() ); - m_tests.erase( - std::unique( m_tests.begin(), m_tests.end() ), - m_tests.end() ); - } - - m_tests = createShard(CATCH_MOVE(m_tests), m_config->shardCount(), m_config->shardIndex()); - } - - Totals execute() { - Totals totals; - for (auto const& testCase : m_tests) { - if (!m_context.aborting()) - totals += m_context.runTest(*testCase); - else - m_reporter->skipTest(testCase->getTestCaseInfo()); - } - - for (auto const& match : m_matches) { - if (match.tests.empty()) { - m_unmatchedTestSpecs = true; - m_reporter->noMatchingTestCases( match.name ); - } - } - - return totals; - } - - bool hadUnmatchedTestSpecs() const { - return m_unmatchedTestSpecs; - } - - - private: - IEventListener* m_reporter; - Config const* m_config; - RunContext m_context; - std::vector<TestCaseHandle const*> m_tests; - TestSpec::Matches m_matches; - bool m_unmatchedTestSpecs = false; - }; - - void applyFilenamesAsTags() { - getMutableRegistryHub().getMutableTestCaseRegistry().enableFilenameTags(); - } - - std::FILE* openGuardFile( std::string const& guardFilePath, - int& error ) { -#if defined( _MSC_VER ) - std::FILE* file = nullptr; - error = fopen_s( &file, guardFilePath.c_str(), "w" ); - return error ? nullptr : file; -#elif defined( _WIN32 ) - const int descriptor = _open( guardFilePath.c_str(), - _O_WRONLY | _O_CREAT | _O_TRUNC | - _O_BINARY | _O_NOINHERIT, - _S_IREAD | _S_IWRITE ); - if ( descriptor == -1 ) { - error = errno; - return nullptr; - } - std::FILE* const file = _fdopen( descriptor, "w" ); - if ( file == nullptr ) { - error = errno; - _close( descriptor ); - } - return file; -#else - int flags = O_WRONLY | O_CREAT | O_TRUNC; -# if defined( O_NONBLOCK ) - flags |= O_NONBLOCK; -# endif -# if defined( O_CLOEXEC ) - flags |= O_CLOEXEC; -# endif -# if defined( O_NOFOLLOW ) - // Do not let a stale guard-file symlink redirect this write. - flags |= O_NOFOLLOW; -# endif - const int descriptor = - ::open( guardFilePath.c_str(), flags, S_IRUSR | S_IWUSR ); - if ( descriptor == -1 ) { - error = errno; - return nullptr; - } - - struct stat fileStatus = {}; - if ( ::fstat( descriptor, &fileStatus ) != 0 ) { - error = errno; - ::close( descriptor ); - return nullptr; - } - if ( !S_ISREG( fileStatus.st_mode ) ) { - error = EINVAL; - ::close( descriptor ); - return nullptr; - } - // The creation mode is already owner-only; also tighten an - // existing regular guard file before it is reused. - if ( ::fchmod( descriptor, S_IRUSR | S_IWUSR ) != 0 ) { - error = errno; - ::close( descriptor ); - return nullptr; - } - - std::FILE* const file = ::fdopen( descriptor, "w" ); - if ( file == nullptr ) { - error = errno; - ::close( descriptor ); - } - return file; -#endif - } - - // Creates an owner-only guard file. The path must be writable; we do - // not create its parent directories. - void setUpGuardFile( std::string const& guardFilePath ) { - if ( !guardFilePath.empty() ) { - int err = 0; - std::FILE* const file = openGuardFile( guardFilePath, err ); - if ( file == nullptr ) { -#if defined( _MSC_VER ) - char msgBuffer[100] = {}; -#endif - std::string errMsg; -#if defined( _MSC_VER ) - if ( !strerror_s( msgBuffer, err ) ) { - errMsg = msgBuffer; - } else { - errMsg = "Could not translate errno to a string"; - } -#else - const char* const message = std::strerror( err ); - errMsg = message ? message : "Unknown error"; -#endif - CATCH_RUNTIME_ERROR( "Could not open the exit guard file '" - << guardFilePath << "' because '" - << errMsg << "' (" << err << ')' ); - } - const int ret = std::fclose( file ); - CATCH_ENFORCE( - ret == 0, - "Error when closing the exit guard file: " << ret ); - } - } - - // Removes file at path. Assuming we created it in setUpGuardFile. - void tearDownGuardFile( std::string const& guardFilePath ) { - if ( !guardFilePath.empty() ) { - const int ret = std::remove( guardFilePath.c_str() ); - CATCH_ENFORCE( - ret == 0, - "Error when removing the exit guard file: " << ret ); - } - } - - } // anon namespace - Session::Session() { static bool alreadyInstantiated = false; if( alreadyInstantiated ) { CATCH_TRY { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); } CATCH_CATCH_ALL { getMutableRegistryHub().registerStartupException(); } } - - // There cannot be exceptions at startup in no-exception mode. -#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) - const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); - if ( !exceptions.empty() ) { - config(); - getCurrentMutableContext().setConfig(m_config.get()); - - m_startupExceptions = true; - auto errStream = makeStream( "%stderr" ); - auto colourImpl = makeColourImpl( - ColourMode::PlatformDefault, errStream.get() ); - auto guard = colourImpl->guardColour( Colour::Red ); - errStream->stream() << "Errors occurred during startup!" << '\n'; - // iterate over all exceptions and notify user - for ( const auto& ex_ptr : exceptions ) { - try { - std::rethrow_exception(ex_ptr); - } catch ( std::exception const& ex ) { - errStream->stream() << TextFlow::Column( ex.what() ).indent(2) << '\n'; - } - } - } -#endif - alreadyInstantiated = true; - m_cli = makeCommandLineParser( m_configData ); } Session::~Session() { Catch::cleanUp(); } void Session::showHelp() const { - Catch::cout() - << "\nCatch2 v" << libraryVersion() << '\n' - << m_cli << '\n' - << "For more detailed usage please see the project docs\n\n" << std::flush; - } - void Session::libIdentify() { - Catch::cout() - << std::left << std::setw(16) << "description: " << "A Catch2 test executable\n" - << std::left << std::setw(16) << "category: " << "testframework\n" - << std::left << std::setw(16) << "framework: " << "Catch2\n" - << std::left << std::setw(16) << "version: " << libraryVersion() << '\n' << std::flush; + Progmasoft::Catch3::PrintHelp( m_arguments.empty() ? std::string( "tests" ) + : m_arguments.front() ); } int Session::applyCommandLine( int argc, char const * const * argv ) { - if ( m_startupExceptions ) { return UnspecifiedErrorExitCode; } - - auto result = m_cli.parse( Clara::Args( argc, argv ) ); - - if( !result ) { - config(); - getCurrentMutableContext().setConfig(m_config.get()); - auto errStream = makeStream( "%stderr" ); - auto colour = makeColourImpl( ColourMode::PlatformDefault, errStream.get() ); - - errStream->stream() - << colour->guardColour( Colour::Red ) - << "\nError(s) in input:\n" - << TextFlow::Column( result.errorMessage() ).indent( 2 ) - << "\n\n"; - errStream->stream() << "Run with -? for usage\n\n" << std::flush; - return UnspecifiedErrorExitCode; - } - - if( m_configData.showHelp ) - showHelp(); - if( m_configData.libIdentify ) - libIdentify(); - - m_config.reset(); + m_arguments.assign( argv, argv + argc ); return 0; } #if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE) int Session::applyCommandLine( int argc, wchar_t const * const * argv ) { - - char **utf8Argv = new char *[ argc ]; - + m_arguments.clear(); for ( int i = 0; i < argc; ++i ) { int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, nullptr, 0, nullptr, nullptr ); - - utf8Argv[ i ] = new char[ bufSize ]; - - WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, nullptr, nullptr ); + std::string utf8( static_cast<std::size_t>( bufSize ), '\0' ); + WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, &utf8[0], bufSize, nullptr, nullptr ); + if ( !utf8.empty() ) { utf8.pop_back(); } // the terminator + m_arguments.push_back( utf8 ); } - - int returnCode = applyCommandLine( argc, utf8Argv ); - - for ( int i = 0; i < argc; ++i ) - delete [] utf8Argv[ i ]; - - delete [] utf8Argv; - - return returnCode; + return 0; } #endif - void Session::useConfigData( ConfigData const& configData ) { - m_configData = configData; - m_config.reset(); - } - int Session::run() { - if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) { - Catch::cout() << "...waiting for enter/ return before starting\n" << std::flush; - static_cast<void>(std::getchar()); - } - int exitCode = runInternal(); - - if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { - Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << '\n' << std::flush; - static_cast<void>(std::getchar()); - } - return exitCode; - } - - Clara::Parser const& Session::cli() const { - return m_cli; - } - void Session::cli( Clara::Parser const& newParser ) { - m_cli = newParser; - } - ConfigData& Session::configData() { - return m_configData; - } - Config& Session::config() { - if( !m_config ) - m_config = Detail::make_unique<Config>( m_configData ); - return *m_config; - } - - int Session::runInternal() { - if ( m_startupExceptions ) { return UnspecifiedErrorExitCode; } - - if (m_configData.showHelp || m_configData.libIdentify) { - return 0; - } - - if ( m_configData.shardIndex >= m_configData.shardCount ) { - Catch::cerr() << "The shard count (" << m_configData.shardCount - << ") must be greater than the shard index (" - << m_configData.shardIndex << ")\n" - << std::flush; - return UnspecifiedErrorExitCode; - } - - CATCH_TRY { - config(); // Force config to be constructed - - if ( m_config->shardCount() > 1 && - m_config->runOrder() == TestRunOrder::Randomized && - !m_config->rngSeedWasFixed() ) { - Catch::cerr() - << "Warning: using sharding (--shard-count) with random " - "order (--order rand, the default) and without a fixed " - "numeric --rng-seed does not guarantee disjoint coverage " - "between shard invocations. Pass the same numeric " - "--rng-seed to every shard, or use --order decl or " - "--order lex instead.\n" - << std::flush; - } - - // We need to retrieve potential Bazel config with the full Config - // constructor, so we have to create the guard file after it is created. - setUpGuardFile( m_config->getExitGuardFilePath() ); - - seedRng( *m_config ); - - if (m_configData.filenamesAsTags) { - applyFilenamesAsTags(); - } - - // Set up global config instance before we start calling into other functions - getCurrentMutableContext().setConfig(m_config.get()); - - // Create reporter(s) so we can route listings through them - IEventListenerPtr reporter; - CATCH_TRY { - auto reporter_ = prepareReporters( m_config.get() ); - swap( reporter, reporter_ ); - } -#if !defined( CATCH_CONFIG_DISABLE_EXCEPTIONS ) - catch ( std::exception& ex ) { - Catch::cerr() << "Error when creating reporter: " << ex.what() << '\n' << std::flush; - return ReporterConstructionErrorExitCode; - } -#endif - - auto const& invalidSpecs = m_config->testSpec().getInvalidSpecs(); - if ( !invalidSpecs.empty() ) { - for ( auto const& spec : invalidSpecs ) { - reporter->reportInvalidTestSpec( spec ); - } - return InvalidTestSpecExitCode; - } - - - // Handle list request - if (list(*reporter, *m_config)) { - return 0; - } - - TestGroup tests { CATCH_MOVE(reporter), m_config.get() }; - auto const totals = tests.execute(); - - // If we got here, running the tests finished normally-enough. - // They might've failed, but that would've been reported elsewhere. - tearDownGuardFile( m_config->getExitGuardFilePath() ); - - if ( tests.hadUnmatchedTestSpecs() - && m_config->warnAboutUnmatchedTestSpecs() ) { - return UnmatchedTestSpecExitCode; - } - - if ( totals.testCases.total() == 0 - && !m_config->zeroTestsCountAsSuccess() ) { - return NoTestsRunExitCode; - } - - if ( totals.testCases.total() > 0 && - totals.testCases.total() == totals.testCases.skipped - && !m_config->zeroTestsCountAsSuccess() ) { - return AllTestsSkippedExitCode; - } - - if ( totals.assertions.failed ) { return TestFailureExitCode; } - return 0; - - } -#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) - catch( std::exception& ex ) { - Catch::cerr() << ex.what() << '\n' << std::flush; - return UnspecifiedErrorExitCode; - } -#endif + return Progmasoft::Catch3::RunTests( m_arguments ); } } // end namespace Catch |