Skip to content

refactor(apple-runner): encapsulate XCTest-channel penalty state #2973

Description

@thymikee

Encapsulate the runner's XCTest-channel penalty state with its existing operations

Deletion-first acceptance

This is part of #2803's simplify-and-shrink initiative. Before production simplification, name the production mechanism, duplicated decision, state, fallback, forwarding layer, or public/internal interface this change removes. Compare deletion and inlining against extraction. Moving the same state into another file is not a deletion case.

Every PR reports baseline/final SHAs; added, deleted and net production lines; test/fixture/documentation changes separately; and the exact mechanisms removed. Use a rename-aware diff (git diff --numstat -M BASE...HEAD) and account for all affected production paths, including destination packages. Moving tests out of production files, generated output, formatting churn, and moving code elsewhere are not production-code deletion. Preserve readable code; no compressed formatting to hit a number.

Default for simplification work: net production reduction. A required behavior-preserving size split may be line-neutral. Growth needs an explicit, quantified explanation of the correctness or type guarantee it buys, why deletion/inlining cannot achieve it, and what old mechanism is removed; it is not automatically acceptable because ownership looks cleaner. If the only benefit is relocating fields or adding a wrapper, defer the change. Required regression tests and a minimal correctness fix are allowed to grow without inventing unrelated deletion to offset them.

Priority and scope gate

Readiness: deferred; remove ready-for-agent. Existing operations are already collocated and no penalty-state defect has been demonstrated. Resume only for a named deletion of duplicate state/coordination or a concrete correctness need. A new class with the same fields plus forwarding methods does not meet this initiative's goal. Preserve the distinct penalty, warmup and depth lifetimes. Run shared Swift work one issue at a time.

Parent: #2803. Readiness: deferred pending a concrete deletion or correctness case. Evidence baseline: 5712a3552e209c9c853bc1f42cb3e007f1213139; recheck current main. If resumed after its deletion/correctness gate, coordinate edits with target-transition characterization and ownership work; the current penalty operations are already explicit. #2911 has merged and #2882 owns additional actor source guards.

Problem and evidence

RunnerTests+SnapshotCapturePlan.swift:133–162 already colocates arm, clear and lookup functions. The missing piece is concrete ownership: their lock, bundle identity, deadline and duration remain writable on the entire XCTest case (RunnerTests.swift:118–122). This extraction should close that seam, not claim to discover a missing state machine or rewrite capture-plan policy.

The current penalty is a single retained record, not a per-app map. SnapshotTiming.swift:119–154 decides whether an XCTest-backed attempt deserves it from acquisition timing, typed timeout and deadline exhaustion. Main-thread abandonment can also arm it. Lifecycle explicitly clears it for process replacement/external relaunch; ordinary invalidation does not mean the same thing.

Small interface and ownership

Create one concrete SnapshotXCTestChannelPenalty module owning its private lock, optional bundle and expiration. Expose arm, clear and isActive(for:). Preserve the 120-second duration, existing log markers, and strict now < expiresAt lookup. Accept the current instant as an explicit operation input, or reuse an existing production clock seam, so expiry tests advance time without production-length sleeps. Do not add test-only setters or a scheduler framework.

Place the instance on RunnerTests, replace direct accesses, and delete superseded fields/functions. The existing acquisition classifier, capture-plan reordering and presentation-quality decisions retain their owners. The new module must not know backend capabilities, app activation or snapshot node shapes.

Keep SnapshotXCTestPenaltyWarmupExemption separate: it already has lock-protected ownership and a different contract (one pending exemption consumed by an actual plan). Keep private-AX accepted-depth memory separate: its positive-PID matching and non-renewing successful reuse are not this penalty's semantics. Shared duration values today do not justify a combined cache or lifetime abstraction.

Required preservation and completion checks

  • Arming replaces the single previous penalty; it does not accumulate one entry per app. A nil-bundle penalty applies to any requested bundle; a named penalty only matches that bundle.
  • Re-arming retains current deadline renewal behavior. Expiry itself need not mutate storage. Clearing emits its existing marker only when the old penalty was still active.
  • An independently recovered capture must still avoid occupied XCTest work; physical-device plans retain bounded XCTest probes while eligible simulator plans can defer to independent acquisition.
  • Slow presentation alone never arms the penalty; acquisition timeout or an exhausted XCTest tier can arm it without crossing the slow-duration threshold. Preserve warmup suppression at its current decision point.
  • Before: lock/state exposed across all runner extensions. After: only the owner accesses penalty state, with exactly the same effective-plan and quality outputs for the same observations.

Validation and exclusions

Move penalty-specific cases into the owner-matched test file; retain the consuming assertions in SnapshotCapturePlanTests, SnapshotTimingTests, LifecycleCacheTests, SnapshotCaptureTargetTests and SnapshotCapturePlanOccupancyTests. Cover nil/named bundle, replacement, exact expiry, clear, renewal and concurrent arm/read through the public interface. Plant a nil-bundle mismatch and a presentation-only penalty mutation to prove the nearest negative cases fail.

Run pnpm check:xctest-selection, unit-enabled iOS/macOS builds and selected XCTest cases, pnpm check:packaged-runner-swift, and pnpm check:affected --run. Preserve source-guard lane ownership. No claim of hostile-screen recovery improvement follows from extraction alone; report live evidence separately. No new retry strategy, clock-domain migration, penalty duration change, actor rewrite or host-side bridge-cache change belongs here.

Source paths in this issue are rooted at apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/; extension names abbreviate the RunnerTests+ prefix where stated. Tests live in its UnitTests/ directory. For a fresh worktree, run pnpm install --frozen-lockfile && pnpm build before the focused checks. Read docs/agents/testing.md and docs/agents/device-verification.md for native lane selection and build freshness.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions