diff --git a/.changeset/reject-v8-coverage-provider.md b/.changeset/reject-v8-coverage-provider.md new file mode 100644 index 0000000000..01f787f071 --- /dev/null +++ b/.changeset/reject-v8-coverage-provider.md @@ -0,0 +1,9 @@ +--- +"@cloudflare/vitest-pool-workers": patch +--- + +Reject V8 coverage provider with a clear error message + +V8 native code coverage (`@vitest/coverage-v8`) requires `node:inspector` to collect profiling data from V8's runtime. workerd only provides `node:inspector` as a non-functional stub, so V8 coverage would silently fail or crash with a confusing `No such module "node:inspector"` error. + +The pool now detects this configuration early — during Vite plugin setup, before Vitest tries to load the coverage provider — and throws a clear error directing users to use Istanbul coverage instead, which works by instrumenting source code at build time and runs on any JavaScript runtime. diff --git a/packages/vitest-pool-workers/src/pool/plugin.ts b/packages/vitest-pool-workers/src/pool/plugin.ts index ef120d6beb..1e1b4cf1b6 100644 --- a/packages/vitest-pool-workers/src/pool/plugin.ts +++ b/packages/vitest-pool-workers/src/pool/plugin.ts @@ -71,6 +71,30 @@ export function cloudflareTest( config.test ??= {}; config.test.server ??= {}; config.test.server.deps ??= {}; + + // V8 coverage requires `node:inspector` to collect coverage data from + // V8's profiler. workerd provides `node:inspector` as a non-functional + // stub, so V8 coverage silently fails or crashes. Istanbul works because + // it instruments source code at build time without needing V8 access. + // See: https://github.com/cloudflare/workers-sdk/issues/5266 + const coverage = config.test.coverage; + if (coverage && coverage.enabled) { + const provider = "provider" in coverage ? coverage.provider : undefined; + if (provider === "v8" || provider === undefined) { + const lines = [ + 'Coverage provider "v8" is not supported by `@cloudflare/vitest-pool-workers`.', + "V8 native coverage requires `node:inspector` which is not functional in the Workers runtime.", + "", + "Use Istanbul instead — it works by instrumenting source code and runs on any JavaScript runtime:", + "", + " 1. Install: npm i -D @vitest/coverage-istanbul", + ' 2. Set `test.coverage.provider` to "istanbul" in your Vitest config', + "", + "See https://vitest.dev/guide/coverage#istanbul-provider for more details.", + ]; + throw new Error(lines.join("\n")); + } + } // See https://vitest.dev/config/server.html#inline // Without this Vitest delegates to native import() for external deps in node_modules config.test.server.deps.inline = true; diff --git a/packages/vitest-pool-workers/test/validation.test.ts b/packages/vitest-pool-workers/test/validation.test.ts index 322e6ec5ac..6e3141dc5f 100644 --- a/packages/vitest-pool-workers/test/validation.test.ts +++ b/packages/vitest-pool-workers/test/validation.test.ts @@ -1,5 +1,6 @@ import path from "node:path"; import dedent from "ts-dedent"; +import { describe } from "vitest"; import { test, vitestConfig } from "./helpers"; test( @@ -107,3 +108,76 @@ test( expect(result.stderr).toMatch(expected); } ); + +describe("coverage provider validation", () => { + test( + "rejects v8 coverage provider with a clear error", + { timeout: 45_000 }, + async ({ expect, seed, vitestRun }) => { + await seed({ + "vitest.config.mts": vitestConfig( + {}, + { coverage: { enabled: true, provider: "v8" } } + ), + "index.test.ts": dedent /* javascript */ ` + import { it, expect } from "vitest"; + it("works", () => { + expect(1 + 1).toBe(2); + }); + `, + }); + const result = await vitestRun(); + expect(await result.exitCode).toBe(1); + expect(result.stderr).toMatch( + 'Coverage provider "v8" is not supported by `@cloudflare/vitest-pool-workers`' + ); + expect(result.stderr).toMatch("Use Istanbul instead"); + } + ); + + test( + "rejects default coverage provider (v8) with a clear error", + { timeout: 45_000 }, + async ({ expect, seed, vitestRun }) => { + // When no provider is specified, Vitest defaults to v8 + await seed({ + "vitest.config.mts": vitestConfig({}, { coverage: { enabled: true } }), + "index.test.ts": dedent /* javascript */ ` + import { it, expect } from "vitest"; + it("works", () => { + expect(1 + 1).toBe(2); + }); + `, + }); + const result = await vitestRun(); + expect(await result.exitCode).toBe(1); + expect(result.stderr).toMatch( + 'Coverage provider "v8" is not supported by `@cloudflare/vitest-pool-workers`' + ); + } + ); + + test( + "allows istanbul coverage provider", + { timeout: 60_000 }, + async ({ expect, seed, vitestRun }) => { + await seed({ + "vitest.config.mts": vitestConfig( + {}, + { coverage: { enabled: true, provider: "istanbul" } } + ), + "index.test.ts": dedent /* javascript */ ` + import { it, expect } from "vitest"; + it("works", () => { + expect(1 + 1).toBe(2); + }); + `, + }); + const result = await vitestRun(); + // Should not fail with a coverage provider error + expect(result.stderr).not.toMatch( + 'Coverage provider "v8" is not supported' + ); + } + ); +});