70bf21e064
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled
121 lines
4.0 KiB
TypeScript
121 lines
4.0 KiB
TypeScript
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import dedent from "ts-dedent";
|
|
import { test } from "./helpers";
|
|
|
|
// Regression test for https://github.com/cloudflare/workers-sdk/issues/5825
|
|
// Istanbul coverage was reporting 0% for source files exercised by test files
|
|
// that ran after the first one. The root cause was that in vitest v1, module
|
|
// re-evaluation after `resetModules()` replaced Istanbul's coverage counter
|
|
// objects, losing data from earlier test files. This was fixed by the vitest v4
|
|
// module runner architecture which correctly preserves counter objects across
|
|
// module re-evaluations via hash-based reuse in istanbul-lib-instrument.
|
|
test(
|
|
"istanbul coverage reports correctly across multiple test files (#5825)",
|
|
{ timeout: 60_000 },
|
|
async ({ expect, seed, vitestRun, tmpPath }) => {
|
|
await seed({
|
|
"wrangler.jsonc": JSON.stringify({
|
|
name: "coverage-test",
|
|
main: "src/index.ts",
|
|
}),
|
|
"vitest.config.mts": dedent /* javascript */ `
|
|
import { cloudflareTest } from "@cloudflare/vitest-pool-workers"
|
|
import { BaseSequencer } from "vitest/node";
|
|
|
|
class DeterministicSequencer extends BaseSequencer {
|
|
sort(files) {
|
|
return [...files].sort((a, b) => a.moduleId.localeCompare(b.moduleId));
|
|
}
|
|
}
|
|
|
|
export default {
|
|
plugins: [
|
|
cloudflareTest({
|
|
miniflare: {
|
|
compatibilityDate: "2025-12-02",
|
|
compatibilityFlags: ["nodejs_compat"],
|
|
},
|
|
wrangler: {
|
|
configPath: "./wrangler.jsonc",
|
|
},
|
|
})
|
|
],
|
|
test: {
|
|
sequence: { sequencer: DeterministicSequencer },
|
|
testTimeout: 90_000,
|
|
coverage: {
|
|
provider: "istanbul",
|
|
reporter: ["json-summary"],
|
|
include: ["src/**"],
|
|
},
|
|
},
|
|
};
|
|
`,
|
|
// Worker with two routes dispatching to separate source files
|
|
"src/index.ts": dedent /* javascript */ `
|
|
import { greetA } from "./a";
|
|
import { greetB } from "./b";
|
|
|
|
export default {
|
|
async fetch(request, env, ctx) {
|
|
if (request.url.endsWith("/a")) {
|
|
return new Response(greetA(request));
|
|
}
|
|
return new Response(greetB(request));
|
|
},
|
|
} satisfies ExportedHandler;
|
|
`,
|
|
"src/a.ts": dedent /* javascript */ `
|
|
export function greetA(request: Request): string {
|
|
return "A: " + request.url;
|
|
}
|
|
`,
|
|
"src/b.ts": dedent /* javascript */ `
|
|
export function greetB(request: Request): string {
|
|
return "B: " + request.url;
|
|
}
|
|
`,
|
|
// Two test files exercising different routes — a.test.ts runs first
|
|
"a.test.ts": dedent /* javascript */ `
|
|
import { SELF } from "cloudflare:test";
|
|
import { it, expect } from "vitest";
|
|
|
|
it("routes to a", async () => {
|
|
const response = await SELF.fetch("http://example.com/a");
|
|
expect(await response.text()).toBe("A: http://example.com/a");
|
|
});
|
|
`,
|
|
"b.test.ts": dedent /* javascript */ `
|
|
import { SELF } from "cloudflare:test";
|
|
import { it, expect } from "vitest";
|
|
|
|
it("routes to b", async () => {
|
|
const response = await SELF.fetch("http://example.com/b");
|
|
expect(await response.text()).toBe("B: http://example.com/b");
|
|
});
|
|
`,
|
|
});
|
|
const result = await vitestRun({ flags: ["--coverage"] });
|
|
expect(await result.exitCode).toBe(0);
|
|
|
|
// Read the JSON coverage summary to verify actual coverage values
|
|
const summaryPath = path.join(tmpPath, "coverage", "coverage-summary.json");
|
|
const summaryJson = JSON.parse(await fs.readFile(summaryPath, "utf8"));
|
|
|
|
// Find coverage for a.ts and b.ts (keys are absolute paths)
|
|
const entries = Object.entries(summaryJson) as [
|
|
string,
|
|
{ functions: { pct: number } },
|
|
][];
|
|
const aCoverage = entries.find(([k]) => k.endsWith("/src/a.ts"))?.[1];
|
|
const bCoverage = entries.find(([k]) => k.endsWith("/src/b.ts"))?.[1];
|
|
|
|
// The bug: b.ts showed 0% coverage when both files ran together,
|
|
// because its counters were lost during module re-evaluation.
|
|
// Both files should now report non-zero function coverage.
|
|
expect(aCoverage?.functions.pct).toBeGreaterThan(0);
|
|
expect(bCoverage?.functions.pct).toBeGreaterThan(0);
|
|
}
|
|
);
|