Standardize exit codes for various failures

The main reason for this is to be able to distinguish between
different errors (or "errors") based on the return code. Before
this change, it was impossible to use the exit code to figure out
whether a test binary failed because all tests were skipped or
because exactly 4 assertions have failed.

This meant that using `catch_discover_tests` and telling it to
check for exit code == 4 to determine skipped tests could lead to
false negatives.
This commit is contained in:
Martin Hořeňovský 2024-09-13 21:33:41 +02:00
parent 18df97df00
commit ce22c0fe8a
No known key found for this signature in database
GPG Key ID: DE48307B8B0D381A
2 changed files with 21 additions and 17 deletions

View File

@ -34,7 +34,13 @@
namespace Catch { namespace Catch {
namespace { namespace {
const int MaxExitCode = 255; 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) { IEventListenerPtr createReporter(std::string const& reporterName, ReporterConfig&& config) {
auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, CATCH_MOVE(config)); auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, CATCH_MOVE(config));
@ -198,8 +204,7 @@ namespace Catch {
} }
int Session::applyCommandLine( int argc, char const * const * argv ) { int Session::applyCommandLine( int argc, char const * const * argv ) {
if( m_startupExceptions ) if ( m_startupExceptions ) { return UnspecifiedErrorExitCode; }
return 1;
auto result = m_cli.parse( Clara::Args( argc, argv ) ); auto result = m_cli.parse( Clara::Args( argc, argv ) );
@ -215,7 +220,7 @@ namespace Catch {
<< TextFlow::Column( result.errorMessage() ).indent( 2 ) << TextFlow::Column( result.errorMessage() ).indent( 2 )
<< "\n\n"; << "\n\n";
errStream->stream() << "Run with -? for usage\n\n" << std::flush; errStream->stream() << "Run with -? for usage\n\n" << std::flush;
return MaxExitCode; return UnspecifiedErrorExitCode;
} }
if( m_configData.showHelp ) if( m_configData.showHelp )
@ -285,8 +290,7 @@ namespace Catch {
} }
int Session::runInternal() { int Session::runInternal() {
if( m_startupExceptions ) if ( m_startupExceptions ) { return UnspecifiedErrorExitCode; }
return 1;
if (m_configData.showHelp || m_configData.libIdentify) { if (m_configData.showHelp || m_configData.libIdentify) {
return 0; return 0;
@ -297,7 +301,7 @@ namespace Catch {
<< ") must be greater than the shard index (" << ") must be greater than the shard index ("
<< m_configData.shardIndex << ")\n" << m_configData.shardIndex << ")\n"
<< std::flush; << std::flush;
return 1; return UnspecifiedErrorExitCode;
} }
CATCH_TRY { CATCH_TRY {
@ -320,7 +324,7 @@ namespace Catch {
for ( auto const& spec : invalidSpecs ) { for ( auto const& spec : invalidSpecs ) {
reporter->reportInvalidTestSpec( spec ); reporter->reportInvalidTestSpec( spec );
} }
return 1; return InvalidTestSpecExitCode;
} }
@ -334,29 +338,29 @@ namespace Catch {
if ( tests.hadUnmatchedTestSpecs() if ( tests.hadUnmatchedTestSpecs()
&& m_config->warnAboutUnmatchedTestSpecs() ) { && m_config->warnAboutUnmatchedTestSpecs() ) {
return 3; // UnmatchedTestSpecExitCode
return UnmatchedTestSpecExitCode;
} }
if ( totals.testCases.total() == 0 if ( totals.testCases.total() == 0
&& !m_config->zeroTestsCountAsSuccess() ) { && !m_config->zeroTestsCountAsSuccess() ) {
return 2; return NoTestsRunExitCode;
} }
if ( totals.testCases.total() > 0 && if ( totals.testCases.total() > 0 &&
totals.testCases.total() == totals.testCases.skipped totals.testCases.total() == totals.testCases.skipped
&& !m_config->zeroTestsCountAsSuccess() ) { && !m_config->zeroTestsCountAsSuccess() ) {
return 4; return AllTestsSkippedExitCode;
} }
// Note that on unices only the lower 8 bits are usually used, clamping if ( totals.assertions.failed ) { return TestFailureExitCode; }
// the return value to 255 prevents false negative when some multiple return 0;
// of 256 tests has failed
return (std::min) (MaxExitCode, static_cast<int>(totals.assertions.failed));
} }
#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) #if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
catch( std::exception& ex ) { catch( std::exception& ex ) {
Catch::cerr() << ex.what() << '\n' << std::flush; Catch::cerr() << ex.what() << '\n' << std::flush;
return MaxExitCode; return UnspecifiedErrorExitCode;
} }
#endif #endif
} }

View File

@ -52,7 +52,7 @@ try:
) )
stdout = ret.stdout stdout = ret.stdout
except subprocess.SubprocessError as ex: except subprocess.SubprocessError as ex:
if ex.returncode == 1: if ex.returncode == 42:
# The test cases are allowed to fail. # The test cases are allowed to fail.
test_passing = False test_passing = False
stdout = ex.stdout stdout = ex.stdout