sxy-test-runner 2.5.3 → 2.5.5

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 (55) hide show
  1. package/AGENTS.md +47 -21
  2. package/dist/cli/lib/agentReport.d.ts +1 -0
  3. package/dist/cli/lib/agentReport.js +3 -0
  4. package/dist/cli/lib/agentReport.js.map +1 -1
  5. package/dist/cli/lib/buildDependencyTree.d.ts +1 -2
  6. package/dist/cli/lib/buildDependencyTree.js +25 -23
  7. package/dist/cli/lib/buildDependencyTree.js.map +1 -1
  8. package/dist/cli/lib/buildDependencyTrees.d.ts +1 -2
  9. package/dist/cli/lib/buildDependencyTrees.js +18 -16
  10. package/dist/cli/lib/buildDependencyTrees.js.map +1 -1
  11. package/dist/cli/lib/esmGraphReload.d.ts +5 -0
  12. package/dist/cli/lib/esmGraphReload.js +24 -0
  13. package/dist/cli/lib/esmGraphReload.js.map +1 -0
  14. package/dist/cli/lib/esmTreeReload.d.ts +4 -0
  15. package/dist/cli/lib/esmTreeReload.js +62 -0
  16. package/dist/cli/lib/esmTreeReload.js.map +1 -0
  17. package/dist/cli/lib/handleUserInput.d.ts +2 -2
  18. package/dist/cli/lib/handleUserInput.js +13 -6
  19. package/dist/cli/lib/handleUserInput.js.map +1 -1
  20. package/dist/cli/lib/listenToUser.d.ts +2 -2
  21. package/dist/cli/lib/listenToUser.js.map +1 -1
  22. package/dist/cli/lib/loadTestFile.d.ts +0 -1
  23. package/dist/cli/lib/loadTestFile.js +23 -8
  24. package/dist/cli/lib/loadTestFile.js.map +1 -1
  25. package/dist/cli/lib/makeDependencyGraph.d.ts +10 -0
  26. package/dist/cli/lib/makeDependencyGraph.js +210 -0
  27. package/dist/cli/lib/makeDependencyGraph.js.map +1 -0
  28. package/dist/cli/lib/reRunManager.js +4 -6
  29. package/dist/cli/lib/reRunManager.js.map +1 -1
  30. package/dist/cli/lib/runTests.js +7 -2
  31. package/dist/cli/lib/runTests.js.map +1 -1
  32. package/dist/cli/lib/showTestResultsSummary.d.ts +1 -1
  33. package/dist/cli/lib/showTestResultsSummary.js +5 -1
  34. package/dist/cli/lib/showTestResultsSummary.js.map +1 -1
  35. package/dist/cli/lib/statusFile.js +20 -1
  36. package/dist/cli/lib/statusFile.js.map +1 -1
  37. package/dist/cli/lib/watchFilesAndRunTestsNodeWatch.d.ts +3 -2
  38. package/dist/cli/lib/watchFilesAndRunTestsNodeWatch.js +24 -48
  39. package/dist/cli/lib/watchFilesAndRunTestsNodeWatch.js.map +1 -1
  40. package/dist/cli/watch.js +25 -12
  41. package/dist/cli/watch.js.map +1 -1
  42. package/dist/describe.js +5 -3
  43. package/dist/describe.js.map +1 -1
  44. package/dist/meta.js +2 -1
  45. package/dist/meta.js.map +1 -1
  46. package/dist/registration.d.ts +4 -0
  47. package/dist/registration.js +34 -0
  48. package/dist/registration.js.map +1 -0
  49. package/dist/types.d.ts +8 -0
  50. package/dist/types.js.map +1 -1
  51. package/package.json +11 -6
  52. package/readme.md +16 -123
  53. package/dist/packages/esmReload.d.ts +0 -11
  54. package/dist/packages/esmReload.js +0 -43
  55. package/dist/packages/esmReload.js.map +0 -1
@@ -3,15 +3,24 @@ import { findSourceMap } from 'node:module';
3
3
  import { join, relative } from 'node:path';
4
4
  import { fileURLToPath, pathToFileURL } from 'node:url';
5
5
  import chalk from '../../packages/chalk.js';
6
+ import { withoutQuery } from '../../registration.js';
6
7
  import slash from '../../packages/slash.js';
7
8
  import { createTimeProfiler } from '../../packages/timeProfiler.js';
8
- import '../../packages/esmReload.js'; // registers the test module reload hook
9
9
  import { showTimeProfiling } from '../../config.js';
10
10
  import { debug } from '../../output.js';
11
11
  import { captureLogs } from './consoleCapture.js';
12
12
  const { timeProfileAsync } = createTimeProfiler(showTimeProfiling);
13
13
  let loading = null;
14
14
  let importIteration = 1;
15
+ // kept across loads: a cached test file may have been evaluated while another file imported it
16
+ const registrations = new Map();
17
+ function takeRegistration(importUrl) {
18
+ const key = withoutQuery(importUrl);
19
+ const registration = registrations.get(key);
20
+ registrations.delete(key);
21
+ // one from an older evaluation is stale - this load registered nothing
22
+ return registration?.url === importUrl ? registration : undefined;
23
+ }
15
24
  // reads the map Node already parsed for stack traces, which is keyed by the exact import URL
16
25
  function mappedSourceFile(moduleUrl) {
17
26
  const sources = findSourceMap(moduleUrl)?.payload.sources;
@@ -31,14 +40,18 @@ export async function loadTestFile(config, testFile, refresh = 'refresh') {
31
40
  }
32
41
  const loadPromise = timeProfileAsync(`load test ${testFile}`, async () => {
33
42
  const projectDir = process.cwd();
43
+ const testFileFullUrl = pathToFileURL(join(projectDir, testFile)).href;
44
+ // resolved through the reload hooks, which may add their own query - the graph loader
45
+ // versions a test file that has changed - so this is the URL the module evaluates as
46
+ const importUrl = import.meta.resolve(refresh === 'refresh'
47
+ ? testFileFullUrl + `?sxyt-instance=${importIteration++}`
48
+ : testFileFullUrl);
34
49
  global.__sxy_test_runner__ = {
35
50
  describes: [],
36
- describeCounter: 1
51
+ describeCounter: 1,
52
+ files: registrations,
53
+ loadingUrl: importUrl
37
54
  };
38
- const testFileFullUrl = pathToFileURL(join(projectDir, testFile)).href;
39
- const importUrl = refresh === 'refresh'
40
- ? testFileFullUrl + `?sxyt-instance=${importIteration++}`
41
- : testFileFullUrl;
42
55
  const test = { file: testFile };
43
56
  //// capture console.log output
44
57
  const logs = [];
@@ -46,8 +59,10 @@ export async function loadTestFile(config, testFile, refresh = 'refresh') {
46
59
  await captureLogs(logs, async () => {
47
60
  try {
48
61
  await import(importUrl);
49
- test.describes = global.__sxy_test_runner__?.describes ?? [];
50
- test.meta = global.__sxy_test_runner__?.fileMeta;
62
+ // registered by this load, or - cached - by an earlier load that imported this file
63
+ const own = takeRegistration(importUrl);
64
+ test.describes = own?.describes ?? [];
65
+ test.meta = own?.fileMeta;
51
66
  test.error = false;
52
67
  }
53
68
  catch (e) {
@@ -1 +1 @@
1
- {"version":3,"file":"loadTestFile.js","sourceRoot":"","sources":["../../../src/cli/lib/loadTestFile.ts"],"names":[],"mappings":"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","sourcesContent":["\nimport { existsSync } from 'node:fs'\nimport { findSourceMap } from 'node:module'\nimport { join, relative } from 'node:path'\nimport { fileURLToPath, pathToFileURL } from 'node:url'\n\nimport type { Describe, FinalConfig } from '../../types.js'\nimport type { FileMeta } from '../../meta.js'\nimport chalk from '../../packages/chalk.js'\nimport slash from '../../packages/slash.js'\nimport { createTimeProfiler } from '../../packages/timeProfiler.js'\nimport '../../packages/esmReload.js' // registers the test module reload hook\nimport { showTimeProfiling } from '../../config.js'\nimport { debug } from '../../output.js'\nimport { captureLogs } from './consoleCapture.js'\n\n\nconst { timeProfileAsync } = createTimeProfiler(showTimeProfiling)\n\nexport type TestFile = {\n file: string\n // project relative, from the file's source map\n sourceFile?: string\n describes?: Describe[]\n meta?: FileMeta\n error?: string | false\n logs?: unknown[][]\n // ms spent importing the file and its module graph\n loadTime?: number\n}\n\nlet loading: Promise<TestFile> | null = null\n\nlet importIteration = 1\n\n// reads the map Node already parsed for stack traces, which is keyed by the exact import URL\nfunction mappedSourceFile(moduleUrl: string): string | undefined {\n const sources = findSourceMap(moduleUrl)?.payload.sources\n const source = sources?.length === 1 ? sources[0] : undefined\n if (source === undefined || !source.startsWith('file:')) return undefined\n const sourcePath = fileURLToPath(source)\n if (!existsSync(sourcePath)) return undefined\n return slash(relative(process.cwd(), sourcePath))\n}\n\nexport async function loadTestFile(\n config: FinalConfig,\n testFile: string,\n refresh: 'refresh' | 'cached' = 'refresh'\n): Promise<TestFile> {\n if (loading !== null) {\n debug(chalk.yellow`loadTestFile - another file is loading - waiting for it to finish`)\n await loading\n debug(chalk.yellow`previous file complete - will now load the file`)\n }\n\n const loadPromise = timeProfileAsync(`load test ${testFile}`, async () => {\n const projectDir = process.cwd()\n\n global.__sxy_test_runner__ = {\n describes: [],\n describeCounter: 1\n }\n\n const testFileFullUrl = pathToFileURL(join(projectDir, testFile)).href\n const importUrl = refresh === 'refresh'\n ? testFileFullUrl + `?sxyt-instance=${importIteration++}`\n : testFileFullUrl\n\n const test: TestFile = { file: testFile }\n\n //// capture console.log output\n const logs: unknown[][] = []\n const loadStart = performance.now()\n await captureLogs(logs, async () => {\n try {\n await import(importUrl)\n test.describes = global.__sxy_test_runner__?.describes ?? []\n test.meta = global.__sxy_test_runner__?.fileMeta\n test.error = false\n } catch (e) {\n test.error = `Error loading file\\n${String(e)}`\n }\n })\n // a cached load shares modules with earlier files, so its time says little about this one\n if (refresh === 'refresh') test.loadTime = Math.round(performance.now() - loadStart)\n const sourceFile = mappedSourceFile(importUrl)\n if (sourceFile !== undefined) test.sourceFile = sourceFile\n test.logs = logs\n ////\n\n return test\n })\n\n loading = loadPromise\n\n return await loadPromise\n}\n"]}
1
+ {"version":3,"file":"loadTestFile.js","sourceRoot":"","sources":["../../../src/cli/lib/loadTestFile.ts"],"names":[],"mappings":"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","sourcesContent":["\nimport { existsSync } from 'node:fs'\nimport { findSourceMap } from 'node:module'\nimport { join, relative } from 'node:path'\nimport { fileURLToPath, pathToFileURL } from 'node:url'\n\nimport type { Describe, FileRegistration, FinalConfig } from '../../types.js'\nimport type { FileMeta } from '../../meta.js'\nimport chalk from '../../packages/chalk.js'\nimport { withoutQuery } from '../../registration.js'\nimport slash from '../../packages/slash.js'\nimport { createTimeProfiler } from '../../packages/timeProfiler.js'\nimport { showTimeProfiling } from '../../config.js'\nimport { debug } from '../../output.js'\nimport { captureLogs } from './consoleCapture.js'\n\n\nconst { timeProfileAsync } = createTimeProfiler(showTimeProfiling)\n\nexport type TestFile = {\n file: string\n // project relative, from the file's source map\n sourceFile?: string\n describes?: Describe[]\n meta?: FileMeta\n error?: string | false\n logs?: unknown[][]\n // ms spent importing the file and its module graph\n loadTime?: number\n}\n\nlet loading: Promise<TestFile> | null = null\n\nlet importIteration = 1\n\n// kept across loads: a cached test file may have been evaluated while another file imported it\nconst registrations = new Map<string, FileRegistration>()\n\nfunction takeRegistration(importUrl: string): FileRegistration | undefined {\n const key = withoutQuery(importUrl)\n const registration = registrations.get(key)\n registrations.delete(key)\n // one from an older evaluation is stale - this load registered nothing\n return registration?.url === importUrl ? registration : undefined\n}\n\n// reads the map Node already parsed for stack traces, which is keyed by the exact import URL\nfunction mappedSourceFile(moduleUrl: string): string | undefined {\n const sources = findSourceMap(moduleUrl)?.payload.sources\n const source = sources?.length === 1 ? sources[0] : undefined\n if (source === undefined || !source.startsWith('file:')) return undefined\n const sourcePath = fileURLToPath(source)\n if (!existsSync(sourcePath)) return undefined\n return slash(relative(process.cwd(), sourcePath))\n}\n\nexport async function loadTestFile(\n config: FinalConfig,\n testFile: string,\n refresh: 'refresh' | 'cached' = 'refresh'\n): Promise<TestFile> {\n if (loading !== null) {\n debug(chalk.yellow`loadTestFile - another file is loading - waiting for it to finish`)\n await loading\n debug(chalk.yellow`previous file complete - will now load the file`)\n }\n\n const loadPromise = timeProfileAsync(`load test ${testFile}`, async () => {\n const projectDir = process.cwd()\n\n const testFileFullUrl = pathToFileURL(join(projectDir, testFile)).href\n // resolved through the reload hooks, which may add their own query - the graph loader\n // versions a test file that has changed - so this is the URL the module evaluates as\n const importUrl = import.meta.resolve(refresh === 'refresh'\n ? testFileFullUrl + `?sxyt-instance=${importIteration++}`\n : testFileFullUrl)\n\n global.__sxy_test_runner__ = {\n describes: [],\n describeCounter: 1,\n files: registrations,\n loadingUrl: importUrl\n }\n\n const test: TestFile = { file: testFile }\n\n //// capture console.log output\n const logs: unknown[][] = []\n const loadStart = performance.now()\n await captureLogs(logs, async () => {\n try {\n await import(importUrl)\n // registered by this load, or - cached - by an earlier load that imported this file\n const own = takeRegistration(importUrl)\n test.describes = own?.describes ?? []\n test.meta = own?.fileMeta\n test.error = false\n } catch (e) {\n test.error = `Error loading file\\n${String(e)}`\n }\n })\n // a cached load shares modules with earlier files, so its time says little about this one\n if (refresh === 'refresh') test.loadTime = Math.round(performance.now() - loadStart)\n const sourceFile = mappedSourceFile(importUrl)\n if (sourceFile !== undefined) test.sourceFile = sourceFile\n test.logs = logs\n ////\n\n return test\n })\n\n loading = loadPromise\n\n return await loadPromise\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import type { FinalConfig } from '../../types.js';
2
+ export interface DependencyGraph {
3
+ addTest(file: string): void;
4
+ update(file: string): void;
5
+ remove(file: string): void;
6
+ testsAffectedBy(file: string): Set<string>;
7
+ versionOf(file: string): number | undefined;
8
+ }
9
+ export declare function makeDependencyGraph(config: FinalConfig, testFiles: Iterable<string>): DependencyGraph;
10
+ //# sourceMappingURL=makeDependencyGraph.d.ts.map
@@ -0,0 +1,210 @@
1
+ import { existsSync, readFileSync, realpathSync } from 'node:fs';
2
+ import { isBuiltin } from 'node:module';
3
+ import { basename, dirname, extname, isAbsolute, join, relative, resolve } from 'node:path';
4
+ import cabinet from 'filing-cabinet';
5
+ import precinct from 'sxy-precinct';
6
+ import slash from '../../packages/slash.js';
7
+ import { createTimeProfiler } from '../../packages/timeProfiler.js';
8
+ import { showTimeProfiling } from '../../config.js';
9
+ const { timeProfile } = createTimeProfiler(showTimeProfiling);
10
+ // precinct type per extension; undefined sniffs ESM vs CommonJS from the source
11
+ const precinctTypes = {
12
+ '.js': undefined, '.jsx': undefined,
13
+ '.mjs': 'es6', '.cjs': 'commonjs',
14
+ '.ts': 'ts', '.mts': 'ts', '.cts': 'ts', '.tsx': 'tsx'
15
+ };
16
+ for (const [extension, from] of [['.mjs', '.js'], ['.cjs', '.js'], ['.mts', '.ts'], ['.cts', '.ts']]) {
17
+ const lookup = cabinet.getLookup(from);
18
+ if (cabinet.getLookup(extension) === undefined && lookup !== undefined)
19
+ cabinet.register(extension, lookup);
20
+ }
21
+ function tryRealpath(path) {
22
+ try {
23
+ return realpathSync.native(path);
24
+ }
25
+ catch {
26
+ return undefined;
27
+ }
28
+ }
29
+ // paths in and out are relative to the cwd with forward slashes; edges are kept between real paths
30
+ export function makeDependencyGraph(config, testFiles) {
31
+ const cwd = realpathSync.native(process.cwd());
32
+ const directory = resolve(config.watch.watchFilesBase === '' ? '.' : config.watch.watchFilesBase);
33
+ const imports = new Map();
34
+ const importers = new Map();
35
+ const tests = new Set();
36
+ const versions = new Map();
37
+ const toKey = (absolute) => slash(relative(cwd, absolute));
38
+ const toAbsolute = (key) => resolve(cwd, key);
39
+ // cleared by each operation, so a path created, deleted or relinked since is never served stale
40
+ const realCache = new Map();
41
+ const resolveCache = new Map();
42
+ const clearCaches = () => {
43
+ realCache.clear();
44
+ resolveCache.clear();
45
+ };
46
+ function realAbsolute(file) {
47
+ const absolute = resolve(file);
48
+ let real = realCache.get(absolute);
49
+ if (real === undefined) {
50
+ // a missing file's directory may still be reached through a link
51
+ real = tryRealpath(absolute)
52
+ ?? join(tryRealpath(dirname(absolute)) ?? dirname(absolute), basename(absolute));
53
+ realCache.set(absolute, real);
54
+ }
55
+ return real;
56
+ }
57
+ const realKey = (file) => toKey(realAbsolute(file));
58
+ // undefined when the file does not parse, so its last known imports stand
59
+ function readImports(key) {
60
+ const filename = toAbsolute(key);
61
+ const extension = extname(filename);
62
+ if (!existsSync(filename) || !(extension in precinctTypes))
63
+ return new Set();
64
+ let specifiers;
65
+ try {
66
+ specifiers = precinct(readFileSync(filename, 'utf8'), {
67
+ type: precinctTypes[extension],
68
+ throwOnParseError: true,
69
+ es6: { mixedImports: true },
70
+ ts: { mixedImports: true },
71
+ tsx: { mixedImports: true }
72
+ });
73
+ }
74
+ catch {
75
+ return undefined;
76
+ }
77
+ // precinct only leaves an ast for content it sniffed; filing-cabinet otherwise parses again
78
+ const ast = precinctTypes[extension] === undefined ? precinct.ast ?? undefined : undefined;
79
+ const found = new Set();
80
+ for (const specifier of specifiers) {
81
+ if (isBuiltin(specifier))
82
+ continue;
83
+ const resolved = resolveImport(specifier, filename, ast);
84
+ if (resolved === null || slash(resolved).includes('/node_modules/'))
85
+ continue;
86
+ found.add(toKey(resolved));
87
+ }
88
+ return found;
89
+ }
90
+ // resolution depends only on the importer's directory and kind, so its neighbours share it
91
+ function resolveImport(specifier, filename, ast) {
92
+ const cacheKey = `${dirname(filename)}\0${extname(filename)}\0${specifier}`;
93
+ let result = resolveCache.get(cacheKey);
94
+ if (result === undefined) {
95
+ result = resolveUncached(specifier, filename, ast);
96
+ resolveCache.set(cacheKey, result);
97
+ }
98
+ return result;
99
+ }
100
+ function resolveUncached(specifier, filename, ast) {
101
+ let resolved = '';
102
+ try {
103
+ resolved = cabinet({ partial: specifier, filename, directory, ...(ast === undefined ? {} : { ast }) });
104
+ }
105
+ catch {
106
+ // left unresolved
107
+ }
108
+ if (resolved !== '')
109
+ return realAbsolute(resolved);
110
+ // filing-cabinet resolves a missing file to '' - keep the edge so its creation is seen
111
+ if (specifier.startsWith('.') || isAbsolute(specifier)) {
112
+ return realAbsolute(resolve(dirname(filename), specifier));
113
+ }
114
+ return null;
115
+ }
116
+ function setImports(key, next) {
117
+ const previous = imports.get(key) ?? new Set();
118
+ for (const dep of previous.difference(next))
119
+ importers.get(dep)?.delete(key);
120
+ for (const dep of next.difference(previous)) {
121
+ let set = importers.get(dep);
122
+ if (set === undefined)
123
+ importers.set(dep, set = new Set());
124
+ set.add(key);
125
+ }
126
+ imports.set(key, next);
127
+ }
128
+ // re-reads `start`, then walks only into files never read before
129
+ function walk(start) {
130
+ const queue = [start];
131
+ const seen = new Set();
132
+ for (let key = queue.pop(); key !== undefined; key = queue.pop()) {
133
+ if (seen.has(key))
134
+ continue;
135
+ seen.add(key);
136
+ const found = readImports(key);
137
+ if (found === undefined) {
138
+ if (!imports.has(key))
139
+ imports.set(key, new Set());
140
+ continue;
141
+ }
142
+ setImports(key, found);
143
+ for (const dep of found)
144
+ if (!imports.has(dep))
145
+ queue.push(dep);
146
+ }
147
+ }
148
+ // the file and everything importing it, directly or not
149
+ function dependents(start) {
150
+ const queue = [start];
151
+ const seen = new Set();
152
+ for (let key = queue.pop(); key !== undefined; key = queue.pop()) {
153
+ if (seen.has(key))
154
+ continue;
155
+ seen.add(key);
156
+ for (const importer of importers.get(key) ?? [])
157
+ queue.push(importer);
158
+ }
159
+ return seen;
160
+ }
161
+ // Windows paths match in any case, and the loader's may differ from the real one
162
+ const versionKey = process.platform === 'win32'
163
+ ? (key) => key.toLowerCase()
164
+ : (key) => key;
165
+ function bump(start) {
166
+ for (const key of dependents(start)) {
167
+ const vKey = versionKey(key);
168
+ versions.set(vKey, (versions.get(vKey) ?? 0) + 1);
169
+ }
170
+ }
171
+ timeProfile('build dependency graph', () => {
172
+ for (const file of testFiles) {
173
+ const key = realKey(file);
174
+ tests.add(key);
175
+ if (!imports.has(key))
176
+ walk(key);
177
+ }
178
+ });
179
+ return {
180
+ addTest(file) {
181
+ clearCaches();
182
+ const key = realKey(file);
183
+ tests.add(key);
184
+ walk(key);
185
+ bump(key);
186
+ },
187
+ update(file) {
188
+ clearCaches();
189
+ const key = realKey(file);
190
+ walk(key);
191
+ bump(key);
192
+ },
193
+ remove(file) {
194
+ clearCaches();
195
+ const key = realKey(file);
196
+ tests.delete(key);
197
+ setImports(key, new Set());
198
+ bump(key);
199
+ },
200
+ testsAffectedBy(file) {
201
+ clearCaches();
202
+ return dependents(realKey(file)).intersection(tests);
203
+ },
204
+ // no realpath: called for every module load, with the real path Node resolved
205
+ versionOf(file) {
206
+ return versions.get(versionKey(toKey(resolve(file))));
207
+ }
208
+ };
209
+ }
210
+ //# sourceMappingURL=makeDependencyGraph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"makeDependencyGraph.js","sourceRoot":"","sources":["../../../src/cli/lib/makeDependencyGraph.ts"],"names":[],"mappings":"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","sourcesContent":["\nimport { existsSync, readFileSync, realpathSync } from 'node:fs'\nimport { isBuiltin } from 'node:module'\nimport { basename, dirname, extname, isAbsolute, join, relative, resolve } from 'node:path'\nimport cabinet from 'filing-cabinet'\nimport precinct from 'sxy-precinct'\n\nimport type { FinalConfig } from '../../types.js'\nimport slash from '../../packages/slash.js'\nimport { createTimeProfiler } from '../../packages/timeProfiler.js'\nimport { showTimeProfiling } from '../../config.js'\n\n\nconst { timeProfile } = createTimeProfiler(showTimeProfiling)\n\nexport interface DependencyGraph {\n addTest(file: string): void\n update(file: string): void\n remove(file: string): void\n testsAffectedBy(file: string): Set<string>\n // moved on for a changed file and everything importing it, so a reload gets fresh copies\n versionOf(file: string): number | undefined\n}\n\n// precinct type per extension; undefined sniffs ESM vs CommonJS from the source\nconst precinctTypes: Record<string, string | undefined> = {\n '.js': undefined, '.jsx': undefined,\n '.mjs': 'es6', '.cjs': 'commonjs',\n '.ts': 'ts', '.mts': 'ts', '.cts': 'ts', '.tsx': 'tsx'\n}\n\nfor (const [extension, from] of [['.mjs', '.js'], ['.cjs', '.js'], ['.mts', '.ts'], ['.cts', '.ts']] as const) {\n const lookup = cabinet.getLookup(from)\n if (cabinet.getLookup(extension) === undefined && lookup !== undefined) cabinet.register(extension, lookup)\n}\n\nfunction tryRealpath(path: string): string | undefined {\n try {\n return realpathSync.native(path)\n } catch {\n return undefined\n }\n}\n\n// paths in and out are relative to the cwd with forward slashes; edges are kept between real paths\nexport function makeDependencyGraph(config: FinalConfig, testFiles: Iterable<string>): DependencyGraph {\n const cwd = realpathSync.native(process.cwd())\n const directory = resolve(config.watch.watchFilesBase === '' ? '.' : config.watch.watchFilesBase)\n\n const imports = new Map<string, Set<string>>()\n const importers = new Map<string, Set<string>>()\n const tests = new Set<string>()\n const versions = new Map<string, number>()\n\n const toKey = (absolute: string): string => slash(relative(cwd, absolute))\n const toAbsolute = (key: string): string => resolve(cwd, key)\n\n // cleared by each operation, so a path created, deleted or relinked since is never served stale\n const realCache = new Map<string, string>()\n const resolveCache = new Map<string, string | null>()\n const clearCaches = (): void => {\n realCache.clear()\n resolveCache.clear()\n }\n function realAbsolute(file: string): string {\n const absolute = resolve(file)\n let real = realCache.get(absolute)\n if (real === undefined) {\n // a missing file's directory may still be reached through a link\n real = tryRealpath(absolute)\n ?? join(tryRealpath(dirname(absolute)) ?? dirname(absolute), basename(absolute))\n realCache.set(absolute, real)\n }\n return real\n }\n const realKey = (file: string): string => toKey(realAbsolute(file))\n\n // undefined when the file does not parse, so its last known imports stand\n function readImports(key: string): Set<string> | undefined {\n const filename = toAbsolute(key)\n const extension = extname(filename)\n if (!existsSync(filename) || !(extension in precinctTypes)) return new Set()\n\n let specifiers: string[]\n try {\n specifiers = precinct(readFileSync(filename, 'utf8'), {\n type: precinctTypes[extension],\n throwOnParseError: true,\n es6: { mixedImports: true },\n ts: { mixedImports: true },\n tsx: { mixedImports: true }\n })\n } catch {\n return undefined\n }\n // precinct only leaves an ast for content it sniffed; filing-cabinet otherwise parses again\n const ast = precinctTypes[extension] === undefined ? precinct.ast ?? undefined : undefined\n\n const found = new Set<string>()\n for (const specifier of specifiers) {\n if (isBuiltin(specifier)) continue\n const resolved = resolveImport(specifier, filename, ast)\n if (resolved === null || slash(resolved).includes('/node_modules/')) continue\n found.add(toKey(resolved))\n }\n return found\n }\n\n // resolution depends only on the importer's directory and kind, so its neighbours share it\n function resolveImport(specifier: string, filename: string, ast: object | undefined): string | null {\n const cacheKey = `${dirname(filename)}\\0${extname(filename)}\\0${specifier}`\n let result = resolveCache.get(cacheKey)\n if (result === undefined) {\n result = resolveUncached(specifier, filename, ast)\n resolveCache.set(cacheKey, result)\n }\n return result\n }\n\n function resolveUncached(specifier: string, filename: string, ast: object | undefined): string | null {\n let resolved = ''\n try {\n resolved = cabinet({ partial: specifier, filename, directory, ...(ast === undefined ? {} : { ast }) })\n } catch {\n // left unresolved\n }\n if (resolved !== '') return realAbsolute(resolved)\n // filing-cabinet resolves a missing file to '' - keep the edge so its creation is seen\n if (specifier.startsWith('.') || isAbsolute(specifier)) {\n return realAbsolute(resolve(dirname(filename), specifier))\n }\n return null\n }\n\n function setImports(key: string, next: Set<string>): void {\n const previous = imports.get(key) ?? new Set<string>()\n for (const dep of previous.difference(next)) importers.get(dep)?.delete(key)\n for (const dep of next.difference(previous)) {\n let set = importers.get(dep)\n if (set === undefined) importers.set(dep, set = new Set())\n set.add(key)\n }\n imports.set(key, next)\n }\n\n // re-reads `start`, then walks only into files never read before\n function walk(start: string): void {\n const queue = [start]\n const seen = new Set<string>()\n for (let key = queue.pop(); key !== undefined; key = queue.pop()) {\n if (seen.has(key)) continue\n seen.add(key)\n const found = readImports(key)\n if (found === undefined) {\n if (!imports.has(key)) imports.set(key, new Set())\n continue\n }\n setImports(key, found)\n for (const dep of found) if (!imports.has(dep)) queue.push(dep)\n }\n }\n\n // the file and everything importing it, directly or not\n function dependents(start: string): Set<string> {\n const queue = [start]\n const seen = new Set<string>()\n for (let key = queue.pop(); key !== undefined; key = queue.pop()) {\n if (seen.has(key)) continue\n seen.add(key)\n for (const importer of importers.get(key) ?? []) queue.push(importer)\n }\n return seen\n }\n\n // Windows paths match in any case, and the loader's may differ from the real one\n const versionKey = process.platform === 'win32'\n ? (key: string): string => key.toLowerCase()\n : (key: string): string => key\n\n function bump(start: string): void {\n for (const key of dependents(start)) {\n const vKey = versionKey(key)\n versions.set(vKey, (versions.get(vKey) ?? 0) + 1)\n }\n }\n\n timeProfile('build dependency graph', () => {\n for (const file of testFiles) {\n const key = realKey(file)\n tests.add(key)\n if (!imports.has(key)) walk(key)\n }\n })\n\n return {\n addTest(file) {\n clearCaches()\n const key = realKey(file)\n tests.add(key)\n walk(key)\n bump(key)\n },\n update(file) {\n clearCaches()\n const key = realKey(file)\n walk(key)\n bump(key)\n },\n remove(file) {\n clearCaches()\n const key = realKey(file)\n tests.delete(key)\n setImports(key, new Set())\n bump(key)\n },\n testsAffectedBy(file) {\n clearCaches()\n return dependents(realKey(file)).intersection(tests)\n },\n // no realpath: called for every module load, with the real path Node resolved\n versionOf(file) {\n return versions.get(versionKey(toKey(resolve(file))))\n }\n }\n}\n"]}
@@ -77,17 +77,15 @@ customOut) {
77
77
  __test.queue = queue = queue.filter(x => x.type !== 'auto');
78
78
  queue.push({
79
79
  type,
80
- //tests,
81
80
  callback
82
81
  });
83
82
  }
84
83
  else {
85
84
  __test.queue = queue = queue.filter(x => x.type === 'auto');
86
- __test.queue = queue = [{
87
- type,
88
- //tests,
89
- callback
90
- }];
85
+ queue.push({
86
+ type,
87
+ callback
88
+ });
91
89
  }
92
90
  debug('added to queue', inspect(queue));
93
91
  // see if we can run it
@@ -1 +1 @@
1
- {"version":3,"file":"reRunManager.js","sourceRoot":"","sources":["../../../src/cli/lib/reRunManager.ts"],"names":[],"mappings":"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","sourcesContent":["\nimport { inspect } from 'util'\n\nimport type { ReRunManageState } from './makeWatchRunState.js'\nimport type { RunTestsPromise } from './runTests.js'\nimport type { OutFn } from '../../types.js'\nimport { debug, log, out as mainOut } from '../../output.js'\nimport { agentSetRunReason } from './agentReport.js'\nimport chalk from '../../packages/chalk.js'\n\n\nexport type RunType = 'auto' | 'all' | 'last' | 'failed'\n\ntype RunReqest = {\n type: RunType\n //tests: string[] | Set<string>\n callback: () => RunTestsPromise\n}\n\n//export type ReRunManager = ReturnType<typeof makeReRunManager>\n\nexport type ReRunManager = {\n state: ReRunManageState\n requestRun: (type: RunType, callback: () => RunTestsPromise) => Promise<void>\n __test: {\n waiting: boolean\n queue: RunReqest[]\n }\n}\n\n\nexport function makeReRunManager(\n currentRun: RunTestsPromise | null,\n state: ReRunManageState,\n //callback: () => RunTestsPromise\n //concurrency: Concurrency,\n customOut?: OutFn\n): ReRunManager {\n // agent mode passes a sink - runTests reports the run instead\n const out = customOut ?? mainOut\n // state.currentRun is the one record of the run in flight - watch mode sets it directly\n // for the initial run, so a copy held here would let a change start a second run beside it\n if (currentRun !== null) state.currentRun = currentRun\n let queue: RunReqest[] = []\n let waiting = false\n\n const __test = {\n waiting,\n queue\n }\n\n async function processQueue() {\n \n debug('processing queue')\n\n if (waiting) return // do nothing if another processQueue is already waiting\n __test.waiting = waiting = true // mark that we are waiting\n await state.currentRun // wait for current run\n __test.waiting = waiting = false\n\n debug('done waitinng')\n\n // see if there is a request waiting\n debug('the queue', inspect(queue))\n const runReqest = queue.shift()\n debug('runReqest', runReqest)\n if (runReqest === undefined) return\n\n // tells the run about to start why it is running, for the agent `why=` field\n agentSetRunReason(runReqest.type)\n\n switch (runReqest.type) {\n case 'all': {\n out(chalk.brightblue`re-running all tests...`)\n break\n }\n case 'last': {\n out(chalk.brightblue`re-running last tests...`)\n break\n }\n case 'failed': {\n out(chalk.brightblue`re-running failed tests...`)\n break\n }\n case 'auto': {\n out(chalk.brightblue`running tests due to file changes`)\n break\n }\n default: throw new Error('Unrecognised run type')\n }\n\n // run it and save it\n debug('calling', runReqest.callback.toString())\n const run = runReqest.callback()\n state.currentRun = run\n // wait for the run then trigger another processQueue\n await run\n // if one has already been started by a run request\n // then this won't start a new one. This is used to\n // clear a queue if it has built it\n processQueue().catch(err => log(`processQueue threw an error: ${err}`))\n }\n\n return {\n state,\n requestRun(type: RunType, callback: () => RunTestsPromise) {\n\n // if it's type auto, we only want to keep one, but\n // we want it to include all relevant tests when it runs\n\n // if it's a human triggered type, we'll replace other\n // human requests, but leave the auto, so the user\n // can see update to changed code that would be hidden\n // otherwise\n \n if (type === 'auto') {\n __test.queue = queue = queue.filter(x => x.type !== 'auto')\n queue.push({\n type,\n //tests,\n callback\n })\n } else {\n __test.queue = queue = queue.filter(x => x.type === 'auto')\n __test.queue = queue = [{\n type,\n //tests,\n callback\n }]\n }\n debug('added to queue', inspect(queue))\n // see if we can run it\n return processQueue().catch(err => log(`processQueue threw an error: ${err}`))\n },\n __test\n }\n}\n"]}
1
+ {"version":3,"file":"reRunManager.js","sourceRoot":"","sources":["../../../src/cli/lib/reRunManager.ts"],"names":[],"mappings":"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","sourcesContent":["\nimport { inspect } from 'util'\n\nimport type { ReRunManageState } from './makeWatchRunState.js'\nimport type { RunTestsPromise } from './runTests.js'\nimport type { OutFn } from '../../types.js'\nimport { debug, log, out as mainOut } from '../../output.js'\nimport { agentSetRunReason } from './agentReport.js'\nimport chalk from '../../packages/chalk.js'\n\n\nexport type RunType = 'auto' | 'all' | 'last' | 'failed'\n\ntype RunReqest = {\n type: RunType\n //tests: string[] | Set<string>\n callback: () => RunTestsPromise\n}\n\n//export type ReRunManager = ReturnType<typeof makeReRunManager>\n\nexport type ReRunManager = {\n state: ReRunManageState\n requestRun: (type: RunType, callback: () => RunTestsPromise) => Promise<void>\n __test: {\n waiting: boolean\n queue: RunReqest[]\n }\n}\n\n\nexport function makeReRunManager(\n currentRun: RunTestsPromise | null,\n state: ReRunManageState,\n //callback: () => RunTestsPromise\n //concurrency: Concurrency,\n customOut?: OutFn\n): ReRunManager {\n // agent mode passes a sink - runTests reports the run instead\n const out = customOut ?? mainOut\n // state.currentRun is the one record of the run in flight - watch mode sets it directly\n // for the initial run, so a copy held here would let a change start a second run beside it\n if (currentRun !== null) state.currentRun = currentRun\n let queue: RunReqest[] = []\n let waiting = false\n\n const __test = {\n waiting,\n queue\n }\n\n async function processQueue() {\n \n debug('processing queue')\n\n if (waiting) return // do nothing if another processQueue is already waiting\n __test.waiting = waiting = true // mark that we are waiting\n await state.currentRun // wait for current run\n __test.waiting = waiting = false\n\n debug('done waitinng')\n\n // see if there is a request waiting\n debug('the queue', inspect(queue))\n const runReqest = queue.shift()\n debug('runReqest', runReqest)\n if (runReqest === undefined) return\n\n // tells the run about to start why it is running, for the agent `why=` field\n agentSetRunReason(runReqest.type)\n\n switch (runReqest.type) {\n case 'all': {\n out(chalk.brightblue`re-running all tests...`)\n break\n }\n case 'last': {\n out(chalk.brightblue`re-running last tests...`)\n break\n }\n case 'failed': {\n out(chalk.brightblue`re-running failed tests...`)\n break\n }\n case 'auto': {\n out(chalk.brightblue`running tests due to file changes`)\n break\n }\n default: throw new Error('Unrecognised run type')\n }\n\n // run it and save it\n debug('calling', runReqest.callback.toString())\n const run = runReqest.callback()\n state.currentRun = run\n // wait for the run then trigger another processQueue\n await run\n // if one has already been started by a run request\n // then this won't start a new one. This is used to\n // clear a queue if it has built it\n processQueue().catch(err => log(`processQueue threw an error: ${err}`))\n }\n\n return {\n state,\n requestRun(type: RunType, callback: () => RunTestsPromise) {\n\n // if it's type auto, we only want to keep one, but\n // we want it to include all relevant tests when it runs\n\n // if it's a human triggered type, we'll replace other\n // human requests, but leave the auto, so the user\n // can see update to changed code that would be hidden\n // otherwise\n \n if (type === 'auto') {\n __test.queue = queue = queue.filter(x => x.type !== 'auto')\n queue.push({\n type,\n callback\n })\n } else {\n __test.queue = queue = queue.filter(x => x.type === 'auto')\n queue.push({\n type,\n callback\n })\n }\n debug('added to queue', inspect(queue))\n // see if we can run it\n return processQueue().catch(err => log(`processQueue threw an error: ${err}`))\n },\n __test\n }\n}\n"]}
@@ -5,11 +5,15 @@ import { loadTestFile } from './loadTestFile.js';
5
5
  import { runTest } from './runTest.js';
6
6
  import { showTestLoadingError } from './showTestLoadingError.js';
7
7
  import { showTestFileResult } from './showTestFileResult.js';
8
- import { agentRunStart } from './agentReport.js';
8
+ import { agentRunStart, currentRunReason } from './agentReport.js';
9
9
  import { makeStopSignal, stopIfFailFast } from './stopSignal.js';
10
10
  import { makeRunManagerState } from './makeWatchRunState.js';
11
11
  import { showTestResultsSummary } from './showTestResultsSummary.js';
12
12
  const { timeProfileAsync } = createTimeProfiler(showTimeProfiling);
13
+ // shown after the summary's tallies, for the runs asked for from the keyboard only
14
+ const runLabels = {
15
+ startup: undefined, once: undefined, auto: undefined, all: 'all', last: 'last', failed: 'failures'
16
+ };
13
17
  function recordEntitiesWithoutTests(state, testFile, testResult) {
14
18
  const describes = testResult.describeResults
15
19
  .filter(describeResult => describeResult.success === null)
@@ -33,6 +37,7 @@ refresh = 'refresh', customOut, filters = {}) {
33
37
  }
34
38
  const state = reRunManager?.state || makeRunManagerState();
35
39
  const runStartTime = new Date().getTime();
40
+ const runLabel = runLabels[currentRunReason()];
36
41
  const runNumber = agentRunStart(config, testFiles.size);
37
42
  const stop = makeStopSignal();
38
43
  //console.log('new runTests:', watchRunState?.activeRuns)
@@ -87,7 +92,7 @@ refresh = 'refresh', customOut, filters = {}) {
87
92
  // wait for the output to complete
88
93
  await promiseAllObj(testRunOutPromises);
89
94
  });
90
- return showTestResultsSummary(runTestResults, erroringTests, runStartTime, config, runNumber, state, stop, filters, customOut);
95
+ return showTestResultsSummary(runTestResults, erroringTests, runStartTime, config, runNumber, state, stop, filters, customOut, runLabel);
91
96
  }
92
97
  finally {
93
98
  if (reRunManager !== undefined)
@@ -1 +1 @@
1
- {"version":3,"file":"runTests.js","sourceRoot":"","sources":["../../../src/cli/lib/runTests.ts"],"names":[],"mappings":"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","sourcesContent":["\nimport { allObj as promiseAllObj } from 'sxy-lib/promises.js'\n\nimport type { FinalConfig, OutFn } from '../../types.js'\nimport type { TestExecutionFilters } from '../cliTypes.js'\nimport type { ReRunManager } from './reRunManager.js'\nimport { createTimeProfiler } from '../../packages/timeProfiler.js'\nimport { showTimeProfiling } from '../../config.js'\nimport { loadTestFile, type TestFile } from './loadTestFile.js'\nimport { runTest, type TestResult } from './runTest.js'\nimport { showTestLoadingError } from './showTestLoadingError.js'\nimport { showTestFileResult } from './showTestFileResult.js'\nimport { agentRunStart } from './agentReport.js'\nimport { makeStopSignal, stopIfFailFast } from './stopSignal.js'\nimport { makeRunManagerState, type ReRunManageState } from './makeWatchRunState.js'\nimport { showTestResultsSummary } from './showTestResultsSummary.js'\n\n\nconst { timeProfileAsync } = createTimeProfiler(showTimeProfiling)\n\nexport interface TestsResultsSummary {\n total: number\n passes: number\n invalid: number\n describesTotal: number\n describesPasses: number\n describesInvalid: number\n itsTotal: number\n itsPasses: number\n}\n\nexport type RunTestsPromise = Promise<boolean | null>\n\n\nexport type Concurrency = {\n limit: number\n}\n\nfunction recordEntitiesWithoutTests(state: ReRunManageState, testFile: string, testResult: TestResult): void {\n const describes = testResult.describeResults\n .filter(describeResult => describeResult.success === null)\n .map(describeResult => describeResult.thing)\n const fileWithoutTests = testResult.success === null\n if (fileWithoutTests || describes.length > 0) {\n state.withoutTests.set(testFile, {\n file: testResult.sourceFile ?? testResult.file, fileWithoutTests, describes\n })\n } else {\n state.withoutTests.delete(testFile)\n }\n}\n\nexport async function runTests(\n config: FinalConfig,\n testFiles: Set<string>,\n reRunManager: ReRunManager | undefined,\n concurrency: Concurrency = { limit: -1 }, // eslint-disable-line @typescript-eslint/no-unused-vars\n // -- WIP\n refresh: 'refresh' | 'cached' = 'refresh',\n customOut?: OutFn,\n filters: TestExecutionFilters = {}\n): RunTestsPromise {\n if (reRunManager !== undefined) {\n reRunManager.state.lastTestFiles = new Set(testFiles)\n reRunManager.state.activeRuns++ // whatever, obsolete. Can use it to check on things\n }\n\n const state = reRunManager?.state || makeRunManagerState()\n const runStartTime = new Date().getTime()\n const runNumber = agentRunStart(config, testFiles.size)\n const stop = makeStopSignal()\n //console.log('new runTests:', watchRunState?.activeRuns)\n //console.log(inspect(testFiles))\n\n try {\n const testRunPromises: Record<string, Promise<TestResult>> = {}\n const erroringTests: Record<string, TestFile> = {}\n const testRunOutPromises: Record<string, Promise<void>> = {}\n\n await timeProfileAsync('run tests', async () => {\n if (Array.isArray(testFiles) ? testFiles.length > 0 : testFiles.size > 0) {\n for (const testFile of testFiles) {\n //console.log('testFiles', testFiles)\n //console.log('testFile', testFile)\n\n // an earlier file failed under failFast - leave the rest unloaded\n if (stop.stopped) break\n\n const test = await loadTestFile(config, testFile, refresh)\n\n if (test.error !== undefined && test.error !== false) {\n erroringTests[testFile] = test\n state.failedTestFiles.add(testFile)\n showTestLoadingError(config, test, customOut)\n stopIfFailFast(config, stop, true)\n } else {\n const testRunPromise = runTest(config, test, filters, stop)\n testRunPromises[testFile] = testRunPromise\n\n testRunOutPromises[testFile] = testRunPromise.then(testResult => {\n if (testResult.success === true || testResult.success === null) {\n state.failedTestFiles.delete(testResult.file)\n } else {\n state.failedTestFiles.add(testFile)\n }\n const executionFilterActive = filters.describe !== undefined || filters.it !== undefined\n // a filter makes files and describes look empty when they are not\n if (!executionFilterActive) recordEntitiesWithoutTests(state, testFile, testResult)\n if (!executionFilterActive || testResult.describeResultsSummary.itsTotal > 0) {\n showTestFileResult(config, testResult, customOut)\n }\n })\n\n // wait for the test to complete before proceeding if we are in 'sequential' mode\n if (config.execution.files === 'sequential') await testRunOutPromises[testFile]\n }\n }\n }\n })\n\n let runTestResults: Record<string, TestResult> = {}\n\n await timeProfileAsync('wait for tests to finish', async () => {\n runTestResults = await promiseAllObj(testRunPromises)\n\n // wait for the output to complete\n await promiseAllObj(testRunOutPromises)\n })\n\n return showTestResultsSummary(\n runTestResults, erroringTests, runStartTime,\n config, runNumber, state, stop, filters, customOut\n )\n \n } finally {\n if (reRunManager !== undefined) reRunManager.state.activeRuns--\n }\n}\n"]}
1
+ {"version":3,"file":"runTests.js","sourceRoot":"","sources":["../../../src/cli/lib/runTests.ts"],"names":[],"mappings":"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","sourcesContent":["\nimport { allObj as promiseAllObj } from 'sxy-lib/promises.js'\n\nimport type { FinalConfig, OutFn } from '../../types.js'\nimport type { TestExecutionFilters } from '../cliTypes.js'\nimport type { ReRunManager } from './reRunManager.js'\nimport { createTimeProfiler } from '../../packages/timeProfiler.js'\nimport { showTimeProfiling } from '../../config.js'\nimport { loadTestFile, type TestFile } from './loadTestFile.js'\nimport { runTest, type TestResult } from './runTest.js'\nimport { showTestLoadingError } from './showTestLoadingError.js'\nimport { showTestFileResult } from './showTestFileResult.js'\nimport { agentRunStart, currentRunReason, type RunReason } from './agentReport.js'\nimport { makeStopSignal, stopIfFailFast } from './stopSignal.js'\nimport { makeRunManagerState, type ReRunManageState } from './makeWatchRunState.js'\nimport { showTestResultsSummary } from './showTestResultsSummary.js'\n\n\nconst { timeProfileAsync } = createTimeProfiler(showTimeProfiling)\n\n// shown after the summary's tallies, for the runs asked for from the keyboard only\nconst runLabels: Record<RunReason, string | undefined> = {\n startup: undefined, once: undefined, auto: undefined, all: 'all', last: 'last', failed: 'failures'\n}\n\nexport interface TestsResultsSummary {\n total: number\n passes: number\n invalid: number\n describesTotal: number\n describesPasses: number\n describesInvalid: number\n itsTotal: number\n itsPasses: number\n}\n\nexport type RunTestsPromise = Promise<boolean | null>\n\n\nexport type Concurrency = {\n limit: number\n}\n\nfunction recordEntitiesWithoutTests(state: ReRunManageState, testFile: string, testResult: TestResult): void {\n const describes = testResult.describeResults\n .filter(describeResult => describeResult.success === null)\n .map(describeResult => describeResult.thing)\n const fileWithoutTests = testResult.success === null\n if (fileWithoutTests || describes.length > 0) {\n state.withoutTests.set(testFile, {\n file: testResult.sourceFile ?? testResult.file, fileWithoutTests, describes\n })\n } else {\n state.withoutTests.delete(testFile)\n }\n}\n\nexport async function runTests(\n config: FinalConfig,\n testFiles: Set<string>,\n reRunManager: ReRunManager | undefined,\n concurrency: Concurrency = { limit: -1 }, // eslint-disable-line @typescript-eslint/no-unused-vars\n // -- WIP\n refresh: 'refresh' | 'cached' = 'refresh',\n customOut?: OutFn,\n filters: TestExecutionFilters = {}\n): RunTestsPromise {\n if (reRunManager !== undefined) {\n reRunManager.state.lastTestFiles = new Set(testFiles)\n reRunManager.state.activeRuns++ // whatever, obsolete. Can use it to check on things\n }\n\n const state = reRunManager?.state || makeRunManagerState()\n const runStartTime = new Date().getTime()\n const runLabel = runLabels[currentRunReason()]\n const runNumber = agentRunStart(config, testFiles.size)\n const stop = makeStopSignal()\n //console.log('new runTests:', watchRunState?.activeRuns)\n //console.log(inspect(testFiles))\n\n try {\n const testRunPromises: Record<string, Promise<TestResult>> = {}\n const erroringTests: Record<string, TestFile> = {}\n const testRunOutPromises: Record<string, Promise<void>> = {}\n\n await timeProfileAsync('run tests', async () => {\n if (Array.isArray(testFiles) ? testFiles.length > 0 : testFiles.size > 0) {\n for (const testFile of testFiles) {\n //console.log('testFiles', testFiles)\n //console.log('testFile', testFile)\n\n // an earlier file failed under failFast - leave the rest unloaded\n if (stop.stopped) break\n\n const test = await loadTestFile(config, testFile, refresh)\n\n if (test.error !== undefined && test.error !== false) {\n erroringTests[testFile] = test\n state.failedTestFiles.add(testFile)\n showTestLoadingError(config, test, customOut)\n stopIfFailFast(config, stop, true)\n } else {\n const testRunPromise = runTest(config, test, filters, stop)\n testRunPromises[testFile] = testRunPromise\n\n testRunOutPromises[testFile] = testRunPromise.then(testResult => {\n if (testResult.success === true || testResult.success === null) {\n state.failedTestFiles.delete(testResult.file)\n } else {\n state.failedTestFiles.add(testFile)\n }\n const executionFilterActive = filters.describe !== undefined || filters.it !== undefined\n // a filter makes files and describes look empty when they are not\n if (!executionFilterActive) recordEntitiesWithoutTests(state, testFile, testResult)\n if (!executionFilterActive || testResult.describeResultsSummary.itsTotal > 0) {\n showTestFileResult(config, testResult, customOut)\n }\n })\n\n // wait for the test to complete before proceeding if we are in 'sequential' mode\n if (config.execution.files === 'sequential') await testRunOutPromises[testFile]\n }\n }\n }\n })\n\n let runTestResults: Record<string, TestResult> = {}\n\n await timeProfileAsync('wait for tests to finish', async () => {\n runTestResults = await promiseAllObj(testRunPromises)\n\n // wait for the output to complete\n await promiseAllObj(testRunOutPromises)\n })\n\n return showTestResultsSummary(\n runTestResults, erroringTests, runStartTime,\n config, runNumber, state, stop, filters, customOut, runLabel\n )\n \n } finally {\n if (reRunManager !== undefined) reRunManager.state.activeRuns--\n }\n}\n"]}
@@ -4,5 +4,5 @@ import type { TestFile } from './loadTestFile.js';
4
4
  import type { ReRunManageState } from './makeWatchRunState.js';
5
5
  import type { TestResult } from './runTest.js';
6
6
  import type { StopSignal } from './stopSignal.js';
7
- export declare function showTestResultsSummary(runTestResults: Record<string, TestResult>, erroringTests: Record<string, TestFile>, runStartTime: number, config: FinalConfig, runNumber: number, state: ReRunManageState, stop: StopSignal, filters: TestExecutionFilters, customOut?: OutFn): boolean | null;
7
+ export declare function showTestResultsSummary(runTestResults: Record<string, TestResult>, erroringTests: Record<string, TestFile>, runStartTime: number, config: FinalConfig, runNumber: number, state: ReRunManageState, stop: StopSignal, filters: TestExecutionFilters, customOut?: OutFn, runLabel?: string): boolean | null;
8
8
  //# sourceMappingURL=showTestResultsSummary.d.ts.map
@@ -19,7 +19,9 @@ function plural(count, noun, verb) {
19
19
  return '';
20
20
  return `${count} ${noun}${count === 1 ? '' : 's'} ${verb}`;
21
21
  }
22
- export function showTestResultsSummary(runTestResults, erroringTests, runStartTime, config, runNumber, state, stop, filters, customOut) {
22
+ export function showTestResultsSummary(runTestResults, erroringTests, runStartTime, config, runNumber, state, stop, filters, customOut,
23
+ // what the run covered, e.g. 'all' or 'failures', shown uncoloured after the tallies
24
+ runLabel) {
23
25
  return timeProfile('show test run result summary', () => {
24
26
  const out = customOut ?? mainOut;
25
27
  const testsResultsSummary = {
@@ -175,6 +177,8 @@ export function showTestResultsSummary(runTestResults, erroringTests, runStartTi
175
177
  : '';
176
178
  if (stoppedPlain !== '')
177
179
  out(chalk.orange(stoppedPlain));
180
+ if (runLabel !== undefined)
181
+ out(`(${runLabel})`);
178
182
  const summaryLines = [
179
183
  testsText,
180
184
  joinPlain(describesText, describesThrownPlain, invalidDescribesPlain),