@travetto/test 8.0.0-alpha.0 → 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 +1 -1
- package/package.json +8 -8
- package/src/assert/check.ts +3 -7
- package/src/assert/util.ts +19 -80
- package/src/consumer/types/cumulative.ts +6 -18
- package/src/consumer/types/delegating.ts +6 -0
- package/src/consumer/types/runnable.ts +2 -1
- package/src/consumer/types/summarizer.ts +4 -16
- package/src/consumer/types/tap-summary.ts +13 -7
- package/src/consumer/types/tap.ts +25 -45
- package/src/consumer/types/util.ts +28 -0
- package/src/consumer/types/xunit.ts +3 -2
- package/src/consumer/types.ts +6 -10
- package/src/decorator/suite.ts +1 -3
- package/src/execute/executor.ts +97 -143
- package/src/execute/phase.ts +12 -22
- package/src/execute/run.ts +3 -2
- package/src/model/suite.ts +13 -11
- package/src/model/test.ts +3 -3
- package/src/model/util.ts +90 -7
- package/src/registry/registry-adapter.ts +20 -5
- package/src/worker/child.ts +1 -3
- package/support/bin/run.ts +1 -18
- package/support/cli.test.ts +4 -7
- package/support/cli.test_diff.ts +3 -6
- package/support/cli.test_direct.ts +3 -6
- package/support/cli.test_watch.ts +4 -6
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
|
-
|
|
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.
|
|
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.
|
|
32
|
-
"@travetto/runtime": "^8.0.0-alpha.
|
|
33
|
-
"@travetto/terminal": "^8.0.0-alpha.
|
|
34
|
-
"@travetto/worker": "^8.0.0-alpha.
|
|
35
|
-
"yaml": "^2.8.
|
|
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.
|
|
39
|
-
"@travetto/transformer": "^8.0.0-alpha.
|
|
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": {
|
package/src/assert/check.ts
CHANGED
|
@@ -178,17 +178,13 @@ export class AssertCheck {
|
|
|
178
178
|
static #onError(
|
|
179
179
|
positive: boolean,
|
|
180
180
|
message: string | undefined,
|
|
181
|
-
|
|
181
|
+
errorValue: unknown,
|
|
182
182
|
missed: Error | undefined,
|
|
183
183
|
shouldThrow: ThrowableError | undefined,
|
|
184
184
|
assertion: CapturedAssertion
|
|
185
185
|
): void {
|
|
186
|
-
|
|
187
|
-
|
|
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;
|
package/src/assert/util.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import util from 'node:util';
|
|
2
|
-
import path from 'node:path';
|
|
3
2
|
|
|
4
|
-
import {
|
|
3
|
+
import { JSONUtil, hasFunction, RuntimeIndex, Util } from '@travetto/runtime';
|
|
5
4
|
|
|
6
|
-
import type {
|
|
7
|
-
import type { SuiteConfig
|
|
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
|
|
|
@@ -40,93 +39,33 @@ export class AssertUtil {
|
|
|
40
39
|
* Determine file location for a given error and the stack trace
|
|
41
40
|
*/
|
|
42
41
|
static getPositionOfError(error: Error): { import: string, line: number } | undefined {
|
|
43
|
-
const
|
|
44
|
-
const frames = Util.stackTraceToParts(stack)
|
|
42
|
+
const frames = Util.stackTraceToParts(error.stack ?? new Error().stack!)
|
|
45
43
|
.map(frame => {
|
|
46
|
-
const
|
|
47
|
-
return { ...frame, import:
|
|
48
|
-
})
|
|
49
|
-
.filter(frame => !!frame.import);
|
|
44
|
+
const entry = RuntimeIndex.getEntry(frame.filename);
|
|
45
|
+
return { ...frame, import: entry?.import!, line: entry?.type === 'ts' ? frame.line : 1 };
|
|
46
|
+
});
|
|
50
47
|
|
|
51
|
-
return frames
|
|
48
|
+
return frames.find(frame => frame.import);
|
|
52
49
|
}
|
|
53
50
|
|
|
54
51
|
/**
|
|
55
52
|
* Generate a suite error given a suite config, and an error
|
|
56
53
|
*/
|
|
57
|
-
static
|
|
58
|
-
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;
|
|
59
57
|
const testImport = importLocation ?? test.import;
|
|
60
58
|
const position = this.getPositionOfError(error);
|
|
61
59
|
const line = position?.line ?? (testImport === suite.import ? suite.lineStart : 1);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
return {
|
|
61
|
+
import: position?.import ?? testImport,
|
|
62
|
+
methodName: test.methodName,
|
|
63
|
+
classId: suite.classId,
|
|
64
|
+
operator: 'throw',
|
|
66
65
|
error,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
assertions: [{
|
|
71
|
-
import: position?.import ?? testImport,
|
|
72
|
-
methodName: test.methodName,
|
|
73
|
-
classId: suite.classId,
|
|
74
|
-
operator: 'throw',
|
|
75
|
-
error,
|
|
76
|
-
line,
|
|
77
|
-
message: error.message.split(/\n/)[0],
|
|
78
|
-
text: test.methodName
|
|
79
|
-
}],
|
|
66
|
+
line,
|
|
67
|
+
message: error.message.split(/\n/)[0],
|
|
68
|
+
text: test.methodName
|
|
80
69
|
};
|
|
81
|
-
|
|
82
|
-
return testResult;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Generate suite failure
|
|
87
|
-
*/
|
|
88
|
-
static generateSuiteTestFailures(suite: SuiteConfig, error: Error): TestResult[] {
|
|
89
|
-
const finalError = error.cause instanceof Error ? error.cause : error;
|
|
90
|
-
return Object.values(suite.tests).map(test => this.generateSuiteTestFailure({ suite, test, error: finalError }));
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Define import failure as a TestResult
|
|
95
|
-
*/
|
|
96
|
-
static gernerateImportFailure(importLocation: string, error: Error): { result: TestResult, test: TestConfig, suite: SuiteResult & SuiteConfig } {
|
|
97
|
-
const name = path.basename(importLocation);
|
|
98
|
-
const classId = `${RuntimeIndex.getFromImport(importLocation)?.id}#${name}`;
|
|
99
|
-
const suite = asFull<SuiteConfig & SuiteResult>({
|
|
100
|
-
class: asFull<Class>({ name }), classId, duration: 0, lineStart: 1, lineEnd: 1, import: importLocation
|
|
101
|
-
});
|
|
102
|
-
error.message = error.message.replaceAll(Runtime.mainSourcePath, '.');
|
|
103
|
-
const result = this.generateSuiteTestFailure({
|
|
104
|
-
suite,
|
|
105
|
-
test: {
|
|
106
|
-
methodName: 'require',
|
|
107
|
-
classId,
|
|
108
|
-
import: importLocation,
|
|
109
|
-
class: suite.class,
|
|
110
|
-
lineBodyStart: 1,
|
|
111
|
-
lineStart: 1,
|
|
112
|
-
lineEnd: 1,
|
|
113
|
-
skip: false
|
|
114
|
-
},
|
|
115
|
-
error
|
|
116
|
-
});
|
|
117
|
-
const test: TestConfig = {
|
|
118
|
-
methodName: 'import',
|
|
119
|
-
classId,
|
|
120
|
-
import: importLocation,
|
|
121
|
-
declarationImport: importLocation,
|
|
122
|
-
lineStart: 0,
|
|
123
|
-
lineEnd: 0,
|
|
124
|
-
lineBodyStart: 0,
|
|
125
|
-
tags: [],
|
|
126
|
-
description: 'Import Failure',
|
|
127
|
-
skip: false,
|
|
128
|
-
class: undefined!
|
|
129
|
-
};
|
|
130
|
-
return { result, test, suite };
|
|
131
70
|
}
|
|
132
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 {
|
|
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
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
12
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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 '
|
|
130
|
-
default: status = `${this.#enhancer.
|
|
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
|
|
142
|
-
if (
|
|
143
|
-
this.logMeta({ error:
|
|
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
|
-
|
|
193
|
-
|
|
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
|
|
63
|
+
const errorMessage = TestConsumerUtil.errorToString(test.error);
|
|
63
64
|
const node = test.status === 'failed' ? 'failure' : 'error';
|
|
64
|
-
body = `<${node} type="${
|
|
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[]> = {};
|
package/src/consumer/types.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
*/
|
package/src/decorator/suite.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { castTo, type Class, type ClassInstance,
|
|
1
|
+
import { castTo, type Class, type ClassInstance, getClass } from '@travetto/runtime';
|
|
2
2
|
|
|
3
3
|
import type { SuiteConfig } from '../model/suite.ts';
|
|
4
4
|
import { SuiteRegistryIndex } from '../registry/registry-index.ts';
|
|
@@ -15,11 +15,9 @@ export function Suite(...rest: Partial<SuiteConfig>[]): ClassDecorator;
|
|
|
15
15
|
export function Suite(description: string, ...rest: Partial<SuiteConfig>[]): ClassDecorator;
|
|
16
16
|
export function Suite(description?: string | Partial<SuiteConfig>, ...rest: Partial<SuiteConfig>[]): ClassDecorator {
|
|
17
17
|
const decorator = (cls: Class): typeof cls => {
|
|
18
|
-
const isAbstract = describeFunction(cls).abstract;
|
|
19
18
|
SuiteRegistryIndex.getForRegister(cls).register(
|
|
20
19
|
...(typeof description !== 'string' && description ? [description] : []),
|
|
21
20
|
...rest,
|
|
22
|
-
...isAbstract ? [{ skip: true }] : [],
|
|
23
21
|
...(typeof description === 'string' ? [{ description }] : []),
|
|
24
22
|
);
|
|
25
23
|
return cls;
|