Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion scripts/build/deps/webkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* for local mode. Override via `--webkit-version=<hash>` to test a branch.
* From https://github.com/oven-sh/WebKit releases.
*/
export const WEBKIT_VERSION = "2e2aa2290fac856d6f451ceacb58f7f5b44dd057";
export const WEBKIT_VERSION = "autobuild-preview-pr-570-ee7526b9";

/**
* WebKit (JavaScriptCore) — the JS engine.
Expand Down
1 change: 0 additions & 1 deletion src/sourcemap_jsc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ bun_core.workspace = true
bun_collections.workspace = true
bun_io.workspace = true
bun_jsc.workspace = true
bun_ast.workspace = true
bun_options_types.workspace = true
bun_paths.workspace = true
# bun_runtime — only used in BakeSourceProvider::get_external_data, which is
Expand Down
530 changes: 246 additions & 284 deletions src/sourcemap_jsc/CodeCoverage.rs

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions test/bundler/bundler_bytecode_portable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -454,14 +454,14 @@ describe("bytecode cache portability", () => {
"js": "50b3e5192dd86a205c73583d585884c1b414467b14db54d03c77d3026bf236ff",
"jsc": {
"bytes": 1997464,
"sha256": "cf47f5e069b3f20c112160c430a3b18c5ce2d501f1c52525d34dfbd054273cfb",
"sha256": "80c9aad9f9890c71ad2cd3c4ee885c5685bd34c60d5c7cb68d730ea5cf04d188",
},
},
"bun build --bytecode --minify features.js": {
"js": "d30a5febed53e316cc2dd2b076502079e809bb0c201ef1671e9a190ecdcf093d",
"jsc": {
"bytes": 46152,
"sha256": "7dc5fe8fbfaed3c41d424d2f172efb0efa1be60524ecefcc9d77dfc8088b32af",
"sha256": "12ce8293f837e44b614ca61517398aeb110738564a27deffc81b3e83357dc831",
},
},
"bun build --bytecode --minify records.js": {
Expand Down Expand Up @@ -517,7 +517,7 @@ describe("bytecode cache portability", () => {
"js": "493bab674ff49b287f26be3f356a3ad6681afb0c7eeffaa590f10cdcd8b58724",
"jsc": {
"bytes": 23772944,
"sha256": "c0bb1ca0664a9c7146fadb951e4f6f801811918489f5777fdbbda101a6ffd6c6",
"sha256": "741e8c74ce2e1a90a50af63bbcebb9b4a42c4e0937d31bd49f1f418c7c49bdb5",
},
},
"bun build --bytecode lodash/lodash.js": {
Expand Down
26 changes: 12 additions & 14 deletions test/cli/test/__snapshots__/coverage.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,24 @@
exports[`lcov coverage reporter: lcov-coverage-reporter-output 1`] = `
"TN:
SF:demo1.ts
FNF:1
FNF:0
FNH:0
DA:2,19
DA:3,16
DA:4,1
LF:3
LH:3
DA:2,1
DA:3,1
LF:2
LH:2
end_of_record
TN:
SF:demo2.ts
FNF:2
FNH:1
DA:2,28
DA:4,11
DA:6,9
DA:2,1
DA:4,1
DA:6,1
DA:9,0
DA:10,0
DA:11,1
DA:14,9
LF:7
LH:5
DA:11,0
DA:14,1
LF:6
LH:4
end_of_record"
`;
207 changes: 195 additions & 12 deletions test/cli/test/coverage.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect, test } from "bun:test";
import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe, normalizeBunSnapshot, tempDir } from "harness";
import { readFileSync } from "node:fs";
import path from "path";
Expand Down Expand Up @@ -192,8 +192,8 @@ test("should call only some functions", () => {
---------------|---------|---------|-------------------
File | % Funcs | % Lines | Uncovered Line #s
---------------|---------|---------|-------------------
All files | 75.00 | 83.33 |
include-me.ts | 50.00 | 66.67 | 6
All files | 75.00 | 75.00 |
include-me.ts | 50.00 | 50.00 | 6-7
test.test.ts | 100.00 | 100.00 |
---------------|---------|---------|-------------------

Expand Down Expand Up @@ -385,22 +385,22 @@ test("should call both functions", () => {
SF:include-me.ts
FNF:1
FNH:1
DA:2,11
DA:3,17
DA:2,1
DA:3,1
LF:2
LH:2
end_of_record
TN:
SF:test.test.ts
FNF:1
FNH:1
DA:2,40
DA:3,41
DA:4,39
DA:6,42
DA:7,39
DA:8,36
DA:9,2
DA:2,1
DA:3,1
DA:4,1
DA:6,1
DA:7,1
DA:8,1
DA:9,1
LF:7
LH:7
end_of_record"
Expand Down Expand Up @@ -697,3 +697,186 @@ test("calls second", () => {
expect(record).toMatch(/FNF:2\nFNH:2\n/);
expect(exitCode).toBe(0);
});

// Every executed basic block used to be OR-ed into the line set, and only a
// never-called function cleared lines afterwards. One executed block that
// enclosed a dead one then marked the dead one as covered, and a hit count
// grew by one per byte of executed block on the line. Now the innermost range
// that contains a line's first significant byte decides the line, the same
// rule JSC uses in findBasicBlockAtTextOffset.
async function lineHits(dir: string, subject: string): Promise<Record<number, number>> {
await using proc = Bun.spawn({
cmd: [bunExe(), "test", "--coverage", "--coverage-reporter=lcov", "./run.test.ts"],
env: bunEnv,
cwd: dir,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(exitCode, stdout + stderr).toBe(0);
const lcov = readFileSync(path.join(dir, "coverage", "lcov.info"), "utf-8");
const record = lcov.split("end_of_record").find(r => r.includes(`SF:${subject}\n`));
expect(record).toBeDefined();
const hits: Record<number, number> = {};
for (const [, line, count] of record!.matchAll(/^DA:(\d+),(\d+)$/gm)) {
hits[Number(line)] = Number(count);
}
return hits;
}

const runTest = `import { test } from "bun:test";
import "./subject";
test("runs", () => {});
`;

describe.concurrent("line coverage follows the innermost block", () => {
test("a dead method, function and if-body stay uncovered next to a class field", async () => {
using dir = tempDir("cov-innermost", {
"subject.ts": `export class Foo {
x = 1;
constructor() {}
dead() {
return 2;
}
}
export function neverCalled() {
return 3;
}
export function loop() {
let n = 0;
for (let i = 0; i < 5; i++) {
n += i;
}
if (n > 100) {
console.log("unreachable");
}
return n;
}
new Foo();
loop();
`,
"run.test.ts": runTest,
});
expect(await lineHits(String(dir), "subject.ts")).toEqual({
1: 1,
2: 1,
3: 1,
4: 0,
5: 0,
8: 0,
9: 0,
11: 1,
12: 1,
13: 1,
14: 5,
16: 1,
17: 0,
19: 1,
21: 1,
22: 1,
});
});

test("a never-called accessor stays uncovered next to a called method", async () => {
using dir = tempDir("cov-accessor", {
"subject.ts": `export class Foo {
m() {
return 1;
}
get g() {
return 2;
}
set s(v) {
this.v = v;
}
}
const f = new Foo();
f.m();
`,
"run.test.ts": runTest,
});
expect(await lineHits(String(dir), "subject.ts")).toEqual({
1: 1,
2: 1,
3: 1,
5: 0,
6: 0,
8: 0,
9: 0,
12: 1,
13: 1,
});
});

test("code after an await or yield that never resumed is uncovered", async () => {
using dir = tempDir("cov-await", {
"subject.ts": `export async function suspended() {
const p = new Promise(() => {});
await p;
console.log("never");
}
export function* gen() {
yield 1;
console.log("never");
}
export async function skipped(flag: boolean) {
flag && await new Promise(() => {});
return "ran";
}
suspended();
gen().next();
skipped(false);
`,
"run.test.ts": runTest,
});
expect(await lineHits(String(dir), "subject.ts")).toEqual({
1: 1,
2: 1,
3: 1,
4: 0,
6: 1,
7: 1,
8: 0,
10: 1,
11: 1,
12: 1,
14: 1,
15: 1,
16: 1,
});
});

test("the same lines without a source map", async () => {
using dir = tempDir("cov-no-sourcemap", {
"bunfig.toml": `[test]
coverageIgnoreSourcemaps = true
`,
"subject.js": `export class Foo {
x = 1;
dead() {
return 2;
}
}
export function neverCalled() {
return 3;
}
if (false) {
console.log("dead");
}
new Foo();
`,
"run.test.ts": runTest,
});
expect(await lineHits(String(dir), "subject.js")).toEqual({
1: 1,
2: 1,
3: 0,
4: 0,
7: 0,
8: 0,
10: 1,
11: 0,
13: 1,
});
});
});
Loading