[SDTEST-3844] Seal active test/suite/module as failed on premature process exit#285
Merged
ypopovych merged 1 commit intoJun 19, 2026
Merged
Conversation
When the test process exits without XCTest delivering the completion
hooks (testCaseDidFinish / testRetryGroupDidFinish / testSuiteDidFinish /
testBundleDidFinish), the in-flight suite and test spans were never
ended and so were dropped from the trace. This is not a crash — the
process exits in an orderly way (e.g. a test calling exit(), or a failure
in an async setUp/tearDown under recent Xcode), so the unload hook still
runs and the session/module spans close, but everything below the module
leaks.
DDXCTestObserver.stop() previously did nothing (default: break) for the
.suite/.group/.test states. It now detects the premature exit and:
- logs a generic premature-exit warning (no vendor-specific attribution,
since a user exit() produces the identical fault),
- force-closes the open test and suite as failed with an explanatory
PrematureTestProcessExit error, and
- ends the module as testBundleDidFinish would; the failure propagates
up to the session.
State is extended so .group/.test carry the references needed to recover
from stop() (previously they held none). Suite-end mirrors
testSuiteDidFinish but skips the feature hooks, which can't run meaningfully
at process exit.
Adds a synchronous regression test driving the observer into .test and
forcing shutdown. It is intentionally sync: stop() unregisters the observer,
which XCTest only allows on the main thread, and async test variants either
crash off-main or deadlock against XCTest's main-thread wait.
SDTEST-3844
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This comment has been minimized.
This comment has been minimized.
anmarchenko
approved these changes
Jun 19, 2026
ypopovych
deleted the
fix/SDTEST-3844-seal-active-spans-on-premature-exit
branch
June 19, 2026 13:44
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The XCTest runner process can exit without XCTest delivering its completion callbacks —
testCaseDidFinish,testRetryGroupDidFinish,testSuiteDidFinish,testBundleDidFinish. This is not a crash: the process exits in an orderly way, so our binary-unload hook (__AutoUnloadHook→FrameworkLoadHandler.libraryUnloaded()) still runs andSessionManager.stop()closes the session and module spans. But any in-flight suite / retry-group / test is left open, neverend()ed, and therefore dropped from the trace.This is a general premature-exit problem, not a single vendor bug. Equivalent triggers:
exit(...)directly;async setUp/tearDownunder recent Xcode, which terminates the runner aftertearDown(forum thread — the "crash" framing there is inaccurate, it's an orderly exit).Fix
DDXCTestObserver.stop()previously hitdefault: breakfor the.suite/.group/.teststates and did nothing. It now detects the premature exit and:exit()produces the identical fault.PrematureTestProcessExiterror so the backend records why they failed.testBundleDidFinishdoes (session.end(module:)); the failure propagates up to the session viaset(failed:). The session/module spans are then closed by the unload path as before.Stateis extended so.group/.testcarry the references needed to recover fromstop()(they previously held none). The suite-end mirrorstestSuiteDidFinishbut skips the feature hooks — those can't run meaningfully once the process is terminating.Test
Adds
testWhenProcessExitsMidTest_activeTestAndSuiteAreSealedAsFailed, which drives the observer into.testand then forces shutdown, asserting both spans are ended, failed, and carry an error.It is intentionally synchronous:
stop()unregisters the observer, which XCTest only permits on the main thread (sync tests run there). Anasynctest crashes off-main on the unregister; a@MainActor asynctest deadlocks against XCTest's main-thread wait at the firstawait.All 8
DDXCTestObserverTestspass; no regression to the normal completion flow.Ref: SDTEST-3844
🤖 Generated with Claude Code