sxy-test-runner 2.5.4 → 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.
- package/AGENTS.md +40 -16
- package/dist/cli/lib/agentReport.d.ts +1 -0
- package/dist/cli/lib/agentReport.js +3 -0
- package/dist/cli/lib/agentReport.js.map +1 -1
- package/dist/cli/lib/esmGraphReload.d.ts +5 -0
- package/dist/cli/lib/esmGraphReload.js +24 -0
- package/dist/cli/lib/esmGraphReload.js.map +1 -0
- package/dist/cli/lib/esmTreeReload.d.ts +4 -0
- package/dist/cli/lib/esmTreeReload.js +62 -0
- package/dist/cli/lib/esmTreeReload.js.map +1 -0
- package/dist/cli/lib/handleUserInput.d.ts +2 -2
- package/dist/cli/lib/handleUserInput.js +13 -6
- package/dist/cli/lib/handleUserInput.js.map +1 -1
- package/dist/cli/lib/listenToUser.d.ts +2 -2
- package/dist/cli/lib/listenToUser.js.map +1 -1
- package/dist/cli/lib/loadTestFile.d.ts +0 -1
- package/dist/cli/lib/loadTestFile.js +23 -8
- package/dist/cli/lib/loadTestFile.js.map +1 -1
- package/dist/cli/lib/makeDependencyGraph.d.ts +1 -0
- package/dist/cli/lib/makeDependencyGraph.js +34 -15
- package/dist/cli/lib/makeDependencyGraph.js.map +1 -1
- package/dist/cli/lib/reRunManager.js +4 -6
- package/dist/cli/lib/reRunManager.js.map +1 -1
- package/dist/cli/lib/runTests.js +7 -2
- package/dist/cli/lib/runTests.js.map +1 -1
- package/dist/cli/lib/showTestResultsSummary.d.ts +1 -1
- package/dist/cli/lib/showTestResultsSummary.js +5 -1
- package/dist/cli/lib/showTestResultsSummary.js.map +1 -1
- package/dist/cli/lib/statusFile.js +20 -1
- package/dist/cli/lib/statusFile.js.map +1 -1
- package/dist/cli/lib/watchFilesAndRunTestsNodeWatch.js +3 -4
- package/dist/cli/lib/watchFilesAndRunTestsNodeWatch.js.map +1 -1
- package/dist/cli/watch.js +14 -1
- package/dist/cli/watch.js.map +1 -1
- package/dist/describe.js +5 -3
- package/dist/describe.js.map +1 -1
- package/dist/meta.js +2 -1
- package/dist/meta.js.map +1 -1
- package/dist/registration.d.ts +4 -0
- package/dist/registration.js +34 -0
- package/dist/registration.js.map +1 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.js.map +1 -1
- package/package.json +5 -2
- package/readme.md +15 -122
- package/dist/packages/esmReload.d.ts +0 -11
- package/dist/packages/esmReload.js +0 -43
- package/dist/packages/esmReload.js.map +0 -1
package/AGENTS.md
CHANGED
|
@@ -138,12 +138,11 @@ filters select no runnable tests, `once` exits `22` - `21` means no test file ma
|
|
|
138
138
|
stdout - nothing else is printed, setup progress included. `--status-file` is watch-only,
|
|
139
139
|
writing the latest run's result to a file. Both are covered under "Watch mode" below.
|
|
140
140
|
|
|
141
|
-
Describe and test filters run after the selected test files have loaded
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
`--filter`
|
|
146
|
-
`--filter-it` only for further narrowing inside those files.
|
|
141
|
+
Describe and test filters run after the selected test files have loaded, so every selected
|
|
142
|
+
file and its module graph is still imported and evaluated. Loading many files and filtering
|
|
143
|
+
their registered describes or tests afterward can therefore be much slower than file
|
|
144
|
+
filtering. Prefer `--filter` when a file glob can narrow the run, then use
|
|
145
|
+
`--filter-describe` or `--filter-it` only for further narrowing inside those files.
|
|
147
146
|
|
|
148
147
|
`--fail-fast` (`-x`) stops the run at the first failure - whether that is a failing test, a
|
|
149
148
|
describe that threw, or a test file that would not load. It is a `once` flag with no config
|
|
@@ -179,14 +178,23 @@ forms exclude exactly the same files, so it costs nothing to prefer the shorter
|
|
|
179
178
|
|
|
180
179
|
## Test lifecycle and data flow
|
|
181
180
|
|
|
182
|
-
All test files run in one Node.js process.
|
|
183
|
-
|
|
184
|
-
static dependency graph. Importing the file registers its describe blocks in
|
|
185
|
-
`global.__sxy_test_runner__`; execution happens afterward.
|
|
181
|
+
All test files run in one Node.js process. Importing a test file registers its describe
|
|
182
|
+
blocks; execution happens afterward.
|
|
186
183
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
184
|
+
Modules are shared between test files, through Node's ordinary module cache: a module
|
|
185
|
+
imported by several test files is evaluated once, and its state carries from one file to the
|
|
186
|
+
next. Tests should set up any shared state they depend on rather than assume a fresh module.
|
|
187
|
+
How modules are reloaded in watch mode is covered under "Watch mode" below.
|
|
188
|
+
|
|
189
|
+
A describe belongs to the test file whose top level made it, directly or through a function
|
|
190
|
+
it calls - a shared suite in a helper module registers in whichever test file calls it. A
|
|
191
|
+
test file imported by another test file still runs its own describes when it is loaded
|
|
192
|
+
itself, but they are not run again as part of the file importing it, and a module that calls
|
|
193
|
+
`describe` as soon as it is imported registers nothing.
|
|
194
|
+
|
|
195
|
+
A module whose filename contains the explicit `._cached_.js` marker is never reloaded, in
|
|
196
|
+
any mode. Its static dependencies are cached with it, allowing explicitly shared state that
|
|
197
|
+
lasts for the whole process:
|
|
190
198
|
|
|
191
199
|
```js
|
|
192
200
|
import { database } from './fixtures/database._cached_.js'
|
|
@@ -333,15 +341,31 @@ for two things: startup stops paying for dependency analysis, which is the slowe
|
|
|
333
341
|
it on a large suite, and the blind spots below stop mattering, because nothing is being
|
|
334
342
|
predicted. On a small or fast suite it is often the better setting.
|
|
335
343
|
|
|
344
|
+
The same setting decides how a rerun reloads modules. Every test file in a rerun is
|
|
345
|
+
evaluated again, so its describes register afresh. Beyond that:
|
|
346
|
+
|
|
347
|
+
- With dependency evaluation on, only a changed file and the files that import it, directly
|
|
348
|
+
or not, are loaded again; everything else stays shared from earlier runs. A rerun with
|
|
349
|
+
nothing changed reloads only the test files themselves.
|
|
350
|
+
- With it off, each test file in a rerun gets a fresh copy of its whole static module graph.
|
|
351
|
+
|
|
352
|
+
In both, packages under `node_modules` are loaded once and never reloaded. A linked package
|
|
353
|
+
(a workspace or `npm link` dependency) resolves to its real path outside `node_modules`, so
|
|
354
|
+
it reloads like the project's own files.
|
|
355
|
+
|
|
336
356
|
Important limitations:
|
|
337
357
|
|
|
338
358
|
- Dependency analysis finds `import()` only when its path is a plain string literal. A path
|
|
339
359
|
computed at runtime (`import(name)`, `import('./' + name)`), or written as a template
|
|
340
360
|
literal, is not found, so a test reached only that way is not rerun when its dependency
|
|
341
361
|
changes. `watch.dependencyEvaluation: false` avoids this.
|
|
342
|
-
-
|
|
343
|
-
|
|
344
|
-
|
|
362
|
+
- With dependency evaluation on, a module is only reloaded when the watcher sees it change
|
|
363
|
+
or sees a change to something it imports. One it cannot connect - a file outside
|
|
364
|
+
`watch.watchFiles` such as a `.json` fixture, or one reached only through a computed
|
|
365
|
+
`import()` - keeps its first loaded copy for the rest of the session. Restart watch mode,
|
|
366
|
+
or set `watch.dependencyEvaluation: false`, to pick up such a change.
|
|
367
|
+
- All reruns still share one long-lived process. Global state and external resources are
|
|
368
|
+
never reset between runs and remain the test author's responsibility.
|
|
345
369
|
- Modules imported through a `._cached_.*` boundary also remain loaded for the life of the
|
|
346
370
|
process. Keep mutable access sequential or provide application-level synchronization,
|
|
347
371
|
and arrange explicit cleanup for resources such as servers and database connections.
|
|
@@ -8,6 +8,7 @@ export declare function agentEnabled(config: FinalConfig): boolean;
|
|
|
8
8
|
export declare function agentInit(enabled: boolean): void;
|
|
9
9
|
export declare function agentFound(config: FinalConfig, files: number): void;
|
|
10
10
|
export declare function agentSetRunReason(reason: RunReason): void;
|
|
11
|
+
export declare function currentRunReason(): RunReason;
|
|
11
12
|
export declare function agentRunStart(config: FinalConfig, files: number): number;
|
|
12
13
|
export declare function agentFailure(config: FinalConfig, run: number, entry: FailureEntry): void;
|
|
13
14
|
export declare function agentDone(config: FinalConfig, run: number, code: StatusCode, description: string, summary: TestsResultsSummary | null, durationMs: number, failingFiles: number): void;
|
|
@@ -40,6 +40,9 @@ export function agentFound(config, files) {
|
|
|
40
40
|
export function agentSetRunReason(reason) {
|
|
41
41
|
nextRunReason = reason;
|
|
42
42
|
}
|
|
43
|
+
export function currentRunReason() {
|
|
44
|
+
return nextRunReason;
|
|
45
|
+
}
|
|
43
46
|
// claims the next run number and announces the run, so a reader knows work is in flight
|
|
44
47
|
// before any result arrives
|
|
45
48
|
export function agentRunStart(config, files) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agentReport.js","sourceRoot":"","sources":["../../../src/cli/lib/agentReport.ts"],"names":[],"mappings":"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","sourcesContent":["\nimport type { FinalConfig } from '../../types.js'\nimport type { FailureEntry } from './failureEntries.js'\nimport type { RunType } from './reRunManager.js'\nimport type { TestsResultsSummary } from './runTests.js'\nimport { out as mainOut } from '../../output.js'\nimport { statusNames, type StatusCode } from './statusFile.js'\n\n\n// Agent mode reports a watch session as a stream of single lines, for a reader tailing the\n// process rather than watching the terminal. Every line is one complete event:\n//\n// [sxyt] <event> | <key>=<value> | <key>=<value>\n//\n// Values are never quoted, so free text is flattened to keep the grammar intact - one event\n// can never span two lines, and a stray `|` can never look like a field boundary.\n\nconst prefix = '[sxyt]'\n\nexport type RunReason = RunType | 'startup' | 'once'\n\nlet runCounter = 0\n// requestRun sets this before handing a run to runTests; the initial run never goes through\n// the queue, so 'startup' is the right default\nlet nextRunReason: RunReason = 'startup'\n\ntype Field = [string, string | number]\n\nfunction emit(event: string, fields: Field[] = []): void {\n mainOut(prefix + ` ${event}` + fields.map(([key, value]) => ` | ${key}=${value}`).join(''))\n}\n\n// newlines would split one event across two lines, and a `|` would read as a field boundary\nfunction flatten(text: string): string {\n return text.replace(/\\s*[\\r\\n]+\\s*/g, ' ').replace(/\\|/g, '/').trim()\n}\n\nexport function agentEnabled(config: FinalConfig): boolean {\n return config.agent\n}\n\n// the process is up, before the config is even loaded - so a slow startup is\n// distinguishable from one that never began. Takes the flag rather than the config for that\n// reason, and resets the counters so repeated in process sessions start from 1.\nexport function agentInit(enabled: boolean): void {\n runCounter = 0\n nextRunReason = 'startup'\n if (!enabled) return\n emit('init', [['pid', process.pid]])\n}\n\nexport function agentFound(config: FinalConfig, files: number): void {\n if (!agentEnabled(config)) return\n emit('found', [['files', files]])\n}\n\nexport function agentSetRunReason(reason: RunReason): void {\n nextRunReason = reason\n}\n\n// claims the next run number and announces the run, so a reader knows work is in flight\n// before any result arrives\nexport function agentRunStart(config: FinalConfig, files: number): number {\n if (!agentEnabled(config)) return 0\n runCounter++\n emit('running', [\n ['n', runCounter],\n ['files', files],\n ['why', nextRunReason],\n ['covers', runScope(config)]\n ])\n return runCounter\n}\n\n// what the run was built from. A reader needs this to know how far the run's own `status`\n// generalises - only a run covering everything can speak for the whole suite.\nfunction runScope(config: FinalConfig): string {\n // known before the run starts: fail fast means it is built to stop early, so it cannot\n // promise to have covered anything beyond the first failure\n if (config.failFast) return 'until-first-failure'\n\n switch (nextRunReason) {\n case 'startup':\n case 'once':\n case 'all': return 'all'\n case 'last': return 'last'\n case 'failed': return 'failing'\n case 'auto': {\n // with dependency evaluation off a change runs the whole suite, so an auto run\n // carries the same weight as a startup one\n if (!config.watch.dependencyEvaluation) return 'all'\n // the reRunFailingTests case is the one worth spelling out: without it a run says\n // nothing about files that were already failing\n return (config.watch.reRunFailingTests) ? 'changed+failing' : 'changed'\n }\n default: return 'changed'\n }\n}\n\n// failures are emitted before the run's done line, so done is a complete end of run marker\nexport function agentFailure(config: FinalConfig, run: number, entry: FailureEntry): void {\n if (!agentEnabled(config)) return\n emit('fail', [\n ['n', run],\n ['file', flatten(entry.file)],\n ...(entry.describe === undefined)\n ? []\n : [['describe', flatten(entry.describe)] as Field],\n ...(entry.test === undefined) ? [] : [['test', flatten(entry.test)] as Field],\n ['error', flatten(entry.error)]\n ])\n}\n\n// `failing` counts the test files still failing across the whole session, not just this\n// run. A watch re-run usually covers only the changed files, so its own tallies can read as\n// a clean pass while the suite is still broken elsewhere.\nexport function agentDone(\n config: FinalConfig,\n run: number,\n code: StatusCode,\n description: string,\n summary: TestsResultsSummary | null,\n durationMs: number,\n failingFiles: number\n): void {\n if (!agentEnabled(config)) return\n emit('done', [\n ['n', run],\n // named, not numbered: `status=failed` needs no lookup table, and cannot be misread\n // as the process exit code the way a bare `code=1` could\n ['status', statusNames[code]],\n ['desc', flatten(description)],\n ...(summary === null) ? [] : [\n ['tests', `${summary.itsPasses}/${summary.itsTotal}`] as Field,\n ['items', `${summary.describesPasses}/${summary.describesTotal}`] as Field,\n ['files', `${summary.passes}/${summary.total}`] as Field\n ],\n ['failing', failingFiles],\n ['ms', durationMs]\n ])\n}\n\nexport function agentWatching(config: FinalConfig, files: number): void {\n if (!agentEnabled(config)) return\n emit('watching', [['files', files]])\n}\n\nexport function agentChange(config: FinalConfig, file: string, event: string): void {\n if (!agentEnabled(config)) return\n emit('change', [['file', flatten(file)], ['event', flatten(event)]])\n}\n\n// agent mode owns stdout, so the ordinary coloured output is routed into a sink\nexport function agentSilentOut(): void {\n // deliberately nothing - the agent lines are the whole stream\n}\n"]}
|
|
1
|
+
{"version":3,"file":"agentReport.js","sourceRoot":"","sources":["../../../src/cli/lib/agentReport.ts"],"names":[],"mappings":"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","sourcesContent":["\nimport type { FinalConfig } from '../../types.js'\nimport type { FailureEntry } from './failureEntries.js'\nimport type { RunType } from './reRunManager.js'\nimport type { TestsResultsSummary } from './runTests.js'\nimport { out as mainOut } from '../../output.js'\nimport { statusNames, type StatusCode } from './statusFile.js'\n\n\n// Agent mode reports a watch session as a stream of single lines, for a reader tailing the\n// process rather than watching the terminal. Every line is one complete event:\n//\n// [sxyt] <event> | <key>=<value> | <key>=<value>\n//\n// Values are never quoted, so free text is flattened to keep the grammar intact - one event\n// can never span two lines, and a stray `|` can never look like a field boundary.\n\nconst prefix = '[sxyt]'\n\nexport type RunReason = RunType | 'startup' | 'once'\n\nlet runCounter = 0\n// requestRun sets this before handing a run to runTests; the initial run never goes through\n// the queue, so 'startup' is the right default\nlet nextRunReason: RunReason = 'startup'\n\ntype Field = [string, string | number]\n\nfunction emit(event: string, fields: Field[] = []): void {\n mainOut(prefix + ` ${event}` + fields.map(([key, value]) => ` | ${key}=${value}`).join(''))\n}\n\n// newlines would split one event across two lines, and a `|` would read as a field boundary\nfunction flatten(text: string): string {\n return text.replace(/\\s*[\\r\\n]+\\s*/g, ' ').replace(/\\|/g, '/').trim()\n}\n\nexport function agentEnabled(config: FinalConfig): boolean {\n return config.agent\n}\n\n// the process is up, before the config is even loaded - so a slow startup is\n// distinguishable from one that never began. Takes the flag rather than the config for that\n// reason, and resets the counters so repeated in process sessions start from 1.\nexport function agentInit(enabled: boolean): void {\n runCounter = 0\n nextRunReason = 'startup'\n if (!enabled) return\n emit('init', [['pid', process.pid]])\n}\n\nexport function agentFound(config: FinalConfig, files: number): void {\n if (!agentEnabled(config)) return\n emit('found', [['files', files]])\n}\n\nexport function agentSetRunReason(reason: RunReason): void {\n nextRunReason = reason\n}\n\nexport function currentRunReason(): RunReason {\n return nextRunReason\n}\n\n// claims the next run number and announces the run, so a reader knows work is in flight\n// before any result arrives\nexport function agentRunStart(config: FinalConfig, files: number): number {\n if (!agentEnabled(config)) return 0\n runCounter++\n emit('running', [\n ['n', runCounter],\n ['files', files],\n ['why', nextRunReason],\n ['covers', runScope(config)]\n ])\n return runCounter\n}\n\n// what the run was built from. A reader needs this to know how far the run's own `status`\n// generalises - only a run covering everything can speak for the whole suite.\nfunction runScope(config: FinalConfig): string {\n // known before the run starts: fail fast means it is built to stop early, so it cannot\n // promise to have covered anything beyond the first failure\n if (config.failFast) return 'until-first-failure'\n\n switch (nextRunReason) {\n case 'startup':\n case 'once':\n case 'all': return 'all'\n case 'last': return 'last'\n case 'failed': return 'failing'\n case 'auto': {\n // with dependency evaluation off a change runs the whole suite, so an auto run\n // carries the same weight as a startup one\n if (!config.watch.dependencyEvaluation) return 'all'\n // the reRunFailingTests case is the one worth spelling out: without it a run says\n // nothing about files that were already failing\n return (config.watch.reRunFailingTests) ? 'changed+failing' : 'changed'\n }\n default: return 'changed'\n }\n}\n\n// failures are emitted before the run's done line, so done is a complete end of run marker\nexport function agentFailure(config: FinalConfig, run: number, entry: FailureEntry): void {\n if (!agentEnabled(config)) return\n emit('fail', [\n ['n', run],\n ['file', flatten(entry.file)],\n ...(entry.describe === undefined)\n ? []\n : [['describe', flatten(entry.describe)] as Field],\n ...(entry.test === undefined) ? [] : [['test', flatten(entry.test)] as Field],\n ['error', flatten(entry.error)]\n ])\n}\n\n// `failing` counts the test files still failing across the whole session, not just this\n// run. A watch re-run usually covers only the changed files, so its own tallies can read as\n// a clean pass while the suite is still broken elsewhere.\nexport function agentDone(\n config: FinalConfig,\n run: number,\n code: StatusCode,\n description: string,\n summary: TestsResultsSummary | null,\n durationMs: number,\n failingFiles: number\n): void {\n if (!agentEnabled(config)) return\n emit('done', [\n ['n', run],\n // named, not numbered: `status=failed` needs no lookup table, and cannot be misread\n // as the process exit code the way a bare `code=1` could\n ['status', statusNames[code]],\n ['desc', flatten(description)],\n ...(summary === null) ? [] : [\n ['tests', `${summary.itsPasses}/${summary.itsTotal}`] as Field,\n ['items', `${summary.describesPasses}/${summary.describesTotal}`] as Field,\n ['files', `${summary.passes}/${summary.total}`] as Field\n ],\n ['failing', failingFiles],\n ['ms', durationMs]\n ])\n}\n\nexport function agentWatching(config: FinalConfig, files: number): void {\n if (!agentEnabled(config)) return\n emit('watching', [['files', files]])\n}\n\nexport function agentChange(config: FinalConfig, file: string, event: string): void {\n if (!agentEnabled(config)) return\n emit('change', [['file', flatten(file)], ['event', flatten(event)]])\n}\n\n// agent mode owns stdout, so the ordinary coloured output is routed into a sink\nexport function agentSilentOut(): void {\n // deliberately nothing - the agent lines are the whole stream\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { registerHooks } from 'node:module';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
const cachedModulePattern = /\._cached_\.(?:[cm]?js|[cm]?ts|jsx|tsx)$/i;
|
|
4
|
+
export function registerGraphReload(getVersion) {
|
|
5
|
+
const resolve = (specifier, context, nextResolve) => {
|
|
6
|
+
const result = nextResolve(specifier, context);
|
|
7
|
+
if (!result.url.startsWith('file:'))
|
|
8
|
+
return result;
|
|
9
|
+
const url = new URL(result.url);
|
|
10
|
+
if (cachedModulePattern.test(url.pathname))
|
|
11
|
+
return result;
|
|
12
|
+
// packages load once; a linked one resolves to its real path outside node_modules, so still reloads
|
|
13
|
+
if (url.pathname.includes('/node_modules/'))
|
|
14
|
+
return result;
|
|
15
|
+
const version = getVersion(fileURLToPath(url));
|
|
16
|
+
if (version === undefined)
|
|
17
|
+
return result;
|
|
18
|
+
url.searchParams.set('sxyt-v', String(version));
|
|
19
|
+
return { ...result, url: url.href };
|
|
20
|
+
};
|
|
21
|
+
const handle = registerHooks({ resolve });
|
|
22
|
+
return { handle };
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=esmGraphReload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"esmGraphReload.js","sourceRoot":"","sources":["../../../src/cli/lib/esmGraphReload.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAwB,MAAM,aAAa,CAAA;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAMxC,MAAM,mBAAmB,GAAG,2CAA2C,CAAA;AAEvE,MAAM,UAAU,mBAAmB,CAAC,UAAyB;IACzD,MAAM,OAAO,GAAoB,CAAC,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE;QACjE,MAAM,MAAM,GAAG,WAAW,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;QAC9C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO,MAAM,CAAA;QAElD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAC/B,IAAI,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,OAAO,MAAM,CAAA;QACzD,oGAAoG;QACpG,IAAI,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,gBAAgB,CAAC;YAAE,OAAO,MAAM,CAAA;QAE1D,MAAM,OAAO,GAAG,UAAU,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAA;QAC9C,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,MAAM,CAAA;QAExC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAA;QAC/C,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,CAAA;IACvC,CAAC,CAAA;IAED,MAAM,MAAM,GAAG,aAAa,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;IACzC,OAAO,EAAE,MAAM,EAAE,CAAA;AACrB,CAAC","sourcesContent":["\nimport { registerHooks, type ResolveHookSync } from 'node:module'\nimport { fileURLToPath } from 'node:url'\n\n\n// a module's current version, or undefined to leave it on Node's ordinary module cache\nexport type VersionLookup = (file: string) => number | undefined\n\nconst cachedModulePattern = /\\._cached_\\.(?:[cm]?js|[cm]?ts|jsx|tsx)$/i\n\nexport function registerGraphReload(getVersion: VersionLookup) {\n const resolve: ResolveHookSync = (specifier, context, nextResolve) => {\n const result = nextResolve(specifier, context)\n if (!result.url.startsWith('file:')) return result\n\n const url = new URL(result.url)\n if (cachedModulePattern.test(url.pathname)) return result\n // packages load once; a linked one resolves to its real path outside node_modules, so still reloads\n if (url.pathname.includes('/node_modules/')) return result\n\n const version = getVersion(fileURLToPath(url))\n if (version === undefined) return result\n\n url.searchParams.set('sxyt-v', String(version))\n return { ...result, url: url.href }\n }\n \n const handle = registerHooks({ resolve })\n return { handle }\n}\n"]}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { isBuiltin, registerHooks } from 'node:module';
|
|
2
|
+
/*
|
|
3
|
+
* Based on esm-reload by Marcel Laverdet and Pierre-Yves Gérardy.
|
|
4
|
+
*
|
|
5
|
+
* ISC License
|
|
6
|
+
*
|
|
7
|
+
* Copyright 2024 Marcel Laverdet & Pierre-Yves Gérardy
|
|
8
|
+
*
|
|
9
|
+
* Permission to use, copy, modify, and/or distribute this software for any
|
|
10
|
+
* purpose with or without fee is hereby granted, provided that the above
|
|
11
|
+
* copyright notice and this permission notice appear in all copies.
|
|
12
|
+
*
|
|
13
|
+
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
14
|
+
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
15
|
+
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
16
|
+
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
17
|
+
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
18
|
+
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
19
|
+
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
20
|
+
*/
|
|
21
|
+
const cachedModulePattern = /\._cached_\.(?:[cm]?js|[cm]?ts|jsx|tsx)$/i;
|
|
22
|
+
let reloadId = 0;
|
|
23
|
+
function isThisModule(url) {
|
|
24
|
+
const candidate = new URL(url);
|
|
25
|
+
const current = new URL(import.meta.url);
|
|
26
|
+
candidate.search = '';
|
|
27
|
+
current.search = '';
|
|
28
|
+
return candidate.href === current.href;
|
|
29
|
+
}
|
|
30
|
+
function isCachedModule(url) {
|
|
31
|
+
return cachedModulePattern.test(url.pathname);
|
|
32
|
+
}
|
|
33
|
+
export const resolve = (specifier, context, nextResolve) => {
|
|
34
|
+
const result = nextResolve(specifier, context);
|
|
35
|
+
if (isThisModule(result.url) || isBuiltin(result.url) || context.parentURL === undefined) {
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
const url = new URL(result.url);
|
|
39
|
+
// A *._cached_.js/ts module and its dependency graph use Node's ordinary
|
|
40
|
+
// process-wide module cache instead of inheriting the test file instance.
|
|
41
|
+
if (isCachedModule(url))
|
|
42
|
+
return result;
|
|
43
|
+
// packages load once; a linked one resolves to its real path outside node_modules, so still reloads
|
|
44
|
+
if (url.pathname.includes('/node_modules/'))
|
|
45
|
+
return result;
|
|
46
|
+
const parentUrl = new URL(context.parentURL);
|
|
47
|
+
const instance = url.searchParams.get('reload') === ''
|
|
48
|
+
? `sxy-test-runner-reload-${reloadId++}`
|
|
49
|
+
: parentUrl.searchParams.get('sxyt-instance');
|
|
50
|
+
if (instance === null)
|
|
51
|
+
return result;
|
|
52
|
+
url.searchParams.delete('reload');
|
|
53
|
+
url.searchParams.set('sxyt-instance', instance);
|
|
54
|
+
return {
|
|
55
|
+
...result,
|
|
56
|
+
url: url.href
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
export function registerTreeReload() {
|
|
60
|
+
return registerHooks({ resolve });
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=esmTreeReload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"esmTreeReload.js","sourceRoot":"","sources":["../../../src/cli/lib/esmTreeReload.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,aAAa,EAA0C,MAAM,aAAa,CAAA;AAG9F;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,mBAAmB,GAAG,2CAA2C,CAAA;AAEvE,IAAI,QAAQ,GAAG,CAAC,CAAA;AAEhB,SAAS,YAAY,CAAC,GAAW;IAC7B,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAA;IAC9B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACxC,SAAS,CAAC,MAAM,GAAG,EAAE,CAAA;IACrB,OAAO,CAAC,MAAM,GAAG,EAAE,CAAA;IACnB,OAAO,SAAS,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI,CAAA;AAC1C,CAAC;AAED,SAAS,cAAc,CAAC,GAAQ;IAC5B,OAAO,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;AACjD,CAAC;AAED,MAAM,CAAC,MAAM,OAAO,GAAoB,CAAC,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE;IACxE,MAAM,MAAM,GAAG,WAAW,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;IAE9C,IAAI,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QACvF,OAAO,MAAM,CAAA;IACjB,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAE/B,yEAAyE;IACzE,0EAA0E;IAC1E,IAAI,cAAc,CAAC,GAAG,CAAC;QAAE,OAAO,MAAM,CAAA;IACtC,oGAAoG;IACpG,IAAI,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,gBAAgB,CAAC;QAAE,OAAO,MAAM,CAAA;IAE1D,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IAC5C,MAAM,QAAQ,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE;QAClD,CAAC,CAAC,0BAA0B,QAAQ,EAAE,EAAE;QACxC,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,eAAe,CAAC,CAAA;IAEjD,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,MAAM,CAAA;IAEpC,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IACjC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAA;IAE/C,OAAO;QACH,GAAG,MAAM;QACT,GAAG,EAAE,GAAG,CAAC,IAAI;KAChB,CAAA;AACL,CAAC,CAAA;AAED,MAAM,UAAU,kBAAkB;IAC9B,OAAO,aAAa,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;AACrC,CAAC","sourcesContent":["\nimport { isBuiltin, registerHooks, type ModuleHooks, type ResolveHookSync } from 'node:module'\n\n\n/*\n * Based on esm-reload by Marcel Laverdet and Pierre-Yves Gérardy.\n *\n * ISC License\n *\n * Copyright 2024 Marcel Laverdet & Pierre-Yves Gérardy\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\nconst cachedModulePattern = /\\._cached_\\.(?:[cm]?js|[cm]?ts|jsx|tsx)$/i\n\nlet reloadId = 0\n\nfunction isThisModule(url: string): boolean {\n const candidate = new URL(url)\n const current = new URL(import.meta.url)\n candidate.search = ''\n current.search = ''\n return candidate.href === current.href\n}\n\nfunction isCachedModule(url: URL): boolean {\n return cachedModulePattern.test(url.pathname)\n}\n\nexport const resolve: ResolveHookSync = (specifier, context, nextResolve) => {\n const result = nextResolve(specifier, context)\n\n if (isThisModule(result.url) || isBuiltin(result.url) || context.parentURL === undefined) {\n return result\n }\n\n const url = new URL(result.url)\n\n // A *._cached_.js/ts module and its dependency graph use Node's ordinary\n // process-wide module cache instead of inheriting the test file instance.\n if (isCachedModule(url)) return result\n // packages load once; a linked one resolves to its real path outside node_modules, so still reloads\n if (url.pathname.includes('/node_modules/')) return result\n\n const parentUrl = new URL(context.parentURL)\n const instance = url.searchParams.get('reload') === ''\n ? `sxy-test-runner-reload-${reloadId++}`\n : parentUrl.searchParams.get('sxyt-instance')\n\n if (instance === null) return result\n\n url.searchParams.delete('reload')\n url.searchParams.set('sxyt-instance', instance)\n\n return {\n ...result,\n url: url.href\n }\n}\n\nexport function registerTreeReload(): ModuleHooks {\n return registerHooks({ resolve })\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReRunManager } from './reRunManager.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { RerunTestsFunction } from './listenToUser.js';
|
|
3
3
|
import type { OutFn } from '../../types.js';
|
|
4
|
-
export declare function handleUserInput(input: string, reRunManager: ReRunManager, rerunTestsCallback:
|
|
4
|
+
export declare function handleUserInput(input: string, reRunManager: ReRunManager, rerunTestsCallback: RerunTestsFunction, customOut?: OutFn): void;
|
|
5
5
|
//# sourceMappingURL=handleUserInput.d.ts.map
|
|
@@ -25,28 +25,35 @@ export function handleUserInput(input, reRunManager, rerunTestsCallback, customO
|
|
|
25
25
|
//console.log('watchRunState.activeRuns', watchRunState.activeRuns)
|
|
26
26
|
// if (rerunManager.watchRunState.activeRuns > 0) return
|
|
27
27
|
let type;
|
|
28
|
-
let
|
|
28
|
+
let testFilesSet;
|
|
29
29
|
if (command === 'r' || command === 'l') {
|
|
30
30
|
type = 'last';
|
|
31
|
-
|
|
31
|
+
testFilesSet = reRunManager.state.lastTestFiles;
|
|
32
32
|
}
|
|
33
33
|
else if (command === 'a') {
|
|
34
34
|
type = 'all';
|
|
35
|
-
|
|
35
|
+
testFilesSet = reRunManager.state.allTestFiles;
|
|
36
36
|
}
|
|
37
37
|
else if (command === 'f') {
|
|
38
38
|
type = 'failed';
|
|
39
|
-
|
|
39
|
+
testFilesSet = reRunManager.state.failedTestFiles;
|
|
40
40
|
}
|
|
41
41
|
else {
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
44
|
//console.log('type', type)
|
|
45
45
|
//console.log('testFiles', testFiles)
|
|
46
|
-
if (
|
|
46
|
+
if (testFilesSet.size === 0) {
|
|
47
47
|
out('No suitable test files to run');
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
|
-
void reRunManager.requestRun(type,
|
|
50
|
+
void reRunManager.requestRun(type, () => {
|
|
51
|
+
if (testFilesSet.size === 0) {
|
|
52
|
+
out(type + ': No suitable test files to run');
|
|
53
|
+
return Promise.resolve(null);
|
|
54
|
+
}
|
|
55
|
+
return rerunTestsCallback(new Set(testFilesSet));
|
|
56
|
+
});
|
|
57
|
+
//rerunTestsCallback(new Set(testFilesSet))) /// CHECK is it appropriate to copy here?
|
|
51
58
|
}
|
|
52
59
|
//# sourceMappingURL=handleUserInput.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handleUserInput.js","sourceRoot":"","sources":["../../../src/cli/lib/handleUserInput.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,MAAM,yBAAyB,CAAA;AAC3C,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAA;AAGrC,SAAS,gBAAgB,CAAC,KAAuB,EAAE,SAAgB;IAC/D,MAAM,OAAO,GAAG,CAAC,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,CAAA;IAChD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,SAAS,CAAC,qCAAqC,CAAC,CAAA;QAChD,OAAM;IACV,CAAC;IAED,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC,CAAA;IACvD,MAAM,MAAM,GAAG,IAAI,CAAA;IACnB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC1B,IAAI,KAAK,CAAC,gBAAgB;YAAE,SAAS,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA;QACxE,KAAK,MAAM,QAAQ,IAAI,KAAK,CAAC,SAAS;YAAE,SAAS,CAAC,GAAG,MAAM,GAAG,KAAK,CAAC,IAAI,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC/G,CAAC;AACL,CAAC;AAGD,MAAM,UAAU,eAAe,CAC3B,KAAa,EACb,YAA0B,EAC1B,
|
|
1
|
+
{"version":3,"file":"handleUserInput.js","sourceRoot":"","sources":["../../../src/cli/lib/handleUserInput.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,MAAM,yBAAyB,CAAA;AAC3C,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAA;AAGrC,SAAS,gBAAgB,CAAC,KAAuB,EAAE,SAAgB;IAC/D,MAAM,OAAO,GAAG,CAAC,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,CAAA;IAChD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,SAAS,CAAC,qCAAqC,CAAC,CAAA;QAChD,OAAM;IACV,CAAC;IAED,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC,CAAA;IACvD,MAAM,MAAM,GAAG,IAAI,CAAA;IACnB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC1B,IAAI,KAAK,CAAC,gBAAgB;YAAE,SAAS,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA;QACxE,KAAK,MAAM,QAAQ,IAAI,KAAK,CAAC,SAAS;YAAE,SAAS,CAAC,GAAG,MAAM,GAAG,KAAK,CAAC,IAAI,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC/G,CAAC;AACL,CAAC;AAGD,MAAM,UAAU,eAAe,CAC3B,KAAa,EACb,YAA0B,EAC1B,kBAAsC,EACtC,YAAmB,GAAG;IAEtB,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAE1C,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC;QAClB,gBAAgB,CAAC,YAAY,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;QAC/C,OAAM;IACV,CAAC;IAED,iCAAiC;IAEjC,mEAAmE;IACnE,wDAAwD;IAExD,IAAI,IAAa,CAAA;IACjB,IAAI,YAAyB,CAAA;IAC7B,IAAI,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC;QACrC,IAAI,GAAG,MAAM,CAAA;QACb,YAAY,GAAG,YAAY,CAAC,KAAK,CAAC,aAAa,CAAA;IACnD,CAAC;SAAM,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC;QACzB,IAAI,GAAG,KAAK,CAAA;QACZ,YAAY,GAAG,YAAY,CAAC,KAAK,CAAC,YAAY,CAAA;IAClD,CAAC;SAAM,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC;QACzB,IAAI,GAAG,QAAQ,CAAA;QACf,YAAY,GAAG,YAAY,CAAC,KAAK,CAAC,eAAe,CAAA;IACrD,CAAC;SAAM,CAAC;QACJ,OAAM;IACV,CAAC;IAED,2BAA2B;IAC3B,qCAAqC;IAErC,IAAI,YAAY,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QAC1B,GAAG,CAAC,+BAA+B,CAAC,CAAA;QACpC,OAAM;IACV,CAAC;IACD,KAAK,YAAY,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,EAAE;QACpC,IAAI,YAAY,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YAC1B,GAAG,CAAC,IAAI,GAAG,iCAAiC,CAAC,CAAA;YAC7C,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QAChC,CAAC;QACD,OAAO,kBAAkB,CAAC,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC,CAAA;IACpD,CAAC,CAAC,CAAA;IACF,sFAAsF;AAE1F,CAAC","sourcesContent":["\r\nimport type { ReRunManager, RunType } from './reRunManager.js'\r\nimport type { RerunTestsFunction } from './listenToUser.js'\r\nimport type { ReRunManageState } from './makeWatchRunState.js'\r\nimport type { OutFn } from '../../types.js'\r\nimport chalk from '../../packages/chalk.js'\r\nimport { out } from '../../output.js'\r\n\r\n\r\nfunction showWithoutTests(state: ReRunManageState, customOut: OutFn): void {\r\n const entries = [...state.withoutTests.values()]\r\n if (entries.length === 0) {\r\n customOut('No files or describes without tests')\r\n return\r\n }\r\n\r\n customOut(chalk.grey('Files/describes without tests:'))\r\n const indent = ' '\r\n for (const entry of entries) {\r\n if (entry.fileWithoutTests) customOut(indent + chalk.yellow(entry.file))\r\n for (const describe of entry.describes) customOut(`${indent}${entry.file} > ` + chalk.brightblue(describe))\r\n }\r\n}\r\n\r\n\r\nexport function handleUserInput(\r\n input: string,\r\n reRunManager: ReRunManager,\r\n rerunTestsCallback: RerunTestsFunction,\r\n customOut: OutFn = out\r\n): void {\r\n const command = input.trim().toLowerCase()\r\n\r\n if (command === 'n') {\r\n showWithoutTests(reRunManager.state, customOut)\r\n return\r\n }\r\n\r\n //console.log('command', command)\r\n\r\n //console.log('watchRunState.activeRuns', watchRunState.activeRuns)\r\n // if (rerunManager.watchRunState.activeRuns > 0) return\r\n\r\n let type: RunType\r\n let testFilesSet: Set<string>\r\n if (command === 'r' || command === 'l') {\r\n type = 'last'\r\n testFilesSet = reRunManager.state.lastTestFiles\r\n } else if (command === 'a') {\r\n type = 'all'\r\n testFilesSet = reRunManager.state.allTestFiles\r\n } else if (command === 'f') {\r\n type = 'failed'\r\n testFilesSet = reRunManager.state.failedTestFiles\r\n } else {\r\n return\r\n }\r\n\r\n //console.log('type', type)\r\n //console.log('testFiles', testFiles)\r\n\r\n if (testFilesSet.size === 0) {\r\n out('No suitable test files to run')\r\n return\r\n }\r\n void reRunManager.requestRun(type, () => {\r\n if (testFilesSet.size === 0) {\r\n out(type + ': No suitable test files to run')\r\n return Promise.resolve(null)\r\n }\r\n return rerunTestsCallback(new Set(testFilesSet))\r\n })\r\n //rerunTestsCallback(new Set(testFilesSet))) /// CHECK is it appropriate to copy here?\r\n \r\n}\r\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { OutFn } from '../../types.js';
|
|
2
2
|
import { type ReRunManager } from './reRunManager.js';
|
|
3
3
|
import type { RunTestsPromise } from './runTests.js';
|
|
4
|
-
export type
|
|
5
|
-
export declare function listenToUser(rerunManager: ReRunManager, rerunTestsCallback:
|
|
4
|
+
export type RerunTestsFunction = (testFiles: Set<string>) => RunTestsPromise;
|
|
5
|
+
export declare function listenToUser(rerunManager: ReRunManager, rerunTestsCallback: RerunTestsFunction, ttyActionsOnKeypress: boolean | undefined, initialTestRun: Promise<boolean | null> | null, input?: NodeJS.ReadStream, customOut?: OutFn): () => void;
|
|
6
6
|
//# sourceMappingURL=listenToUser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listenToUser.js","sourceRoot":"","sources":["../../../src/cli/lib/listenToUser.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAY,MAAM,eAAe,CAAA;AAK7E,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,GAAG,IAAI,OAAO,EAAE,KAAK,IAAI,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAKrE,MAAM,UAAU,YAAY;AACxB,4BAA4B;AAC5B,4BAA4B;AAC5B,YAA0B,EAC1B,
|
|
1
|
+
{"version":3,"file":"listenToUser.js","sourceRoot":"","sources":["../../../src/cli/lib/listenToUser.ts"],"names":[],"mappings":"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","sourcesContent":["\nimport { createInterface, emitKeypressEvents, type Key } from 'node:readline'\n\nimport type { OutFn } from '../../types.js'\nimport { type ReRunManager } from './reRunManager.js'\nimport type { RunTestsPromise } from './runTests.js'\nimport { showTtyHelp } from './showTtyHelp.js'\nimport { handleUserInput } from './handleUserInput.js'\nimport { out as mainOut, error as printError } from '../../output.js'\n\n\nexport type RerunTestsFunction = (testFiles: Set<string>) => RunTestsPromise\n\nexport function listenToUser(\n //allTestFiles: Set<string>,\n //failingTests: Set<string>,\n rerunManager: ReRunManager,\n rerunTestsCallback: RerunTestsFunction,\n ttyActionsOnKeypress = false,\n initialTestRun: Promise<boolean | null> | null,\n input: NodeJS.ReadStream = process.stdin,\n customOut?: OutFn\n): () => void {\n // an agent spawned session has no TTY, so these notices would otherwise be the only\n // lines in agent mode that are not agent events\n const out = customOut ?? mainOut\n\n //console.log('initia test run', initialTestRun)\n //console.log('listen to user')\n\n //setInterval(() => console.log('active runs: ', rerunManager.state.activeRuns), 10_000)\n\n // set by whichever branch attaches listeners below\n let disposeInput = (): void => {}\n\n if (!input.isTTY) {\n out('No TTY available, not receiving cli input')\n return () => {}\n }\n\n if (ttyActionsOnKeypress) {\n \n //console.log('ttyActionsOnKeypress')\n\n emitKeypressEvents(input)\n\n const wasRaw = input.isRaw\n const restoreRawMode = (): void => {\n //console.log('restoreRawMode')\n if (input.isRaw === wasRaw) return\n try {\n input.setRawMode(wasRaw)\n } catch {\n // The terminal stream may already be closed during process shutdown.\n }\n }\n\n input.setRawMode(true)\n input.resume()\n input.once('close', restoreRawMode)\n if (input === process.stdin) process.once('exit', restoreRawMode)\n\n const onKeypress = (keypressInput: string, key: Key): void => {\n //console.log('keypress', keypressInput)\n if (key.ctrl === true && key.name === 'c') {\n restoreRawMode()\n // process.kill(process.pid, 'SIGINT') terminates unconditionally on Windows\n // rather than raising the event, which skips the shutdown handlers and so\n // skips the user's teardown. Emitting reaches them on every platform.\n // Nothing listening means nobody will exit for us, so do it here.\n if (!process.emit('SIGINT', 'SIGINT')) process.exit(130)\n return\n }\n\n handleUserInput(keypressInput, rerunManager, rerunTestsCallback, out)\n }\n input.on('keypress', onKeypress)\n\n disposeInput = () => {\n input.off('keypress', onKeypress)\n // a `once` listener that never fired is still attached\n input.off('close', restoreRawMode)\n process.off('exit', restoreRawMode)\n restoreRawMode()\n // undoes the resume above, so stdin stops holding the event loop open\n input.pause()\n }\n } else {\n //console.log('no ttyActionsOnKeypress')\n\n const userInput = createInterface({ input })\n\n userInput.on('line', input => {\n //console.log('line input', input)\n handleUserInput(input, rerunManager, rerunTestsCallback, out)\n })\n\n disposeInput = () => {\n // close() takes readline's own data/end/error listeners off the stream\n userInput.close()\n input.pause()\n }\n }\n\n let stopped = false\n\n //console.log('initialTestRun 2', initialTestRun)\n initialTestRun\n ?.then(\n () => { if (!stopped) showTtyHelp(input.isTTY, out, rerunManager.state.withoutTests.size > 0) },\n e => printError('Error in initial test run', e)\n )\n .catch(e => { printError('Error showing TTY help', e) })\n\n return () => {\n if (!stopped) {\n stopped = true\n disposeInput()\n }\n }\n}\n"]}
|
|
@@ -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
|
-
|
|
50
|
-
|
|
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":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAIvD,OAAO,KAAK,MAAM,yBAAyB,CAAA;AAC3C,OAAO,KAAK,MAAM,yBAAyB,CAAA;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AACnE,OAAO,
|
|
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"]}
|
|
@@ -4,6 +4,7 @@ export interface DependencyGraph {
|
|
|
4
4
|
update(file: string): void;
|
|
5
5
|
remove(file: string): void;
|
|
6
6
|
testsAffectedBy(file: string): Set<string>;
|
|
7
|
+
versionOf(file: string): number | undefined;
|
|
7
8
|
}
|
|
8
9
|
export declare function makeDependencyGraph(config: FinalConfig, testFiles: Iterable<string>): DependencyGraph;
|
|
9
10
|
//# sourceMappingURL=makeDependencyGraph.d.ts.map
|
|
@@ -33,6 +33,7 @@ export function makeDependencyGraph(config, testFiles) {
|
|
|
33
33
|
const imports = new Map();
|
|
34
34
|
const importers = new Map();
|
|
35
35
|
const tests = new Set();
|
|
36
|
+
const versions = new Map();
|
|
36
37
|
const toKey = (absolute) => slash(relative(cwd, absolute));
|
|
37
38
|
const toAbsolute = (key) => resolve(cwd, key);
|
|
38
39
|
// cleared by each operation, so a path created, deleted or relinked since is never served stale
|
|
@@ -144,6 +145,29 @@ export function makeDependencyGraph(config, testFiles) {
|
|
|
144
145
|
queue.push(dep);
|
|
145
146
|
}
|
|
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
|
+
}
|
|
147
171
|
timeProfile('build dependency graph', () => {
|
|
148
172
|
for (const file of testFiles) {
|
|
149
173
|
const key = realKey(file);
|
|
@@ -158,33 +182,28 @@ export function makeDependencyGraph(config, testFiles) {
|
|
|
158
182
|
const key = realKey(file);
|
|
159
183
|
tests.add(key);
|
|
160
184
|
walk(key);
|
|
185
|
+
bump(key);
|
|
161
186
|
},
|
|
162
187
|
update(file) {
|
|
163
188
|
clearCaches();
|
|
164
|
-
|
|
189
|
+
const key = realKey(file);
|
|
190
|
+
walk(key);
|
|
191
|
+
bump(key);
|
|
165
192
|
},
|
|
166
193
|
remove(file) {
|
|
167
194
|
clearCaches();
|
|
168
195
|
const key = realKey(file);
|
|
169
196
|
tests.delete(key);
|
|
170
197
|
setImports(key, new Set());
|
|
198
|
+
bump(key);
|
|
171
199
|
},
|
|
172
200
|
testsAffectedBy(file) {
|
|
173
201
|
clearCaches();
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (seen.has(key))
|
|
180
|
-
continue;
|
|
181
|
-
seen.add(key);
|
|
182
|
-
if (tests.has(key))
|
|
183
|
-
affected.add(key);
|
|
184
|
-
for (const importer of importers.get(key) ?? [])
|
|
185
|
-
queue.push(importer);
|
|
186
|
-
}
|
|
187
|
-
return affected;
|
|
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))));
|
|
188
207
|
}
|
|
189
208
|
};
|
|
190
209
|
}
|