sxy-test-runner 2.4.5 → 2.4.6

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 CHANGED
@@ -388,12 +388,12 @@ ignored there, so the output format cannot change out from under a caller.
388
388
  [sxyt] found | files=2
389
389
  [sxyt] running | n=1 | files=2 | why=startup | covers=all
390
390
  [sxyt] fail | n=1 | file=tests/users.test.js | describe=creating a user | test=rejects a duplicate email | error=AssertionError: expected 409 to equal 200
391
- [sxyt] done | n=1 | code=1 | desc=1 test failed | tests=2/3 | items=1/2 | files=1/2 | failing=1 | ms=150
391
+ [sxyt] done | n=1 | status=failed | desc=1 test failed | tests=2/3 | items=1/2 | files=1/2 | failing=1 | ms=150
392
392
  [sxyt] watching | files=2
393
393
  [sxyt] change | file=src/orders.js | event=update
394
394
  [sxyt] running | n=2 | files=2 | why=auto | covers=changed+failing
395
395
  [sxyt] fail | n=2 | file=tests/users.test.js | describe=creating a user | test=rejects a duplicate email | error=AssertionError: expected 409 to equal 200
396
- [sxyt] done | n=2 | code=1 | desc=1 test failed | tests=2/3 | items=1/2 | files=1/2 | failing=1 | ms=169
396
+ [sxyt] done | n=2 | status=failed | desc=1 test failed | tests=2/3 | items=1/2 | files=1/2 | failing=1 | ms=169
397
397
  ```
398
398
 
399
399
  The grammar is `[sxyt] <event>` followed by ` | <key>=<value>` pairs. Values are never
@@ -413,8 +413,19 @@ is always exactly one line and a field boundary is always a real one.
413
413
  `n` ties a run's lines together. Failures are emitted before their `done`, so `done` is a
414
414
  complete end-of-run marker: wait for it and everything about that run has already arrived.
415
415
 
416
- `code` on `done` is the same status code the status file uses, so `0` is a pass and `2`
417
- still means the run died partway rather than merely failing.
416
+ `status` on `done` is the state the run ended in, named rather than numbered:
417
+
418
+ | status | meaning | status-file code |
419
+ | --- | --- | --- |
420
+ | `passed` | everything that ran, passed | `0` |
421
+ | `failed` | tests ran and failed | `1` |
422
+ | `aborted` | the run died partway - a test file would not load, or a describe threw | `2` |
423
+ | `could-not-run` | nothing ran - no test files, or the filters matched nothing | `3` |
424
+
425
+ `aborted` is worth handling separately from `failed`: it usually means the suite broke
426
+ rather than the code under test. The right-hand column is the number `--status-file` records
427
+ for the same state, for anything correlating the two. It is **not** the process exit code -
428
+ see the exit codes above.
418
429
 
419
430
  `covers` on the `running` line says what the run was built from, and so how far its result
420
431
  generalises:
@@ -428,7 +439,7 @@ generalises:
428
439
  | `last` | the previous run's file set |
429
440
  | `failing` | only the files still failing |
430
441
 
431
- **No incremental run can tell you the suite is green.** `code` and the tallies describe
442
+ **No incremental run can tell you the suite is green.** `status` and the tallies describe
432
443
  only the files that run contained. `failing` is broader - it counts the test files still
433
444
  known to be failing across the session - but it is still only knowledge from files that
434
445
  have actually been run, and a run happens only when the watcher both notices a change and
@@ -448,7 +459,7 @@ maps it to a test. Either half can miss:
448
459
  So `failing=0` means "nothing is known to be broken", not "everything passes", and silence
449
460
  means "no watched file changed", not "still green". To actually verify the suite, use a run
450
461
  with `covers=all` - the startup run, `a` at the keyboard, or a separate `once` - and read
451
- `code` and `failing` from that. For the ordinary edit-test loop in between, `failing` is the
462
+ `status` and `failing` from that. For the ordinary edit-test loop in between, `failing` is the
452
463
  right signal: with the default `watch.reRunFailingTests`, a failure keeps being re-run and
453
464
  re-reported until it passes.
454
465
 
@@ -457,9 +468,9 @@ it matters, at the cost of more reruns.
457
468
 
458
469
  Diffs, logs and stack traces are not in the agent stream; errors are reduced to one line.
459
470
 
460
- In `once`, `code` on the `done` line is the status code above, **not** the process exit
461
- code - `code=1` accompanies exit `20`. Read the process's exit code for pass/fail; read the
462
- line for the detail. `watching` and `change` never appear, since there is no watcher.
471
+ In `once`, read the process's exit code for pass/fail and the `status` on `done` for what
472
+ kind of failure it was - `status=failed` accompanies exit `20`. `watching` and `change`
473
+ never appear, since there is no watcher.
463
474
 
464
475
  ```sh
465
476
  # a finite, agent readable run: failures and a summary, exit code preserved
@@ -1,7 +1,7 @@
1
1
  import type { FinalConfig, TestsResultsSummary } from '../../types.js';
2
2
  import type { FailureEntry } from './failureEntries.js';
3
3
  import type { RunType } from './reRunManager.js';
4
- import type { StatusCode } from './statusFile.js';
4
+ import { type StatusCode } from './statusFile.js';
5
5
  export type RunReason = RunType | 'startup' | 'once';
6
6
  export declare function agentEnabled(config: FinalConfig): boolean;
7
7
  export declare function agentInit(enabled: boolean): void;
@@ -1,4 +1,5 @@
1
1
  import { out as mainOut } from '../../output.js';
2
+ import { statusNames } from './statusFile.js';
2
3
  // Agent mode reports a watch session as a stream of single lines, for a reader tailing the
3
4
  // process rather than watching the terminal. Every line is one complete event:
4
5
  //
@@ -53,7 +54,7 @@ export function agentRunStart(config, files) {
53
54
  ]);
54
55
  return runCounter;
55
56
  }
56
- // what the run was built from. A reader needs this to know how far the run's own `code`
57
+ // what the run was built from. A reader needs this to know how far the run's own `status`
57
58
  // generalises - only a run covering everything can speak for the whole suite.
58
59
  function runScope(config) {
59
60
  // known before the run starts: fail fast means it is built to stop early, so it cannot
@@ -100,7 +101,9 @@ export function agentDone(config, run, code, description, summary, durationMs, f
100
101
  return;
101
102
  emit('done', [
102
103
  ['n', run],
103
- ['code', code],
104
+ // named, not numbered: `status=failed` needs no lookup table, and cannot be misread
105
+ // as the process exit code the way a bare `code=1` could
106
+ ['status', statusNames[code]],
104
107
  ['desc', flatten(description)],
105
108
  ...(summary === null) ? [] : [
106
109
  ['tests', `${summary.itsPasses}/${summary.itsTotal}`],
@@ -1 +1 @@
1
- {"version":3,"file":"agentReport.js","sourceRoot":"","sources":["../../../src/cli/lib/agentReport.ts"],"names":[],"mappings":"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","sourcesContent":["import { out as mainOut } from '../../output.js'\n\nimport type { FinalConfig, TestsResultsSummary } from '../../types.js'\nimport type { FailureEntry } from './failureEntries.js'\nimport type { RunType } from './reRunManager.js'\nimport type { StatusCode } from './statusFile.js'\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.watch.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 `code`\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 ['code', 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":["import { out as mainOut } from '../../output.js'\n\nimport type { FinalConfig, TestsResultsSummary } from '../../types.js'\nimport type { FailureEntry } from './failureEntries.js'\nimport type { RunType } from './reRunManager.js'\nimport { statusNames, type StatusCode } from './statusFile.js'\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.watch.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"]}
@@ -7,6 +7,7 @@ export declare const statusCodes: {
7
7
  readonly couldNotRun: 3;
8
8
  };
9
9
  export type StatusCode = typeof statusCodes[keyof typeof statusCodes];
10
+ export declare const statusNames: Record<StatusCode, string>;
10
11
  export declare function resetStatusFile(config: FinalConfig): void;
11
12
  export declare function writeStatusFile(config: FinalConfig, code: StatusCode, description: string, details?: string[], durationMs?: number): void;
12
13
  //# sourceMappingURL=statusFile.d.ts.map
@@ -13,6 +13,16 @@ export const statusCodes = {
13
13
  aborted: 2, // the run died partway - a test file failed to load, or a describe threw
14
14
  couldNotRun: 3 // nothing ran at all - no test files, or no match for the filters
15
15
  };
16
+ // The same states as words, for the agent stream. A number needs the table above to mean
17
+ // anything; a reader of one line should not have to go and find it. Defined here so the two
18
+ // cannot drift apart.
19
+ export const statusNames = {
20
+ [statusCodes.notRunYet]: 'not-run',
21
+ [statusCodes.passed]: 'passed',
22
+ [statusCodes.failed]: 'failed',
23
+ [statusCodes.aborted]: 'aborted',
24
+ [statusCodes.couldNotRun]: 'could-not-run'
25
+ };
16
26
  export function resetStatusFile(config) {
17
27
  writeStatusFile(config, statusCodes.notRunYet, 'no run has finished yet');
18
28
  }
@@ -1 +1 @@
1
- {"version":3,"file":"statusFile.js","sourceRoot":"","sources":["../../../src/cli/lib/statusFile.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'fs'\nimport path from 'path'\n\nimport chalk from '../../packages/chalk.js'\nimport { packageName } from '../../config.js'\nimport { log } from '../../output.js'\n\nimport type { FinalConfig } from '../../types.js'\n\n// The status file holds the result of the latest run: `<code> - <description>` on the first\n// line, then any detail. Watch mode resets it to -1 on startup so a stale result is never\n// mistaken for the current one.\nexport const statusCodes = {\n notRunYet: -1, // watch mode has started, but no run has finished\n passed: 0,\n failed: 1, // tests ran and failed\n aborted: 2, // the run died partway - a test file failed to load, or a describe threw\n couldNotRun: 3 // nothing ran at all - no test files, or no match for the filters\n} as const\n\nexport type StatusCode = typeof statusCodes[keyof typeof statusCodes]\n\nexport function resetStatusFile(config: FinalConfig): void {\n writeStatusFile(config, statusCodes.notRunYet, 'no run has finished yet')\n}\n\nexport function writeStatusFile(\n config: FinalConfig,\n code: StatusCode,\n description: string,\n details: string[] = [],\n durationMs?: number\n): void {\n write(config, [\n `${code} - ${description}`,\n localTime(new Date()),\n ...details,\n ...(durationMs === undefined) ? [] : [`${durationMs.toLocaleString()}ms`]\n ].join('\\n') + '\\n')\n}\n\n// local time, so the file reads the way the terminal beside it does, with the offset so a\n// reader elsewhere can still place it\nfunction localTime(date: Date): string {\n const pad = (value: number): string => String(value).padStart(2, '0')\n // getTimezoneOffset() counts minutes behind UTC, so the sign is the other way round\n const offsetMinutes = -date.getTimezoneOffset()\n const offset = `${(offsetMinutes < 0) ? '-' : '+'}`\n + `${pad(Math.floor(Math.abs(offsetMinutes) / 60))}:${pad(Math.abs(offsetMinutes) % 60)}`\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`\n + ` ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`\n + ` ${offset}`\n}\n\nfunction write(config: FinalConfig, contents: string): void {\n const statusFile = config.watch.statusFile\n if (typeof statusFile !== 'string' || statusFile === '') return\n\n const location = path.resolve(process.cwd(), statusFile)\n // written via a temp file and renamed, so a poller never reads a half written status\n const tempLocation = `${location}.${process.pid}.tmp`\n try {\n fs.mkdirSync(path.dirname(location), { recursive: true })\n fs.writeFileSync(tempLocation, contents)\n fs.renameSync(tempLocation, location)\n } catch (error) {\n // never let the status file take the run down with it\n log(chalk.mediumred(\n `[${packageName}] Could not write status file ${location}: ${String(error)}`\n ))\n try { fs.rmSync(tempLocation, { force: true }) } catch { /* nothing more to do */ }\n }\n}\n"]}
1
+ {"version":3,"file":"statusFile.js","sourceRoot":"","sources":["../../../src/cli/lib/statusFile.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'fs'\nimport path from 'path'\n\nimport chalk from '../../packages/chalk.js'\nimport { packageName } from '../../config.js'\nimport { log } from '../../output.js'\n\nimport type { FinalConfig } from '../../types.js'\n\n// The status file holds the result of the latest run: `<code> - <description>` on the first\n// line, then any detail. Watch mode resets it to -1 on startup so a stale result is never\n// mistaken for the current one.\nexport const statusCodes = {\n notRunYet: -1, // watch mode has started, but no run has finished\n passed: 0,\n failed: 1, // tests ran and failed\n aborted: 2, // the run died partway - a test file failed to load, or a describe threw\n couldNotRun: 3 // nothing ran at all - no test files, or no match for the filters\n} as const\n\nexport type StatusCode = typeof statusCodes[keyof typeof statusCodes]\n\n// The same states as words, for the agent stream. A number needs the table above to mean\n// anything; a reader of one line should not have to go and find it. Defined here so the two\n// cannot drift apart.\nexport const statusNames: Record<StatusCode, string> = {\n [statusCodes.notRunYet]: 'not-run',\n [statusCodes.passed]: 'passed',\n [statusCodes.failed]: 'failed',\n [statusCodes.aborted]: 'aborted',\n [statusCodes.couldNotRun]: 'could-not-run'\n}\n\nexport function resetStatusFile(config: FinalConfig): void {\n writeStatusFile(config, statusCodes.notRunYet, 'no run has finished yet')\n}\n\nexport function writeStatusFile(\n config: FinalConfig,\n code: StatusCode,\n description: string,\n details: string[] = [],\n durationMs?: number\n): void {\n write(config, [\n `${code} - ${description}`,\n localTime(new Date()),\n ...details,\n ...(durationMs === undefined) ? [] : [`${durationMs.toLocaleString()}ms`]\n ].join('\\n') + '\\n')\n}\n\n// local time, so the file reads the way the terminal beside it does, with the offset so a\n// reader elsewhere can still place it\nfunction localTime(date: Date): string {\n const pad = (value: number): string => String(value).padStart(2, '0')\n // getTimezoneOffset() counts minutes behind UTC, so the sign is the other way round\n const offsetMinutes = -date.getTimezoneOffset()\n const offset = `${(offsetMinutes < 0) ? '-' : '+'}`\n + `${pad(Math.floor(Math.abs(offsetMinutes) / 60))}:${pad(Math.abs(offsetMinutes) % 60)}`\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`\n + ` ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`\n + ` ${offset}`\n}\n\nfunction write(config: FinalConfig, contents: string): void {\n const statusFile = config.watch.statusFile\n if (typeof statusFile !== 'string' || statusFile === '') return\n\n const location = path.resolve(process.cwd(), statusFile)\n // written via a temp file and renamed, so a poller never reads a half written status\n const tempLocation = `${location}.${process.pid}.tmp`\n try {\n fs.mkdirSync(path.dirname(location), { recursive: true })\n fs.writeFileSync(tempLocation, contents)\n fs.renameSync(tempLocation, location)\n } catch (error) {\n // never let the status file take the run down with it\n log(chalk.mediumred(\n `[${packageName}] Could not write status file ${location}: ${String(error)}`\n ))\n try { fs.rmSync(tempLocation, { force: true }) } catch { /* nothing more to do */ }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sxy-test-runner",
3
- "version": "2.4.5",
3
+ "version": "2.4.6",
4
4
  "license": "MIT",
5
5
  "homepage": "https://github.com/RobertSandiford/sxy-test-runner",
6
6
  "repository": {
package/readme.md CHANGED
@@ -189,7 +189,7 @@ npx sxy-test-runner once --agent
189
189
  [sxyt] found | files=2
190
190
  [sxyt] running | n=1 | files=2 | why=once | covers=all
191
191
  [sxyt] fail | n=1 | file=tests/users.test.js | describe=creating a user | test=rejects a duplicate email | error=AssertionError: expected 409 to equal 200
192
- [sxyt] done | n=1 | code=1 | desc=1 test failed | tests=2/3 | items=1/2 | files=1/2 | failing=1 | ms=150
192
+ [sxyt] done | n=1 | status=failed | desc=1 test failed | tests=2/3 | items=1/2 | files=1/2 | failing=1 | ms=150
193
193
  ```
194
194
 
195
195
  Every line is one complete event: `[sxyt] <event>` followed by ` | <key>=<value>` pairs.
@@ -199,7 +199,9 @@ lines. In watch mode the stream continues with `change`, further `run`/`done` pa
199
199
 
200
200
  `failing` counts the test files still known to be failing across the whole session, and
201
201
  `covers` says what the run was built from, so a partial re-run cannot be mistaken for a
202
- clean suite. `code` is the status code from the table above, not the process exit code.
202
+ clean suite. `status` is one of `passed`, `failed`, `aborted` (a test file would not load, or
203
+ a describe threw) or `could-not-run` - the same states as the status file table above, named
204
+ rather than numbered. It is not the process exit code: read that from the process.
203
205
 
204
206
  ## Stopping at the first failure
205
207