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

88 lines
2.9 KiB
TypeScript

import type { Measurement, RawSample, Summary } from './types.ts';
function percentile(values: number[], percentage: number): number {
const rank = Math.ceil((percentage / 100) * values.length);
return values[Math.min(values.length - 1, Math.max(0, rank - 1))]!;
}
export function summarize(values: number[], outlierCount = 0): Summary | null {
const finite = values.filter(Number.isFinite).sort((left, right) => left - right);
if (finite.length === 0) return null;
return {
n: finite.length,
min: finite[0]!,
median: percentile(finite, 50),
p95: percentile(finite, 95),
max: finite.at(-1)!,
outlierCount,
outlierRule: 'tukey-1.5-iqr',
};
}
function outlierIndexes(values: number[]): Set<number> {
const finite = values.filter(Number.isFinite).sort((left, right) => left - right);
if (finite.length < 4) return new Set();
const q1 = percentile(finite, 25);
const q3 = percentile(finite, 75);
const spread = q3 - q1;
const lower = q1 - 1.5 * spread;
const upper = q3 + 1.5 * spread;
return new Set(values.flatMap((value, index) => (value < lower || value > upper ? [index] : [])));
}
export function markOutliers(samples: RawSample[]): RawSample[] {
const indexes = outlierIndexes(
samples.filter((sample) => sample.ok).map((sample) => sample.wallClockMs),
);
let successfulIndex = 0;
return samples.map((sample) => {
if (!sample.ok) return { ...sample, outlier: false };
const outlier = indexes.has(successfulIndex);
successfulIndex += 1;
return { ...sample, outlier };
});
}
export function buildMeasurement(options: {
transport: Measurement['transport'];
execution: Measurement['execution'];
state: Measurement['state'];
screen: Measurement['screen'];
sampleMinimum: number;
operation: Measurement['operation'];
samples: RawSample[];
network?: Measurement['network'];
}): Measurement {
const samples = markOutliers(options.samples);
const successful = samples.filter((sample) => sample.ok);
const outlierCount = successful.filter((sample) => sample.outlier).length;
const failures = samples.length - successful.length;
const failureCategories: Measurement['failureCategories'] = {};
for (const sample of samples) {
const category = sample.failure?.category;
if (category) failureCategories[category] = (failureCategories[category] ?? 0) + 1;
}
return {
...options,
samples,
wallClockMs: summarize(
successful.map((sample) => sample.wallClockMs),
outlierCount,
),
daemonDurationMs: summarize(
successful.flatMap((sample) =>
typeof sample.daemonDurationMs === 'number' ? [sample.daemonDurationMs] : [],
),
outlierCount,
),
responseBytes: summarize(
successful.flatMap((sample) =>
typeof sample.responseBytes === 'number' ? [sample.responseBytes] : [],
),
outlierCount,
),
failures,
failureCategories,
};
}