Files
callstack__agent-device/vitest.config.ts
Michał Pierzchała 9aa6465768 perf(scripts): add a device-free PNG crop benchmark (#2505)
* perf(scripts): add a device-free PNG crop benchmark

`pnpm bench:png-crop` runs the whole-image pipeline and the shipped region crop
over the same bytes in one process, so the comparison holds the capture content,
the deflate stream, and the machine fixed. The corpus is generated, which keeps a
run at seconds with no device; real captures join the same table via `--file`, and
each corpus entry prints its compressed size so an unrealistic corpus is visible.

The README records what the measurements said, including the case the encoder
policy loses: `None` on every scanline is faster everywhere but writes about 1.7x
more bytes than a filtered encoding on smooth low-frequency content.

* chore(gates): run the PNG crop benchmark's model tests in unit-core

Registers scripts/png-crop-benchmark/*.test.ts so the timing summary that the
report is built from stays covered without a device lane.
2026-09-12 18:23:28 +00:00

261 lines
14 KiB
TypeScript

import { defineConfig } from 'vitest/config';
import { resolveVitestMaxWorkers } from './scripts/lib/vitest-concurrency.ts';
import slowTestGateReporter from './scripts/vitest-slow-test-reporter.ts';
// A real per-case spawn is timeout noise under thousands of mutant reruns, so the
// mutation lane excludes these tests even though the unit lane runs them normally.
const MUTATION_EXCLUDED_SUBPROCESS_TESTS: readonly string[] = [
// Stubs npx plus the package managers and spawns a real Metro dev server per case.
'src/__tests__/client-metro.test.ts',
// The SUT is the subprocess watchdog: a node subprocess per case, one hangs on purpose (#1414).
'scripts/fuzz/harness.test.ts',
// The daemon takeover test launches a branch-named daemon per case to prove
// that worktree identity does not strand the predecessor (#1545).
'src/daemon/__tests__/daemon-process-takeover.test.ts',
];
// The fuzz corpus replay, which must not run under V8 coverage instrumentation.
//
// #1824 found two causes behind `Worker exited unexpectedly`. Shape (A) — a test signalling
// a fabricated pid that landed on a sibling fork — was fixed at the source in #1854. Shape
// (B) was left open: one fork dies mid-file, alone, with no test attributed. Scanning every
// failed Coverage job across the 120 CI runs after #1854 merged found the signature five
// times, and the vanished file was this one all five (plus #1866's, so six for six) — 23% of
// Coverage failures in that window. The uninstrumented unit lane has never lost it.
//
// So the coverage run skips this project and a second, uninstrumented Vitest invocation owns
// it — see `test:coverage:ci`. That costs no coverage at all, which is measured rather than
// assumed: the cases execute out of process, which the fork's inspector session never
// instruments, so this file reports the same lines with or without it.
//
// Membership is by demonstrated failure, not by a property of the code. In particular it is
// NOT "constructs a `node:worker_threads` Worker": `session-replay-runtime-maestro-run-script.test.ts`
// does exactly that and stays in `unit-core`, instrumented and green. The proximate cause was
// never reproduced — what these entries share is an observed record of vanishing from the
// Coverage lane, and that record is the only thing that admits a file here. A new entry needs
// its own run URLs; a theory about workers is not enough.
//
// #2053: the split did not stop it — the uninstrumented second leg lost the same file six more
// times in three days. Calibrating each mechanism against this project (see the issue) showed
// that every death Node can report is reported, leaving an uncatchable signal; and the only
// thing this file did that no other file does was run adversarial parser cases inside the fork,
// on worker threads it created and terminated. They now run in a worker process
// (`scripts/fuzz/execute.ts`), where such a fault is a `crash` failure named against its input.
// This entry and the second leg it forces are the mitigation: remove both — file back to
// `unit-core`, `test:coverage:ci` back to one invocation — after 30 consecutive Coverage runs
// with no `Worker exited unexpectedly`, and reopen #2053 with the run URL if one appears first.
const FUZZ_WORKER_TESTS: readonly string[] = [
// Replays the fuzz corpus through the worker watchdog, waiting its per-case budget (#1414).
'scripts/fuzz/corpus-replay.test.ts',
];
/**
* Every test the mutation lane must not collect. The two lists above stay
* module-local: this union is the whole cross-file surface, and the mutation lane
* wants exactly it.
*/
export const MUTATION_EXCLUDED_TESTS: readonly string[] = [
...MUTATION_EXCLUDED_SUBPROCESS_TESTS,
...FUZZ_WORKER_TESTS,
];
// Imported by vitest.mutation.config.ts so the two lanes cannot drift: a guard
// added here must reach the Stryker sandbox too.
export const SETUP_FILES = [
'src/__tests__/hermetic-env-setup.ts',
'src/__tests__/hermetic-signal-setup.ts',
'src/__tests__/process-memo-setup.ts',
];
export default defineConfig({
test: {
// Redirects TMPDIR to one per-run directory for the whole invocation (all
// projects, every worker) and removes it once at the end — see the file
// for why a single global hook beats per-file cleanup here.
globalSetup: ['scripts/vitest-tmpdir-global-setup.ts'],
// Wall-clock discipline: unit tests must not wait real time. Measured
// 2026-07-04: the suite's duration was bounded by files sleeping through
// production timeout budgets. slowTestThreshold surfaces creep in local
// output; the slow-test reporter enforces the ratchet (pinned offenders
// only shrink). Isolation stays ON and pool stays forks: measured
// --no-isolate = 205s wall vs 48s (module state thrashes across files),
// threads = no change.
slowTestThreshold: 500,
// Four workers gave a worthwhile solo-run speedup without approaching
// Vitest's 11-worker default on a 12-core host. CI stays uncapped so Vitest
// derives the runner-appropriate worker count from its available CPU pool.
maxWorkers: resolveVitestMaxWorkers(),
// hermetic-env-setup clears worker-scoped device claims after every case.
// Capping explicit `test.concurrent` work at one enforces that teardown
// assumption without reducing ordinary file-level parallelism.
maxConcurrency: 1,
// Gate reporters for every lane; a `--reporter` flag would replace them, so no lane passes one.
reporters: ['default', slowTestGateReporter()],
projects: [
{
test: {
name: 'unit-core',
// Explicit script entries keep maintained conformance guards in the
// unit suite without waking every ad-hoc *.test.ts under scripts/.
include: [
'src/**/*.test.ts',
'packages/*/src/**/*.test.ts',
// The subprocess watchdog self-check: spawns a real node subprocess per case,
// and one hangs on purpose (#1414).
'scripts/fuzz/harness.test.ts',
// The validation fuzz generators' expectation gates (#1781 B2): in-process, no
// subprocess or worker, so they ride the fast lane unlike their serialized siblings.
'scripts/fuzz/validation-arbitraries.test.ts',
'scripts/fuzz/validation-arbitraries-cli.test.ts',
'scripts/fuzz/validation-arbitraries-maestro.test.ts',
'scripts/fuzz/validation-case.test.ts',
'scripts/fuzz/envelope.test.ts',
'scripts/__tests__/help-conformance-bench.test.ts',
'scripts/__tests__/help-conformance-error-recovery-coverage.test.ts',
'scripts/__tests__/help-conformance-expectation-falsification.test.ts',
'scripts/__tests__/help-conformance-sample-outputs.test.ts',
'scripts/__tests__/help-conformance-topic-coverage.test.ts',
// Lives here rather than under src/ on purpose: it measures the repository's own
// files and git history, which Stryker's sandbox copy cannot answer (the copies are
// rewritten, and there is no origin/main). The mutation lane admits only root/package
// `src` tests, so this address makes it unreachable there by construction instead of
// by a classifier that has to recognise it — see KERNEL_TEST_FILE_RE in
// scripts/mutation/modules.ts.
'scripts/__tests__/test-file-size-ratchet.test.ts',
'scripts/__tests__/eager-closure-budgets.test.ts',
'scripts/__tests__/agent-setup-startup-contract.test.ts',
'scripts/__tests__/npm-skills-exclusion.test.ts',
'scripts/__tests__/simulator-skills-contract.test.ts',
// Parses ios.yml and the runner's Swift sources: no Xcode, no simulator, and
// the check it guards is what keeps the PR lane's `-only-testing:` list honest.
'scripts/__tests__/xctest-selection.test.ts',
// The nightly XCTest lane's reporter/liveness check, which otherwise only ever
// executes on a macOS runner at 04:30.
'scripts/__tests__/xctest-run-summary.test.ts',
// The Fallow fixture policy is executable configuration: unused exports are exempt,
// but fixture modules remain visible to the other analysis families.
'scripts/__tests__/fallow-fixture-policy.test.ts',
// The publishing gate's closure audit against fixture packages: parse-only, and the
// only place the gate's failure direction is exercised at all (the gate itself needs a
// real `npm pack`, so CI can only watch a healthy package pass).
'scripts/__tests__/package-closure-audit.test.ts',
// The Bundle Size lane's PR-comment path: spawns the real script against a
// stubbed fetch, so it needs no network; pins retry/reconcile/fatal outcomes.
'scripts/__tests__/size-report-post-comment.test.ts',
// Package attribution models npm-pack output. Keep it in the fast lane so every new
// package path remains accounted for.
'scripts/__tests__/size-report-package.test.ts',
// Publish preparation spawns only fixture-owned scripts and proves both Android
// helper families are rebuilt through the shared release/size-report owner.
'scripts/__tests__/prepare-publish-assets.test.ts',
// The packager's Swift comment scanner: pure string transform, and the only place a
// literal that looks like a comment (a URL, a raw or multi-line literal) is proven
// to survive packaging before the npm package ships unbuildable Swift.
'scripts/__tests__/strip-swift-comments.test.ts',
// The line-parity comparison behind `pnpm check:packaged-runner-swift`. Pure text
// over two strings; the gate itself is what runs the packager and the Swift parse.
'scripts/__tests__/packaged-runner-swift.test.ts',
// Parse-only guard on the checked-in registry entry: the npm package must declare
// the fixed mcp subcommand, or registry-format launchers run the bare CLI.
'scripts/__tests__/mcp-metadata.test.ts',
'scripts/ios-snapshot-benchmark/*.test.ts',
'scripts/png-crop-benchmark/*.test.ts',
'scripts/ios-ax-bridge-spike/*.test.ts',
// Parses CI configuration only, so this action guard needs no device or subprocess lane.
'test/ci/upload-agent-device-artifacts.test.ts',
'test/ci/upload-artifact-hidden-paths.test.ts',
'scripts/__tests__/diagnose-1874-iteration.test.ts',
// The size reporter is preserved across a base checkout; its entrypoint and imported
// modules must move as one directory or the Bundle Size lane fails before measuring.
'test/ci/size-workflow.test.ts',
// #1781 A9: pins the root-doc paths-ignore entries directly against the
// real workflow YAML, parse-only like its sibling above.
'test/ci/root-docs-paths-ignore.test.ts',
// The daemon leak oracle's lifecycle/residue rules (#1781 B1): pure
// decisions over fixture state-dir listings, so they need no daemon,
// device, or subprocess.
'test/integration/support/daemon-leak-model.test.ts',
// The frozen replay-compat corpus (#1417): parse-only, no device or
// subprocess work, so it belongs in the fast lane next to the
// grammar it guards.
'test/replay-compat/corpus.test.ts',
// The daemon RPC wire ledger (#1432): parses source and hashes
// declarations, so it needs no history, network, or device — the
// released-tag half runs in its own full-history job.
'test/wire-compat/wire-compat.test.ts',
'test/wire-compat/wire-mutations.test.ts',
// The Maestro conformance oracle runs via `node --test` in its own CI
// job (scripts/maestro-conformance), like the layering guard.
],
exclude: [...FUZZ_WORKER_TESTS, 'packages/platform-apple/src/runner/**/*.test.ts'],
setupFiles: SETUP_FILES,
},
},
{
test: {
// The apple-runner package's own suites: same lane semantics as
// unit-core, plus a setup that installs the real root host
// capabilities behind the package's test-host seam (R11 keeps the
// package from importing root utilities directly).
name: 'apple-runner',
include: ['packages/platform-apple/src/runner/**/*.test.ts'],
setupFiles: [...SETUP_FILES, 'scripts/vitest-apple-runner-host-setup.ts'],
},
},
{
test: {
// Serialized because the per-case watchdog budget is real wall clock. The project exists so the
// coverage run can leave it out (see the comment above), not to run it differently.
name: 'fuzz-worker',
include: [...FUZZ_WORKER_TESTS],
setupFiles: SETUP_FILES,
fileParallelism: false,
isolate: true,
maxWorkers: 1,
},
},
{
test: {
name: 'provider-integration',
include: ['test/integration/provider-scenarios/**/*.test.ts'],
setupFiles: SETUP_FILES,
},
},
{
test: {
name: 'interaction-contract',
include: ['test/integration/interaction-contract/**/*.test.ts'],
setupFiles: SETUP_FILES,
},
},
{
test: {
name: 'output-economy',
include: ['test/output-economy/**/*.test.ts'],
setupFiles: SETUP_FILES,
},
},
],
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'lcov', 'json-summary'],
thresholds: {
statements: 78,
lines: 80,
},
include: ['src/**/*.ts', 'packages/*/src/**/*.ts'],
exclude: [
'src/**/*.test.ts',
'src/**/__tests__/**',
'**/*.fixtures.ts',
'src/**/*-types.ts',
'src/**/types.ts',
'src/sdk/**',
'src/bin.ts',
'src/client/client-types.ts',
'src/core/interactor-types.ts',
'src/remote/remote-config.ts',
],
},
},
});