@travetto/test 3.4.2 → 4.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -27,7 +27,7 @@ A simple example would be:
27
27
 
28
28
  **Code: Example Test Suite**
29
29
  ```typescript
30
- import assert from 'assert';
30
+ import assert from 'node:assert';
31
31
 
32
32
  import { Suite, Test } from '@travetto/test';
33
33
 
@@ -58,7 +58,7 @@ A common aspect of the tests themselves are the assertions that are made. [Node
58
58
 
59
59
  **Code: Example assertion for deep comparison**
60
60
  ```typescript
61
- import assert from 'assert';
61
+ import assert from 'node:assert';
62
62
 
63
63
  import { Suite, Test } from '@travetto/test';
64
64
 
@@ -81,13 +81,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
81
81
  const tslib_1 = require("tslib");
82
82
  const Ⲑ_util_1 = tslib_1.__importStar(require("@travetto/test/src/execute/util.js"));
83
83
  const Ⲑ_check_1 = tslib_1.__importStar(require("@travetto/test/src/assert/check.js"));
84
- const Ⲑ_root_index_1 = tslib_1.__importStar(require("@travetto/manifest/src/root-index.js"));
84
+ const Ⲑ_runtime_1 = tslib_1.__importStar(require("@travetto/manifest/src/runtime.js"));
85
85
  const Ⲑ_decorator_1 = tslib_1.__importStar(require("@travetto/registry/src/decorator.js"));
86
86
  var ᚕf = "@travetto/test/doc/assert-example.js";
87
- const assert_1 = tslib_1.__importDefault(require("assert"));
87
+ const node_assert_1 = tslib_1.__importDefault(require("node:assert"));
88
88
  const test_1 = require("@travetto/test");
89
89
  let SimpleTest = class SimpleTest {
90
- static Ⲑinit = Ⲑ_root_index_1.RootIndex.registerFunction(SimpleTest, ᚕf, 1887908328, { test: { hash: 102834457 } }, false, false);
90
+ static Ⲑinit = Ⲑ_runtime_1.RuntimeIndex.registerFunction(SimpleTest, ᚕf, 1887908328, { test: { hash: 102834457 } }, false, false);
91
91
  async test() {
92
92
  if (Ⲑ_util_1.RunnerUtil.tryDebugger)
93
93
  debugger;
@@ -131,7 +131,7 @@ In addition to the standard operations, there is support for throwing/rejecting
131
131
 
132
132
  **Code: Throws vs Does Not Throw**
133
133
  ```typescript
134
- import assert from 'assert';
134
+ import assert from 'node:assert';
135
135
 
136
136
  import { Suite, Test } from '@travetto/test';
137
137
 
@@ -157,7 +157,7 @@ class SimpleTest {
157
157
 
158
158
  **Code: Rejects vs Does Not Reject**
159
159
  ```typescript
160
- import assert from 'assert';
160
+ import assert from 'node:assert';
161
161
 
162
162
  import { Suite, Test } from '@travetto/test';
163
163
 
@@ -186,7 +186,7 @@ Additionally, the `throws`/`rejects` assertions take in a secondary parameter to
186
186
 
187
187
  **Code: Example of different Error matching paradigms**
188
188
  ```typescript
189
- import assert from 'assert';
189
+ import assert from 'node:assert';
190
190
 
191
191
  import { Suite, Test } from '@travetto/test';
192
192
 
package/__index__.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /// <reference path="./src/trv.d.ts" />
1
2
  export * from './src/decorator/suite';
2
3
  export * from './src/decorator/test';
3
4
  export * from './src/model/suite';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/test",
3
- "version": "3.4.2",
3
+ "version": "4.0.0-rc.0",
4
4
  "description": "Declarative test framework",
5
5
  "keywords": [
6
6
  "unit-testing",
@@ -27,15 +27,15 @@
27
27
  "directory": "module/test"
28
28
  },
29
29
  "dependencies": {
30
- "@travetto/base": "^3.4.1",
31
- "@travetto/registry": "^3.4.1",
32
- "@travetto/terminal": "^3.4.0",
33
- "@travetto/worker": "^3.4.1",
34
- "@travetto/yaml": "^3.4.1"
30
+ "@travetto/base": "^4.0.0-rc.0",
31
+ "@travetto/registry": "^4.0.0-rc.0",
32
+ "@travetto/terminal": "^4.0.0-rc.0",
33
+ "@travetto/worker": "^4.0.0-rc.0",
34
+ "@travetto/yaml": "^4.0.0-rc.0"
35
35
  },
36
36
  "peerDependencies": {
37
- "@travetto/cli": "^3.4.6",
38
- "@travetto/transformer": "^3.4.2"
37
+ "@travetto/cli": "^4.0.0-rc.0",
38
+ "@travetto/transformer": "^4.0.0-rc.0"
39
39
  },
40
40
  "peerDependenciesMeta": {
41
41
  "@travetto/transformer": {
@@ -1,4 +1,4 @@
1
- import { EventEmitter } from 'events';
1
+ import { EventEmitter } from 'node:events';
2
2
 
3
3
  import { Assertion, TestConfig } from '../model/test';
4
4
 
@@ -1,6 +1,6 @@
1
- import assert from 'assert';
1
+ import assert from 'node:assert';
2
2
 
3
- import { RootIndex } from '@travetto/manifest';
3
+ import { RuntimeIndex } from '@travetto/manifest';
4
4
  import { ObjectUtil, AppError, ClassInstance, Class } from '@travetto/base';
5
5
 
6
6
  import { ThrowableError, TestConfig, Assertion } from '../model/test';
@@ -30,7 +30,7 @@ export class AssertCheck {
30
30
  * @param args The arguments passed in
31
31
  */
32
32
  static check(assertion: CaptureAssert, positive: boolean, ...args: unknown[]): void {
33
- assertion.file = RootIndex.getSourceFile(assertion.file);
33
+ assertion.file = RuntimeIndex.getSourceFile(assertion.file);
34
34
 
35
35
  let fn = assertion.operator;
36
36
  assertion.operator = ASSERT_FN_OPERATOR[fn];
@@ -227,7 +227,7 @@ export class AssertCheck {
227
227
  ): void {
228
228
  let missed: Error | undefined;
229
229
 
230
- assertion.file = RootIndex.getSourceFile(assertion.file);
230
+ assertion.file = RuntimeIndex.getSourceFile(assertion.file);
231
231
 
232
232
  try {
233
233
  action();
@@ -261,7 +261,7 @@ export class AssertCheck {
261
261
  ): Promise<void> {
262
262
  let missed: Error | undefined;
263
263
 
264
- assertion.file = RootIndex.getSourceFile(assertion.file);
264
+ assertion.file = RuntimeIndex.getSourceFile(assertion.file);
265
265
 
266
266
  try {
267
267
  if ('then' in action) {
@@ -1,6 +1,6 @@
1
- import util from 'util';
1
+ import util from 'node:util';
2
2
 
3
- import { path, RootIndex } from '@travetto/manifest';
3
+ import { path, RuntimeIndex, RuntimeContext } from '@travetto/manifest';
4
4
  import { Class, ClassInstance, ObjectUtil } from '@travetto/base';
5
5
 
6
6
  import { TestConfig, Assertion, TestResult } from '../model/test';
@@ -41,7 +41,7 @@ export class AssertUtil {
41
41
  * Determine file location for a given error and the stack trace
42
42
  */
43
43
  static getPositionOfError(err: Error, filename: string): { file: string, line: number } {
44
- const cwd = RootIndex.mainModule.sourcePath;
44
+ const cwd = RuntimeIndex.mainModule.sourcePath;
45
45
  const lines = path.toPosix(err.stack ?? new Error().stack!)
46
46
  .split('\n')
47
47
  // Exclude node_modules, target self
@@ -94,7 +94,7 @@ export class AssertUtil {
94
94
 
95
95
  const msg = error.message.split(/\n/)[0];
96
96
 
97
- const core = { file, classId: suite.classId, methodName, module: RootIndex.manifest.mainModule };
97
+ const core = { file, classId: suite.classId, methodName, module: RuntimeContext.main.name };
98
98
  const coreAll = { ...core, description: msg, lines: { start: line, end: line, codeStart: line } };
99
99
 
100
100
  const assert: Assertion = {
@@ -1,18 +1,18 @@
1
- import { GlobalTerminal } from '@travetto/terminal';
1
+ import { StyleUtil } from '@travetto/terminal';
2
2
 
3
- export const CONSOLE_ENHANCER = GlobalTerminal.palette({
4
- assertDescription: 'lightGray',
5
- testDescription: 'white',
6
- success: 'green',
7
- failure: 'red',
8
- assertNumber: 'brightCyan',
9
- testNumber: 'dodgerBlue',
10
- assertFile: 'lightGreen',
11
- assertLine: 'lightYellow',
12
- objectInspect: 'magenta',
13
- suiteName: 'yellow',
14
- testName: 'cyan',
15
- total: 'white'
3
+ export const CONSOLE_ENHANCER = StyleUtil.getPalette({
4
+ assertDescription: '#d3d3d3', // light gray
5
+ testDescription: '#e5e5e5', // White
6
+ success: '#00cd00', // Green
7
+ failure: '#cd0000', // Red
8
+ assertNumber: '#00ffff', // Bright cyan
9
+ testNumber: '#1e90ff', // dodger blue
10
+ assertFile: '#90e90', // lightGreen
11
+ assertLine: '#ffffe0', // light yellow
12
+ objectInspect: '#cd00cd', // Magenta
13
+ suiteName: '#cdcd00', // Yellow
14
+ testName: '#00cdcd', // Cyan
15
+ total: '#e5e5e5', // White
16
16
  });
17
17
 
18
18
  export type TestResultsEnhancer = typeof CONSOLE_ENHANCER;
@@ -1,4 +1,4 @@
1
- import { existsSync } from 'fs';
1
+ import { existsSync } from 'node:fs';
2
2
 
3
3
  import { Class } from '@travetto/base';
4
4
 
@@ -1,4 +1,4 @@
1
- import { Writable } from 'stream';
1
+ import { Writable } from 'node:stream';
2
2
 
3
3
  import { TestEvent } from '../../model/event';
4
4
  import { TestConsumer } from '../types';
@@ -1,4 +1,4 @@
1
- import { Writable } from 'stream';
1
+ import { Writable } from 'node:stream';
2
2
 
3
3
  import { TestEvent } from '../../model/event';
4
4
  import { SuitesSummary, TestConsumer } from '../types';
@@ -1,5 +1,6 @@
1
- import { GlobalTerminal, TermStyleInput, Terminal } from '@travetto/terminal';
2
- import { ManualAsyncIterator } from '@travetto/worker';
1
+ import { Util } from '@travetto/base';
2
+ import { StyleUtil, Terminal, TerminalUtil } from '@travetto/terminal';
3
+ import { WorkQueue } from '@travetto/worker';
3
4
 
4
5
  import { TestEvent } from '../../model/event';
5
6
  import { TestResult } from '../../model/test';
@@ -15,35 +16,12 @@ import { TapEmitter } from './tap';
15
16
  @Consumable('tap-streamed')
16
17
  export class TapStreamedEmitter implements TestConsumer {
17
18
 
18
- static makeProgressBar(term: Terminal, total: number): (t: TestResult, idx: number) => string {
19
- let failed = 0;
20
- const palette: TermStyleInput[] = [
21
- { text: 'white', background: 'darkGreen' },
22
- { text: 'white', background: 'darkRed' }
23
- ];
24
- const styles = palette.map(s => GlobalTerminal.colorer(s));
25
-
26
- return (t: TestResult, idx: number): string => {
27
- if (t.status === 'failed') {
28
- failed += 1;
29
- }
30
- const i = idx + 1;
31
- const digits = total.toString().length;
32
- const paddedI = `${i}`.padStart(digits);
33
- const paddedFailed = `${failed}`.padStart(digits);
34
- const line = `Tests ${paddedI}/${total} [${paddedFailed} failed] -- ${t.classId}`.padEnd(term.width);
35
- const pos = Math.trunc(line.length * (i / total));
36
- const colorer = styles[Math.min(failed, styles.length - 1)];
37
- return `${colorer(line.substring(0, pos))}${line.substring(pos)}`;
38
- };
39
- }
40
-
41
19
  #terminal: Terminal;
42
- #results = new ManualAsyncIterator<TestResult>();
20
+ #results = new WorkQueue<TestResult>();
43
21
  #progress: Promise<unknown> | undefined;
44
22
  #consumer: TapEmitter;
45
23
 
46
- constructor(terminal: Terminal = new Terminal({ output: process.stderr })) {
24
+ constructor(terminal: Terminal = new Terminal(process.stderr)) {
47
25
  this.#terminal = terminal;
48
26
  this.#consumer = new TapEmitter(this.#terminal);
49
27
  }
@@ -51,9 +29,19 @@ export class TapStreamedEmitter implements TestConsumer {
51
29
  async onStart(state: TestRunState): Promise<void> {
52
30
  this.#consumer.onStart();
53
31
 
54
- this.#progress = this.#terminal.streamToPosition(this.#results,
55
- TapStreamedEmitter.makeProgressBar(this.#terminal, state.testCount ?? 0),
56
- { position: 'bottom', minDelay: 100 }
32
+ let failed = 0;
33
+ const succ = StyleUtil.getStyle({ text: '#e5e5e5', background: '#026020' }); // White on dark green
34
+ const fail = StyleUtil.getStyle({ text: '#e5e5e5', background: '#8b0000' }); // White on dark red
35
+ this.#progress = this.#terminal.streamToBottom(
36
+ Util.mapAsyncItr(
37
+ this.#results,
38
+ (value, idx) => {
39
+ failed += (value.status === 'failed' ? 1 : 0);
40
+ return { value: `Tests %idx/%total [${failed} failed] -- ${value.classId}`, total: state.testCount, idx };
41
+ },
42
+ TerminalUtil.progressBarUpdater(this.#terminal, { style: () => ({ complete: failed ? fail : succ }) })
43
+ ),
44
+ { minDelay: 100 }
57
45
  );
58
46
  }
59
47
 
@@ -1,5 +1,5 @@
1
- import { RootIndex } from '@travetto/manifest';
2
- import { GlobalTerminal, Terminal } from '@travetto/terminal';
1
+ import { RuntimeIndex } from '@travetto/manifest';
2
+ import { Terminal } from '@travetto/terminal';
3
3
  import { ObjectUtil, TimeUtil } from '@travetto/base';
4
4
  import { YamlUtil } from '@travetto/yaml';
5
5
 
@@ -20,7 +20,7 @@ export class TapEmitter implements TestConsumer {
20
20
  #start: number;
21
21
 
22
22
  constructor(
23
- terminal = new Terminal({ output: process.stdout }),
23
+ terminal = new Terminal(),
24
24
  enhancer: TestResultsEnhancer = CONSOLE_ENHANCER
25
25
  ) {
26
26
  this.#terminal = terminal;
@@ -28,7 +28,7 @@ export class TapEmitter implements TestConsumer {
28
28
  }
29
29
 
30
30
  log(message: string): void {
31
- this.#terminal.writeLines(message);
31
+ this.#terminal.writer.writeLine(message).commit();
32
32
  }
33
33
 
34
34
  /**
@@ -43,7 +43,7 @@ export class TapEmitter implements TestConsumer {
43
43
  * Output supplemental data (e.g. logs)
44
44
  */
45
45
  logMeta(obj: Record<string, unknown>): void {
46
- const lineLength = GlobalTerminal.width - 5;
46
+ const lineLength = this.#terminal.width - 5;
47
47
  let body = YamlUtil.serialize(obj, { wordwrap: lineLength });
48
48
  body = body.split('\n').map(x => ` ${x}`).join('\n');
49
49
  this.log(`---\n${this.#enhancer.objectInspect(body)}\n...`);
@@ -71,7 +71,7 @@ export class TapEmitter implements TestConsumer {
71
71
  this.#enhancer.assertNumber(++subCount),
72
72
  '-',
73
73
  this.#enhancer.assertDescription(text),
74
- `${this.#enhancer.assertFile(asrt.file.replace(RootIndex.mainModule.sourcePath, '.'))}:${this.#enhancer.assertLine(asrt.line)}`
74
+ `${this.#enhancer.assertFile(asrt.file.replace(RuntimeIndex.mainModule.sourcePath, '.'))}:${this.#enhancer.assertLine(asrt.line)}`
75
75
  ].join(' ');
76
76
 
77
77
  if (asrt.error) {
@@ -1,4 +1,4 @@
1
- import { Writable } from 'stream';
1
+ import { Writable } from 'node:stream';
2
2
 
3
3
  import { YamlUtil } from '@travetto/yaml';
4
4
 
@@ -1,4 +1,4 @@
1
- import { RootIndex } from '@travetto/manifest';
1
+ import { RuntimeIndex } from '@travetto/manifest';
2
2
  import { Class, ClassInstance } from '@travetto/base';
3
3
 
4
4
  import { SuiteRegistry } from '../registry/suite';
@@ -27,7 +27,7 @@ export function Suite(description?: string | Partial<SuiteConfig>, ...rest: Part
27
27
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
28
28
  const decorator = ((target: Class) => {
29
29
  const cfg = { description: descriptionString, ...extra };
30
- if (RootIndex.getFunctionMetadata(target)?.abstract) {
30
+ if (RuntimeIndex.getFunctionMetadata(target)?.abstract) {
31
31
  cfg.skip = true;
32
32
  }
33
33
  SuiteRegistry.register(target, cfg);
@@ -1,5 +1,5 @@
1
1
  import { ClassInstance } from '@travetto/base';
2
- import { RootIndex } from '@travetto/manifest';
2
+ import { RuntimeIndex } from '@travetto/manifest';
3
3
 
4
4
  import { SuiteRegistry } from '../registry/suite';
5
5
  import { TestConfig } from '../model/test';
@@ -32,7 +32,7 @@ export function Test(description?: string | Partial<TestConfig>, ...rest: Partia
32
32
  return (inst: ClassInstance, prop: string | symbol, descriptor: PropertyDescriptor) => {
33
33
  SuiteRegistry.registerField(inst.constructor, descriptor.value, {
34
34
  ...extra,
35
- file: RootIndex.getFunctionMetadata(inst.constructor)!.source,
35
+ file: RuntimeIndex.getFunctionMetadata(inst.constructor)!.source,
36
36
  description: descriptionString
37
37
  });
38
38
  return descriptor;
@@ -1,4 +1,4 @@
1
- import util from 'util';
1
+ import util from 'node:util';
2
2
 
3
3
  import { ConsoleEvent, ConsoleManager } from '@travetto/base';
4
4
 
@@ -1,7 +1,8 @@
1
- import timers from 'timers/promises';
1
+ import timers from 'node:timers/promises';
2
+ import { AssertionError } from 'node:assert';
2
3
 
3
- import { path, RootIndex } from '@travetto/manifest';
4
- import { TimeUtil, Util } from '@travetto/base';
4
+ import { path, RuntimeIndex, RuntimeContext } from '@travetto/manifest';
5
+ import { Env, Util } from '@travetto/base';
5
6
  import { Barrier, ExecutionError } from '@travetto/worker';
6
7
 
7
8
  import { SuiteRegistry } from '../registry/suite';
@@ -15,7 +16,7 @@ import { TestPhaseManager } from './phase';
15
16
  import { PromiseCapture } from './promise';
16
17
  import { AssertUtil } from '../assert/util';
17
18
 
18
- const TEST_TIMEOUT = TimeUtil.getEnvTime('TRV_TEST_TIMEOUT', '5s');
19
+ const TEST_TIMEOUT = Env.TRV_TEST_TIMEOUT.time ?? 5000;
19
20
 
20
21
  /**
21
22
  * Support execution of the tests
@@ -68,10 +69,10 @@ export class TestExecutor {
68
69
  */
69
70
  static failFile(consumer: TestConsumer, file: string, err: Error): void {
70
71
  const name = path.basename(file);
71
- const classId = RootIndex.getId(file, name);
72
+ const classId = RuntimeIndex.getId(file, name);
72
73
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
73
74
  const suite = { class: { name }, classId, duration: 0, lines: { start: 1, end: 1 }, file, } as SuiteConfig & SuiteResult;
74
- err.message = err.message.replaceAll(RootIndex.mainModule.sourcePath, '.');
75
+ err.message = err.message.replaceAll(RuntimeIndex.mainModule.sourcePath, '.');
75
76
  const res = AssertUtil.generateSuiteError(suite, 'require', err);
76
77
  consumer.onEvent({ type: 'suite', phase: 'before', suite });
77
78
  consumer.onEvent({ type: 'test', phase: 'before', test: res.testConfig });
@@ -109,7 +110,7 @@ export class TestExecutor {
109
110
 
110
111
  const result: TestResult = {
111
112
  methodName: test.methodName,
112
- module: RootIndex.manifest.mainModule,
113
+ module: RuntimeContext.main.name,
113
114
  description: test.description,
114
115
  classId: test.classId,
115
116
  lines: { ...test.lines },
@@ -140,10 +141,14 @@ export class TestExecutor {
140
141
  let error = await this.#executeTestMethod(test);
141
142
 
142
143
  if (error) {
143
- if (error instanceof ExecutionError) { // Errors that are not expected
144
+ if (error instanceof AssertionError) {
145
+ // Pass
146
+ } else if (error instanceof ExecutionError) { // Errors that are not expected
144
147
  AssertCheck.checkUnhandled(test, error);
145
148
  } else if (test.shouldThrow) { // Errors that are
146
149
  error = AssertCheck.checkError(test.shouldThrow!, error); // Rewrite error
150
+ } else if (error instanceof Error) {
151
+ AssertCheck.checkUnhandled(test, error);
147
152
  }
148
153
  }
149
154
 
@@ -254,7 +259,7 @@ export class TestExecutor {
254
259
 
255
260
  file = path.resolve(file);
256
261
 
257
- const entry = RootIndex.getEntry(file)!;
262
+ const entry = RuntimeIndex.getEntry(file)!;
258
263
 
259
264
  try {
260
265
  await import(entry.import);
@@ -1,5 +1,5 @@
1
1
  import { Barrier } from '@travetto/worker';
2
- import { TimeUtil } from '@travetto/base';
2
+ import { Env } from '@travetto/base';
3
3
 
4
4
  import { TestConsumer } from '../consumer/types';
5
5
  import { SuiteConfig, SuiteResult } from '../model/suite';
@@ -8,7 +8,7 @@ import { TestResult } from '../model/test';
8
8
 
9
9
  class TestBreakout extends Error { }
10
10
 
11
- const TEST_PHASE_TIMEOUT = TimeUtil.getEnvTime('TRV_TEST_PHASE_TIMEOUT', '15s');
11
+ const TEST_PHASE_TIMEOUT = Env.TRV_TEST_PHASE_TIMEOUT.time ?? 15000;
12
12
 
13
13
  /**
14
14
  * Test Phase Execution Manager.
@@ -1,6 +1,6 @@
1
1
  import { path } from '@travetto/manifest';
2
2
  import { TimeUtil } from '@travetto/base';
3
- import { WorkPool, IterableWorkSet } from '@travetto/worker';
3
+ import { WorkPool } from '@travetto/worker';
4
4
 
5
5
  import { buildStandardTestManager } from '../worker/standard';
6
6
  import { RunnableTestConsumer } from '../consumer/types/runnable';
@@ -34,17 +34,17 @@ export class Runner {
34
34
 
35
35
  console.debug('Running', { files, patterns: this.patterns });
36
36
 
37
- const manager = buildStandardTestManager;
38
-
39
- const pool = new WorkPool(manager(consumer), {
40
- idleTimeoutMillis: TimeUtil.timeToMs('10s'),
41
- min: 1,
42
- max: this.#state.concurrency
43
- });
44
-
45
37
  const testCount = await RunnerUtil.getTestCount(this.#state.args);
46
38
  await consumer.onStart({ testCount });
47
- await pool.process(new IterableWorkSet(files));
39
+ await WorkPool.run(
40
+ () => buildStandardTestManager(consumer),
41
+ files,
42
+ {
43
+ idleTimeoutMillis: TimeUtil.timeToMs('10s'),
44
+ min: 1,
45
+ max: this.#state.concurrency,
46
+ });
47
+
48
48
  return consumer.summarizeAsBoolean();
49
49
  }
50
50
 
@@ -1,8 +1,9 @@
1
- import { createReadStream } from 'fs';
2
- import readline from 'readline';
1
+ import { createReadStream } from 'node:fs';
2
+ import readline from 'node:readline';
3
+ import timers from 'node:timers/promises';
3
4
 
4
- import { ExecUtil, ShutdownManager, TimeUtil } from '@travetto/base';
5
- import { IndexedFile, RootIndex } from '@travetto/manifest';
5
+ import { Env, ExecUtil, ShutdownManager } from '@travetto/base';
6
+ import { IndexedFile, RuntimeIndex } from '@travetto/manifest';
6
7
 
7
8
  /**
8
9
  * Simple Test Utilities
@@ -12,7 +13,7 @@ export class RunnerUtil {
12
13
  * Add 50 ms to the shutdown to allow for buffers to output properly
13
14
  */
14
15
  static registerCleanup(scope: string): void {
15
- ShutdownManager.onShutdown(`test.${scope}.bufferOutput`, () => TimeUtil.wait(50));
16
+ ShutdownManager.onGracefulShutdown(() => timers.setTimeout(50), `test.${scope}.bufferOutput`);
16
17
  }
17
18
 
18
19
  /**
@@ -37,7 +38,7 @@ export class RunnerUtil {
37
38
  * Find all valid test files given the globs
38
39
  */
39
40
  static async getTestFiles(globs?: RegExp[]): Promise<IndexedFile[]> {
40
- const files = RootIndex.find({
41
+ const files = RuntimeIndex.find({
41
42
  module: m => m.roles.includes('test') || m.roles.includes('std'),
42
43
  folder: f => f === 'test',
43
44
  file: f => f.role === 'test'
@@ -58,7 +59,13 @@ export class RunnerUtil {
58
59
  * @returns
59
60
  */
60
61
  static async getTestCount(patterns: string[]): Promise<number> {
61
- const proc = ExecUtil.spawn('npx', ['trv', 'test:count', ...patterns], { stdio: 'pipe', catchAsResult: true, env: { FORCE_COLOR: '0', NO_COLOR: '1' } });
62
+ const proc = ExecUtil.spawn('npx', ['trv', 'test:count', ...patterns],
63
+ {
64
+ stdio: 'pipe',
65
+ catchAsResult: true,
66
+ env: { ...Env.FORCE_COLOR.export(0), ...Env.NO_COLOR.export(true) }
67
+ }
68
+ );
62
69
  const countRes = await proc.result;
63
70
  if (!countRes.valid) {
64
71
  throw new Error(countRes.stderr);
@@ -70,6 +77,6 @@ export class RunnerUtil {
70
77
  * Determine if we should invoke the debugger
71
78
  */
72
79
  static get tryDebugger(): boolean {
73
- return process.env.TRV_TEST_BREAK_ENTRY === '1';
80
+ return Env.TRV_TEST_BREAK_ENTRY.isTrue;
74
81
  }
75
82
  }
@@ -1,6 +1,6 @@
1
1
  import { RootRegistry, MethodSource } from '@travetto/registry';
2
- import { WorkPool, IterableWorkSet, ManualAsyncIterator } from '@travetto/worker';
3
- import { RootIndex } from '@travetto/manifest';
2
+ import { WorkPool, WorkQueue } from '@travetto/worker';
3
+ import { RuntimeIndex } from '@travetto/manifest';
4
4
  import { ObjectUtil } from '@travetto/base';
5
5
 
6
6
  import { SuiteRegistry } from '../registry/suite';
@@ -27,22 +27,16 @@ export class TestWatcher {
27
27
  static async watch(format: string, runAllOnStart = true): Promise<void> {
28
28
  console.debug('Listening for changes');
29
29
 
30
- const itr = new ManualAsyncIterator<string>();
31
- const src = new IterableWorkSet(itr);
30
+ const itr = new WorkQueue<string>();
32
31
 
33
32
  await SuiteRegistry.init();
34
33
  SuiteRegistry.listen(RootRegistry);
35
34
 
36
35
  const consumer = new CumulativeSummaryConsumer(await TestConsumerRegistry.getInstance(format));
37
- const pool = new WorkPool(buildStandardTestManager(consumer), {
38
- idleTimeoutMillis: 120000,
39
- min: 2,
40
- max: WorkPool.DEFAULT_SIZE
41
- });
42
36
 
43
37
  new MethodSource(RootRegistry).on(e => {
44
38
  const [cls, method] = (e.prev ?? e.curr ?? []);
45
- if (!cls || RootIndex.getFunctionMetadata(cls)?.abstract) {
39
+ if (!cls || RuntimeIndex.getFunctionMetadata(cls)?.abstract) {
46
40
  return;
47
41
  }
48
42
  if (!method) {
@@ -60,7 +54,7 @@ export class TestWatcher {
60
54
  type: 'removeTest',
61
55
  method: method?.name,
62
56
  classId: cls?.Ⲑid,
63
- file: RootIndex.getFunctionMetadata(cls)?.source
57
+ file: RuntimeIndex.getFunctionMetadata(cls)?.source
64
58
  });
65
59
  }
66
60
  });
@@ -72,6 +66,7 @@ export class TestWatcher {
72
66
 
73
67
  process.on('message', ev => {
74
68
  if (isRunEvent(ev)) {
69
+ console.debug('Manually triggered', ev);
75
70
  itr.add([ev.file, ev.class, ev.method].filter(x => !!x).join('#'), true);
76
71
  }
77
72
  });
@@ -85,6 +80,14 @@ export class TestWatcher {
85
80
  }
86
81
  }
87
82
 
88
- await pool.process(src);
83
+ await WorkPool.run(
84
+ () => buildStandardTestManager(consumer),
85
+ itr,
86
+ {
87
+ idleTimeoutMillis: 120000,
88
+ min: 2,
89
+ max: WorkPool.DEFAULT_SIZE
90
+ }
91
+ );
89
92
  }
90
93
  }
@@ -1,5 +1,5 @@
1
1
  import { Class, ConcreteClass } from '@travetto/base';
2
- import { RootIndex } from '@travetto/manifest';
2
+ import { RuntimeIndex, RuntimeContext } from '@travetto/manifest';
3
3
  import { MetadataRegistry } from '@travetto/registry';
4
4
 
5
5
  import { SuiteConfig } from '../model/suite';
@@ -14,15 +14,15 @@ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> {
14
14
  * Find all valid tests (ignoring abstract)
15
15
  */
16
16
  getValidClasses(): Class[] {
17
- return this.getClasses().filter(c => !RootIndex.getFunctionMetadata(c)?.abstract);
17
+ return this.getClasses().filter(c => !RuntimeIndex.getFunctionMetadata(c)?.abstract);
18
18
  }
19
19
 
20
20
  createPending(cls: Class): Partial<SuiteConfig> {
21
21
  return {
22
22
  class: cls,
23
- module: RootIndex.manifest.mainModule,
23
+ module: RuntimeContext.main.name,
24
24
  classId: cls.Ⲑid,
25
- file: RootIndex.getFunctionMetadata(cls)!.source,
25
+ file: RuntimeIndex.getFunctionMetadata(cls)!.source,
26
26
  tests: [],
27
27
  beforeAll: [],
28
28
  beforeEach: [],
@@ -34,8 +34,8 @@ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> {
34
34
  override createPendingField(cls: Class, fn: Function): Partial<TestConfig> {
35
35
  return {
36
36
  class: cls,
37
- module: RootIndex.manifest.mainModule,
38
- file: RootIndex.getFunctionMetadata(cls)!.source,
37
+ module: RuntimeContext.main.name,
38
+ file: RuntimeIndex.getFunctionMetadata(cls)!.source,
39
39
  methodName: fn.name
40
40
  };
41
41
  }
@@ -91,7 +91,7 @@ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> {
91
91
  getRunParams(file: string, clsName?: string, method?: string): { suites: SuiteConfig[] } | { suite: SuiteConfig, test?: TestConfig } {
92
92
  if (clsName && /^\d+$/.test(clsName)) { // If we only have a line number
93
93
  const line = parseInt(clsName, 10);
94
- const suites = this.getValidClasses().filter(cls => RootIndex.getFunctionMetadata(cls)!.source === file).map(x => this.get(x)).filter(x => !x.skip);
94
+ const suites = this.getValidClasses().filter(cls => RuntimeIndex.getFunctionMetadata(cls)!.source === file).map(x => this.get(x)).filter(x => !x.skip);
95
95
  const suite = suites.find(x => x.lines && (line >= x.lines.start && line <= x.lines.end));
96
96
 
97
97
  if (suite) {
@@ -113,7 +113,7 @@ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> {
113
113
  } else {
114
114
  const suites = this.getValidClasses()
115
115
  .map(x => this.get(x))
116
- .filter(x => !RootIndex.getFunctionMetadata(x.class)?.abstract); // Do not run abstract suites
116
+ .filter(x => !RuntimeIndex.getFunctionMetadata(x.class)?.abstract); // Do not run abstract suites
117
117
  return { suites };
118
118
  }
119
119
  }
package/src/trv.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ import { TimeSpan } from '@travetto/base';
2
+
3
+ declare global {
4
+ interface TravettoEnv {
5
+ /**
6
+ * The default time to wait for each phase to finish.
7
+ * @default 15s
8
+ */
9
+ TRV_TEST_PHASE_TIMEOUT: TimeSpan | number;
10
+ /**
11
+ * The default time for a single test to finish.
12
+ * @default 5s
13
+ */
14
+ TRV_TEST_TIMEOUT: TimeSpan | number;
15
+ /**
16
+ * An additional wait for triggering test runs, useful for code that takes time to warm up
17
+ */
18
+ TRV_TEST_DELAY: TimeSpan | number;
19
+ /**
20
+ * Should the test break on the first line of debugging
21
+ */
22
+ TRV_TEST_BREAK_ENTRY: boolean;
23
+ }
24
+ }
@@ -1,7 +1,8 @@
1
- import { createWriteStream } from 'fs';
1
+ import { createWriteStream } from 'node:fs';
2
+ import timers from 'node:timers/promises';
2
3
 
3
- import { ManifestFileUtil, RootIndex } from '@travetto/manifest';
4
- import { ConsoleManager, TimeUtil } from '@travetto/base';
4
+ import { ManifestFileUtil, RuntimeIndex } from '@travetto/manifest';
5
+ import { ConsoleManager, Env, TimeUtil } from '@travetto/base';
5
6
  import { ChildCommChannel } from '@travetto/worker';
6
7
 
7
8
  import { ErrorUtil } from '../consumer/error';
@@ -32,8 +33,8 @@ export class TestChildWorker extends ChildCommChannel<RunEvent> {
32
33
  * Start the worker
33
34
  */
34
35
  async activate(): Promise<void> {
35
- if (/\b@travetto[/]test\b/.test(process.env.DEBUG ?? '')) {
36
- const stdout = createWriteStream(ManifestFileUtil.toolPath(RootIndex, `test-worker.${process.pid}.log`), { flags: 'a' });
36
+ if (/\b@travetto[/]test\b/.test(Env.DEBUG.val ?? '')) {
37
+ const stdout = createWriteStream(ManifestFileUtil.toolPath(RuntimeIndex, `test-worker.${process.pid}.log`), { flags: 'a' });
37
38
  const c = new console.Console({ stdout, inspectOptions: { depth: 4, colors: false } });
38
39
  ConsoleManager.set({ onLog: (ev) => c[ev.level](process.pid, ...ev.args) });
39
40
  } else {
@@ -48,7 +49,7 @@ export class TestChildWorker extends ChildCommChannel<RunEvent> {
48
49
  // Let parent know the child is ready for handling commands
49
50
  this.send(Events.READY);
50
51
 
51
- await TimeUtil.wait('10m');
52
+ await timers.setTimeout(TimeUtil.timeToMs('10m'));
52
53
  }
53
54
 
54
55
  /**
@@ -1,5 +1,5 @@
1
- import { RootIndex } from '@travetto/manifest';
2
- import { ExecUtil } from '@travetto/base';
1
+ import { RuntimeIndex } from '@travetto/manifest';
2
+ import { Env, ExecUtil } from '@travetto/base';
3
3
  import { ParentCommChannel, Worker } from '@travetto/worker';
4
4
 
5
5
  import { Events, RunEvent } from './types';
@@ -21,11 +21,11 @@ function buildEvent(ev: string): RunEvent {
21
21
  /**
22
22
  * Produce a handler for the child worker
23
23
  */
24
- export function buildStandardTestManager(consumer: TestConsumer): () => Worker<string> {
24
+ export function buildStandardTestManager(consumer: TestConsumer): Worker<string> {
25
25
  /**
26
26
  * Spawn a child
27
27
  */
28
- return () => ({
28
+ return {
29
29
  id: i += 1,
30
30
  active: true,
31
31
  async destroy(): Promise<void> { },
@@ -35,16 +35,19 @@ export function buildStandardTestManager(consumer: TestConsumer): () => Worker<s
35
35
 
36
36
  const event = buildEvent(file);
37
37
 
38
- const { module } = RootIndex.getEntry(event.file!)!;
39
- const cwd = RootIndex.getModule(module)!.sourcePath;
38
+ const { module } = RuntimeIndex.getEntry(event.file!)!;
39
+ const cwd = RuntimeIndex.getModule(module)!.sourcePath;
40
40
 
41
41
  const channel = new ParentCommChannel<TestEvent & { error?: Error }>(
42
42
  ExecUtil.fork(
43
- RootIndex.resolveFileImport('@travetto/cli/support/entry.trv'),
43
+ RuntimeIndex.resolveFileImport('@travetto/cli/support/entry.trv'),
44
44
  ['test:child'],
45
45
  {
46
46
  cwd,
47
- env: { TRV_MANIFEST: RootIndex.getModule(module)!.outputPath, TRV_QUIET: '1' },
47
+ env: {
48
+ ...Env.TRV_MANIFEST.export(RuntimeIndex.getModule(module)!.outputPath),
49
+ ...Env.TRV_QUIET.export(true)
50
+ },
48
51
  stdio: ['ignore', 'ignore', 2, 'ipc']
49
52
  }
50
53
  )
@@ -80,5 +83,5 @@ export function buildStandardTestManager(consumer: TestConsumer): () => Worker<s
80
83
  throw ErrorUtil.deserializeError(error);
81
84
  }
82
85
  },
83
- });
86
+ };
84
87
  }
@@ -1,15 +1,8 @@
1
- import { ShutdownManager, TimeUtil } from '@travetto/base';
1
+ import timers from 'node:timers/promises';
2
2
 
3
- import type { RunState } from '../../src/execute/types';
3
+ import { Env } from '@travetto/base';
4
4
 
5
- declare global {
6
- // eslint-disable-next-line @typescript-eslint/no-namespace
7
- namespace NodeJS {
8
- interface ProcessEnv {
9
- TRV_TEST_DELAY?: '2s';
10
- }
11
- }
12
- }
5
+ import type { RunState } from '../../src/execute/types';
13
6
 
14
7
  /**
15
8
  * Run tests given the input state
@@ -21,15 +14,13 @@ export async function runTests(opts: RunState): Promise<void> {
21
14
 
22
15
  RunnerUtil.registerCleanup('runner');
23
16
 
24
- if (process.env.TRV_TEST_DELAY) {
25
- await TimeUtil.wait(process.env.TRV_TEST_DELAY);
26
- }
17
+ await timers.setTimeout(Env.TRV_TEST_DELAY.time ?? 0);
27
18
 
28
19
  try {
29
20
  const res = await new Runner(opts).run();
30
- return ShutdownManager.exit(res ? 0 : 1);
21
+ process.exitCode = res ? 0 : 1;
31
22
  } catch (err) {
32
23
  console.error('Test Worker Failed', { error: err });
33
- return ShutdownManager.exit(1);
24
+ process.exitCode = 1;
34
25
  }
35
26
  }
@@ -1,8 +1,8 @@
1
- import { EventEmitter } from 'events';
2
- import fs from 'fs/promises';
1
+ import { EventEmitter } from 'node:events';
2
+ import fs from 'node:fs/promises';
3
3
 
4
4
  import { path } from '@travetto/manifest';
5
- import { defineEnv } from '@travetto/base';
5
+ import { Env } from '@travetto/base';
6
6
  import { CliCommandShape, CliCommand, CliValidationError } from '@travetto/cli';
7
7
  import { WorkPool } from '@travetto/worker';
8
8
  import { Max, Min } from '@travetto/schema';
@@ -24,7 +24,11 @@ export class TestCommand implements CliCommandShape {
24
24
 
25
25
  preMain(): void {
26
26
  EventEmitter.defaultMaxListeners = 1000;
27
- defineEnv({ envName: 'test' });
27
+ Env.TRV_ROLE.set('test');
28
+ Env.TRV_ENV.set('test');
29
+ Env.DEBUG.set(false);
30
+ Env.TRV_LOG_PLAIN.set(true);
31
+ Env.TRV_LOG_TIME.clear();
28
32
  }
29
33
 
30
34
  isFirstFile(first: string): Promise<boolean> {
@@ -1,6 +1,6 @@
1
- import { EventEmitter } from 'events';
1
+ import { EventEmitter } from 'node:events';
2
2
 
3
- import { defineEnv, ShutdownManager } from '@travetto/base';
3
+ import { Env, ExecUtil } from '@travetto/base';
4
4
  import { CliCommand } from '@travetto/cli';
5
5
 
6
6
  /** Test child worker target */
@@ -8,15 +8,16 @@ import { CliCommand } from '@travetto/cli';
8
8
  export class TestChildWorkerCommand {
9
9
  preMain(): void {
10
10
  EventEmitter.defaultMaxListeners = 1000;
11
- process.env.FORCE_COLOR = '0';
12
- defineEnv({ envName: 'test' });
11
+ Env.TRV_ROLE.set('test');
12
+ Env.TRV_ENV.set('test');
13
+ Env.DEBUG.set(false);
14
+ Env.FORCE_COLOR.set(false);
15
+ Env.TRV_LOG_PLAIN.set(true);
16
+ Env.TRV_LOG_TIME.clear();
13
17
  }
14
18
 
15
19
  async main(): Promise<void> {
16
- if (process.send) {
17
- // Shutdown when ipc bridge is closed
18
- process.on('disconnect', () => ShutdownManager.execute());
19
- }
20
+ ExecUtil.exitOnDisconnect();
20
21
  const { TestChildWorker } = await import('../src/worker/child.js');
21
22
  return new TestChildWorker().activate();
22
23
  }
@@ -1,6 +1,6 @@
1
1
  import { CliCommand } from '@travetto/cli';
2
- import { RootIndex } from '@travetto/manifest';
3
- import { defineEnv } from '@travetto/base';
2
+ import { RuntimeIndex } from '@travetto/manifest';
3
+ import { Env } from '@travetto/base';
4
4
 
5
5
  import { SuiteRegistry } from '../src/registry/suite';
6
6
  import { RunnerUtil } from '../src/execute/util';
@@ -9,7 +9,8 @@ import { RunnerUtil } from '../src/execute/util';
9
9
  export class TestCountCommand {
10
10
 
11
11
  preMain(): void {
12
- defineEnv({ debug: false, envName: 'test' });
12
+ Env.TRV_ROLE.set('test');
13
+ Env.DEBUG.set(false);
13
14
  }
14
15
 
15
16
  async main(patterns: string[]) {
@@ -30,7 +31,7 @@ export class TestCountCommand {
30
31
  const suites = SuiteRegistry.getClasses();
31
32
  const total = suites
32
33
  .map(c => SuiteRegistry.get(c))
33
- .filter(c => !RootIndex.getFunctionMetadata(c.class)?.abstract)
34
+ .filter(c => !RuntimeIndex.getFunctionMetadata(c.class)?.abstract)
34
35
  .reduce((acc, c) => acc + (c.tests?.length ?? 0), 0);
35
36
 
36
37
  console.log(total);
@@ -1,4 +1,4 @@
1
- import { defineEnv } from '@travetto/base';
1
+ import { Env } from '@travetto/base';
2
2
  import { CliCommand } from '@travetto/cli';
3
3
 
4
4
  import { runTests } from './bin/run';
@@ -11,7 +11,10 @@ export class TestDirectCommand {
11
11
  format: TestFormat = 'tap';
12
12
 
13
13
  preMain(): void {
14
- defineEnv({ envName: 'test' });
14
+ Env.TRV_ROLE.set('test');
15
+ Env.TRV_ENV.set('test');
16
+ Env.TRV_LOG_PLAIN.set(true);
17
+ Env.TRV_LOG_TIME.clear();
15
18
  }
16
19
 
17
20
  main(file: string, args: string[]): Promise<void> {
@@ -1,4 +1,4 @@
1
- import { defineEnv } from '@travetto/base';
1
+ import { Env, ExecUtil } from '@travetto/base';
2
2
  import { CliCommand, CliUtil } from '@travetto/cli';
3
3
 
4
4
  import { TestFormat } from './bin/types';
@@ -13,7 +13,8 @@ export class TestWatcherCommand {
13
13
  mode: 'all' | 'change' = 'all';
14
14
 
15
15
  preMain(): void {
16
- defineEnv({ envName: 'test', dynamic: true });
16
+ Env.TRV_ROLE.set('test');
17
+ Env.TRV_DYNAMIC.set(true);
17
18
  }
18
19
 
19
20
  async main(): Promise<void> {
@@ -21,10 +22,7 @@ export class TestWatcherCommand {
21
22
  return;
22
23
  }
23
24
 
24
- // Quit on parent disconnect
25
- if (process.send) {
26
- process.on('disconnect', () => process.exit(0));
27
- }
25
+ ExecUtil.exitOnDisconnect();
28
26
 
29
27
  try {
30
28
  const { TestWatcher } = await import('../src/execute/watcher.js');