@travetto/test 8.0.0-alpha.1 → 8.0.0-alpha.10

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
@@ -231,7 +231,7 @@ Options:
231
231
  -c, --concurrency <number> Number of tests to run concurrently (default: 9)
232
232
  -t, --tags <string> Tags to target or exclude when using globs
233
233
  -o, --format-options <string> Format options
234
- -h, --help display help for command
234
+ --help display help for command
235
235
  ```
236
236
 
237
237
  The regexes are the patterns of tests you want to run, and all tests must be found under the `test/` folder.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/test",
3
- "version": "8.0.0-alpha.1",
3
+ "version": "8.0.0-alpha.10",
4
4
  "type": "module",
5
5
  "description": "Declarative test framework",
6
6
  "keywords": [
@@ -28,15 +28,15 @@
28
28
  "directory": "module/test"
29
29
  },
30
30
  "dependencies": {
31
- "@travetto/registry": "^8.0.0-alpha.1",
32
- "@travetto/runtime": "^8.0.0-alpha.1",
33
- "@travetto/terminal": "^8.0.0-alpha.1",
34
- "@travetto/worker": "^8.0.0-alpha.1",
35
- "yaml": "^2.8.2"
31
+ "@travetto/registry": "^8.0.0-alpha.10",
32
+ "@travetto/runtime": "^8.0.0-alpha.10",
33
+ "@travetto/terminal": "^8.0.0-alpha.10",
34
+ "@travetto/worker": "^8.0.0-alpha.10",
35
+ "yaml": "^2.8.3"
36
36
  },
37
37
  "peerDependencies": {
38
- "@travetto/cli": "^8.0.0-alpha.1",
39
- "@travetto/transformer": "^8.0.0-alpha.1"
38
+ "@travetto/cli": "^8.0.0-alpha.15",
39
+ "@travetto/transformer": "^8.0.0-alpha.5"
40
40
  },
41
41
  "peerDependenciesMeta": {
42
42
  "@travetto/transformer": {
@@ -178,17 +178,13 @@ export class AssertCheck {
178
178
  static #onError(
179
179
  positive: boolean,
180
180
  message: string | undefined,
181
- error: unknown,
181
+ errorValue: unknown,
182
182
  missed: Error | undefined,
183
183
  shouldThrow: ThrowableError | undefined,
184
184
  assertion: CapturedAssertion
185
185
  ): void {
186
- if (!(error instanceof Error)) {
187
- error = new Error(`${error}`);
188
- }
189
- if (!(error instanceof Error)) {
190
- throw error;
191
- }
186
+ const error = errorValue instanceof Error ? errorValue : new Error(`${errorValue}`);
187
+
192
188
  if (positive) {
193
189
  missed = new assert.AssertionError({ message: 'Error thrown, but expected no errors' });
194
190
  missed.stack = error.stack;
@@ -1,10 +1,9 @@
1
1
  import util from 'node:util';
2
- import path from 'node:path';
3
2
 
4
- import { asFull, type Class, JSONUtil, hasFunction, Runtime, RuntimeIndex, Util } from '@travetto/runtime';
3
+ import { JSONUtil, hasFunction, RuntimeIndex, Util } from '@travetto/runtime';
5
4
 
6
- import type { TestConfig, TestResult } from '../model/test.ts';
7
- import type { SuiteConfig, SuiteResult } from '../model/suite.ts';
5
+ import type { Assertion, TestConfig } from '../model/test.ts';
6
+ import type { SuiteConfig } from '../model/suite.ts';
8
7
 
9
8
  const isCleanable = hasFunction<{ toClean(): unknown }>('toClean');
10
9
 
@@ -52,79 +51,21 @@ export class AssertUtil {
52
51
  /**
53
52
  * Generate a suite error given a suite config, and an error
54
53
  */
55
- static generateSuiteTestFailure(config: { suite: SuiteConfig, test: TestConfig, error: Error, importLocation?: string }): TestResult {
56
- const { suite, test, error, importLocation } = config;
54
+ static generateAssertion(config: { suite: SuiteConfig, test: TestConfig, error: Error, importLocation?: string }): Assertion {
55
+ const { suite, test, error: errorValue, importLocation } = config;
56
+ const error = (errorValue.cause && errorValue.cause instanceof Error) ? errorValue.cause : errorValue;
57
57
  const testImport = importLocation ?? test.import;
58
58
  const position = this.getPositionOfError(error);
59
59
  const line = position?.line ?? (testImport === suite.import ? suite.lineStart : 1);
60
- const testResult: TestResult = {
61
- ...suite.tests[test.methodName],
62
- suiteLineStart: suite.lineStart,
63
- status: 'errored',
60
+ return {
61
+ import: position?.import ?? testImport,
62
+ methodName: test.methodName,
63
+ classId: suite.classId,
64
+ operator: 'throw',
64
65
  error,
65
- duration: 0,
66
- durationTotal: 0,
67
- output: [],
68
- assertions: [{
69
- import: position?.import ?? testImport,
70
- methodName: test.methodName,
71
- classId: suite.classId,
72
- operator: 'throw',
73
- error,
74
- line,
75
- message: error.message.split(/\n/)[0],
76
- text: test.methodName
77
- }],
66
+ line,
67
+ message: error.message.split(/\n/)[0],
68
+ text: test.methodName
78
69
  };
79
-
80
- return testResult;
81
- }
82
-
83
- /**
84
- * Generate suite failure
85
- */
86
- static generateSuiteTestFailures(suite: SuiteConfig, error: Error): TestResult[] {
87
- const finalError = error.cause instanceof Error ? error.cause : error;
88
- return Object.values(suite.tests).map(test => this.generateSuiteTestFailure({ suite, test, error: finalError }));
89
- }
90
-
91
- /**
92
- * Define import failure as a TestResult
93
- */
94
- static gernerateImportFailure(importLocation: string, error: Error): { result: TestResult, test: TestConfig, suite: SuiteResult & SuiteConfig } {
95
- const name = path.basename(importLocation);
96
- const classId = `${RuntimeIndex.getFromImport(importLocation)?.id}#${name}`;
97
- const suite = asFull<SuiteConfig & SuiteResult>({
98
- class: asFull<Class>({ name }), classId, duration: 0, lineStart: 1, lineEnd: 1, import: importLocation
99
- });
100
- error.message = error.message.replaceAll(Runtime.mainSourcePath, '.');
101
- const result = this.generateSuiteTestFailure({
102
- suite,
103
- test: {
104
- methodName: 'require',
105
- classId,
106
- import: importLocation,
107
- class: suite.class,
108
- lineBodyStart: 1,
109
- lineStart: 1,
110
- lineEnd: 1,
111
- skip: false
112
- },
113
- error
114
- });
115
- const test: TestConfig = {
116
- methodName: 'import',
117
- classId,
118
- import: importLocation,
119
- declarationImport: importLocation,
120
- lineStart: 0,
121
- lineEnd: 0,
122
- lineBodyStart: 0,
123
- tags: [],
124
- description: 'Import Failure',
125
- skip: false,
126
- class: undefined!
127
- };
128
- return { result, test, suite };
129
70
  }
130
71
  }
@@ -1,7 +1,7 @@
1
1
  import type { TestConsumerShape } from '../types.ts';
2
2
  import type { TestEvent, TestRemoveEvent } from '../../model/event.ts';
3
3
  import type { TestConfig, TestDiffSource, TestResult } from '../../model/test.ts';
4
- import type { Counts, SuiteConfig, SuiteResult } from '../../model/suite.ts';
4
+ import type { SuiteConfig, SuiteResult } from '../../model/suite.ts';
5
5
  import { DelegatingConsumer } from './delegating.ts';
6
6
  import type { SuiteCore } from '../../model/common.ts';
7
7
  import { TestModelUtil } from '../../model/util.ts';
@@ -9,7 +9,7 @@ import { TestModelUtil } from '../../model/util.ts';
9
9
  type ClassId = string;
10
10
  type ImportName = string;
11
11
 
12
- type CumulativeTestResult = Pick<TestResult, 'sourceHash' | 'status' | 'duration'>;
12
+ type CumulativeTestResult = Pick<TestResult, 'sourceHash' | 'status' | 'duration' | 'selfDuration'>;
13
13
  type CumulativeSuiteResult = Pick<SuiteCore, 'import' | 'classId' | 'sourceHash'> & {
14
14
  tests: Record<string, CumulativeTestResult>;
15
15
  };
@@ -37,7 +37,7 @@ export class CumulativeSummaryConsumer extends DelegatingConsumer {
37
37
 
38
38
  onTestBefore(config: TestConfig): TestConfig {
39
39
  const suite = this.getSuite(config);
40
- suite.tests[config.methodName] = { sourceHash: config.sourceHash, status: 'unknown', duration: 0 };
40
+ suite.tests[config.methodName] = { sourceHash: config.sourceHash, status: 'unknown', duration: 0, selfDuration: 0 };
41
41
  return config;
42
42
  }
43
43
 
@@ -56,21 +56,9 @@ export class CumulativeSummaryConsumer extends DelegatingConsumer {
56
56
  onSuiteAfter(result: SuiteResult): SuiteResult {
57
57
  // Reset counts
58
58
  const suite = this.getSuite(result);
59
- const totals: Counts & { duration: number } = {
60
- passed: 0,
61
- failed: 0,
62
- skipped: 0,
63
- errored: 0,
64
- unknown: 0,
65
- total: 0,
66
- duration: 0
67
- };
68
- for (const test of Object.values(suite.tests)) {
69
- totals[test.status] += 1;
70
- totals.total += 1;
71
- totals.duration += test.duration ?? 0;
72
- }
73
- return { ...result, ...totals, status: TestModelUtil.countsToTestStatus(totals) };
59
+ const results = TestModelUtil.buildSummary();
60
+ TestModelUtil.countTestResult(results, Object.values(suite.tests));
61
+ return { ...result, ...results, status: TestModelUtil.computeTestStatus(results) };
74
62
  }
75
63
 
76
64
  removeTest(importName: string, classId?: string, methodName?: string): void {
@@ -20,6 +20,12 @@ export abstract class DelegatingConsumer implements TestConsumerShape {
20
20
  }
21
21
  }
22
22
 
23
+ async onTestRunState(state: TestRunState): Promise<void> {
24
+ for (const consumer of this.#consumers) {
25
+ await consumer.onTestRunState?.(state);
26
+ }
27
+ }
28
+
23
29
  onRemoveEvent(event: TestRemoveEvent): void {
24
30
  let result = event;
25
31
  if (this.transformRemove) {
@@ -22,6 +22,7 @@ export class RunnableTestConsumer extends DelegatingConsumer {
22
22
 
23
23
  async summarizeAsBoolean(): Promise<boolean> {
24
24
  await this.summarize(this.#results?.summary);
25
- return (this.#results?.summary.failed ?? 0) <= 0;
25
+ return (this.#results?.summary.failed ?? 0) <= 0 &&
26
+ (this.#results?.summary.errored ?? 0) <= 0;
26
27
  }
27
28
  }
@@ -1,6 +1,7 @@
1
1
  import type { SuiteResult } from '../../model/suite.ts';
2
2
  import type { TestEvent } from '../../model/event.ts';
3
3
  import type { SuitesSummary, TestConsumerShape } from '../types.ts';
4
+ import { TestModelUtil } from '../../model/util.ts';
4
5
 
5
6
  /**
6
7
  * Test Result Collector, combines all results into a single Suite Result
@@ -8,26 +9,13 @@ import type { SuitesSummary, TestConsumerShape } from '../types.ts';
8
9
  export class TestResultsSummarizer implements TestConsumerShape {
9
10
 
10
11
  summary: SuitesSummary = {
11
- passed: 0,
12
- failed: 0,
13
- errored: 0,
14
- skipped: 0,
15
- unknown: 0,
16
- total: 0,
17
- duration: 0,
18
- suites: [],
19
- errors: []
12
+ ...TestModelUtil.buildSummary(),
13
+ suites: []
20
14
  };
21
15
 
22
16
  #merge(result: SuiteResult): void {
17
+ TestModelUtil.countTestResult(this.summary, Object.values(result.tests));
23
18
  this.summary.suites.push(result);
24
- this.summary.failed += result.failed;
25
- this.summary.errored += result.errored;
26
- this.summary.passed += result.passed;
27
- this.summary.unknown += result.unknown;
28
- this.summary.skipped += result.skipped;
29
- this.summary.duration += result.duration;
30
- this.summary.total += result.total;
31
19
  }
32
20
 
33
21
  /**
@@ -2,7 +2,7 @@ import { Util, AsyncQueue } from '@travetto/runtime';
2
2
  import { StyleUtil, Terminal, TerminalUtil } from '@travetto/terminal';
3
3
 
4
4
  import type { TestEvent } from '../../model/event.ts';
5
- import type { TestResult, TestStatus } from '../../model/test.ts';
5
+ import type { TestResult } from '../../model/test.ts';
6
6
 
7
7
  import type { SuitesSummary, TestConsumerShape, TestRunState } from '../types.ts';
8
8
  import { TestConsumer } from '../decorator.ts';
@@ -10,6 +10,7 @@ import { TestConsumer } from '../decorator.ts';
10
10
  import { TapEmitter } from './tap.ts';
11
11
  import { CONSOLE_ENHANCER, type TestResultsEnhancer } from '../enhancer.ts';
12
12
  import type { SuiteResult } from '../../model/suite.ts';
13
+ import { TestModelUtil } from '../../model/util.ts';
13
14
 
14
15
  type Result = {
15
16
  key: string;
@@ -31,6 +32,7 @@ export class TapSummaryEmitter implements TestConsumerShape {
31
32
  #consumer: TapEmitter;
32
33
  #enhancer: TestResultsEnhancer;
33
34
  #options?: Record<string, unknown>;
35
+ #state: TestRunState = {};
34
36
 
35
37
  constructor(terminal: Terminal = new Terminal(process.stderr)) {
36
38
  this.#terminal = terminal;
@@ -87,20 +89,24 @@ export class TapSummaryEmitter implements TestConsumerShape {
87
89
  this.#consumer.setOptions(options);
88
90
  }
89
91
 
92
+ onTestRunState(state: TestRunState): void {
93
+ Object.assign(this.#state, state);
94
+ }
95
+
90
96
  async onStart(state: TestRunState): Promise<void> {
91
97
  this.#consumer.onStart();
92
- const total: Record<TestStatus | 'count', number> = { errored: 0, failed: 0, passed: 0, skipped: 0, unknown: 0, count: 0 };
98
+ this.onTestRunState(state);
99
+
100
+ const total = TestModelUtil.buildSummary();
93
101
  const success = StyleUtil.getStyle({ text: '#e5e5e5', background: '#026020' }); // White on dark green
94
102
  const fail = StyleUtil.getStyle({ text: '#e5e5e5', background: '#8b0000' }); // White on dark red
95
103
  this.#progress = this.#terminal.streamToBottom(
96
104
  Util.mapAsyncIterable(
97
105
  this.#results,
98
106
  (value) => {
99
- total[value.status] += 1;
100
- total.count += 1;
107
+ TestModelUtil.countTestResult(total, [value]);
101
108
  const statusLine = `${total.failed} failed, ${total.errored} errored, ${total.skipped} skipped`;
102
- return { value: `Tests %idx/%total [${statusLine}] -- ${value.classId}`, total: state.testCount, idx: total.passed };
103
-
109
+ return { value: `Tests %idx/%total [${statusLine}] -- ${value.classId}`, total: this.#state.testCount, idx: total.passed };
104
110
  },
105
111
  TerminalUtil.progressBarUpdater(this.#terminal, { style: () => ({ complete: (total.failed || total.errored) ? fail : success }) })
106
112
  ),
@@ -112,7 +118,7 @@ export class TapSummaryEmitter implements TestConsumerShape {
112
118
  if (event.type === 'test' && event.phase === 'after') {
113
119
  const { test } = event;
114
120
  this.#results.add(test);
115
- if (test.status === 'failed') {
121
+ if (test.status !== 'passed' && test.status !== 'skipped') {
116
122
  this.#consumer.onEvent(event);
117
123
  }
118
124
  const tests = this.#timings.getOrInsert('test', new Map<string, Result>());
@@ -2,12 +2,15 @@ import path from 'node:path';
2
2
  import { stringify } from 'yaml';
3
3
 
4
4
  import { Terminal, StyleUtil } from '@travetto/terminal';
5
- import { TimeUtil, RuntimeIndex, hasToJSON, JSONUtil } from '@travetto/runtime';
5
+ import { TimeUtil, RuntimeIndex } from '@travetto/runtime';
6
6
 
7
7
  import type { TestEvent } from '../../model/event.ts';
8
8
  import type { SuitesSummary, TestConsumerShape } from '../types.ts';
9
9
  import { TestConsumer } from '../decorator.ts';
10
10
  import { type TestResultsEnhancer, CONSOLE_ENHANCER } from '../enhancer.ts';
11
+ import { TestConsumerUtil } from './util.ts';
12
+
13
+ const SPACE = ' ';
11
14
 
12
15
  /**
13
16
  * TAP Format consumer
@@ -17,8 +20,8 @@ export class TapEmitter implements TestConsumerShape {
17
20
  #count = 0;
18
21
  #enhancer: TestResultsEnhancer;
19
22
  #terminal: Terminal;
20
- #start: number;
21
23
  #options?: Record<string, unknown>;
24
+ #start: number = 0;
22
25
 
23
26
  constructor(
24
27
  terminal = new Terminal(),
@@ -54,24 +57,6 @@ export class TapEmitter implements TestConsumerShape {
54
57
  this.log(`---\n${this.#enhancer.objectInspect(body)}\n...`);
55
58
  }
56
59
 
57
- /**
58
- * Error to string
59
- * @param error
60
- */
61
- errorToString(error?: Error): string | undefined {
62
- if (error && error.name !== 'AssertionError') {
63
- if (error instanceof Error) {
64
- let out = JSONUtil.toUTF8(hasToJSON(error) ? error.toJSON() : error, { indent: 2 });
65
- if (this.#options?.verbose && error.stack) {
66
- out = `${out}\n${error.stack}`;
67
- }
68
- return out;
69
- } else {
70
- return `${error}`;
71
- }
72
- }
73
- }
74
-
75
60
  /**
76
61
  * Listen for each event
77
62
  */
@@ -125,9 +110,10 @@ export class TapEmitter implements TestConsumerShape {
125
110
  // Track test result
126
111
  let status = `${this.#enhancer.testNumber(++this.#count)} `;
127
112
  switch (test.status) {
113
+ case 'passed': `${this.#enhancer.success('ok')} ${status}`; break;
128
114
  case 'skipped': status += ' # SKIP'; break;
129
- case 'failed': status = `${this.#enhancer.failure('not ok')} ${status}`; break;
130
- default: status = `${this.#enhancer.success('ok')} ${status}`;
115
+ case 'unknown': break;
116
+ default: status = `${this.#enhancer.failure('not ok')} ${status}`; break;
131
117
  }
132
118
  status += header;
133
119
 
@@ -138,9 +124,9 @@ export class TapEmitter implements TestConsumerShape {
138
124
  case 'errored':
139
125
  case 'failed': {
140
126
  if (test.error) {
141
- const msg = this.errorToString(test.error);
142
- if (msg) {
143
- this.logMeta({ error: msg });
127
+ const message = TestConsumerUtil.errorToString(test.error, !!this.#options?.verbose);
128
+ if (message) {
129
+ this.logMeta({ error: message });
144
130
  }
145
131
  }
146
132
  break;
@@ -168,28 +154,22 @@ export class TapEmitter implements TestConsumerShape {
168
154
  onSummary(summary: SuitesSummary): void {
169
155
  this.log(`${this.#enhancer.testNumber(1)}..${this.#enhancer.testNumber(summary.total)}`);
170
156
 
171
- if (summary.errors.length) {
172
- this.log('---\n');
173
- for (const error of summary.errors) {
174
- const msg = this.errorToString(error);
175
- if (msg) {
176
- this.log(this.#enhancer.failure(msg));
177
- }
178
- }
179
- }
180
-
181
157
  const allPassed = !summary.failed && !summary.errored;
182
158
 
183
159
  this.log([
184
- this.#enhancer[allPassed ? 'success' : 'failure']('Results'),
185
- `${this.#enhancer.total(summary.passed)}/${this.#enhancer.total(summary.total)},`,
186
- allPassed ? 'failed' : this.#enhancer.failure('failed'),
187
- `${this.#enhancer.total(summary.failed)}`,
188
- allPassed ? 'errored' : this.#enhancer.failure('errored'),
189
- `${this.#enhancer.total(summary.errored)}`,
190
- 'skipped',
191
- this.#enhancer.total(summary.skipped),
192
- `# (Total Test Time: ${TimeUtil.asClock(summary.duration)}, Total Run Time: ${TimeUtil.asClock(Date.now() - this.#start)})`
193
- ].join(' '));
160
+ this.#enhancer[allPassed ? 'success' : 'failure']('Results'), SPACE,
161
+ `${this.#enhancer.total(summary.passed)}/${this.#enhancer.total(summary.total)},`, SPACE,
162
+ allPassed ? 'failed' : this.#enhancer.failure('failed'), SPACE,
163
+ `${this.#enhancer.total(summary.failed)}`, SPACE,
164
+ allPassed ? 'errored' : this.#enhancer.failure('errored'), SPACE,
165
+ `${this.#enhancer.total(summary.errored)}`, SPACE,
166
+ 'skipped', SPACE,
167
+ this.#enhancer.total(summary.skipped), SPACE,
168
+ '#', SPACE, '(Timings:', SPACE,
169
+ 'Self=', TimeUtil.asClock(summary.selfDuration), ',', SPACE,
170
+ 'Total=', TimeUtil.asClock(summary.duration), ',', SPACE,
171
+ 'Clock=', TimeUtil.asClock(Date.now() - this.#start),
172
+ ')',
173
+ ].join(''));
194
174
  }
195
175
  }
@@ -0,0 +1,28 @@
1
+ import util from 'node:util';
2
+ import { AssertionError } from 'node:assert';
3
+
4
+ import { TypedObject } from '@travetto/runtime';
5
+
6
+ export class TestConsumerUtil {
7
+ /**
8
+ * Convert error to string
9
+ */
10
+ static errorToString(error?: Error, verbose?: boolean): string | undefined {
11
+ if (error instanceof AssertionError) {
12
+ return;
13
+ } else if (error instanceof Error) {
14
+ const stack = error.stack ?
15
+ error.stack.split(/\n/).slice(0, verbose ? -1 : 5).join('\n') :
16
+ error.message;
17
+ const subObject: Record<string, unknown> = {};
18
+ for (const key of TypedObject.keys(error)) {
19
+ if (key !== 'stack' && key !== 'message' && key !== 'name') {
20
+ subObject[key] = error[key];
21
+ }
22
+ }
23
+ return `${stack}${Object.keys(subObject).length ? `\n${util.inspect(subObject)}` : ''}`;
24
+ } else {
25
+ return `${error}`;
26
+ }
27
+ }
28
+ }
@@ -7,6 +7,7 @@ import { RuntimeIndex } from '@travetto/runtime';
7
7
  import type { TestEvent } from '../../model/event.ts';
8
8
  import type { SuitesSummary, TestConsumerShape } from '../types.ts';
9
9
  import { TestConsumer } from '../decorator.ts';
10
+ import { TestConsumerUtil } from './util.ts';
10
11
 
11
12
  /**
12
13
  * Xunit consumer, compatible with JUnit formatters
@@ -59,9 +60,9 @@ export class XunitEmitter implements TestConsumerShape {
59
60
  let body = '';
60
61
 
61
62
  if (test.error) {
62
- const assertion = test.assertions.find(item => !!item.error)!;
63
+ const errorMessage = TestConsumerUtil.errorToString(test.error);
63
64
  const node = test.status === 'failed' ? 'failure' : 'error';
64
- body = `<${node} type="${assertion.text}" message="${encodeURIComponent(assertion.message!)}"><![CDATA[${assertion.error!.stack}]]></${node}>`;
65
+ body = `<${node} type="${test.error.constructor.name}" message="${encodeURIComponent(test.error.message)}"><![CDATA[${errorMessage}]]></${node}>`;
65
66
  }
66
67
 
67
68
  const groupedByLevel: Record<string, string[]> = {};
@@ -1,24 +1,16 @@
1
1
  import type { Class } from '@travetto/runtime';
2
2
 
3
3
  import type { TestEvent, TestRemoveEvent } from '../model/event.ts';
4
- import type { Counts, SuiteResult } from '../model/suite.ts';
4
+ import type { ResultsSummary, SuiteResult } from '../model/suite.ts';
5
5
 
6
6
  /**
7
7
  * All suite results
8
8
  */
9
- export interface SuitesSummary extends Counts {
9
+ export interface SuitesSummary extends ResultsSummary {
10
10
  /**
11
11
  * List of all suites
12
12
  */
13
13
  suites: SuiteResult[];
14
- /**
15
- * List of all errors
16
- */
17
- errors: Error[];
18
- /**
19
- * Total duration
20
- */
21
- duration: number;
22
14
  }
23
15
 
24
16
  export type TestRunState = {
@@ -44,6 +36,10 @@ export interface TestConsumerShape {
44
36
  * Set options
45
37
  */
46
38
  setOptions?(options?: Record<string, unknown>): Promise<void> | void;
39
+ /**
40
+ * Can directly update the known test run state as needed
41
+ */
42
+ onTestRunState?(state: TestRunState): Promise<void> | void;
47
43
  /**
48
44
  * Listen for start of the test run
49
45
  */
@@ -26,38 +26,18 @@ export class TestExecutor {
26
26
  this.#consumer = consumer;
27
27
  }
28
28
 
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
29
  /**
48
30
  * Raw execution, runs the method and then returns any thrown errors as the result.
49
31
  *
50
32
  * This method should never throw under any circumstances.
51
33
  */
52
- async #executeTestMethod(test: TestConfig): Promise<Error | undefined> {
53
- const suite = SuiteRegistryIndex.getConfig(test.class);
54
-
34
+ async #executeTestMethod(instance: unknown, test: TestConfig): Promise<Error | undefined> {
55
35
  // Ensure all the criteria below are satisfied before moving forward
56
36
  return Barrier.awaitOperation(test.timeout || TEST_TIMEOUT, async () => {
57
37
  const env = process.env;
58
38
  process.env = { ...env }; // Created an isolated environment
59
39
  try {
60
- await castTo<Record<string, Function>>(suite.instance)[test.methodName]();
40
+ await castTo<Record<string, Function>>(instance)[test.methodName]();
61
41
  } finally {
62
42
  process.env = env; // Restore
63
43
  }
@@ -73,96 +53,43 @@ export class TestExecutor {
73
53
  }
74
54
  }
75
55
 
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
56
  /**
116
57
  * Execute the test, capture output, assertions and promises
117
58
  */
118
- async executeTest(test: TestConfig, suite: SuiteConfig): Promise<TestResult> {
59
+ async executeTest(instance: unknown, test: TestConfig, suite: SuiteConfig, override?: Partial<TestResult>): Promise<TestResult> {
60
+
61
+ const result = TestModelUtil.createTestResult(suite, test, override);
119
62
 
120
63
  // Mark test start
121
64
  this.#consumer.onEvent({ type: 'test', phase: 'before', test });
122
65
 
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
66
 
144
67
  // 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
- })
151
- );
68
+ const getAssertions = AssertCapture.collector(test, item =>
69
+ this.#consumer.onEvent({ type: 'assertion', phase: 'after', assertion: item }));
152
70
 
153
71
  const consoleCapture = new ConsoleCapture().start(); // Capture all output from transpiled code
154
72
 
155
- // Run method and get result
156
- const error = await this.#executeTestMethod(test);
157
- const [status, finalError] = AssertCheck.validateTestResultError(test, error);
73
+ // Already finished
74
+ if (result.status !== 'unknown') {
75
+ if (result.error) {
76
+ result.assertions.push(AssertUtil.generateAssertion({ suite, test, error: result.error }));
77
+ }
78
+ for (const item of result.assertions ?? []) { AssertCapture.add(item); }
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 });
@@ -175,18 +102,21 @@ export class TestExecutor {
175
102
  */
176
103
  async executeSuite(suite: SuiteConfig, tests: TestConfig[]): Promise<void> {
177
104
 
178
- suite.instance = classConstruct(suite.class);
105
+ const instance = classConstruct(suite.class);
179
106
 
180
- const shouldSkip = await this.#shouldSkip(suite, suite.instance);
107
+ const shouldSkip = await this.#shouldSkip(suite, instance);
108
+
109
+ const result: SuiteResult = TestModelUtil.createSuiteResult(suite);
181
110
 
182
111
  if (shouldSkip) {
183
112
  this.#consumer.onEvent({
184
113
  phase: 'after', type: 'suite',
185
- suite: this.createSuiteResult(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,80 @@ export class TestExecutor {
194
124
  return;
195
125
  }
196
126
 
197
- const result: SuiteResult = this.createSuiteResult(suite);
127
+ const manager = new TestPhaseManager(suite, instance);
128
+ const originalEnv = { ...process.env };
129
+ const startTime = Date.now();
130
+ const testResultOverrides: Record<string, Partial<TestResult>> = {};
131
+
198
132
  const validTestMethodNames = new Set(tests.map(t => t.methodName));
199
133
  const testConfigs = Object.fromEntries(
200
134
  Object.entries(suite.tests).filter(([key]) => validTestMethodNames.has(key))
201
135
  );
202
136
 
203
- const startTime = Date.now();
204
-
205
137
  // Mark suite start
206
138
  this.#consumer.onEvent({ phase: 'before', type: 'suite', suite: { ...suite, tests: testConfigs } });
207
139
 
208
- const manager = new TestPhaseManager(suite);
209
-
210
- const originalEnv = { ...process.env };
211
-
212
140
  try {
213
141
  // Handle the BeforeAll calls
214
142
  await manager.startPhase('all');
143
+ } catch (someError) {
144
+ const suiteError = await manager.onError('all', someError);
145
+ for (const method of validTestMethodNames) {
146
+ testResultOverrides[method] ??= { status: 'errored', error: suiteError };
147
+ }
148
+ }
215
149
 
216
- const suiteEnv = { ...process.env };
150
+ const suiteEnv = { ...process.env };
217
151
 
218
- for (const test of tests ?? suite.tests) {
219
- if (await this.#shouldSkip(test, suite.instance)) {
220
- this.#skipTest(test, result);
221
- continue;
222
- }
152
+ for (const test of tests) {
153
+ // Reset env before each test
154
+ process.env = { ...suiteEnv };
223
155
 
224
- // Reset env before each test
225
- process.env = { ...suiteEnv };
156
+ const testStart = Date.now();
157
+ const testResultOverride = (testResultOverrides[test.methodName] ??= {});
226
158
 
227
- const testStart = Date.now();
228
- try {
159
+ if (await this.#shouldSkip(test, instance)) {
160
+ testResultOverride.status = 'skipped';
161
+ }
229
162
 
230
- // Handle BeforeEach
231
- await manager.startPhase('each');
163
+ try {
164
+ // Handle BeforeEach
165
+ testResultOverride.status || await manager.startPhase('each');
166
+ } catch (someError) {
167
+ const testError = await manager.onError('each', someError);
168
+ testResultOverride.error = testError;
169
+ testResultOverride.status = 'errored';
170
+ }
232
171
 
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;
172
+ // Run test
173
+ const testResult = await this.executeTest(instance, test, suite, testResultOverride);
238
174
 
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);
245
- }
175
+ // Handle after each
176
+ try {
177
+ testResultOverride.status || await manager.endPhase('each');
178
+ } catch (testError) {
179
+ if (!(testError instanceof Error)) { throw testError; };
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)) { throw suiteError; };
193
+ console.error('Failed to properly shutdown test', suiteError.message);
253
194
  }
254
195
 
255
196
  // Restore env
256
197
  process.env = { ...originalEnv };
257
198
 
258
199
  result.duration = Date.now() - startTime;
259
- result.status = TestModelUtil.countsToTestStatus(result);
200
+ result.status = TestModelUtil.computeTestStatus(result);
260
201
 
261
202
  // Mark suite complete
262
203
  this.#consumer.onEvent({ phase: 'after', type: 'suite', suite: result });
@@ -265,19 +206,13 @@ export class TestExecutor {
265
206
  /**
266
207
  * Handle executing a suite's test/tests based on command line inputs
267
208
  */
268
- async execute(run: TestRun): Promise<void> {
209
+ async execute(run: TestRun, singleFile?: boolean): Promise<void> {
269
210
  try {
270
211
  await Runtime.importFrom(run.import);
271
212
  } catch (error) {
272
- if (!(error instanceof Error)) {
273
- throw error;
274
- }
213
+ if (!(error instanceof Error)) { throw error; }
214
+ const suite = TestModelUtil.createImportErrorSuiteResult(run);
275
215
  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
216
  this.#consumer.onEvent({ type: 'suite', phase: 'after', suite });
282
217
  return;
283
218
  }
@@ -291,6 +226,11 @@ export class TestExecutor {
291
226
  console.warn('Unable to find suites for ', run);
292
227
  }
293
228
 
229
+ if (singleFile) {
230
+ const testCount = suites.reduce((acc, suite) => acc + suite.tests.length, 0);
231
+ this.#consumer.onTestRunState?.({ testCount });
232
+ }
233
+
294
234
  for (const { suite, tests } of suites) {
295
235
  await this.executeSuite(suite, tests);
296
236
  }
@@ -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
  }
@@ -3,7 +3,7 @@ import fs from 'node:fs/promises';
3
3
  import readline from 'node:readline/promises';
4
4
  import path from 'node:path';
5
5
 
6
- import { Env, ExecUtil, Util, RuntimeIndex, Runtime, TimeUtil, JSONUtil } from '@travetto/runtime';
6
+ import { Env, ExecUtil, Util, RuntimeIndex, Runtime, TimeUtil, JSONUtil, describeFunction } from '@travetto/runtime';
7
7
  import { WorkPool } from '@travetto/worker';
8
8
  import { Registry } from '@travetto/registry';
9
9
 
@@ -126,6 +126,7 @@ export class RunUtil {
126
126
  const imported = await Registry.manualInit([importPath]);
127
127
  const classes = Object.fromEntries(
128
128
  imported
129
+ .filter(cls => !describeFunction(cls).abstract)
129
130
  .filter(cls => SuiteRegistryIndex.hasConfig(cls))
130
131
  .map(cls => [cls.Ⲑid, SuiteRegistryIndex.getConfig(cls)])
131
132
  );
@@ -222,7 +223,7 @@ export class RunUtil {
222
223
  }
223
224
 
224
225
  if (runs.length === 1) {
225
- await new TestExecutor(consumer).execute(runs[0]);
226
+ await new TestExecutor(consumer).execute(runs[0], true);
226
227
  } else {
227
228
  await WorkPool.run(
228
229
  run => buildStandardTestManager(consumer, run),
@@ -19,10 +19,6 @@ export interface SuiteConfig extends SuiteCore {
19
19
  * Should this be skipped
20
20
  */
21
21
  skip: Skip;
22
- /**
23
- * Actual class instance
24
- */
25
- instance?: unknown;
26
22
  /**
27
23
  * Tests to run
28
24
  */
@@ -34,29 +30,35 @@ export interface SuiteConfig extends SuiteCore {
34
30
  }
35
31
 
36
32
  /**
37
- * All counts for the suite summary
33
+ * Test Counts
38
34
  */
39
- export interface Counts {
35
+ export interface ResultsSummary {
36
+ /** Passing Test Count */
40
37
  passed: number;
38
+ /** Skipped Test Count */
41
39
  skipped: number;
40
+ /** Failed Test Count */
42
41
  failed: number;
42
+ /** Errored Test Count */
43
43
  errored: number;
44
+ /** Unknown Test Count */
44
45
  unknown: number;
46
+ /** Total Test Count */
45
47
  total: number;
48
+ /** Test Self Execution Duration */
49
+ selfDuration: number;
50
+ /** Total Duration */
51
+ duration: number;
46
52
  }
47
53
 
48
54
  /**
49
55
  * Results of a suite run
50
56
  */
51
- export interface SuiteResult extends Counts, SuiteCore {
57
+ export interface SuiteResult extends ResultsSummary, SuiteCore {
52
58
  /**
53
59
  * All test results
54
60
  */
55
61
  tests: Record<string, TestResult>;
56
- /**
57
- * Suite duration
58
- */
59
- duration: number;
60
62
  /**
61
63
  * Overall status
62
64
  */
package/src/model/test.ts CHANGED
@@ -105,13 +105,13 @@ export interface TestResult extends TestCore {
105
105
  */
106
106
  assertions: Assertion[];
107
107
  /**
108
- * Duration for the test
108
+ * Self Execution Duration
109
109
  */
110
- duration: number;
110
+ selfDuration: number;
111
111
  /**
112
112
  * Total duration including before/after
113
113
  */
114
- durationTotal: number;
114
+ duration: number;
115
115
  /**
116
116
  * Logging output
117
117
  */
package/src/model/util.ts CHANGED
@@ -1,14 +1,97 @@
1
- import type { Counts } from './suite.ts';
2
- import type { TestStatus } from './test.ts';
1
+ import path from 'node:path';
2
+
3
+ import { asFull, RuntimeIndex } from '@travetto/runtime';
4
+
5
+ import type { ResultsSummary, SuiteConfig, SuiteResult } from './suite.ts';
6
+ import type { TestConfig, TestResult, TestRun, TestStatus } from './test.ts';
3
7
 
4
8
  export class TestModelUtil {
5
- static countsToTestStatus(counts: Counts): TestStatus {
9
+ static computeTestStatus(summary: ResultsSummary): TestStatus {
6
10
  switch (true) {
7
- case counts.errored > 0: return 'errored';
8
- case counts.failed > 0: return 'failed';
9
- case counts.skipped > 0: return 'skipped';
10
- case counts.unknown > 0: return 'unknown';
11
+ case summary.errored > 0: return 'errored';
12
+ case summary.failed > 0: return 'failed';
13
+ case summary.skipped > 0: return 'skipped';
14
+ case summary.unknown > 0: return 'unknown';
11
15
  default: return 'passed';
12
16
  }
13
17
  }
18
+
19
+ static buildSummary(): ResultsSummary {
20
+ return { passed: 0, failed: 0, skipped: 0, errored: 0, unknown: 0, total: 0, duration: 0, selfDuration: 0 };
21
+ }
22
+
23
+ static countTestResult<T extends ResultsSummary>(summary: T, tests: Pick<TestResult, 'status' | 'selfDuration' | 'duration'>[]): T {
24
+ for (const test of tests) {
25
+ summary[test.status] += 1;
26
+ summary.total += 1;
27
+ summary.selfDuration += (test.selfDuration ?? 0);
28
+ summary.duration += (test.duration ?? 0);
29
+ }
30
+ return summary;
31
+ }
32
+
33
+
34
+ /**
35
+ * An empty suite result based on a suite config
36
+ */
37
+ static createSuiteResult(suite: SuiteConfig, override?: Partial<SuiteResult>): SuiteResult {
38
+ return {
39
+ ...TestModelUtil.buildSummary(),
40
+ status: 'unknown',
41
+ lineStart: suite.lineStart,
42
+ lineEnd: suite.lineEnd,
43
+ import: suite.import,
44
+ classId: suite.classId,
45
+ sourceHash: suite.sourceHash,
46
+ tests: {},
47
+ duration: 0,
48
+ selfDuration: 0,
49
+ ...override
50
+ };
51
+ }
52
+
53
+ /**
54
+ * An empty test result based on a suite and test config
55
+ */
56
+ static createTestResult(suite: SuiteConfig, test: TestConfig, override?: Partial<TestResult>): TestResult {
57
+ return {
58
+ methodName: test.methodName,
59
+ description: test.description,
60
+ classId: test.classId,
61
+ tags: test.tags,
62
+ suiteLineStart: suite.lineStart,
63
+ lineStart: test.lineStart,
64
+ lineEnd: test.lineEnd,
65
+ lineBodyStart: test.lineBodyStart,
66
+ import: test.import,
67
+ declarationImport: test.declarationImport,
68
+ sourceHash: test.sourceHash,
69
+ status: 'unknown',
70
+ assertions: [],
71
+ duration: 0,
72
+ selfDuration: 0,
73
+ output: [],
74
+ ...override
75
+ };
76
+ }
77
+
78
+ static createImportErrorSuiteResult(run: TestRun): SuiteResult {
79
+ const name = path.basename(run.import);
80
+ const classId = `${RuntimeIndex.getFromImport(run.import)?.id}#${name}`;
81
+ const common = { classId, duration: 0, lineStart: 1, lineEnd: 1, import: run.import } as const;
82
+ return asFull<SuiteResult>({
83
+ ...common,
84
+ status: 'errored', errored: 1,
85
+ tests: {
86
+ impport: asFull<TestResult>({
87
+ ...common,
88
+ status: 'errored',
89
+ assertions: [{
90
+ ...common, line: common.lineStart,
91
+ methodName: 'import', operator: 'import', text: `Failed to import ${run.import}`,
92
+ }]
93
+ })
94
+ }
95
+ });
96
+ }
14
97
  }
@@ -20,9 +20,7 @@ export class TestChildWorker extends IpcChannel<TestRun> {
20
20
  await operation();
21
21
  this.send(type); // Respond
22
22
  } catch (error) {
23
- if (!(error instanceof Error)) {
24
- throw error;
25
- }
23
+ if (!(error instanceof Error)) { throw error; }
26
24
  // Mark as errored out
27
25
  this.send(type, JSONUtil.cloneForTransmit(error));
28
26
  }