[Bugfix][CI] Report subprocess test skips as skips, not passes (#58701)
Signed-off-by: Stefan Koncarevic <Stefan.Koncarevic@amd.com> Co-authored-by: Andreas Karatzas <akaratza@amd.com>
stefankoncarevic committed
Sep 25, 2026 at 21:44 UTC
2d7dd0e5c1f9800a3b8c8f38fd832ee335fa7fba
2 files changed
+25
-8
tests/utils.py
+12
-8
@@ -1928,7 +1928,7 @@ def spawn_new_process_for_each_test(f: Callable[_P, None]) -> Callable[_P, None]
1928
1929
The child inherits the parent's stdout/stderr so its output (engine
1930
cores, NCCL, CUDA, ...) reaches the test runner live; the Python-level
1931
- traceback is serialized to ``tb_file`` for structured re-raising. A
1931
+ traceback or skip reason is serialized to ``tb_file`` for propagation. A
1932
native crash leaves ``tb_file`` empty — the diagnostic is then only in
1933
the inherited subprocess output.
1934
"""
@@ -1953,7 +1953,7 @@ def spawn_new_process_for_each_test(f: Callable[_P, None]) -> Callable[_P, None]
1953
)
1954
1955
child_script = (
1956
- "import sys, importlib, cloudpickle, traceback\n"
1956
+ "import sys, importlib, cloudpickle, traceback, json\n"
1957
"try:\n"
1958
" from _pytest.outcomes import Skipped\n"
1959
"except ImportError:\n"
@@ -1965,7 +1965,9 @@ def spawn_new_process_for_each_test(f: Callable[_P, None]) -> Callable[_P, None]
1965
" target = getattr(target, name)\n"
1966
"try:\n"
1967
" target(*data['args'], **data['kwargs'])\n"
1968
- "except Skipped:\n"
1968
+ "except Skipped as exc:\n"
1969
+ " with open(data['tb_file'], 'w') as fp:\n"
1970
+ " json.dump(str(exc), fp)\n"
1971
" sys.exit(0)\n"
1972
"except BaseException:\n"
1973
" with open(data['tb_file'], 'w') as fp:\n"
@@ -1984,18 +1986,20 @@ def spawn_new_process_for_each_test(f: Callable[_P, None]) -> Callable[_P, None]
1986
env=env,
1987
)
1988
1989
+ try:
1990
+ with open(tb_file) as fp:
1991
+ tb = fp.read()
1992
+ except OSError:
1993
+ tb = ""
1994
if result.returncode != 0:
1988
- try:
1989
- with open(tb_file) as fp:
1990
- tb = fp.read()
1991
- except OSError:
1992
- tb = ""
1995
if not tb:
1996
tb = "<no Python traceback; see subprocess output above>"
1997
raise RuntimeError(
1998
f"Test subprocess '{f.__name__}' failed "
1999
f"({_format_subprocess_exit(result.returncode)}):\n{tb}"
2000
)
2001
+ if tb:
2002
+ pytest.skip(json.loads(tb))
2003
finally:
2004
with contextlib.suppress(OSError):
2005
os.remove(tb_file)
tests/utils_/test_spawn_decorator.py
+13
@@ -2,10 +2,14 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
"""Tests for spawn_new_process_for_each_test decorator."""
4
5
+from pathlib import Path
6
+
7
import pytest
8
9
from tests.utils import spawn_new_process_for_each_test
10
11
+pytest_plugins = ["pytester"]
12
+
13
14
@spawn_new_process_for_each_test
15
def test_spawn_decorator_passing():
@@ -26,6 +30,15 @@ def test_spawn_decorator_skip():
30
pytest.skip("intentional skip")
31
32
33
+def test_spawn_decorator_reports_skip(pytester):
34
+ """A child skip must remain a skip in the parent pytest report."""
35
+ result = pytester.runpytest_subprocess(
36
+ f"{Path(__file__).resolve()}::test_spawn_decorator_skip", "-rs"
37
+ )
38
+ result.assert_outcomes(skipped=1)
39
+ result.stdout.fnmatch_lines(["SKIPPED *intentional skip"])
40
+
41
+
42
@spawn_new_process_for_each_test
43
@pytest.mark.parametrize("x,y,expected", [(1, 2, 3), (0, 0, 0)])
44
def test_spawn_decorator_parametrized(x, y, expected):