@uniflowed/test 0.3.0 → 0.5.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.
@@ -252,6 +252,14 @@ function sourceMapsForCallSites(): FindSourceMap | false {
252
252
  nodeSourceMaps = false;
253
253
  return false;
254
254
  }
255
+ // A `uf test` worker whose loader maps lazily holds the maps Node would
256
+ // have, and answers for them the way `findSourceMap` would; see
257
+ // `@uniflowed/host`'s `internal/lazy-source-maps.js`.
258
+ const lazy = host[Symbol.for("@uniflowed/host/source-maps")];
259
+ if (typeof lazy?.findSourceMap === "function") {
260
+ nodeSourceMaps = (file) => lazy.findSourceMap(file);
261
+ return nodeSourceMaps;
262
+ }
255
263
  const findSourceMap = process.getBuiltinModule("node:module")?.findSourceMap;
256
264
  nodeSourceMaps =
257
265
  typeof findSourceMap === "function"
package/internal/run.js CHANGED
@@ -75,6 +75,19 @@ export type RunOptions = {|
75
75
  * round. `uf test --bench` sets it.
76
76
  */
77
77
  readonly bench?: boolean,
78
+ /**
79
+ * Run only share `index` of `count` of the file's cases, and report nothing
80
+ * about the rest.
81
+ *
82
+ * `uf test` splits a file long enough to hold up a run across several
83
+ * workers, and each is handed one share: the cases are numbered in the
84
+ * order the file registered them, and share `index` is the contiguous run
85
+ * from `index * n / count` to `(index + 1) * n / count`. Every worker
86
+ * imports the whole file and walks the same tree, so the shares between
87
+ * them report every case once. A case outside the share is not reported at
88
+ * all — not as skipped — because another worker is reporting it.
89
+ */
90
+ readonly part?: {| readonly index: number, readonly count: number |} | null,
78
91
  |};
79
92
 
80
93
  /** Default budget for one case, matching what most runners use. */
@@ -381,7 +394,7 @@ async function runSuite(
381
394
  options: RunOptions,
382
395
  onlyMode: boolean,
383
396
  emit: (result: Result) => void,
384
- state: {| bail: boolean |},
397
+ state: {| bail: boolean, readonly owned: Set<Case> | null |},
385
398
  setUpAncestors: () => Promise<void>,
386
399
  ): Promise<boolean> {
387
400
  const skipped = context.skipped || node.modifier === "skip" || node.modifier === "todo";
@@ -430,6 +443,10 @@ async function runSuite(
430
443
  break;
431
444
  }
432
445
  if (child.kind !== "suite") {
446
+ // Another worker's share: it reports this case, and sets up for it.
447
+ if (state.owned != null && !state.owned.has(child)) {
448
+ continue;
449
+ }
433
450
  const willRun =
434
451
  !inner.skipped &&
435
452
  child.modifier !== "skip" &&
@@ -490,6 +507,7 @@ async function runSuite(
490
507
  export async function run(options: RunOptions, emit: (result: Result) => void): Promise<void> {
491
508
  const root = collected();
492
509
  const onlyMode = hasOnly(root, false);
510
+ const owned = share(root, options.part);
493
511
  const context: Context = {
494
512
  path: [],
495
513
  beforeEach: [],
@@ -497,5 +515,36 @@ export async function run(options: RunOptions, emit: (result: Result) => void):
497
515
  skipped: false,
498
516
  onlyPath: !onlyMode,
499
517
  };
500
- await runSuite(root, context, options, onlyMode, emit, { bail: false }, async () => {});
518
+ await runSuite(root, context, options, onlyMode, emit, { bail: false, owned }, async () => {});
519
+ }
520
+
521
+ /**
522
+ * The cases of `part`, or `null` for a run of every case.
523
+ *
524
+ * Numbered in the order a walk of the tree meets them, which is the order the
525
+ * file registered them and the order `runSuite` runs them in — the same in
526
+ * every worker that imported the file, which is what lets each work out its
527
+ * own share without being told the others'.
528
+ */
529
+ function share(
530
+ root: Suite,
531
+ part: ?{| readonly index: number, readonly count: number |},
532
+ ): Set<Case> | null {
533
+ if (part == null || part.count < 2) {
534
+ return null;
535
+ }
536
+ const cases: Array<Case> = [];
537
+ const walk = (node: Suite) => {
538
+ for (const child of node.children) {
539
+ if (child.kind === "suite") {
540
+ walk(child);
541
+ } else {
542
+ cases.push(child);
543
+ }
544
+ }
545
+ };
546
+ walk(root);
547
+ const from = Math.floor((part.index * cases.length) / part.count);
548
+ const to = Math.floor(((part.index + 1) * cases.length) / part.count);
549
+ return new Set(cases.slice(from, to));
501
550
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniflowed/test",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "The test API and worker for `uf test`: describe/it, a full matcher set, and the process uf fans test files out to.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -38,7 +38,7 @@
38
38
  "!*.test.js"
39
39
  ],
40
40
  "dependencies": {
41
- "@uniflowed/host": "0.3.0",
41
+ "@uniflowed/host": "0.5.0",
42
42
  "pixelmatch": "^7.1.0",
43
43
  "pngjs": "^7.0.0"
44
44
  },
package/worker.js CHANGED
@@ -86,6 +86,8 @@ type Request = {|
86
86
  */
87
87
  readonly invalidate?: $ReadOnlyArray<string>,
88
88
  readonly filter?: string | null,
89
+ /** Run only this share of the file's cases; see `RunOptions.part`. */
90
+ readonly part?: {| readonly index: number, readonly count: number |} | null,
89
91
  readonly timeoutMs?: number,
90
92
  /**
91
93
  * Which request this is, counting from one within this worker.
@@ -259,6 +261,7 @@ async function runImportedFile(
259
261
  await run(
260
262
  {
261
263
  filter: request.filter ?? null,
264
+ part: request.part ?? null,
262
265
  timeoutMs: request.timeoutMs,
263
266
  file: absolute,
264
267
  bench: benching(),