Skip to content

Commit 5454517

Browse files
committed
test(ios): deterministic injection seam for recorded-tap-failure corroboration (#1605 merge gate)
The field failure cannot be reproduced on this head: the tap false-failures were a downstream symptom of XCTest-channel saturation, which the #1587 capture fixes removed. The seam records a real XCTIssue AFTER the real gesture inside the per-command failure-count window, so xctestRecordedFailureResponse and target invalidation fire byte-for-byte like the field failure. Armed via a decrementing /tmp flag file (the daemon regenerates tampered xctestrun templates, so env plumbing cannot reach a daemon-spawned runner); compiled only under AGENT_DEVICE_RUNNER_UNIT_TESTS. Live evidence on a daemon-spawned runner (Bluesky, ad-bsky-repro sim): - landed case: injected failure on a real Search-tab tap -> success with the corroboration warning, screen verifiably on Search, no redispatch, runner serving next commands; flag consumed exactly once. - unchanged case: injected failure on a dead-coordinate tap -> capture unchanged -> XCTEST_RECORDED_FAILURE preserved with the new honest hint; runner still usable. - field-shape race (relaunch -> full snapshot -> immediate press, 5 attempts): no natural recorded failure occurs on this head — the hostile tree needed for channel saturation is gone, corroborating the causal story.
1 parent 961c9f8 commit 5454517

2 files changed

Lines changed: 60 additions & 0 deletions

File tree

apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,16 @@ extension RunnerTests {
340340
}
341341
#endif
342342

343+
func testInjectedTapRecordedFailureGateIsTapOnlyAndCountGated() {
344+
// The seam's recording side cannot run in-bundle (a real XCTIssue would
345+
// fail this very test run — same constraint the record(_:) suppression
346+
// tests document); the live daemon proof covers it. This pins the gate.
347+
XCTAssertFalse(RunnerTests.shouldInjectTapRecordedFailure(command: .tap, remaining: 0))
348+
XCTAssertTrue(RunnerTests.shouldInjectTapRecordedFailure(command: .tap, remaining: 1))
349+
XCTAssertFalse(RunnerTests.shouldInjectTapRecordedFailure(command: .type, remaining: 1))
350+
XCTAssertFalse(RunnerTests.shouldInjectTapRecordedFailure(command: .snapshot, remaining: 1))
351+
}
352+
343353
func testXCTestRecordedFailureResponseFailsMutatingSuccesses() throws {
344354
let command = try runnerCommandFixture(#"{"command":"tap","commandId":"tap-1"}"#)
345355
let response = Response(ok: true, data: DataPayload(message: "tapped"))
@@ -1128,6 +1138,20 @@ extension RunnerTests {
11281138
userInfo: [NSLocalizedDescriptionKey: "command returned no response"]
11291139
)
11301140
}
1141+
#if AGENT_DEVICE_RUNNER_UNIT_TESTS
1142+
// #1605 merge gate: the REAL gesture already executed above; recording a
1143+
// production-shaped issue here makes the per-command failure-count
1144+
// conversion below fire exactly as in the field (bsky-24: activation
1145+
// lands, bookkeeping records a failure). Compiled out of production.
1146+
if consumeInjectedTapRecordedFailureForTesting(command: command.command) {
1147+
record(
1148+
XCTIssue(
1149+
type: .assertionFailure,
1150+
compactDescription: "Injected tap recorded-failure (#1605 corroboration merge gate)"
1151+
)
1152+
)
1153+
}
1154+
#endif
11311155
if didRecordXCTestFailure(since: failureCountBefore),
11321156
let failureResponse = xctestRecordedFailureResponse(command: command, response: response)
11331157
{

apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,42 @@ final class RunnerTests: XCTestCase {
115115
// seconds on remote-hosted consent dialogs and bypass the plan budget (#1244).
116116
let systemModalProbeBudget: TimeInterval = 4
117117
#if AGENT_DEVICE_RUNNER_UNIT_TESTS
118+
// #1605 merge gate: deterministic live reproduction of the field ambiguity —
119+
// a tap whose coordinate activation LANDS while XCTest bookkeeping records a
120+
// failure. Armed by writing a decrementing count to the flag file below
121+
// (the daemon regenerates tampered xctestrun templates, so env plumbing
122+
// cannot reach a daemon-spawned runner); consumed one injection per tap.
123+
// The injection records a real XCTIssue AFTER the real gesture, so
124+
// `xctestRecordedFailureResponse` and target invalidation fire byte-for-byte
125+
// like a field failure. Production builds compile none of this.
126+
static let injectedTapFailureFlagPathForTesting =
127+
"/tmp/agent-device-inject-tap-recorded-failure-for-testing"
128+
129+
static func shouldInjectTapRecordedFailure(command: CommandType, remaining: Int) -> Bool {
130+
command == .tap && remaining > 0
131+
}
132+
133+
func consumeInjectedTapRecordedFailureForTesting(command: CommandType) -> Bool {
134+
guard
135+
let raw = try? String(
136+
contentsOfFile: Self.injectedTapFailureFlagPathForTesting,
137+
encoding: .utf8
138+
),
139+
let remaining = Int(raw.trimmingCharacters(in: .whitespacesAndNewlines))
140+
else {
141+
return false
142+
}
143+
guard Self.shouldInjectTapRecordedFailure(command: command, remaining: remaining) else {
144+
return false
145+
}
146+
try? String(remaining - 1).write(
147+
toFile: Self.injectedTapFailureFlagPathForTesting,
148+
atomically: true,
149+
encoding: .utf8
150+
)
151+
return true
152+
}
153+
118154
// Unit-test-only injectable override for the system-modal probe (see
119155
// `boundedBlockingSystemAlertSnapshot` in RunnerTests+Snapshot.swift): when set, a test's probe
120156
// body runs in place of `blockingSystemAlertSnapshot` so it can force a real timeout without a

0 commit comments

Comments
 (0)