@travetto/test 5.0.0-rc.8 → 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/capture.ts +3 -3
- package/src/assert/check.ts +23 -25
- package/src/assert/util.ts +21 -9
- package/src/consumer/registry.ts +2 -3
- package/src/consumer/{error.ts → serialize.ts} +12 -21
- 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 +9 -6
- package/src/consumer/types/tap.ts +2 -2
- package/src/decorator/suite.ts +4 -5
- package/src/execute/executor.ts +82 -95
- package/src/execute/phase.ts +19 -29
- package/src/execute/promise.ts +3 -3
- 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 +28 -24
- 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 +10 -10
package/src/execute/watcher.ts
CHANGED
|
@@ -1,27 +1,14 @@
|
|
|
1
1
|
import { RootRegistry, MethodSource } from '@travetto/registry';
|
|
2
|
-
import { WorkPool
|
|
3
|
-
import { Runtime, describeFunction } from '@travetto/runtime';
|
|
2
|
+
import { WorkPool } from '@travetto/worker';
|
|
3
|
+
import { AsyncQueue, Runtime, RuntimeIndex, castTo, describeFunction } from '@travetto/runtime';
|
|
4
4
|
|
|
5
5
|
import { SuiteRegistry } from '../registry/suite';
|
|
6
6
|
import { buildStandardTestManager } from '../worker/standard';
|
|
7
7
|
import { TestConsumerRegistry } from '../consumer/registry';
|
|
8
8
|
import { CumulativeSummaryConsumer } from '../consumer/types/cumulative';
|
|
9
|
-
import {
|
|
9
|
+
import { TestRun } from '../model/test';
|
|
10
10
|
import { RunnerUtil } from './util';
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
function isRunRequest(ev: unknown): ev is RunRequest {
|
|
14
|
-
return typeof ev === 'object' && !!ev && 'type' in ev && typeof ev.type === 'string' && ev.type === 'run-test';
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
type RemoveTestEvent = { type: 'removeTest', method: string, import: string, classId: string };
|
|
18
|
-
|
|
19
|
-
export type TestWatchEvent =
|
|
20
|
-
TestEvent |
|
|
21
|
-
RemoveTestEvent |
|
|
22
|
-
{ type: 'ready' } |
|
|
23
|
-
{ type: 'log', message: string };
|
|
24
|
-
|
|
11
|
+
import { TestReadyEvent, TestRemovedEvent } from '../worker/types';
|
|
25
12
|
/**
|
|
26
13
|
* Test Watcher.
|
|
27
14
|
*
|
|
@@ -35,12 +22,20 @@ export class TestWatcher {
|
|
|
35
22
|
static async watch(format: string, runAllOnStart = true): Promise<void> {
|
|
36
23
|
console.debug('Listening for changes');
|
|
37
24
|
|
|
38
|
-
const itr = new WorkQueue<string | RunRequest>();
|
|
39
|
-
|
|
40
25
|
await SuiteRegistry.init();
|
|
41
26
|
SuiteRegistry.listen(RootRegistry);
|
|
27
|
+
await RootRegistry.init();
|
|
28
|
+
|
|
29
|
+
const events: TestRun[] = [];
|
|
30
|
+
|
|
31
|
+
if (runAllOnStart) {
|
|
32
|
+
const tests = await RunnerUtil.getTestDigest();
|
|
33
|
+
events.push(...RunnerUtil.getTestRuns(tests));
|
|
34
|
+
}
|
|
42
35
|
|
|
43
|
-
const
|
|
36
|
+
const itr = new AsyncQueue(events);
|
|
37
|
+
const consumer = new CumulativeSummaryConsumer(await TestConsumerRegistry.getInstance(format))
|
|
38
|
+
.withFilter(x => x.metadata?.partial !== true || x.type !== 'suite');
|
|
44
39
|
|
|
45
40
|
new MethodSource(RootRegistry).on(e => {
|
|
46
41
|
const [cls, method] = (e.prev ?? e.curr ?? []);
|
|
@@ -54,39 +49,40 @@ export class TestWatcher {
|
|
|
54
49
|
const conf = SuiteRegistry.getByClassAndMethod(cls, method)!;
|
|
55
50
|
if (e.type !== 'removing') {
|
|
56
51
|
if (conf) {
|
|
57
|
-
const
|
|
58
|
-
|
|
52
|
+
const run: TestRun = {
|
|
53
|
+
import: conf.import, classId: conf.classId, methodNames: [conf.methodName], metadata: { partial: true }
|
|
54
|
+
};
|
|
55
|
+
console.log('Triggering', run);
|
|
56
|
+
itr.add(run, true); // Shift to front
|
|
59
57
|
}
|
|
60
58
|
} else {
|
|
61
59
|
process.send?.({
|
|
62
60
|
type: 'removeTest',
|
|
61
|
+
methodNames: method?.name ? [method.name!] : undefined!,
|
|
63
62
|
method: method?.name,
|
|
64
63
|
classId: cls?.Ⲑid,
|
|
65
64
|
import: Runtime.getImport(cls)
|
|
66
|
-
} satisfies
|
|
65
|
+
} satisfies TestRemovedEvent);
|
|
67
66
|
}
|
|
68
67
|
});
|
|
69
68
|
|
|
70
|
-
// If a file is changed, but doesn't emit classes, re-run whole file
|
|
71
|
-
RootRegistry.onNonClassChanges(imp => itr.add(imp));
|
|
72
69
|
|
|
73
|
-
|
|
70
|
+
// If a file is changed, but doesn't emit classes, re-run whole file
|
|
71
|
+
RootRegistry.onNonClassChanges(imp => itr.add({ import: imp }));
|
|
74
72
|
|
|
75
73
|
process.on('message', ev => {
|
|
76
|
-
if (
|
|
74
|
+
if (typeof ev === 'object' && ev && 'type' in ev && ev.type === 'run-test') {
|
|
75
|
+
console.log('Received message', ev);
|
|
76
|
+
// Legacy
|
|
77
|
+
if ('file' in ev && typeof ev.file === 'string') {
|
|
78
|
+
ev = { import: RuntimeIndex.getFromSource(ev.file)?.import! };
|
|
79
|
+
}
|
|
77
80
|
console.debug('Manually triggered', ev);
|
|
78
|
-
itr.add(ev, true);
|
|
81
|
+
itr.add(castTo(ev), true);
|
|
79
82
|
}
|
|
80
83
|
});
|
|
81
84
|
|
|
82
|
-
process.send?.({ type: 'ready' });
|
|
83
|
-
|
|
84
|
-
if (runAllOnStart) {
|
|
85
|
-
for await (const imp of await RunnerUtil.getTestImports()) {
|
|
86
|
-
await Runtime.importFrom(imp);
|
|
87
|
-
itr.add(imp);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
85
|
+
process.send?.({ type: 'ready' } satisfies TestReadyEvent);
|
|
90
86
|
|
|
91
87
|
await WorkPool.run(
|
|
92
88
|
buildStandardTestManager.bind(null, consumer),
|
package/src/model/common.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Configuration of a skip */
|
|
2
|
-
export type Skip = boolean | ((instance: unknown) => boolean | Promise<boolean>)
|
|
2
|
+
export type Skip = boolean | ((instance: unknown) => boolean | Promise<boolean>);
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Core Suite definition
|
|
@@ -25,6 +25,10 @@ export interface SuiteCore {
|
|
|
25
25
|
* The last line of the unit
|
|
26
26
|
*/
|
|
27
27
|
lineEnd: number;
|
|
28
|
+
/**
|
|
29
|
+
* Tags for a suite or a test
|
|
30
|
+
*/
|
|
31
|
+
tags?: string[];
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
/**
|
|
@@ -35,4 +39,8 @@ export interface TestCore extends SuiteCore {
|
|
|
35
39
|
* The first line of the unit body
|
|
36
40
|
*/
|
|
37
41
|
lineBodyStart: number;
|
|
42
|
+
/**
|
|
43
|
+
* For extended suites, this is location of the actual file where the test exists
|
|
44
|
+
*/
|
|
45
|
+
sourceImport?: string;
|
|
38
46
|
}
|
package/src/model/event.ts
CHANGED
|
@@ -5,17 +5,21 @@ import { SuiteConfig, SuiteResult } from './suite';
|
|
|
5
5
|
* Targets
|
|
6
6
|
*/
|
|
7
7
|
export type EventEntity = 'test' | 'suite' | 'assertion';
|
|
8
|
+
|
|
8
9
|
/**
|
|
9
10
|
* Phases
|
|
10
11
|
*/
|
|
11
12
|
export type EventPhase = 'before' | 'after';
|
|
12
13
|
|
|
14
|
+
type EventTpl<T extends EventEntity, P extends EventPhase, V extends {}> =
|
|
15
|
+
{ type: T, phase: P, metadata?: Record<string, unknown> } & V;
|
|
16
|
+
|
|
13
17
|
/**
|
|
14
18
|
* Different test event shapes
|
|
15
19
|
*/
|
|
16
20
|
export type TestEvent =
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
EventTpl<'assertion', 'after', { assertion: Assertion }> |
|
|
22
|
+
EventTpl<'test', 'before', { test: TestConfig }> |
|
|
23
|
+
EventTpl<'test', 'after', { test: TestResult }> |
|
|
24
|
+
EventTpl<'suite', 'before', { suite: SuiteConfig }> |
|
|
25
|
+
EventTpl<'suite', 'after', { suite: SuiteResult }>;
|
package/src/model/suite.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Class } from '@travetto/runtime';
|
|
2
2
|
|
|
3
|
-
import { TestConfig, TestResult } from './test';
|
|
3
|
+
import { Assertion, TestConfig, TestResult } from './test';
|
|
4
4
|
import { Skip, SuiteCore } from './common';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -80,3 +80,13 @@ export interface SuiteResult extends Counts {
|
|
|
80
80
|
*/
|
|
81
81
|
duration: number;
|
|
82
82
|
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A total suite failure
|
|
86
|
+
*/
|
|
87
|
+
export interface SuiteFailure {
|
|
88
|
+
assert: Assertion;
|
|
89
|
+
testResult: TestResult;
|
|
90
|
+
test: TestConfig;
|
|
91
|
+
suite: SuiteConfig;
|
|
92
|
+
}
|
package/src/model/test.ts
CHANGED
|
@@ -107,4 +107,30 @@ export interface TestResult extends TestCore {
|
|
|
107
107
|
* Logging output
|
|
108
108
|
*/
|
|
109
109
|
output: Record<string, string>;
|
|
110
|
-
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Test Run
|
|
114
|
+
*/
|
|
115
|
+
export type TestRun = {
|
|
116
|
+
/**
|
|
117
|
+
* Import for run
|
|
118
|
+
*/
|
|
119
|
+
import: string;
|
|
120
|
+
/**
|
|
121
|
+
* Suite class id
|
|
122
|
+
*/
|
|
123
|
+
classId?: string;
|
|
124
|
+
/**
|
|
125
|
+
* Methods names we want to target
|
|
126
|
+
*/
|
|
127
|
+
methodNames?: string[];
|
|
128
|
+
/**
|
|
129
|
+
* Test run metadata
|
|
130
|
+
*/
|
|
131
|
+
metadata?: Record<string, unknown>;
|
|
132
|
+
/**
|
|
133
|
+
* unique id for the run
|
|
134
|
+
*/
|
|
135
|
+
runId?: string;
|
|
136
|
+
};
|
package/src/registry/suite.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Class,
|
|
1
|
+
import { Class, Runtime, classConstruct, describeFunction, asFull } from '@travetto/runtime';
|
|
2
2
|
import { MetadataRegistry } from '@travetto/registry';
|
|
3
3
|
|
|
4
4
|
import { SuiteConfig } from '../model/suite';
|
|
5
|
-
import { TestConfig } from '../model/test';
|
|
5
|
+
import { TestConfig, TestRun } from '../model/test';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Test Suite registry
|
|
@@ -21,6 +21,7 @@ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> {
|
|
|
21
21
|
return {
|
|
22
22
|
class: cls,
|
|
23
23
|
classId: cls.Ⲑid,
|
|
24
|
+
tags: [],
|
|
24
25
|
import: Runtime.getImport(cls),
|
|
25
26
|
lineStart: lines?.[0],
|
|
26
27
|
lineEnd: lines?.[1],
|
|
@@ -36,6 +37,7 @@ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> {
|
|
|
36
37
|
const lines = describeFunction(cls)?.methods?.[fn.name].lines;
|
|
37
38
|
return {
|
|
38
39
|
class: cls,
|
|
40
|
+
tags: [],
|
|
39
41
|
import: Runtime.getImport(cls),
|
|
40
42
|
lineStart: lines?.[0],
|
|
41
43
|
lineEnd: lines?.[1],
|
|
@@ -57,8 +59,7 @@ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> {
|
|
|
57
59
|
* a full projection of all listeners and tests.
|
|
58
60
|
*/
|
|
59
61
|
onInstallFinalize<T>(cls: Class<T>): SuiteConfig {
|
|
60
|
-
|
|
61
|
-
const config = this.getOrCreatePending(cls) as SuiteConfig;
|
|
62
|
+
const config = asFull(this.getOrCreatePending(cls));
|
|
62
63
|
const tests = [...this.pendingFields.get(cls.Ⲑid)!.values()];
|
|
63
64
|
|
|
64
65
|
const parent = this.getParentClass(cls);
|
|
@@ -71,20 +72,19 @@ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> {
|
|
|
71
72
|
config.beforeEach.push(...pConf.beforeEach);
|
|
72
73
|
tests.push(...[...pConf.tests.values()].map(t => ({
|
|
73
74
|
...t,
|
|
75
|
+
sourceImport: pConf.import,
|
|
74
76
|
class: cls
|
|
75
77
|
})));
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
|
|
79
|
-
config.
|
|
80
|
-
|
|
81
|
-
config.tests = tests as TestConfig[];
|
|
80
|
+
config.instance = classConstruct(config.class);
|
|
81
|
+
config.tests = tests!.map(x => asFull(x));
|
|
82
|
+
config.description ||= config.classId;
|
|
82
83
|
|
|
83
|
-
if (!config.description) {
|
|
84
|
-
config.description = config.classId;
|
|
85
|
-
}
|
|
86
84
|
for (const t of config.tests) {
|
|
87
85
|
t.classId = config.classId;
|
|
86
|
+
t.import = config.import;
|
|
87
|
+
t.tags = [...t.tags!, ...config.tags!];
|
|
88
88
|
}
|
|
89
89
|
return config;
|
|
90
90
|
}
|
|
@@ -92,9 +92,13 @@ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> {
|
|
|
92
92
|
/**
|
|
93
93
|
* Get run parameters from provided input
|
|
94
94
|
*/
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
getSuiteTests(run: TestRun): { suite: SuiteConfig, tests: TestConfig[] }[] {
|
|
96
|
+
const clsId = run.classId;
|
|
97
|
+
const imp = run.import;
|
|
98
|
+
const methodNames = run.methodNames ?? [];
|
|
99
|
+
|
|
100
|
+
if (clsId && /^\d+$/.test(clsId)) { // If we only have a line number
|
|
101
|
+
const line = parseInt(clsId, 10);
|
|
98
102
|
const suites = this.getValidClasses()
|
|
99
103
|
.filter(cls => Runtime.getImport(cls) === imp)
|
|
100
104
|
.map(x => this.get(x)).filter(x => !x.skip);
|
|
@@ -102,25 +106,25 @@ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> {
|
|
|
102
106
|
|
|
103
107
|
if (suite) {
|
|
104
108
|
const test = suite.tests.find(x => line >= x.lineStart && line <= x.lineEnd);
|
|
105
|
-
return test ? { suite, test } : { suite };
|
|
109
|
+
return test ? [{ suite, tests: [test] }] : [{ suite, tests: suite.tests }];
|
|
106
110
|
} else {
|
|
107
|
-
return {
|
|
111
|
+
return suites.map(x => ({ suite: x, tests: x.tests }));
|
|
108
112
|
}
|
|
109
113
|
} else { // Else lookup directly
|
|
110
|
-
if (
|
|
111
|
-
const cls = this.getValidClasses().find(x => x
|
|
114
|
+
if (methodNames.length) {
|
|
115
|
+
const cls = this.getValidClasses().find(x => x.Ⲑid === clsId)!;
|
|
112
116
|
const suite = this.get(cls);
|
|
113
|
-
const
|
|
114
|
-
return { suite,
|
|
115
|
-
} else if (
|
|
116
|
-
const cls = this.getValidClasses().find(x => x
|
|
117
|
+
const tests = suite.tests.filter(x => methodNames.includes(x.methodName))!;
|
|
118
|
+
return [{ suite, tests }];
|
|
119
|
+
} else if (clsId) {
|
|
120
|
+
const cls = this.getValidClasses().find(x => x.Ⲑid === clsId)!;
|
|
117
121
|
const suite = this.get(cls);
|
|
118
|
-
return { suite };
|
|
122
|
+
return [{ suite, tests: suite.tests }];
|
|
119
123
|
} else {
|
|
120
124
|
const suites = this.getValidClasses()
|
|
121
125
|
.map(x => this.get(x))
|
|
122
126
|
.filter(x => !describeFunction(x.class).abstract); // Do not run abstract suites
|
|
123
|
-
return {
|
|
127
|
+
return suites.map(x => ({ suite: x, tests: x.tests }));
|
|
124
128
|
}
|
|
125
129
|
}
|
|
126
130
|
}
|
package/src/trv.d.ts
CHANGED
package/src/worker/child.ts
CHANGED
|
@@ -3,16 +3,17 @@ import { createWriteStream } from 'node:fs';
|
|
|
3
3
|
import { ConsoleManager, Env, Util, Runtime } from '@travetto/runtime';
|
|
4
4
|
import { ChildCommChannel } from '@travetto/worker';
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { SerializeUtil } from '../consumer/serialize';
|
|
7
7
|
import { RunnerUtil } from '../execute/util';
|
|
8
8
|
import { Runner } from '../execute/runner';
|
|
9
|
-
import { Events
|
|
9
|
+
import { Events } from './types';
|
|
10
|
+
import { TestRun } from '../model/test';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Child Worker for the Test Runner. Receives events as commands
|
|
13
14
|
* to run specific tests
|
|
14
15
|
*/
|
|
15
|
-
export class TestChildWorker extends ChildCommChannel<
|
|
16
|
+
export class TestChildWorker extends ChildCommChannel<TestRun> {
|
|
16
17
|
|
|
17
18
|
#done = Util.resolvablePromise();
|
|
18
19
|
|
|
@@ -25,7 +26,7 @@ export class TestChildWorker extends ChildCommChannel<RunEvent> {
|
|
|
25
26
|
throw err;
|
|
26
27
|
}
|
|
27
28
|
// Mark as errored out
|
|
28
|
-
this.send(type, { error:
|
|
29
|
+
this.send(type, { error: SerializeUtil.serializeError(err) });
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -56,7 +57,7 @@ export class TestChildWorker extends ChildCommChannel<RunEvent> {
|
|
|
56
57
|
/**
|
|
57
58
|
* When we receive a command from the parent
|
|
58
59
|
*/
|
|
59
|
-
async onCommand(event:
|
|
60
|
+
async onCommand(event: TestRun & { type: string }): Promise<boolean> {
|
|
60
61
|
console.debug('on message', { ...event });
|
|
61
62
|
|
|
62
63
|
if (event.type === Events.INIT) { // On request to init, start initialization
|
|
@@ -76,18 +77,11 @@ export class TestChildWorker extends ChildCommChannel<RunEvent> {
|
|
|
76
77
|
/**
|
|
77
78
|
* Run a specific test/suite
|
|
78
79
|
*/
|
|
79
|
-
async onRunCommand(
|
|
80
|
-
console.debug('
|
|
81
|
-
|
|
82
|
-
console.debug('Running', { import: event.import });
|
|
80
|
+
async onRunCommand(run: TestRun): Promise<void> {
|
|
81
|
+
console.debug('Running', { import: run.import });
|
|
83
82
|
|
|
84
83
|
try {
|
|
85
|
-
await new Runner({
|
|
86
|
-
format: 'exec',
|
|
87
|
-
mode: 'single',
|
|
88
|
-
args: [event.import, event.class!, event.method!],
|
|
89
|
-
concurrency: 1
|
|
90
|
-
}).run();
|
|
84
|
+
await new Runner({ format: 'exec', target: run }).run();
|
|
91
85
|
} finally {
|
|
92
86
|
this.#done.resolve();
|
|
93
87
|
}
|
package/src/worker/standard.ts
CHANGED
|
@@ -3,39 +3,34 @@ import { fork } from 'node:child_process';
|
|
|
3
3
|
import { Env, RuntimeIndex } from '@travetto/runtime';
|
|
4
4
|
import { ParentCommChannel } from '@travetto/worker';
|
|
5
5
|
|
|
6
|
-
import { Events,
|
|
6
|
+
import { Events, TestLogEvent } from './types';
|
|
7
7
|
import { TestConsumer } from '../consumer/types';
|
|
8
|
-
import {
|
|
8
|
+
import { SerializeUtil } from '../consumer/serialize';
|
|
9
9
|
import { TestEvent } from '../model/event';
|
|
10
|
+
import { TestRun } from '../model/test';
|
|
11
|
+
|
|
12
|
+
const log = (message: string): void => {
|
|
13
|
+
process.send?.({ type: 'log', message } satisfies TestLogEvent);
|
|
14
|
+
};
|
|
10
15
|
|
|
11
16
|
/**
|
|
12
17
|
* Produce a handler for the child worker
|
|
13
18
|
*/
|
|
14
|
-
export async function buildStandardTestManager(consumer: TestConsumer,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (typeof imp === 'string') {
|
|
19
|
-
event = { import: imp };
|
|
20
|
-
} else if ('file' in imp) {
|
|
21
|
-
event = { import: RuntimeIndex.getFromSource(imp.file)?.import!, class: imp.class, method: imp.method };
|
|
22
|
-
} else {
|
|
23
|
-
event = imp;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
process.send?.({ type: 'log', message: `Worker Executing ${event.import}` });
|
|
19
|
+
export async function buildStandardTestManager(consumer: TestConsumer, run: TestRun): Promise<void> {
|
|
20
|
+
log(`Worker Input ${JSON.stringify(run)}`);
|
|
21
|
+
log(`Worker Executing ${run.import}`);
|
|
27
22
|
|
|
28
|
-
const { module } = RuntimeIndex.getFromImport(
|
|
29
|
-
const suiteMod = RuntimeIndex.getModule(module)
|
|
23
|
+
const { module } = RuntimeIndex.getFromImport(run.import)!;
|
|
24
|
+
const suiteMod = RuntimeIndex.getModule(module)!;
|
|
30
25
|
|
|
31
26
|
const channel = new ParentCommChannel<TestEvent & { error?: Error }>(
|
|
32
27
|
fork(
|
|
33
28
|
RuntimeIndex.resolveFileImport('@travetto/cli/support/entry.trv'), ['test:child'],
|
|
34
29
|
{
|
|
35
|
-
cwd: suiteMod
|
|
30
|
+
cwd: suiteMod.sourcePath,
|
|
36
31
|
env: {
|
|
37
32
|
...process.env,
|
|
38
|
-
...Env.TRV_MANIFEST.export(suiteMod
|
|
33
|
+
...Env.TRV_MANIFEST.export(suiteMod.outputPath),
|
|
39
34
|
...Env.TRV_QUIET.export(true)
|
|
40
35
|
},
|
|
41
36
|
stdio: ['ignore', 'ignore', 2, 'ipc']
|
|
@@ -58,7 +53,7 @@ export async function buildStandardTestManager(consumer: TestConsumer, imp: stri
|
|
|
58
53
|
// Listen for child to complete
|
|
59
54
|
const complete = channel.once(Events.RUN_COMPLETE);
|
|
60
55
|
// Start test
|
|
61
|
-
channel.send(Events.RUN,
|
|
56
|
+
channel.send(Events.RUN, run);
|
|
62
57
|
|
|
63
58
|
// Wait for complete
|
|
64
59
|
const { error } = await complete;
|
|
@@ -66,10 +61,10 @@ export async function buildStandardTestManager(consumer: TestConsumer, imp: stri
|
|
|
66
61
|
// Kill on complete
|
|
67
62
|
await channel.destroy();
|
|
68
63
|
|
|
69
|
-
|
|
64
|
+
log(`Worker Finished ${run.import}`);
|
|
70
65
|
|
|
71
66
|
// If we received an error, throw it
|
|
72
67
|
if (error) {
|
|
73
|
-
throw
|
|
68
|
+
throw SerializeUtil.deserializeError(error);
|
|
74
69
|
}
|
|
75
70
|
}
|
package/src/worker/types.ts
CHANGED
|
@@ -1,25 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*/
|
|
4
|
-
export type RunRequest = {
|
|
5
|
-
file: string;
|
|
6
|
-
class?: string;
|
|
7
|
-
method?: string;
|
|
8
|
-
} | {
|
|
9
|
-
import: string;
|
|
10
|
-
class?: string;
|
|
11
|
-
method?: string;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Test Run Event
|
|
16
|
-
*/
|
|
17
|
-
export type RunEvent = {
|
|
18
|
-
import: string;
|
|
19
|
-
error?: unknown;
|
|
20
|
-
class?: string;
|
|
21
|
-
method?: string;
|
|
22
|
-
};
|
|
1
|
+
import { TestEvent } from '../model/event';
|
|
2
|
+
import { TestRun } from '../model/test';
|
|
23
3
|
|
|
24
4
|
/**
|
|
25
5
|
* Test Run Event Keys
|
|
@@ -30,4 +10,14 @@ export const Events = {
|
|
|
30
10
|
INIT: 'init',
|
|
31
11
|
INIT_COMPLETE: 'initComplete',
|
|
32
12
|
READY: 'ready'
|
|
33
|
-
};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type TestRemovedEvent = { type: 'removeTest', method?: string } & TestRun;
|
|
16
|
+
export type TestReadyEvent = { type: 'ready' };
|
|
17
|
+
export type TestLogEvent = { type: 'log', message: string };
|
|
18
|
+
|
|
19
|
+
export type TestWatchEvent =
|
|
20
|
+
TestEvent |
|
|
21
|
+
TestRemovedEvent |
|
|
22
|
+
TestReadyEvent |
|
|
23
|
+
TestLogEvent;
|
package/support/cli.test.ts
CHANGED
|
@@ -21,6 +21,11 @@ export class TestCommand implements CliCommandShape {
|
|
|
21
21
|
concurrency: number = WorkPool.DEFAULT_SIZE;
|
|
22
22
|
/** Test run mode */
|
|
23
23
|
mode: TestMode = 'standard';
|
|
24
|
+
/**
|
|
25
|
+
* Tags to target or exclude
|
|
26
|
+
* @alias env.TRV_TEST_TAGS
|
|
27
|
+
*/
|
|
28
|
+
tags?: string[];
|
|
24
29
|
|
|
25
30
|
preMain(): void {
|
|
26
31
|
EventEmitter.defaultMaxListeners = 1000;
|
|
@@ -48,14 +53,23 @@ export class TestCommand implements CliCommandShape {
|
|
|
48
53
|
}
|
|
49
54
|
}
|
|
50
55
|
|
|
51
|
-
async main(first: string = '**/*',
|
|
56
|
+
async main(first: string = '**/*', globs: string[] = []): Promise<void> {
|
|
52
57
|
const { runTests } = await import('./bin/run');
|
|
53
58
|
|
|
59
|
+
const isFirst = await this.isFirstFile(first);
|
|
60
|
+
const isSingle = this.mode === 'single' || (isFirst && globs.length === 0);
|
|
61
|
+
|
|
54
62
|
return runTests({
|
|
55
|
-
args: [first, ...regexes],
|
|
56
|
-
mode: await this.resolvedMode(first, regexes),
|
|
57
63
|
concurrency: this.concurrency,
|
|
58
|
-
format: this.format
|
|
64
|
+
format: this.format,
|
|
65
|
+
tags: this.tags,
|
|
66
|
+
target: isSingle ?
|
|
67
|
+
{
|
|
68
|
+
import: first,
|
|
69
|
+
classId: globs[0],
|
|
70
|
+
methodNames: globs.slice(1),
|
|
71
|
+
} :
|
|
72
|
+
{ globs: [first, ...globs], }
|
|
59
73
|
});
|
|
60
74
|
}
|
|
61
75
|
}
|
|
@@ -5,16 +5,18 @@ import { SuiteRegistry } from '../src/registry/suite';
|
|
|
5
5
|
import { RunnerUtil } from '../src/execute/util';
|
|
6
6
|
|
|
7
7
|
@CliCommand({ hidden: true })
|
|
8
|
-
export class
|
|
8
|
+
export class TestDigestCommand {
|
|
9
|
+
|
|
10
|
+
output: 'json' | 'text' = 'text';
|
|
9
11
|
|
|
10
12
|
preMain(): void {
|
|
11
13
|
Env.TRV_ROLE.set('test');
|
|
12
14
|
Env.DEBUG.set(false);
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
async main(
|
|
17
|
+
async main(globs: string[] = ['**/*.ts']) {
|
|
16
18
|
// Load all tests
|
|
17
|
-
for await (const imp of await RunnerUtil.getTestImports(
|
|
19
|
+
for await (const imp of await RunnerUtil.getTestImports(globs)) {
|
|
18
20
|
try {
|
|
19
21
|
await Runtime.importFrom(imp);
|
|
20
22
|
} catch (err) {
|
|
@@ -25,11 +27,17 @@ export class TestCountCommand {
|
|
|
25
27
|
await SuiteRegistry.init();
|
|
26
28
|
|
|
27
29
|
const suites = SuiteRegistry.getClasses();
|
|
28
|
-
const
|
|
30
|
+
const all = suites
|
|
29
31
|
.map(c => SuiteRegistry.get(c))
|
|
30
32
|
.filter(c => !describeFunction(c.class).abstract)
|
|
31
|
-
.
|
|
33
|
+
.flatMap(c => c.tests);
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
if (this.output === 'json') {
|
|
36
|
+
console.log(JSON.stringify(all));
|
|
37
|
+
} else {
|
|
38
|
+
for (const item of all) {
|
|
39
|
+
console.log(`${item.classId}#${item.methodName}`, item.tags?.join('|') ?? '');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
34
42
|
}
|
|
35
43
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Env } from '@travetto/runtime';
|
|
1
|
+
import { Env, RuntimeIndex } from '@travetto/runtime';
|
|
2
2
|
import { CliCommand } from '@travetto/cli';
|
|
3
3
|
|
|
4
4
|
import { runTests } from './bin/run';
|
|
@@ -17,7 +17,14 @@ export class TestDirectCommand {
|
|
|
17
17
|
Env.TRV_LOG_TIME.clear();
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
main(
|
|
21
|
-
return runTests({
|
|
20
|
+
main(importOrFile: string, clsId?: string, methodsNames: string[] = []): Promise<void> {
|
|
21
|
+
return runTests({
|
|
22
|
+
format: this.format,
|
|
23
|
+
target: {
|
|
24
|
+
import: importOrFile,
|
|
25
|
+
classId: clsId,
|
|
26
|
+
methodNames: methodsNames,
|
|
27
|
+
}
|
|
28
|
+
});
|
|
22
29
|
}
|
|
23
30
|
}
|