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

4.7 KiB

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 (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). 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.