How to Write Death Tests in GoogleTest to Verify Crashes and Exits
Death tests in GoogleTest verify that code terminates abnormally by launching the statement in a child process and validating its exit status and standard error output against regular expression matchers.
The GoogleTest framework (google/googletest) provides a dedicated API for asserting that functions crash, abort, or exit with specific codes. These tests are essential for validating error-handling paths that must terminate the program, such as failed invariant checks or fatal signal handlers.
Core Death Test Macros
The public death-testing API is declared in googletest/include/gtest/gtest-death-test.h. Four primary macros cover most use cases:
ASSERT_DEATH(statement, matcher)– Fails the test immediately if the statement does not terminate abnormally.EXPECT_DEATH(statement, matcher)– Continues the test after reporting a failure if the statement does not die.ASSERT_EXIT(statement, predicate, matcher)– Allows custom validation of the exit status code via a predicate function.EXPECT_EXIT(statement, predicate, matcher)– Non-fatal version ofASSERT_EXIT.
For debug-only checks, the header also defines ASSERT_DEBUG_DEATH and EXPECT_DEBUG_DEATH, which execute the statement only when NDEBUG is not defined.
Architecture and Process Model
All death-test macros expand to the internal macro GTEST_DEATH_TEST_ defined in googletest/include/gtest/internal/gtest-death-test-internal.h. This macro implements a fork-and-check pattern:
- Role Determination – The macro calls
testing::internal::DeathTest::Createto obtain a platform-specificDeathTestobject. The current process assumes either an "oversee" role (parent) or "execute" role (child). - Child Execution – In the execute role, the macro runs the user-provided statement. If the statement returns normally, the child aborts with
TEST_DID_NOT_DIE. - Parent Validation – In the oversee role, the parent waits for the child process to exit, retrieves its exit status via
Wait(), and validates it usingPassed(bool). The abstractDeathTestclass encapsulates this lifecycle with methodsAssumeRole(),Abort(AbortReason), and status checking utilities.
The implementation in googletest/src/gtest-death-test.cc handles platform-specific child-process creation using fork(), clone(), or process spawning depending on the operating system.
Validating Exit Status with Predicates
When using ASSERT_EXIT or EXPECT_EXIT, you must supply a predicate that evaluates the exit code. The framework provides two built-in predicates in gtest-death-test.h:
testing::ExitedWithCode(int)– Returns true if the program exited normally with the specified status code.testing::KilledBySignal(int)– Returns true if the process terminated due to the specified signal (POSIX only).
TEST(ProcessTest, CleanExit) {
auto func = []() { std::exit(42); };
EXPECT_EXIT(func(), ::testing::ExitedWithCode(42), ".*");
}
#if !defined(GTEST_OS_WINDOWS)
TEST(ProcessTest, SignalTermination) {
auto func = []() { raise(SIGTERM); };
EXPECT_EXIT(func(), ::testing::KilledBySignal(SIGTERM), ".*");
}
#endif
Matching Standard Error Output
The matcher argument validates the child process's stderr output. If you provide a plain string literal, MakeDeathTestMatcher (defined in gtest-death-test-internal.h) implicitly converts it to a regular expression. The framework supports a subset of POSIX regular expression syntax documented in the internal headers.
TEST(CrashTest, SpecificErrorMessage) {
ASSERT_DEATH({
LOG(FATAL) << "Disk full";
}, "Disk full");
}
Debug-Only Death Tests
Use EXPECT_DEBUG_DEATH or ASSERT_DEBUG_DEATH when a fatal assertion should only trigger in debug builds. These macros compile to no-ops when NDEBUG is defined, allowing you to test assert() statements and debug-only CHECK macros without affecting release builds.
#ifndef NDEBUG
TEST(DebugTest, AssertFailure) {
EXPECT_DEBUGdeath({
assert(false && "Invariant failed");
}, "Invariant failed");
}
#endif
Practical Examples
#include <gtest/gtest.h>
#include <signal.h>
#include <cstdlib>
// Verify null pointer dereference causes SIGSEGV
TEST(FooTest, CrashOnBadInput) {
ASSERT_DEATH({
int* p = nullptr;
*p = 42;
}, "Segmentation fault|SIGSEGV");
}
// Verify specific exit code
TEST(FooTest, ExitWithCodeZero) {
auto exit_func = []() { std::exit(0); };
ASSERT_EXIT(exit_func(), ::testing::ExitedWithCode(0), ".*");
}
// Verify abort signal (POSIX only)
#if !defined(GTEST_OS_WINDOWS)
TEST(FooTest, AbortSignal) {
auto abort_func = []() { std::abort(); };
EXPECT_DEATH(abort_func(), "Aborted");
}
#endif
// Debug-only crash test
#ifndef NDEBUG
TEST(FooTest, DebugDeath) {
EXPECT_DEBUG_DEATH(LOG(DFATAL) << "die now", "die now");
}
#endif
Summary
- Death tests spawn code in a separate child process to safely capture crashes without terminating the test runner.
- Use
ASSERT_DEATHandEXPECT_DEATHfor general crash validation via the public API ingtest/gtest-death-test.h. - Use
ASSERT_EXITwithExitedWithCodeorKilledBySignalto validate specific termination conditions. - The
matcherstring performs regex matching againststderr; plain strings are implicitly converted viaMakeDeathTestMatcher. ASSERT_DEBUG_DEATHskips execution in release builds, making it ideal for testingassert()statements.
Frequently Asked Questions
What is the difference between ASSERT_DEATH and ASSERT_EXIT?
ASSERT_DEATH asserts that a statement terminates abnormally (non-zero exit or signal) and matches the stderr output against a regex. ASSERT_EXIT provides additional control by accepting a predicate function that inspects the exact exit code, such as ExitedWithCode(0) for successful exits or KilledBySignal(SIGABRT) for specific signals.
Why does my death test hang when the code doesn't crash?
If the statement passed to a death test macro returns normally, the child process aborts with a special TEST_DID_NOT_DIE status. However, if the code enters an infinite loop or waits on I/O, the parent process will hang at the Wait() call. Ensure the statement always terminates, either via exit(), abort(), a signal, or a failed assertion.
How do death tests work on Windows versus POSIX?
On POSIX systems, death tests use fork() or clone() to create the child process and support signal-based predicates like KilledBySignal. On Windows, the implementation in gtest-death-test.cc uses process spawning instead of forking, and signal-related predicates are unavailable. The GTEST_DEATH_TEST_STYLE environment variable (threadsafe or fast) controls the spawning strategy on each platform.
Can I use death tests to check for C++ exceptions?
No. Death tests only capture process termination via exit(), signals, or abort(). If your code throws an exception that is not caught, the process may terminate, but this behavior is implementation-defined. For exception testing, use ASSERT_THROW or EXPECT_THROW from the standard GoogleTest assertion macros instead.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →