Files
Michał Pierzchała b15121ffc8 test(ios): establish snapshot convergence baselines and permanent evidence (#2204)
* test(ios): add snapshot convergence evidence harness

* fix(ios): satisfy benchmark CI guards

* fix(ios): constrain benchmark proxy routes

* fix(ios-benchmark): enforce cell admission evidence

* fix(ios-benchmark): protect benchmark state ownership

* fix(ios-benchmark): use proxy port flag

* fix(ios-benchmark): let proxy choose an ephemeral port

* test(ios-benchmark): keep CLI process seam local

* fix(ios-benchmark): parse proxy startup envelope

* fix(ios-benchmark): bind proxy lease to simulator

* fix(ios-benchmark): keep fresh proxy CLI sessions isolated

* fix(ios-benchmark): preserve async timeout evidence

* docs(ios-benchmark): retain exact-head evidence

* test(ios): reveal offscreen alert fixture controls

* test(ios): reset alert between relaunch samples

* test(ios): admit native alert snapshots

* docs(ios): publish snapshot convergence corpus

* chore(ios): format benchmark evidence

* fix(ios-benchmark): admit proxy fixture anchors

* docs(ios): republish exact-head benchmark corpus

* fix(size): make publish asset evidence hermetic

* style(size): format package evidence test

* test(size): update publish preparation contracts

* fix: retire stale utils layering zone

* test: pin shared publish asset owner

* test: verify preserved size reporter closure

* fix: move mutation ownership to snapshot module

* test(ios): add snapshot convergence evidence harness

* fix(ios): satisfy benchmark CI guards

* fix(ios): constrain benchmark proxy routes

* fix(ios-benchmark): enforce cell admission evidence

* fix(ios-benchmark): protect benchmark state ownership

* fix(ios-benchmark): use proxy port flag

* fix(ios-benchmark): let proxy choose an ephemeral port

* test(ios-benchmark): keep CLI process seam local

* fix(ios-benchmark): parse proxy startup envelope

* fix(ios-benchmark): bind proxy lease to simulator

* fix(ios-benchmark): keep fresh proxy CLI sessions isolated

* fix(ios-benchmark): preserve async timeout evidence

* docs(ios-benchmark): retain exact-head evidence

* test(ios): reveal offscreen alert fixture controls

* test(ios): reset alert between relaunch samples

* test(ios): admit native alert snapshots

* docs(ios): publish snapshot convergence corpus

* chore(ios): format benchmark evidence

* fix(ios-benchmark): admit proxy fixture anchors

* docs(ios): republish exact-head benchmark corpus

* fix(size): make publish asset evidence hermetic

* test(size): update publish preparation contracts

* fix: keep git-state gates out of mutation sandboxes
2026-09-01 21:39:05 +02:00

113 lines
3.1 KiB
TypeScript

import { spawn } from 'node:child_process';
import { BenchmarkInfrastructureError, stopDaemon } from './lifecycle.ts';
import { findProxyStartup, type ProxyStartup } from './proxy-startup.ts';
export type ProxyProcess = ProxyStartup & { stop(): Promise<void> };
export async function startProxy(repoRoot: string, stateDir: string): Promise<ProxyProcess> {
const token = `bench-${process.pid}-${Date.now()}`;
const child = spawn(
process.execPath,
[
'bin/agent-device.mjs',
'proxy',
'--state-dir',
stateDir,
'--daemon-auth-token',
token,
'--json',
],
{
cwd: repoRoot,
env: { ...process.env, AGENT_DEVICE_NO_UPDATE_NOTIFIER: '1' },
stdio: ['ignore', 'pipe', 'pipe'],
},
);
let stderr = '';
child.stderr?.on('data', (chunk: Buffer) => {
stderr += chunk.toString();
});
try {
const startup = await readProxyStartup(child, () => stderr);
let stopPromise: Promise<void> | undefined;
return {
...startup,
stop: () => (stopPromise ??= stopProxyProcess(child, repoRoot, stateDir)),
};
} catch (error) {
await terminateChild(child);
throw error;
}
}
function readProxyStartup(
child: ReturnType<typeof spawn>,
getStderr: () => string,
): Promise<ProxyStartup> {
return new Promise((resolve, reject) => {
let output = '';
const timer = setTimeout(() => {
reject(
new BenchmarkInfrastructureError(
`Proxy did not publish startup metadata: ${getStderr().trim() || output.trim()}`,
'agent-device proxy --json',
),
);
child.kill('SIGTERM');
}, 60_000);
child.stdout?.on('data', (chunk: Buffer) => {
output += chunk.toString();
const startup = findProxyStartup(output);
if (!startup) return;
clearTimeout(timer);
resolve(startup);
});
child.once('error', (error) => {
clearTimeout(timer);
reject(error);
});
child.once('exit', (code, signal) => {
if (isCleanExit(code, signal)) return;
clearTimeout(timer);
reject(
new BenchmarkInfrastructureError(
`Proxy exited before startup: ${getStderr().trim() || `exit ${String(code)}`}`,
'agent-device proxy --json',
),
);
});
});
}
async function stopProxyProcess(
child: ReturnType<typeof spawn>,
repoRoot: string,
stateDir: string,
): Promise<void> {
await terminateChild(child);
stopDaemon(repoRoot, stateDir);
}
async function terminateChild(child: ReturnType<typeof spawn>): Promise<void> {
if (child.exitCode === null) child.kill('SIGTERM');
await waitForExit(child);
}
function isCleanExit(code: number | null, signal: NodeJS.Signals | null): boolean {
return code === 0 && signal === null;
}
function waitForExit(child: ReturnType<typeof spawn>): Promise<void> {
if (child.exitCode !== null) return Promise.resolve();
return new Promise((resolve) => {
const timer = setTimeout(() => {
child.kill('SIGKILL');
resolve();
}, 10_000);
child.once('exit', () => {
clearTimeout(timer);
resolve();
});
});
}