Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/src/catch2/catch_session.cpp
diff options
context:
space:
mode:
authorLeitwolf2026-10-24 19:03:40 +0300
committerGitHub2026-10-24 19:03:40 +0300
commit4f1c759cffcb14bd6511e1d7c5fbace682e24f2d (patch)
tree415fe5d48a5b48650bba3747beaf245d727c4f61 /src/catch2/catch_session.cpp
parent83c6fc3ac13abe83fa39f7b5f8559ccaf4412233 (diff)
parent622188f9c08e4af35941c146979883ed4882410e (diff)
downloadcatch3-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.cpp488
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