Correctness
The correctness gate. A sandbox that runs commands faster but produces different results than running them normally is worse than useless — it silently breaks builds. Correctness beats speed, always; this is the largest test surface in the package.
The bar: behaviourally identical to native
For any command, the sandbox must produce the same observable result as running it natively: same exit code, same stdout/stderr (modulo explicitly-normalized nondeterminism), same files produced (content-identical), same dependency resolution (lockfile honored). The vfs backend's no-native-subprocess limit is a capability boundary, not a correctness excuse — within its scope it must match native exactly; outside it, it must fail loudly (fall back), never silently differ.
Test layers
Every layer but the acceptance and equivalence ones runs as a CI coverage shard and hard-fails; those two are parked and run on demand, for the reason the equivalence entry gives.
- Unit — FS provider (read/write/mount namespace/symlink/module-load), exec backend wiring, snapshot addressing. Fast, deterministic, run everywhere; lives beside the code (
*.test.ts). - Integration/acceptance — a real
pnpm installwith a native postinstall (sharp, esbuild) completing fully in RAM — theosbackend's reason to exist (*.acceptance.test.ts).createOsBackend.acceptance.test.tsandcreateSnapshot.acceptance.test.tsare parked asdescribe.todolike the equivalence layer: each runs a real networked install, together the better part of two minutes of wall clock and the longest items in the coverage run. Drop the.todoand restore theisSandboxInstallSupportedgate to run one when the install or capture path changes. - Differential (golden) — the core technique: run the same command through the candidate backend and the native baseline, normalize both, assert identical. Shared infrastructure under
services/exec/differential/; each backend's*.differential.test.tsfeeds its corpus to one helper. Nothing is normalized implicitly — each case carries explicit{ pattern, placeholder }rules, so a real divergence is never hidden. Every reported correctness bug becomes a permanent golden case before it's fixed. - Equivalence —
forkSnapshot.equivalence.test.tsproves a forked warm run is observably identical to a cold in-place install;persistRun.equivalence.test.tsproves a persist run leaves the host disk exactly as native would (write-back);taskCache.equivalence.test.tsproves a replay matches a real re-run. All three are parked asdescribe.todo, not skipped by host capability: every case boots a sandbox and installs, so a layer that costs minutes of wall clock is not worth paying on every run of the whole repo's suite. Bodies stay intact and each grows its golden cases as usual — drop the.todoto run one when the path it covers changes. Nothing else in the package asserts these three properties, so a regression in one merges green. - Property/fuzz — two halves. The vfs seam: fast-check drives randomized read/write/exists sequences against the provider and a real
node:fstemp directory in lockstep and diffs the full trace (node:fs is the oracle, so no fs semantics are re-implemented; failures shrink to a minimal counterexample). The os half: randomized command sequences through the ephemeral RAM overlay assert the isolation invariants under every ordering — the host disk is never mutated, no write leaks across fresh-per-exec uppers, every command yields a well-formed result. Host-gated, small run counts (each op is a real subprocess).
Key files
Paths relative to packages/virrun/src/.
| File | Role |
|---|---|
models/exec/differential/DifferentialCase.ts | one corpus entry — command + name + optional per-case normalization rules |
models/exec/differential/NormalizationRule.ts | a single explicit { pattern, placeholder } substitution |
services/exec/differential/normalizeExecResult.ts | applies a case's rules to stdout/stderr (exit code untouched) |
services/exec/differential/differentialCorpus.test.ts | NODE_DIFFERENTIAL_CORPUS (every backend) + SHELL_DIFFERENTIAL_CORPUS (real-exec backends) |
services/exec/differential/assertDifferential.test.ts | candidate vs native baseline, normalize, assert identical — the shared body |
services/exec/os/createOsBackend.differential.test.ts | os backend × shell corpus + the host-disk isolation assertion |
services/exec/vfs/createVfsBackend.differential.test.ts | vfs backend × node corpus + a multi-file run off real disk |
services/exec/snapshot/forkSnapshot.equivalence.test.ts | warm fork ≡ cold install |
services/exec/snapshot/persistRun.equivalence.test.ts | write-back host parity vs native |
services/exec/cache/taskCache.equivalence.test.ts | cache replay ≡ a real re-run |
services/vfs/createPlatformaticFsProvider.property.test.ts | fast-check FS sequences vs the node:fs oracle |
services/exec/os/createOsBackend.property.test.ts | fast-check command sequences vs the isolation invariants |
Notes
- The full matrix to keep covering as the corpus grows: package managers × {with, without native deps} × backends × cache states (cold, warm store, warm snapshot) × hosts (Linux native, WSL2 bridge). A pass on one cell is not a pass on the matrix.
- The differential suite is plain Vitest, so it hard-fails the CI coverage shards; speed is tracked separately by the committed bench artifacts (benchmarking), never by a CI wall-clock gate (rejected).