@specific.dev/spectest 0.81.0 → 0.82.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 +87 -137
- package/dist/coverage.js +321 -367
- package/dist/daemon.js +11 -24
- package/dist/harness/coverage.d.ts +6 -1
- package/dist/harness/coverage.js +11 -3
- package/package.json +1 -1
- package/src/coverage.test.ts +163 -336
- package/src/coverage.ts +331 -399
- package/src/daemon.ts +11 -19
- package/src/harness/coverage.ts +12 -3
package/dist/daemon.js
CHANGED
|
@@ -24,7 +24,7 @@ import { pathToFileURL } from "node:url";
|
|
|
24
24
|
import { assert, expect, expectRaw, lowerIngress, dnsName as makeDnsDecl, isWildcard, validateServiceImage, proxy as makeProxyDecl, } from "./index.js";
|
|
25
25
|
import { COVERAGE_CONTAINER_DIR, coverageBundleRef, coverageHostDir, encodeCoverageBundle, isEmptyReport, readCoverageDir, applyCoverageDelta, } from "./harness/coverage.js";
|
|
26
26
|
import { configureBrowserCoverage } from "./browser-coverage.js";
|
|
27
|
-
import { applyCoverageAdapters, coverageAdapters, coverageReportsMode, validateCoverage, } from "./coverage.js";
|
|
27
|
+
import { NODE_COVERAGE_MARKERS_SUBDIR, NODE_COVERAGE_SCRIPTS_SUBDIR, applyCoverageAdapters, coverageAdapters, coverageReportsMode, validateCoverage, } from "./coverage.js";
|
|
28
28
|
import { serviceForHost as hostToService } from "./harness/browser-coverage.js";
|
|
29
29
|
import { acquirePersistentBrowser, mobileKey } from "./browser.js";
|
|
30
30
|
import { isMobileApp, openPersistentMobile } from "./mobile.js";
|
|
@@ -40,7 +40,7 @@ import { LOG_DELTA_MAX_BYTES, capMiddle, streamDelta } from "./harness/log-delta
|
|
|
40
40
|
import { resolveHostPath as resolveVolumeHostPath, sanitizeSegment, } from "./harness/volume-paths.js";
|
|
41
41
|
import { pollUntilReady } from "./harness/ready-poll.js";
|
|
42
42
|
import { runWrapperRules } from "./harness/wrapper-rules.js";
|
|
43
|
-
import { cpus
|
|
43
|
+
import { cpus } from "node:os";
|
|
44
44
|
import { APP_DIR, WORKSPACE, resolveExistingProjectPath, resolveProjectPath } from "./project-files.js";
|
|
45
45
|
import { installFetchWrapper, isTransportError } from "./harness/fetch.js";
|
|
46
46
|
import { bindInstrumentationScope, createInstrumentationScope, runInstrumented, runUninstrumented, } from "./harness/instrumentation-scope.js";
|
|
@@ -688,10 +688,15 @@ async function ensureCoverage(svc) {
|
|
|
688
688
|
return [];
|
|
689
689
|
validateCoverage(svc.name, svc.coverage);
|
|
690
690
|
const host = coverageHostDir(WORKSPACE, svc.name);
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
//
|
|
694
|
-
|
|
691
|
+
// World-writable, the subdirectories the node() hook writes into too:
|
|
692
|
+
// the container's processes run as whatever user the image says, and
|
|
693
|
+
// a directory the first of them created would be closed to the next.
|
|
694
|
+
for (const d of [host, path.join(host, NODE_COVERAGE_SCRIPTS_SUBDIR), path.join(host, NODE_COVERAGE_MARKERS_SUBDIR)]) {
|
|
695
|
+
await fs.mkdir(d, { recursive: true });
|
|
696
|
+
await fs.chmod(d, 0o777);
|
|
697
|
+
}
|
|
698
|
+
// Nothing else is mounted: nothing runs in the container for coverage
|
|
699
|
+
// beyond the hook in the process itself.
|
|
695
700
|
return [`--volume=${host}:${COVERAGE_CONTAINER_DIR}`];
|
|
696
701
|
}
|
|
697
702
|
/** Keyed by image ID, not tag: `spectest/<svc>:latest` is retagged onto
|
|
@@ -3392,24 +3397,6 @@ function coverageCaptureContext(svc, signal, written) {
|
|
|
3392
3397
|
await fs.rename(tmp, file);
|
|
3393
3398
|
written.add(path.relative(reportDir, file));
|
|
3394
3399
|
},
|
|
3395
|
-
async readContainerFile(containerPath) {
|
|
3396
|
-
// `docker cp` rather than `exec cat`: a source map can be tens of
|
|
3397
|
-
// MB, and the exec path buffers stdout as a string. The copy lands
|
|
3398
|
-
// in the VM's tmp, never in the coverage directory (it would ship).
|
|
3399
|
-
const tmp = path.join(tmpdir(), `spectest-cov-${svc.name}-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
3400
|
-
try {
|
|
3401
|
-
const r = await docker(["cp", `${svc.name}:${containerPath}`, tmp], COVERAGE_CAPTURE_TIMEOUT_MS);
|
|
3402
|
-
if (r.code !== 0)
|
|
3403
|
-
return null;
|
|
3404
|
-
return await fs.readFile(tmp);
|
|
3405
|
-
}
|
|
3406
|
-
catch {
|
|
3407
|
-
return null;
|
|
3408
|
-
}
|
|
3409
|
-
finally {
|
|
3410
|
-
await fs.rm(tmp, { force: true, recursive: true }).catch(() => { });
|
|
3411
|
-
}
|
|
3412
|
-
},
|
|
3413
3400
|
};
|
|
3414
3401
|
}
|
|
3415
3402
|
/** Run one service's adapters in order under one budget. Returns the
|
|
@@ -62,7 +62,12 @@ export declare function isLcovShaped(text: string): boolean;
|
|
|
62
62
|
* can be megabytes); the control plane parses for real.
|
|
63
63
|
*/
|
|
64
64
|
export declare function isV8CoverageJson(text: string): boolean;
|
|
65
|
-
/** True when `text` is a
|
|
65
|
+
/** True when `text` is a script record the `node()` hook writes
|
|
66
|
+
* (`scripts/<id>.json`: `{ "spectestScript": { url, lineLengths,
|
|
67
|
+
* functions, sourceMap } }`), the static side of its dumps. */
|
|
68
|
+
export declare function isSpectestScriptRecord(text: string): boolean;
|
|
69
|
+
/** True when `text` is a report spectest stores: lcov, V8 JSON, or a
|
|
70
|
+
* node() script record. */
|
|
66
71
|
export declare function isWellFormedReport(text: string): boolean;
|
|
67
72
|
/** True when a well-formed report names no source file at all (an lcov
|
|
68
73
|
* with no `SF:`, a V8 document with an empty `result`). */
|
package/dist/harness/coverage.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
// A service that opts in (`coverage: true | { command }`) gets a writable
|
|
4
4
|
// directory bind-mounted at `/spectest/coverage/` in its container. The
|
|
5
5
|
// service's own tool, or its coverage adapters, write reports there —
|
|
6
|
-
// lcov,
|
|
6
|
+
// lcov, the V8 coverage JSON shape (what `coverage.node()`'s hook or
|
|
7
|
+
// `NODE_V8_COVERAGE` write), or a node() script record. The daemon reads the
|
|
7
8
|
// directory from the VM side (no `docker exec` to fetch) after bring-up
|
|
8
9
|
// and after each test, and ships the reports **verbatim** to the control
|
|
9
10
|
// plane as a gzipped bundle. Nothing is interpreted here beyond the one
|
|
@@ -62,9 +63,16 @@ export function isLcovShaped(text) {
|
|
|
62
63
|
export function isV8CoverageJson(text) {
|
|
63
64
|
return /^\s*\{/.test(text) && /"result"\s*:\s*\[/.test(text);
|
|
64
65
|
}
|
|
65
|
-
/** True when `text` is a
|
|
66
|
+
/** True when `text` is a script record the `node()` hook writes
|
|
67
|
+
* (`scripts/<id>.json`: `{ "spectestScript": { url, lineLengths,
|
|
68
|
+
* functions, sourceMap } }`), the static side of its dumps. */
|
|
69
|
+
export function isSpectestScriptRecord(text) {
|
|
70
|
+
return /^\s*\{\s*"spectestScript"\s*:/.test(text);
|
|
71
|
+
}
|
|
72
|
+
/** True when `text` is a report spectest stores: lcov, V8 JSON, or a
|
|
73
|
+
* node() script record. */
|
|
66
74
|
export function isWellFormedReport(text) {
|
|
67
|
-
return isLcovShaped(text) || isV8CoverageJson(text);
|
|
75
|
+
return isLcovShaped(text) || isV8CoverageJson(text) || isSpectestScriptRecord(text);
|
|
68
76
|
}
|
|
69
77
|
/** True when a well-formed report names no source file at all (an lcov
|
|
70
78
|
* with no `SF:`, a V8 document with an empty `result`). */
|