Skip to content

dofs: the workerd test project segfaults and is not in CI, so nothing has ever run against real Durable Object SQLite #71

Description

@Rahul-s-007

Describe the bug

packages/dofs ships two runners over the same src/**/*.test.ts sources:

  • npm testvitest run, the node:sqlite project (src/fs/with-db.ts)
  • npm run test:workersvitest run --config vitest.config.workers.ts, which aliases every with-db.js import to src/fs/with-db.workers.ts so withDB drives a real Durable Object's SqlStorage

CI runs only the first (.github/workflows/ci.yml:101, npm test --workspace). The second is never invoked by any workflow. Since every consumer of dofs runs inside a Durable Object, the runtime the package actually ships on has no CI coverage at all — only node:sqlite, which src/testing.ts:1-4 itself describes as a superset ("Workers' DO SQL surface is a subset of this"). Anywhere the real DO diverges — RETURNING, json_each, the recursive CTE in fs/resolve.ts:137-160, transactionSync rollback semantics — is currently unverified.

The suite is also red, which is presumably why it was never wired up. On a clean checkout of 76d9e75:

$ npm run test:workers --workspace @cloudflare/dofs

 RUN  v4.1.10 packages/dofs

*** Received signal #11: Segmentation fault: 11
*** Received signal #11: Segmentation fault: 11

⎯⎯⎯ Unhandled Errors ⎯⎯⎯
Vitest caught 2 unhandled errors during the test run.

Error: [vitest-pool]: Worker cloudflare-pool emitted error.
Caused by: Error: Worker exited unexpectedly

 Test Files  34 passed (36)
      Tests  412 passed (412)
     Errors  2 errors

npm error code 1

Exit status 1, two files never report, 24 tests silently absent.

Bisecting one file at a time identifies both crashers:

SEGFAULT: src/fs/filesystem.test.ts
SEGFAULT: src/schema/index.test.ts

Root cause is the same for both: each imports SQLiteTestStorage, which pulls node:sqlite in at module scope (src/testing.ts:14). That module's own header already states the constraint (src/testing.ts:6-7):

This module imports node:sqlite at the top level and therefore cannot be loaded under workerd.

Under workerd this is not a catchable module-resolution error — it takes the pool worker down with SIGSEGV, so the failure surfaces as an unhandled pool error rather than a test failure. Vitest still prints 34 passed and a green-looking test count above the error block, which makes the run easy to misread as "passing with some noise."

vitest.config.workers.ts:33 excludes only one of the three files that import the fixture:

exclude: ["src/testing.test.ts"],

and the comment above it (:27-32) asserts something that is not true of the other two:

All other tests run under both backends

src/fs/filesystem.test.ts:14 and src/schema/index.test.ts:1 both import it and are both included by include: ["src/**/*.test.ts"].

Expected behavior

npm run test:workers --workspace @cloudflare/dofs exits 0 with every included file reporting, and CI runs it on every PR, so a change that works against node:sqlite but breaks on real Durable Object SQLite fails the build.

Steps to reproduce

From a clean checkout at 76d9e75:

  1. npm ci
  2. npm run build --workspace @cloudflare/dofs
  3. npm test --workspace @cloudflare/dofs → 37 files, 436 tests, exit 0 (this is all CI ever runs)
  4. npm run test:workers --workspace @cloudflare/dofs → 2 segfaults, 34/36 files, exit 1
  5. Isolate either crasher directly, e.g. npx vitest run --config vitest.config.workers.ts src/fs/filesystem.test.ts
  6. grep -rln SQLiteTestStorage packages/dofs/src --include='*.test.ts' → three files; only one is excluded

Proposed fix

The two crashers need different treatment, because only one of them is legitimately node-only.

src/fs/filesystem.test.ts — make it portable, don't exclude it. It is a smoke test of the WorkspaceFilesystem wrapper and reaches for SQLiteTestStorage only to obtain a (db, now) pair. Routing it through withDB — which the workers config already aliases — makes it run under both backends:

async function withFs<T>(
  fn: (fs: WorkspaceFilesystem) => T | Promise<T>,
  now: () => number = () => 1234,
): Promise<T> {
  return withDB((db) => fn(new WorkspaceFilesystem(db, { now })), { now });
}

This is a net gain of 13 tests against a real DO, not a mute.

src/schema/index.test.ts — exclude it, with an accurate comment. It drives the v1 → v5 migrations, which need a raw handle to a pre-migration database (:85, :134, :208, :265 each construct one from raw SQL). withDB always returns a DB already at SCHEMA_VERSION, so those fixtures cannot be built through it. Exclusion is correct here; the surrounding comment should say why, and should note that the failure mode for a stray node:sqlite import is a segfault rather than an import error.

Wire the project into CI. Add an optional per-package extra test step to the package matrix job, set on the dofs entry:

- name: dofs
  workspace: "@cloudflare/dofs"
  path: packages/dofs
  needs-siblings: false
  extra-test: "test:workers"
- name: Test (${{ matrix.extra-test }})
  if: matrix.extra-test
  run: npm run ${{ matrix.extra-test }} --workspace ${{ matrix.workspace }}

The if: guard keeps every other matrix entry unchanged, and the same seam is there for packages/computerd later if wanted.

Results with the three changes applied:

before after
test:workers exit code 1 0
segfaults 2 0
files / tests 34/36, 412 35/35, 425
added CI time ~5.8s

Verification:

$ npx vitest run --config vitest.config.workers.ts
 Test Files  35 passed (35)
      Tests  425 passed (425)
   Duration  5.78s

$ npx vitest run                     # node project, unchanged
 Test Files  37 passed (37)
      Tests  436 passed (436)

$ npx biome check packages/dofs
Checked 96 files. No fixes applied.

Total diff is 33 insertions / 17 deletions across .github/workflows/ci.yml, packages/dofs/src/fs/filesystem.test.ts, and packages/dofs/vitest.config.workers.ts. No src/ behavior change — test and CI wiring only, so there is nothing to migrate and no compatibility surface. Happy to open the PR if you want it.

Environment

  • cloudflare/computer at 76d9e75 (current main), local checkout
  • Node v25.2.1, npm 11.6.2, macOS 15.7.3 arm64
  • vitest 4.1.10, @cloudflare/vitest-pool-workers ^0.16.10
  • Baselines on this machine: npm test --workspace @cloudflare/dofs 436/436 pass; npm run test:workers exits 1
  • The SIGSEGV signal is what workerd does with a top-level node:sqlite import on this platform; the structural facts are platform-independent — test:workers exits non-zero, CI never invokes it, and two included files import a module documented as unloadable under workerd

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions