@travetto/test 3.0.0-rc.4 → 3.0.0-rc.7
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 +19 -18
- package/{index.ts → __index__.ts} +2 -0
- package/package.json +19 -12
- package/src/assert/capture.ts +1 -0
- package/src/assert/check.ts +13 -7
- package/src/assert/util.ts +12 -12
- package/src/consumer/enhancer.ts +16 -24
- package/src/consumer/registry.ts +5 -2
- package/src/consumer/types/{index.ts → all.ts} +1 -1
- package/src/consumer/types/cumulative.ts +11 -6
- package/src/consumer/types/runnable.ts +8 -12
- package/src/consumer/types/tap-streamed.ts +92 -0
- package/src/consumer/types/tap.ts +25 -21
- package/src/consumer/types.ts +2 -2
- package/src/consumer/util.ts +1 -1
- package/src/decorator/suite.ts +3 -2
- package/src/decorator/test.ts +11 -2
- package/src/execute/console.ts +4 -3
- package/src/execute/executor.ts +26 -40
- package/src/execute/phase.ts +2 -2
- package/src/execute/runner.ts +15 -30
- package/src/execute/{types.d.ts → types.ts} +4 -4
- package/src/execute/util.ts +8 -8
- package/src/execute/watcher.ts +43 -20
- package/src/fixture.ts +7 -0
- package/src/model/common.ts +4 -0
- package/src/registry/suite.ts +10 -7
- package/src/worker/child.ts +6 -51
- package/src/worker/standard.ts +39 -18
- package/src/worker/types.ts +0 -1
- package/{bin/lib → support/bin}/run.ts +8 -9
- package/support/cli.test.ts +76 -0
- package/support/main.test-child.ts +32 -0
- package/support/main.test-direct.ts +15 -0
- package/support/main.test-watch.ts +8 -0
- package/support/transformer.annotate.ts +4 -5
- package/support/transformer.assert.ts +22 -20
- package/bin/cli-test.ts +0 -121
- package/bin/test-child.ts +0 -38
- package/bin/test-direct.ts +0 -23
- package/bin/test-watch.ts +0 -25
- package/src/consumer/types/tap-summary.ts +0 -78
- package/src/worker/isolated.ts +0 -19
- package/support/phase.reset.ts +0 -12
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!-- This file was generated by @travetto/doc and should not be modified directly -->
|
|
2
|
-
<!-- Please modify https://github.com/travetto/travetto/tree/main/module/test/
|
|
2
|
+
<!-- Please modify https://github.com/travetto/travetto/tree/main/module/test/DOC.ts and execute "npx trv doc" to rebuild -->
|
|
3
3
|
# Testing
|
|
4
4
|
## Declarative test framework
|
|
5
5
|
|
|
@@ -19,13 +19,13 @@ This module provides unit testing functionality that integrates with the framewo
|
|
|
19
19
|
|
|
20
20
|
## Definition
|
|
21
21
|
|
|
22
|
-
A test suite is a collection of individual tests. All test suites are classes with the [@Suite](https://github.com/travetto/travetto/tree/main/module/test/src/decorator/suite.ts#
|
|
22
|
+
A test suite is a collection of individual tests. All test suites are classes with the [@Suite](https://github.com/travetto/travetto/tree/main/module/test/src/decorator/suite.ts#L14) decorator. Tests are defined as methods on the suite class, using the [@Test](https://github.com/travetto/travetto/tree/main/module/test/src/decorator/test.ts#L20) decorator. All tests intrinsically support `async`/`await`.
|
|
23
23
|
|
|
24
24
|
A simple example would be:
|
|
25
25
|
|
|
26
26
|
**Code: Example Test Suite**
|
|
27
27
|
```typescript
|
|
28
|
-
import
|
|
28
|
+
import assert from 'assert';
|
|
29
29
|
|
|
30
30
|
import { Suite, Test } from '@travetto/test';
|
|
31
31
|
|
|
@@ -56,7 +56,7 @@ A common aspect of the tests themselves are the assertions that are made. [Node
|
|
|
56
56
|
|
|
57
57
|
**Code: Example assertion for deep comparison**
|
|
58
58
|
```typescript
|
|
59
|
-
import
|
|
59
|
+
import assert from 'assert';
|
|
60
60
|
|
|
61
61
|
import { Suite, Test } from '@travetto/test';
|
|
62
62
|
|
|
@@ -73,28 +73,28 @@ class SimpleTest {
|
|
|
73
73
|
would translate to:
|
|
74
74
|
|
|
75
75
|
**Code: Transpiled test Code**
|
|
76
|
-
```
|
|
76
|
+
```javascript
|
|
77
77
|
"use strict";
|
|
78
78
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
79
79
|
const tslib_1 = require("tslib");
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const
|
|
80
|
+
const Ⲑ_check_1 = tslib_1.__importStar(require("@travetto/test/src/assert/check.js"));
|
|
81
|
+
const Ⲑ_root_index_1 = tslib_1.__importStar(require("@travetto/manifest/src/root-index.js"));
|
|
82
|
+
const Ⲑ_decorator_1 = tslib_1.__importStar(require("@travetto/registry/src/decorator.js"));
|
|
83
|
+
const assert_1 = tslib_1.__importDefault(require("assert"));
|
|
83
84
|
const test_1 = require("@travetto/test");
|
|
84
85
|
let SimpleTest = class SimpleTest {
|
|
85
|
-
static
|
|
86
|
+
static Ⲑinit = Ⲑ_root_index_1.RootIndex.registerFunction(SimpleTest, __filename, 1887908328, { test: { hash: 102834457 } }, false, false);
|
|
86
87
|
async test() {
|
|
87
|
-
|
|
88
|
+
Ⲑ_check_1.AssertCheck.check({ file: __filename, line: 10, text: "{ size: 20, address: { state: 'VA' } }", operator: "deepStrictEqual" }, true, { size: 20, address: { state: 'VA' } }, {});
|
|
88
89
|
}
|
|
89
90
|
};
|
|
90
91
|
tslib_1.__decorate([
|
|
91
|
-
(0, test_1.Test)({ lines: { start: 8, end: 11, codeStart: 10 } })
|
|
92
|
+
(0, test_1.Test)({ ident: "@Test()", lines: { start: 8, end: 11, codeStart: 10 } })
|
|
92
93
|
], SimpleTest.prototype, "test", null);
|
|
93
94
|
SimpleTest = tslib_1.__decorate([
|
|
94
|
-
|
|
95
|
-
(0, test_1.Suite)({ lines: {} })
|
|
95
|
+
Ⲑ_decorator_1.Register(),
|
|
96
|
+
(0, test_1.Suite)({ ident: "@Suite()", lines: { start: 5, end: 12 } })
|
|
96
97
|
], SimpleTest);
|
|
97
|
-
Object.defineProperty(exports, 'ᚕtrv', { configurable: true, value: true });
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
This would ultimately produce the error like:
|
|
@@ -128,7 +128,7 @@ In addition to the standard operations, there is support for throwing/rejecting
|
|
|
128
128
|
|
|
129
129
|
**Code: Throws vs Does Not Throw**
|
|
130
130
|
```typescript
|
|
131
|
-
import
|
|
131
|
+
import assert from 'assert';
|
|
132
132
|
|
|
133
133
|
import { Suite, Test } from '@travetto/test';
|
|
134
134
|
|
|
@@ -154,7 +154,7 @@ In addition to the standard operations, there is support for throwing/rejecting
|
|
|
154
154
|
|
|
155
155
|
**Code: Rejects vs Does Not Reject**
|
|
156
156
|
```typescript
|
|
157
|
-
import
|
|
157
|
+
import assert from 'assert';
|
|
158
158
|
|
|
159
159
|
import { Suite, Test } from '@travetto/test';
|
|
160
160
|
|
|
@@ -185,7 +185,7 @@ Additionally, the `throws`/`rejects` assertions take in a secondary parameter to
|
|
|
185
185
|
|
|
186
186
|
**Code: Example of different Error matching paradigms**
|
|
187
187
|
```typescript
|
|
188
|
-
import
|
|
188
|
+
import assert from 'assert';
|
|
189
189
|
|
|
190
190
|
import { Suite, Test } from '@travetto/test';
|
|
191
191
|
|
|
@@ -228,9 +228,10 @@ Usage: test [options] [regexes...]
|
|
|
228
228
|
Options:
|
|
229
229
|
-f, --format <format> Output format for test results (default: "tap")
|
|
230
230
|
-c, --concurrency <concurrency> Number of tests to run concurrently (default: 4)
|
|
231
|
-
-i, --isolated Isolated mode
|
|
232
231
|
-m, --mode <mode> Test run mode (default: "standard")
|
|
233
232
|
-h, --help display help for command
|
|
233
|
+
|
|
234
|
+
[s[r[u
|
|
234
235
|
```
|
|
235
236
|
|
|
236
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,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/test",
|
|
3
|
-
"
|
|
4
|
-
"version": "3.0.0-rc.4",
|
|
3
|
+
"version": "3.0.0-rc.7",
|
|
5
4
|
"description": "Declarative test framework",
|
|
6
5
|
"keywords": [
|
|
7
6
|
"unit-testing",
|
|
@@ -18,32 +17,40 @@
|
|
|
18
17
|
"name": "Travetto Framework"
|
|
19
18
|
},
|
|
20
19
|
"files": [
|
|
21
|
-
"
|
|
20
|
+
"__index__.ts",
|
|
22
21
|
"src",
|
|
23
|
-
"bin",
|
|
24
22
|
"support"
|
|
25
23
|
],
|
|
26
|
-
"main": "
|
|
24
|
+
"main": "__index__.ts",
|
|
27
25
|
"repository": {
|
|
28
26
|
"url": "https://github.com/travetto/travetto.git",
|
|
29
27
|
"directory": "module/test"
|
|
30
28
|
},
|
|
31
29
|
"dependencies": {
|
|
32
|
-
"@travetto/base": "^3.0.0-rc.
|
|
33
|
-
"@travetto/
|
|
34
|
-
"@travetto/
|
|
35
|
-
"@travetto/
|
|
36
|
-
"@travetto/
|
|
37
|
-
"@travetto/yaml": "^3.0.0-rc.2"
|
|
30
|
+
"@travetto/base": "^3.0.0-rc.5",
|
|
31
|
+
"@travetto/registry": "^3.0.0-rc.7",
|
|
32
|
+
"@travetto/terminal": "^3.0.0-rc.4",
|
|
33
|
+
"@travetto/worker": "^3.0.0-rc.5",
|
|
34
|
+
"@travetto/yaml": "^3.0.0-rc.5"
|
|
38
35
|
},
|
|
39
36
|
"peerDependencies": {
|
|
40
|
-
"@travetto/cli": "^3.0.0-rc.
|
|
37
|
+
"@travetto/cli": "^3.0.0-rc.5",
|
|
38
|
+
"@travetto/transformer": "^3.0.0-rc.7"
|
|
41
39
|
},
|
|
42
40
|
"peerDependenciesMeta": {
|
|
41
|
+
"@travetto/transformer": {
|
|
42
|
+
"optional": true
|
|
43
|
+
},
|
|
43
44
|
"@travetto/cli": {
|
|
44
45
|
"optional": true
|
|
45
46
|
}
|
|
46
47
|
},
|
|
48
|
+
"travetto": {
|
|
49
|
+
"displayName": "Testing",
|
|
50
|
+
"profiles": [
|
|
51
|
+
"test"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
47
54
|
"publishConfig": {
|
|
48
55
|
"access": "public"
|
|
49
56
|
}
|
package/src/assert/capture.ts
CHANGED
package/src/assert/check.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import assert from 'assert';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { RootIndex } from '@travetto/manifest';
|
|
4
|
+
import { ObjectUtil, AppError, ClassInstance, Class } from '@travetto/base';
|
|
5
5
|
|
|
6
6
|
import { ThrowableError, TestConfig, Assertion } from '../model/test';
|
|
7
7
|
import { AssertCapture, CaptureAssert } from './capture';
|
|
@@ -30,6 +30,8 @@ export class AssertCheck {
|
|
|
30
30
|
* @param args The arguments passed in
|
|
31
31
|
*/
|
|
32
32
|
static check(assertion: CaptureAssert, positive: boolean, ...args: unknown[]): void {
|
|
33
|
+
assertion.file = RootIndex.getSourceFile(assertion.file);
|
|
34
|
+
|
|
33
35
|
let fn = assertion.operator;
|
|
34
36
|
assertion.operator = ASSERT_FN_OPERATOR[fn];
|
|
35
37
|
|
|
@@ -191,7 +193,7 @@ export class AssertCheck {
|
|
|
191
193
|
shouldThrow: ThrowableError | undefined,
|
|
192
194
|
assertion: CaptureAssert
|
|
193
195
|
): void {
|
|
194
|
-
if (
|
|
196
|
+
if (ObjectUtil.isPrimitive(err)) {
|
|
195
197
|
err = new Error(`${err}`);
|
|
196
198
|
}
|
|
197
199
|
if (!(err instanceof Error)) {
|
|
@@ -225,10 +227,12 @@ export class AssertCheck {
|
|
|
225
227
|
): void {
|
|
226
228
|
let missed: Error | undefined;
|
|
227
229
|
|
|
230
|
+
assertion.file = RootIndex.getSourceFile(assertion.file);
|
|
231
|
+
|
|
228
232
|
try {
|
|
229
233
|
action();
|
|
230
234
|
if (!positive) {
|
|
231
|
-
if (!
|
|
235
|
+
if (!ObjectUtil.isPrimitive(shouldThrow)) {
|
|
232
236
|
shouldThrow = shouldThrow?.name;
|
|
233
237
|
}
|
|
234
238
|
throw (missed = new AppError(`No error thrown, but expected ${shouldThrow ?? 'an error'}`));
|
|
@@ -257,6 +261,8 @@ export class AssertCheck {
|
|
|
257
261
|
): Promise<void> {
|
|
258
262
|
let missed: Error | undefined;
|
|
259
263
|
|
|
264
|
+
assertion.file = RootIndex.getSourceFile(assertion.file);
|
|
265
|
+
|
|
260
266
|
try {
|
|
261
267
|
if ('then' in action) {
|
|
262
268
|
await action;
|
|
@@ -264,7 +270,7 @@ export class AssertCheck {
|
|
|
264
270
|
await action();
|
|
265
271
|
}
|
|
266
272
|
if (!positive) {
|
|
267
|
-
if (!
|
|
273
|
+
if (!ObjectUtil.isPrimitive(shouldThrow)) {
|
|
268
274
|
shouldThrow = shouldThrow?.name;
|
|
269
275
|
}
|
|
270
276
|
throw (missed = new AppError(`No error thrown, but expected ${shouldThrow ?? 'an error'} `));
|
|
@@ -286,7 +292,7 @@ export class AssertCheck {
|
|
|
286
292
|
}
|
|
287
293
|
|
|
288
294
|
AssertCapture.add({
|
|
289
|
-
file: test.file
|
|
295
|
+
file: test.file,
|
|
290
296
|
line,
|
|
291
297
|
operator: 'throws',
|
|
292
298
|
error: err,
|
package/src/assert/util.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import util from 'util';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { Class, ClassInstance,
|
|
3
|
+
import { path, RootIndex } from '@travetto/manifest';
|
|
4
|
+
import { Class, ClassInstance, ObjectUtil } from '@travetto/base';
|
|
5
5
|
|
|
6
6
|
import { TestConfig, Assertion, TestResult } from '../model/test';
|
|
7
7
|
import { SuiteConfig } from '../model/suite';
|
|
@@ -22,14 +22,14 @@ export class AssertUtil {
|
|
|
22
22
|
if (isCleanable(val)) {
|
|
23
23
|
return val.toClean();
|
|
24
24
|
} else if (val === null || val === undefined
|
|
25
|
-
|| (!(val instanceof RegExp) &&
|
|
26
|
-
||
|
|
25
|
+
|| (!(val instanceof RegExp) && ObjectUtil.isPrimitive(val))
|
|
26
|
+
|| ObjectUtil.isPlainObject(val) || Array.isArray(val)
|
|
27
27
|
) {
|
|
28
28
|
return JSON.stringify(val);
|
|
29
29
|
} else {
|
|
30
30
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
31
31
|
const subV = val as (Class | ClassInstance);
|
|
32
|
-
if (subV
|
|
32
|
+
if (subV.Ⲑid || !subV.constructor || (!subV.constructor.Ⲑid && ObjectUtil.isFunction(subV))) { // If a function, show name
|
|
33
33
|
return subV.name;
|
|
34
34
|
} else { // Else inspect
|
|
35
35
|
return util.inspect(val, false, 1).replace(/\n/g, ' ');
|
|
@@ -41,16 +41,16 @@ export class AssertUtil {
|
|
|
41
41
|
* Determine file location for a given error and the stack trace
|
|
42
42
|
*/
|
|
43
43
|
static getPositionOfError(err: Error, filename: string): { file: string, line: number } {
|
|
44
|
-
const
|
|
45
|
-
|
|
44
|
+
const cwd = path.cwd();
|
|
45
|
+
const lines = path.toPosix(err.stack ?? new Error().stack!)
|
|
46
46
|
.split('\n')
|
|
47
47
|
// Exclude node_modules, target self
|
|
48
|
-
.filter(x => x.includes(
|
|
48
|
+
.filter(x => x.includes(cwd) && (!x.includes('node_modules') || x.includes('/support/')));
|
|
49
49
|
|
|
50
50
|
let best = lines.filter(x => x.includes(filename))[0];
|
|
51
51
|
|
|
52
52
|
if (!best) {
|
|
53
|
-
[best] = lines.filter(x => x.includes(`${
|
|
53
|
+
[best] = lines.filter(x => x.includes(`${cwd}/test`));
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
if (!best) {
|
|
@@ -72,7 +72,7 @@ export class AssertUtil {
|
|
|
72
72
|
line = -1;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
const outFileParts = file.split(
|
|
75
|
+
const outFileParts = file.split(cwd.replace(/^[A-Za-z]:/, ''));
|
|
76
76
|
|
|
77
77
|
const outFile = outFileParts.length > 1 ? outFileParts[1].replace(/^[\/]/, '') : filename;
|
|
78
78
|
|
|
@@ -94,7 +94,7 @@ export class AssertUtil {
|
|
|
94
94
|
|
|
95
95
|
const msg = error.message.split(/\n/)[0];
|
|
96
96
|
|
|
97
|
-
const core = { file, classId: suite.classId, methodName };
|
|
97
|
+
const core = { file, classId: suite.classId, methodName, module: RootIndex.manifest.mainModule };
|
|
98
98
|
const coreAll = { ...core, description: msg, lines: { start: line, end: line, codeStart: line } };
|
|
99
99
|
|
|
100
100
|
const assert: Assertion = {
|
package/src/consumer/enhancer.ts
CHANGED
|
@@ -1,26 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { GlobalTerminal } from '@travetto/terminal';
|
|
2
2
|
|
|
3
|
-
export const
|
|
4
|
-
assertDescription:
|
|
5
|
-
testDescription:
|
|
6
|
-
success:
|
|
7
|
-
failure:
|
|
8
|
-
assertNumber:
|
|
9
|
-
testNumber:
|
|
10
|
-
assertFile:
|
|
11
|
-
assertLine:
|
|
12
|
-
objectInspect:
|
|
13
|
-
suiteName:
|
|
14
|
-
testName:
|
|
15
|
-
total:
|
|
16
|
-
};
|
|
3
|
+
export const CONSOLE_ENHANCER = GlobalTerminal.palette({
|
|
4
|
+
assertDescription: 'lightGray',
|
|
5
|
+
testDescription: 'white',
|
|
6
|
+
success: 'green',
|
|
7
|
+
failure: 'red',
|
|
8
|
+
assertNumber: 'brightCyan',
|
|
9
|
+
testNumber: 'dodgerBlue',
|
|
10
|
+
assertFile: 'lightGreen',
|
|
11
|
+
assertLine: 'lightYellow',
|
|
12
|
+
objectInspect: 'magenta',
|
|
13
|
+
suiteName: 'yellow',
|
|
14
|
+
testName: 'cyan',
|
|
15
|
+
total: 'white'
|
|
16
|
+
});
|
|
17
17
|
|
|
18
|
-
export type TestResultsEnhancer = typeof
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Dummy enhancer does nothing
|
|
22
|
-
*/
|
|
23
|
-
export const DUMMY_ENHANCER: TestResultsEnhancer = [
|
|
24
|
-
Object.keys(COLOR_ENHANCER)
|
|
25
|
-
.reduce<Partial<TestResultsEnhancer>>((acc, k) => (acc[k] = (x: unknown): string => `${x}`) && acc, {})
|
|
26
|
-
].filter((x): x is TestResultsEnhancer => !!x)[0];
|
|
18
|
+
export type TestResultsEnhancer = typeof CONSOLE_ENHANCER;
|
package/src/consumer/registry.ts
CHANGED
|
@@ -12,7 +12,7 @@ class $TestConsumerRegistry {
|
|
|
12
12
|
* Manual initialization when running outside of the bootstrap process
|
|
13
13
|
*/
|
|
14
14
|
async manualInit(): Promise<void> {
|
|
15
|
-
await import('./types/
|
|
15
|
+
await import('./types/all.js');
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -40,7 +40,10 @@ class $TestConsumerRegistry {
|
|
|
40
40
|
* Get a consumer instance that supports summarization
|
|
41
41
|
* @param consumer The consumer identifier or the actual consumer
|
|
42
42
|
*/
|
|
43
|
-
getInstance(consumer: string | TestConsumer): TestConsumer {
|
|
43
|
+
async getInstance(consumer: string | TestConsumer): Promise<TestConsumer> {
|
|
44
|
+
// TODO: Fix consumer registry init
|
|
45
|
+
await this.manualInit();
|
|
46
|
+
|
|
44
47
|
return typeof consumer === 'string' ?
|
|
45
48
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
46
49
|
new ((this.get(consumer) ?? this.#primary) as ConcreteClass)() :
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
|
|
1
3
|
import { Class } from '@travetto/base';
|
|
2
4
|
|
|
3
5
|
import { TestConsumer } from '../types';
|
|
@@ -25,16 +27,19 @@ export class CumulativeSummaryConsumer implements TestConsumer {
|
|
|
25
27
|
* state
|
|
26
28
|
*/
|
|
27
29
|
summarizeSuite(test: TestResult): SuiteResult {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
require(test.file);
|
|
30
|
+
// Was only loading to verify existence (TODO: double-check)
|
|
31
|
+
if (fs.existsSync(test.file)) {
|
|
31
32
|
this.#state[test.classId] = this.#state[test.classId] ?? {};
|
|
32
33
|
this.#state[test.classId][test.methodName] = test.status;
|
|
33
34
|
const SuiteCls = SuiteRegistry.getClasses().find(x =>
|
|
34
|
-
x
|
|
35
|
+
x.Ⲑid === test.classId
|
|
35
36
|
)!;
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
if (SuiteCls) {
|
|
38
|
+
return this.computeTotal(SuiteCls);
|
|
39
|
+
} else {
|
|
40
|
+
return this.removeClass(test.classId);
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
38
43
|
return this.removeClass(test.classId);
|
|
39
44
|
}
|
|
40
45
|
}
|
|
@@ -10,8 +10,8 @@ export class RunnableTestConsumer implements TestConsumer {
|
|
|
10
10
|
/**
|
|
11
11
|
* Build a runnable test consumer given a format or a full consumer
|
|
12
12
|
*/
|
|
13
|
-
static get(consumer: string | TestConsumer): RunnableTestConsumer {
|
|
14
|
-
return new RunnableTestConsumer(TestConsumerRegistry.getInstance(consumer));
|
|
13
|
+
static async get(consumer: string | TestConsumer): Promise<RunnableTestConsumer> {
|
|
14
|
+
return new RunnableTestConsumer(await TestConsumerRegistry.getInstance(consumer));
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
#consumers: TestConsumer[];
|
|
@@ -27,11 +27,9 @@ export class RunnableTestConsumer implements TestConsumer {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
onStart(): void {
|
|
30
|
+
async onStart(files: string[]): Promise<void> {
|
|
31
31
|
for (const c of this.#consumers) {
|
|
32
|
-
|
|
33
|
-
c.onStart();
|
|
34
|
-
}
|
|
32
|
+
await c.onStart?.(files);
|
|
35
33
|
}
|
|
36
34
|
}
|
|
37
35
|
|
|
@@ -44,19 +42,17 @@ export class RunnableTestConsumer implements TestConsumer {
|
|
|
44
42
|
}
|
|
45
43
|
}
|
|
46
44
|
|
|
47
|
-
summarize(): TestResultsSummarizer | undefined {
|
|
45
|
+
async summarize(): Promise<TestResultsSummarizer | undefined> {
|
|
48
46
|
if (this.#results) {
|
|
49
47
|
for (const c of this.#consumers) {
|
|
50
|
-
|
|
51
|
-
c.onSummary(this.#results.summary);
|
|
52
|
-
}
|
|
48
|
+
await c.onSummary?.(this.#results.summary);
|
|
53
49
|
}
|
|
54
50
|
return this.#results;
|
|
55
51
|
}
|
|
56
52
|
}
|
|
57
53
|
|
|
58
|
-
summarizeAsBoolean(): boolean {
|
|
59
|
-
const result = this.summarize();
|
|
54
|
+
async summarizeAsBoolean(): Promise<boolean> {
|
|
55
|
+
const result = await this.summarize();
|
|
60
56
|
if (result) {
|
|
61
57
|
return result.summary.failed <= 0;
|
|
62
58
|
} else {
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { GlobalTerminal, TermStyleInput, Terminal } from '@travetto/terminal';
|
|
2
|
+
import { ManualAsyncIterator } from '@travetto/worker';
|
|
3
|
+
import { RootIndex } from '@travetto/manifest';
|
|
4
|
+
|
|
5
|
+
import { SuitesSummary, TestConsumer } from '../types';
|
|
6
|
+
import { Consumable } from '../registry';
|
|
7
|
+
|
|
8
|
+
import { TestEvent } from '../../model/event';
|
|
9
|
+
import { TestResult } from '../../model/test';
|
|
10
|
+
import { SuiteRegistry } from '../../registry/suite';
|
|
11
|
+
import { TapEmitter } from './tap';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Streamed summary results
|
|
15
|
+
*/
|
|
16
|
+
@Consumable('tap-streamed')
|
|
17
|
+
export class TapStreamedEmitter implements TestConsumer {
|
|
18
|
+
|
|
19
|
+
static makeProgressBar(term: Terminal, total: number): (t: TestResult, idx: number) => string {
|
|
20
|
+
let failed = 0;
|
|
21
|
+
const palette: TermStyleInput[] = [
|
|
22
|
+
{ text: 'white', background: 'darkGreen' },
|
|
23
|
+
{ text: 'white', background: 'darkRed' }
|
|
24
|
+
];
|
|
25
|
+
const styles = palette.map(s => GlobalTerminal.colorer(s));
|
|
26
|
+
|
|
27
|
+
return (t: TestResult, idx: number): string => {
|
|
28
|
+
if (t.status === 'failed') {
|
|
29
|
+
failed += 1;
|
|
30
|
+
}
|
|
31
|
+
const i = idx + 1;
|
|
32
|
+
const digits = total.toString().length;
|
|
33
|
+
const paddedI = `${i}`.padStart(digits);
|
|
34
|
+
const paddedFailed = `${failed}`.padStart(digits);
|
|
35
|
+
const line = `Tests ${paddedI}/${total} [${paddedFailed} failed] -- ${t.classId}`.padEnd(term.width);
|
|
36
|
+
const pos = Math.trunc(line.length * (i / total));
|
|
37
|
+
const colorer = styles[Math.min(failed, styles.length - 1)];
|
|
38
|
+
return `${colorer(line.substring(0, pos))}${line.substring(pos)}`;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#terminal: Terminal;
|
|
43
|
+
#results = new ManualAsyncIterator<TestResult>();
|
|
44
|
+
#progress: Promise<unknown> | undefined;
|
|
45
|
+
#consumer: TapEmitter;
|
|
46
|
+
|
|
47
|
+
constructor(terminal: Terminal = new Terminal({ output: process.stderr })) {
|
|
48
|
+
this.#terminal = terminal;
|
|
49
|
+
this.#consumer = new TapEmitter(this.#terminal);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async onStart(files: string[]): Promise<void> {
|
|
53
|
+
this.#consumer.onStart();
|
|
54
|
+
|
|
55
|
+
// Load all tests
|
|
56
|
+
for (const file of files) {
|
|
57
|
+
await import(RootIndex.getFromSource(file)!.import);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await SuiteRegistry.init();
|
|
61
|
+
|
|
62
|
+
const suites = SuiteRegistry.getClasses();
|
|
63
|
+
const total = suites
|
|
64
|
+
.map(c => SuiteRegistry.get(c))
|
|
65
|
+
.filter(c => !RootIndex.getFunctionMetadata(c.class)?.abstract)
|
|
66
|
+
.reduce((acc, c) => acc + (c.tests?.length ?? 0), 0);
|
|
67
|
+
|
|
68
|
+
this.#progress = this.#terminal.streamToPosition(this.#results,
|
|
69
|
+
TapStreamedEmitter.makeProgressBar(this.#terminal, total),
|
|
70
|
+
{ position: 'bottom' }
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
onEvent(ev: TestEvent): void {
|
|
75
|
+
if (ev.type === 'test' && ev.phase === 'after') {
|
|
76
|
+
const { test } = ev;
|
|
77
|
+
this.#results.add(test);
|
|
78
|
+
if (test.status === 'failed') {
|
|
79
|
+
this.#consumer.onEvent(ev);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Summarize all results
|
|
86
|
+
*/
|
|
87
|
+
async onSummary(summary: SuitesSummary): Promise<void> {
|
|
88
|
+
this.#results.close();
|
|
89
|
+
await this.#progress;
|
|
90
|
+
await this.#consumer.onSummary?.(summary);
|
|
91
|
+
}
|
|
92
|
+
}
|