Add defensive handling for missing test results#103
Conversation
… for skips Hardens the collector against any code path that leaves result=None: - as_json() now always emits a "result" key, defaulting to "unknown" with a warning log when result is unset. Previously the key was silently omitted, causing test-engine-client to miscount test statuses. - finalize_test() logs a warning when a test reaches finalization without a result, making the issue visible before serialization. - update_test_result() uses elif instead of independent if statements to make the mutual exclusivity of pytest outcomes explicit. - Adds integration tests exercising @pytest.mark.skip, @pytest.mark.skipif, pytest.skip() in fixtures, and pytest.skip() in the test body. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Emitting "result": "unknown" doesn't improve downstream behavior — test-engine-client's RunResult.Status() treats unrecognized values the same as a missing key, so it wouldn't prevent the accounting bug. Keep the warning log for observability without introducing a new result value that no consumer handles. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The changes for logging the unknown result sound good. The warning logs in However, regarding the:
We're intentionally keeping the current behavior where unrecognized test statuses cause the run to be treated as unknown. The design principle in test-engine-client is: propagate the test runner's exit code unless we can prove that all failures come from muted tests, and nothing else. This is the only case where we suppress a non-zero exit code. If we can't fully account for every test's outcome, we don't have that proof, so the error propagates. In a correctly functioning collector, every test should have a known result: We've been bitten by this before. There have been cases across multiple collectors where legitimate failures were reported without a recognized result, for example runtime errors in Vitest/Jest (#377) and rspec exiting non-zero with no reported failures (#436). In each case, the fact that test-engine-client treated the unknown result as untrusted is what surfaced the bug. If we had been lenient, those failures would have silently passed CI. In fact, #102 and this PR are proof that we need this principle. If we had been suppressing unknown test results, we wouldn't have known there was a bug in the Python collector in the first place. |
|
Hi @jasonwbarnett, I merged #102 and the branch gets deleted. Since this PR targets that branch, GitHub automatically closed this PR. Can you please reopen and change the target to |
Context
Follow-up to #102. While reviewing the setup-skip fix, I identified a few additional hardening opportunities that prevent the same class of bug (
result=None→ missing"result"key → test-engine-client treats it as unknown) from resurfacing through other code paths.See: buildkite/test-engine-client#464
Changes
Warning logs for unset results — Both
as_json()andfinalize_test()now log a warning when a test has no result set. This makes any futureresult=Nonebug immediately visible in logs instead of silently producing bad JSON. We intentionally do not emit a default"result"value — test-engine-client treats any unrecognized value the same as a missing key, so a default wouldn't help downstream.if/elifinupdate_test_result()— Changed independentifstatements toif/elif/elifto make the mutual exclusivity of pytest outcomes (passed,failed,skipped) explicit.Integration tests for skip scenarios — Subprocess-based tests covering
@pytest.mark.skip,@pytest.mark.skipif,pytest.skip()in fixtures, andpytest.skip()in the test body. Verifies the actual JSON output contains"result": "skipped"end-to-end.Note on test-engine-client hardening
The root cause of #464 is that
RunResult.Status()lets a single test with an unrecognized status poison the entire run — thepassed + skipped + failedMuted == len(r.tests)check fails and falls through toRunStatusUnknown. A more defensive pattern would be to either:"skipped"(the least harmful default)This would prevent any future collector bug in any language from breaking
OnlyMutedFailures().Test plan
test_all_tests_have_result_keyasserts every test entry in JSON has aresultkey🤖 Generated with Claude Code