diff --git a/AGENTS.md b/AGENTS.md index ef6cb86f1..7bdd8dfe8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -238,7 +238,7 @@ This repo encodes invariants as self-declaring gates. The correct response to a ## PR Readiness Checklist - Static gates first: required checks from **Testing Matrix** pass, `pnpm check:fallow --base origin/main` is clean when code quality/dead-code risk is relevant, CI guards are green, and no conflict markers or unmerged paths remain. -- Do not report a PR as CI-green from a local unit-only run alone: use `pnpm test:unit` for the repo unit bundle, or `vitest run --project unit-core --project android-adb` when invoking Vitest directly. The **Integration Tests** and **Coverage** jobs run the `provider-integration` project, so verify green on the actual PR head across those jobs, not just unit. +- Do not report a PR as CI-green from a local unit-only run alone: use `pnpm test:unit` for the repo unit bundle, or `vitest run --project unit-core --project subprocess-stub` when invoking Vitest directly. The **Integration Tests** and **Coverage** jobs run the `provider-integration` project, so verify green on the actual PR head across those jobs, not just unit. - Command-surface changes preserve CLI, Node.js, daemon, MCP, help, docs, and SkillGym coverage where that surface is affected. Do not duplicate command contracts across layers. - Device-facing behavior is not merge-ready until it has real simulator/emulator/device evidence for the changed path. Fixture-backed tests can prove contracts, but they do not replace a live run that creates or observes the artifact/state the feature claims to handle. - If live verification is blocked, state the blocker, exact command or device needed, and downgrade the PR to residual risk instead of calling it ready. diff --git a/package.json b/package.json index 6c1e09bc4..2a894e10e 100644 --- a/package.json +++ b/package.json @@ -140,8 +140,8 @@ "test-app:maestro": "node scripts/run-test-app-maestro-suite.mjs", "test-app:maestro:ios": "node scripts/run-test-app-maestro-suite.mjs --platform ios", "test-app:maestro:android": "node scripts/run-test-app-maestro-suite.mjs --platform android", - "test": "vitest run --project unit-core --project android-adb", - "test:unit": "vitest run --project unit-core --project android-adb", + "test": "vitest run --project unit-core --project subprocess-stub", + "test:unit": "vitest run --project unit-core --project subprocess-stub", "test:coverage": "vitest run --coverage", "test:integration:provider": "vitest run --project provider-integration", "test:integration:progress": "node --experimental-strip-types scripts/integration-progress.ts", diff --git a/vitest.config.ts b/vitest.config.ts index b3eab91b7..d2fd6638c 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,8 +1,20 @@ import { defineConfig } from 'vitest/config'; import slowTestGateReporter from './scripts/vitest-slow-test-reporter.ts'; -const ANDROID_ADB_STUB_TESTS = - 'src/platforms/android/__tests__/{app-lifecycle-install,app-lifecycle-open,device-input-state,input-actions,notifications,settings}.test.ts'; +// Tests that stub a real binary (adb/xcrun) by mutating process.env.PATH and +// then spawn it, so each case waits real subprocess/retry/poll time. Run at the +// unit suite's default ~7x 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. +const SUBPROCESS_STUB_TESTS = [ + 'src/platforms/android/__tests__/{app-lifecycle-install,app-lifecycle-open,device-input-state,input-actions,notifications,settings}.test.ts', + 'src/daemon/__tests__/runtime-hints.test.ts', + 'src/platforms/apple/core/__tests__/index.test.ts', +]; export default defineConfig({ test: { @@ -27,19 +39,19 @@ export default defineConfig({ // The Maestro conformance oracle runs via `node --test` in its own CI // job (scripts/maestro-conformance), like the layering guard. ], - exclude: [ANDROID_ADB_STUB_TESTS], + exclude: SUBPROCESS_STUB_TESTS, setupFiles: ['src/__tests__/process-memo-setup.ts'], }, }, { - // The scripted-adb tests stub the adb binary by mutating process.env - // (PATH, AGENT_DEVICE_TEST_ARGS_FILE) and wait real retry/poll time, - // so the group runs serialized with per-file isolation — the same + // The subprocess-stub tests stub adb/xcrun 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: 'android-adb', - include: [ANDROID_ADB_STUB_TESTS], + name: 'subprocess-stub', + include: SUBPROCESS_STUB_TESTS, setupFiles: ['src/__tests__/process-memo-setup.ts'], fileParallelism: false, isolate: true,