diff --git a/scripts/build/deps/webkit.ts b/scripts/build/deps/webkit.ts index 37dbd9913885..1e352fc538bb 100644 --- a/scripts/build/deps/webkit.ts +++ b/scripts/build/deps/webkit.ts @@ -3,7 +3,7 @@ * for local mode. Override via `--webkit-version=` to test a branch. * From https://github.com/oven-sh/WebKit releases. */ -export const WEBKIT_VERSION = "274ebb3cf6008f627197d818e3939d1e2bfc514d"; +export const WEBKIT_VERSION = "d0b16ad0ce2bac652f0f63cdcc642a7d3ba69631"; /** * WebKit (JavaScriptCore) — the JS engine. diff --git a/test/cli/test/coverage.test.ts b/test/cli/test/coverage.test.ts index 6a0e690fa26f..008a19a88fa7 100644 --- a/test/cli/test/coverage.test.ts +++ b/test/cli/test/coverage.test.ts @@ -755,6 +755,55 @@ test("calls second", () => { // it is loaded. Each case has a subject file of its own. Where a case loads // its subject twice, one load runs the `if` branch and the other runs the // `return` after it. +test("what is after a rejected await at the top level of a module did not run", async () => { + using dir = tempDir("cov-top-level-await", { + "subject.ts": `let ran = 0; +export const first = await 1; +ran++; +try { + await Promise.reject(new Error()); + ran++; +} catch { + ran++; +} +export default await ran; +export { ran }; +`, + "subject.test.ts": ` +import { expect, test } from "bun:test"; +import ran from "./subject.ts"; + +test("ran", () => { + expect(ran).toBe(2); +}); +`, + }); + await using proc = Bun.spawn({ + cmd: [bunExe(), "test", "--coverage", "--coverage-reporter=lcov"], + env: bunEnv, + cwd: String(dir), + stderr: "pipe", + }); + const [stderr, exitCode] = await Promise.all([proc.stderr.text(), proc.exited]); + + const lcov = readFileSync(path.join(String(dir), "coverage", "lcov.info"), "utf-8"); + const record = lcov.split("end_of_record").find(r => r.includes("SF:subject.ts")); + expect(record?.match(/^DA:.*$/gm)).toEqual([ + "DA:1,1", + "DA:2,1", + "DA:3,1", + "DA:4,1", + "DA:5,1", + "DA:6,0", + "DA:7,1", + "DA:8,1", + "DA:10,1", + "DA:11,1", + ]); + expect(stderr).toContain(" 1 pass"); + expect(exitCode).toBe(0); +}); + describe("a file loaded more than once counts every load", () => { const esm = `export function covered(n: number): number { if (n > 5) { diff --git a/test/js/bun/sourcemap/source-positions-corpus.ts b/test/js/bun/sourcemap/source-positions-corpus.ts index 22b5e6e91ae3..cf77499f51ae 100644 --- a/test/js/bun/sourcemap/source-positions-corpus.ts +++ b/test/js/bun/sourcemap/source-positions-corpus.ts @@ -421,21 +421,118 @@ export const cases: Record = { 1|} catch {} `, }, + // What is after a statement that the function was left at, and did not come back to. "generators.ts": { - functions: "4/4", + functions: "7/7", lines: ` - 1|function* once() { - 1| yield 1; - -|} 1|function* never() { 0| yield 1; -|} + 1|never(); + 2|function* generator() { + 2| at(); + 2| yield 1; + 1| at(); + 1| yield 2; + 0| at(); + -|} + 1|const resumed = generator(); + 1|resumed.next(); + 1|resumed.next(); + 1|generator().next(); + 2|function* left() { + 2| try { + 2| yield 1; + 0| at(); + 1| } catch { + 1| at(); + -| } + 1| at(); + -|} + 1|const returned = left(); + 1|returned.next(); + 1|returned.return(); + 1|const thrown = left(); + 1|thrown.next(); + 1|thrown.throw(new Error()); + 1|function* delegates() { + 1| yield* [1, 2]; + 0| at(); + -|} + 1|delegates().next(); 1|async function stuck() { + 1| at(); 1| await new Promise(() => {}); + 0| at(); -|} - 1|once().next(); - 1|never(); 1|stuck(); + 1|async function rejected() { + 1| try { + 1| await Promise.reject(new Error()); + 0| at(); + 1| } catch { + 1| at(); + -| } + 1| at(); + -|} + 1|rejected(); +`, + }, + // What `?.`, a logical assignment and a value that is there skip. A `case` that does not match did not run. + "skipped.ts": { + functions: "8/8", + lines: ` + 4|function one() { + 4| at(); + 4| return 1; + -|} + 2|function chain(o?: { a(n: number): void }) { + 2| o + 1| ?.a( + 1| one(), + 1| ); + -|} + 1|chain(); + 1|chain({ a() {} }); + 2|function call(f?: (n: number) => void) { + 2| f?.( + 1| one(), + 1| ); + -|} + 1|call(); + 1|call(() => {}); + 2|function assign(o: Record) { + 2| o.a ||= + 1| one(); + 2| o.b &&= + 0| one(); + 2| o.c ??= + 0| one(); + -|} + 1|assign({ a: 1, b: 0, c: 0 }); + 1|assign({ a: 0, b: 0, c: 0 }); + 2|function parameter( + 2| v = + 1| one(), + -|) { + 2| at(); + -|} + 1|parameter(); + 1|parameter(1); + 1|function pattern({ + 1| v = + 0| one(), + -|}: { v?: number }) { + 1| at(); + -|} + 1|pattern({ v: 1 }); + 1|let node: any = { next: { next: {} } }; + 3|while (node?.next) { + 2| node = node.next; + -|} + 1|switch (2 as number) { + 0| case node?.next: at(); + -|} `, }, // What is a function to the engine alone: not one of the file's functions, and not run until it is. diff --git a/test/js/node/inspector/inspector-profiler.test.ts b/test/js/node/inspector/inspector-profiler.test.ts index f8a6b8cf2c18..b23a04635319 100644 --- a/test/js/node/inspector/inspector-profiler.test.ts +++ b/test/js/node/inspector/inspector-profiler.test.ts @@ -760,6 +760,70 @@ console.log( ]); }); + test.concurrent("what is skipped, or after a yield or an await that is not resumed, is a range", async () => { + using dir = tempDir("inspector-coverage-skipped", { + "fixture.cjs": ` + const inspector = require("node:inspector"); + const vm = require("node:vm"); + const code = [ + "function one() { return 1; }", + "function parameter(v = one()) { return v; }", + "function chain(o) { return o?.a(one()); }", + "function assign(o) { o.a ||= one(); return o; }", + "function* generator() { yield 1; one(); }", + "async function stuck() { await new Promise(() => {}); one(); }", + "parameter(1); parameter(); chain(); assign({ a: 1 }); generator().next(); stuck();", + "", + ].join("\\n"); + const session = new inspector.Session(); + session.connect(); + session.post("Profiler.enable"); + session.post("Profiler.startPreciseCoverage", { callCount: true, detailed: true }); + vm.runInThisContext(code, { filename: "/virtual/skipped.js" }); + session.post("Profiler.takePreciseCoverage", (err, { result }) => { + const [script, ...functions] = result.find(script => script.url.endsWith("/virtual/skipped.js")).functions; + console.log( + JSON.stringify(functions.map(f => f.ranges.map(r => [r.count, code.slice(r.startOffset, r.endOffset)]))), + ); + }); + `, + }); + + await using proc = Bun.spawn({ + cmd: [bunExe(), "fixture.cjs"], + env: bunEnv, + cwd: String(dir), + stderr: "pipe", + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect({ stderrIfFailed: exitCode === 0 ? "" : stderr, exitCode }).toEqual({ stderrIfFailed: "", exitCode: 0 }); + expect(JSON.parse(stdout)).toEqual([ + [[1, "function one() { return 1; }"]], + [ + [2, "function parameter(v = one()) { return v; }"], + [1, " one()"], + ], + [ + [1, "function chain(o) { return o?.a(one()); }"], + [0, "?.a(one())"], + ], + [ + [1, "function assign(o) { o.a ||= one(); return o; }"], + [0, " one()"], + ], + [ + [1, "function* generator() { yield 1; one(); }"], + [0, " one(); "], + ], + [ + [1, "async function stuck() { await new Promise(() => {}); one(); }"], + [0, " one(); "], + ], + [[1, "() => {}"]], + ]); + }); + test.concurrent("respects callCount: false and detailed: false", async () => { using dir = tempDir("inspector-coverage-flags", { "fixture.mjs": coverageVmFixture,