@travetto/test 8.0.0-alpha.20 → 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.
- package/README.md +14 -20
- package/__index__.ts +9 -9
- package/package.json +7 -7
- package/src/assert/capture.ts +1 -2
- package/src/assert/check.ts +52 -25
- package/src/assert/types.ts +1 -1
- package/src/assert/util.ts +16 -12
- package/src/consumer/decorator.ts +2 -2
- package/src/consumer/enhancer.ts +3 -3
- package/src/consumer/registry-adapter.ts +3 -3
- package/src/consumer/registry-index.ts +6 -5
- package/src/consumer/types/cumulative.ts +6 -6
- package/src/consumer/types/delegating.ts +2 -2
- package/src/consumer/types/event.ts +2 -2
- package/src/consumer/types/exec.ts +3 -4
- package/src/consumer/types/json.ts +3 -4
- package/src/consumer/types/noop.ts +3 -3
- package/src/consumer/types/runnable.ts +4 -6
- package/src/consumer/types/summarizer.ts +2 -3
- package/src/consumer/types/tap-summary.ts +15 -20
- package/src/consumer/types/tap.ts +57 -29
- package/src/consumer/types/util.ts +8 -5
- package/src/consumer/types/xunit.ts +7 -6
- package/src/decorator/suite.ts +3 -3
- package/src/decorator/test.ts +6 -4
- package/src/execute/barrier.ts +15 -9
- package/src/execute/console.ts +3 -4
- package/src/execute/executor.ts +29 -25
- package/src/execute/phase.ts +1 -1
- package/src/execute/run.ts +39 -39
- package/src/execute/watcher.ts +12 -19
- package/src/fixture.ts +6 -7
- package/src/model/common.ts +1 -1
- package/src/model/error.ts +2 -2
- package/src/model/event.ts +14 -9
- package/src/model/suite.ts +2 -2
- package/src/model/test.ts +8 -5
- package/src/model/util.ts +24 -14
- package/src/registry/registry-adapter.ts +10 -13
- package/src/registry/registry-index.ts +13 -11
- package/src/worker/child.ts +13 -10
- package/src/worker/standard.ts +11 -14
- package/src/worker/types.ts +3 -7
- package/support/bin/run.ts +1 -1
- package/support/cli.test.ts +16 -14
- package/support/cli.test_child.ts +2 -2
- package/support/cli.test_diff.ts +5 -6
- package/support/cli.test_digest.ts +3 -4
- package/support/cli.test_direct.ts +5 -6
- package/support/cli.test_watch.ts +2 -3
- package/support/transformer.assert.ts +40 -32
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import type { SuiteResult } from '../../model/suite.ts';
|
|
2
1
|
import type { TestEvent } from '../../model/event.ts';
|
|
3
|
-
import type {
|
|
2
|
+
import type { SuiteResult } from '../../model/suite.ts';
|
|
4
3
|
import { TestModelUtil } from '../../model/util.ts';
|
|
4
|
+
import type { SuitesSummary, TestConsumerShape } from '../types.ts';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Test Result Collector, combines all results into a single Suite Result
|
|
8
8
|
*/
|
|
9
9
|
export class TestResultsSummarizer implements TestConsumerShape {
|
|
10
|
-
|
|
11
10
|
summary: SuitesSummary = {
|
|
12
11
|
...TestModelUtil.buildSummary(),
|
|
13
12
|
suites: []
|
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AsyncQueue, Util } from '@travetto/runtime';
|
|
2
2
|
import { Terminal, TerminalUtil } from '@travetto/terminal';
|
|
3
3
|
|
|
4
4
|
import type { TestEvent } from '../../model/event.ts';
|
|
5
|
+
import type { SuiteResult } from '../../model/suite.ts';
|
|
5
6
|
import type { TestResult } from '../../model/test.ts';
|
|
6
|
-
|
|
7
|
-
import type { SuitesSummary, TestConsumerShape, TestRunState } from '../types.ts';
|
|
7
|
+
import { TestModelUtil } from '../../model/util.ts';
|
|
8
8
|
import { TestConsumer } from '../decorator.ts';
|
|
9
|
-
|
|
10
|
-
import { TapEmitter } from './tap.ts';
|
|
11
9
|
import { CONSOLE_ENHANCER, type TestResultsEnhancer } from '../enhancer.ts';
|
|
12
|
-
import type {
|
|
13
|
-
import {
|
|
10
|
+
import type { SuitesSummary, TestConsumerShape, TestRunState } from '../types.ts';
|
|
11
|
+
import { TapEmitter } from './tap.ts';
|
|
14
12
|
|
|
15
13
|
type Result = {
|
|
16
14
|
key: string;
|
|
@@ -23,7 +21,6 @@ type Result = {
|
|
|
23
21
|
*/
|
|
24
22
|
@TestConsumer()
|
|
25
23
|
export class TapSummaryEmitter implements TestConsumerShape {
|
|
26
|
-
|
|
27
24
|
#timings = new Map<'test' | 'module' | 'file' | 'suite', Map<string, Result>>();
|
|
28
25
|
|
|
29
26
|
#terminal: Terminal;
|
|
@@ -53,20 +50,16 @@ export class TapSummaryEmitter implements TestConsumerShape {
|
|
|
53
50
|
foundModule.duration += suite.duration;
|
|
54
51
|
foundModule.tests += testCount;
|
|
55
52
|
|
|
56
|
-
const foundFile = this.#timings
|
|
57
|
-
.getOrInsert('file', new Map<string, Result>())
|
|
58
|
-
.getOrInsert(file, { key: file, duration: 0, tests: 0 });
|
|
53
|
+
const foundFile = this.#timings.getOrInsert('file', new Map<string, Result>()).getOrInsert(file, { key: file, duration: 0, tests: 0 });
|
|
59
54
|
|
|
60
55
|
foundFile.duration += suite.duration;
|
|
61
56
|
foundFile.tests += testCount;
|
|
62
57
|
|
|
63
|
-
this.#timings
|
|
64
|
-
.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
tests: testCount
|
|
69
|
-
});
|
|
58
|
+
this.#timings.getOrInsert('suite', new Map<string, Result>()).set(suite.classId, {
|
|
59
|
+
key: suite.classId,
|
|
60
|
+
duration: suite.duration,
|
|
61
|
+
tests: testCount
|
|
62
|
+
});
|
|
70
63
|
}
|
|
71
64
|
|
|
72
65
|
#renderTimings(): void {
|
|
@@ -77,7 +70,9 @@ export class TapSummaryEmitter implements TestConsumerShape {
|
|
|
77
70
|
const top10 = [...results.values()].toSorted((a, b) => b.duration - a.duration).slice(0, count);
|
|
78
71
|
|
|
79
72
|
for (const result of top10) {
|
|
80
|
-
console.log(
|
|
73
|
+
console.log(
|
|
74
|
+
` * ${this.#enhancer.testName(result.key)} - ${this.#enhancer.total(result.duration)}ms / ${this.#enhancer.total(result.tests)} tests`
|
|
75
|
+
);
|
|
81
76
|
}
|
|
82
77
|
this.#consumer.log('');
|
|
83
78
|
}
|
|
@@ -101,7 +96,7 @@ export class TapSummaryEmitter implements TestConsumerShape {
|
|
|
101
96
|
this.#progress = this.#terminal.streamToBottom(
|
|
102
97
|
Util.mapAsyncIterable(
|
|
103
98
|
this.#results,
|
|
104
|
-
|
|
99
|
+
value => {
|
|
105
100
|
TestModelUtil.countTestResult(total, [value]);
|
|
106
101
|
const statusLine = `${total.failed} failed, ${total.errored} errored, ${total.skipped} skipped`;
|
|
107
102
|
return {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
|
|
2
3
|
import { stringify } from 'yaml';
|
|
3
4
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
5
|
+
import { RuntimeIndex, TimeUtil } from '@travetto/runtime';
|
|
6
|
+
import { StyleUtil, Terminal } from '@travetto/terminal';
|
|
6
7
|
|
|
7
8
|
import type { TestEvent } from '../../model/event.ts';
|
|
8
|
-
import type { SuitesSummary, TestConsumerShape } from '../types.ts';
|
|
9
9
|
import { TestConsumer } from '../decorator.ts';
|
|
10
|
-
import { type TestResultsEnhancer
|
|
10
|
+
import { CONSOLE_ENHANCER, type TestResultsEnhancer } from '../enhancer.ts';
|
|
11
|
+
import type { SuitesSummary, TestConsumerShape } from '../types.ts';
|
|
11
12
|
import { TestConsumerUtil } from './util.ts';
|
|
12
13
|
|
|
13
14
|
const SPACE = ' ';
|
|
@@ -23,10 +24,7 @@ export class TapEmitter implements TestConsumerShape {
|
|
|
23
24
|
#options?: Record<string, unknown>;
|
|
24
25
|
#start: number = 0;
|
|
25
26
|
|
|
26
|
-
constructor(
|
|
27
|
-
terminal = new Terminal(),
|
|
28
|
-
enhancer: TestResultsEnhancer = CONSOLE_ENHANCER
|
|
29
|
-
) {
|
|
27
|
+
constructor(terminal = new Terminal(), enhancer: TestResultsEnhancer = CONSOLE_ENHANCER) {
|
|
30
28
|
this.#terminal = terminal;
|
|
31
29
|
this.#enhancer = enhancer;
|
|
32
30
|
}
|
|
@@ -53,7 +51,10 @@ export class TapEmitter implements TestConsumerShape {
|
|
|
53
51
|
logMeta(metadata: Record<string, unknown>): void {
|
|
54
52
|
const lineLength = this.#terminal.width - 5;
|
|
55
53
|
let body = stringify(metadata, { lineWidth: lineLength, indent: 2 });
|
|
56
|
-
body = body
|
|
54
|
+
body = body
|
|
55
|
+
.split('\n')
|
|
56
|
+
.map(line => ` ${line}`)
|
|
57
|
+
.join('\n');
|
|
57
58
|
this.log(`---\n${this.#enhancer.objectInspect(body)}\n...`);
|
|
58
59
|
}
|
|
59
60
|
|
|
@@ -71,7 +72,7 @@ export class TapEmitter implements TestConsumerShape {
|
|
|
71
72
|
StyleUtil.link(suiteId, `file://${suiteSourceFile}#${test.suiteLineStart ?? 1}`),
|
|
72
73
|
' - ',
|
|
73
74
|
StyleUtil.link(this.#enhancer.testName(test.methodName), `file://${testSourceFile}#${test.lineStart}`),
|
|
74
|
-
...test.description ? [`: ${this.#enhancer.testDescription(test.description)}`] : []
|
|
75
|
+
...(test.description ? [`: ${this.#enhancer.testDescription(test.description)}`] : [])
|
|
75
76
|
].join('');
|
|
76
77
|
|
|
77
78
|
this.log(`# ${header}`);
|
|
@@ -110,10 +111,17 @@ export class TapEmitter implements TestConsumerShape {
|
|
|
110
111
|
// Track test result
|
|
111
112
|
let status = `${this.#enhancer.testNumber(++this.#count)} `;
|
|
112
113
|
switch (test.status) {
|
|
113
|
-
case 'passed':
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
case 'passed':
|
|
115
|
+
`${this.#enhancer.success('ok')} ${status}`;
|
|
116
|
+
break;
|
|
117
|
+
case 'skipped':
|
|
118
|
+
status += ' # SKIP';
|
|
119
|
+
break;
|
|
120
|
+
case 'unknown':
|
|
121
|
+
break;
|
|
122
|
+
default:
|
|
123
|
+
status = `${this.#enhancer.failure('not ok')} ${status}`;
|
|
124
|
+
break;
|
|
117
125
|
}
|
|
118
126
|
status += header;
|
|
119
127
|
|
|
@@ -156,20 +164,40 @@ export class TapEmitter implements TestConsumerShape {
|
|
|
156
164
|
|
|
157
165
|
const allPassed = !summary.failed && !summary.errored;
|
|
158
166
|
|
|
159
|
-
this.log(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
167
|
+
this.log(
|
|
168
|
+
[
|
|
169
|
+
this.#enhancer[allPassed ? 'success' : 'failure']('Results'),
|
|
170
|
+
SPACE,
|
|
171
|
+
`${this.#enhancer.total(summary.passed)}/${this.#enhancer.total(summary.total)},`,
|
|
172
|
+
SPACE,
|
|
173
|
+
allPassed ? 'failed' : this.#enhancer.failure('failed'),
|
|
174
|
+
SPACE,
|
|
175
|
+
`${this.#enhancer.total(summary.failed)}`,
|
|
176
|
+
SPACE,
|
|
177
|
+
allPassed ? 'errored' : this.#enhancer.failure('errored'),
|
|
178
|
+
SPACE,
|
|
179
|
+
`${this.#enhancer.total(summary.errored)}`,
|
|
180
|
+
SPACE,
|
|
181
|
+
'skipped',
|
|
182
|
+
SPACE,
|
|
183
|
+
this.#enhancer.total(summary.skipped),
|
|
184
|
+
SPACE,
|
|
185
|
+
'#',
|
|
186
|
+
SPACE,
|
|
187
|
+
'(Timings:',
|
|
188
|
+
SPACE,
|
|
189
|
+
'Self=',
|
|
190
|
+
TimeUtil.asClock(summary.selfDuration),
|
|
191
|
+
',',
|
|
192
|
+
SPACE,
|
|
193
|
+
'Total=',
|
|
194
|
+
TimeUtil.asClock(summary.duration),
|
|
195
|
+
',',
|
|
196
|
+
SPACE,
|
|
197
|
+
'Clock=',
|
|
198
|
+
TimeUtil.asClock(Date.now() - this.#start),
|
|
199
|
+
')'
|
|
200
|
+
].join('')
|
|
201
|
+
);
|
|
174
202
|
}
|
|
175
203
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import util from 'node:util';
|
|
2
1
|
import { AssertionError } from 'node:assert';
|
|
2
|
+
import util from 'node:util';
|
|
3
3
|
|
|
4
4
|
import { TypedObject } from '@travetto/runtime';
|
|
5
5
|
|
|
@@ -11,9 +11,12 @@ export class TestConsumerUtil {
|
|
|
11
11
|
if (error instanceof AssertionError) {
|
|
12
12
|
return;
|
|
13
13
|
} else if (error instanceof Error) {
|
|
14
|
-
const stack = error.stack
|
|
15
|
-
error.stack
|
|
16
|
-
|
|
14
|
+
const stack = error.stack
|
|
15
|
+
? error.stack
|
|
16
|
+
.split(/\n/)
|
|
17
|
+
.slice(0, verbose ? -1 : 5)
|
|
18
|
+
.join('\n')
|
|
19
|
+
: error.message;
|
|
17
20
|
const subObject: Record<string, unknown> = {};
|
|
18
21
|
for (const key of TypedObject.keys(error)) {
|
|
19
22
|
if (key !== 'stack' && key !== 'message' && key !== 'name') {
|
|
@@ -25,4 +28,4 @@ export class TestConsumerUtil {
|
|
|
25
28
|
return `${error}`;
|
|
26
29
|
}
|
|
27
30
|
}
|
|
28
|
-
}
|
|
31
|
+
}
|
|
@@ -5,8 +5,8 @@ import { stringify } from 'yaml';
|
|
|
5
5
|
import { RuntimeIndex } from '@travetto/runtime';
|
|
6
6
|
|
|
7
7
|
import type { TestEvent } from '../../model/event.ts';
|
|
8
|
-
import type { SuitesSummary, TestConsumerShape } from '../types.ts';
|
|
9
8
|
import { TestConsumer } from '../decorator.ts';
|
|
9
|
+
import type { SuitesSummary, TestConsumerShape } from '../types.ts';
|
|
10
10
|
import { TestConsumerUtil } from './util.ts';
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -37,7 +37,10 @@ export class XunitEmitter implements TestConsumerShape {
|
|
|
37
37
|
}
|
|
38
38
|
if (Object.keys(metadata).length) {
|
|
39
39
|
let body = stringify(metadata);
|
|
40
|
-
body = body
|
|
40
|
+
body = body
|
|
41
|
+
.split('\n')
|
|
42
|
+
.map(line => ` ${line}`)
|
|
43
|
+
.join('\n');
|
|
41
44
|
return `<![CDATA[\n${body}\n]]>`;
|
|
42
45
|
} else {
|
|
43
46
|
return '';
|
|
@@ -49,7 +52,6 @@ export class XunitEmitter implements TestConsumerShape {
|
|
|
49
52
|
*/
|
|
50
53
|
onEvent(event: TestEvent): void {
|
|
51
54
|
if (event.type === 'test' && event.phase === 'after') {
|
|
52
|
-
|
|
53
55
|
const { test } = event;
|
|
54
56
|
|
|
55
57
|
let name = `${test.methodName}`;
|
|
@@ -79,8 +81,7 @@ export class XunitEmitter implements TestConsumerShape {
|
|
|
79
81
|
${body}
|
|
80
82
|
<system-out>${this.buildMeta({ log: groupedByLevel.log, info: groupedByLevel.info, debug: groupedByLevel.debug })}</system-out>
|
|
81
83
|
<system-err>${this.buildMeta({ error: groupedByLevel.error, warn: groupedByLevel.warn })}</system-err>
|
|
82
|
-
</testcase>`
|
|
83
|
-
);
|
|
84
|
+
</testcase>`);
|
|
84
85
|
} else if (event.type === 'suite' && event.phase === 'after') {
|
|
85
86
|
const { suite } = event;
|
|
86
87
|
const testBodies = this.#tests.slice(0);
|
|
@@ -121,4 +122,4 @@ export class XunitEmitter implements TestConsumerShape {
|
|
|
121
122
|
</testsuites>
|
|
122
123
|
`);
|
|
123
124
|
}
|
|
124
|
-
}
|
|
125
|
+
}
|
package/src/decorator/suite.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
+
}
|
package/src/decorator/test.ts
CHANGED
|
@@ -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(
|
|
31
|
-
|
|
30
|
+
SuiteRegistryIndex.getForRegister(getClass(instance)).registerTest(
|
|
31
|
+
property,
|
|
32
|
+
descriptor.value,
|
|
33
|
+
...(typeof description !== 'string' && description ? [description] : []),
|
|
32
34
|
...rest,
|
|
33
|
-
...(typeof description === 'string'
|
|
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
|
+
}
|
package/src/execute/barrier.ts
CHANGED
|
@@ -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
|
|
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(() => {
|
|
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
|
|
34
|
+
static uncaughtErrorPromise(): { promise: Promise<void>; resolve: () => unknown } {
|
|
33
35
|
const uncaught = Promise.withResolvers<void>();
|
|
34
|
-
const onError = (error: Error): void => {
|
|
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(() => {
|
|
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
|
|
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
|
+
}
|
package/src/execute/console.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/execute/executor.ts
CHANGED
|
@@ -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 {
|
|
10
|
-
import { TestPhaseManager } from './phase.ts';
|
|
5
|
+
import { AssertCheck } from '../assert/check.ts';
|
|
11
6
|
import { AssertUtil } from '../assert/util.ts';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
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,7 +19,6 @@ 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) {
|
|
@@ -57,16 +56,15 @@ export class TestExecutor {
|
|
|
57
56
|
* Execute the test, capture output, assertions and promises
|
|
58
57
|
*/
|
|
59
58
|
async executeTest(instance: unknown, test: TestConfig, suite: SuiteConfig, override?: Partial<TestResult>): Promise<TestResult> {
|
|
60
|
-
|
|
61
59
|
const result = TestModelUtil.createTestResult(suite, test, override);
|
|
62
60
|
|
|
63
61
|
// Mark test start
|
|
64
62
|
this.#consumer.onEvent({ type: 'test', phase: 'before', test });
|
|
65
63
|
|
|
66
|
-
|
|
67
64
|
// Emit every assertion as it occurs
|
|
68
65
|
const getAssertions = AssertCapture.collector(test, item =>
|
|
69
|
-
this.#consumer.onEvent({ type: 'assertion', phase: 'after', assertion: item })
|
|
66
|
+
this.#consumer.onEvent({ type: 'assertion', phase: 'after', assertion: item })
|
|
67
|
+
);
|
|
70
68
|
|
|
71
69
|
const consoleCapture = new ConsoleCapture().start(); // Capture all output from transpiled code
|
|
72
70
|
|
|
@@ -75,7 +73,9 @@ export class TestExecutor {
|
|
|
75
73
|
if (result.error) {
|
|
76
74
|
result.assertions.push(AssertUtil.generateAssertion({ suite, test, error: result.error }));
|
|
77
75
|
}
|
|
78
|
-
for (const item of result.assertions ?? []) {
|
|
76
|
+
for (const item of result.assertions ?? []) {
|
|
77
|
+
AssertCapture.add(item);
|
|
78
|
+
}
|
|
79
79
|
} else {
|
|
80
80
|
// Run method and get result
|
|
81
81
|
const startTime = Date.now();
|
|
@@ -101,7 +101,6 @@ export class TestExecutor {
|
|
|
101
101
|
* Execute an entire suite
|
|
102
102
|
*/
|
|
103
103
|
async executeSuite(suite: SuiteConfig, tests: TestConfig[]): Promise<void> {
|
|
104
|
-
|
|
105
104
|
const instance = classConstruct(suite.class);
|
|
106
105
|
|
|
107
106
|
const shouldSkip = await this.#shouldSkip(suite, instance);
|
|
@@ -110,7 +109,8 @@ export class TestExecutor {
|
|
|
110
109
|
|
|
111
110
|
if (shouldSkip) {
|
|
112
111
|
this.#consumer.onEvent({
|
|
113
|
-
phase: 'after',
|
|
112
|
+
phase: 'after',
|
|
113
|
+
type: 'suite',
|
|
114
114
|
suite: {
|
|
115
115
|
...result,
|
|
116
116
|
status: 'skipped',
|
|
@@ -130,9 +130,7 @@ export class TestExecutor {
|
|
|
130
130
|
const testResultOverrides: Record<string, Partial<TestResult>> = {};
|
|
131
131
|
|
|
132
132
|
const validTestMethodNames = new Set(tests.map(t => t.methodName));
|
|
133
|
-
const testConfigs = Object.fromEntries(
|
|
134
|
-
Object.entries(suite.tests).filter(([key]) => validTestMethodNames.has(key))
|
|
135
|
-
);
|
|
133
|
+
const testConfigs = Object.fromEntries(Object.entries(suite.tests).filter(([key]) => validTestMethodNames.has(key)));
|
|
136
134
|
|
|
137
135
|
// Mark suite start
|
|
138
136
|
this.#consumer.onEvent({ phase: 'before', type: 'suite', suite: { ...suite, tests: testConfigs } });
|
|
@@ -162,7 +160,7 @@ export class TestExecutor {
|
|
|
162
160
|
|
|
163
161
|
try {
|
|
164
162
|
// Handle BeforeEach
|
|
165
|
-
testResultOverride.status || await manager.startPhase('each');
|
|
163
|
+
testResultOverride.status || (await manager.startPhase('each'));
|
|
166
164
|
} catch (someError) {
|
|
167
165
|
const testError = await manager.onError('each', someError);
|
|
168
166
|
testResultOverride.error = testError;
|
|
@@ -174,9 +172,11 @@ export class TestExecutor {
|
|
|
174
172
|
|
|
175
173
|
// Handle after each
|
|
176
174
|
try {
|
|
177
|
-
testResultOverride.status || await manager.endPhase('each');
|
|
175
|
+
testResultOverride.status || (await manager.endPhase('each'));
|
|
178
176
|
} catch (testError) {
|
|
179
|
-
if (!(testError instanceof Error)) {
|
|
177
|
+
if (!(testError instanceof Error)) {
|
|
178
|
+
throw testError;
|
|
179
|
+
}
|
|
180
180
|
console.error('Failed to properly shutdown test', testError.message);
|
|
181
181
|
}
|
|
182
182
|
|
|
@@ -189,7 +189,9 @@ export class TestExecutor {
|
|
|
189
189
|
// Handle after all
|
|
190
190
|
await manager.endPhase('all');
|
|
191
191
|
} catch (suiteError) {
|
|
192
|
-
if (!(suiteError instanceof Error)) {
|
|
192
|
+
if (!(suiteError instanceof Error)) {
|
|
193
|
+
throw suiteError;
|
|
194
|
+
}
|
|
193
195
|
console.error('Failed to properly shutdown test', suiteError.message);
|
|
194
196
|
}
|
|
195
197
|
|
|
@@ -210,7 +212,9 @@ export class TestExecutor {
|
|
|
210
212
|
try {
|
|
211
213
|
await Runtime.importFrom(run.import);
|
|
212
214
|
} catch (error) {
|
|
213
|
-
if (!(error instanceof Error)) {
|
|
215
|
+
if (!(error instanceof Error)) {
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
214
218
|
const suite = TestModelUtil.createImportErrorSuiteResult(run);
|
|
215
219
|
console.error(error);
|
|
216
220
|
this.#consumer.onEvent({ type: 'suite', phase: 'after', suite });
|
|
@@ -235,4 +239,4 @@ export class TestExecutor {
|
|
|
235
239
|
await this.executeSuite(suite, tests);
|
|
236
240
|
}
|
|
237
241
|
}
|
|
238
|
-
}
|
|
242
|
+
}
|
package/src/execute/phase.ts
CHANGED
|
@@ -60,7 +60,7 @@ export class TestPhaseManager {
|
|
|
60
60
|
*/
|
|
61
61
|
async onError(phase: 'all' | 'each', error: unknown): Promise<Error> {
|
|
62
62
|
if (!(error instanceof Error)) {
|
|
63
|
-
await this.endPhase(phase).catch(() => {
|
|
63
|
+
await this.endPhase(phase).catch(() => {});
|
|
64
64
|
throw error;
|
|
65
65
|
}
|
|
66
66
|
return error;
|