From 02782241a16996ccf6706b2d56bb0b8f1b88c65e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20H=C3=A4ggstr=C3=B6m?= Date: Thu, 19 Feb 2026 20:05:35 +0000 Subject: [PATCH 1/3] Improve crash message: distinguish exit vs signal, add signal name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a forked test process exits with a non-zero exit code (signal == 0), the message previously said "CRASHED with signal 0" which is misleading — signal 0 is not a real signal and the process was not killed by one. Now: - Non-signal exit: "EXITED with status " - Signal kill: "CRASHED with signal ()" e.g. "signal 11 (SIGSEGV)" signal.Signals() is called without a try/except because result.signal comes from os.WTERMSIG(), which extracts the signal from the kernel's wait status — the kernel can only kill a process with a valid signal number. Note: sys.exit() is caught by pytest as SystemExit and does not trigger report_process_crash; os._exit() bypasses the marshal pipe and does. --- src/pytest_forked/__init__.py | 20 +++++++++++++++----- testing/test_boxed.py | 26 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/pytest_forked/__init__.py b/src/pytest_forked/__init__.py index a4e5d94..80f4f25 100644 --- a/src/pytest_forked/__init__.py +++ b/src/pytest_forked/__init__.py @@ -83,13 +83,23 @@ def runforked(): def report_process_crash(item, result): from _pytest._code import getfslineno + import signal as signal_module path, lineno = getfslineno(item) - info = "%s:%s: running the test CRASHED with signal %d" % ( - path, - lineno, - result.signal, - ) + if result.signal: + sig_name = signal_module.Signals(result.signal).name + info = "%s:%s: running the test CRASHED with signal %d (%s)" % ( + path, + lineno, + result.signal, + sig_name, + ) + else: + info = "%s:%s: running the test EXITED with status %d" % ( + path, + lineno, + result.exitstatus, + ) from _pytest import runner # pytest >= 4.1 diff --git a/testing/test_boxed.py b/testing/test_boxed.py index 6ea430e..63c0515 100644 --- a/testing/test_boxed.py +++ b/testing/test_boxed.py @@ -67,6 +67,32 @@ def test_function(): ) +@needsfork +def test_crash_message_shows_signal_name(testdir): + p1 = testdir.makepyfile( + """ + import os, signal + def test_function(): + os.kill(os.getpid(), signal.SIGTERM) + """ + ) + result = testdir.runpytest(p1, "--forked") + result.stdout.fnmatch_lines(["*CRASHED with signal 15 (SIGTERM)*", "*1 failed*"]) + + +@needsfork +def test_crash_message_shows_exit_status(testdir): + p1 = testdir.makepyfile( + """ + import os + def test_function(): + os._exit(42) + """ + ) + result = testdir.runpytest(p1, "--forked") + result.stdout.fnmatch_lines(["*EXITED with status 42*", "*1 failed*"]) + + def test_is_not_boxed_by_default(testdir): config = testdir.parseconfig(testdir.tmpdir) assert not config.option.forked From 3fd3508db32ac9e236e4718f47a8d79516b74149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20H=C3=A4ggstr=C3=B6m?= Date: Sun, 22 Feb 2026 16:51:10 +0100 Subject: [PATCH 2/3] Add releasenote --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7148366..a6753bb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,7 @@ UNRELEASED ========== * Drop support for EOL Python versions: 3.7, 3.8, 3.9. +* Print proper child process exit status. v1.6.0 ====== From 61776673f386efa80627f511ba4d8ddba34b1c98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20H=C3=A4ggstr=C3=B6m?= Date: Mon, 23 Feb 2026 11:31:52 +0100 Subject: [PATCH 3/3] Fix test_xfail pattern to match new signal name in crash message The reason_string pattern lacked a trailing wildcard, so it failed to match after the crash message started including the signal name (e.g. "signal 15 (SIGTERM)"). Co-Authored-By: Claude Sonnet 4.6 --- testing/test_xfail_behavior.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_xfail_behavior.py b/testing/test_xfail_behavior.py index fdad433..5bba8fa 100644 --- a/testing/test_xfail_behavior.py +++ b/testing/test_xfail_behavior.py @@ -69,7 +69,7 @@ def test_xfail(is_crashing, is_strict, testdir): reason_string = ( f"reason: The process gets terminated; " f"pytest-forked reason: " - f"*:*: running the test CRASHED with signal {sig_num:d}" + f"*:*: running the test CRASHED with signal {sig_num:d}*" ) if expected_lowercase == "xfailed" and PYTEST_GTE_7_2: short_test_summary += " - " + reason_string