Skip to content

refactor(move): split RunnerTests+CommandExecution.swift along command families - #2855

Merged
thymikee merged 1 commit into
refactor/2792-move-runner-inline-testsfrom
refactor/2792-split-command-execution
Sep 24, 2026
Merged

thymikee merged 1 commit into
refactor/2792-move-runner-inline-testsfrom
refactor/2792-split-command-execution

Conversation

@thymikee

Copy link
Copy Markdown
Member

Summary

Closes #2792. Stacked on #2854.

Pure move: RunnerTests+CommandExecution.swift (1,985 lines after #2854) is split by moving whole declarations along command families:

  • CommandDispatch (620): dispatch entry, recovery loop, target preparation, recorded-failure conversion
  • CommandExecution (638): the prepared-command switch
  • GestureExecution (165)
  • ScrollDragExecution (287)
  • TypeExecution (142)
  • SnapshotExecution (151)

The tests split the same way. The dispatch recovery test follows executeDispatchedWithRecovery into CommandDispatchTests. The shared helpers move beside what they drive: execute(command:) goes to TransportTests and runnerCommandFixture to ModelsTests. The split widens 12 declarations from private to internal, each read from another file. Every #if guard is kept. 15 files.

git diff -M90% --stat refactor/2792-move-runner-inline-tests...HEAD: 15 files, +2041/−1986. A removed-vs-added line multiset differs only in private→internal, the per-file import/extension RunnerTests { headers, and the iOS stub block split between two test files under the same guard.

Validation

Tested at e9a7193a7 (tree-identical to the originally tested fe30c8f65):

  • macOS host lane, signed: 233 tests, 0 failures before (base 60e533284) and 233, 0 failures after.
  • pnpm check:xctest-selection: host 233, iOS PR list 103, nightly 280, green.
  • pnpm check:packaged-runner-swift: green.
  • pnpm check:affected --run: all runnable checks passed.
  • iOS simulator build-for-testing, with and without the unit-test flag: succeeded.
  • Declared func test names: 285 before and after.

An adversarial fresh-context review found no functional issues: bodies, guards, lane reach, and widenings were confirmed, and every file is under 1,000 lines. The three stale comments it found are fixed in #2854.

🤖 Generated with Claude Code

…d families

The 1,985-line file becomes six, each under 1,000 lines, by moving whole
declarations:

- CommandDispatch: executeAccepted through executeOnMain, the dispatched
  recovery loop, target preparation, activation preflight, and recorded-failure
  conversion.
- CommandExecution: the prepared-command switch (executeOnMainPrepared).
- GestureExecution: gesture timing and gesture response building.
- ScrollDragExecution: scroll and drag execution and their refusals.
- TypeExecution: executeTypeCommand.
- SnapshotExecution: snapshot dispatch, preparation, and post-capture marks.

Its tests split the same way, and the dispatch recovery test follows
executeDispatchedWithRecovery into CommandDispatchTests. The shared test
helpers move beside what they drive: execute(command:) to TransportTests and
runnerCommandFixture to ModelsTests. Declarations now read across files widen
from private to internal.

Closes #2792

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
Installed (including dependencies) 4.79 MB 4.79 MB +299 B
Package (unpacked) 4.79 MB 4.79 MB +299 B
Package (download) 1.43 MB 1.43 MB +508 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 25.6 ms 25.6 ms -0.1 ms
CLI --help 77.9 ms 75.7 ms -2.2 ms

@thymikee

Copy link
Copy Markdown
Member Author

This is a clean move: RunnerTests+CommandExecution.swift splits along command families with no logic changes. Compile success for the 12 widened cross-file calls and the reported 233/0 test result rest on the author's report and green CI, since the Swift lanes were not run locally here. Swift only lets an internal declaration shadow a same-named one elsewhere through overload resolution, and the multiset check found no new name collisions from the 12 widened names, but the full RunnerTests extension set was not grepped for pre-existing internal overloads of those names, so a narrower check on just those 12 names would remove the last bit of doubt. All 13 checks pass, and the diff only touches Swift runner sources, unit-test files, and the README. #2854 needs to merge first since this PR is stacked on it; there are no other findings to act on here.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Sep 23, 2026
@thymikee
thymikee added this pull request to stack #2859 September 24, 2026 05:43
@thymikee
thymikee merged commit f356394 into main Sep 24, 2026
18 checks passed
@thymikee
thymikee deleted the refactor/2792-split-command-execution branch September 24, 2026 05:44
thymikee added a commit that referenced this pull request Sep 24, 2026
main (#2854, #2855) moved the runner's inline unit tests into UnitTests/ and
split RunnerTests+CommandExecution.swift along command families. Rebase conflicts
dropped this PR's edits from the inline blocks the rebase kept on main; relocate
them into the file main now owns for each family:

- RunnerTests+SnapshotTests.swift: the query-sweep slice-deadline and
  fail-closed-invalidation tests, and the bounded-modal-probe helper now takes a
  SnapshotCaptureTarget.
- RunnerTests+SnapshotCapturePlanTests.swift: the tier-timeout rejection pair and
  the private-AX depth helper now builds a SnapshotCaptureTarget.
- RunnerTests+SnapshotTimingTests.swift: the deadline-exhausted tier penalty test.
thymikee added a commit that referenced this pull request Sep 24, 2026
…e query sweep by its slice (#2836)

* fix(ios): take snapshot target identity on main and hop plan state writes

The capture plan runs on the command queue but read currentBundleId,
currentAppProcessIdentifier and the penalty warm-up exemption, and wrote
runnerAccessibilityHealth and invalidated the cached target directly.

Snapshot preparation now takes a SnapshotCaptureTarget on main (app,
bundle id, pid, consumed warm-up exemption) and the plan, the modal
probe penalty, the XCTest penalty recorder and the private AX tier read
only that copy. Health writes and the fail-closed invalidation go through
applyMainOwnedSnapshotState, the abandoned-work-guarded main hop that
invalidateCachedTargetAfterSnapshotFailure now also uses.

Fixes #2781

* fix(ios): bound the query sweep by its tier slice deadline

A non-interactive query sweep used Date.distantFuture as its own
deadline, so only the up-to-20 s plan deadline bounded it while the
caller abandoned the tier after its 1 s slice. XCTest queries cannot be
cancelled, so the sweep kept the main thread busy and later commands got
RUNNER_BUSY.

The caller now derives one slice deadline (querySweepSliceDeadline) for
both its wait and the sweep, for interactive and non-interactive
requests alike. Neither the element queries nor the per-element reads
start with less than flatInteractiveQueryBudget left before that
deadline (querySweepCanStartQuery).

Fixes #2783

* fix(ios): spend the penalty warm-up exemption only in a capture plan that runs

Taking the snapshot target on main consumed the exemption during command
preparation, so a snapshot answered by the blocking system-modal probe, or
an abandoned preparation that ran late, spent it without running a plan.
The exemption now lives in a lock-owned type that lifecycle code arms on
main and runSnapshotCapturePlan consumes at its start.

* test(ios): pin the non-interactive query sweep to its tier slice through the plan

Swizzles XCUIElementQuery.allElementsBoundByIndex with a slow query and runs
a non-interactive [.querySweep] plan with a 20 s plan deadline. The sweep
must not be abandoned, and no query may start after the plan answers, so
passing the plan deadline to the sweep in either loop turns it red.

* test(ios): pin the query-sweep tier timeout and the tap routing identity to main

Two regressions #2781 still leaves open, both red on this head:

A query sweep that stops at its own slice deadline is a tier timeout, but
the plan classified its payload by node count, so a partial sweep that beat
the sparse threshold armed no XCTest-channel penalty and no later capture
of the same screen was deferred.

The coordinate tap's system-modal routing probe armed its penalty with
currentBundleId read on the command queue, while main owned that state and
could still be clearing or rebinding it.

* fix(ios): count a query sweep that ends on its slice deadline as a tier timeout

The sweep tier stopped starting queries at its slice deadline, but the plan
classified only what it collected. sparsePayloadReason accepts anything above
the sparse node threshold, so a partial flat sweep became a `recovered`
capture: no XCTest-channel penalty was armed, private AX never ran, and every
later capture of that screen paid for the full sweep again.

`runFlatInteractiveQueries` now returns a typed `SnapshotTierOutcome`, the
sweep tier carries it into the attempt, and the plan rejects a
`.deadlineExhausted` tier through `snapshotTierRejectionReason` — keeping its
payload only as the fallback, arming the penalty through the existing tier
timeout reason, and letting the next backend answer.

* fix(ios): take the system-modal probe's target identity on main

The coordinate tap resolved its system-modal routing on the command queue and
armed an abandoned probe's penalty with `currentBundleId` read there, while
`applyMainOwnedSnapshotState` and the lifecycle code write that identity
through `DispatchQueue.main.async`. A tap could penalize a target main was
still clearing or rebinding.

The probe now takes a `SnapshotProbePenaltyTarget`: a capture hands over the
identity it already took on main, and the tap route hands over nothing and lets
the probe's own main-side block capture the identity main holds once its work
starts. No off-main path reads target identity any more.

* test(ios): place this PR's runner tests in main's split UnitTests files

main (#2854, #2855) moved the runner's inline unit tests into UnitTests/ and
split RunnerTests+CommandExecution.swift along command families. Rebase conflicts
dropped this PR's edits from the inline blocks the rebase kept on main; relocate
them into the file main now owns for each family:

- RunnerTests+SnapshotTests.swift: the query-sweep slice-deadline and
  fail-closed-invalidation tests, and the bounded-modal-probe helper now takes a
  SnapshotCaptureTarget.
- RunnerTests+SnapshotCapturePlanTests.swift: the tier-timeout rejection pair and
  the private-AX depth helper now builds a SnapshotCaptureTarget.
- RunnerTests+SnapshotTimingTests.swift: the deadline-exhausted tier penalty test.
@thymikee

Copy link
Copy Markdown
Member Author

Follow-up on the name-collision check. I ran it only on the 12 names this split widened from private to internal: currentUptimeMs, executeDragGesture, executeScrollDragGesture, executeSnapshotDispatched, gestureFallback, invalidScrollDirectionResponse, invalidScrollDurationResponse, measureGesture, plannedGestureResponse, resolveAppWithoutActivation, scrollDurationIsValid, scrollKeyboardOccludedResponse. I searched every func/var/let declaration under apple/runner at f356394d0.

  • 10 of the names have exactly one declaration.
  • currentUptimeMs and gestureFallback each have a second match. Both are stored properties of struct DataPayload (RunnerTests+Models.swift:308, :322), the response payload fields, not members of RunnerTests. They can't shadow or overload the widened RunnerTests methods.

So there are no internal overloads or shadowing among the widened names. The #if guard of every test is also unchanged: 285/285, 0 mismatches (details on #2854).

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor(ios-runner): move ~3.9k lines of inline unit tests out of production Swift files; split CommandExecution under 1,000 lines

1 participant