@travetto/test 8.0.0-alpha.2 → 8.0.0-alpha.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +24 -22
  2. package/__index__.ts +9 -9
  3. package/package.json +8 -8
  4. package/src/assert/capture.ts +1 -2
  5. package/src/assert/check.ts +56 -33
  6. package/src/assert/types.ts +1 -1
  7. package/src/assert/util.ts +26 -81
  8. package/src/consumer/decorator.ts +2 -2
  9. package/src/consumer/enhancer.ts +3 -3
  10. package/src/consumer/registry-adapter.ts +3 -3
  11. package/src/consumer/registry-index.ts +6 -5
  12. package/src/consumer/types/cumulative.ts +11 -23
  13. package/src/consumer/types/delegating.ts +8 -2
  14. package/src/consumer/types/event.ts +2 -2
  15. package/src/consumer/types/exec.ts +3 -4
  16. package/src/consumer/types/json.ts +3 -4
  17. package/src/consumer/types/noop.ts +3 -3
  18. package/src/consumer/types/runnable.ts +4 -5
  19. package/src/consumer/types/summarizer.ts +5 -18
  20. package/src/consumer/types/tap-summary.ts +39 -30
  21. package/src/consumer/types/tap.ts +64 -56
  22. package/src/consumer/types/util.ts +31 -0
  23. package/src/consumer/types/xunit.ts +10 -8
  24. package/src/consumer/types.ts +6 -10
  25. package/src/decorator/suite.ts +3 -3
  26. package/src/decorator/test.ts +6 -4
  27. package/src/execute/barrier.ts +15 -9
  28. package/src/execute/console.ts +3 -4
  29. package/src/execute/executor.ts +101 -157
  30. package/src/execute/phase.ts +12 -22
  31. package/src/execute/run.ts +41 -40
  32. package/src/execute/watcher.ts +12 -19
  33. package/src/fixture.ts +6 -7
  34. package/src/model/common.ts +1 -1
  35. package/src/model/error.ts +2 -2
  36. package/src/model/event.ts +14 -9
  37. package/src/model/suite.ts +15 -13
  38. package/src/model/test.ts +11 -8
  39. package/src/model/util.ts +102 -9
  40. package/src/registry/registry-adapter.ts +10 -13
  41. package/src/registry/registry-index.ts +13 -11
  42. package/src/worker/child.ts +10 -9
  43. package/src/worker/standard.ts +11 -14
  44. package/src/worker/types.ts +3 -7
  45. package/support/bin/run.ts +1 -1
  46. package/support/cli.test.ts +20 -15
  47. package/support/cli.test_child.ts +8 -3
  48. package/support/cli.test_diff.ts +11 -7
  49. package/support/cli.test_digest.ts +10 -4
  50. package/support/cli.test_direct.ts +11 -7
  51. package/support/cli.test_watch.ts +6 -4
  52. package/support/transformer.assert.ts +40 -32
@@ -1,4 +1,4 @@
1
- import { castTo, type Class, type ClassInstance, getClass } from '@travetto/runtime';
1
+ import { type Class, type ClassInstance, castTo, getClass } from '@travetto/runtime';
2
2
 
3
3
  import type { SuiteConfig } from '../model/suite.ts';
4
4
  import { SuiteRegistryIndex } from '../registry/registry-index.ts';
@@ -18,7 +18,7 @@ export function Suite(description?: string | Partial<SuiteConfig>, ...rest: Part
18
18
  SuiteRegistryIndex.getForRegister(cls).register(
19
19
  ...(typeof description !== 'string' && description ? [description] : []),
20
20
  ...rest,
21
- ...(typeof description === 'string' ? [{ description }] : []),
21
+ ...(typeof description === 'string' ? [{ description }] : [])
22
22
  );
23
23
  return cls;
24
24
  };
@@ -76,4 +76,4 @@ export function AfterEach() {
76
76
  });
77
77
  return descriptor;
78
78
  };
79
- }
79
+ }
@@ -27,10 +27,12 @@ export function Test(...rest: Partial<TestConfig>[]): MethodDecorator;
27
27
  export function Test(description: string, ...rest: Partial<TestConfig>[]): MethodDecorator;
28
28
  export function Test(description?: string | Partial<TestConfig>, ...rest: Partial<TestConfig>[]): MethodDecorator {
29
29
  return (instance: ClassInstance, property: string, descriptor: PropertyDescriptor) => {
30
- SuiteRegistryIndex.getForRegister(getClass(instance)).registerTest(property, descriptor.value,
31
- ...(typeof description !== 'string' && description) ? [description] : [],
30
+ SuiteRegistryIndex.getForRegister(getClass(instance)).registerTest(
31
+ property,
32
+ descriptor.value,
33
+ ...(typeof description !== 'string' && description ? [description] : []),
32
34
  ...rest,
33
- ...(typeof description === 'string') ? [{ description }] : []
35
+ ...(typeof description === 'string' ? [{ description }] : [])
34
36
  );
35
37
  return descriptor;
36
38
  };
@@ -58,4 +60,4 @@ export function Timeout(ms: number): MethodDecorator {
58
60
  SuiteRegistryIndex.getForRegister(getClass(instance)).registerTest(property, descriptor.value, { timeout: ms });
59
61
  return descriptor;
60
62
  };
61
- }
63
+ }
@@ -1,5 +1,5 @@
1
- import { isPromise } from 'node:util/types';
2
1
  import { createHook, executionAsyncId } from 'node:async_hooks';
2
+ import { isPromise } from 'node:util/types';
3
3
 
4
4
  import { type TimeSpan, TimeUtil, Util } from '@travetto/runtime';
5
5
 
@@ -11,7 +11,7 @@ export class Barrier {
11
11
  /**
12
12
  * Track timeout
13
13
  */
14
- static timeout(duration: number | TimeSpan, operation: string = 'Operation'): { promise: Promise<void>, resolve: () => unknown } {
14
+ static timeout(duration: number | TimeSpan, operation: string = 'Operation'): { promise: Promise<void>; resolve: () => unknown } {
15
15
  const resolver = Promise.withResolvers<void>();
16
16
  const durationMs = TimeUtil.duration(duration, 'ms');
17
17
  let timeout: NodeJS.Timeout;
@@ -22,25 +22,31 @@ export class Barrier {
22
22
  timeout = setTimeout(() => resolver.reject(new TimeoutError(msg)), durationMs).unref();
23
23
  }
24
24
 
25
- resolver.promise.finally(() => { clearTimeout(timeout); });
25
+ resolver.promise.finally(() => {
26
+ clearTimeout(timeout);
27
+ });
26
28
  return resolver;
27
29
  }
28
30
 
29
31
  /**
30
32
  * Track uncaught error
31
33
  */
32
- static uncaughtErrorPromise(): { promise: Promise<void>, resolve: () => unknown } {
34
+ static uncaughtErrorPromise(): { promise: Promise<void>; resolve: () => unknown } {
33
35
  const uncaught = Promise.withResolvers<void>();
34
- const onError = (error: Error): void => { Util.queueMacroTask().then(() => uncaught.reject(error)); };
36
+ const onError = (error: Error): void => {
37
+ Util.queueMacroTask().then(() => uncaught.reject(error));
38
+ };
35
39
  UNCAUGHT_ERR_EVENTS.map(key => process.on(key, onError));
36
- uncaught.promise.finally(() => { UNCAUGHT_ERR_EVENTS.map(key => process.off(key, onError)); });
40
+ uncaught.promise.finally(() => {
41
+ UNCAUGHT_ERR_EVENTS.map(key => process.off(key, onError));
42
+ });
37
43
  return uncaught;
38
44
  }
39
45
 
40
46
  /**
41
47
  * Promise capturer
42
48
  */
43
- static capturePromises(): { start: () => Promise<void>, finish: () => Promise<void>, cleanup: () => void } {
49
+ static capturePromises(): { start: () => Promise<void>; finish: () => Promise<void>; cleanup: () => void } {
44
50
  const pending = new Map<number, Promise<unknown>>();
45
51
  let id: number = 0;
46
52
 
@@ -90,7 +96,7 @@ export class Barrier {
90
96
  let capturedError: Error | undefined;
91
97
  const opProm = operation().then(() => promises.finish());
92
98
 
93
- await Promise.race([opProm, uncaught.promise, timer.promise]).catch(error => capturedError ??= error);
99
+ await Promise.race([opProm, uncaught.promise, timer.promise]).catch(error => (capturedError ??= error));
94
100
 
95
101
  return capturedError;
96
102
  } finally {
@@ -99,4 +105,4 @@ export class Barrier {
99
105
  uncaught.resolve();
100
106
  }
101
107
  }
102
- }
108
+ }
@@ -1,6 +1,7 @@
1
1
  import util from 'node:util';
2
2
 
3
3
  import { type ConsoleEvent, type ConsoleListener, ConsoleManager } from '@travetto/runtime';
4
+
4
5
  import type { TestLog } from '../model/test.ts';
5
6
 
6
7
  /**
@@ -21,9 +22,7 @@ export class ConsoleCapture implements ConsoleListener {
21
22
  log({ args, scope: _, ...rest }: ConsoleEvent): void {
22
23
  this.out.push({
23
24
  ...rest,
24
- message: args
25
- .map((arg => typeof arg === 'string' ? arg : util.inspect(arg, false, 5)))
26
- .join(' ')
25
+ message: args.map(arg => (typeof arg === 'string' ? arg : util.inspect(arg, false, 5))).join(' ')
27
26
  });
28
27
  }
29
28
 
@@ -33,4 +32,4 @@ export class ConsoleCapture implements ConsoleListener {
33
32
  ConsoleManager.set(ConsoleCapture.#listener);
34
33
  return result;
35
34
  }
36
- }
35
+ }
@@ -1,17 +1,17 @@
1
- import { Env, TimeUtil, Runtime, castTo, classConstruct } from '@travetto/runtime';
2
1
  import { Registry } from '@travetto/registry';
2
+ import { castTo, classConstruct, Env, Runtime, TimeUtil } from '@travetto/runtime';
3
3
 
4
- import type { TestConfig, TestResult, TestRun } from '../model/test.ts';
5
- import type { SuiteConfig, SuiteResult } from '../model/suite.ts';
6
- import type { TestConsumerShape } from '../consumer/types.ts';
7
- import { AssertCheck } from '../assert/check.ts';
8
4
  import { AssertCapture } from '../assert/capture.ts';
9
- import { ConsoleCapture } from './console.ts';
10
- import { TestPhaseManager } from './phase.ts';
5
+ import { AssertCheck } from '../assert/check.ts';
11
6
  import { AssertUtil } from '../assert/util.ts';
12
- import { Barrier } from './barrier.ts';
13
- import { SuiteRegistryIndex } from '../registry/registry-index.ts';
7
+ import type { TestConsumerShape } from '../consumer/types.ts';
8
+ import type { SuiteConfig, SuiteResult } from '../model/suite.ts';
9
+ import type { TestConfig, TestResult, TestRun } from '../model/test.ts';
14
10
  import { TestModelUtil } from '../model/util.ts';
11
+ import { SuiteRegistryIndex } from '../registry/registry-index.ts';
12
+ import { Barrier } from './barrier.ts';
13
+ import { ConsoleCapture } from './console.ts';
14
+ import { TestPhaseManager } from './phase.ts';
15
15
 
16
16
  const TEST_TIMEOUT = TimeUtil.duration(Env.TRV_TEST_TIMEOUT.value || 5000, 'ms');
17
17
 
@@ -19,45 +19,24 @@ const TEST_TIMEOUT = TimeUtil.duration(Env.TRV_TEST_TIMEOUT.value || 5000, 'ms')
19
19
  * Support execution of the tests
20
20
  */
21
21
  export class TestExecutor {
22
-
23
22
  #consumer: TestConsumerShape;
24
23
 
25
24
  constructor(consumer: TestConsumerShape) {
26
25
  this.#consumer = consumer;
27
26
  }
28
27
 
29
- #onSuiteTestError(result: TestResult, test: TestConfig): void {
30
- this.#consumer.onEvent({ type: 'test', phase: 'before', test });
31
- for (const assertion of result.assertions) {
32
- this.#consumer.onEvent({ type: 'assertion', phase: 'after', assertion });
33
- }
34
- this.#consumer.onEvent({ type: 'test', phase: 'after', test: result });
35
- }
36
-
37
- #recordSuiteErrors(suiteConfig: SuiteConfig, suiteResult: SuiteResult, errors: TestResult[]): void {
38
- for (const test of errors) {
39
- if (!suiteResult.tests[test.methodName]) {
40
- this.#onSuiteTestError(test, suiteConfig.tests[test.methodName]);
41
- suiteResult.errored += 1;
42
- suiteResult.total += 1;
43
- }
44
- }
45
- }
46
-
47
28
  /**
48
29
  * Raw execution, runs the method and then returns any thrown errors as the result.
49
30
  *
50
31
  * This method should never throw under any circumstances.
51
32
  */
52
- async #executeTestMethod(test: TestConfig): Promise<Error | undefined> {
53
- const suite = SuiteRegistryIndex.getConfig(test.class);
54
-
33
+ async #executeTestMethod(instance: unknown, test: TestConfig): Promise<Error | undefined> {
55
34
  // Ensure all the criteria below are satisfied before moving forward
56
35
  return Barrier.awaitOperation(test.timeout || TEST_TIMEOUT, async () => {
57
36
  const env = process.env;
58
37
  process.env = { ...env }; // Created an isolated environment
59
38
  try {
60
- await castTo<Record<string, Function>>(suite.instance)[test.methodName]();
39
+ await castTo<Record<string, Function>>(instance)[test.methodName]();
61
40
  } finally {
62
41
  process.env = env; // Restore
63
42
  }
@@ -73,96 +52,44 @@ export class TestExecutor {
73
52
  }
74
53
  }
75
54
 
76
- #skipTest(test: TestConfig, result: SuiteResult): void {
77
- // Mark test start
78
- this.#consumer.onEvent({ type: 'test', phase: 'before', test });
79
- result.skipped += 1;
80
- result.total += 1;
81
- this.#consumer.onEvent({
82
- type: 'test',
83
- phase: 'after',
84
- test: {
85
- ...test,
86
- suiteLineStart: result.lineStart,
87
- assertions: [], duration: 0, durationTotal: 0, output: [], status: 'skipped'
88
- }
89
- });
90
- }
91
-
92
- /**
93
- * An empty suite result based on a suite config
94
- */
95
- createSuiteResult(suite: SuiteConfig, override?: Partial<SuiteResult>): SuiteResult {
96
- return {
97
- passed: 0,
98
- failed: 0,
99
- errored: 0,
100
- skipped: 0,
101
- unknown: 0,
102
- total: 0,
103
- status: 'unknown',
104
- lineStart: suite.lineStart,
105
- lineEnd: suite.lineEnd,
106
- import: suite.import,
107
- classId: suite.classId,
108
- sourceHash: suite.sourceHash,
109
- duration: 0,
110
- tests: {},
111
- ...override
112
- };
113
- }
114
-
115
55
  /**
116
56
  * Execute the test, capture output, assertions and promises
117
57
  */
118
- async executeTest(test: TestConfig, suite: SuiteConfig): Promise<TestResult> {
58
+ async executeTest(instance: unknown, test: TestConfig, suite: SuiteConfig, override?: Partial<TestResult>): Promise<TestResult> {
59
+ const result = TestModelUtil.createTestResult(suite, test, override);
119
60
 
120
61
  // Mark test start
121
62
  this.#consumer.onEvent({ type: 'test', phase: 'before', test });
122
63
 
123
- const startTime = Date.now();
124
-
125
- const result: TestResult = {
126
- methodName: test.methodName,
127
- description: test.description,
128
- classId: test.classId,
129
- tags: test.tags,
130
- suiteLineStart: suite.lineStart,
131
- lineStart: test.lineStart,
132
- lineEnd: test.lineEnd,
133
- lineBodyStart: test.lineBodyStart,
134
- import: test.import,
135
- declarationImport: test.declarationImport,
136
- sourceHash: test.sourceHash,
137
- status: 'unknown',
138
- assertions: [],
139
- duration: 0,
140
- durationTotal: 0,
141
- output: [],
142
- };
143
-
144
64
  // Emit every assertion as it occurs
145
- const getAssertions = AssertCapture.collector(test, asrt =>
146
- this.#consumer.onEvent({
147
- type: 'assertion',
148
- phase: 'after',
149
- assertion: asrt
150
- })
65
+ const getAssertions = AssertCapture.collector(test, item =>
66
+ this.#consumer.onEvent({ type: 'assertion', phase: 'after', assertion: item })
151
67
  );
152
68
 
153
69
  const consoleCapture = new ConsoleCapture().start(); // Capture all output from transpiled code
154
70
 
155
- // Run method and get result
156
- const error = await this.#executeTestMethod(test);
157
- const [status, finalError] = AssertCheck.validateTestResultError(test, error);
71
+ // Already finished
72
+ if (result.status !== 'unknown') {
73
+ if (result.error) {
74
+ result.assertions.push(AssertUtil.generateAssertion({ suite, test, error: result.error }));
75
+ }
76
+ for (const item of result.assertions ?? []) {
77
+ AssertCapture.add(item);
78
+ }
79
+ } else {
80
+ // Run method and get result
81
+ const startTime = Date.now();
82
+ const error = await this.#executeTestMethod(instance, test);
83
+ const [status, finalError] = AssertCheck.validateTestResultError(test, error);
84
+ result.status = status;
85
+ result.selfDuration = Date.now() - startTime;
86
+ if (finalError) {
87
+ result.error = finalError;
88
+ }
89
+ }
158
90
 
159
- Object.assign(result, {
160
- status,
161
- output: consoleCapture.end(),
162
- assertions: getAssertions(),
163
- duration: Date.now() - startTime,
164
- ...(finalError ? { error: finalError } : {})
165
- });
91
+ result.output = consoleCapture.end();
92
+ result.assertions = getAssertions();
166
93
 
167
94
  // Mark completion
168
95
  this.#consumer.onEvent({ type: 'test', phase: 'after', test: result });
@@ -174,19 +101,22 @@ export class TestExecutor {
174
101
  * Execute an entire suite
175
102
  */
176
103
  async executeSuite(suite: SuiteConfig, tests: TestConfig[]): Promise<void> {
104
+ const instance = classConstruct(suite.class);
177
105
 
178
- suite.instance = classConstruct(suite.class);
106
+ const shouldSkip = await this.#shouldSkip(suite, instance);
179
107
 
180
- const shouldSkip = await this.#shouldSkip(suite, suite.instance);
108
+ const result: SuiteResult = TestModelUtil.createSuiteResult(suite);
181
109
 
182
110
  if (shouldSkip) {
183
111
  this.#consumer.onEvent({
184
- phase: 'after', type: 'suite',
185
- suite: this.createSuiteResult(suite, {
112
+ phase: 'after',
113
+ type: 'suite',
114
+ suite: {
115
+ ...result,
186
116
  status: 'skipped',
187
117
  skipped: tests.length,
188
118
  total: tests.length
189
- })
119
+ }
190
120
  });
191
121
  }
192
122
 
@@ -194,69 +124,82 @@ export class TestExecutor {
194
124
  return;
195
125
  }
196
126
 
197
- const result: SuiteResult = this.createSuiteResult(suite);
198
- const validTestMethodNames = new Set(tests.map(t => t.methodName));
199
- const testConfigs = Object.fromEntries(
200
- Object.entries(suite.tests).filter(([key]) => validTestMethodNames.has(key))
201
- );
202
-
127
+ const manager = new TestPhaseManager(suite, instance);
128
+ const originalEnv = { ...process.env };
203
129
  const startTime = Date.now();
130
+ const testResultOverrides: Record<string, Partial<TestResult>> = {};
131
+
132
+ const validTestMethodNames = new Set(tests.map(t => t.methodName));
133
+ const testConfigs = Object.fromEntries(Object.entries(suite.tests).filter(([key]) => validTestMethodNames.has(key)));
204
134
 
205
135
  // Mark suite start
206
136
  this.#consumer.onEvent({ phase: 'before', type: 'suite', suite: { ...suite, tests: testConfigs } });
207
137
 
208
- const manager = new TestPhaseManager(suite);
209
-
210
- const originalEnv = { ...process.env };
211
-
212
138
  try {
213
139
  // Handle the BeforeAll calls
214
140
  await manager.startPhase('all');
141
+ } catch (someError) {
142
+ const suiteError = await manager.onError('all', someError);
143
+ for (const method of validTestMethodNames) {
144
+ testResultOverrides[method] ??= { status: 'errored', error: suiteError };
145
+ }
146
+ }
215
147
 
216
- const suiteEnv = { ...process.env };
148
+ const suiteEnv = { ...process.env };
217
149
 
218
- for (const test of tests ?? suite.tests) {
219
- if (await this.#shouldSkip(test, suite.instance)) {
220
- this.#skipTest(test, result);
221
- continue;
222
- }
150
+ for (const test of tests) {
151
+ // Reset env before each test
152
+ process.env = { ...suiteEnv };
223
153
 
224
- // Reset env before each test
225
- process.env = { ...suiteEnv };
154
+ const testStart = Date.now();
155
+ const testResultOverride = (testResultOverrides[test.methodName] ??= {});
226
156
 
227
- const testStart = Date.now();
228
- try {
157
+ if (await this.#shouldSkip(test, instance)) {
158
+ testResultOverride.status = 'skipped';
159
+ }
229
160
 
230
- // Handle BeforeEach
231
- await manager.startPhase('each');
161
+ try {
162
+ // Handle BeforeEach
163
+ testResultOverride.status || (await manager.startPhase('each'));
164
+ } catch (someError) {
165
+ const testError = await manager.onError('each', someError);
166
+ testResultOverride.error = testError;
167
+ testResultOverride.status = 'errored';
168
+ }
232
169
 
233
- // Run test
234
- const testResult = await this.executeTest(test, suite);
235
- result.tests[testResult.methodName] = testResult;
236
- result[testResult.status]++;
237
- result.total += 1;
170
+ // Run test
171
+ const testResult = await this.executeTest(instance, test, suite, testResultOverride);
238
172
 
239
- // Handle after each
240
- await manager.endPhase('each');
241
- testResult.durationTotal = Date.now() - testStart;
242
- } catch (testError) {
243
- const errors = await manager.errorPhase('each', testError, suite, test);
244
- this.#recordSuiteErrors(suite, result, errors);
173
+ // Handle after each
174
+ try {
175
+ testResultOverride.status || (await manager.endPhase('each'));
176
+ } catch (testError) {
177
+ if (!(testError instanceof Error)) {
178
+ throw testError;
245
179
  }
180
+ console.error('Failed to properly shutdown test', testError.message);
246
181
  }
247
182
 
183
+ result.tests[testResult.methodName] = testResult;
184
+ testResult.duration = Date.now() - testStart;
185
+ TestModelUtil.countTestResult(result, [testResult]);
186
+ }
187
+
188
+ try {
248
189
  // Handle after all
249
190
  await manager.endPhase('all');
250
191
  } catch (suiteError) {
251
- const errors = await manager.errorPhase('all', suiteError, suite);
252
- this.#recordSuiteErrors(suite, result, errors);
192
+ if (!(suiteError instanceof Error)) {
193
+ throw suiteError;
194
+ }
195
+ console.error('Failed to properly shutdown test', suiteError.message);
253
196
  }
254
197
 
255
198
  // Restore env
256
199
  process.env = { ...originalEnv };
257
200
 
258
201
  result.duration = Date.now() - startTime;
259
- result.status = TestModelUtil.countsToTestStatus(result);
202
+ result.status = TestModelUtil.computeTestStatus(result);
260
203
 
261
204
  // Mark suite complete
262
205
  this.#consumer.onEvent({ phase: 'after', type: 'suite', suite: result });
@@ -265,19 +208,15 @@ export class TestExecutor {
265
208
  /**
266
209
  * Handle executing a suite's test/tests based on command line inputs
267
210
  */
268
- async execute(run: TestRun): Promise<void> {
211
+ async execute(run: TestRun, singleFile?: boolean): Promise<void> {
269
212
  try {
270
213
  await Runtime.importFrom(run.import);
271
214
  } catch (error) {
272
215
  if (!(error instanceof Error)) {
273
216
  throw error;
274
217
  }
218
+ const suite = TestModelUtil.createImportErrorSuiteResult(run);
275
219
  console.error(error);
276
-
277
- // Fire import failure as a test failure for each test in the suite
278
- const { result, test, suite } = AssertUtil.gernerateImportFailure(run.import, error);
279
- this.#consumer.onEvent({ type: 'suite', phase: 'before', suite });
280
- this.#onSuiteTestError(result, test);
281
220
  this.#consumer.onEvent({ type: 'suite', phase: 'after', suite });
282
221
  return;
283
222
  }
@@ -291,8 +230,13 @@ export class TestExecutor {
291
230
  console.warn('Unable to find suites for ', run);
292
231
  }
293
232
 
233
+ if (singleFile) {
234
+ const testCount = suites.reduce((acc, suite) => acc + suite.tests.length, 0);
235
+ this.#consumer.onTestRunState?.({ testCount });
236
+ }
237
+
294
238
  for (const { suite, tests } of suites) {
295
239
  await this.executeSuite(suite, tests);
296
240
  }
297
241
  }
298
- }
242
+ }
@@ -1,9 +1,7 @@
1
- import { describeFunction, Env, TimeUtil } from '@travetto/runtime';
1
+ import { Env, TimeUtil } from '@travetto/runtime';
2
2
 
3
3
  import type { SuiteConfig, SuitePhase } from '../model/suite.ts';
4
- import { AssertUtil } from '../assert/util.ts';
5
4
  import { Barrier } from './barrier.ts';
6
- import type { TestConfig, TestResult } from '../model/test.ts';
7
5
 
8
6
  const TEST_PHASE_TIMEOUT = TimeUtil.duration(Env.TRV_TEST_PHASE_TIMEOUT.value ?? 15000, 'ms');
9
7
 
@@ -15,9 +13,11 @@ const TEST_PHASE_TIMEOUT = TimeUtil.duration(Env.TRV_TEST_PHASE_TIMEOUT.value ??
15
13
  export class TestPhaseManager {
16
14
  #progress: ('all' | 'each')[] = [];
17
15
  #suite: SuiteConfig;
16
+ #instance: unknown;
18
17
 
19
- constructor(suite: SuiteConfig) {
18
+ constructor(suite: SuiteConfig, instance: unknown) {
20
19
  this.#suite = suite;
20
+ this.#instance = instance;
21
21
  }
22
22
 
23
23
  /**
@@ -31,12 +31,10 @@ export class TestPhaseManager {
31
31
  }
32
32
 
33
33
  // Ensure all the criteria below are satisfied before moving forward
34
- error = await Barrier.awaitOperation(TEST_PHASE_TIMEOUT, async () => handler[phase]?.(this.#suite.instance));
34
+ error = await Barrier.awaitOperation(TEST_PHASE_TIMEOUT, async () => handler[phase]?.(this.#instance));
35
35
 
36
36
  if (error) {
37
- const toThrow = new Error(phase, { cause: error });
38
- Object.assign(toThrow, { import: describeFunction(handler.constructor) ?? undefined });
39
- throw toThrow;
37
+ throw error;
40
38
  }
41
39
  }
42
40
  }
@@ -58,21 +56,13 @@ export class TestPhaseManager {
58
56
  }
59
57
 
60
58
  /**
61
- * Handles if an error occurs during a phase, ensuring that we attempt to end the phase and then return the appropriate test results for the failure
59
+ * Handle an error during phase operation
62
60
  */
63
- async errorPhase(phase: 'all' | 'each', error: unknown, suite: SuiteConfig, test?: TestConfig): Promise<TestResult[]> {
64
- try { await this.endPhase(phase); } catch { }
65
- if (!(error instanceof Error)) { throw error; }
66
-
67
- // Don't propagate our own errors
68
- if (error.message === 'afterAll' || error.message === 'afterEach') {
69
- return [];
70
- }
71
-
72
- if (test) {
73
- return [AssertUtil.generateSuiteTestFailure({ suite, error, test })];
74
- } else {
75
- return AssertUtil.generateSuiteTestFailures(suite, error);
61
+ async onError(phase: 'all' | 'each', error: unknown): Promise<Error> {
62
+ if (!(error instanceof Error)) {
63
+ await this.endPhase(phase).catch(() => {});
64
+ throw error;
76
65
  }
66
+ return error;
77
67
  }
78
68
  }