@travetto/test 7.0.0-rc.1 → 7.0.0-rc.3
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 +7 -8
- package/__index__.ts +1 -0
- package/package.json +7 -7
- package/src/assert/check.ts +46 -46
- package/src/assert/util.ts +31 -31
- package/src/communication.ts +66 -0
- package/src/consumer/registry-index.ts +11 -11
- package/src/consumer/types/cumulative.ts +91 -62
- package/src/consumer/types/delegating.ts +30 -27
- package/src/consumer/types/event.ts +11 -4
- package/src/consumer/types/exec.ts +12 -3
- package/src/consumer/types/runnable.ts +4 -3
- package/src/consumer/types/summarizer.ts +12 -10
- package/src/consumer/types/tap-summary.ts +22 -20
- package/src/consumer/types/tap.ts +15 -15
- package/src/consumer/types/xunit.ts +15 -15
- package/src/consumer/types.ts +6 -2
- package/src/decorator/suite.ts +2 -2
- package/src/decorator/test.ts +6 -4
- package/src/execute/barrier.ts +8 -8
- package/src/execute/console.ts +1 -1
- package/src/execute/executor.ts +32 -21
- package/src/execute/phase.ts +7 -7
- package/src/execute/run.ts +247 -0
- package/src/execute/types.ts +2 -17
- package/src/execute/watcher.ts +33 -60
- package/src/fixture.ts +2 -2
- package/src/model/common.ts +4 -0
- package/src/model/event.ts +3 -1
- package/src/model/suite.ts +10 -21
- package/src/model/test.ts +48 -2
- package/src/model/util.ts +8 -0
- package/src/registry/registry-adapter.ts +23 -21
- package/src/registry/registry-index.ts +25 -25
- package/src/worker/child.ts +21 -21
- package/src/worker/standard.ts +28 -19
- package/src/worker/types.ts +9 -5
- package/support/bin/run.ts +10 -10
- package/support/cli.test.ts +20 -41
- package/support/cli.test_diff.ts +47 -0
- package/support/cli.test_digest.ts +7 -7
- package/support/cli.test_direct.ts +13 -12
- package/support/cli.test_watch.ts +3 -8
- package/support/transformer.assert.ts +12 -12
- package/src/execute/runner.ts +0 -87
- package/src/execute/util.ts +0 -108
package/src/execute/watcher.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
+
import { ManifestModuleUtil } from '@travetto/manifest';
|
|
1
2
|
import { Registry } from '@travetto/registry';
|
|
2
3
|
import { WorkPool } from '@travetto/worker';
|
|
3
|
-
import { AsyncQueue,
|
|
4
|
+
import { AsyncQueue, RuntimeIndex, TimeUtil, watchCompiler } from '@travetto/runtime';
|
|
4
5
|
|
|
5
6
|
import { buildStandardTestManager } from '../worker/standard.ts';
|
|
6
7
|
import { TestConsumerRegistryIndex } from '../consumer/registry-index.ts';
|
|
7
8
|
import { CumulativeSummaryConsumer } from '../consumer/types/cumulative.ts';
|
|
8
|
-
import { TestRun } from '../model/test.ts';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { SuiteRegistryIndex } from '../registry/registry-index.ts';
|
|
9
|
+
import type { TestDiffInput, TestRun } from '../model/test.ts';
|
|
10
|
+
import { RunUtil } from './run.ts';
|
|
11
|
+
import { isTestRunEvent, type TestReadyEvent } from '../worker/types.ts';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Test Watcher.
|
|
@@ -25,78 +25,51 @@ export class TestWatcher {
|
|
|
25
25
|
|
|
26
26
|
await Registry.init();
|
|
27
27
|
|
|
28
|
-
const events: TestRun[] = [];
|
|
28
|
+
const events: (TestRun | TestDiffInput)[] = [];
|
|
29
29
|
|
|
30
30
|
if (runAllOnStart) {
|
|
31
|
-
|
|
32
|
-
events.push(...RunnerUtil.getTestRuns(tests));
|
|
31
|
+
events.push(...await RunUtil.resolveGlobInput({ globs: [] }));
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
const
|
|
34
|
+
const queue = new AsyncQueue(events);
|
|
36
35
|
const consumer = new CumulativeSummaryConsumer(
|
|
37
36
|
await TestConsumerRegistryIndex.getInstance({ consumer: format })
|
|
38
|
-
)
|
|
39
|
-
.withFilter(x => x.metadata?.partial !== true || x.type !== 'suite');
|
|
40
|
-
|
|
41
|
-
Registry.onMethodChange((event) => {
|
|
42
|
-
const [cls, method] = ('prev' in event && event.prev ? event.prev : null) ??
|
|
43
|
-
('curr' in event && event.curr ? event.curr : []);
|
|
44
|
-
|
|
45
|
-
if (!cls || describeFunction(cls).abstract) {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const classId = cls.Ⲑid;
|
|
50
|
-
if (!method) {
|
|
51
|
-
consumer.removeClass(classId);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const conf = SuiteRegistryIndex.getTestConfig(cls, method)!;
|
|
56
|
-
if (event.type !== 'removing') {
|
|
57
|
-
if (conf) {
|
|
58
|
-
const run: TestRun = {
|
|
59
|
-
import: conf.import, classId: conf.classId, methodNames: [conf.methodName], metadata: { partial: true }
|
|
60
|
-
};
|
|
61
|
-
console.log('Triggering', run);
|
|
62
|
-
itr.add(run, true); // Shift to front
|
|
63
|
-
}
|
|
64
|
-
} else {
|
|
65
|
-
process.send?.({
|
|
66
|
-
type: 'removeTest',
|
|
67
|
-
methodNames: method?.name ? [method.name!] : undefined!,
|
|
68
|
-
method: method?.name,
|
|
69
|
-
classId,
|
|
70
|
-
import: Runtime.getImport(cls)
|
|
71
|
-
} satisfies TestRemovedEvent);
|
|
72
|
-
}
|
|
73
|
-
}, SuiteRegistryIndex);
|
|
74
|
-
|
|
75
|
-
// If a file is changed, but doesn't emit classes, re-run whole file
|
|
76
|
-
Registry.onNonClassChanges(imp => itr.add({ import: imp }));
|
|
37
|
+
);
|
|
77
38
|
|
|
78
|
-
process.on('message',
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
// Legacy
|
|
82
|
-
if ('file' in ev && typeof ev.file === 'string') {
|
|
83
|
-
ev = { import: RuntimeIndex.getFromSource(ev.file)?.import! };
|
|
84
|
-
}
|
|
85
|
-
console.debug('Manually triggered', ev);
|
|
86
|
-
itr.add(castTo(ev), true);
|
|
39
|
+
process.on('message', event => {
|
|
40
|
+
if (isTestRunEvent(event)) {
|
|
41
|
+
queue.add(event, true);
|
|
87
42
|
}
|
|
88
43
|
});
|
|
89
44
|
|
|
90
45
|
process.send?.({ type: 'ready' } satisfies TestReadyEvent);
|
|
91
46
|
|
|
92
|
-
|
|
47
|
+
const queueProcessor = WorkPool.run(
|
|
93
48
|
buildStandardTestManager.bind(null, consumer),
|
|
94
|
-
|
|
49
|
+
queue,
|
|
95
50
|
{
|
|
96
|
-
idleTimeoutMillis:
|
|
51
|
+
idleTimeoutMillis: TimeUtil.asMillis('2m'),
|
|
97
52
|
min: 2,
|
|
98
53
|
max: WorkPool.DEFAULT_SIZE
|
|
99
54
|
}
|
|
100
55
|
);
|
|
56
|
+
|
|
57
|
+
for await (const event of watchCompiler()) {
|
|
58
|
+
const fileType = ManifestModuleUtil.getFileType(event.file);
|
|
59
|
+
if (
|
|
60
|
+
(fileType === 'ts' || fileType === 'js') &&
|
|
61
|
+
RuntimeIndex.findModuleForArbitraryFile(event.file) !== undefined
|
|
62
|
+
) {
|
|
63
|
+
if (event.action === 'delete') {
|
|
64
|
+
consumer.removeTest(event.import);
|
|
65
|
+
} else {
|
|
66
|
+
const diffSource = consumer.produceDiffSource(event.import);
|
|
67
|
+
queue.add({ import: event.import, diffSource }, true);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Cleanup
|
|
73
|
+
await queueProcessor;
|
|
101
74
|
}
|
|
102
75
|
}
|
package/src/fixture.ts
CHANGED
|
@@ -5,8 +5,8 @@ export class TestFixtures extends FileLoader {
|
|
|
5
5
|
super([
|
|
6
6
|
'@#test/fixtures',
|
|
7
7
|
'@#support/fixtures',
|
|
8
|
-
...modules.flat().map(
|
|
8
|
+
...modules.flat().map(mod => `${mod}#support/fixtures`),
|
|
9
9
|
'@@#support/fixtures'
|
|
10
|
-
].map(
|
|
10
|
+
].map(value => Runtime.modulePath(value)));
|
|
11
11
|
}
|
|
12
12
|
}
|
package/src/model/common.ts
CHANGED
package/src/model/event.ts
CHANGED
|
@@ -14,6 +14,8 @@ export type EventPhase = 'before' | 'after';
|
|
|
14
14
|
type EventTpl<T extends EventEntity, P extends EventPhase, V extends {}> =
|
|
15
15
|
{ type: T, phase: P, metadata?: Record<string, unknown> } & V;
|
|
16
16
|
|
|
17
|
+
export type TestRemoveEvent = { type: 'removeTest', import: string, classId?: string, methodName?: string, metadata?: Record<string, unknown> };
|
|
18
|
+
|
|
17
19
|
/**
|
|
18
20
|
* Different test event shapes
|
|
19
21
|
*/
|
|
@@ -22,4 +24,4 @@ export type TestEvent =
|
|
|
22
24
|
EventTpl<'test', 'before', { test: TestConfig }> |
|
|
23
25
|
EventTpl<'test', 'after', { test: TestResult }> |
|
|
24
26
|
EventTpl<'suite', 'before', { suite: SuiteConfig }> |
|
|
25
|
-
EventTpl<'suite', 'after', { suite: SuiteResult }>;
|
|
27
|
+
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 { Assertion, TestConfig, TestResult } from './test.ts';
|
|
3
|
+
import { Assertion, TestConfig, TestResult, type TestStatus } from './test.ts';
|
|
4
4
|
import { Skip, SuiteCore } from './common.ts';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -22,7 +22,7 @@ export interface SuiteConfig extends SuiteCore {
|
|
|
22
22
|
/**
|
|
23
23
|
* Tests to run
|
|
24
24
|
*/
|
|
25
|
-
tests: Record<string
|
|
25
|
+
tests: Record<string, TestConfig>;
|
|
26
26
|
/**
|
|
27
27
|
* Before all handlers
|
|
28
28
|
*/
|
|
@@ -48,37 +48,26 @@ export interface Counts {
|
|
|
48
48
|
passed: number;
|
|
49
49
|
skipped: number;
|
|
50
50
|
failed: number;
|
|
51
|
+
unknown: number;
|
|
51
52
|
total: number;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
56
|
* Results of a suite run
|
|
56
57
|
*/
|
|
57
|
-
export interface SuiteResult extends Counts {
|
|
58
|
+
export interface SuiteResult extends Counts, SuiteCore {
|
|
58
59
|
/**
|
|
59
|
-
*
|
|
60
|
+
* All test results
|
|
60
61
|
*/
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Import for the suite
|
|
64
|
-
*/
|
|
65
|
-
import: string;
|
|
66
|
-
/**
|
|
67
|
-
* Start of the suite
|
|
68
|
-
*/
|
|
69
|
-
lineStart: number;
|
|
70
|
-
/**
|
|
71
|
-
* End of the suite
|
|
72
|
-
*/
|
|
73
|
-
lineEnd: number;
|
|
74
|
-
/**
|
|
75
|
-
* ALl test results
|
|
76
|
-
*/
|
|
77
|
-
tests: TestResult[];
|
|
62
|
+
tests: Record<string, TestResult>;
|
|
78
63
|
/**
|
|
79
64
|
* Suite duration
|
|
80
65
|
*/
|
|
81
66
|
duration: number;
|
|
67
|
+
/**
|
|
68
|
+
* Overall status
|
|
69
|
+
*/
|
|
70
|
+
status: TestStatus;
|
|
82
71
|
}
|
|
83
72
|
|
|
84
73
|
/**
|
package/src/model/test.ts
CHANGED
|
@@ -2,9 +2,16 @@ import type { Class, ConsoleEvent, TimeSpan } from '@travetto/runtime';
|
|
|
2
2
|
|
|
3
3
|
import { Skip, TestCore } from './common.ts';
|
|
4
4
|
|
|
5
|
-
export type ThrowableError = string | RegExp | Class<Error> | ((
|
|
5
|
+
export type ThrowableError = string | RegExp | Class<Error> | ((error: Error | string) => boolean | void | undefined);
|
|
6
6
|
export type TestLog = Omit<ConsoleEvent, 'args' | 'scope'> & { message: string };
|
|
7
7
|
|
|
8
|
+
export type TestDiffSource = Record<string, {
|
|
9
|
+
sourceHash: number;
|
|
10
|
+
methods: Record<string, number>;
|
|
11
|
+
}>;
|
|
12
|
+
|
|
13
|
+
export type TestStatus = 'passed' | 'skipped' | 'failed' | 'unknown';
|
|
14
|
+
|
|
8
15
|
/**
|
|
9
16
|
* Specific configuration for a test
|
|
10
17
|
*/
|
|
@@ -88,7 +95,7 @@ export interface TestResult extends TestCore {
|
|
|
88
95
|
/**
|
|
89
96
|
* status
|
|
90
97
|
*/
|
|
91
|
-
status:
|
|
98
|
+
status: TestStatus;
|
|
92
99
|
/**
|
|
93
100
|
* Error if failed
|
|
94
101
|
*/
|
|
@@ -136,3 +143,42 @@ export type TestRun = {
|
|
|
136
143
|
*/
|
|
137
144
|
runId?: string;
|
|
138
145
|
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Test Diff Input
|
|
149
|
+
*/
|
|
150
|
+
export type TestDiffInput = {
|
|
151
|
+
/**
|
|
152
|
+
* Import for run
|
|
153
|
+
*/
|
|
154
|
+
import: string;
|
|
155
|
+
/**
|
|
156
|
+
* Diff Source
|
|
157
|
+
*/
|
|
158
|
+
diffSource: TestDiffSource;
|
|
159
|
+
/**
|
|
160
|
+
* Test run metadata
|
|
161
|
+
*/
|
|
162
|
+
metadata?: Record<string, unknown>;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Test Glob Input
|
|
167
|
+
*/
|
|
168
|
+
export type TestGlobInput = {
|
|
169
|
+
/**
|
|
170
|
+
* Globs to run
|
|
171
|
+
*/
|
|
172
|
+
globs: string[];
|
|
173
|
+
/**
|
|
174
|
+
* Tags to filter by
|
|
175
|
+
*/
|
|
176
|
+
tags?: string[];
|
|
177
|
+
/**
|
|
178
|
+
* Test run metadata
|
|
179
|
+
*/
|
|
180
|
+
metadata?: Record<string, unknown>;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
export type TestRunInput = TestRun | TestDiffInput | TestGlobInput;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Counts } from './suite';
|
|
2
|
+
import type { TestStatus } from './test';
|
|
3
|
+
|
|
4
|
+
export class TestModelUtil {
|
|
5
|
+
static countsToTestStatus(counts: Counts): TestStatus {
|
|
6
|
+
return counts.failed ? 'failed' : (counts.passed ? 'passed' : counts.skipped ? 'skipped' : 'unknown');
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -6,24 +6,24 @@ import { SuiteConfig } from '../model/suite';
|
|
|
6
6
|
import { TestConfig } from '../model/test';
|
|
7
7
|
|
|
8
8
|
function combineClasses(baseConfig: SuiteConfig, ...subConfig: Partial<SuiteConfig>[]): SuiteConfig {
|
|
9
|
-
for (const
|
|
10
|
-
if (
|
|
11
|
-
baseConfig.beforeAll = [...baseConfig.beforeAll, ...
|
|
9
|
+
for (const config of subConfig) {
|
|
10
|
+
if (config.beforeAll) {
|
|
11
|
+
baseConfig.beforeAll = [...baseConfig.beforeAll, ...config.beforeAll];
|
|
12
12
|
}
|
|
13
|
-
if (
|
|
14
|
-
baseConfig.beforeEach = [...baseConfig.beforeEach, ...
|
|
13
|
+
if (config.beforeEach) {
|
|
14
|
+
baseConfig.beforeEach = [...baseConfig.beforeEach, ...config.beforeEach];
|
|
15
15
|
}
|
|
16
|
-
if (
|
|
17
|
-
baseConfig.afterAll = [...baseConfig.afterAll, ...
|
|
16
|
+
if (config.afterAll) {
|
|
17
|
+
baseConfig.afterAll = [...baseConfig.afterAll, ...config.afterAll];
|
|
18
18
|
}
|
|
19
|
-
if (
|
|
20
|
-
baseConfig.afterEach = [...baseConfig.afterEach, ...
|
|
19
|
+
if (config.afterEach) {
|
|
20
|
+
baseConfig.afterEach = [...baseConfig.afterEach, ...config.afterEach];
|
|
21
21
|
}
|
|
22
|
-
if (
|
|
23
|
-
baseConfig.tags = [...baseConfig.tags ?? [], ...
|
|
22
|
+
if (config.tags) {
|
|
23
|
+
baseConfig.tags = [...baseConfig.tags ?? [], ...config.tags];
|
|
24
24
|
}
|
|
25
|
-
if (
|
|
26
|
-
for (const [key, test] of Object.entries(
|
|
25
|
+
if (config.tests) {
|
|
26
|
+
for (const [key, test] of Object.entries(config.tests ?? {})) {
|
|
27
27
|
baseConfig.tests[key] = {
|
|
28
28
|
...test,
|
|
29
29
|
sourceImport: Runtime.getImport(baseConfig.class),
|
|
@@ -40,11 +40,11 @@ function combineClasses(baseConfig: SuiteConfig, ...subConfig: Partial<SuiteConf
|
|
|
40
40
|
function combineMethods(suite: SuiteConfig, baseConfig: TestConfig, ...subConfig: Partial<TestConfig>[]): TestConfig {
|
|
41
41
|
baseConfig.classId = suite.classId;
|
|
42
42
|
baseConfig.import = suite.import;
|
|
43
|
-
for (const
|
|
44
|
-
safeAssign(baseConfig,
|
|
43
|
+
for (const config of subConfig) {
|
|
44
|
+
safeAssign(baseConfig, config, {
|
|
45
45
|
tags: [
|
|
46
46
|
...baseConfig.tags ?? [],
|
|
47
|
-
...
|
|
47
|
+
...config.tags ?? []
|
|
48
48
|
]
|
|
49
49
|
});
|
|
50
50
|
}
|
|
@@ -61,7 +61,7 @@ export class SuiteRegistryAdapter implements RegistryAdapter<SuiteConfig> {
|
|
|
61
61
|
|
|
62
62
|
register(...data: Partial<SuiteConfig>[]): SuiteConfig {
|
|
63
63
|
if (!this.#config) {
|
|
64
|
-
const lines = describeFunction(this.#cls)
|
|
64
|
+
const { lines, hash } = describeFunction(this.#cls) ?? {};
|
|
65
65
|
this.#config = asFull<SuiteConfig>({
|
|
66
66
|
class: this.#cls,
|
|
67
67
|
classId: this.#cls.Ⲑid,
|
|
@@ -69,6 +69,7 @@ export class SuiteRegistryAdapter implements RegistryAdapter<SuiteConfig> {
|
|
|
69
69
|
import: Runtime.getImport(this.#cls),
|
|
70
70
|
lineStart: lines?.[0],
|
|
71
71
|
lineEnd: lines?.[1],
|
|
72
|
+
sourceHash: hash,
|
|
72
73
|
tests: {},
|
|
73
74
|
beforeAll: [],
|
|
74
75
|
beforeEach: [],
|
|
@@ -80,11 +81,11 @@ export class SuiteRegistryAdapter implements RegistryAdapter<SuiteConfig> {
|
|
|
80
81
|
return this.#config;
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
registerTest(method: string
|
|
84
|
+
registerTest(method: string, ...data: Partial<TestConfig>[]): TestConfig {
|
|
84
85
|
const suite = this.register();
|
|
85
86
|
|
|
86
87
|
if (!(method in this.#config.tests)) {
|
|
87
|
-
const lines = describeFunction(this.#cls)?.methods?.[method]
|
|
88
|
+
const { lines, hash } = describeFunction(this.#cls)?.methods?.[method] ?? {};
|
|
88
89
|
const config = asFull<TestConfig>({
|
|
89
90
|
class: this.#cls,
|
|
90
91
|
tags: [],
|
|
@@ -92,7 +93,8 @@ export class SuiteRegistryAdapter implements RegistryAdapter<SuiteConfig> {
|
|
|
92
93
|
lineStart: lines?.[0],
|
|
93
94
|
lineEnd: lines?.[1],
|
|
94
95
|
lineBodyStart: lines?.[2],
|
|
95
|
-
methodName: method
|
|
96
|
+
methodName: method,
|
|
97
|
+
sourceHash: hash,
|
|
96
98
|
});
|
|
97
99
|
this.#config.tests[method] = config;
|
|
98
100
|
}
|
|
@@ -117,7 +119,7 @@ export class SuiteRegistryAdapter implements RegistryAdapter<SuiteConfig> {
|
|
|
117
119
|
return this.#config;
|
|
118
120
|
}
|
|
119
121
|
|
|
120
|
-
getMethod(method: string
|
|
122
|
+
getMethod(method: string): TestConfig {
|
|
121
123
|
const test = this.#config.tests[method];
|
|
122
124
|
if (!test) {
|
|
123
125
|
throw new AppError(`Test not registered: ${String(method)} on ${this.#cls.name}`);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { AppError, Class, Runtime, describeFunction } from '@travetto/runtime';
|
|
2
|
-
import {
|
|
2
|
+
import { RegistryIndex, RegistryIndexStore, Registry } from '@travetto/registry';
|
|
3
3
|
|
|
4
4
|
import { SuiteConfig } from '../model/suite.ts';
|
|
5
5
|
import { TestConfig, TestRun } from '../model/test.ts';
|
|
6
6
|
import { SuiteRegistryAdapter } from './registry-adapter.ts';
|
|
7
7
|
|
|
8
|
-
const sortedTests = (
|
|
9
|
-
Object.values(
|
|
8
|
+
const sortedTests = (config: SuiteConfig): TestConfig[] =>
|
|
9
|
+
Object.values(config.tests).toSorted((a, b) => a.lineStart - b.lineStart);
|
|
10
10
|
|
|
11
11
|
type SuiteTests = { suite: SuiteConfig, tests: TestConfig[] };
|
|
12
12
|
|
|
@@ -21,11 +21,7 @@ export class SuiteRegistryIndex implements RegistryIndex {
|
|
|
21
21
|
return this.#instance.store.getForRegister(cls);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
static
|
|
25
|
-
return this.#instance.store.has(cls);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
static getTestConfig(cls: Class, method: Function): TestConfig | undefined {
|
|
24
|
+
static getTestConfig(cls: Class, method: Function | string): TestConfig | undefined {
|
|
29
25
|
return this.#instance.getTestConfig(cls, method);
|
|
30
26
|
}
|
|
31
27
|
|
|
@@ -41,17 +37,19 @@ export class SuiteRegistryIndex implements RegistryIndex {
|
|
|
41
37
|
return this.#instance.store.getClasses();
|
|
42
38
|
}
|
|
43
39
|
|
|
40
|
+
static hasConfig(cls: Class): boolean {
|
|
41
|
+
return this.#instance.store.has(cls);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
44
|
store = new RegistryIndexStore(SuiteRegistryAdapter);
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
// No-op for now
|
|
48
|
-
}
|
|
46
|
+
/** @private */ constructor(source: unknown) { Registry.validateConstructor(source); }
|
|
49
47
|
|
|
50
48
|
/**
|
|
51
49
|
* Find all valid tests (ignoring abstract)
|
|
52
50
|
*/
|
|
53
51
|
getValidClasses(): Class[] {
|
|
54
|
-
return this.store.getClasses().filter(
|
|
52
|
+
return this.store.getClasses().filter(cls => !describeFunction(cls).abstract);
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
getConfig(cls: Class): SuiteConfig {
|
|
@@ -70,27 +68,28 @@ export class SuiteRegistryIndex implements RegistryIndex {
|
|
|
70
68
|
const line = parseInt(clsId, 10);
|
|
71
69
|
const suites = this.getValidClasses()
|
|
72
70
|
.filter(cls => Runtime.getImport(cls) === imp)
|
|
73
|
-
.map(
|
|
74
|
-
|
|
71
|
+
.map(cls => this.getConfig(cls))
|
|
72
|
+
.filter(config => !config.skip);
|
|
73
|
+
const suite = suites.find(config => line >= config.lineStart && line <= config.lineEnd);
|
|
75
74
|
|
|
76
75
|
if (suite) {
|
|
77
76
|
const tests = sortedTests(suite);
|
|
78
|
-
const test = tests.find(
|
|
77
|
+
const test = tests.find(config => line >= config.lineStart && line <= config.lineEnd);
|
|
79
78
|
return test ? [{ suite, tests: [test] }] : [{ suite, tests }];
|
|
80
79
|
} else {
|
|
81
|
-
return suites.map(
|
|
80
|
+
return suites.map(config => ({ suite: config, tests: sortedTests(config) }));
|
|
82
81
|
}
|
|
83
82
|
} else { // Else lookup directly
|
|
84
83
|
if (methodNames.length) {
|
|
85
|
-
const cls = this.getValidClasses().find(
|
|
84
|
+
const cls = this.getValidClasses().find(type => type.Ⲑid === clsId);
|
|
86
85
|
if (!cls) {
|
|
87
86
|
throw new AppError('Unable to find suite for class ID', { details: { classId: clsId } });
|
|
88
87
|
}
|
|
89
88
|
const suite = this.getConfig(cls);
|
|
90
|
-
const tests = sortedTests(suite).filter(
|
|
89
|
+
const tests = sortedTests(suite).filter(config => methodNames.includes(config.methodName));
|
|
91
90
|
return [{ suite, tests }];
|
|
92
91
|
} else if (clsId) {
|
|
93
|
-
const cls = this.getValidClasses().find(
|
|
92
|
+
const cls = this.getValidClasses().find(type => type.Ⲑid === clsId)!;
|
|
94
93
|
if (!cls) {
|
|
95
94
|
throw new AppError('Unable to find suite for class ID', { details: { classId: clsId } });
|
|
96
95
|
}
|
|
@@ -98,9 +97,9 @@ export class SuiteRegistryIndex implements RegistryIndex {
|
|
|
98
97
|
return suite ? [{ suite, tests: sortedTests(suite) }] : [];
|
|
99
98
|
} else {
|
|
100
99
|
const suites = this.getValidClasses()
|
|
101
|
-
.map(
|
|
102
|
-
.filter(
|
|
103
|
-
return suites.map(
|
|
100
|
+
.map(type => this.getConfig(type))
|
|
101
|
+
.filter(config => !describeFunction(config.class).abstract); // Do not run abstract suites
|
|
102
|
+
return suites.map(config => ({ suite: config, tests: sortedTests(config) }));
|
|
104
103
|
}
|
|
105
104
|
}
|
|
106
105
|
}
|
|
@@ -108,10 +107,11 @@ export class SuiteRegistryIndex implements RegistryIndex {
|
|
|
108
107
|
/**
|
|
109
108
|
* Find a test configuration given class and optionally a method
|
|
110
109
|
*/
|
|
111
|
-
getTestConfig(cls: Class, method: Function): TestConfig | undefined {
|
|
110
|
+
getTestConfig(cls: Class, method: Function | string): TestConfig | undefined {
|
|
112
111
|
if (this.store.has(cls)) {
|
|
113
|
-
const
|
|
114
|
-
|
|
112
|
+
const config = this.getConfig(cls);
|
|
113
|
+
const methodName = typeof method === 'string' ? method : method.name;
|
|
114
|
+
return Object.values(config.tests).find(item => item.methodName === methodName);
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
}
|
package/src/worker/child.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { createWriteStream } from 'node:fs';
|
|
2
2
|
|
|
3
|
-
import { ConsoleManager, Env,
|
|
3
|
+
import { ConsoleManager, Env, Runtime } from '@travetto/runtime';
|
|
4
4
|
import { IpcChannel } from '@travetto/worker';
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { Events } from './types.ts';
|
|
6
|
+
import { RunUtil } from '../execute/run.ts';
|
|
7
|
+
import { TestWorkerEvents } from './types.ts';
|
|
9
8
|
import { TestRun } from '../model/test.ts';
|
|
9
|
+
import { CommunicationUtil } from '../communication.ts';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Child Worker for the Test Runner. Receives events as commands
|
|
@@ -16,16 +16,16 @@ export class TestChildWorker extends IpcChannel<TestRun> {
|
|
|
16
16
|
|
|
17
17
|
#done = Promise.withResolvers<void>();
|
|
18
18
|
|
|
19
|
-
async #exec(
|
|
19
|
+
async #exec(operation: () => Promise<unknown>, type: string): Promise<void> {
|
|
20
20
|
try {
|
|
21
|
-
await
|
|
21
|
+
await operation();
|
|
22
22
|
this.send(type); // Respond
|
|
23
|
-
} catch (
|
|
24
|
-
if (!(
|
|
25
|
-
throw
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (!(error instanceof Error)) {
|
|
25
|
+
throw error;
|
|
26
26
|
}
|
|
27
27
|
// Mark as errored out
|
|
28
|
-
this.send(type,
|
|
28
|
+
this.send(type, CommunicationUtil.serializeToObject({ error }));
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -33,22 +33,22 @@ export class TestChildWorker extends IpcChannel<TestRun> {
|
|
|
33
33
|
* Start the worker
|
|
34
34
|
*/
|
|
35
35
|
async activate(): Promise<void> {
|
|
36
|
-
if (/\b@travetto[/]test\b/.test(Env.DEBUG.
|
|
36
|
+
if (/\b@travetto[/]test\b/.test(Env.DEBUG.value ?? '')) {
|
|
37
37
|
const file = Runtime.toolPath(`test-worker.${process.pid}.log`);
|
|
38
38
|
const stdout = createWriteStream(file, { flags: 'a' });
|
|
39
|
-
const
|
|
40
|
-
ConsoleManager.set({ log: (
|
|
39
|
+
const cons = new console.Console({ stdout, inspectOptions: { depth: 4, colors: false } });
|
|
40
|
+
ConsoleManager.set({ log: (event) => cons[event.level](process.pid, ...event.args) });
|
|
41
41
|
} else {
|
|
42
42
|
ConsoleManager.set({ log: () => { } });
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
RunUtil.registerCleanup('worker');
|
|
46
46
|
|
|
47
47
|
// Listen for inbound requests
|
|
48
|
-
this.on('*',
|
|
48
|
+
this.on('*', event => this.onCommand(event));
|
|
49
49
|
|
|
50
50
|
// Let parent know the child is ready for handling commands
|
|
51
|
-
this.send(
|
|
51
|
+
this.send(TestWorkerEvents.READY);
|
|
52
52
|
|
|
53
53
|
await this.#done.promise;
|
|
54
54
|
}
|
|
@@ -59,10 +59,10 @@ export class TestChildWorker extends IpcChannel<TestRun> {
|
|
|
59
59
|
async onCommand(event: TestRun & { type: string }): Promise<boolean> {
|
|
60
60
|
console.debug('on message', { ...event });
|
|
61
61
|
|
|
62
|
-
if (event.type ===
|
|
63
|
-
await this.#exec(() => this.onInitCommand(),
|
|
64
|
-
} else if (event.type ===
|
|
65
|
-
await this.#exec(() => this.onRunCommand(event),
|
|
62
|
+
if (event.type === TestWorkerEvents.INIT) { // On request to init, start initialization
|
|
63
|
+
await this.#exec(() => this.onInitCommand(), TestWorkerEvents.INIT_COMPLETE);
|
|
64
|
+
} else if (event.type === TestWorkerEvents.RUN) { // On request to run, start running
|
|
65
|
+
await this.#exec(() => this.onRunCommand(event), TestWorkerEvents.RUN_COMPLETE);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
return false;
|
|
@@ -80,7 +80,7 @@ export class TestChildWorker extends IpcChannel<TestRun> {
|
|
|
80
80
|
console.debug('Running', { import: run.import });
|
|
81
81
|
|
|
82
82
|
try {
|
|
83
|
-
await
|
|
83
|
+
await RunUtil.runTests({ consumer: 'exec' }, run);
|
|
84
84
|
} finally {
|
|
85
85
|
this.#done.resolve();
|
|
86
86
|
}
|