@travetto/test 8.0.0-alpha.9 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -21
- package/__index__.ts +9 -9
- package/package.json +20 -20
- package/src/assert/capture.ts +4 -5
- package/src/assert/check.ts +53 -26
- 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 +28 -25
- package/src/consumer/types/tap.ts +68 -40
- 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 +38 -38
- 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 +25 -15
- package/src/registry/registry-adapter.ts +10 -13
- package/src/registry/registry-index.ts +13 -11
- package/src/trv.d.ts +2 -2
- 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 +20 -15
- package/support/cli.test_child.ts +8 -3
- package/support/cli.test_diff.ts +11 -7
- package/support/cli.test_digest.ts +10 -4
- package/support/cli.test_direct.ts +11 -7
- package/support/cli.test_watch.ts +6 -4
- package/support/transformer.assert.ts +51 -43
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import { IpcChannel } from '@travetto/worker';
|
|
2
1
|
import { JSONUtil } from '@travetto/runtime';
|
|
2
|
+
import { IpcChannel } from '@travetto/worker';
|
|
3
3
|
|
|
4
4
|
import type { TestEvent, TestRemoveEvent } from '../../model/event.ts';
|
|
5
|
-
import type { TestConsumerShape } from '../types.ts';
|
|
6
5
|
import { TestConsumer } from '../decorator.ts';
|
|
6
|
+
import type { TestConsumerShape } from '../types.ts';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Triggers each event as an IPC command to a parent process
|
|
10
10
|
*/
|
|
11
11
|
@TestConsumer()
|
|
12
12
|
export class ExecutionEmitter extends IpcChannel<TestEvent> implements TestConsumerShape {
|
|
13
|
-
|
|
14
13
|
sendPayload(payload: unknown & { type: string }): void {
|
|
15
14
|
this.send(payload.type, JSONUtil.cloneForTransmit(payload));
|
|
16
15
|
}
|
|
@@ -22,4 +21,4 @@ export class ExecutionEmitter extends IpcChannel<TestEvent> implements TestConsu
|
|
|
22
21
|
onRemoveEvent(event: TestRemoveEvent): void {
|
|
23
22
|
this.sendPayload(event);
|
|
24
23
|
}
|
|
25
|
-
}
|
|
24
|
+
}
|
|
@@ -2,24 +2,23 @@ import type { Writable } from 'node:stream';
|
|
|
2
2
|
|
|
3
3
|
import { JSONUtil } from '@travetto/runtime';
|
|
4
4
|
|
|
5
|
-
import type { SuitesSummary } from '../types.ts';
|
|
6
5
|
import { TestConsumer } from '../decorator.ts';
|
|
6
|
+
import type { SuitesSummary } from '../types.ts';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Returns the entire result set as a single JSON document
|
|
10
10
|
*/
|
|
11
11
|
@TestConsumer()
|
|
12
12
|
export class JSONEmitter {
|
|
13
|
-
|
|
14
13
|
#stream: Writable;
|
|
15
14
|
|
|
16
15
|
constructor(stream: Writable = process.stdout) {
|
|
17
16
|
this.#stream = stream;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
onEvent(): void {
|
|
19
|
+
onEvent(): void {}
|
|
21
20
|
|
|
22
21
|
onSummary(summary: SuitesSummary): void {
|
|
23
22
|
this.#stream.write(JSONUtil.toUTF8Pretty(summary));
|
|
24
23
|
}
|
|
25
|
-
}
|
|
24
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { TestConsumerShape } from '../types.ts';
|
|
2
1
|
import { TestConsumer } from '../decorator.ts';
|
|
2
|
+
import type { TestConsumerShape } from '../types.ts';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Does nothing consumer
|
|
6
6
|
*/
|
|
7
7
|
@TestConsumer()
|
|
8
8
|
export class NoopConsumer implements TestConsumerShape {
|
|
9
|
-
onEvent(): void {
|
|
10
|
-
}
|
|
9
|
+
onEvent(): void {}
|
|
10
|
+
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import type { TestConsumerShape } from '../types.ts';
|
|
2
|
-
import { TestResultsSummarizer } from './summarizer.ts';
|
|
3
1
|
import type { TestEvent } from '../../model/event.ts';
|
|
2
|
+
import type { TestConsumerShape } from '../types.ts';
|
|
4
3
|
import { DelegatingConsumer } from './delegating.ts';
|
|
4
|
+
import { TestResultsSummarizer } from './summarizer.ts';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Test consumer with support for multiple nested consumers, and summarization
|
|
8
8
|
*/
|
|
9
9
|
export class RunnableTestConsumer extends DelegatingConsumer {
|
|
10
|
-
|
|
11
10
|
#results?: TestResultsSummarizer;
|
|
12
11
|
|
|
13
12
|
constructor(...consumers: TestConsumerShape[]) {
|
|
@@ -22,7 +21,6 @@ export class RunnableTestConsumer extends DelegatingConsumer {
|
|
|
22
21
|
|
|
23
22
|
async summarizeAsBoolean(): Promise<boolean> {
|
|
24
23
|
await this.summarize(this.#results?.summary);
|
|
25
|
-
return (this.#results?.summary.failed ?? 0) <= 0 &&
|
|
26
|
-
(this.#results?.summary.errored ?? 0) <= 0;
|
|
24
|
+
return (this.#results?.summary.failed ?? 0) <= 0 && (this.#results?.summary.errored ?? 0) <= 0;
|
|
27
25
|
}
|
|
28
|
-
}
|
|
26
|
+
}
|
|
@@ -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 {
|
|
2
|
-
import {
|
|
1
|
+
import { AsyncQueue, Util } from '@travetto/runtime';
|
|
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
|
}
|
|
@@ -98,17 +93,25 @@ export class TapSummaryEmitter implements TestConsumerShape {
|
|
|
98
93
|
this.onTestRunState(state);
|
|
99
94
|
|
|
100
95
|
const total = TestModelUtil.buildSummary();
|
|
101
|
-
const success = StyleUtil.getStyle({ text: '#e5e5e5', background: '#026020' }); // White on dark green
|
|
102
|
-
const fail = StyleUtil.getStyle({ text: '#e5e5e5', background: '#8b0000' }); // White on dark red
|
|
103
96
|
this.#progress = this.#terminal.streamToBottom(
|
|
104
97
|
Util.mapAsyncIterable(
|
|
105
98
|
this.#results,
|
|
106
|
-
|
|
99
|
+
value => {
|
|
107
100
|
TestModelUtil.countTestResult(total, [value]);
|
|
108
101
|
const statusLine = `${total.failed} failed, ${total.errored} errored, ${total.skipped} skipped`;
|
|
109
|
-
return {
|
|
102
|
+
return {
|
|
103
|
+
value: `Tests %completed/%total [${statusLine}] -- ${value.classId}`,
|
|
104
|
+
total: this.#state.testCount,
|
|
105
|
+
completed: total.passed,
|
|
106
|
+
failed: total.failed + total.errored
|
|
107
|
+
};
|
|
110
108
|
},
|
|
111
|
-
TerminalUtil.progressBarUpdater(this.#terminal, {
|
|
109
|
+
TerminalUtil.progressBarUpdater(this.#terminal, {
|
|
110
|
+
style: {
|
|
111
|
+
complete: { text: '#e5e5e5', background: '#026020' },
|
|
112
|
+
failed: { text: '#e5e5e5', background: '#8b0000' }
|
|
113
|
+
}
|
|
114
|
+
})
|
|
112
115
|
),
|
|
113
116
|
{ minDelay: 100 }
|
|
114
117
|
);
|
|
@@ -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}`);
|
|
@@ -79,41 +80,48 @@ export class TapEmitter implements TestConsumerShape {
|
|
|
79
80
|
// Handle each assertion
|
|
80
81
|
if (test.assertions.length) {
|
|
81
82
|
let subCount = 0;
|
|
82
|
-
for (const
|
|
83
|
-
const assertSourceFile = RuntimeIndex.getFromImport(
|
|
84
|
-
const text =
|
|
85
|
-
const location =
|
|
83
|
+
for (const assertion of test.assertions) {
|
|
84
|
+
const assertSourceFile = RuntimeIndex.getFromImport(assertion.import)!.sourceFile;
|
|
85
|
+
const text = assertion.message ? `${assertion.text} (${this.#enhancer.failure(assertion.message)})` : assertion.text;
|
|
86
|
+
const location = assertion.import ? `./${path.relative(process.cwd(), assertSourceFile)}` : '<unknown>';
|
|
86
87
|
let subMessage = [
|
|
87
|
-
this.#enhancer.assertNumber(
|
|
88
|
+
this.#enhancer.assertNumber((subCount += 1)),
|
|
88
89
|
'-',
|
|
89
90
|
this.#enhancer.assertDescription(text),
|
|
90
91
|
StyleUtil.link(
|
|
91
|
-
`${this.#enhancer.assertFile(location)}:${this.#enhancer.assertLine(
|
|
92
|
-
`file://${assertSourceFile}#${
|
|
92
|
+
`${this.#enhancer.assertFile(location)}:${this.#enhancer.assertLine(assertion.line)}`,
|
|
93
|
+
`file://${assertSourceFile}#${assertion.line}`
|
|
93
94
|
)
|
|
94
95
|
].join(' ');
|
|
95
96
|
|
|
96
|
-
if (
|
|
97
|
+
if (assertion.error) {
|
|
97
98
|
subMessage = `${this.#enhancer.failure('not ok')} ${subMessage}`;
|
|
98
99
|
} else {
|
|
99
100
|
subMessage = `${this.#enhancer.success('ok')} ${subMessage}`;
|
|
100
101
|
}
|
|
101
102
|
this.log(` ${subMessage}`);
|
|
102
103
|
|
|
103
|
-
if (
|
|
104
|
-
this.logMeta({ message:
|
|
104
|
+
if (assertion.message && assertion.message.length > 100) {
|
|
105
|
+
this.logMeta({ message: assertion.message.replace(/\\n/g, '\n') });
|
|
105
106
|
}
|
|
106
107
|
}
|
|
107
108
|
this.log(` ${this.#enhancer.assertNumber(1)}..${this.#enhancer.assertNumber(subCount)}`);
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
// Track test result
|
|
111
|
-
let status = `${this.#enhancer.testNumber(
|
|
112
|
+
let status = `${this.#enhancer.testNumber((this.#count += 1))} `;
|
|
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
|
+
}
|