Skip to content

Commit f1d48c2

Browse files
committed
refactor: share the bounded app-log poller in capture-kit (#2616)
1 parent 4c2ae0c commit f1d48c2

8 files changed

Lines changed: 682 additions & 305 deletions

File tree

‎packages/capture-kit/package.json‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
"types": "./src/snapshot/android-replacement-surface-occlusion.ts",
1919
"default": "./src/snapshot/android-replacement-surface-occlusion.ts"
2020
},
21+
"./app-log-polling": {
22+
"types": "./src/app-log-polling.ts",
23+
"default": "./src/app-log-polling.ts"
24+
},
2125
"./audio-probe-admission-ledger": {
2226
"types": "./src/capture-admission/audio-probe-admission-ledger.ts",
2327
"default": "./src/capture-admission/audio-probe-admission-ledger.ts"
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import type { AppLogRuntimeHost } from '@agent-device/contracts/app-log-runtime';
2+
3+
export type DeferredSleeps = Readonly<{
4+
wait(milliseconds: number): Promise<void>;
5+
resolveNext(milliseconds: number): void;
6+
resolveAny(): void;
7+
hasPending(milliseconds: number): boolean;
8+
}>;
9+
10+
export function createDeferredSleeps(): DeferredSleeps {
11+
const pending: Array<{ milliseconds: number; resolve: () => void }> = [];
12+
return {
13+
wait: async (milliseconds: number) =>
14+
await new Promise<void>((resolve) => pending.push({ milliseconds, resolve })),
15+
resolveNext: (milliseconds: number) => {
16+
const index = pending.findIndex((entry) => entry.milliseconds === milliseconds);
17+
if (index < 0) throw new Error(`No ${milliseconds}ms sleep is pending`);
18+
pending.splice(index, 1)[0]?.resolve();
19+
},
20+
resolveAny: () => pending.shift()?.resolve(),
21+
hasPending: (milliseconds: number) =>
22+
pending.some((entry) => entry.milliseconds === milliseconds),
23+
};
24+
}
25+
26+
export function createPollerHost(options: {
27+
existingTail: string;
28+
writes: string[];
29+
sleeps: DeferredSleeps;
30+
onOutputDispose?: () => void;
31+
outputDisposeError?: boolean;
32+
failure?: 'readTail' | 'openAppend';
33+
}): AppLogRuntimeHost {
34+
return {
35+
appleTools: {
36+
isXcrunAvailable: async () => false,
37+
run: async () => {
38+
throw new Error('unused');
39+
},
40+
},
41+
toolchains: { prepare: async () => undefined },
42+
artifacts: {
43+
resolveSession: () => ({
44+
outputPath: '/sessions/one/app.log',
45+
pidPath: '/sessions/one/app-log.pid',
46+
}),
47+
},
48+
commands: {
49+
which: async () => undefined,
50+
run: async () => ({ stdout: '', stderr: '', exitCode: 0 }),
51+
},
52+
outputs: {
53+
readTail: async () => {
54+
if (options.failure === 'readTail') throw new Error('tail failed');
55+
return options.existingTail;
56+
},
57+
openAppend: async () => {
58+
if (options.failure === 'openAppend') throw new Error('open failed');
59+
return {
60+
write: async (chunk) => {
61+
options.writes.push(String(chunk));
62+
},
63+
[Symbol.asyncDispose]: async () => {
64+
if (options.outputDisposeError) throw new Error('output cleanup failed');
65+
options.onOutputDispose?.();
66+
},
67+
};
68+
},
69+
},
70+
processTransports: {
71+
resolve: async () => ({ mode: 'local' }),
72+
},
73+
processes: {
74+
start: async () => {
75+
throw new Error('unused');
76+
},
77+
readMarker: async () => ({ status: 'missing' }),
78+
clearMarker: async () => {},
79+
inspect: async () => 'missing',
80+
terminate: async () => 'already-missing',
81+
},
82+
clock: {
83+
now: () => 100,
84+
sleep: async (milliseconds) => await options.sleeps.wait(milliseconds),
85+
},
86+
};
87+
}

0 commit comments

Comments
 (0)