@jterrazz/test 17.0.0 → 18.0.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.
Files changed (53) hide show
  1. package/dist/browser/index.js +1 -2
  2. package/dist/browser/services.js +1 -2
  3. package/dist/browser/stubs.js +1 -2
  4. package/dist/{shared-index.browser.js → browser-shared-index.browser.js} +82 -40
  5. package/dist/checker.js +15 -824
  6. package/dist/in-place.global-setup.d.ts +1 -0
  7. package/dist/in-place.global-setup.js +1 -1
  8. package/dist/index.d.ts +2 -14
  9. package/dist/index.js +83 -204
  10. package/dist/mobile-session.global-setup.d.ts +1 -0
  11. package/dist/oxlint.cjs +2 -2
  12. package/dist/oxlint.js +67 -2321
  13. package/dist/route-coverage.global-setup.d.ts +1 -0
  14. package/dist/route-coverage.global-setup.js +1 -2
  15. package/dist/services.global-setup.d.ts +1 -1
  16. package/dist/services.global-setup.js +1 -2
  17. package/dist/services.js +1 -1
  18. package/dist/shared-appium.adapter.js +14 -14
  19. package/dist/shared-artifacts.js +7 -5
  20. package/dist/shared-derived-projects.js +23 -8
  21. package/dist/shared-in-place.js +1 -1
  22. package/dist/shared-ledger.js +1 -1
  23. package/dist/shared-listened-port.js +19 -0
  24. package/dist/shared-literate-plugin.js +2 -1
  25. package/dist/shared-pause.js +12 -1
  26. package/dist/shared-playwright.adapter.js +17 -19
  27. package/dist/shared-process-alive.js +13 -0
  28. package/dist/shared-record-key.js +13 -0
  29. package/dist/shared-serve.js +180 -69
  30. package/dist/shared-services.global-setup.js +4 -9
  31. package/dist/shared-session-ledger.js +10 -6
  32. package/dist/shared-spec-document.js +2 -64
  33. package/dist/shared-spec-grammar.js +66 -0
  34. package/dist/shared-specs-modules.js +1033 -0
  35. package/dist/{shared-stream-body.js → shared-stub-reply.js} +40 -3
  36. package/dist/shared-stub-server.js +30 -78
  37. package/dist/shared-temp-root.global-setup.js +3 -10
  38. package/dist/shared-testcontainers.adapter.js +111 -8
  39. package/dist/temp-root.global-setup.js +1 -2
  40. package/dist/vitest.d.ts +17 -22
  41. package/dist/vitest.js +37 -53
  42. package/docs/concepts/architecture.md +20 -20
  43. package/docs/concepts/conventions.md +2 -2
  44. package/docs/guides/cli-specs.md +2 -0
  45. package/docs/guides/develop.md +20 -17
  46. package/docs/guides/mobile-specs.md +3 -3
  47. package/docs/guides/release.md +4 -3
  48. package/docs/guides/website-specs.md +1 -1
  49. package/docs/reference/linting.md +2 -2
  50. package/docs/reference/services.md +13 -9
  51. package/docs/reference/stubs.md +4 -2
  52. package/docs/start/set-up-a-project.md +31 -17
  53. package/package.json +10 -10
@@ -1,10 +1,13 @@
1
- import { n as refusal, t as invariant } from "./shared-refusal.js";
2
- import { t as pause } from "./shared-pause.js";
1
+ import { n as refusal } from "./shared-refusal.js";
2
+ import { n as untilDeadline } from "./shared-pause.js";
3
+ import "./shared-listened-port.js";
3
4
  import "node:module";
4
5
  import { resolve } from "node:path";
5
6
  import { execSync, spawn, spawnSync } from "node:child_process";
6
- import { Socket, createServer } from "node:net";
7
+ import { randomInt } from "node:crypto";
8
+ import { Socket, connect } from "node:net";
7
9
  import { once } from "node:events";
10
+ import { createSocket } from "node:dgram";
8
11
  //#region \0rolldown/runtime.js
9
12
  var __defProp = Object.defineProperty;
10
13
  var __exportAll = (all, no_symbols) => {
@@ -18,24 +21,6 @@ var __exportAll = (all, no_symbols) => {
18
21
  };
19
22
  //#endregion
20
23
  //#region src/services/process/listening.ts
21
- async function findFreePort() {
22
- return await new Promise((resolve, reject) => {
23
- const server = createServer();
24
- server.listen(0, () => {
25
- const address = server.address();
26
- if (address === null || typeof address === "string") {
27
- server.close();
28
- reject(invariant("a server listening on port 0 answered no port"));
29
- return;
30
- }
31
- const { port } = address;
32
- server.close(() => {
33
- resolve(port);
34
- });
35
- });
36
- server.on("error", reject);
37
- });
38
- }
39
24
  async function firstAnswering(urls) {
40
25
  return (await Promise.all(urls.map(async (url) => await answers(url) ? url : void 0))).find((url) => url !== void 0);
41
26
  }
@@ -47,6 +32,44 @@ async function answers(url) {
47
32
  return false;
48
33
  }
49
34
  }
35
+ const LOOPBACK = ["127.0.0.1", "::1"];
36
+ async function holdingHost(port) {
37
+ return (await Promise.all(LOOPBACK.map(async (host) => await accepts(host, port) ? host : void 0))).find((host) => host !== void 0);
38
+ }
39
+ async function accepts(host, port) {
40
+ return await new Promise((resolve) => {
41
+ const socket = connect({
42
+ host,
43
+ port
44
+ });
45
+ socket.once("connect", () => {
46
+ socket.destroy();
47
+ resolve(true);
48
+ });
49
+ socket.once("error", () => {
50
+ resolve(false);
51
+ });
52
+ });
53
+ }
54
+ //#endregion
55
+ //#region src/services/process/output-change.ts
56
+ /** Wakes one waiter when the output of a process grows or the process closes. */
57
+ var OutputChange = class {
58
+ waiting;
59
+ notify() {
60
+ this.waiting?.();
61
+ }
62
+ /** Resolves on the next change, or after `withinMs` when none comes. */
63
+ async next(withinMs) {
64
+ await new Promise((resolve) => {
65
+ const late = setTimeout(resolve, withinMs);
66
+ this.waiting = () => {
67
+ clearTimeout(late);
68
+ resolve();
69
+ };
70
+ });
71
+ }
72
+ };
50
73
  //#endregion
51
74
  //#region src/services/process/port-in-use.ts
52
75
  const PORT_IN_USE = /EADDRINUSE|address already in use|port \d+ is already in use/iu;
@@ -58,6 +81,80 @@ function lessonOf(reason, output, port) {
58
81
  why: "A process is handed a free port unless `port` pins one, and a port another process holds is not free."
59
82
  };
60
83
  }
84
+ async function refuseHeldPort(port, specification, command) {
85
+ if (port === void 0) return;
86
+ const host = await holdingHost(port);
87
+ if (host === void 0) return;
88
+ throw new Error(refusal({
89
+ fix: `stop the process holding port ${String(port)}, or drop the \`port\` option so a free one is picked`,
90
+ found: `specification.${specification}(): port ${String(port)}, which \`port\` pins, is already held: something answers on ${host} before the server is started.\nCommand: ${command}`,
91
+ read: "docs/reference/services.md#process-the-one-shape-an-external-process-takes",
92
+ why: "A process pinned to a port is ready once that port answers, and a port another process holds would answer in its place."
93
+ }));
94
+ }
95
+ //#endregion
96
+ //#region src/services/process/port-reservation.ts
97
+ const DRAWN_PORTS = {
98
+ first: 1e4,
99
+ last: 32767
100
+ };
101
+ function draw() {
102
+ return randomInt(DRAWN_PORTS.first, DRAWN_PORTS.last + 1);
103
+ }
104
+ async function claimPort(port) {
105
+ const socket = createSocket("udp4");
106
+ const failure = await new Promise((resolve) => {
107
+ socket.on("error", resolve);
108
+ socket.bind({
109
+ address: "127.0.0.1",
110
+ port
111
+ }, () => {
112
+ resolve(void 0);
113
+ });
114
+ });
115
+ if (failure === void 0) {
116
+ socket.unref();
117
+ return {
118
+ port,
119
+ release: closing(socket)
120
+ };
121
+ }
122
+ socket.close();
123
+ return failure instanceof Error && "code" in failure && failure.code === "EADDRINUSE" ? void 0 : {
124
+ port,
125
+ release: () => void 0
126
+ };
127
+ }
128
+ function closing(socket) {
129
+ let open = true;
130
+ return () => {
131
+ if (open) {
132
+ open = false;
133
+ socket.close();
134
+ }
135
+ };
136
+ }
137
+ async function reserveFreePort(pick = draw) {
138
+ for (let attempt = 0; attempt < 64; attempt += 1) {
139
+ const reservation = await claimPort(pick());
140
+ if (reservation === void 0) continue;
141
+ if (await holdingHost(reservation.port) === void 0) return reservation;
142
+ reservation.release();
143
+ }
144
+ }
145
+ /** The port a served process is handed (the one `port` pins, else a drawn one) reserved for it, or the lesson of why none could be. */
146
+ async function reservePort(pinned, pick = draw) {
147
+ if (pinned !== void 0) return await claimPort(pinned) ?? {
148
+ fix: "drop the `port` option so each process is handed a free one, or run the specifications that pin it one at a time",
149
+ found: `port ${String(pinned)}, which \`port\` pins, is reserved by another served process on this machine`,
150
+ why: "A port is one served process's at a time, and a server sharing it would answer in the other's place."
151
+ };
152
+ return await reserveFreePort(pick) ?? {
153
+ fix: "stop the servers holding the ports of that range, or pin a free one with `port`",
154
+ found: `no free port in ${String(DRAWN_PORTS.first)}-${String(DRAWN_PORTS.last)} after ${String(64)} draws`,
155
+ why: "A process is handed a port drawn below the range the OS hands unasked ports from, and one nothing holds."
156
+ };
157
+ }
61
158
  //#endregion
62
159
  //#region src/services/process/process-group.ts
63
160
  const KILL_GRACE_MS = 2e3;
@@ -127,14 +224,11 @@ function groupRunning(group) {
127
224
  return false;
128
225
  }
129
226
  }
130
- async function endedBy(group, deadline) {
131
- if (!groupRunning(group)) return true;
132
- if (Date.now() >= deadline) return false;
133
- await pause(GROUP_SETTLE_MS);
134
- return await endedBy(group, deadline);
135
- }
136
227
  async function groupEnded(group, withinMs) {
137
- return await endedBy(group, Date.now() + withinMs);
228
+ return await untilDeadline(() => !groupRunning(group) || void 0, {
229
+ interval: GROUP_SETTLE_MS,
230
+ timeout: withinMs
231
+ }) === true;
138
232
  }
139
233
  /** Resolves once the child is reaped, at once for one reaped already. */
140
234
  async function reaped(child) {
@@ -150,6 +244,13 @@ function signalGroup(child, signal = "SIGTERM") {
150
244
  } catch {}
151
245
  return child.kill(signal);
152
246
  }
247
+ async function endGroup(child) {
248
+ if (child.pid === void 0) return;
249
+ const leaderReaped = reaped(child);
250
+ signalGroup(child, "SIGTERM");
251
+ if (!await groupEnded(-child.pid, 2e3)) signalGroup(child, "SIGKILL");
252
+ await leaderReaped;
253
+ }
153
254
  //#endregion
154
255
  //#region src/services/process/serve.ts
155
256
  var serve_exports = /* @__PURE__ */ __exportAll({
@@ -157,7 +258,7 @@ var serve_exports = /* @__PURE__ */ __exportAll({
157
258
  runToCompletion: () => runToCompletion
158
259
  });
159
260
  const DEFAULT_READY_TIMEOUT = 3e4;
160
- const READY_POLL_INTERVAL_MS = 250;
261
+ const READY_POLL_INTERVAL_MS = 25;
161
262
  function watchGroup(groupId, strays) {
162
263
  const args = [
163
264
  "-e",
@@ -198,13 +299,15 @@ function runToCompletion(command, env) {
198
299
  stdio: "pipe"
199
300
  });
200
301
  }
201
- /** Any HTTP status counts as ready. The child runs in its own process group so `stop()` kills a `next start` grandchild too, and a watchdog kills that group when the run dies before `stop()`; both reap the strays it declares, the processes it spawns outside its group. */
302
+ /** Any HTTP status counts as ready, since the port it is handed is reserved for it until `stop()`: no other served process is handed it, and no socket is handed it by chance. The child runs in its own process group so `stop()` kills a `next start` grandchild too, and a watchdog kills that group when the run dies before `stop()`; both reap the strays it declares, the processes it spawns outside its group. */
202
303
  var ServedProcess = class {
203
304
  port = null;
204
305
  child = null;
306
+ change = new OutputChange();
205
307
  extraEnv;
206
308
  specification;
207
309
  options;
310
+ reservation = null;
208
311
  exited = false;
209
312
  output = "";
210
313
  root;
@@ -218,17 +321,25 @@ var ServedProcess = class {
218
321
  this.root = root;
219
322
  }
220
323
  async start() {
324
+ await refuseHeldPort(this.options.port, this.specification, this.options.command);
221
325
  this.runBefore();
222
326
  const banner = this.options.ready instanceof RegExp ? this.options.ready : null;
223
- const port = banner ? null : this.options.port ?? await findFreePort();
327
+ const port = banner ? null : await this.reserve();
224
328
  this.spawnServer(port);
225
- const deadline = Date.now() + this.timeout();
226
- return (banner ? await this.awaitBanner(banner, deadline) : await this.awaitAnswer(port ?? 0, deadline)) ?? await this.refuse({
329
+ return (banner ? await this.awaitBanner(banner) : await this.awaitAnswer(port ?? 0)) ?? await this.refuse({
227
330
  fix: "run the command by hand and correct what its output names",
228
331
  found: "the server command exited before it was ready",
229
332
  why: "A process is handed to the chain once it is ready, and one that exits never is."
230
333
  });
231
334
  }
335
+ async reserve() {
336
+ const reserved = await reservePort(this.options.port);
337
+ if ("release" in reserved) {
338
+ this.reservation = reserved;
339
+ return reserved.port;
340
+ }
341
+ return await this.refuse(reserved);
342
+ }
232
343
  environment(port) {
233
344
  return {
234
345
  ...process.env,
@@ -256,57 +367,62 @@ var ServedProcess = class {
256
367
  if (child.pid !== void 0 && SIGNALS_GROUPS) this.watchdog = watchGroup(child.pid, this.strays);
257
368
  const collect = (data) => {
258
369
  this.output += String(data);
370
+ this.change.notify();
259
371
  };
260
372
  child.stdout?.on("data", collect);
261
373
  child.stderr?.on("data", collect);
262
374
  child.on("close", () => {
263
375
  this.exited = true;
376
+ this.change.notify();
264
377
  });
265
378
  child.on("error", (error) => {
266
379
  collect(`${String(error)}\n`);
267
380
  });
268
381
  }
269
- async awaitBanner(banner, deadline) {
270
- while (!this.exited) {
382
+ async awaitBanner(banner) {
383
+ const seen = await untilDeadline(() => {
384
+ if (this.exited) return "exited";
271
385
  banner.lastIndex = 0;
272
- const found = banner.exec(this.output);
273
- if (found) return await this.bannerPort(banner, found[1]);
274
- if (Date.now() >= deadline) return await this.refuse({
275
- fix: "correct the pattern to what the server prints, or raise `timeout`",
276
- found: `the server did not print ${String(banner)} within ${this.timeout()}ms`,
277
- why: "A process is ready once its output matches `ready`, within `timeout`."
278
- });
279
- await pause(READY_POLL_INTERVAL_MS);
280
- }
281
- return null;
386
+ return banner.exec(this.output) ?? void 0;
387
+ }, {
388
+ timeout: this.timeout(),
389
+ wait: async (remaining) => {
390
+ await this.change.next(remaining);
391
+ }
392
+ });
393
+ if (seen === "exited") return null;
394
+ if (seen === void 0) return await this.refuse({
395
+ fix: "correct the pattern to what the server prints, or raise `timeout`",
396
+ found: `the server did not print ${String(banner)} within ${this.timeout()}ms`,
397
+ why: "A process is ready once its output matches `ready`, within `timeout`."
398
+ });
399
+ return await this.bannerPort(banner, seen[1]);
282
400
  }
283
401
  async bannerPort(banner, group) {
284
402
  const captured = Number(group);
285
403
  if (!Number.isInteger(captured)) return await this.refuse({
286
- fix: "capture the port alone in the pattern's one group, e.g. /listening on (\\d+)/",
404
+ fix: String.raw`capture the port alone in the pattern's one group, e.g. /listening on (\d+)/`,
287
405
  found: `the ready pattern ${String(banner)} matched, and its capture group is not a port ("${group}")`,
288
406
  why: "A RegExp `ready` is read over the output, and its one capture group is the port."
289
407
  });
290
408
  this.port = captured;
291
409
  return `http://127.0.0.1:${captured}`;
292
410
  }
293
- async awaitAnswer(port, deadline) {
411
+ async awaitAnswer(port) {
294
412
  const baseUrls = [`http://127.0.0.1:${port}`, `http://[::1]:${port}`];
295
413
  const path = typeof this.options.ready === "string" ? this.options.ready : "/";
296
- while (!this.exited) {
297
- const answering = await firstAnswering(baseUrls.map((baseUrl) => `${baseUrl}${path}`));
298
- if (answering !== void 0) {
299
- this.port = port;
300
- return answering.slice(0, answering.length - path.length);
301
- }
302
- if (Date.now() >= deadline) return await this.refuse({
303
- fix: "correct `ready` to a path the server answers, have it listen on `PORT`, or raise `timeout`",
304
- found: `the server did not answer on ${baseUrls.map((baseUrl) => `${baseUrl}${path}`).join(" or ")} within ${this.timeout()}ms, whatever address it printed above`,
305
- why: "A process is ready once its `ready` path answers, within `timeout`."
306
- });
307
- await pause(READY_POLL_INTERVAL_MS);
308
- }
309
- return null;
414
+ const seen = await untilDeadline(async () => this.exited ? "exited" : await firstAnswering(baseUrls.map((baseUrl) => `${baseUrl}${path}`)), {
415
+ interval: READY_POLL_INTERVAL_MS,
416
+ timeout: this.timeout()
417
+ });
418
+ if (seen === "exited") return null;
419
+ if (seen === void 0) return await this.refuse({
420
+ fix: "correct `ready` to a path the server answers, have it listen on `PORT`, or raise `timeout`",
421
+ found: `the server did not answer on ${baseUrls.map((baseUrl) => `${baseUrl}${path}`).join(" or ")} within ${this.timeout()}ms, whatever address it printed above`,
422
+ why: "A process is ready once its `ready` path answers, within `timeout`."
423
+ });
424
+ this.port = port;
425
+ return seen.slice(0, seen.length - path.length);
310
426
  }
311
427
  timeout() {
312
428
  return this.options.timeout ?? DEFAULT_READY_TIMEOUT;
@@ -343,6 +459,8 @@ var ServedProcess = class {
343
459
  }));
344
460
  }
345
461
  async stop() {
462
+ this.reservation?.release();
463
+ this.reservation = null;
346
464
  const { child } = this;
347
465
  if (!child) return;
348
466
  this.child = null;
@@ -352,14 +470,7 @@ var ServedProcess = class {
352
470
  this.watchdog = null;
353
471
  }
354
472
  };
355
- async function endGroup(child) {
356
- if (child.pid === void 0) return;
357
- const leaderReaped = reaped(child);
358
- signalGroup(child, "SIGTERM");
359
- if (!await groupEnded(-child.pid, 2e3)) signalGroup(child, "SIGKILL");
360
- await leaderReaped;
361
- }
362
473
  //#endregion
363
- export { signalGroup as a, SIGNALS_GROUPS as i, serve_exports as n, KILL_GRACE_MS as r, ServedProcess as t };
474
+ export { endGroup as a, SIGNALS_GROUPS as i, serve_exports as n, signalGroup as o, KILL_GRACE_MS as r, ServedProcess as t };
364
475
 
365
476
  //# sourceMappingURL=shared-serve.js.map
@@ -1,8 +1,9 @@
1
1
  import { n as refusal } from "./shared-refusal.js";
2
- import { a as field, i as RunContainers, o as serveContainers, r as TestcontainersAdapter, t as OWNER_LABEL } from "./shared-testcontainers.adapter.js";
2
+ import { a as field, i as RunContainers, o as serveContainers, r as TestcontainersAdapter, s as DOCKER_ABSENT, t as OWNER_LABEL } from "./shared-testcontainers.adapter.js";
3
3
  import { t as loadPeer } from "./shared-peer.js";
4
4
  import { t as SERVICE_IMAGES } from "./shared-service-images.js";
5
5
  import { t as setupEntry } from "./shared-setup-entry.js";
6
+ import { t as processAlive } from "./shared-process-alive.js";
6
7
  import { existsSync, readFileSync } from "node:fs";
7
8
  import { dirname, join } from "node:path";
8
9
  import { hostname } from "node:os";
@@ -15,12 +16,7 @@ function ownerAlive(owner, host = hostname(), probe = (pid) => process.kill(pid,
15
16
  const separator = owner.lastIndexOf(":");
16
17
  const pid = Number(owner.slice(separator + 1));
17
18
  if (separator === -1 || owner.slice(0, separator) !== host || !Number.isSafeInteger(pid) || pid <= 0) return true;
18
- try {
19
- probe(pid);
20
- return true;
21
- } catch (error) {
22
- return Reflect.get(new Object(error), "code") !== "ESRCH";
23
- }
19
+ return processAlive(pid, probe);
24
20
  }
25
21
  function orphans(containers, alive) {
26
22
  const reaped = new Set(containers.filter((container) => container.State === "running" && container.Labels[REAPER_LABEL] === "true").map((reaper) => reaper.Labels[SESSION_LABEL]));
@@ -50,7 +46,6 @@ async function sweepOrphans(runtime, alive = ownerAlive) {
50
46
  //#endregion
51
47
  //#region src/services/containers/services.global-setup.ts
52
48
  /** Every project's setup joins the one run's containers. Default images are pulled in the background, so the first file waits on a pull at most once; a project declaring no service needs no Docker. */
53
- const DOCKER_ABSENT = "no Docker daemon answered";
54
49
  /** `connect` stands in for the runtime's own client lookup, so the refusal is reachable without a daemon to stop. */
55
50
  async function prepareDocker(connect) {
56
51
  const { getContainerRuntimeClient, ImageName } = await loadPeer("testcontainers", "a declared service", async () => await import("testcontainers"));
@@ -174,6 +169,6 @@ async function setup(project, seams = {}) {
174
169
  return joined.leave;
175
170
  }
176
171
  //#endregion
177
- export { setup as i, prepareDocker as n, servicesSetup as r, DOCKER_ABSENT as t };
172
+ export { servicesSetup as n, setup as r, prepareDocker as t };
178
173
 
179
174
  //# sourceMappingURL=shared-services.global-setup.js.map
@@ -1,7 +1,7 @@
1
1
  import { t as ServedProcess } from "./shared-serve.js";
2
2
  import { t as isRecord } from "./shared-json-shape.js";
3
3
  import { n as refusal } from "./shared-refusal.js";
4
- import { l as VITEST_ARTIFACTS_DIR } from "./shared-artifacts.js";
4
+ import { u as VITEST_ARTIFACTS_DIR } from "./shared-artifacts.js";
5
5
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
6
6
  import { join, resolve } from "node:path";
7
7
  import { execFile } from "node:child_process";
@@ -227,9 +227,13 @@ async function reachAppium({ appium: declared, device }, root, run) {
227
227
  //#region src/specifications/mobile/session/session-ledger.ts
228
228
  const SERVER_CALL_TIMEOUT_MS = 1e4;
229
229
  const MOBILE_SESSIONS_DIR = `${VITEST_ARTIFACTS_DIR}/mobile-sessions`;
230
- function keyOf(serverUrl, requested) {
230
+ function keyOf(serverUrl, requested, worker) {
231
231
  const sorted = Object.entries(requested).toSorted(([a], [b]) => a.localeCompare(b));
232
- return JSON.stringify([serverUrl, sorted]);
232
+ return JSON.stringify([
233
+ serverUrl,
234
+ sorted,
235
+ worker
236
+ ]);
233
237
  }
234
238
  function isEntry(value) {
235
239
  return isRecord(value) && [
@@ -249,13 +253,13 @@ function entries(directory) {
249
253
  const parsed = (name) => JSON.parse(readFileSync(join(directory, name), "utf8"));
250
254
  return entryFiles(directory).map((name) => parsed(name)).filter((entry) => isEntry(entry));
251
255
  }
252
- function sessionLedger(directory) {
256
+ function sessionLedger(directory, worker = process.env.VITEST_POOL_ID ?? "") {
253
257
  return {
254
258
  forget: (sessionId) => {
255
259
  rmSync(join(directory, `${sessionId}.json`), { force: true });
256
260
  },
257
261
  lookup: (serverUrl, requested) => {
258
- const key = keyOf(serverUrl, requested);
262
+ const key = keyOf(serverUrl, requested, worker);
259
263
  return entries(directory).filter((entry) => entry.key === key).map(({ capabilities, serverUrl: url, sessionId }) => ({
260
264
  capabilities,
261
265
  serverUrl: url,
@@ -265,7 +269,7 @@ function sessionLedger(directory) {
265
269
  record: (requested, session) => {
266
270
  const entry = {
267
271
  ...session,
268
- key: keyOf(session.serverUrl, requested)
272
+ key: keyOf(session.serverUrl, requested, worker)
269
273
  };
270
274
  mkdirSync(directory, { recursive: true });
271
275
  writeFileSync(join(directory, `${session.sessionId}.json`), `${JSON.stringify(entry)}\n`);
@@ -1,10 +1,6 @@
1
+ import { a as RUN_KEYS, c as isRelativePath, i as PRESENCE, n as FILE_CONTENT_KEYS, r as FILE_STATES, s as SpecSyntaxError, t as DOCUMENT_KEYS } from "./shared-spec-grammar.js";
1
2
  import { a as isPair, c as nodeItems, f as yamlSyntaxErrors, i as isNode, l as nodePairs, o as isScalar, r as isMap, s as isSeq, u as parseYamlSource } from "./shared-yaml-document.js";
2
3
  import { GROUND_FIXTURES, isGroundFolder } from "@jterrazz/typescript/rules";
3
- //#region lint/kit/project/relative-path.ts
4
- function isRelativePath(value) {
5
- return value.startsWith("./") || value.startsWith("../");
6
- }
7
- //#endregion
8
4
  //#region lint/kit/project/dedent.ts
9
5
  function indentOf(line) {
10
6
  return /^[\t ]*/u.exec(line)?.[0].length ?? 0;
@@ -19,64 +15,6 @@ function dedent(text) {
19
15
  return lines.map((line) => line.slice(common)).join("\n");
20
16
  }
21
17
  //#endregion
22
- //#region lint/kit/project/spec-grammar.ts
23
- const SPEC_EXTENSION = ".spec.yaml";
24
- const DOCUMENT_KEYS = [
25
- "description",
26
- "fixture",
27
- "env",
28
- "serve",
29
- "runs"
30
- ];
31
- const RUN_KEYS = [
32
- "command",
33
- "stdin",
34
- "timeout",
35
- "waitFor",
36
- "exit",
37
- "stdout",
38
- "stderr",
39
- "files"
40
- ];
41
- const FILE_CONTENT_KEYS = ["contains", "equals"];
42
- const FILE_STATES = ["absent", "exists"];
43
- const PRESENCE = {
44
- document: [{
45
- keys: ["description"],
46
- law: "each",
47
- refusal: () => "missing \"description:\": every document is one test, and this is its title"
48
- }, {
49
- keys: ["runs"],
50
- law: "each",
51
- refusal: () => "missing \"runs:\": description and runs are mandatory"
52
- }],
53
- files: [{
54
- keys: FILE_CONTENT_KEYS,
55
- law: "some",
56
- refusal: (path) => `files: ${path} asserts nothing: give it contains: or equals:`
57
- }],
58
- run: [{
59
- keys: ["command", "exit"],
60
- law: "each",
61
- refusal: (_subject, key) => `a run is missing "${key}:": command and exit are mandatory`
62
- }, {
63
- keys: ["waitFor"],
64
- law: "apart",
65
- refusal: () => "waitFor: and stdin: cannot share a run: the pattern is watched on a process nobody closed the input of",
66
- with: "stdin"
67
- }]
68
- };
69
- var SpecSyntaxError = class extends Error {
70
- line;
71
- reason;
72
- constructor(fileName, line, message) {
73
- super(`${fileName}:${line}: ${message}`);
74
- this.line = line;
75
- this.reason = message;
76
- this.name = "SpecSyntaxError";
77
- }
78
- };
79
- //#endregion
80
18
  //#region lint/kit/project/spec-node.ts
81
19
  function fail(context, node, message) {
82
20
  throw new SpecSyntaxError(context.fileName, lineOf(context, node), message);
@@ -360,6 +298,6 @@ function parseSpecDocument(content, fileName) {
360
298
  return readSpecFile(content, fileName).document;
361
299
  }
362
300
  //#endregion
363
- export { scalarString as a, SPEC_EXTENSION as c, isRelativePath as d, readSpecFile as i, SpecSyntaxError as l, documentPaths as n, DOCUMENT_KEYS as o, parseSpecDocument as r, RUN_KEYS as s, assertedStreams as t, dedent as u };
301
+ export { scalarString as a, readSpecFile as i, documentPaths as n, dedent as o, parseSpecDocument as r, assertedStreams as t };
364
302
 
365
303
  //# sourceMappingURL=shared-spec-document.js.map
@@ -0,0 +1,66 @@
1
+ //#region lint/kit/project/relative-path.ts
2
+ function isRelativePath(value) {
3
+ return value.startsWith("./") || value.startsWith("../");
4
+ }
5
+ //#endregion
6
+ //#region lint/kit/project/spec-grammar.ts
7
+ const SPEC_EXTENSION = ".spec.yaml";
8
+ const DOCUMENT_KEYS = [
9
+ "description",
10
+ "fixture",
11
+ "env",
12
+ "serve",
13
+ "runs"
14
+ ];
15
+ const RUN_KEYS = [
16
+ "command",
17
+ "stdin",
18
+ "timeout",
19
+ "waitFor",
20
+ "exit",
21
+ "stdout",
22
+ "stderr",
23
+ "files"
24
+ ];
25
+ const FILE_CONTENT_KEYS = ["contains", "equals"];
26
+ const FILE_STATES = ["absent", "exists"];
27
+ const PRESENCE = {
28
+ document: [{
29
+ keys: ["description"],
30
+ law: "each",
31
+ refusal: () => "missing \"description:\": every document is one test, and this is its title"
32
+ }, {
33
+ keys: ["runs"],
34
+ law: "each",
35
+ refusal: () => "missing \"runs:\": description and runs are mandatory"
36
+ }],
37
+ files: [{
38
+ keys: FILE_CONTENT_KEYS,
39
+ law: "some",
40
+ refusal: (path) => `files: ${path} asserts nothing: give it contains: or equals:`
41
+ }],
42
+ run: [{
43
+ keys: ["command", "exit"],
44
+ law: "each",
45
+ refusal: (_subject, key) => `a run is missing "${key}:": command and exit are mandatory`
46
+ }, {
47
+ keys: ["waitFor"],
48
+ law: "apart",
49
+ refusal: () => "waitFor: and stdin: cannot share a run: the pattern is watched on a process nobody closed the input of",
50
+ with: "stdin"
51
+ }]
52
+ };
53
+ var SpecSyntaxError = class extends Error {
54
+ line;
55
+ reason;
56
+ constructor(fileName, line, message) {
57
+ super(`${fileName}:${line}: ${message}`);
58
+ this.line = line;
59
+ this.reason = message;
60
+ this.name = "SpecSyntaxError";
61
+ }
62
+ };
63
+ //#endregion
64
+ export { RUN_KEYS as a, isRelativePath as c, PRESENCE as i, FILE_CONTENT_KEYS as n, SPEC_EXTENSION as o, FILE_STATES as r, SpecSyntaxError as s, DOCUMENT_KEYS as t };
65
+
66
+ //# sourceMappingURL=shared-spec-grammar.js.map