Skip to content

refactor(move): move the runner's inline unit tests into UnitTests/ - #2854

Merged
thymikee merged 2 commits into
mainfrom
refactor/2792-move-runner-inline-tests
Sep 24, 2026
Merged

thymikee merged 2 commits into
mainfrom
refactor/2792-move-runner-inline-tests

Conversation

@thymikee

Copy link
Copy Markdown
Member

Summary

Part of #2792. Based on main: the #2825#2845 stack merged, and main fa3b7291f has the same tree as 60e533284.

Pure move: every whole #if AGENT_DEVICE_RUNNER_UNIT_TESTS test block in a production runner source moves to UnitTests/RunnerTests+<Source>Tests.swift under the same guard, with the same test names and bodies. This includes the blocks the rebased base added in RunnerTests+Lifecycle.swift and RunnerTests+SynthesizedGesturePolicy.swift. Production files keep only test seams: overrides, injected failures, the recorder timestamp accessor, and stored test properties. The move widens 20 declarations from private to internal, each read by a moved test. Five fixture loaders step up one more directory. Comments that pointed at the old test locations now point at the new ones. The final chore(gates) commit rewords the declaration-scan comments. 54 files.

git diff -M90% --stat origin/main...HEAD: 54 files, +4088/−4033. A removed-vs-added line multiset differs only in privateinternal, imports, extension RunnerTests { wrappers, and the five // UnitTests path steps.

Validation

Tested at 5785e206b..61fcc6969 (tip 61fcc6969, tree-identical to the originally tested ea85fd32e):

  • 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, 0 unreachable, green.
  • pnpm check:packaged-runner-swift: green.
  • pnpm check:affected --run: all runnable checks passed. It selected the full set because of the workflow and tooling paths.
  • iOS simulator build-for-testing with the unit-test flag: succeeded.
  • Declared func test names: 285 before and after, with no duplicates.

An adversarial fresh-context review tried to refute the pure move and confirmed test bodies, guards, lane reach, and every access widening. It found three stale comments, which are now fixed.

🤖 Generated with Claude Code

thymikee and others added 2 commits September 23, 2026 20:32
Every whole `#if AGENT_DEVICE_RUNNER_UNIT_TESTS` test block in a production
runner source moves to `UnitTests/RunnerTests+<Source>Tests.swift` under the
same guard, with the same test names and bodies. Production sources keep only
the guarded test seams (overrides, injected failures, the recorder timestamp
accessor, and RunnerTests' stored test properties).

Declarations a moved test reads widen from private to internal. Fixture loaders
that resolve contracts/fixtures from #filePath step up one more directory, as
the existing UnitTests loaders do.

Part of #2792

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

RunnerTapPointPolicy.swift no longer declares a test, so the declaration scan and
its test stop citing it as the file whose name hid one.

Part of #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 -4.2 kB
Package (unpacked) 4.79 MB 4.79 MB -4.2 kB
Package (download) 1.43 MB 1.43 MB -256 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 18.7 ms 19.2 ms +0.5 ms
CLI --help 53.8 ms 51.4 ms -2.4 ms

@thymikee

Copy link
Copy Markdown
Member Author

This is a pure move, and 61fcc69 checks out: I compared the multiset of test lines before and after, and the content is preserved. CI is green across all 18 checks, including the runner unit-test lanes and the xctest-selection check, which are the routes that matter here.

I did not run the macOS host lane or the iOS build-for-testing myself, and the 233-test counts before and after come from the PR body and CI, not from a run I did. The multiset match proves line content survived the move, but it does not prove each test kept an equivalent guard (for example && os(iOS)); a changed guard would still show equal counts, so is there a check that confirms each test landed under the same conditional compilation as before?

Not blocking: the PR says this is part of #2792 and the CommandExecution split under 1,000 lines (apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift) is still to do, so keep #2792 open for that follow-up, but that's optional to act on now.

Nothing here stops this from merging.

@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 7fbf491 into main Sep 24, 2026
18 checks passed
@thymikee
thymikee deleted the refactor/2792-move-runner-inline-tests branch September 24, 2026 05:44
@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-09-24 05:45 UTC

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 guard question: yes. I compared each test's full #if guard stack before and after (7fbf491b8^ vs 7fbf491b8). The script walks every runner Swift file, tracks the #if/#elseif/#else/#endif stack, and records the combined condition for each test function, testCommand included. Result: 285 test declarations on both sides and 0 guard mismatches, so tests under conditions like AGENT_DEVICE_RUNNER_UNIT_TESTS && os(iOS) kept the same condition. The same check on #2855 (f356394d0^ vs f356394d0) also gives 285/285 with 0 mismatches.

The earlier review had cross-checked this a second way. Running the repo's own #if evaluator, the active code for iOS, macOS and tvOS was identical before and after, with the unit-test flag both on and off. check-xctest-selection also reported the same per-lane reach: host 233, iOS PR 103, nightly 280.

#2792 is now closed by #2855. RunnerTests+CommandExecution.swift is 638 lines and no runner file is over 1,000.

🤖 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.

1 participant