base red: the test run-loop guard holds only the Swift Testing helper, never xctest (xctest signal 6 after every CmuxNext suite)
Summary
Takes back #33023: its -XCTest argv check in CmuxNextTestRunLoopGuard.c is replaced by a stricter gate (the guard installs only in swiftpm-testing-helper), which covers the same xctest leg and also the whole-package swift test XCTest phase that starts xctest <bundle> with no -XCTest (SwiftPM release/6.3 SwiftTestCommand.swift lines 402, 1008-1014); nothing of #33023's behavior is lost.
Update after the feat merge (head 11884afff783). #33023 (e512575f955, merged 05:17Z) fixed the same fault on feat in a different way: the guard skips the hold when argv has -XCTest. In the merge conflict I kept one gate: the guard installs nothing unless the process is swiftpm-testing-helper. That covers #33023's case and every other host (an Xcode test host, xctest without -XCTest), and runs_xctest_leg is removed. This PR now adds (1) the stricter gate and (2) the regression test #33023 does not have. At the new base the regression test also passes, because its child xctest has -XCTest in its argv. The red results below are against the old base 362be7b7d362, before #33023.
Base red on feat-cmux-next (STATUS 8187): CmuxNext Swift test steps pass every Swift Testing test, then the XCTest phase of swift test (xctest -XCTest '' …/CmuxNextPackageTests.xctest) hangs for about 10 minutes and ends with "unexpected signal code 6". Seen on aws-m4pro-1, -3, -4, -6, -9 and on Blacksmith macOS 26.
Cause, from the stack of the hung xctest (aws-m4pro-6, feat 305434be55a1, step 3bd61ca1):
_XCTestMain -> -[XCTestDriver _runTests] -> -[XCTFuture value]
-> +[XCTWaiter _synchronouslyWaitForTimeInterval:] -> CFRunLoopRun -> __CFRunLoopDoBlocks
-> __cmux_next_test_runloop_guard_install_block_invoke -> guard_hold_the_main_loop -> CFRunLoopRun
The run-loop guard in Tests/Support/CmuxNextTestExitTrace (#25861) queues a block on the main run loop that never returns. That is correct only in swiftpm-testing-helper, whose main is swift_task_asyncMainDrainQueue. xctest loads the same bundle. When XCTest's start-up work is not done at once, XCTest turns the main run loop to wait for it. The guard's block runs in that turn and keeps the main thread, so XCTest never runs its suite. The host-dependent part is only how slow XCTest start-up is. On some hosts a Contacts store lookup in xctest fails for 22 s first: those are the CoreData 134020 / NSXPCConnection lines. On aws-m4pro-1 the same hang came with no CoreData line. No cmux code uses Contacts, AddressBook or CoreData. The CoreData lines are a symptom of a slow start-up, not the abort.
Fix: the guard installs nothing (no observer, no source, no block) in a process that is not swiftpm-testing-helper. In the helper, it works the same as before. Its own regression test (TestProcessExitTests.aStopOfTheOutermostRunLoopDoesNotEndTheTestRun) is unchanged.
Testing
Regression test XCTestPhaseTests.anXCTestWaitInTheXCTestPhaseReturns (red commit 980a603, before the fix): it runs a child xctest of this same bundle with an XCTest probe that waits on an expectation, the same way XCTest's start-up waits. It needs the probe to pass and xctest to exit 0 within 120 s. On the base, the guard keeps the child's main thread on every host, so the test does not depend on a slow start-up.
Focused command (fleet): cmux-ci run --class light --script scripts/ci/package-test-lane.sh --arg suite --arg Packages/macOS/CmuxNext --arg XCTestPhaseTests --arg EmptyLabelMeasureTests --ref SHA --target-worker WORKER
Red: fix/base-red-xctest-red-proof at 69e5a3b7c330 (base 362be7b7d362 plus the head's XCTestPhaseTests.swift, without the one test that calls the new C function; never merge). Green: PR head bc800c77fbe0.
| commit | worker | step | XCTestPhaseTests | EmptyLabelMeasureTests | step exit |
|---|---|---|---|---|---|
| red 69e5a3b | aws-m4pro-6 | 7afcf04e | failed after 120.3 s (child xctest killed at the limit; its only output is the guard's "holding the main run loop open" line); then xctest signal 6, 787 s | xctest signal 6 after 2/2 Swift Testing passed, 659 s | 1 |
| red 69e5a3b | aws-m4pro-8 | 7a5152c2 | failed after 121.7 s, same three issues | CoreData XPC lines, then failed, 660 s | 1 |
| green bc800c7 | aws-m4pro-6 | fb48cb94 | 2/2 passed (the XCTest wait returns in 0.15 s); XCTest phase "Executed 0 tests", exit 0; 11 s | 2/2 passed; XCTest phase exit 0 | 0 |
| green bc800c7 | aws-m4pro-8 | 1cfd2401 | 2/2 passed; XCTest phase exit 0; 11 s | 2/2 passed; XCTest phase exit 0; 3 s | 0 |
aws-m4pro-6 failed on the base before (steps 3554f3fe, 3bd61ca1), and it fails at the red commit and passes at the head.
Observed / Not run
Observed: the hung xctest stack above (step 3bd61ca1, from the lane watchdog with a 240 s limit); the four steps in the table; scripts/cmux-next/prepush-gate.py on each push: no new violations. The base passed on aws-m4pro-7 and -9 in some runs (steps 7048809b, 65f92591): the base red needs a slow XCTest start-up, but the new test is red at the red commit on every host, because the probe's own wait(for:) turns the run loop.
Not run: the whole-package swift test (the broad pass); Blacksmith macOS. I did not find what in xctest does the Contacts store lookup (a system framework; no cmux code uses Contacts, AddressBook or CoreData). With the fix, that lookup can only make the XCTest phase slower; it cannot hold it.
Changelog
none
Proof
No capture: test-support code only; nothing in the app changes.