Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1568,8 +1568,7 @@ extension RunnerTests {

let skipExistenceWait = canUseFastForegroundAppGuard(
activeApp: activeApp,
requestedBundleId: requestedBundleId,
command: command.command
requestedBundleId: requestedBundleId
)
if !skipExistenceWait && !activeApp.waitForExistence(timeout: appExistenceTimeout) {
if let bundleId = requestedBundleId {
Expand All @@ -1591,8 +1590,7 @@ extension RunnerTests {
}
let skipInteractionExistenceWait = canUseFastForegroundAppGuard(
activeApp: activeApp,
requestedBundleId: requestedBundleId,
command: command.command
requestedBundleId: requestedBundleId
)
if !skipInteractionExistenceWait && !activeApp.waitForExistence(timeout: 2) {
if let bundleId = requestedBundleId {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,17 @@ extension RunnerTests {
currentApp = app
currentBundleId = nil
currentAppProcessIdentifier = nil
resetTargetBoundState()
}

/// State that belongs to the currently bound target and must not outlive it: the text-entry tap
/// witness, the fresh-process snapshot warmup exemption, and the last-written per-command log
/// markers. Every site that binds, rebinds, or drops the target runs this.
func resetTargetBoundState() {
clearRememberedTextEntryTap()
snapshotXCTestPenaltyWarmupExemptionPending = false
lastLoggedFastAppGuardLine = nil
lastLoggedGesturePolicyLines.removeAll()
Comment thread
Copilot marked this conversation as resolved.
}

func invalidateCachedTarget(reason: String) {
Expand All @@ -177,8 +186,7 @@ extension RunnerTests {
currentApp = nil
currentBundleId = nil
currentAppProcessIdentifier = nil
clearRememberedTextEntryTap()
snapshotXCTestPenaltyWarmupExemptionPending = false
resetTargetBoundState()
}

func resetTargetAfterExternalRelaunch() -> Response {
Expand Down Expand Up @@ -208,7 +216,7 @@ extension RunnerTests {
)
currentApp = candidate
currentAppProcessIdentifier = observedProcessIdentifier
clearRememberedTextEntryTap()
resetTargetBoundState()
clearSnapshotXCTestChannelPenalty(reason: "target_process_changed")
clearPrivateAXAcceptedDepth(reason: "target_process_changed")
snapshotXCTestPenaltyWarmupExemptionPending = true
Expand Down Expand Up @@ -246,22 +254,26 @@ extension RunnerTests {

func canUseFastForegroundAppGuard(
activeApp: XCUIApplication,
requestedBundleId: String?,
command: CommandType
requestedBundleId: String?
) -> Bool {
guard let requestedBundleId, currentBundleId == requestedBundleId, currentApp != nil else {
return false
}
guard activeApp.state == .runningForeground else { return false }
NSLog(
"AGENT_DEVICE_RUNNER_FAST_APP_GUARD command=%@ bundle=%@ state=%d",
String(describing: command),
requestedBundleId,
activeApp.state.rawValue
)
writeFastAppGuardMarker(bundleId: requestedBundleId, state: activeApp.state)
return true
}

func writeFastAppGuardMarker(bundleId: String, state: XCUIApplication.State) {
// The command is on the adjacent COMMAND_ACCEPTED line; repeating it here would make a deduped
// marker read as if only that command ever passed the guard.
let line = "AGENT_DEVICE_RUNNER_FAST_APP_GUARD bundle=\(bundleId) state=\(state.rawValue)"
if lastLoggedFastAppGuardLine != line {
lastLoggedFastAppGuardLine = line
runnerMarkerWriter(line)
}
}

/// The pid of the one other application holding an active accessibility session, or nil unless
/// exactly one exists. What this proves is that liveness claim and nothing more: the private AX
/// client exposes no ordering of `activeApplications`, so this is NOT a foreground owner — it is
Expand Down Expand Up @@ -312,8 +324,7 @@ extension RunnerTests {
currentApp = target
currentBundleId = bundleId
currentAppProcessIdentifier = Self.processIdentifier(of: target)
clearRememberedTextEntryTap()
snapshotXCTestPenaltyWarmupExemptionPending = false
resetTargetBoundState()
beginFirstInteractionStabilization()
return target
}
Expand Down Expand Up @@ -479,3 +490,33 @@ extension RunnerTests {
usleep(useconds_t(delay * 1_000_000))
}
}

#if AGENT_DEVICE_RUNNER_UNIT_TESTS
extension RunnerTests {
func testResettingTargetBoundStateForgetsTheLastWrittenMarkers() {
defer { invalidateCachedTarget(reason: "unit_test_cleanup") }
lastLoggedFastAppGuardLine = "AGENT_DEVICE_RUNNER_FAST_APP_GUARD bundle=app state=4"
lastLoggedGesturePolicyLines[.scroll] = "AGENT_DEVICE_RUNNER_SYNTHESIZED_GESTURE_POLICY kind=scroll"
resetTargetBoundState()
XCTAssertNil(lastLoggedFastAppGuardLine, "a rebind must state the guard once more")
XCTAssertTrue(lastLoggedGesturePolicyLines.isEmpty, "a rebind must state the policy once more")
}

func testFastAppGuardMarkerWritesOnceUntilTheFactChanges() {
var written: [String] = []
runnerMarkerWriter = { written.append($0) }
defer {
runnerMarkerWriter = { NSLog("%@", $0) }
invalidateCachedTarget(reason: "unit_test_cleanup")
}
writeFastAppGuardMarker(bundleId: "com.example.app", state: .runningForeground)
writeFastAppGuardMarker(bundleId: "com.example.app", state: .runningForeground)
XCTAssertEqual(written.count, 1, "a repeated fact writes no second line")
writeFastAppGuardMarker(bundleId: "com.example.other", state: .runningForeground)
XCTAssertEqual(written.count, 2, "a changed fact writes a new line")
resetTargetBoundState()
writeFastAppGuardMarker(bundleId: "com.example.other", state: .runningForeground)
XCTAssertEqual(written.count, 3, "a rebind states the same fact once more")
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -139,26 +139,60 @@ extension RunnerTests {
fallbackAttempted: Bool
) {
#if os(iOS)
guard let context else {
NSLog(
"AGENT_DEVICE_RUNNER_SYNTHESIZED_GESTURE_POLICY kind=%@ context=unavailable fallbackAttempted=%@",
kind.rawValue,
fallbackAttempted.description
)
return
}
NSLog(
"AGENT_DEVICE_RUNNER_SYNTHESIZED_GESTURE_POLICY kind=%@ axHealth=%@ frameSource=window keyboardPolicy=%@ fallbackPolicy=%@ fallbackAllowed=%@ fallbackAttempted=%@",
kind.rawValue,
context.accessibilityHealth.rawValue,
context.keyboardPolicy.rawValue,
context.fallbackPolicy.rawValue,
context.allowsXCTestCoordinateFallback.description,
fallbackAttempted.description
let line = Self.synthesizedGesturePolicyLine(
kind: kind,
context: context,
fallbackAttempted: fallbackAttempted
)
// The same decision for the same gesture kind on every command is one line; a changed policy
// (AX health, keyboard, fallback) is a new one.
if lastLoggedGesturePolicyLines[kind] != line {
lastLoggedGesturePolicyLines[kind] = line
runnerMarkerWriter(line)
Comment on lines +149 to +151
}
#endif
}

static func synthesizedGesturePolicyLine(
kind: SynthesizedGesturePolicyKind,
context: SynthesizedCoordinateContext?,
fallbackAttempted: Bool
) -> String {
guard let context else {
return "AGENT_DEVICE_RUNNER_SYNTHESIZED_GESTURE_POLICY kind=\(kind.rawValue)"
+ " context=unavailable fallbackAttempted=\(fallbackAttempted)"
}
return "AGENT_DEVICE_RUNNER_SYNTHESIZED_GESTURE_POLICY kind=\(kind.rawValue)"
+ " axHealth=\(context.accessibilityHealth.rawValue) frameSource=window"
+ " keyboardPolicy=\(context.keyboardPolicy.rawValue)"
+ " fallbackPolicy=\(context.fallbackPolicy.rawValue)"
+ " fallbackAllowed=\(context.allowsXCTestCoordinateFallback)"
+ " fallbackAttempted=\(fallbackAttempted)"
}
}

#if AGENT_DEVICE_RUNNER_UNIT_TESTS && os(iOS)
extension RunnerTests {
func testSynthesizedGesturePolicyMarkerWritesOncePerKindUntilTheDecisionChanges() {
var written: [String] = []
runnerMarkerWriter = { written.append($0) }
defer {
runnerMarkerWriter = { NSLog("%@", $0) }
invalidateCachedTarget(reason: "unit_test_cleanup")
}
logSynthesizedGesturePolicyDecision(kind: .coordinateTap, context: nil, fallbackAttempted: false)
logSynthesizedGesturePolicyDecision(kind: .coordinateTap, context: nil, fallbackAttempted: false)
XCTAssertEqual(written.count, 1, "a repeated decision writes no second line")
logSynthesizedGesturePolicyDecision(kind: .scroll, context: nil, fallbackAttempted: false)
Comment thread
Copilot marked this conversation as resolved.
XCTAssertEqual(written.count, 2, "each gesture kind states its own decision")
logSynthesizedGesturePolicyDecision(kind: .coordinateTap, context: nil, fallbackAttempted: true)
XCTAssertEqual(written.count, 3, "a changed decision writes a new line")
resetTargetBoundState()
logSynthesizedGesturePolicyDecision(kind: .coordinateTap, context: nil, fallbackAttempted: true)
XCTAssertEqual(written.count, 4, "a rebind states the same decision once more")
}
}
#endif

#if AGENT_DEVICE_RUNNER_UNIT_TESTS
extension RunnerTests {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ final class RunnerTests: XCTestCase {
let minRecordingFps = 1
let maxRecordingFps = 120
var needsPostSnapshotInteractionDelay = false
// Per-command markers that restate a fact of the bound target (the fast app guard, the
// synthesized gesture policy per gesture kind) write only when that fact changes; otherwise a
// long session fills runner.log with one identical line per command. Cleared with the rest of the
// target-bound state so a rebind states the fact once more.
var lastLoggedFastAppGuardLine: String?
var lastLoggedGesturePolicyLines: [SynthesizedGesturePolicyKind: String] = [:]
var runnerMarkerWriter: (String) -> Void = { NSLog("%@", $0) }
/// When the first interaction after an activation may run, on the monotonic uptime clock.
/// The guarantee is a minimum gap *since the activation*, not a pause at the interaction:
/// a caller that already spent that gap elsewhere (an agent's round trip is 190-260 ms)
Expand Down
Loading