@specific.dev/spectest 0.81.1 → 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/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, tmpdir } from "node:os";
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
- await fs.mkdir(host, { recursive: true });
692
- await fs.chmod(host, 0o777);
693
- // Nothing else is mounted: no conversion runs in the container (the
694
- // c8 tools mount went with SDK 0.80).
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 report spectest stores: lcov or V8 JSON. */
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`). */
@@ -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, or the V8 coverage JSON that `NODE_V8_COVERAGE` writes. The daemon reads the
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 report spectest stores: lcov or V8 JSON. */
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`). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specific.dev/spectest",
3
- "version": "0.81.1",
3
+ "version": "0.82.0",
4
4
  "description": "Spectest SDK for defining test environments in TypeScript.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",