@specific.dev/spectest 0.82.0 → 0.83.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/coverage.d.ts +6 -3
- package/dist/coverage.js +25 -5
- package/dist/harness/coverage.d.ts +4 -3
- package/dist/harness/coverage.js +4 -3
- package/package.json +1 -1
- package/src/coverage.test.ts +21 -11
- package/src/coverage.ts +25 -5
- package/src/harness/coverage.ts +4 -3
package/dist/coverage.d.ts
CHANGED
|
@@ -144,9 +144,12 @@ export declare const NODE_COVERAGE_DUMP_PREFIX = "node-";
|
|
|
144
144
|
* - Writes a **dump** `node-<pid>-<thread>-<ms>-<n>.json`: the app's own
|
|
145
145
|
* scripts (`file://`, outside `node_modules` and `.cache`, not the
|
|
146
146
|
* hook) in which some range count is above zero, and of each only the
|
|
147
|
-
* functions that ran,
|
|
148
|
-
*
|
|
149
|
-
* `
|
|
147
|
+
* functions that ran, each one flat array: `[start, length, count]`
|
|
148
|
+
* for its extent (V8's first range), then `[offset from start,
|
|
149
|
+
* length, count]` per block range in V8's order. Name and block flag
|
|
150
|
+
* are in the record, joined on the extent. Nothing when nothing ran.
|
|
151
|
+
* `{ spectestVersion: 2, result: [{ url, spectestScript, functions:
|
|
152
|
+
* [[s, l, c, o, l, c, …], …] }] }`.
|
|
150
153
|
* - Writes, **once per script per branch**, a **script record**
|
|
151
154
|
* `scripts/<id>.json` (`id` = sha256 of the URL): the length of every
|
|
152
155
|
* line of the source V8 compiled (`Debugger.getScriptSource`, so a
|
package/dist/coverage.js
CHANGED
|
@@ -156,9 +156,12 @@ export const NODE_COVERAGE_DUMP_PREFIX = "node-";
|
|
|
156
156
|
* - Writes a **dump** `node-<pid>-<thread>-<ms>-<n>.json`: the app's own
|
|
157
157
|
* scripts (`file://`, outside `node_modules` and `.cache`, not the
|
|
158
158
|
* hook) in which some range count is above zero, and of each only the
|
|
159
|
-
* functions that ran,
|
|
160
|
-
*
|
|
161
|
-
* `
|
|
159
|
+
* functions that ran, each one flat array: `[start, length, count]`
|
|
160
|
+
* for its extent (V8's first range), then `[offset from start,
|
|
161
|
+
* length, count]` per block range in V8's order. Name and block flag
|
|
162
|
+
* are in the record, joined on the extent. Nothing when nothing ran.
|
|
163
|
+
* `{ spectestVersion: 2, result: [{ url, spectestScript, functions:
|
|
164
|
+
* [[s, l, c, o, l, c, …], …] }] }`.
|
|
162
165
|
* - Writes, **once per script per branch**, a **script record**
|
|
163
166
|
* `scripts/<id>.json` (`id` = sha256 of the URL): the length of every
|
|
164
167
|
* line of the source V8 compiled (`Debugger.getScriptSource`, so a
|
|
@@ -331,10 +334,27 @@ export const NODE_COVERAGE_HOOK = `"use strict";
|
|
|
331
334
|
}
|
|
332
335
|
post("Debugger.disable");
|
|
333
336
|
}
|
|
337
|
+
// A ran function is one flat array: [start, length, count] for its
|
|
338
|
+
// extent (V8's first range), then [offset from start, length, count]
|
|
339
|
+
// per block range in V8's order. Small numbers, so the dump gzips to
|
|
340
|
+
// less than half of V8's own JSON; name and block flag live in the
|
|
341
|
+
// script record, joined on the extent.
|
|
334
342
|
const doc = {
|
|
335
|
-
spectestVersion:
|
|
343
|
+
spectestVersion: 2,
|
|
336
344
|
result: kept.map(function (s) {
|
|
337
|
-
return {
|
|
345
|
+
return {
|
|
346
|
+
url: s.url,
|
|
347
|
+
spectestScript: idOf(s.url),
|
|
348
|
+
functions: s.functions.filter(ran).map(function (f) {
|
|
349
|
+
const first = f.ranges[0];
|
|
350
|
+
const out = [first.startOffset, first.endOffset - first.startOffset, first.count];
|
|
351
|
+
for (let i = 1; i < f.ranges.length; i++) {
|
|
352
|
+
const r = f.ranges[i];
|
|
353
|
+
out.push(r.startOffset - first.startOffset, r.endOffset - r.startOffset, r.count);
|
|
354
|
+
}
|
|
355
|
+
return out;
|
|
356
|
+
}),
|
|
357
|
+
};
|
|
338
358
|
}),
|
|
339
359
|
};
|
|
340
360
|
const name = ${JSON.stringify(NODE_COVERAGE_DUMP_PREFIX)} + process.pid + "-" + wt.threadId + "-" + Date.now() + "-" + seq++ + ".json";
|
|
@@ -57,9 +57,10 @@ export declare function hasLcovSourceFile(text: string): boolean;
|
|
|
57
57
|
export declare function isLcovShaped(text: string): boolean;
|
|
58
58
|
/**
|
|
59
59
|
* True when `text` looks like a V8 coverage JSON document — what
|
|
60
|
-
* `NODE_V8_COVERAGE` / `v8.takeCoverage()` write
|
|
61
|
-
* `
|
|
62
|
-
*
|
|
60
|
+
* `NODE_V8_COVERAGE` / `v8.takeCoverage()` write, and the shape the
|
|
61
|
+
* `node()` hook's dumps keep: an object with a `result` array of
|
|
62
|
+
* scripts. A cheap shape check, not a parse (a report can be
|
|
63
|
+
* megabytes); the control plane parses for real.
|
|
63
64
|
*/
|
|
64
65
|
export declare function isV8CoverageJson(text: string): boolean;
|
|
65
66
|
/** True when `text` is a script record the `node()` hook writes
|
package/dist/harness/coverage.js
CHANGED
|
@@ -56,9 +56,10 @@ export function isLcovShaped(text) {
|
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
58
|
* True when `text` looks like a V8 coverage JSON document — what
|
|
59
|
-
* `NODE_V8_COVERAGE` / `v8.takeCoverage()` write
|
|
60
|
-
* `
|
|
61
|
-
*
|
|
59
|
+
* `NODE_V8_COVERAGE` / `v8.takeCoverage()` write, and the shape the
|
|
60
|
+
* `node()` hook's dumps keep: an object with a `result` array of
|
|
61
|
+
* scripts. A cheap shape check, not a parse (a report can be
|
|
62
|
+
* megabytes); the control plane parses for real.
|
|
62
63
|
*/
|
|
63
64
|
export function isV8CoverageJson(text) {
|
|
64
65
|
return /^\s*\{/.test(text) && /"result"\s*:\s*\[/.test(text);
|
package/package.json
CHANGED
package/src/coverage.test.ts
CHANGED
|
@@ -159,7 +159,7 @@ const WRAPPER_JS =
|
|
|
159
159
|
|
|
160
160
|
interface Dump {
|
|
161
161
|
spectestVersion: number;
|
|
162
|
-
result: { url: string; spectestScript: string; functions:
|
|
162
|
+
result: { url: string; spectestScript: string; functions: number[][] }[];
|
|
163
163
|
}
|
|
164
164
|
interface ScriptRecord {
|
|
165
165
|
spectestScript: {
|
|
@@ -190,6 +190,11 @@ describe.if(hasNode)("nodeCoverage hook (real node)", () => {
|
|
|
190
190
|
}
|
|
191
191
|
return out;
|
|
192
192
|
}
|
|
193
|
+
/** The names of a dump's ran functions, joined to the record on the extent. */
|
|
194
|
+
function ranNames(dump: Dump, rec: ScriptRecord["spectestScript"]): string[] {
|
|
195
|
+
const byExtent = new Map(rec.functions.map(([name, start, end]) => [`${start}-${end}`, name]));
|
|
196
|
+
return dump.result[0]!.functions.map((f) => byExtent.get(`${f[0]}-${f[0]! + f[1]!}`) ?? "?");
|
|
197
|
+
}
|
|
193
198
|
async function scratch(): Promise<string> {
|
|
194
199
|
resetNodeCoverageMemory();
|
|
195
200
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "spectest-covhook-"));
|
|
@@ -221,11 +226,14 @@ describe.if(hasNode)("nodeCoverage hook (real node)", () => {
|
|
|
221
226
|
await node().capture!(fakeCtx(dir));
|
|
222
227
|
const [name] = await dumpsIn(dir);
|
|
223
228
|
const dump = await readDump(dir, name!);
|
|
224
|
-
expect(dump.spectestVersion).toBe(
|
|
229
|
+
expect(dump.spectestVersion).toBe(2);
|
|
225
230
|
expect(dump.result.map((s) => s.url)).toEqual([`file://${path.join(dir, "server.js")}`]);
|
|
226
231
|
const fns = dump.result[0]!.functions;
|
|
227
|
-
|
|
232
|
+
// Triples: the extent, then blocks relative to it; some count > 0.
|
|
233
|
+
expect(fns.every((f) => f.length >= 3 && f.length % 3 === 0)).toBe(true);
|
|
234
|
+
expect(fns.every((f) => f.some((_, i) => i % 3 === 2 && f[i]! > 0))).toBe(true);
|
|
228
235
|
const rec = (await records(dir)).get(`file://${path.join(dir, "server.js")}`)!;
|
|
236
|
+
expect(ranNames(dump, rec).sort()).toEqual(["", "tick"]);
|
|
229
237
|
expect(rec.lineLengths).toEqual([28, 51, 0]);
|
|
230
238
|
expect(rec.functions.map((f) => f[0])).toEqual(["", "tick"]);
|
|
231
239
|
expect(rec.sourceMap).toBeNull();
|
|
@@ -241,7 +249,7 @@ describe.if(hasNode)("nodeCoverage hook (real node)", () => {
|
|
|
241
249
|
expect(names.length).toBe(1);
|
|
242
250
|
expect(names[0]).not.toBe(name);
|
|
243
251
|
const second = await readDump(dir, names[0]!);
|
|
244
|
-
expect(second
|
|
252
|
+
expect(ranNames(second, rec)).toEqual(["tick"]);
|
|
245
253
|
expect((await fs.readdir(path.join(dir, NODE_COVERAGE_SCRIPTS_SUBDIR))).length).toBe(0);
|
|
246
254
|
} finally {
|
|
247
255
|
server.kill("SIGKILL");
|
|
@@ -277,14 +285,14 @@ describe.if(hasNode)("nodeCoverage hook (real node)", () => {
|
|
|
277
285
|
await node().capture!(fakeCtx(dir));
|
|
278
286
|
const names = await dumpsIn(dir);
|
|
279
287
|
expect(names.length).toBe(2);
|
|
288
|
+
const recs = await records(dir);
|
|
289
|
+
expect(recs.size).toBe(1);
|
|
290
|
+
const rec = recs.get(`file://${path.join(dir, "cli.js")}`)!;
|
|
280
291
|
for (const n of names) {
|
|
281
292
|
const d = await readDump(dir, n);
|
|
282
293
|
expect(d.result.map((s) => s.url)).toEqual([`file://${path.join(dir, "cli.js")}`]);
|
|
283
|
-
expect(d
|
|
294
|
+
expect(ranNames(d, rec).sort()).toEqual(["", "used"]);
|
|
284
295
|
}
|
|
285
|
-
const recs = await records(dir);
|
|
286
|
-
expect(recs.size).toBe(1);
|
|
287
|
-
const rec = recs.get(`file://${path.join(dir, "cli.js")}`)!;
|
|
288
296
|
expect(rec.functions.map((f) => f[0])).toEqual(["", "used", "unused"]);
|
|
289
297
|
expect(rec.lineLengths).toEqual([28, 30, 7, 0]);
|
|
290
298
|
expect(rec.sourceMap!.url).toBe(`file://${path.join(dir, "cli.js.map")}`);
|
|
@@ -333,11 +341,13 @@ describe.if(hasNode)("nodeCoverage hook (real node)", () => {
|
|
|
333
341
|
expect(names.length).toBe(2);
|
|
334
342
|
const byThread = new Map<string, Dump>();
|
|
335
343
|
for (const n of names) byThread.set(n.split("-")[2]!, await readDump(dir, n));
|
|
344
|
+
const recs = await records(dir);
|
|
345
|
+
expect(recs.size).toBe(1); // one script, one record, whichever thread came first
|
|
346
|
+
const rec = recs.get(`file://${path.join(dir, "threads.js")}`)!;
|
|
336
347
|
const worker = [...byThread.entries()].find(([t]) => t !== "0")![1];
|
|
337
|
-
expect(worker.
|
|
348
|
+
expect(ranNames(worker, rec)).toContain("inWorker");
|
|
338
349
|
const main = byThread.get("0")!;
|
|
339
|
-
expect(main.
|
|
340
|
-
expect((await records(dir)).size).toBe(1); // one script, one record, whichever thread came first
|
|
350
|
+
expect(ranNames(main, rec)).not.toContain("inWorker");
|
|
341
351
|
});
|
|
342
352
|
|
|
343
353
|
test("a stale socket left by a killed process is removed at capture", async () => {
|
package/src/coverage.ts
CHANGED
|
@@ -258,9 +258,12 @@ export const NODE_COVERAGE_DUMP_PREFIX = "node-";
|
|
|
258
258
|
* - Writes a **dump** `node-<pid>-<thread>-<ms>-<n>.json`: the app's own
|
|
259
259
|
* scripts (`file://`, outside `node_modules` and `.cache`, not the
|
|
260
260
|
* hook) in which some range count is above zero, and of each only the
|
|
261
|
-
* functions that ran,
|
|
262
|
-
*
|
|
263
|
-
* `
|
|
261
|
+
* functions that ran, each one flat array: `[start, length, count]`
|
|
262
|
+
* for its extent (V8's first range), then `[offset from start,
|
|
263
|
+
* length, count]` per block range in V8's order. Name and block flag
|
|
264
|
+
* are in the record, joined on the extent. Nothing when nothing ran.
|
|
265
|
+
* `{ spectestVersion: 2, result: [{ url, spectestScript, functions:
|
|
266
|
+
* [[s, l, c, o, l, c, …], …] }] }`.
|
|
264
267
|
* - Writes, **once per script per branch**, a **script record**
|
|
265
268
|
* `scripts/<id>.json` (`id` = sha256 of the URL): the length of every
|
|
266
269
|
* line of the source V8 compiled (`Debugger.getScriptSource`, so a
|
|
@@ -433,10 +436,27 @@ export const NODE_COVERAGE_HOOK = `"use strict";
|
|
|
433
436
|
}
|
|
434
437
|
post("Debugger.disable");
|
|
435
438
|
}
|
|
439
|
+
// A ran function is one flat array: [start, length, count] for its
|
|
440
|
+
// extent (V8's first range), then [offset from start, length, count]
|
|
441
|
+
// per block range in V8's order. Small numbers, so the dump gzips to
|
|
442
|
+
// less than half of V8's own JSON; name and block flag live in the
|
|
443
|
+
// script record, joined on the extent.
|
|
436
444
|
const doc = {
|
|
437
|
-
spectestVersion:
|
|
445
|
+
spectestVersion: 2,
|
|
438
446
|
result: kept.map(function (s) {
|
|
439
|
-
return {
|
|
447
|
+
return {
|
|
448
|
+
url: s.url,
|
|
449
|
+
spectestScript: idOf(s.url),
|
|
450
|
+
functions: s.functions.filter(ran).map(function (f) {
|
|
451
|
+
const first = f.ranges[0];
|
|
452
|
+
const out = [first.startOffset, first.endOffset - first.startOffset, first.count];
|
|
453
|
+
for (let i = 1; i < f.ranges.length; i++) {
|
|
454
|
+
const r = f.ranges[i];
|
|
455
|
+
out.push(r.startOffset - first.startOffset, r.endOffset - r.startOffset, r.count);
|
|
456
|
+
}
|
|
457
|
+
return out;
|
|
458
|
+
}),
|
|
459
|
+
};
|
|
440
460
|
}),
|
|
441
461
|
};
|
|
442
462
|
const name = ${JSON.stringify(NODE_COVERAGE_DUMP_PREFIX)} + process.pid + "-" + wt.threadId + "-" + Date.now() + "-" + seq++ + ".json";
|
package/src/harness/coverage.ts
CHANGED
|
@@ -108,9 +108,10 @@ export function isLcovShaped(text: string): boolean {
|
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
110
|
* True when `text` looks like a V8 coverage JSON document — what
|
|
111
|
-
* `NODE_V8_COVERAGE` / `v8.takeCoverage()` write
|
|
112
|
-
* `
|
|
113
|
-
*
|
|
111
|
+
* `NODE_V8_COVERAGE` / `v8.takeCoverage()` write, and the shape the
|
|
112
|
+
* `node()` hook's dumps keep: an object with a `result` array of
|
|
113
|
+
* scripts. A cheap shape check, not a parse (a report can be
|
|
114
|
+
* megabytes); the control plane parses for real.
|
|
114
115
|
*/
|
|
115
116
|
export function isV8CoverageJson(text: string): boolean {
|
|
116
117
|
return /^\s*\{/.test(text) && /"result"\s*:\s*\[/.test(text);
|