@specific.dev/spectest 0.60.0 → 0.60.1
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/harness/coverage.d.ts +15 -6
- package/dist/harness/coverage.js +27 -11
- package/package.json +1 -1
- package/src/harness/coverage.test.ts +14 -1
- package/src/harness/coverage.ts +29 -10
|
@@ -92,13 +92,22 @@ export interface LcovRecordCounts {
|
|
|
92
92
|
* path are merged (a tool may split a file across records). */
|
|
93
93
|
export declare function parseLcovCounts(text: string): Map<string, LcovRecordCounts>;
|
|
94
94
|
/**
|
|
95
|
-
* `now` minus `prev`, per line and per function. A
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
95
|
+
* `now` minus `prev`, per line and per function. A file not in `prev`
|
|
96
|
+
* is new: everything in it counts. A **record in which any count went
|
|
97
|
+
* down** is a process that restarted (a restart resets every counter of
|
|
98
|
+
* that process at once): the whole record stands as-is, never
|
|
99
|
+
* subtracted — per line would report a line that ran exactly as often
|
|
100
|
+
* again as "not executed", and the restarted boot set (1 − 1) would
|
|
101
|
+
* vanish. Per record, not per report: a combined coverage.py file can
|
|
102
|
+
* hold several processes. A restart in which no counter dropped is not
|
|
103
|
+
* detectable from counts and is accepted as the residual (a service
|
|
104
|
+
* restarting mid-test is an anomaly already). Lines with a zero delta
|
|
105
|
+
* stay in the record: the server distinguishes "ran 0 times" from "not
|
|
106
|
+
* a counted line". Only records with at least one execution are kept —
|
|
107
|
+
* a file with nothing new in it did not run. `restarted` receives the
|
|
108
|
+
* paths of the records taken as-is.
|
|
100
109
|
*/
|
|
101
|
-
export declare function subtractLcovCounts(now: Map<string, LcovRecordCounts>, prev: Map<string, LcovRecordCounts
|
|
110
|
+
export declare function subtractLcovCounts(now: Map<string, LcovRecordCounts>, prev: Map<string, LcovRecordCounts>, restarted?: string[]): Map<string, LcovRecordCounts>;
|
|
102
111
|
/** Counts rendered back to lcov, records in path order. */
|
|
103
112
|
export declare function renderLcovCounts(counts: Map<string, LcovRecordCounts>): string;
|
|
104
113
|
/** Forget every previous capture (tests). */
|
package/dist/harness/coverage.js
CHANGED
|
@@ -215,29 +215,41 @@ export function parseLcovCounts(text) {
|
|
|
215
215
|
return out;
|
|
216
216
|
}
|
|
217
217
|
/**
|
|
218
|
-
* `now` minus `prev`, per line and per function. A
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
218
|
+
* `now` minus `prev`, per line and per function. A file not in `prev`
|
|
219
|
+
* is new: everything in it counts. A **record in which any count went
|
|
220
|
+
* down** is a process that restarted (a restart resets every counter of
|
|
221
|
+
* that process at once): the whole record stands as-is, never
|
|
222
|
+
* subtracted — per line would report a line that ran exactly as often
|
|
223
|
+
* again as "not executed", and the restarted boot set (1 − 1) would
|
|
224
|
+
* vanish. Per record, not per report: a combined coverage.py file can
|
|
225
|
+
* hold several processes. A restart in which no counter dropped is not
|
|
226
|
+
* detectable from counts and is accepted as the residual (a service
|
|
227
|
+
* restarting mid-test is an anomaly already). Lines with a zero delta
|
|
228
|
+
* stay in the record: the server distinguishes "ran 0 times" from "not
|
|
229
|
+
* a counted line". Only records with at least one execution are kept —
|
|
230
|
+
* a file with nothing new in it did not run. `restarted` receives the
|
|
231
|
+
* paths of the records taken as-is.
|
|
223
232
|
*/
|
|
224
|
-
export function subtractLcovCounts(now, prev) {
|
|
233
|
+
export function subtractLcovCounts(now, prev, restarted) {
|
|
225
234
|
const out = new Map();
|
|
226
235
|
for (const [file, rec] of now) {
|
|
227
236
|
const before = prev.get(file);
|
|
237
|
+
const dropped = !!before &&
|
|
238
|
+
([...rec.lines].some(([l, h]) => h < (before.lines.get(l) ?? 0)) ||
|
|
239
|
+
[...rec.functions].some(([n, h]) => h < (before.functions.get(n) ?? 0)));
|
|
240
|
+
if (dropped)
|
|
241
|
+
restarted?.push(file);
|
|
228
242
|
const lines = new Map();
|
|
229
243
|
const functions = new Map();
|
|
230
244
|
let executed = false;
|
|
231
245
|
for (const [l, h] of rec.lines) {
|
|
232
|
-
const
|
|
233
|
-
const d = h < b ? h : h - b;
|
|
246
|
+
const d = dropped ? h : h - (before?.lines.get(l) ?? 0);
|
|
234
247
|
lines.set(l, d);
|
|
235
248
|
if (d > 0)
|
|
236
249
|
executed = true;
|
|
237
250
|
}
|
|
238
251
|
for (const [name, h] of rec.functions) {
|
|
239
|
-
const
|
|
240
|
-
const d = h < b ? h : h - b;
|
|
252
|
+
const d = dropped ? h : h - (before?.functions.get(name) ?? 0);
|
|
241
253
|
functions.set(name, d);
|
|
242
254
|
if (d > 0)
|
|
243
255
|
executed = true;
|
|
@@ -309,7 +321,11 @@ export function applyCoverageDelta(capture, mode, deltaReports) {
|
|
|
309
321
|
const now = parseLcovCounts(r.content);
|
|
310
322
|
const prev = prevByName.get(r.name) ?? new Map();
|
|
311
323
|
prevByName.set(r.name, now);
|
|
312
|
-
|
|
324
|
+
const restarted = [];
|
|
325
|
+
reports.push({ name: r.name, content: renderLcovCounts(subtractLcovCounts(now, prev, restarted)) });
|
|
326
|
+
if (restarted.length > 0) {
|
|
327
|
+
console.warn(`[coverage] service "${capture.service}" report ${r.name}: counters dropped in ${restarted.length} file(s) (${restarted.slice(0, 3).join(", ")}) — a process restarted; those records count whole`);
|
|
328
|
+
}
|
|
313
329
|
}
|
|
314
330
|
return { ...capture, reports };
|
|
315
331
|
}
|
package/package.json
CHANGED
|
@@ -159,8 +159,21 @@ describe("coverage delta", () => {
|
|
|
159
159
|
expect(d.get("/app/a.py")!.lines.get(1)).toBe(0);
|
|
160
160
|
expect(d.get("/app/a.py")!.functions.get("main")).toBe(0);
|
|
161
161
|
expect(d.get("/app/c.py")!.lines.get(1)).toBe(2);
|
|
162
|
-
const
|
|
162
|
+
const names: string[] = [];
|
|
163
|
+
const restarted = subtractLcovCounts(parseLcovCounts("SF:/app/b.py\nDA:1,2\nend_of_record\n"), parseLcovCounts(boot), names);
|
|
163
164
|
expect(restarted.get("/app/b.py")!.lines.get(1)).toBe(2);
|
|
165
|
+
expect(names).toEqual(["/app/b.py"]);
|
|
166
|
+
// A restart is per record: one dropped counter means every count in
|
|
167
|
+
// that record stands, including a line that ran exactly as often
|
|
168
|
+
// again (the boot line: 1 before, 1 now — not 0). Other records in
|
|
169
|
+
// the same report still subtract.
|
|
170
|
+
const mixed = subtractLcovCounts(
|
|
171
|
+
parseLcovCounts("SF:/app/a.py\nFNDA:1,main\nDA:1,1\nDA:2,1\nDA:3,0\nend_of_record\nSF:/app/b.py\nDA:1,3\nend_of_record\n"),
|
|
172
|
+
parseLcovCounts("SF:/app/a.py\nFNDA:1,main\nDA:1,1\nDA:2,1\nDA:3,7\nend_of_record\nSF:/app/b.py\nDA:1,1\nend_of_record\n"),
|
|
173
|
+
);
|
|
174
|
+
expect(mixed.get("/app/a.py")!.lines.get(1)).toBe(1);
|
|
175
|
+
expect(mixed.get("/app/a.py")!.functions.get("main")).toBe(1);
|
|
176
|
+
expect(mixed.get("/app/b.py")!.lines.get(1)).toBe(2);
|
|
164
177
|
// A record with no counts at all: presence is its signal, new only once.
|
|
165
178
|
const bare = "SF:/app/bare.py\nend_of_record\n";
|
|
166
179
|
expect(subtractLcovCounts(parseLcovCounts(bare), new Map()).has("/app/bare.py")).toBe(true);
|
package/src/harness/coverage.ts
CHANGED
|
@@ -277,31 +277,44 @@ export function parseLcovCounts(text: string): Map<string, LcovRecordCounts> {
|
|
|
277
277
|
}
|
|
278
278
|
|
|
279
279
|
/**
|
|
280
|
-
* `now` minus `prev`, per line and per function. A
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
280
|
+
* `now` minus `prev`, per line and per function. A file not in `prev`
|
|
281
|
+
* is new: everything in it counts. A **record in which any count went
|
|
282
|
+
* down** is a process that restarted (a restart resets every counter of
|
|
283
|
+
* that process at once): the whole record stands as-is, never
|
|
284
|
+
* subtracted — per line would report a line that ran exactly as often
|
|
285
|
+
* again as "not executed", and the restarted boot set (1 − 1) would
|
|
286
|
+
* vanish. Per record, not per report: a combined coverage.py file can
|
|
287
|
+
* hold several processes. A restart in which no counter dropped is not
|
|
288
|
+
* detectable from counts and is accepted as the residual (a service
|
|
289
|
+
* restarting mid-test is an anomaly already). Lines with a zero delta
|
|
290
|
+
* stay in the record: the server distinguishes "ran 0 times" from "not
|
|
291
|
+
* a counted line". Only records with at least one execution are kept —
|
|
292
|
+
* a file with nothing new in it did not run. `restarted` receives the
|
|
293
|
+
* paths of the records taken as-is.
|
|
285
294
|
*/
|
|
286
295
|
export function subtractLcovCounts(
|
|
287
296
|
now: Map<string, LcovRecordCounts>,
|
|
288
297
|
prev: Map<string, LcovRecordCounts>,
|
|
298
|
+
restarted?: string[],
|
|
289
299
|
): Map<string, LcovRecordCounts> {
|
|
290
300
|
const out = new Map<string, LcovRecordCounts>();
|
|
291
301
|
for (const [file, rec] of now) {
|
|
292
302
|
const before = prev.get(file);
|
|
303
|
+
const dropped =
|
|
304
|
+
!!before &&
|
|
305
|
+
([...rec.lines].some(([l, h]) => h < (before.lines.get(l) ?? 0)) ||
|
|
306
|
+
[...rec.functions].some(([n, h]) => h < (before.functions.get(n) ?? 0)));
|
|
307
|
+
if (dropped) restarted?.push(file);
|
|
293
308
|
const lines = new Map<number, number>();
|
|
294
309
|
const functions = new Map<string, number>();
|
|
295
310
|
let executed = false;
|
|
296
311
|
for (const [l, h] of rec.lines) {
|
|
297
|
-
const
|
|
298
|
-
const d = h < b ? h : h - b;
|
|
312
|
+
const d = dropped ? h : h - (before?.lines.get(l) ?? 0);
|
|
299
313
|
lines.set(l, d);
|
|
300
314
|
if (d > 0) executed = true;
|
|
301
315
|
}
|
|
302
316
|
for (const [name, h] of rec.functions) {
|
|
303
|
-
const
|
|
304
|
-
const d = h < b ? h : h - b;
|
|
317
|
+
const d = dropped ? h : h - (before?.functions.get(name) ?? 0);
|
|
305
318
|
functions.set(name, d);
|
|
306
319
|
if (d > 0) executed = true;
|
|
307
320
|
}
|
|
@@ -374,7 +387,13 @@ export function applyCoverageDelta(
|
|
|
374
387
|
const now = parseLcovCounts(r.content);
|
|
375
388
|
const prev = prevByName.get(r.name) ?? new Map<string, LcovRecordCounts>();
|
|
376
389
|
prevByName.set(r.name, now);
|
|
377
|
-
|
|
390
|
+
const restarted: string[] = [];
|
|
391
|
+
reports.push({ name: r.name, content: renderLcovCounts(subtractLcovCounts(now, prev, restarted)) });
|
|
392
|
+
if (restarted.length > 0) {
|
|
393
|
+
console.warn(
|
|
394
|
+
`[coverage] service "${capture.service}" report ${r.name}: counters dropped in ${restarted.length} file(s) (${restarted.slice(0, 3).join(", ")}) — a process restarted; those records count whole`,
|
|
395
|
+
);
|
|
396
|
+
}
|
|
378
397
|
}
|
|
379
398
|
return { ...capture, reports };
|
|
380
399
|
}
|