Skip to content

Commit 3726f02

Browse files
authored
test(platform): extract Apple perf and Android runtime fixtures (#2253)
* test(platform): extract shared fixtures from the Apple perf and Android runtime suites Move the duplicated host-command routing, app-bundle writers, and ps handlers behind packages/platform-apple/src/core/__tests__/perf.fixtures.ts, and the Android runtime host, device, and ordinary-bind builders behind packages/platform-android/src/runtime.fixtures.ts. Every test keeps its title and its own assertions; test and assertion counts are unchanged. * chore(gates): exclude .fixtures.ts modules from changed-line coverage runtime.fixtures.ts (packages/platform-android/src) sits outside any __tests__ dir, so vitest's coverage.include picked it up as production source and the changed-line gate scored its 20 unreachable construction lines directly, failing at 39.39% against the 70% threshold. Add '**/*.fixtures.ts' to coverage.exclude (repo-wide convention: 40+ fixture modules, all test support, never production). The file disappears from lcov and scripts/coverage-changed/model.ts's existing excluded-path fallback reports it non-gating instead, with no duplicate classifier needed there. Planted red (targeted run against the real diff/model, not committed): BEFORE (file present in lcov, all added lines uncovered): totalLines=110 coveredLines=0 pct=0 passed=false. AFTER (file absent from lcov post-fix): totalLines=0 excludedTotal=99 excludedReason=excluded-path passed=true. Confirmed against a real `vitest run --coverage` + `check:coverage-changed` pass: runtime.fixtures.ts absent from coverage/lcov.info, gate PASS, 99 lines reported under excluded/excluded-path.
1 parent 9941330 commit 3726f02

5 files changed

Lines changed: 374 additions & 564 deletions

File tree

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import type { AndroidClipboardShellSupport } from '@agent-device/contracts/android-clipboard-support';
2+
import type { DeviceBinding } from '@agent-device/contracts/platform-runtime';
3+
import type {
4+
PlatformRuntimeHost,
5+
PlatformRuntimeOperations,
6+
PlatformRuntimeOwner,
7+
} from '@agent-device/contracts/platform-runtime-operations';
8+
import type { DeviceInfo } from '@agent-device/kernel/device';
9+
10+
export const ANDROID_EMULATOR: DeviceInfo = {
11+
platform: 'android',
12+
id: 'emulator-5554',
13+
name: 'Android',
14+
kind: 'emulator',
15+
target: 'mobile',
16+
booted: true,
17+
};
18+
19+
export const UNKNOWN_KIND_DEVICE = {
20+
...ANDROID_EMULATOR,
21+
kind: 'unknown',
22+
} as unknown as DeviceInfo;
23+
24+
const audioProbeHost: PlatformRuntimeHost['audioProbe'] = {
25+
hostCapture: {
26+
info: {
27+
source: 'system-audio',
28+
backend: 'fixture',
29+
sourceCount: 0,
30+
notes: () => [],
31+
},
32+
start: async () => {
33+
throw new Error('Audio probe is outside this runtime fixture.');
34+
},
35+
inspectProcess: async () => 'missing',
36+
terminateProcess: async () => 'already-missing',
37+
},
38+
web: { resolve: async () => undefined },
39+
ownedProcesses: { replace: () => {}, clear: () => {} },
40+
};
41+
42+
export const emptyAppInventory = {
43+
apple: { listApps: async () => [] },
44+
android: { listApps: async () => [] },
45+
harmonyos: { listApps: async () => [] },
46+
};
47+
48+
const emptyAppState = {
49+
android: { run: async () => ({ stdout: '' }) },
50+
harmonyos: { run: async () => ({ stdout: '' }) },
51+
};
52+
53+
function localAndroidScreenRecording() {
54+
return {
55+
mode: 'local' as const,
56+
start: async () => {
57+
throw new Error('unused');
58+
},
59+
signal: async () => true,
60+
isRunning: async () => false,
61+
exists: async () => false,
62+
pull: async () => ({ stdout: '', stderr: '', exitCode: 0 }),
63+
remove: async () => true,
64+
readManifest: async () => undefined,
65+
writeManifest: async () => {},
66+
removeManifest: async () => {},
67+
};
68+
}
69+
70+
/** The smallest host the Android runtime binds against; `overrides` replace whole facets. */
71+
export function androidRuntimeHost(overrides: Record<string, unknown> = {}): PlatformRuntimeHost {
72+
return {
73+
androidTools: { probeClipboardShellSupport: async () => 'supported' as const },
74+
processTransports: { resolve: async () => ({ mode: 'local' as const }) },
75+
appInventory: emptyAppInventory,
76+
localInteractors: { resolve: async () => ({}) },
77+
audioProbe: audioProbeHost,
78+
screenRecording: { android: { resolve: async () => localAndroidScreenRecording() } },
79+
...overrides,
80+
} as unknown as PlatformRuntimeHost;
81+
}
82+
83+
/** A host with app state and device readiness, so navigation and clipboard cells can bind. */
84+
export function androidNavigationHost(
85+
probeClipboardShellSupport: () => Promise<AndroidClipboardShellSupport> = async () => 'supported',
86+
): PlatformRuntimeHost {
87+
return androidRuntimeHost({
88+
androidTools: {
89+
probeClipboardShellSupport,
90+
runAdb: async () => ({ stdout: '', stderr: '', exitCode: 0 }),
91+
},
92+
appState: emptyAppState,
93+
deviceReadiness: { android: { ensureReady: async (selected: DeviceInfo) => selected } },
94+
});
95+
}
96+
97+
export async function bindOrdinary(
98+
runtime: PlatformRuntimeOwner,
99+
device: DeviceInfo,
100+
): Promise<DeviceBinding<PlatformRuntimeOperations>> {
101+
return await runtime.bind({
102+
device,
103+
intent: { kind: 'ordinary' },
104+
scope: {
105+
signal: new AbortController().signal,
106+
diagnostics: { emit: () => {} },
107+
progress: { report: () => {} },
108+
},
109+
});
110+
}

0 commit comments

Comments
 (0)