diff options
| author | Lexi Winter <lexi@le-fay.org> | 2025-06-29 19:25:29 +0100 |
|---|---|---|
| committer | Lexi Winter <lexi@le-fay.org> | 2025-06-29 19:25:29 +0100 |
| commit | bc524d70253a4ab2fe40c3ca3e5666e267c0a4d1 (patch) | |
| tree | 1e629e7b46b1d9972a973bc93fd100bcebd395be /src/catch2/catch_session.cpp | |
| download | nihil-548ea226e1944e077d3ff305df43ef6b366b03f4.tar.gz nihil-548ea226e1944e077d3ff305df43ef6b366b03f4.tar.bz2 | |
import catch2 3.8.1vendor/catch2/3.8.1vendor/catch2
Diffstat (limited to 'src/catch2/catch_session.cpp')
| -rw-r--r-- | src/catch2/catch_session.cpp | 368 |
1 files changed, 368 insertions, 0 deletions
diff --git a/src/catch2/catch_session.cpp b/src/catch2/catch_session.cpp new file mode 100644 index 0000000..a4a410c --- /dev/null +++ b/src/catch2/catch_session.cpp @@ -0,0 +1,368 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// 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_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/internal/catch_move_and_forward.hpp> +#include <catch2/internal/catch_stdstreams.hpp> +#include <catch2/internal/catch_istream.hpp> + +#include <algorithm> +#include <cassert> +#include <exception> +#include <iomanip> +#include <set> + +namespace Catch { + + namespace { + static constexpr int TestFailureExitCode = 42; + static constexpr int UnspecifiedErrorExitCode = 1; + static constexpr int AllTestsSkippedExitCode = 4; + static constexpr int NoTestsRunExitCode = 2; + static constexpr int UnmatchedTestSpecExitCode = 3; + static constexpr int InvalidTestSpecExitCode = 5; + + + 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.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.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" ); + + auto const& allTestCases = getAllTestCasesSorted(*m_config); + auto const& testSpec = m_config->testSpec(); + if ( !testSpec.hasFilters() ) { + for ( auto const& test : allTestCases ) { + if ( !test.getTestCaseInfo().isHidden() ) { + m_tests.emplace( &test ); + } + } + } else { + m_matches = + testSpec.matchesByFilter( allTestCases, *m_config ); + for ( auto const& match : m_matches ) { + m_tests.insert( match.tests.begin(), + match.tests.end() ); + } + } + + m_tests = createShard(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::set<TestCaseHandle const*> m_tests; + TestSpec::Matches m_matches; + bool m_unmatchedTestSpecs = false; + }; + + void applyFilenamesAsTags() { + for (auto const& testInfo : getRegistryHub().getTestCaseRegistry().getAllInfos()) { + testInfo->addFilenameTag(); + } + } + + } // 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; + } + + 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(); + 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 ]; + + 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 ); + } + + int returnCode = applyCommandLine( argc, utf8Argv ); + + for ( int i = 0; i < argc; ++i ) + delete [] utf8Argv[ i ]; + + delete [] utf8Argv; + + return returnCode; + } +#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 + + 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 + auto reporter = prepareReporters(m_config.get()); + + 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 ( tests.hadUnmatchedTestSpecs() + && m_config->warnAboutUnmatchedTestSpecs() ) { + // UnmatchedTestSpecExitCode + 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 + } + +} // end namespace Catch |
