Files
suaveplan/openspec/archive/2026-05-02-testing-integration-bun-runtime-compat/proposal.md
2026-05-27 23:29:53 +01:00

68 lines
4.7 KiB
Markdown

# OpenSpec Change: testing-integration-bun-runtime-compat
- **Change ID**: testing-integration-bun-runtime-compat
- **Package**: `@suaveplan/testing-integration` (modify) + every `testing-integration-*` package
- **Status**: PROPOSED
- **Date**: 2026-04-28
## Summary
Make `bunx vitest run` actually start testcontainers in this monorepo. Today every `testing-integration-*` package lands in env-bridge mode only; the moment a fixture tries to spin a container it crashes or hangs. Two distinct Bun limitations are stacked on top of each other:
1. **Default wait strategy hangs** — testcontainers' default `Wait.forListeningPorts()` runs port probes inside Bun's net stack and never resolves on macOS/Linux. Bun issue [oven-sh/bun#21342](https://github.com/oven-sh/bun/issues/21342) (open since 2025-07-24, reproed on Bun 1.3.2 in March 2026) tracks this; community workaround is `Wait.forLogMessage(...)` + explicit port exposure.
2. **`uv_version_string` panic** — `dockerode` (which testcontainers uses for the Docker socket) pulls `ssh2`, whose NAPI sshcrypto.node calls `uv_version_string` at module-load. Bun's libuv shim doesn't implement it ([oven-sh/bun#18546](https://github.com/oven-sh/bun/issues/18546)). Crashes the runner before any fixture runs.
Today's symptom in this repo: `bun run --filter=@suaveplan/storage-s3 test:integration` panics on the second issue before the first is even reached.
## Current state
- Repo standard runner: `bunx vitest` via Turbo. CLAUDE.md mandates Bun for everything.
- All existing `testing-integration-*` unit tests inject fakes (`containerFactory`, `execFn`, `fetchFn`); no test in the monorepo has ever exercised a real container under Bun.
- `@suaveplan/storage-s3`'s `set-partial.integration.test.ts` is the first real consumer; on Bun it skips with no useful diagnostic.
## What changes
Three options, with cost increasing:
### Option A (cheapest): Document + dual-runner switch
- Add a project-wide `test:integration` Turbo pipeline that invokes `node $(bun pm ls -g vitest | grep ...)` (or shells `npx vitest run`) instead of `bunx vitest`. Keeps `test` (unit) on Bun, sends `test:integration` to Node where testcontainers works today.
- Add a clear log line + early-skip when `process.versions.bun !== undefined` and the fixture would start a container.
- Update CLAUDE.md to note the runtime split for `test:integration`.
Time-to-green: hours. Tradeoff: Bun-purity violation for one task.
### Option B (medium): Bun-friendly fixture defaults + bun-test-compatible wiring
Per Bun #21342 the wait-strategy hang is the load-bearing issue, not `uv_version_string`. The `ssh2` panic only fires because `dockerode` is in the dependency graph; testcontainers v11+ uses `dockerode-modem`-light paths for most operations, and **modeshift** ports of testcontainers exist that drop ssh2 entirely.
Plan:
- Pin every `*-fixture.ts` to `Wait.forLogMessage(<known-ready-line>, { times: <n> })` rather than the default `forListeningPorts`. Per #21342 thumbs-up workaround this resolves the hang on Bun 1.3.0+.
- Audit testcontainers deps for `ssh2`. If it's still pulled in transitively, replace `testcontainers` with a minimal in-repo `docker run -d` shell + `docker exec` polling wrapper (per `eyueldk`'s working pattern, comment 2025-11-23). The seven-step Garage init sequence already shells out, so this is mostly pre-paved.
- Migrate fixture self-tests to `bun:test` via the existing `@suaveplan/testing/runner` dual-runner abstraction.
Time-to-green: ~1 sprint. Tradeoff: forking testcontainers behaviour; carry-cost.
### Option C: Wait for upstream
Track Bun #21342. Currently the team has not assigned a milestone (open + unprioritised at 9 months old). Not a viable plan — would block real container coverage indefinitely.
## Recommendation
**Ship Option A immediately** — unblocks `@suaveplan/storage-s3` and any other package wanting real-container coverage today, with a one-runner-deviation cost that's clearly documented.
**Plan Option B as a follow-up** once we have ≥3 `*-integration` packages actively consuming containers. By then the Bun upstream picture may also have moved.
## Impact
- Option A: Adds a divergent runner for `test:integration` only. Default `test` stays on Bun. Roughly +20s startup per integration run (Node spin-up + module resolution).
- Unblocks every existing `testing-integration-*` package for real-container coverage today.
- `@suaveplan/storage-s3` integration test will run automatically once Option A lands; no test changes needed.
## Out of scope
- The `createIntegrationSetup` data-URL emission bug (separate change: `testing-integration-build-fix`).
- Adding new fixtures.
- Migrating to a different test runner for the unit-test path.