3636
3737ANSI_ESCAPE = re .compile (r"\x1B\[[0-9;]*[A-Za-z]" )
3838PYTEST_NODE_ID = re .compile (r"tests/\S+\.py::\S+" )
39- PYTEST_TEST_OUTCOME = re .compile (r"(tests/\S+\.py::\S+)\s+(PASSED|FAILED|ERROR|SKIPPED|XFAIL|XPASS)\b" )
39+ PYTEST_TEST_OUTCOME = re .compile (
40+ r"(tests/\S+\.py::\S+)\s+"
41+ r"(PASSED|FAILED|ERROR|SKIPPED|XFAIL|XPASS|SUBPASSED|SUBFAILED|SUBERROR|SUBSKIPPED|SUBXFAIL|SUBXPASS)\b"
42+ )
4043
4144# GHA log format markers used to identify which test suite is active.
4245# `gh api` logs: ##[group]<step-name> opens a section, ##[endgroup] closes it.
@@ -194,6 +197,8 @@ def extract_test_status_sets(text: str) -> tuple[set[str], set[str], dict[str, s
194197 """Parse pytest output and return (skipped, non_skipped, test_id->suite)."""
195198 skipped : set [str ] = set ()
196199 non_skipped : set [str ] = set ()
200+ passed : set [str ] = set ()
201+ subtest_seen : set [str ] = set ()
197202 test_suites : dict [str , str ] = {}
198203 current_suite = ""
199204
@@ -216,10 +221,20 @@ def extract_test_status_sets(text: str) -> tuple[set[str], set[str], dict[str, s
216221
217222 # Parse per-test outcomes first so PASS/FAIL lines disqualify tests.
218223 for test_id , outcome in PYTEST_TEST_OUTCOME .findall (line ):
219- if outcome == "SKIPPED" :
224+ if outcome .startswith ("SUB" ):
225+ subtest_seen .add (test_id )
226+ if outcome == "SUBSKIPPED" :
227+ skipped .add (test_id )
228+ if current_suite :
229+ test_suites .setdefault (test_id , current_suite )
230+ else :
231+ non_skipped .add (test_id )
232+ elif outcome == "SKIPPED" :
220233 skipped .add (test_id )
221234 if current_suite :
222235 test_suites .setdefault (test_id , current_suite )
236+ elif outcome == "PASSED" :
237+ passed .add (test_id )
223238 else :
224239 non_skipped .add (test_id )
225240
@@ -233,6 +248,11 @@ def extract_test_status_sets(text: str) -> tuple[set[str], set[str], dict[str, s
233248 if current_suite :
234249 test_suites .setdefault (test_id , current_suite )
235250
251+ # Pytest reports a passing parent after its subtests even when every
252+ # subtest skipped. Only treat that parent pass as execution evidence when
253+ # the test did not emit subtest outcomes of its own.
254+ non_skipped .update (passed - subtest_seen )
255+
236256 return skipped , non_skipped , test_suites
237257
238258
0 commit comments