@specific.dev/spectest 0.79.2 → 0.81.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 +111 -69
- package/dist/coverage.js +280 -171
- package/dist/daemon.js +24 -14
- package/package.json +1 -1
- package/src/coverage.test.ts +198 -131
- package/src/coverage.ts +320 -177
- package/src/daemon.ts +20 -16
package/dist/coverage.d.ts
CHANGED
|
@@ -8,22 +8,11 @@ export declare const NODE_COVERAGE_EMPTY_REPORT = "coverage-spectest-empty.json"
|
|
|
8
8
|
/** Prefix of the per-process sockets the node hook answers on, relative
|
|
9
9
|
* to the coverage dir: `.ctl-<pid>`. */
|
|
10
10
|
export declare const NODE_COVERAGE_SOCKET_PREFIX = ".ctl-";
|
|
11
|
-
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
|
|
15
|
-
export declare const
|
|
16
|
-
/** Where the container sees {@link NODE_COVERAGE_TOOLS_DIR}. */
|
|
17
|
-
export declare const NODE_COVERAGE_TOOLS_CONTAINER_DIR = "/spectest/coverage-tools";
|
|
18
|
-
/** The `c8` entry point, relative to the tools dir. */
|
|
19
|
-
export declare const NODE_COVERAGE_C8_BIN = "node_modules/c8/bin/c8.js";
|
|
20
|
-
/** The hidden subdirectory of the coverage dir where `node()` parks the
|
|
21
|
-
* V8 dumps it converts at one capture. Hidden, so the harness never
|
|
22
|
-
* ships them: the lcov is the report. Emptied at every capture — the
|
|
23
|
-
* lcov is this capture's delta, see {@link convertV8ReportsToLcov}. */
|
|
24
|
-
export declare const NODE_COVERAGE_DUMPS_SUBDIR = ".v8";
|
|
25
|
-
/** The lcov `node()` writes: the dumps of this capture, merged. */
|
|
26
|
-
export declare const NODE_COVERAGE_LCOV = "node.lcov";
|
|
11
|
+
/** Key of the content hash (`sha256:<hex>`) a shipped `source-map-cache`
|
|
12
|
+
* entry carries, and key of the stub that stands in for an entry that
|
|
13
|
+
* shipped earlier on the same branch. See {@link compactV8Document}. */
|
|
14
|
+
export declare const SOURCE_MAP_HASH_KEY = "spectestHash";
|
|
15
|
+
export declare const SOURCE_MAP_REF_KEY = "spectestRef";
|
|
27
16
|
/** What `configure` learns about the service it rewrites. */
|
|
28
17
|
export interface CoverageConfigureInfo {
|
|
29
18
|
/** The services-map key. */
|
|
@@ -64,6 +53,9 @@ export interface CoverageCaptureContext {
|
|
|
64
53
|
}>;
|
|
65
54
|
/** Write one report file (name relative to the directory). */
|
|
66
55
|
writeReport(name: string, content: string): Promise<void>;
|
|
56
|
+
/** Read one file out of the container (`docker cp`). `null` when it
|
|
57
|
+
* does not exist. Absent on a context that has no container. */
|
|
58
|
+
readContainerFile?(containerPath: string): Promise<Buffer | null>;
|
|
67
59
|
/** Aborts when the per-service capture budget runs out. */
|
|
68
60
|
signal: AbortSignal;
|
|
69
61
|
}
|
|
@@ -146,50 +138,28 @@ export declare const NODE_COVERAGE_HOOK: string;
|
|
|
146
138
|
* capture time — the server, and any wrapper it sits behind (`pnpm exec`,
|
|
147
139
|
* `tsx`). Nothing for the app to write.
|
|
148
140
|
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
141
|
+
* What ships is the V8 documents themselves, compacted
|
|
142
|
+
* ({@link compactV8Document}): the app's own scripts that ran, and each
|
|
143
|
+
* script's source map once per branch. A map comes from Node's own
|
|
144
|
+
* `source-map-cache` when Node found one through the script's
|
|
145
|
+
* `sourceMappingURL` (it caches maps under NODE_V8_COVERAGE with or
|
|
146
|
+
* without `--enable-source-maps`), else from `<script>.map` next to the
|
|
147
|
+
* script, read out of the container once
|
|
148
|
+
* ({@link resolveSourceMapFromContainer}). The second way is the one to
|
|
149
|
+
* build a short-lived bundled process in: a map Node finds is serialized
|
|
150
|
+
* whole into every exit dump — 12 MB per invocation for a bundled CLI,
|
|
151
|
+
* inside the test's own time — and parsed again by the harness; a map
|
|
152
|
+
* with no comment costs nothing there. Nothing is converted in the VM.
|
|
153
|
+
* A dump is a **delta** by construction: V8 resets its counters at every
|
|
154
|
+
* `takeCoverage()`, so a live server's dump after a test is that test's
|
|
155
|
+
* own execution, and the boot dump (everything loaded) lands in the
|
|
156
|
+
* bring-up capture and nowhere else. The control plane derives lines
|
|
157
|
+
* from the stored documents, off the test run. Converting to lcov in
|
|
158
|
+
* the container (`c8`, SDK 0.60 to 0.79) took seconds per capture per
|
|
159
|
+
* service on a real project, inside the test run; a real project turned
|
|
160
|
+
* coverage off because of it, and the conversion went.
|
|
166
161
|
*/
|
|
167
162
|
export declare function node(): CoverageAdapter;
|
|
168
|
-
/** The VM-side tools dir, after the test override. */
|
|
169
|
-
export declare function nodeCoverageToolsDir(): string;
|
|
170
|
-
/** True when golden (or the override) ships `c8`. The daemon mounts the
|
|
171
|
-
* directory into covered containers on the same check, so what the VM
|
|
172
|
-
* has is what the container sees. */
|
|
173
|
-
export declare function nodeCoverageToolsAvailable(): Promise<boolean>;
|
|
174
|
-
/** The command `convertV8ReportsToLcov` runs in the container. From `/`,
|
|
175
|
-
* so lcov paths come out relative to the root (made absolute after). */
|
|
176
|
-
export declare function nodeCoverageConvertCommand(): string;
|
|
177
|
-
/**
|
|
178
|
-
* Convert the V8 dumps written since the previous capture to one lcov,
|
|
179
|
-
* `node.lcov`. New dumps at the root of the dir are compacted to the
|
|
180
|
-
* app's scripts (their maps and `sourcesContent` kept — `v8-to-istanbul`
|
|
181
|
-
* reads the original sources from there when they are not on disk, the
|
|
182
|
-
* usual shape of a multi-stage image) and moved into `.v8/`, which is
|
|
183
|
-
* emptied first; `c8 report` merges that directory. So the lcov is this
|
|
184
|
-
* capture's delta: what the live processes ran since their last take,
|
|
185
|
-
* plus every process that exited since. No new dump ⇒ `TN:` alone,
|
|
186
|
-
* nothing ran. The `SF:` paths come out relative to `/` and are made
|
|
187
|
-
* absolute, so they read as container paths like every other tool's.
|
|
188
|
-
*/
|
|
189
|
-
export declare function convertV8ReportsToLcov(ctx: CoverageCaptureContext): Promise<void>;
|
|
190
|
-
/** `SF:` records relative to `/` (what `c8` run from `/` writes) made
|
|
191
|
-
* absolute. An lcov with nothing in it becomes the empty report. */
|
|
192
|
-
export declare function absoluteLcovPaths(lcov: string): string;
|
|
193
163
|
/**
|
|
194
164
|
* Ask every node process that holds a hook socket in `dir` for a dump.
|
|
195
165
|
* A socket nobody answers (its process died without unlinking — SIGKILL,
|
|
@@ -197,27 +167,99 @@ export declare function absoluteLcovPaths(lcov: string): string;
|
|
|
197
167
|
* answered with an error throws.
|
|
198
168
|
*/
|
|
199
169
|
export declare function dumpAllNodeProcesses(dir: string, signal: AbortSignal): Promise<number>;
|
|
170
|
+
/** Forget the branch memory (tests). */
|
|
171
|
+
export declare function resetNodeCoverageMemory(): void;
|
|
172
|
+
/** Remove from `dir` the reports an earlier capture shipped. */
|
|
173
|
+
export declare function removeShippedV8Reports(dir: string): Promise<void>;
|
|
200
174
|
/** A script is the app's own when it is a file outside node_modules —
|
|
201
175
|
* and not our hook, which every dump would otherwise carry. */
|
|
202
176
|
export declare function isAppScriptUrl(url: string): boolean;
|
|
177
|
+
interface V8Range {
|
|
178
|
+
count?: unknown;
|
|
179
|
+
}
|
|
180
|
+
interface V8Function {
|
|
181
|
+
ranges?: V8Range[];
|
|
182
|
+
}
|
|
183
|
+
interface V8Script {
|
|
184
|
+
url?: unknown;
|
|
185
|
+
functions?: V8Function[];
|
|
186
|
+
}
|
|
187
|
+
/** True when any range of any function of the script has a count above
|
|
188
|
+
* zero: something in it ran since the previous take. A script whose
|
|
189
|
+
* every count is zero is one V8 still lists after a reset; it says
|
|
190
|
+
* nothing and is dropped. */
|
|
191
|
+
export declare function scriptExecuted(script: V8Script): boolean;
|
|
203
192
|
/**
|
|
204
|
-
* Compact one V8 coverage document to the
|
|
205
|
-
* writes is everything the process loaded: `node:` internals, every
|
|
193
|
+
* Compact one V8 coverage document to what the derivation needs. What
|
|
194
|
+
* Node writes is everything the process loaded: `node:` internals, every
|
|
206
195
|
* `node_modules` file, and — under `--enable-source-maps` — a
|
|
207
196
|
* `source-map-cache` with each file's full map **and its sources**,
|
|
208
197
|
* repeated in every dump. Measured on a real project: a 12–20 MiB dump
|
|
209
198
|
* per capture, of which the app's own coverage was under 0.5 MiB, and a
|
|
210
199
|
* suite that hit the 64 MiB cap on its third test. Kept: `file://`
|
|
211
|
-
* scripts outside `node_modules
|
|
212
|
-
* scripts, minus `sourcesContent` (the sources
|
|
213
|
-
*
|
|
214
|
-
*
|
|
200
|
+
* scripts outside `node_modules` in which something ran, and the map
|
|
201
|
+
* entries of exactly those scripts, minus `sourcesContent` (the sources
|
|
202
|
+
* are the repo). Each kept map entry carries its content hash under
|
|
203
|
+
* {@link SOURCE_MAP_HASH_KEY}; when `shipped` holds the same hash for the
|
|
204
|
+
* URL — the map went out with an earlier dump on this branch — a stub
|
|
205
|
+
* `{ [SOURCE_MAP_REF_KEY]: hash }` stands in for it, and the reader finds
|
|
206
|
+
* the map in an earlier capture of the branch. `shipped` is updated in
|
|
207
|
+
* place. Still a V8 document — nothing is converted here.
|
|
208
|
+
*/
|
|
209
|
+
export declare function compactV8Document(doc: Record<string, unknown>, shipped?: Map<string, string>): Record<string, unknown>;
|
|
210
|
+
/** What {@link prepareV8Reports} did, for the capture's log line. */
|
|
211
|
+
export interface PrepareV8Stats {
|
|
212
|
+
dumps: number;
|
|
213
|
+
scripts: number;
|
|
214
|
+
bytesIn: number;
|
|
215
|
+
bytesOut: number;
|
|
216
|
+
/** Source maps read out of the container this capture. */
|
|
217
|
+
mapsFromContainer: number;
|
|
218
|
+
}
|
|
219
|
+
/** One `source-map-cache` entry in the shape Node writes: the map (its
|
|
220
|
+
* `sourcesContent` dropped), the generated script's line lengths, and
|
|
221
|
+
* the map's own URL. */
|
|
222
|
+
export interface SourceMapEntry {
|
|
223
|
+
url: string | null;
|
|
224
|
+
data: Record<string, unknown>;
|
|
225
|
+
lineLengths: number[];
|
|
226
|
+
}
|
|
227
|
+
/** Finds the source map of a script the dump carries no map for.
|
|
228
|
+
* `null` when there is none. */
|
|
229
|
+
export type SourceMapResolver = (scriptUrl: string) => Promise<SourceMapEntry | null>;
|
|
230
|
+
/** The length of every line of `text`, the way Node computes it for
|
|
231
|
+
* `source-map-cache`: split on `\n` (and U+2028/2029), `\r` kept, the
|
|
232
|
+
* last line included. Lengths in UTF-16 units, which is what V8's byte
|
|
233
|
+
* offsets count in. */
|
|
234
|
+
export declare function lineLengthsOf(text: string): number[];
|
|
235
|
+
/** The last `sourceMappingURL` comment of a script, or `null`. */
|
|
236
|
+
export declare function sourceMappingUrlOf(script: string): string | null;
|
|
237
|
+
/**
|
|
238
|
+
* The source map of `scriptUrl`, read out of the container: the script
|
|
239
|
+
* itself (for its `sourceMappingURL` and its line lengths), then the map
|
|
240
|
+
* — inline as a `data:` URL, a file the comment names, or, with no
|
|
241
|
+
* comment, `<script>.map` next to it. `null` when the script is not a
|
|
242
|
+
* `file://` URL, cannot be read, or the map is not there or not JSON; a
|
|
243
|
+
* map's `sourcesContent` is dropped.
|
|
244
|
+
* One read per script per branch: the caller remembers the outcome.
|
|
245
|
+
*/
|
|
246
|
+
export declare function resolveSourceMapFromContainer(ctx: CoverageCaptureContext, scriptUrl: string): Promise<SourceMapEntry | null>;
|
|
247
|
+
/**
|
|
248
|
+
* Give every kept script of a compacted document a `source-map-cache`
|
|
249
|
+
* entry it lacks: a stub when the map shipped earlier on this branch,
|
|
250
|
+
* else the map `resolve` finds (shipped whole, with its hash, and
|
|
251
|
+
* remembered), else nothing — and that outcome is remembered too, so a
|
|
252
|
+
* script with no map is looked up once per branch. Returns how many maps
|
|
253
|
+
* `resolve` supplied.
|
|
254
|
+
*/
|
|
255
|
+
export declare function attachSourceMaps(doc: Record<string, unknown>, resolve: SourceMapResolver, shipped?: Map<string, string>, missing?: Set<string>): Promise<number>;
|
|
256
|
+
/**
|
|
257
|
+
* Compact, in place, every `coverage-*.json` in `dir` that no earlier
|
|
258
|
+
* capture shipped, attach the maps the dump lacks through `resolve`
|
|
259
|
+
* ({@link attachSourceMaps}), and mark it shipped. A dump mid-write (not
|
|
260
|
+
* yet valid JSON) is left for the next capture.
|
|
215
261
|
*/
|
|
216
|
-
export declare function
|
|
217
|
-
keepSourcesContent?: boolean;
|
|
218
|
-
}): Record<string, unknown>;
|
|
219
|
-
/** Compact every not-yet-compacted V8 document in `dir`, in place. */
|
|
220
|
-
export declare function compactV8Reports(dir: string): Promise<void>;
|
|
262
|
+
export declare function prepareV8Reports(dir: string, resolve?: SourceMapResolver): Promise<PrepareV8Stats>;
|
|
221
263
|
/**
|
|
222
264
|
* Coverage for the frontend a service serves. The code runs in the guest
|
|
223
265
|
* browser — spectest's own process — so no report can be written by the
|