Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/TESTS.md

Raw
Rendered preview

Extension test tiers

Tests are routed to a Vitest project by the folder they live in, not by the extension name. Pick the folder by how the test behaves, so a new extension needs no change to vitest.config.ts.

Folder Project Isolation Put a test here when it…
__tests__/ unit shared process, parallel is pure and deterministic: no module mocks, no global/env mutation left behind. Most tests.
__tests__/contract.test.ts contract per-file process.env sandbox is the required owned-fixture contract test (see AGENTS.md). One per extension.
__serial__/ isolated-serial fresh module state, one worker calls vi.mock(...), or needs a single worker for real subprocesses / fake-command PATH / process.chdir.
__e2e__/ Playwright browser drives rendered browser assets.
__fuzz__/ campaign — hostile-input fuzz targets (<name>.fuzz.ts) plus their regressions/.
__bench__/ bench — benchmarks.

The rule that matters

The unit project runs non-isolated across files in a shared worker, so a vi.mock(...) persists in the module registry and leaks into the next file. //:check fails any __tests__/*.test.ts (except contract.test.ts) that calls vi.mock, with a pointer to __serial__/.

Global or env stubs (vi.stubGlobal, vi.stubEnv) are fine in __tests__/ only if restored in cleanup; otherwise move them to __serial__/ too. Spawning real subprocesses is allowed in __tests__/ (scope cwd/env per call); move to __serial__/ if contention under parallelism is a problem.

Coverage, mutation, fuzz

  • mise run //extensions/<name>:coverage — V8 coverage over the deterministic projects, scoped to the extension.
  • mise run //extensions/<name>:mutate <file> — Stryker, per-extension cache.
  • mise run //extensions/<name>:fuzz <target> — fuzz campaign; minimized findings become replay tests next to the target.
# Extension test tiers

Tests are routed to a Vitest project by the **folder** they live in, not by the
extension name.
Pick the folder by how the test behaves, so a new extension needs no change to
`vitest.config.ts`.

| Folder | Project | Isolation | Put a test here when it… |
| --- | --- | --- | --- |
| `__tests__/` | `unit` | shared process, parallel | is pure and deterministic: no module mocks, no global/env mutation left behind. Most tests. |
| `__tests__/contract.test.ts` | `contract` | per-file process.env sandbox | is the required owned-fixture contract test (see `AGENTS.md`). One per extension. |
| `__serial__/` | `isolated-serial` | fresh module state, one worker | calls `vi.mock(...)`, or needs a single worker for real subprocesses / fake-command `PATH` / `process.chdir`. |
| `__e2e__/` | Playwright | browser | drives rendered browser assets. |
| `__fuzz__/` | campaign | — | hostile-input fuzz targets (`<name>.fuzz.ts`) plus their `regressions/`. |
| `__bench__/` | bench | — | benchmarks. |

## The rule that matters

The `unit` project runs **non-isolated** across files in a shared worker, so a
`vi.mock(...)` persists in the module registry and leaks into the next file.
`//:check` fails any `__tests__/*.test.ts` (except `contract.test.ts`) that calls
`vi.mock`, with a pointer to `__serial__/`.

Global or env stubs (`vi.stubGlobal`, `vi.stubEnv`) are fine in `__tests__/`
**only if** restored in cleanup; otherwise move them to `__serial__/` too.
Spawning real subprocesses is allowed in `__tests__/` (scope `cwd`/`env` per
call); move to `__serial__/` if contention under parallelism is a problem.

## Coverage, mutation, fuzz

- `mise run //extensions/<name>:coverage` — V8 coverage over the deterministic
  projects, scoped to the extension.
- `mise run //extensions/<name>:mutate <file>` — Stryker, per-extension cache.
- `mise run //extensions/<name>:fuzz <target>` — fuzz campaign; minimized
  findings become replay tests next to the target.