Files
callstack__agent-device/vitest.config.ts
T
Michał Pierzchała 7f5dbd2e50 chore: drive unused production exports to zero (#1743)
* chore: drive unused production exports to zero

`pnpm check:production-exports` has been failing on main with 21 findings.
Each was investigated rather than blanket-suppressed; they split three ways.

Genuinely dead, deleted:
- `androidDeviceForSerial` (android/adb.ts) had zero references anywhere,
  tests included.
- `streamAndroidLogcatWithAdb` (android/logcat.ts) had no production consumer
  and only a guard-clause test; its `captureAndroidLogcatWithAdb` sibling is
  the published SDK surface. Removed with its options type and test.

Test-only aliases over live siblings, collapsed:
- app-log-resource-store re-exposed four bound store methods; production used
  only `resolvePath`, tests used the other three. The sibling
  screen-recording-resource-store exports just the store, so this now matches:
  one export, all consumers call `appLogResourceStore.x`.
- device-claims re-exported `canonicalLocalDeviceKey` for a single test, while
  production imports it from device-claim-paths directly. Dropped the
  re-export and pointed the test at the canonical module.

Real consumers the analysis cannot see, exempted with the reason:
- The nine remaining `src/cli/commands/*Command` handlers are reached only
  through `dedicatedCliCommandHandlerLoaders`, the dynamic import() table in
  router.ts. `deviceCommand` already carried an inline suppression for exactly
  this; replaced it with one config entry naming the table that enumerates all
  ten, matching the existing daemon route-handler entry.
- `resolveVitestMaxWorkers` (vitest.config.ts), `DEVICE_CLAIM_IN_USE_SAMPLE`
  (bench sample producers) and the capture-kit `createAppLogLiveHandle` facade
  export joined the existing entries that already record their exact shape.
- `**/*.fixtures.ts` is now an ignorePattern: all 15 build doubles for
  co-located tests, several import `vi`, and none is imported by production
  source. Pattern-matching them as test infrastructure also keeps this class
  of finding from recurring.

Gate now reports zero. Unit suite, layering, fallow audit, MCP metadata,
build, bundle-owner and package checks all pass.

* chore: scope the fixture exemption to unused exports

Review feedback on #1743: `ignorePatterns` removes a file from every Fallow
mode and rule, but the false positive here is only production-unused-exports.
Moved *.fixtures.ts to an ignoreExports entry so fixtures stay inside health,
dead-code and cycle analysis. Kept `exports: ["*"]` rather than today's three
symbols because the property is per-file — no fixture module has a production
consumer — so a new fixture symbol should not reopen the finding.

check:production-exports still reports zero, and a full `fallow --summary`
returns identical totals (2 dead-code / 4 dupes / 123 health) with and without
the change, so nothing was newly surfaced or newly hidden.

* test(fallow): prove fixture policy scope
2026-08-12 11:17:33 +02:00

164 lines
7.6 KiB
TypeScript

import type { Reporter } from 'vitest/node';
import { defineConfig } from 'vitest/config';
import contentionRetryReporter, {
FAILURE_FILE_ENV,
} from './scripts/lib/contention-retry-reporter.ts';
import { SUBPROCESS_STUB_TESTS } from './scripts/lib/contention-retry.ts';
import { resolveVitestMaxWorkers } from './scripts/lib/vitest-concurrency.ts';
import slowTestGateReporter from './scripts/vitest-slow-test-reporter.ts';
// Tests that stub a real binary (adb/xcrun/npx) by mutating process.env.PATH and
// then spawn it, so each case waits real subprocess/retry/poll time. Run with
// broad file parallelism they contend for CPU and their stub
// spawns get starved past an internal budget, so production takes a generic
// failure path and returns a different error than the assertion expects — a
// contention flake whose failing subset shifts between runs (see
// docs/agents/testing.md "tests must not wait real time"). Serialized below with
// per-file isolation so only one such file spawns stubs at a time, the same
// execution contract the pre-split android index.test.ts aggregation provided.
// The enumerated file list, with each file's contention reason and its owned
// waiver, lives in scripts/lib/contention-retry.ts — the same constant the CI
// single-retry policy (#1419) reads, so the two cannot drift.
export { SUBPROCESS_STUB_TESTS };
/** Every project loads the same setup, including the runner-timeout provenance hook. */
const SETUP_FILES = [
'scripts/vitest-runner-timeout-setup.ts',
'src/__tests__/hermetic-env-setup.ts',
'src/__tests__/process-memo-setup.ts',
];
/** Reporters for every lane; a `--reporter` flag would replace them, so no lane passes one. */
export function reporters(env: NodeJS.ProcessEnv = process.env): Array<string | Reporter> {
const gates: Array<string | Reporter> = ['default', slowTestGateReporter()];
// The failure sink drains the gates' verdicts, so it reports after them.
return env[FAILURE_FILE_ENV] ? [...gates, contentionRetryReporter()] : gates;
}
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,
// Vitest otherwise derives 11 workers from this 12-core host. Three
// concurrent Codex worktrees can then request 33 workers and starve the
// subprocess/test-server paths behind exact timeout budgets. Two workers
// per local invocation preserves useful parallelism while leaving host
// headroom. CI stays uncapped so Vitest derives the runner-appropriate
// worker count from the isolated machine's 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,
reporters: reporters(),
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',
'scripts/__tests__/help-conformance-bench.test.ts',
'scripts/__tests__/help-conformance-error-recovery-coverage.test.ts',
'scripts/__tests__/help-conformance-sample-outputs.test.ts',
'scripts/__tests__/help-conformance-topic-coverage.test.ts',
'scripts/__tests__/agent-setup-startup-contract.test.ts',
'scripts/__tests__/npm-skills-exclusion.test.ts',
'scripts/__tests__/simulator-skills-contract.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',
// Parses CI configuration only, so this action guard needs no device or subprocess lane.
'test/ci/upload-agent-device-artifacts.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: [...SUBPROCESS_STUB_TESTS],
setupFiles: SETUP_FILES,
},
},
{
// The subprocess-stub tests stub adb/xcrun/npx by mutating process.env
// (PATH, AGENT_DEVICE_TEST_ARGS_FILE) and wait real subprocess/retry/poll
// time, so the group runs serialized with per-file isolation — the same
// execution contract the pre-split android index.test.ts aggregation
// provided without leaking module caches between split files.
test: {
name: 'subprocess-stub',
include: [...SUBPROCESS_STUB_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__/**',
'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',
],
},
},
});