@travetto/test 5.0.0-rc.9 → 5.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 +2 -1
- package/package.json +8 -8
- package/src/assert/check.ts +1 -1
- package/src/assert/util.ts +20 -6
- package/src/consumer/{error.ts → serialize.ts} +10 -17
- package/src/consumer/types/cumulative.ts +9 -17
- package/src/consumer/types/delegating.ts +58 -0
- package/src/consumer/types/event.ts +2 -4
- package/src/consumer/types/execution.ts +2 -4
- package/src/consumer/types/runnable.ts +12 -41
- package/src/consumer/types/tap-streamed.ts +7 -4
- package/src/consumer/types/tap.ts +2 -2
- package/src/execute/executor.ts +79 -90
- package/src/execute/phase.ts +19 -29
- package/src/execute/runner.ts +29 -20
- package/src/execute/types.ts +12 -10
- package/src/execute/util.ts +30 -5
- package/src/execute/watcher.ts +32 -36
- package/src/model/common.ts +9 -1
- package/src/model/event.ts +9 -5
- package/src/model/suite.ts +11 -1
- package/src/model/test.ts +27 -1
- package/src/registry/suite.ts +24 -17
- package/src/trv.d.ts +4 -0
- package/src/worker/child.ts +9 -15
- package/src/worker/standard.ts +17 -22
- package/src/worker/types.ts +13 -23
- package/support/cli.test.ts +18 -4
- package/support/{cli.test_count.ts → cli.test_digest.ts} +14 -6
- package/support/cli.test_direct.ts +10 -3
- package/support/transformer.assert.ts +2 -2
package/README.md
CHANGED
|
@@ -221,12 +221,13 @@ To run the tests you can either call the [Command Line Interface](https://github
|
|
|
221
221
|
```bash
|
|
222
222
|
$ trv test --help
|
|
223
223
|
|
|
224
|
-
Usage: test [options] [first:string] [
|
|
224
|
+
Usage: test [options] [first:string] [globs...:string]
|
|
225
225
|
|
|
226
226
|
Options:
|
|
227
227
|
-f, --format <string> Output format for test results (default: "tap")
|
|
228
228
|
-c, --concurrency <number> Number of tests to run concurrently (default: 4)
|
|
229
229
|
-m, --mode <single|standard> Test run mode (default: "standard")
|
|
230
|
+
-t, --tags <string> Tags to target or exclude
|
|
230
231
|
-h, --help display help for command
|
|
231
232
|
```
|
|
232
233
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/test",
|
|
3
|
-
"version": "5.0.0
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Declarative test framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"unit-testing",
|
|
@@ -27,15 +27,15 @@
|
|
|
27
27
|
"directory": "module/test"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@travetto/
|
|
31
|
-
"@travetto/
|
|
32
|
-
"@travetto/terminal": "^5.0.0
|
|
33
|
-
"@travetto/worker": "^5.0.0
|
|
34
|
-
"yaml": "^2.
|
|
30
|
+
"@travetto/registry": "^5.0.0",
|
|
31
|
+
"@travetto/runtime": "^5.0.0",
|
|
32
|
+
"@travetto/terminal": "^5.0.0",
|
|
33
|
+
"@travetto/worker": "^5.0.0",
|
|
34
|
+
"yaml": "^2.5.0"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@travetto/cli": "^5.0.0
|
|
38
|
-
"@travetto/transformer": "^5.0.0
|
|
37
|
+
"@travetto/cli": "^5.0.0",
|
|
38
|
+
"@travetto/transformer": "^5.0.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependenciesMeta": {
|
|
41
41
|
"@travetto/transformer": {
|
package/src/assert/check.ts
CHANGED
|
@@ -265,7 +265,7 @@ export class AssertCheck {
|
|
|
265
265
|
* Look for any unhandled exceptions
|
|
266
266
|
*/
|
|
267
267
|
static checkUnhandled(test: TestConfig, err: Error | assert.AssertionError): void {
|
|
268
|
-
let line = AssertUtil.getPositionOfError(err, test.import).line;
|
|
268
|
+
let line = AssertUtil.getPositionOfError(err, test.sourceImport ?? test.import).line;
|
|
269
269
|
if (line === 1) {
|
|
270
270
|
line = test.lineStart;
|
|
271
271
|
}
|
package/src/assert/util.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import util from 'node:util';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
|
|
3
|
-
import { Runtime, RuntimeIndex } from '@travetto/runtime';
|
|
4
|
+
import { asFull, Class, Runtime, RuntimeIndex } from '@travetto/runtime';
|
|
4
5
|
|
|
5
6
|
import { TestConfig, Assertion, TestResult } from '../model/test';
|
|
6
|
-
import { SuiteConfig } from '../model/suite';
|
|
7
|
+
import { SuiteConfig, SuiteFailure, SuiteResult } from '../model/suite';
|
|
7
8
|
|
|
8
9
|
function isCleanable(o: unknown): o is { toClean(): unknown } {
|
|
9
10
|
return !!o && typeof o === 'object' && 'toClean' in o && typeof o.toClean === 'function';
|
|
@@ -18,6 +19,7 @@ export class AssertUtil {
|
|
|
18
19
|
*/
|
|
19
20
|
static cleanValue(val: unknown): unknown {
|
|
20
21
|
switch (typeof val) {
|
|
22
|
+
case 'number': case 'boolean': case 'bigint': case 'string': case 'undefined': return val;
|
|
21
23
|
case 'object': {
|
|
22
24
|
if (isCleanable(val)) {
|
|
23
25
|
return val.toClean();
|
|
@@ -26,7 +28,6 @@ export class AssertUtil {
|
|
|
26
28
|
}
|
|
27
29
|
break;
|
|
28
30
|
}
|
|
29
|
-
case 'undefined': case 'string': case 'number': case 'bigint': case 'boolean': return JSON.stringify(val);
|
|
30
31
|
case 'function': {
|
|
31
32
|
if (val.Ⲑid || !val.constructor) {
|
|
32
33
|
return val.name;
|
|
@@ -87,7 +88,7 @@ export class AssertUtil {
|
|
|
87
88
|
/**
|
|
88
89
|
* Generate a suite error given a suite config, and an error
|
|
89
90
|
*/
|
|
90
|
-
static
|
|
91
|
+
static generateSuiteFailure(suite: SuiteConfig, methodName: string, error: Error): SuiteFailure {
|
|
91
92
|
const { import: imp, ...pos } = this.getPositionOfError(error, suite.import);
|
|
92
93
|
let line = pos.line;
|
|
93
94
|
|
|
@@ -108,11 +109,24 @@ export class AssertUtil {
|
|
|
108
109
|
...coreAll,
|
|
109
110
|
status: 'failed', error, duration: 0, durationTotal: 0, assertions: [assert], output: {}
|
|
110
111
|
};
|
|
111
|
-
const
|
|
112
|
+
const test: TestConfig = {
|
|
112
113
|
...coreAll,
|
|
113
114
|
class: suite.class, skip: false
|
|
114
115
|
};
|
|
115
116
|
|
|
116
|
-
return { assert, testResult,
|
|
117
|
+
return { assert, testResult, test, suite };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Define import failure as a SuiteFailure object
|
|
122
|
+
*/
|
|
123
|
+
static gernerateImportFailure(imp: string, err: Error): SuiteFailure {
|
|
124
|
+
const name = path.basename(imp);
|
|
125
|
+
const classId = `${RuntimeIndex.getFromImport(imp)?.id}○${name}`;
|
|
126
|
+
const suite = asFull<SuiteConfig & SuiteResult>({
|
|
127
|
+
class: asFull<Class>({ name }), classId, duration: 0, lineStart: 1, lineEnd: 1, import: imp
|
|
128
|
+
});
|
|
129
|
+
err.message = err.message.replaceAll(Runtime.mainSourcePath, '.');
|
|
130
|
+
return this.generateSuiteFailure(suite, 'require', err);
|
|
117
131
|
}
|
|
118
132
|
}
|
|
@@ -5,11 +5,11 @@ import { TestEvent, } from '../model/event';
|
|
|
5
5
|
|
|
6
6
|
export type SerializedError = { $?: boolean, message: string, stack?: string, name: string };
|
|
7
7
|
|
|
8
|
-
function
|
|
8
|
+
function isError(e: unknown): e is SerializedError {
|
|
9
9
|
return !!e && (typeof e === 'object') && '$' in e;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export class
|
|
12
|
+
export class SerializeUtil {
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Prepare error for transmission
|
|
@@ -41,7 +41,7 @@ export class ErrorUtil {
|
|
|
41
41
|
static deserializeError(e: Error | SerializedError): Error;
|
|
42
42
|
static deserializeError(e: undefined): undefined;
|
|
43
43
|
static deserializeError(e: Error | SerializedError | undefined): Error | undefined {
|
|
44
|
-
if (
|
|
44
|
+
if (isError(e)) {
|
|
45
45
|
const err = new Error();
|
|
46
46
|
|
|
47
47
|
for (const k of TypedObject.keys(e)) {
|
|
@@ -54,25 +54,18 @@ export class ErrorUtil {
|
|
|
54
54
|
err.stack = e.stack;
|
|
55
55
|
err.name = e.name;
|
|
56
56
|
return err;
|
|
57
|
-
} else
|
|
57
|
+
} else {
|
|
58
58
|
return e;
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
* Serialize
|
|
63
|
+
* Serialize to JSON
|
|
64
64
|
*/
|
|
65
|
-
static
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
} else if (out.type === 'assertion') {
|
|
72
|
-
if (out.assertion.error) {
|
|
73
|
-
out.assertion.error = this.serializeError(out.assertion.error);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
65
|
+
static serializeToJSON(out: TestEvent): string {
|
|
66
|
+
return JSON.stringify(out, (_, v) =>
|
|
67
|
+
v instanceof Error ? this.serializeError(v) :
|
|
68
|
+
typeof v === 'bigint' ? v.toString() : v
|
|
69
|
+
);
|
|
77
70
|
}
|
|
78
71
|
}
|
|
@@ -7,19 +7,19 @@ import { TestEvent } from '../../model/event';
|
|
|
7
7
|
import { TestResult } from '../../model/test';
|
|
8
8
|
import { SuiteResult } from '../../model/suite';
|
|
9
9
|
import { SuiteRegistry } from '../../registry/suite';
|
|
10
|
+
import { DelegatingConsumer } from './delegating';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Cumulative Summary consumer
|
|
13
14
|
*/
|
|
14
|
-
export class CumulativeSummaryConsumer
|
|
15
|
+
export class CumulativeSummaryConsumer extends DelegatingConsumer {
|
|
15
16
|
/**
|
|
16
17
|
* Total state of all tests run so far
|
|
17
18
|
*/
|
|
18
19
|
#state: Record<string, Record<string, TestResult['status']>> = {};
|
|
19
|
-
#target: TestConsumer;
|
|
20
20
|
|
|
21
21
|
constructor(target: TestConsumer) {
|
|
22
|
-
|
|
22
|
+
super([target]);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
@@ -29,16 +29,9 @@ export class CumulativeSummaryConsumer implements TestConsumer {
|
|
|
29
29
|
summarizeSuite(test: TestResult): SuiteResult {
|
|
30
30
|
// Was only loading to verify existence (TODO: double-check)
|
|
31
31
|
if (existsSync(RuntimeIndex.getFromImport(test.import)!.sourceFile)) {
|
|
32
|
-
this.#state[test.classId]
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
x.Ⲑid === test.classId
|
|
36
|
-
)!;
|
|
37
|
-
if (SuiteCls) {
|
|
38
|
-
return this.computeTotal(SuiteCls);
|
|
39
|
-
} else {
|
|
40
|
-
return this.removeClass(test.classId);
|
|
41
|
-
}
|
|
32
|
+
(this.#state[test.classId] ??= {})[test.methodName] = test.status;
|
|
33
|
+
const SuiteCls = SuiteRegistry.getClasses().find(x => x.Ⲑid === test.classId);
|
|
34
|
+
return SuiteCls ? this.computeTotal(SuiteCls) : this.removeClass(test.classId);
|
|
42
35
|
} else {
|
|
43
36
|
return this.removeClass(test.classId);
|
|
44
37
|
}
|
|
@@ -83,14 +76,13 @@ export class CumulativeSummaryConsumer implements TestConsumer {
|
|
|
83
76
|
* Listen for event, process the full event, and if the event is an after test,
|
|
84
77
|
* send a full suite summary
|
|
85
78
|
*/
|
|
86
|
-
|
|
87
|
-
this.#target.onEvent(e);
|
|
79
|
+
onEventDone(e: TestEvent): void {
|
|
88
80
|
try {
|
|
89
81
|
if (e.type === 'test' && e.phase === 'after') {
|
|
90
|
-
this
|
|
82
|
+
this.onEvent({
|
|
91
83
|
type: 'suite',
|
|
92
84
|
phase: 'after',
|
|
93
|
-
suite: this.summarizeSuite(e.test)
|
|
85
|
+
suite: this.summarizeSuite(e.test),
|
|
94
86
|
});
|
|
95
87
|
}
|
|
96
88
|
} catch (err) {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { SuitesSummary, TestConsumer, TestRunState } from '../types';
|
|
2
|
+
import { TestEvent } from '../../model/event';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Delegating event consumer
|
|
6
|
+
*/
|
|
7
|
+
export abstract class DelegatingConsumer implements TestConsumer {
|
|
8
|
+
#consumers: TestConsumer[];
|
|
9
|
+
#transformer?: (ev: TestEvent) => typeof ev;
|
|
10
|
+
#filter?: (ev: TestEvent) => boolean;
|
|
11
|
+
|
|
12
|
+
constructor(consumers: TestConsumer[]) {
|
|
13
|
+
this.#consumers = consumers;
|
|
14
|
+
for (const c of consumers) {
|
|
15
|
+
c.onEvent = c.onEvent.bind(c);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
withTransformer(transformer: (ev: TestEvent) => typeof ev): this {
|
|
20
|
+
this.#transformer = transformer;
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
withFilter(filter: (ev: TestEvent) => boolean): this {
|
|
25
|
+
this.#filter = filter;
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async onStart(state: TestRunState): Promise<void> {
|
|
30
|
+
for (const c of this.#consumers) {
|
|
31
|
+
await c.onStart?.(state);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
onEvent(e: TestEvent): void {
|
|
36
|
+
if (this.#transformer) {
|
|
37
|
+
e = this.#transformer(e);
|
|
38
|
+
}
|
|
39
|
+
if (this.#filter?.(e) === false) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
for (const c of this.#consumers) {
|
|
43
|
+
c.onEvent(e);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this.onEventDone?.(e);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async summarize(summary?: SuitesSummary): Promise<void> {
|
|
50
|
+
if (summary) {
|
|
51
|
+
for (const c of this.#consumers) {
|
|
52
|
+
await c.onSummary?.(summary);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
onEventDone?(e: TestEvent): void;
|
|
58
|
+
}
|
|
@@ -2,7 +2,7 @@ import { Writable } from 'node:stream';
|
|
|
2
2
|
|
|
3
3
|
import { TestEvent } from '../../model/event';
|
|
4
4
|
import { TestConsumer } from '../types';
|
|
5
|
-
import {
|
|
5
|
+
import { SerializeUtil } from '../serialize';
|
|
6
6
|
import { Consumable } from '../registry';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -17,8 +17,6 @@ export class EventStreamer implements TestConsumer {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
onEvent(event: TestEvent): void {
|
|
20
|
-
|
|
21
|
-
ErrorUtil.serializeTestErrors(out);
|
|
22
|
-
this.#stream.write(`${JSON.stringify(out)}\n`);
|
|
20
|
+
this.#stream.write(`${SerializeUtil.serializeToJSON(event)}\n`);
|
|
23
21
|
}
|
|
24
22
|
}
|
|
@@ -2,7 +2,7 @@ import { ChildCommChannel } from '@travetto/worker';
|
|
|
2
2
|
|
|
3
3
|
import { TestEvent } from '../../model/event';
|
|
4
4
|
import { TestConsumer } from '../types';
|
|
5
|
-
import {
|
|
5
|
+
import { SerializeUtil } from '../serialize';
|
|
6
6
|
import { Consumable } from '../registry';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -12,8 +12,6 @@ import { Consumable } from '../registry';
|
|
|
12
12
|
export class ExecutionEmitter extends ChildCommChannel<TestEvent> implements TestConsumer {
|
|
13
13
|
|
|
14
14
|
onEvent(event: TestEvent): void {
|
|
15
|
-
|
|
16
|
-
ErrorUtil.serializeTestErrors(out);
|
|
17
|
-
this.send(event.type, out);
|
|
15
|
+
this.send(event.type, JSON.parse(SerializeUtil.serializeToJSON(event)));
|
|
18
16
|
}
|
|
19
17
|
}
|
|
@@ -1,62 +1,33 @@
|
|
|
1
|
-
import { TestConsumer
|
|
1
|
+
import { TestConsumer } from '../types';
|
|
2
2
|
import { TestResultsSummarizer } from './summarizer';
|
|
3
3
|
import { TestConsumerRegistry } from '../registry';
|
|
4
4
|
import { TestEvent } from '../../model/event';
|
|
5
|
+
import { DelegatingConsumer } from './delegating';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Test consumer with support for multiple nested consumers, and summarization
|
|
8
9
|
*/
|
|
9
|
-
export class RunnableTestConsumer
|
|
10
|
+
export class RunnableTestConsumer extends DelegatingConsumer {
|
|
10
11
|
/**
|
|
11
12
|
* Build a runnable test consumer given a format or a full consumer
|
|
12
13
|
*/
|
|
13
14
|
static async get(consumer: string | TestConsumer): Promise<RunnableTestConsumer> {
|
|
14
|
-
return new RunnableTestConsumer(await TestConsumerRegistry.getInstance(consumer));
|
|
15
|
+
return new RunnableTestConsumer([await TestConsumerRegistry.getInstance(consumer)]);
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
#
|
|
18
|
-
#results: TestResultsSummarizer | undefined;
|
|
18
|
+
#results?: TestResultsSummarizer;
|
|
19
19
|
|
|
20
|
-
constructor(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (!this.#results && c.onSummary) { // If expecting summary
|
|
24
|
-
this.#results = new TestResultsSummarizer();
|
|
25
|
-
}
|
|
26
|
-
c.onEvent = c.onEvent.bind(c);
|
|
27
|
-
}
|
|
20
|
+
constructor(consumers: TestConsumer[]) {
|
|
21
|
+
super(consumers);
|
|
22
|
+
this.#results = consumers.find(x => !!x.onSummary) ? new TestResultsSummarizer() : undefined;
|
|
28
23
|
}
|
|
29
24
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
await c.onStart?.(state);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
onEvent(e: TestEvent): void {
|
|
37
|
-
if (this.#results) {
|
|
38
|
-
this.#results.onEvent(e);
|
|
39
|
-
}
|
|
40
|
-
for (const c of this.#consumers) {
|
|
41
|
-
c.onEvent(e);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async summarize(): Promise<TestResultsSummarizer | undefined> {
|
|
46
|
-
if (this.#results) {
|
|
47
|
-
for (const c of this.#consumers) {
|
|
48
|
-
await c.onSummary?.(this.#results.summary);
|
|
49
|
-
}
|
|
50
|
-
return this.#results;
|
|
51
|
-
}
|
|
25
|
+
onEventDone(e: TestEvent): void {
|
|
26
|
+
this.#results?.onEvent(e);
|
|
52
27
|
}
|
|
53
28
|
|
|
54
29
|
async summarizeAsBoolean(): Promise<boolean> {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return result.summary.failed <= 0;
|
|
58
|
-
} else {
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
30
|
+
await this.summarize(this.#results?.summary);
|
|
31
|
+
return (this.#results?.summary.failed ?? 0) <= 0;
|
|
61
32
|
}
|
|
62
33
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { Util } from '@travetto/runtime';
|
|
1
|
+
import { Util, AsyncQueue } from '@travetto/runtime';
|
|
2
2
|
import { StyleUtil, Terminal, TerminalUtil } from '@travetto/terminal';
|
|
3
|
-
import { WorkQueue } from '@travetto/worker';
|
|
4
3
|
|
|
5
4
|
import { TestEvent } from '../../model/event';
|
|
6
5
|
import { TestResult } from '../../model/test';
|
|
@@ -17,7 +16,7 @@ import { TapEmitter } from './tap';
|
|
|
17
16
|
export class TapStreamedEmitter implements TestConsumer {
|
|
18
17
|
|
|
19
18
|
#terminal: Terminal;
|
|
20
|
-
#results = new
|
|
19
|
+
#results = new AsyncQueue<TestResult>();
|
|
21
20
|
#progress: Promise<unknown> | undefined;
|
|
22
21
|
#consumer: TapEmitter;
|
|
23
22
|
|
|
@@ -30,6 +29,8 @@ export class TapStreamedEmitter implements TestConsumer {
|
|
|
30
29
|
this.#consumer.onStart();
|
|
31
30
|
|
|
32
31
|
let failed = 0;
|
|
32
|
+
let skipped = 0;
|
|
33
|
+
let completed = 0;
|
|
33
34
|
const success = StyleUtil.getStyle({ text: '#e5e5e5', background: '#026020' }); // White on dark green
|
|
34
35
|
const fail = StyleUtil.getStyle({ text: '#e5e5e5', background: '#8b0000' }); // White on dark red
|
|
35
36
|
this.#progress = this.#terminal.streamToBottom(
|
|
@@ -37,7 +38,9 @@ export class TapStreamedEmitter implements TestConsumer {
|
|
|
37
38
|
this.#results,
|
|
38
39
|
(value, idx) => {
|
|
39
40
|
failed += (value.status === 'failed' ? 1 : 0);
|
|
40
|
-
|
|
41
|
+
skipped += (value.status === 'skipped' ? 1 : 0);
|
|
42
|
+
completed += (value.status !== 'skipped' ? 1 : 0);
|
|
43
|
+
return { value: `Tests %idx/%total [${failed} failed, ${skipped} skipped] -- ${value.classId}`, total: state.testCount, idx: completed };
|
|
41
44
|
},
|
|
42
45
|
TerminalUtil.progressBarUpdater(this.#terminal, { style: () => ({ complete: failed ? fail : success }) })
|
|
43
46
|
),
|
|
@@ -5,7 +5,7 @@ import { stringify } from 'yaml';
|
|
|
5
5
|
import { TestEvent } from '../../model/event';
|
|
6
6
|
import { SuitesSummary, TestConsumer } from '../types';
|
|
7
7
|
import { Consumable } from '../registry';
|
|
8
|
-
import {
|
|
8
|
+
import { SerializeUtil } from '../serialize';
|
|
9
9
|
import { TestResultsEnhancer, CONSOLE_ENHANCER } from '../enhancer';
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -102,7 +102,7 @@ export class TapEmitter implements TestConsumer {
|
|
|
102
102
|
// Handle error
|
|
103
103
|
if (test.status === 'failed') {
|
|
104
104
|
if (test.error && test.error.name !== 'AssertionError') {
|
|
105
|
-
const err =
|
|
105
|
+
const err = SerializeUtil.deserializeError(test.error);
|
|
106
106
|
this.logMeta({ error: err instanceof AppError ? err.toJSON() : err });
|
|
107
107
|
}
|
|
108
108
|
}
|