@travetto/test 3.4.3 → 4.0.0-rc.1
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 +8 -8
- package/__index__.ts +1 -0
- package/package.json +8 -8
- package/src/assert/capture.ts +1 -1
- package/src/assert/check.ts +21 -9
- package/src/assert/util.ts +4 -4
- package/src/consumer/enhancer.ts +14 -14
- package/src/consumer/error.ts +2 -2
- package/src/consumer/types/cumulative.ts +1 -1
- package/src/consumer/types/event.ts +1 -1
- package/src/consumer/types/json.ts +1 -1
- package/src/consumer/types/tap-streamed.ts +18 -30
- package/src/consumer/types/tap.ts +6 -6
- package/src/consumer/types/xunit.ts +1 -1
- package/src/decorator/suite.ts +2 -2
- package/src/decorator/test.ts +2 -2
- package/src/execute/console.ts +1 -1
- package/src/execute/executor.ts +19 -12
- package/src/execute/phase.ts +2 -2
- package/src/execute/runner.ts +10 -10
- package/src/execute/util.ts +16 -10
- package/src/execute/watcher.ts +23 -13
- package/src/registry/suite.ts +8 -8
- package/src/trv.d.ts +24 -0
- package/src/worker/child.ts +8 -6
- package/src/worker/standard.ts +16 -11
- package/support/bin/run.ts +6 -15
- package/support/bin/types.ts +1 -1
- package/support/cli.test.ts +8 -6
- package/support/cli.test_child.ts +9 -8
- package/support/cli.test_count.ts +5 -4
- package/support/cli.test_direct.ts +5 -2
- package/support/cli.test_watch.ts +4 -6
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ A simple example would be:
|
|
|
27
27
|
|
|
28
28
|
**Code: Example Test Suite**
|
|
29
29
|
```typescript
|
|
30
|
-
import assert from 'assert';
|
|
30
|
+
import assert from 'node:assert';
|
|
31
31
|
|
|
32
32
|
import { Suite, Test } from '@travetto/test';
|
|
33
33
|
|
|
@@ -58,7 +58,7 @@ A common aspect of the tests themselves are the assertions that are made. [Node
|
|
|
58
58
|
|
|
59
59
|
**Code: Example assertion for deep comparison**
|
|
60
60
|
```typescript
|
|
61
|
-
import assert from 'assert';
|
|
61
|
+
import assert from 'node:assert';
|
|
62
62
|
|
|
63
63
|
import { Suite, Test } from '@travetto/test';
|
|
64
64
|
|
|
@@ -81,13 +81,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
81
81
|
const tslib_1 = require("tslib");
|
|
82
82
|
const Ⲑ_util_1 = tslib_1.__importStar(require("@travetto/test/src/execute/util.js"));
|
|
83
83
|
const Ⲑ_check_1 = tslib_1.__importStar(require("@travetto/test/src/assert/check.js"));
|
|
84
|
-
const Ⲑ
|
|
84
|
+
const Ⲑ_runtime_1 = tslib_1.__importStar(require("@travetto/manifest/src/runtime.js"));
|
|
85
85
|
const Ⲑ_decorator_1 = tslib_1.__importStar(require("@travetto/registry/src/decorator.js"));
|
|
86
86
|
var ᚕf = "@travetto/test/doc/assert-example.js";
|
|
87
|
-
const
|
|
87
|
+
const node_assert_1 = tslib_1.__importDefault(require("node:assert"));
|
|
88
88
|
const test_1 = require("@travetto/test");
|
|
89
89
|
let SimpleTest = class SimpleTest {
|
|
90
|
-
static Ⲑinit = Ⲑ
|
|
90
|
+
static Ⲑinit = Ⲑ_runtime_1.RuntimeIndex.registerFunction(SimpleTest, ᚕf, 1887908328, { test: { hash: 102834457 } }, false, false);
|
|
91
91
|
async test() {
|
|
92
92
|
if (Ⲑ_util_1.RunnerUtil.tryDebugger)
|
|
93
93
|
debugger;
|
|
@@ -131,7 +131,7 @@ In addition to the standard operations, there is support for throwing/rejecting
|
|
|
131
131
|
|
|
132
132
|
**Code: Throws vs Does Not Throw**
|
|
133
133
|
```typescript
|
|
134
|
-
import assert from 'assert';
|
|
134
|
+
import assert from 'node:assert';
|
|
135
135
|
|
|
136
136
|
import { Suite, Test } from '@travetto/test';
|
|
137
137
|
|
|
@@ -157,7 +157,7 @@ class SimpleTest {
|
|
|
157
157
|
|
|
158
158
|
**Code: Rejects vs Does Not Reject**
|
|
159
159
|
```typescript
|
|
160
|
-
import assert from 'assert';
|
|
160
|
+
import assert from 'node:assert';
|
|
161
161
|
|
|
162
162
|
import { Suite, Test } from '@travetto/test';
|
|
163
163
|
|
|
@@ -186,7 +186,7 @@ Additionally, the `throws`/`rejects` assertions take in a secondary parameter to
|
|
|
186
186
|
|
|
187
187
|
**Code: Example of different Error matching paradigms**
|
|
188
188
|
```typescript
|
|
189
|
-
import assert from 'assert';
|
|
189
|
+
import assert from 'node:assert';
|
|
190
190
|
|
|
191
191
|
import { Suite, Test } from '@travetto/test';
|
|
192
192
|
|
package/__index__.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/test",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-rc.1",
|
|
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/base": "^
|
|
31
|
-
"@travetto/registry": "^
|
|
32
|
-
"@travetto/terminal": "^
|
|
33
|
-
"@travetto/worker": "^
|
|
34
|
-
"@travetto/yaml": "^
|
|
30
|
+
"@travetto/base": "^4.0.0-rc.1",
|
|
31
|
+
"@travetto/registry": "^4.0.0-rc.1",
|
|
32
|
+
"@travetto/terminal": "^4.0.0-rc.1",
|
|
33
|
+
"@travetto/worker": "^4.0.0-rc.1",
|
|
34
|
+
"@travetto/yaml": "^4.0.0-rc.1"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@travetto/cli": "^
|
|
38
|
-
"@travetto/transformer": "^
|
|
37
|
+
"@travetto/cli": "^4.0.0-rc.1",
|
|
38
|
+
"@travetto/transformer": "^4.0.0-rc.1"
|
|
39
39
|
},
|
|
40
40
|
"peerDependenciesMeta": {
|
|
41
41
|
"@travetto/transformer": {
|
package/src/assert/capture.ts
CHANGED
package/src/assert/check.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import assert from 'assert';
|
|
1
|
+
import assert from 'node:assert';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { RuntimeIndex } from '@travetto/manifest';
|
|
4
4
|
import { ObjectUtil, AppError, ClassInstance, Class } from '@travetto/base';
|
|
5
5
|
|
|
6
6
|
import { ThrowableError, TestConfig, Assertion } from '../model/test';
|
|
@@ -30,7 +30,7 @@ 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 =
|
|
33
|
+
assertion.file = RuntimeIndex.getSourceFile(assertion.file);
|
|
34
34
|
|
|
35
35
|
let fn = assertion.operator;
|
|
36
36
|
assertion.operator = ASSERT_FN_OPERATOR[fn];
|
|
@@ -115,7 +115,7 @@ export class AssertCheck {
|
|
|
115
115
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
116
116
|
case 'greaterThanEqual': assertFn((actual as number) >= (expected as number), message); break;
|
|
117
117
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
118
|
-
case 'ok': assertFn
|
|
118
|
+
case 'ok': assertFn(...args as [unknown, string]); break; // eslint-disable-line prefer-spread
|
|
119
119
|
default:
|
|
120
120
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
121
121
|
if (fn && assert[fn as keyof typeof assert]) { // Assert call
|
|
@@ -161,11 +161,19 @@ export class AssertCheck {
|
|
|
161
161
|
|
|
162
162
|
// If a string, check if error exists, and then see if the string is included in the message
|
|
163
163
|
if (typeof shouldThrow === 'string' && (!err || !(err instanceof Error ? err.message : err).includes(shouldThrow))) {
|
|
164
|
-
return new assert.AssertionError({
|
|
164
|
+
return new assert.AssertionError({
|
|
165
|
+
message: `Expected error containing text '${shouldThrow}', but got ${actual}`,
|
|
166
|
+
actual,
|
|
167
|
+
expected: shouldThrow
|
|
168
|
+
});
|
|
165
169
|
}
|
|
166
170
|
// If a regexp, check if error exists, and then test the error message against the regex
|
|
167
171
|
if (shouldThrow instanceof RegExp && (!err || !shouldThrow.test(typeof err === 'string' ? err : err.message))) {
|
|
168
|
-
return new assert.AssertionError({
|
|
172
|
+
return new assert.AssertionError({
|
|
173
|
+
message: `Expected error with message matching '${shouldThrow.source}', but got ${actual}`,
|
|
174
|
+
actual,
|
|
175
|
+
expected: shouldThrow.source
|
|
176
|
+
});
|
|
169
177
|
}
|
|
170
178
|
// If passing in a constructor
|
|
171
179
|
} else if (shouldThrow === Error ||
|
|
@@ -173,7 +181,11 @@ export class AssertCheck {
|
|
|
173
181
|
Object.getPrototypeOf(shouldThrow) !== Object.getPrototypeOf(Function)
|
|
174
182
|
) { // if not simple function, treat as class
|
|
175
183
|
if (!err || !(err instanceof shouldThrow)) {
|
|
176
|
-
return new assert.AssertionError({
|
|
184
|
+
return new assert.AssertionError({
|
|
185
|
+
message: `Expected to throw ${shouldThrow.name}, but got ${err ?? 'nothing'}`,
|
|
186
|
+
actual: (err ?? 'nothing'),
|
|
187
|
+
expected: shouldThrow.name
|
|
188
|
+
});
|
|
177
189
|
}
|
|
178
190
|
} else {
|
|
179
191
|
// Else treat as a simple function to build an error or not
|
|
@@ -227,7 +239,7 @@ export class AssertCheck {
|
|
|
227
239
|
): void {
|
|
228
240
|
let missed: Error | undefined;
|
|
229
241
|
|
|
230
|
-
assertion.file =
|
|
242
|
+
assertion.file = RuntimeIndex.getSourceFile(assertion.file);
|
|
231
243
|
|
|
232
244
|
try {
|
|
233
245
|
action();
|
|
@@ -261,7 +273,7 @@ export class AssertCheck {
|
|
|
261
273
|
): Promise<void> {
|
|
262
274
|
let missed: Error | undefined;
|
|
263
275
|
|
|
264
|
-
assertion.file =
|
|
276
|
+
assertion.file = RuntimeIndex.getSourceFile(assertion.file);
|
|
265
277
|
|
|
266
278
|
try {
|
|
267
279
|
if ('then' in action) {
|
package/src/assert/util.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import util from 'util';
|
|
1
|
+
import util from 'node:util';
|
|
2
2
|
|
|
3
|
-
import { path,
|
|
3
|
+
import { path, RuntimeIndex, RuntimeContext } from '@travetto/manifest';
|
|
4
4
|
import { Class, ClassInstance, ObjectUtil } from '@travetto/base';
|
|
5
5
|
|
|
6
6
|
import { TestConfig, Assertion, TestResult } from '../model/test';
|
|
@@ -41,7 +41,7 @@ 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 cwd =
|
|
44
|
+
const cwd = RuntimeIndex.mainModule.sourcePath;
|
|
45
45
|
const lines = path.toPosix(err.stack ?? new Error().stack!)
|
|
46
46
|
.split('\n')
|
|
47
47
|
// Exclude node_modules, target self
|
|
@@ -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, module:
|
|
97
|
+
const core = { file, classId: suite.classId, methodName, module: RuntimeContext.main.name };
|
|
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,18 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { StyleUtil } from '@travetto/terminal';
|
|
2
2
|
|
|
3
|
-
export const CONSOLE_ENHANCER =
|
|
4
|
-
assertDescription: '
|
|
5
|
-
testDescription: '
|
|
6
|
-
success: '
|
|
7
|
-
failure: '
|
|
8
|
-
assertNumber: '
|
|
9
|
-
testNumber: '
|
|
10
|
-
assertFile: '
|
|
11
|
-
assertLine: '
|
|
12
|
-
objectInspect: '
|
|
13
|
-
suiteName: '
|
|
14
|
-
testName: '
|
|
15
|
-
total: '
|
|
3
|
+
export const CONSOLE_ENHANCER = StyleUtil.getPalette({
|
|
4
|
+
assertDescription: '#d3d3d3', // light gray
|
|
5
|
+
testDescription: '#e5e5e5', // White
|
|
6
|
+
success: '#00cd00', // Green
|
|
7
|
+
failure: '#cd0000', // Red
|
|
8
|
+
assertNumber: '#00ffff', // Bright cyan
|
|
9
|
+
testNumber: '#1e90ff', // dodger blue
|
|
10
|
+
assertFile: '#90e90', // lightGreen
|
|
11
|
+
assertLine: '#ffffe0', // light yellow
|
|
12
|
+
objectInspect: '#cd00cd', // Magenta
|
|
13
|
+
suiteName: '#cdcd00', // Yellow
|
|
14
|
+
testName: '#00cdcd', // Cyan
|
|
15
|
+
total: '#e5e5e5', // White
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
export type TestResultsEnhancer = typeof CONSOLE_ENHANCER;
|
package/src/consumer/error.ts
CHANGED
|
@@ -28,8 +28,8 @@ export class ErrorUtil {
|
|
|
28
28
|
if (ObjectUtil.hasToJSON(e)) {
|
|
29
29
|
Object.assign(error, e.toJSON());
|
|
30
30
|
}
|
|
31
|
-
error.message
|
|
32
|
-
error.stack ??= e.stack;
|
|
31
|
+
error.message ||= e.message;
|
|
32
|
+
error.stack ??= e.stack?.replace(/.*\[ERR_ASSERTION\]:\s*/, '');
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
return error;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Util } from '@travetto/base';
|
|
2
|
+
import { StyleUtil, Terminal, TerminalUtil } from '@travetto/terminal';
|
|
3
|
+
import { WorkQueue } from '@travetto/worker';
|
|
3
4
|
|
|
4
5
|
import { TestEvent } from '../../model/event';
|
|
5
6
|
import { TestResult } from '../../model/test';
|
|
@@ -15,35 +16,12 @@ import { TapEmitter } from './tap';
|
|
|
15
16
|
@Consumable('tap-streamed')
|
|
16
17
|
export class TapStreamedEmitter implements TestConsumer {
|
|
17
18
|
|
|
18
|
-
static makeProgressBar(term: Terminal, total: number): (t: TestResult, idx: number) => string {
|
|
19
|
-
let failed = 0;
|
|
20
|
-
const palette: TermStyleInput[] = [
|
|
21
|
-
{ text: 'white', background: 'darkGreen' },
|
|
22
|
-
{ text: 'white', background: 'darkRed' }
|
|
23
|
-
];
|
|
24
|
-
const styles = palette.map(s => GlobalTerminal.colorer(s));
|
|
25
|
-
|
|
26
|
-
return (t: TestResult, idx: number): string => {
|
|
27
|
-
if (t.status === 'failed') {
|
|
28
|
-
failed += 1;
|
|
29
|
-
}
|
|
30
|
-
const i = idx + 1;
|
|
31
|
-
const digits = total.toString().length;
|
|
32
|
-
const paddedI = `${i}`.padStart(digits);
|
|
33
|
-
const paddedFailed = `${failed}`.padStart(digits);
|
|
34
|
-
const line = `Tests ${paddedI}/${total} [${paddedFailed} failed] -- ${t.classId}`.padEnd(term.width);
|
|
35
|
-
const pos = Math.trunc(line.length * (i / total));
|
|
36
|
-
const colorer = styles[Math.min(failed, styles.length - 1)];
|
|
37
|
-
return `${colorer(line.substring(0, pos))}${line.substring(pos)}`;
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
19
|
#terminal: Terminal;
|
|
42
|
-
#results = new
|
|
20
|
+
#results = new WorkQueue<TestResult>();
|
|
43
21
|
#progress: Promise<unknown> | undefined;
|
|
44
22
|
#consumer: TapEmitter;
|
|
45
23
|
|
|
46
|
-
constructor(terminal: Terminal = new Terminal(
|
|
24
|
+
constructor(terminal: Terminal = new Terminal(process.stderr)) {
|
|
47
25
|
this.#terminal = terminal;
|
|
48
26
|
this.#consumer = new TapEmitter(this.#terminal);
|
|
49
27
|
}
|
|
@@ -51,9 +29,19 @@ export class TapStreamedEmitter implements TestConsumer {
|
|
|
51
29
|
async onStart(state: TestRunState): Promise<void> {
|
|
52
30
|
this.#consumer.onStart();
|
|
53
31
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
32
|
+
let failed = 0;
|
|
33
|
+
const succ = StyleUtil.getStyle({ text: '#e5e5e5', background: '#026020' }); // White on dark green
|
|
34
|
+
const fail = StyleUtil.getStyle({ text: '#e5e5e5', background: '#8b0000' }); // White on dark red
|
|
35
|
+
this.#progress = this.#terminal.streamToBottom(
|
|
36
|
+
Util.mapAsyncItr(
|
|
37
|
+
this.#results,
|
|
38
|
+
(value, idx) => {
|
|
39
|
+
failed += (value.status === 'failed' ? 1 : 0);
|
|
40
|
+
return { value: `Tests %idx/%total [${failed} failed] -- ${value.classId}`, total: state.testCount, idx };
|
|
41
|
+
},
|
|
42
|
+
TerminalUtil.progressBarUpdater(this.#terminal, { style: () => ({ complete: failed ? fail : succ }) })
|
|
43
|
+
),
|
|
44
|
+
{ minDelay: 100 }
|
|
57
45
|
);
|
|
58
46
|
}
|
|
59
47
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { RuntimeIndex } from '@travetto/manifest';
|
|
2
|
+
import { Terminal } from '@travetto/terminal';
|
|
3
3
|
import { ObjectUtil, TimeUtil } from '@travetto/base';
|
|
4
4
|
import { YamlUtil } from '@travetto/yaml';
|
|
5
5
|
|
|
@@ -20,7 +20,7 @@ export class TapEmitter implements TestConsumer {
|
|
|
20
20
|
#start: number;
|
|
21
21
|
|
|
22
22
|
constructor(
|
|
23
|
-
terminal = new Terminal(
|
|
23
|
+
terminal = new Terminal(),
|
|
24
24
|
enhancer: TestResultsEnhancer = CONSOLE_ENHANCER
|
|
25
25
|
) {
|
|
26
26
|
this.#terminal = terminal;
|
|
@@ -28,7 +28,7 @@ export class TapEmitter implements TestConsumer {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
log(message: string): void {
|
|
31
|
-
this.#terminal.
|
|
31
|
+
this.#terminal.writer.writeLine(message).commit();
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
@@ -43,7 +43,7 @@ export class TapEmitter implements TestConsumer {
|
|
|
43
43
|
* Output supplemental data (e.g. logs)
|
|
44
44
|
*/
|
|
45
45
|
logMeta(obj: Record<string, unknown>): void {
|
|
46
|
-
const lineLength =
|
|
46
|
+
const lineLength = this.#terminal.width - 5;
|
|
47
47
|
let body = YamlUtil.serialize(obj, { wordwrap: lineLength });
|
|
48
48
|
body = body.split('\n').map(x => ` ${x}`).join('\n');
|
|
49
49
|
this.log(`---\n${this.#enhancer.objectInspect(body)}\n...`);
|
|
@@ -71,7 +71,7 @@ export class TapEmitter implements TestConsumer {
|
|
|
71
71
|
this.#enhancer.assertNumber(++subCount),
|
|
72
72
|
'-',
|
|
73
73
|
this.#enhancer.assertDescription(text),
|
|
74
|
-
`${this.#enhancer.assertFile(asrt.file.replace(
|
|
74
|
+
`${this.#enhancer.assertFile(asrt.file.replace(RuntimeIndex.mainModule.sourcePath, '.'))}:${this.#enhancer.assertLine(asrt.line)}`
|
|
75
75
|
].join(' ');
|
|
76
76
|
|
|
77
77
|
if (asrt.error) {
|
package/src/decorator/suite.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RuntimeIndex } from '@travetto/manifest';
|
|
2
2
|
import { Class, ClassInstance } from '@travetto/base';
|
|
3
3
|
|
|
4
4
|
import { SuiteRegistry } from '../registry/suite';
|
|
@@ -27,7 +27,7 @@ export function Suite(description?: string | Partial<SuiteConfig>, ...rest: Part
|
|
|
27
27
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
28
28
|
const decorator = ((target: Class) => {
|
|
29
29
|
const cfg = { description: descriptionString, ...extra };
|
|
30
|
-
if (
|
|
30
|
+
if (RuntimeIndex.getFunctionMetadata(target)?.abstract) {
|
|
31
31
|
cfg.skip = true;
|
|
32
32
|
}
|
|
33
33
|
SuiteRegistry.register(target, cfg);
|
package/src/decorator/test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ClassInstance } from '@travetto/base';
|
|
2
|
-
import {
|
|
2
|
+
import { RuntimeIndex } from '@travetto/manifest';
|
|
3
3
|
|
|
4
4
|
import { SuiteRegistry } from '../registry/suite';
|
|
5
5
|
import { TestConfig } from '../model/test';
|
|
@@ -32,7 +32,7 @@ export function Test(description?: string | Partial<TestConfig>, ...rest: Partia
|
|
|
32
32
|
return (inst: ClassInstance, prop: string | symbol, descriptor: PropertyDescriptor) => {
|
|
33
33
|
SuiteRegistry.registerField(inst.constructor, descriptor.value, {
|
|
34
34
|
...extra,
|
|
35
|
-
file:
|
|
35
|
+
file: RuntimeIndex.getFunctionMetadata(inst.constructor)!.source,
|
|
36
36
|
description: descriptionString
|
|
37
37
|
});
|
|
38
38
|
return descriptor;
|
package/src/execute/console.ts
CHANGED
package/src/execute/executor.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import timers from 'timers/promises';
|
|
1
|
+
import timers from 'node:timers/promises';
|
|
2
|
+
import { AssertionError } from 'node:assert';
|
|
2
3
|
|
|
3
|
-
import { path,
|
|
4
|
-
import {
|
|
4
|
+
import { path, RuntimeIndex, RuntimeContext } from '@travetto/manifest';
|
|
5
|
+
import { Env, Util } from '@travetto/base';
|
|
5
6
|
import { Barrier, ExecutionError } from '@travetto/worker';
|
|
6
7
|
|
|
7
8
|
import { SuiteRegistry } from '../registry/suite';
|
|
@@ -15,7 +16,7 @@ import { TestPhaseManager } from './phase';
|
|
|
15
16
|
import { PromiseCapture } from './promise';
|
|
16
17
|
import { AssertUtil } from '../assert/util';
|
|
17
18
|
|
|
18
|
-
const TEST_TIMEOUT =
|
|
19
|
+
const TEST_TIMEOUT = Env.TRV_TEST_TIMEOUT.time ?? 5000;
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Support execution of the tests
|
|
@@ -68,10 +69,10 @@ export class TestExecutor {
|
|
|
68
69
|
*/
|
|
69
70
|
static failFile(consumer: TestConsumer, file: string, err: Error): void {
|
|
70
71
|
const name = path.basename(file);
|
|
71
|
-
const classId =
|
|
72
|
+
const classId = RuntimeIndex.getId(file, name);
|
|
72
73
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
73
74
|
const suite = { class: { name }, classId, duration: 0, lines: { start: 1, end: 1 }, file, } as SuiteConfig & SuiteResult;
|
|
74
|
-
err.message = err.message.replaceAll(
|
|
75
|
+
err.message = err.message.replaceAll(RuntimeIndex.mainModule.sourcePath, '.');
|
|
75
76
|
const res = AssertUtil.generateSuiteError(suite, 'require', err);
|
|
76
77
|
consumer.onEvent({ type: 'suite', phase: 'before', suite });
|
|
77
78
|
consumer.onEvent({ type: 'test', phase: 'before', test: res.testConfig });
|
|
@@ -109,7 +110,7 @@ export class TestExecutor {
|
|
|
109
110
|
|
|
110
111
|
const result: TestResult = {
|
|
111
112
|
methodName: test.methodName,
|
|
112
|
-
module:
|
|
113
|
+
module: RuntimeContext.main.name,
|
|
113
114
|
description: test.description,
|
|
114
115
|
classId: test.classId,
|
|
115
116
|
lines: { ...test.lines },
|
|
@@ -139,11 +140,17 @@ export class TestExecutor {
|
|
|
139
140
|
// Run method and get result
|
|
140
141
|
let error = await this.#executeTestMethod(test);
|
|
141
142
|
|
|
142
|
-
if (error) {
|
|
143
|
-
|
|
143
|
+
if (!error) {
|
|
144
|
+
error = AssertCheck.checkError(test.shouldThrow, error); // Rewrite error
|
|
145
|
+
} else {
|
|
146
|
+
if (error instanceof AssertionError) {
|
|
147
|
+
// Pass, do nothing
|
|
148
|
+
} else if (error instanceof ExecutionError) { // Errors that are not expected
|
|
149
|
+
AssertCheck.checkUnhandled(test, error);
|
|
150
|
+
} else if (test.shouldThrow) {
|
|
151
|
+
error = AssertCheck.checkError(test.shouldThrow, error); // Rewrite error
|
|
152
|
+
} else if (error instanceof Error) {
|
|
144
153
|
AssertCheck.checkUnhandled(test, error);
|
|
145
|
-
} else if (test.shouldThrow) { // Errors that are
|
|
146
|
-
error = AssertCheck.checkError(test.shouldThrow!, error); // Rewrite error
|
|
147
154
|
}
|
|
148
155
|
}
|
|
149
156
|
|
|
@@ -254,7 +261,7 @@ export class TestExecutor {
|
|
|
254
261
|
|
|
255
262
|
file = path.resolve(file);
|
|
256
263
|
|
|
257
|
-
const entry =
|
|
264
|
+
const entry = RuntimeIndex.getEntry(file)!;
|
|
258
265
|
|
|
259
266
|
try {
|
|
260
267
|
await import(entry.import);
|
package/src/execute/phase.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Barrier } from '@travetto/worker';
|
|
2
|
-
import {
|
|
2
|
+
import { Env } from '@travetto/base';
|
|
3
3
|
|
|
4
4
|
import { TestConsumer } from '../consumer/types';
|
|
5
5
|
import { SuiteConfig, SuiteResult } from '../model/suite';
|
|
@@ -8,7 +8,7 @@ import { TestResult } from '../model/test';
|
|
|
8
8
|
|
|
9
9
|
class TestBreakout extends Error { }
|
|
10
10
|
|
|
11
|
-
const TEST_PHASE_TIMEOUT =
|
|
11
|
+
const TEST_PHASE_TIMEOUT = Env.TRV_TEST_PHASE_TIMEOUT.time ?? 15000;
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Test Phase Execution Manager.
|
package/src/execute/runner.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { path } from '@travetto/manifest';
|
|
2
2
|
import { TimeUtil } from '@travetto/base';
|
|
3
|
-
import { WorkPool
|
|
3
|
+
import { WorkPool } from '@travetto/worker';
|
|
4
4
|
|
|
5
5
|
import { buildStandardTestManager } from '../worker/standard';
|
|
6
6
|
import { RunnableTestConsumer } from '../consumer/types/runnable';
|
|
@@ -34,17 +34,17 @@ export class Runner {
|
|
|
34
34
|
|
|
35
35
|
console.debug('Running', { files, patterns: this.patterns });
|
|
36
36
|
|
|
37
|
-
const manager = buildStandardTestManager;
|
|
38
|
-
|
|
39
|
-
const pool = new WorkPool(manager(consumer), {
|
|
40
|
-
idleTimeoutMillis: TimeUtil.timeToMs('10s'),
|
|
41
|
-
min: 1,
|
|
42
|
-
max: this.#state.concurrency
|
|
43
|
-
});
|
|
44
|
-
|
|
45
37
|
const testCount = await RunnerUtil.getTestCount(this.#state.args);
|
|
46
38
|
await consumer.onStart({ testCount });
|
|
47
|
-
await
|
|
39
|
+
await WorkPool.run(
|
|
40
|
+
() => buildStandardTestManager(consumer),
|
|
41
|
+
files,
|
|
42
|
+
{
|
|
43
|
+
idleTimeoutMillis: TimeUtil.timeToMs('10s'),
|
|
44
|
+
min: 1,
|
|
45
|
+
max: this.#state.concurrency,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
48
|
return consumer.summarizeAsBoolean();
|
|
49
49
|
}
|
|
50
50
|
|
package/src/execute/util.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { createReadStream } from 'node:fs';
|
|
3
|
+
import readline from 'node:readline';
|
|
4
|
+
import timers from 'node:timers/promises';
|
|
3
5
|
|
|
4
|
-
import { ExecUtil, ShutdownManager
|
|
5
|
-
import { IndexedFile,
|
|
6
|
+
import { Env, ExecUtil, ShutdownManager } from '@travetto/base';
|
|
7
|
+
import { IndexedFile, RuntimeIndex } from '@travetto/manifest';
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Simple Test Utilities
|
|
@@ -12,7 +14,7 @@ export class RunnerUtil {
|
|
|
12
14
|
* Add 50 ms to the shutdown to allow for buffers to output properly
|
|
13
15
|
*/
|
|
14
16
|
static registerCleanup(scope: string): void {
|
|
15
|
-
ShutdownManager.
|
|
17
|
+
ShutdownManager.onGracefulShutdown(() => timers.setTimeout(50), `test.${scope}.bufferOutput`);
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
/**
|
|
@@ -37,12 +39,12 @@ export class RunnerUtil {
|
|
|
37
39
|
* Find all valid test files given the globs
|
|
38
40
|
*/
|
|
39
41
|
static async getTestFiles(globs?: RegExp[]): Promise<IndexedFile[]> {
|
|
40
|
-
const files =
|
|
42
|
+
const files = RuntimeIndex.find({
|
|
41
43
|
module: m => m.roles.includes('test') || m.roles.includes('std'),
|
|
42
44
|
folder: f => f === 'test',
|
|
43
45
|
file: f => f.role === 'test'
|
|
44
46
|
})
|
|
45
|
-
.filter(f => globs?.some(g => g.test(f.
|
|
47
|
+
.filter(f => globs?.some(g => g.test(f.sourceFile)) ?? true);
|
|
46
48
|
|
|
47
49
|
const validFiles = files
|
|
48
50
|
.map(f => this.isTestFile(f.sourceFile).then(valid => ({ file: f, valid })));
|
|
@@ -58,8 +60,12 @@ export class RunnerUtil {
|
|
|
58
60
|
* @returns
|
|
59
61
|
*/
|
|
60
62
|
static async getTestCount(patterns: string[]): Promise<number> {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
+
const countRes = await ExecUtil.getResult(
|
|
64
|
+
spawn('npx', ['trv', 'test:count', ...patterns], {
|
|
65
|
+
env: { ...process.env, ...Env.FORCE_COLOR.export(0), ...Env.NO_COLOR.export(true) }
|
|
66
|
+
}),
|
|
67
|
+
{ catch: true }
|
|
68
|
+
);
|
|
63
69
|
if (!countRes.valid) {
|
|
64
70
|
throw new Error(countRes.stderr);
|
|
65
71
|
}
|
|
@@ -70,6 +76,6 @@ export class RunnerUtil {
|
|
|
70
76
|
* Determine if we should invoke the debugger
|
|
71
77
|
*/
|
|
72
78
|
static get tryDebugger(): boolean {
|
|
73
|
-
return
|
|
79
|
+
return Env.TRV_TEST_BREAK_ENTRY.isTrue;
|
|
74
80
|
}
|
|
75
81
|
}
|
package/src/execute/watcher.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RootRegistry, MethodSource } from '@travetto/registry';
|
|
2
|
-
import { WorkPool,
|
|
3
|
-
import {
|
|
2
|
+
import { WorkPool, WorkQueue } from '@travetto/worker';
|
|
3
|
+
import { RuntimeIndex } from '@travetto/manifest';
|
|
4
4
|
import { ObjectUtil } from '@travetto/base';
|
|
5
5
|
|
|
6
6
|
import { SuiteRegistry } from '../registry/suite';
|
|
@@ -9,11 +9,18 @@ import { TestConsumerRegistry } from '../consumer/registry';
|
|
|
9
9
|
import { CumulativeSummaryConsumer } from '../consumer/types/cumulative';
|
|
10
10
|
import { RunEvent } from '../worker/types';
|
|
11
11
|
import { RunnerUtil } from './util';
|
|
12
|
+
import { TestEvent } from '../model/event';
|
|
12
13
|
|
|
13
14
|
function isRunEvent(ev: unknown): ev is RunEvent {
|
|
14
15
|
return ObjectUtil.isPlainObject(ev) && 'type' in ev && typeof ev.type === 'string' && ev.type === 'run-test';
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
export type TestWatchEvent =
|
|
19
|
+
TestEvent |
|
|
20
|
+
{ type: 'removeTest', method: string, file: string, classId: string } |
|
|
21
|
+
{ type: 'ready' } |
|
|
22
|
+
{ type: 'log', message: string };
|
|
23
|
+
|
|
17
24
|
/**
|
|
18
25
|
* Test Watcher.
|
|
19
26
|
*
|
|
@@ -27,22 +34,16 @@ export class TestWatcher {
|
|
|
27
34
|
static async watch(format: string, runAllOnStart = true): Promise<void> {
|
|
28
35
|
console.debug('Listening for changes');
|
|
29
36
|
|
|
30
|
-
const itr = new
|
|
31
|
-
const src = new IterableWorkSet(itr);
|
|
37
|
+
const itr = new WorkQueue<string>();
|
|
32
38
|
|
|
33
39
|
await SuiteRegistry.init();
|
|
34
40
|
SuiteRegistry.listen(RootRegistry);
|
|
35
41
|
|
|
36
42
|
const consumer = new CumulativeSummaryConsumer(await TestConsumerRegistry.getInstance(format));
|
|
37
|
-
const pool = new WorkPool(buildStandardTestManager(consumer), {
|
|
38
|
-
idleTimeoutMillis: 120000,
|
|
39
|
-
min: 2,
|
|
40
|
-
max: WorkPool.DEFAULT_SIZE
|
|
41
|
-
});
|
|
42
43
|
|
|
43
44
|
new MethodSource(RootRegistry).on(e => {
|
|
44
45
|
const [cls, method] = (e.prev ?? e.curr ?? []);
|
|
45
|
-
if (!cls ||
|
|
46
|
+
if (!cls || RuntimeIndex.getFunctionMetadata(cls)?.abstract) {
|
|
46
47
|
return;
|
|
47
48
|
}
|
|
48
49
|
if (!method) {
|
|
@@ -60,7 +61,7 @@ export class TestWatcher {
|
|
|
60
61
|
type: 'removeTest',
|
|
61
62
|
method: method?.name,
|
|
62
63
|
classId: cls?.Ⲑid,
|
|
63
|
-
file:
|
|
64
|
+
file: RuntimeIndex.getFunctionMetadata(cls)?.source
|
|
64
65
|
});
|
|
65
66
|
}
|
|
66
67
|
});
|
|
@@ -72,11 +73,12 @@ export class TestWatcher {
|
|
|
72
73
|
|
|
73
74
|
process.on('message', ev => {
|
|
74
75
|
if (isRunEvent(ev)) {
|
|
76
|
+
console.debug('Manually triggered', ev);
|
|
75
77
|
itr.add([ev.file, ev.class, ev.method].filter(x => !!x).join('#'), true);
|
|
76
78
|
}
|
|
77
79
|
});
|
|
78
80
|
|
|
79
|
-
process.send?.('ready');
|
|
81
|
+
process.send?.({ type: 'ready' });
|
|
80
82
|
|
|
81
83
|
if (runAllOnStart) {
|
|
82
84
|
for (const test of await RunnerUtil.getTestFiles()) {
|
|
@@ -85,6 +87,14 @@ export class TestWatcher {
|
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
|
|
88
|
-
await
|
|
90
|
+
await WorkPool.run(
|
|
91
|
+
() => buildStandardTestManager(consumer),
|
|
92
|
+
itr,
|
|
93
|
+
{
|
|
94
|
+
idleTimeoutMillis: 120000,
|
|
95
|
+
min: 2,
|
|
96
|
+
max: WorkPool.DEFAULT_SIZE
|
|
97
|
+
}
|
|
98
|
+
);
|
|
89
99
|
}
|
|
90
100
|
}
|
package/src/registry/suite.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Class, ConcreteClass } from '@travetto/base';
|
|
2
|
-
import {
|
|
2
|
+
import { RuntimeIndex, RuntimeContext } from '@travetto/manifest';
|
|
3
3
|
import { MetadataRegistry } from '@travetto/registry';
|
|
4
4
|
|
|
5
5
|
import { SuiteConfig } from '../model/suite';
|
|
@@ -14,15 +14,15 @@ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> {
|
|
|
14
14
|
* Find all valid tests (ignoring abstract)
|
|
15
15
|
*/
|
|
16
16
|
getValidClasses(): Class[] {
|
|
17
|
-
return this.getClasses().filter(c => !
|
|
17
|
+
return this.getClasses().filter(c => !RuntimeIndex.getFunctionMetadata(c)?.abstract);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
createPending(cls: Class): Partial<SuiteConfig> {
|
|
21
21
|
return {
|
|
22
22
|
class: cls,
|
|
23
|
-
module:
|
|
23
|
+
module: RuntimeContext.main.name,
|
|
24
24
|
classId: cls.Ⲑid,
|
|
25
|
-
file:
|
|
25
|
+
file: RuntimeIndex.getFunctionMetadata(cls)!.source,
|
|
26
26
|
tests: [],
|
|
27
27
|
beforeAll: [],
|
|
28
28
|
beforeEach: [],
|
|
@@ -34,8 +34,8 @@ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> {
|
|
|
34
34
|
override createPendingField(cls: Class, fn: Function): Partial<TestConfig> {
|
|
35
35
|
return {
|
|
36
36
|
class: cls,
|
|
37
|
-
module:
|
|
38
|
-
file:
|
|
37
|
+
module: RuntimeContext.main.name,
|
|
38
|
+
file: RuntimeIndex.getFunctionMetadata(cls)!.source,
|
|
39
39
|
methodName: fn.name
|
|
40
40
|
};
|
|
41
41
|
}
|
|
@@ -91,7 +91,7 @@ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> {
|
|
|
91
91
|
getRunParams(file: string, clsName?: string, method?: string): { suites: SuiteConfig[] } | { suite: SuiteConfig, test?: TestConfig } {
|
|
92
92
|
if (clsName && /^\d+$/.test(clsName)) { // If we only have a line number
|
|
93
93
|
const line = parseInt(clsName, 10);
|
|
94
|
-
const suites = this.getValidClasses().filter(cls =>
|
|
94
|
+
const suites = this.getValidClasses().filter(cls => RuntimeIndex.getFunctionMetadata(cls)!.source === file).map(x => this.get(x)).filter(x => !x.skip);
|
|
95
95
|
const suite = suites.find(x => x.lines && (line >= x.lines.start && line <= x.lines.end));
|
|
96
96
|
|
|
97
97
|
if (suite) {
|
|
@@ -113,7 +113,7 @@ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> {
|
|
|
113
113
|
} else {
|
|
114
114
|
const suites = this.getValidClasses()
|
|
115
115
|
.map(x => this.get(x))
|
|
116
|
-
.filter(x => !
|
|
116
|
+
.filter(x => !RuntimeIndex.getFunctionMetadata(x.class)?.abstract); // Do not run abstract suites
|
|
117
117
|
return { suites };
|
|
118
118
|
}
|
|
119
119
|
}
|
package/src/trv.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { TimeSpan } from '@travetto/base';
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
interface TravettoEnv {
|
|
5
|
+
/**
|
|
6
|
+
* The default time to wait for each phase to finish.
|
|
7
|
+
* @default 15s
|
|
8
|
+
*/
|
|
9
|
+
TRV_TEST_PHASE_TIMEOUT: TimeSpan | number;
|
|
10
|
+
/**
|
|
11
|
+
* The default time for a single test to finish.
|
|
12
|
+
* @default 5s
|
|
13
|
+
*/
|
|
14
|
+
TRV_TEST_TIMEOUT: TimeSpan | number;
|
|
15
|
+
/**
|
|
16
|
+
* An additional wait for triggering test runs, useful for code that takes time to warm up
|
|
17
|
+
*/
|
|
18
|
+
TRV_TEST_DELAY: TimeSpan | number;
|
|
19
|
+
/**
|
|
20
|
+
* Should the test break on the first line of debugging
|
|
21
|
+
*/
|
|
22
|
+
TRV_TEST_BREAK_ENTRY: boolean;
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/worker/child.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { createWriteStream } from 'fs';
|
|
1
|
+
import { createWriteStream } from 'node:fs';
|
|
2
|
+
import timers from 'node:timers/promises';
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
-
import { ConsoleManager, TimeUtil } from '@travetto/base';
|
|
4
|
+
import { RuntimeContext } from '@travetto/manifest';
|
|
5
|
+
import { ConsoleManager, Env, TimeUtil } from '@travetto/base';
|
|
5
6
|
import { ChildCommChannel } from '@travetto/worker';
|
|
6
7
|
|
|
7
8
|
import { ErrorUtil } from '../consumer/error';
|
|
@@ -32,8 +33,9 @@ export class TestChildWorker extends ChildCommChannel<RunEvent> {
|
|
|
32
33
|
* Start the worker
|
|
33
34
|
*/
|
|
34
35
|
async activate(): Promise<void> {
|
|
35
|
-
if (/\b@travetto[/]test\b/.test(
|
|
36
|
-
const
|
|
36
|
+
if (/\b@travetto[/]test\b/.test(Env.DEBUG.val ?? '')) {
|
|
37
|
+
const file = RuntimeContext.toolPath(`test-worker.${process.pid}.log`);
|
|
38
|
+
const stdout = createWriteStream(file, { flags: 'a' });
|
|
37
39
|
const c = new console.Console({ stdout, inspectOptions: { depth: 4, colors: false } });
|
|
38
40
|
ConsoleManager.set({ onLog: (ev) => c[ev.level](process.pid, ...ev.args) });
|
|
39
41
|
} else {
|
|
@@ -48,7 +50,7 @@ export class TestChildWorker extends ChildCommChannel<RunEvent> {
|
|
|
48
50
|
// Let parent know the child is ready for handling commands
|
|
49
51
|
this.send(Events.READY);
|
|
50
52
|
|
|
51
|
-
await TimeUtil.
|
|
53
|
+
await timers.setTimeout(TimeUtil.timeToMs('10m'));
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
/**
|
package/src/worker/standard.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { fork } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
import { RuntimeIndex } from '@travetto/manifest';
|
|
4
|
+
import { Env } from '@travetto/base';
|
|
3
5
|
import { ParentCommChannel, Worker } from '@travetto/worker';
|
|
4
6
|
|
|
5
7
|
import { Events, RunEvent } from './types';
|
|
@@ -21,11 +23,11 @@ function buildEvent(ev: string): RunEvent {
|
|
|
21
23
|
/**
|
|
22
24
|
* Produce a handler for the child worker
|
|
23
25
|
*/
|
|
24
|
-
export function buildStandardTestManager(consumer: TestConsumer):
|
|
26
|
+
export function buildStandardTestManager(consumer: TestConsumer): Worker<string> {
|
|
25
27
|
/**
|
|
26
28
|
* Spawn a child
|
|
27
29
|
*/
|
|
28
|
-
return
|
|
30
|
+
return {
|
|
29
31
|
id: i += 1,
|
|
30
32
|
active: true,
|
|
31
33
|
async destroy(): Promise<void> { },
|
|
@@ -35,16 +37,19 @@ export function buildStandardTestManager(consumer: TestConsumer): () => Worker<s
|
|
|
35
37
|
|
|
36
38
|
const event = buildEvent(file);
|
|
37
39
|
|
|
38
|
-
const { module } =
|
|
39
|
-
const cwd =
|
|
40
|
+
const { module } = RuntimeIndex.getEntry(event.file!)!;
|
|
41
|
+
const cwd = RuntimeIndex.getModule(module)!.sourcePath;
|
|
40
42
|
|
|
41
43
|
const channel = new ParentCommChannel<TestEvent & { error?: Error }>(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
['test:child'],
|
|
44
|
+
fork(
|
|
45
|
+
RuntimeIndex.resolveFileImport('@travetto/cli/support/entry.trv'), ['test:child'],
|
|
45
46
|
{
|
|
46
47
|
cwd,
|
|
47
|
-
env: {
|
|
48
|
+
env: {
|
|
49
|
+
...process.env,
|
|
50
|
+
...Env.TRV_MANIFEST.export(RuntimeIndex.getModule(module)!.outputPath),
|
|
51
|
+
...Env.TRV_QUIET.export(true)
|
|
52
|
+
},
|
|
48
53
|
stdio: ['ignore', 'ignore', 2, 'ipc']
|
|
49
54
|
}
|
|
50
55
|
)
|
|
@@ -80,5 +85,5 @@ export function buildStandardTestManager(consumer: TestConsumer): () => Worker<s
|
|
|
80
85
|
throw ErrorUtil.deserializeError(error);
|
|
81
86
|
}
|
|
82
87
|
},
|
|
83
|
-
}
|
|
88
|
+
};
|
|
84
89
|
}
|
package/support/bin/run.ts
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import timers from 'node:timers/promises';
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { Env } from '@travetto/base';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
7
|
-
namespace NodeJS {
|
|
8
|
-
interface ProcessEnv {
|
|
9
|
-
TRV_TEST_DELAY?: '2s';
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
}
|
|
5
|
+
import type { RunState } from '../../src/execute/types';
|
|
13
6
|
|
|
14
7
|
/**
|
|
15
8
|
* Run tests given the input state
|
|
@@ -21,15 +14,13 @@ export async function runTests(opts: RunState): Promise<void> {
|
|
|
21
14
|
|
|
22
15
|
RunnerUtil.registerCleanup('runner');
|
|
23
16
|
|
|
24
|
-
|
|
25
|
-
await TimeUtil.wait(process.env.TRV_TEST_DELAY);
|
|
26
|
-
}
|
|
17
|
+
await timers.setTimeout(Env.TRV_TEST_DELAY.time ?? 0);
|
|
27
18
|
|
|
28
19
|
try {
|
|
29
20
|
const res = await new Runner(opts).run();
|
|
30
|
-
|
|
21
|
+
process.exitCode = res ? 0 : 1;
|
|
31
22
|
} catch (err) {
|
|
32
23
|
console.error('Test Worker Failed', { error: err });
|
|
33
|
-
|
|
24
|
+
process.exitCode = 1;
|
|
34
25
|
}
|
|
35
26
|
}
|
package/support/bin/types.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type TestFormat = 'tap' | 'tap-streamed' | 'xunit' | 'event' | 'exec';
|
|
1
|
+
export type TestFormat = 'tap' | 'tap-streamed' | 'xunit' | 'event' | 'exec' | 'json';
|
|
2
2
|
export type TestMode = 'single' | 'standard';
|
package/support/cli.test.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { EventEmitter } from 'events';
|
|
2
|
-
import fs from 'fs/promises';
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
3
|
|
|
4
4
|
import { path } from '@travetto/manifest';
|
|
5
|
-
import {
|
|
5
|
+
import { Env } from '@travetto/base';
|
|
6
6
|
import { CliCommandShape, CliCommand, CliValidationError } from '@travetto/cli';
|
|
7
7
|
import { WorkPool } from '@travetto/worker';
|
|
8
8
|
import { Max, Min } from '@travetto/schema';
|
|
@@ -24,7 +24,11 @@ export class TestCommand implements CliCommandShape {
|
|
|
24
24
|
|
|
25
25
|
preMain(): void {
|
|
26
26
|
EventEmitter.defaultMaxListeners = 1000;
|
|
27
|
-
|
|
27
|
+
Env.TRV_ROLE.set('test');
|
|
28
|
+
Env.TRV_ENV.set('test');
|
|
29
|
+
Env.DEBUG.set(false);
|
|
30
|
+
Env.TRV_LOG_PLAIN.set(true);
|
|
31
|
+
Env.TRV_LOG_TIME.clear();
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
isFirstFile(first: string): Promise<boolean> {
|
|
@@ -41,8 +45,6 @@ export class TestCommand implements CliCommandShape {
|
|
|
41
45
|
|
|
42
46
|
if (mode === 'single' && !await this.isFirstFile(first)) {
|
|
43
47
|
return { message: 'You must specify a proper test file to run in single mode', source: 'arg' };
|
|
44
|
-
} else if (!/test\//.test(first)) {
|
|
45
|
-
return { message: 'Only files in the test/ folder are permitted to be run', source: 'arg' };
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { EventEmitter } from 'events';
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { Env } from '@travetto/base';
|
|
4
4
|
import { CliCommand } from '@travetto/cli';
|
|
5
5
|
|
|
6
6
|
/** Test child worker target */
|
|
@@ -8,15 +8,16 @@ import { CliCommand } from '@travetto/cli';
|
|
|
8
8
|
export class TestChildWorkerCommand {
|
|
9
9
|
preMain(): void {
|
|
10
10
|
EventEmitter.defaultMaxListeners = 1000;
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
Env.TRV_ROLE.set('test');
|
|
12
|
+
Env.TRV_ENV.set('test');
|
|
13
|
+
Env.DEBUG.set(false);
|
|
14
|
+
Env.FORCE_COLOR.set(false);
|
|
15
|
+
Env.TRV_LOG_PLAIN.set(true);
|
|
16
|
+
Env.TRV_LOG_TIME.clear();
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
async main(): Promise<void> {
|
|
16
|
-
|
|
17
|
-
// Shutdown when ipc bridge is closed
|
|
18
|
-
process.on('disconnect', () => ShutdownManager.execute());
|
|
19
|
-
}
|
|
20
|
+
process.once('disconnect', () => process.exit());
|
|
20
21
|
const { TestChildWorker } = await import('../src/worker/child.js');
|
|
21
22
|
return new TestChildWorker().activate();
|
|
22
23
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CliCommand } from '@travetto/cli';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { RuntimeIndex } from '@travetto/manifest';
|
|
3
|
+
import { Env } from '@travetto/base';
|
|
4
4
|
|
|
5
5
|
import { SuiteRegistry } from '../src/registry/suite';
|
|
6
6
|
import { RunnerUtil } from '../src/execute/util';
|
|
@@ -9,7 +9,8 @@ import { RunnerUtil } from '../src/execute/util';
|
|
|
9
9
|
export class TestCountCommand {
|
|
10
10
|
|
|
11
11
|
preMain(): void {
|
|
12
|
-
|
|
12
|
+
Env.TRV_ROLE.set('test');
|
|
13
|
+
Env.DEBUG.set(false);
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
async main(patterns: string[]) {
|
|
@@ -30,7 +31,7 @@ export class TestCountCommand {
|
|
|
30
31
|
const suites = SuiteRegistry.getClasses();
|
|
31
32
|
const total = suites
|
|
32
33
|
.map(c => SuiteRegistry.get(c))
|
|
33
|
-
.filter(c => !
|
|
34
|
+
.filter(c => !RuntimeIndex.getFunctionMetadata(c.class)?.abstract)
|
|
34
35
|
.reduce((acc, c) => acc + (c.tests?.length ?? 0), 0);
|
|
35
36
|
|
|
36
37
|
console.log(total);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Env } from '@travetto/base';
|
|
2
2
|
import { CliCommand } from '@travetto/cli';
|
|
3
3
|
|
|
4
4
|
import { runTests } from './bin/run';
|
|
@@ -11,7 +11,10 @@ export class TestDirectCommand {
|
|
|
11
11
|
format: TestFormat = 'tap';
|
|
12
12
|
|
|
13
13
|
preMain(): void {
|
|
14
|
-
|
|
14
|
+
Env.TRV_ROLE.set('test');
|
|
15
|
+
Env.TRV_ENV.set('test');
|
|
16
|
+
Env.TRV_LOG_PLAIN.set(true);
|
|
17
|
+
Env.TRV_LOG_TIME.clear();
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
main(file: string, args: string[]): Promise<void> {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Env } from '@travetto/base';
|
|
2
2
|
import { CliCommand, CliUtil } from '@travetto/cli';
|
|
3
3
|
|
|
4
4
|
import { TestFormat } from './bin/types';
|
|
@@ -13,7 +13,8 @@ export class TestWatcherCommand {
|
|
|
13
13
|
mode: 'all' | 'change' = 'all';
|
|
14
14
|
|
|
15
15
|
preMain(): void {
|
|
16
|
-
|
|
16
|
+
Env.TRV_ROLE.set('test');
|
|
17
|
+
Env.TRV_DYNAMIC.set(true);
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
async main(): Promise<void> {
|
|
@@ -21,10 +22,7 @@ export class TestWatcherCommand {
|
|
|
21
22
|
return;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
if (process.send) {
|
|
26
|
-
process.on('disconnect', () => process.exit(0));
|
|
27
|
-
}
|
|
25
|
+
process.once('disconnect', () => process.exit());
|
|
28
26
|
|
|
29
27
|
try {
|
|
30
28
|
const { TestWatcher } = await import('../src/execute/watcher.js');
|