Skip to content

Commit 74bf80b

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 516f8db commit 74bf80b

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
@@ -439,6 +439,16 @@ extension RunnerTests {
439439
}
440440
#endif
441441

442+
func testInjectedTapRecordedFailureGateIsTapOnlyAndCountGated() {
443+
// The seam's recording side cannot run in-bundle (a real XCTIssue would
444+
// fail this very test run — same constraint the record(_:) suppression
445+
// tests document); the live daemon proof covers it. This pins the gate.
446+
XCTAssertFalse(RunnerTests.shouldInjectTapRecordedFailure(command: .tap, remaining: 0))
447+
XCTAssertTrue(RunnerTests.shouldInjectTapRecordedFailure(command: .tap, remaining: 1))
448+
XCTAssertFalse(RunnerTests.shouldInjectTapRecordedFailure(command: .type, remaining: 1))
449+
XCTAssertFalse(RunnerTests.shouldInjectTapRecordedFailure(command: .snapshot, remaining: 1))
450+
}
451+
442452
func testXCTestRecordedFailureResponseFailsMutatingSuccesses() throws {
443453
let command = try runnerCommandFixture(#"{"command":"tap","commandId":"tap-1"}"#)
444454
let response = Response(ok: true, data: DataPayload(message: "tapped"))
@@ -1227,6 +1237,20 @@ extension RunnerTests {
12271237
userInfo: [NSLocalizedDescriptionKey: "command returned no response"]
12281238
)
12291239
}
1240+
#if AGENT_DEVICE_RUNNER_UNIT_TESTS
1241+
// #1605 merge gate: the REAL gesture already executed above; recording a
1242+
// production-shaped issue here makes the per-command failure-count
1243+
// conversion below fire exactly as in the field (bsky-24: activation
1244+
// lands, bookkeeping records a failure). Compiled out of production.
1245+
if consumeInjectedTapRecordedFailureForTesting(command: command.command) {
1246+
record(
1247+
XCTIssue(
1248+
type: .assertionFailure,
1249+
compactDescription: "Injected tap recorded-failure (#1605 corroboration merge gate)"
1250+
)
1251+
)
1252+
}
1253+
#endif
12301254
if didRecordXCTestFailure(since: failureCountBefore),
12311255
let failureResponse = xctestRecordedFailureResponse(command: command, response: response)
12321256
{

apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift

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

0 commit comments

Comments
 (0)