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

summaryrefslogtreecommitdiffhomepage
path: root/src/catch2
diff options
context:
space:
mode:
authorMartin Hořeňovský2025-09-21 11:19:18 +0200
committerMartin Hořeňovský2025-09-22 14:02:45 +0200
commit8ed8c431c601c7b303dbfe7eb412a63f1641b812 (patch)
tree4e23dd0b60bfb09b58bb5bac770965c83243c569 /src/catch2
parentdc3a4ea41a78c643fbfa07b1cb456738c9e25ef6 (diff)
downloadcatch3-8ed8c431c601c7b303dbfe7eb412a63f1641b812.tar.gz
Support Bazel's TEST_PREMATURE_EXIT_FILE and add it to CLI as well
This tells Catch2 to create an empty file at specified path before the tests start, and delete it after the tests finish. This allows callers to catch cases where the test binary silently exits before finishing (e.g. via call to `exit(0)` inside the code under test), by looking whether the file still exists. Closes #3020
Diffstat (limited to 'src/catch2')
-rw-r--r--src/catch2/catch_config.cpp9
-rw-r--r--src/catch2/catch_config.hpp4
-rw-r--r--src/catch2/catch_session.cpp56
-rw-r--r--src/catch2/internal/catch_commandline.cpp3
4 files changed, 71 insertions, 1 deletions
diff --git a/src/catch2/catch_config.cpp b/src/catch2/catch_config.cpp
index 352c1f42..978bcfd7 100644
--- a/src/catch2/catch_config.cpp
+++ b/src/catch2/catch_config.cpp
@@ -119,6 +119,8 @@ namespace Catch {
m_data.reporterSpecifications.push_back( std::move( *parsed ) );
}
+ // Reading bazel env vars can change some parts of the config data,
+ // so we have to process the bazel env before acting on the config.
if ( enableBazelEnvSupport() ) {
readBazelEnvVars();
}
@@ -183,6 +185,8 @@ namespace Catch {
bool Config::showHelp() const { return m_data.showHelp; }
+ std::string const& Config::getExitGuardFilePath() const { return m_data.prematureExitGuardFilePath; }
+
// IConfig interface
bool Config::allowThrows() const { return !m_data.noThrow; }
StringRef Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; }
@@ -244,6 +248,11 @@ namespace Catch {
m_data.shardCount = bazelShardOptions->shardCount;
}
}
+
+ const auto bazelExitGuardFile = Detail::getEnv( "TEST_PREMATURE_EXIT_FILE" );
+ if (bazelExitGuardFile) {
+ m_data.prematureExitGuardFilePath = bazelExitGuardFile;
+ }
}
} // end namespace Catch
diff --git a/src/catch2/catch_config.hpp b/src/catch2/catch_config.hpp
index b7b1315e..cdf286ad 100644
--- a/src/catch2/catch_config.hpp
+++ b/src/catch2/catch_config.hpp
@@ -87,6 +87,8 @@ namespace Catch {
std::vector<std::string> testsOrTags;
std::vector<std::string> sectionsToRun;
+
+ std::string prematureExitGuardFilePath;
};
@@ -114,6 +116,8 @@ namespace Catch {
bool showHelp() const;
+ std::string const& getExitGuardFilePath() const;
+
// IConfig interface
bool allowThrows() const override;
StringRef name() const override;
diff --git a/src/catch2/catch_session.cpp b/src/catch2/catch_session.cpp
index 7542447b..042b7f4d 100644
--- a/src/catch2/catch_session.cpp
+++ b/src/catch2/catch_session.cpp
@@ -26,6 +26,8 @@
#include <catch2/internal/catch_istream.hpp>
#include <cassert>
+#include <cstdio>
+#include <cstdlib>
#include <exception>
#include <iomanip>
#include <set>
@@ -140,6 +142,50 @@ namespace Catch {
}
}
+ // Creates empty file at path. The path must be writable, we do not
+ // try to create directories in path because that's hard in C++14.
+ void setUpGuardFile( std::string const& guardFilePath ) {
+ if ( !guardFilePath.empty() ) {
+#if defined( _MSC_VER )
+ std::FILE* file = nullptr;
+ if ( fopen_s( &file, guardFilePath.c_str(), "w" ) ) {
+ char msgBuffer[100];
+ const auto err = errno;
+ std::string errMsg;
+ if ( !strerror_s( msgBuffer, err ) ) {
+ errMsg = msgBuffer;
+ } else {
+ errMsg = "Could not translate errno to a string";
+ }
+
+#else
+ std::FILE* file = std::fopen( guardFilePath.c_str(), "w" );
+ if ( !file ) {
+ const auto err = errno;
+ const char* errMsg = std::strerror( err );
+#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() {
@@ -258,6 +304,7 @@ namespace Catch {
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());
@@ -298,6 +345,10 @@ namespace Catch {
CATCH_TRY {
config(); // Force config to be constructed
+ // 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) {
@@ -327,9 +378,12 @@ namespace Catch {
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() ) {
- // UnmatchedTestSpecExitCode
return UnmatchedTestSpecExitCode;
}
diff --git a/src/catch2/internal/catch_commandline.cpp b/src/catch2/internal/catch_commandline.cpp
index 212f1774..db903507 100644
--- a/src/catch2/internal/catch_commandline.cpp
+++ b/src/catch2/internal/catch_commandline.cpp
@@ -305,6 +305,9 @@ namespace Catch {
| Opt( config.allowZeroTests )
["--allow-running-no-tests"]
( "Treat 'No tests run' as a success" )
+ | Opt( config.prematureExitGuardFilePath, "path" )
+ ["--premature-exit-guard-file"]
+ ( "create a file before running tests and delete it during clean exit" )
| Arg( config.testsOrTags, "test name|pattern|tags" )
( "which test or tests to use" );