Skip to content

[SDTEST-3844] Seal active test/suite/module as failed on premature process exit#285

Merged
ypopovych merged 1 commit into
mainfrom
fix/SDTEST-3844-seal-active-spans-on-premature-exit
Jun 19, 2026
Merged

[SDTEST-3844] Seal active test/suite/module as failed on premature process exit#285
ypopovych merged 1 commit into
mainfrom
fix/SDTEST-3844-seal-active-spans-on-premature-exit

Conversation

@ypopovych

Copy link
Copy Markdown
Contributor

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 (__AutoUnloadHookFrameworkLoadHandler.libraryUnloaded()) still runs and SessionManager.stop() closes the session and module spans. But any in-flight suite / retry-group / test is left open, never end()ed, and therefore dropped from the trace.

This is a general premature-exit problem, not a single vendor bug. Equivalent triggers:

  • a test (or its setup) calling exit(...) directly;
  • a failure in an async setUp/tearDown under recent Xcode, which terminates the runner after tearDown (forum thread — the "crash" framing there is inaccurate, it's an orderly exit).

Fix

DDXCTestObserver.stop() previously hit default: break for the .suite/.group/.test states and did nothing. It now detects the premature exit and:

  1. Logs a generic premature-exit warning — deliberately not attributed to any specific Xcode/vendor bug, since a user exit() produces the identical fault.
  2. Force-closes the open test and suite as failed, attaching a PrematureTestProcessExit error so the backend records why they failed.
  3. Ends the module the way testBundleDidFinish does (session.end(module:)); the failure propagates up to the session via set(failed:). The session/module spans are then closed by the unload path as before.

State is extended so .group/.test carry the references needed to recover from stop() (they previously held none). The suite-end mirrors testSuiteDidFinish but skips the feature hooks — those can't run meaningfully once the process is terminating.

Test

Adds testWhenProcessExitsMidTest_activeTestAndSuiteAreSealedAsFailed, which drives the observer into .test and 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). An async test crashes off-main on the unregister; a @MainActor async test deadlocks against XCTest's main-thread wait at the first await.

All 8 DDXCTestObserverTests pass; no regression to the normal completion flow.

Ref: SDTEST-3844

🤖 Generated with Claude Code

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]>
@ypopovych
ypopovych requested review from a team as code owners June 18, 2026 19:08
@datadog-prod-us1-4

This comment has been minimized.

@ypopovych
ypopovych requested a review from anmarchenko June 19, 2026 13:42
@ypopovych
ypopovych merged commit e49c497 into main Jun 19, 2026
20 of 21 checks passed
@ypopovych
ypopovych deleted the fix/SDTEST-3844-seal-active-spans-on-premature-exit branch June 19, 2026 13:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants