@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/worker/standard.ts
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import { fork } from 'node:child_process';
|
|
2
2
|
|
|
3
|
-
import { Env, RuntimeIndex
|
|
3
|
+
import { Env, RuntimeIndex } from '@travetto/runtime';
|
|
4
4
|
import { IpcChannel } from '@travetto/worker';
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import { TestConsumerShape } from '../consumer/types.ts';
|
|
8
|
-
import { TestEvent } from '../model/event.ts';
|
|
9
|
-
import { TestRun } from '../model/test.ts';
|
|
6
|
+
import { TestWorkerEvents, type TestLogEvent } from './types.ts';
|
|
7
|
+
import type { TestConsumerShape } from '../consumer/types.ts';
|
|
8
|
+
import type { TestEvent, TestRemoveEvent } from '../model/event.ts';
|
|
9
|
+
import type { TestDiffInput, TestRun } from '../model/test.ts';
|
|
10
|
+
import { CommunicationUtil } from '../communication.ts';
|
|
10
11
|
|
|
11
|
-
const log = (message: string): void => {
|
|
12
|
-
|
|
12
|
+
const log = (message: string | TestLogEvent): void => {
|
|
13
|
+
const event: TestLogEvent = typeof message === 'string' ? { type: 'log', message } : message;
|
|
14
|
+
process.send ? process.send?.(event) : console.debug(event.message);
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* Produce a handler for the child worker
|
|
17
19
|
*/
|
|
18
|
-
export async function buildStandardTestManager(consumer: TestConsumerShape, run: TestRun): Promise<void> {
|
|
20
|
+
export async function buildStandardTestManager(consumer: TestConsumerShape, run: TestRun | TestDiffInput): Promise<void> {
|
|
19
21
|
log(`Worker Input ${JSON.stringify(run)}`);
|
|
20
|
-
log(`Worker Executing ${run.import}`);
|
|
21
22
|
|
|
22
|
-
const
|
|
23
|
-
const suiteMod = RuntimeIndex.getModule(module)!;
|
|
23
|
+
const suiteMod = RuntimeIndex.findModuleForArbitraryImport(run.import)!;
|
|
24
24
|
|
|
25
25
|
const channel = new IpcChannel<TestEvent & { error?: Error }>(
|
|
26
26
|
fork(
|
|
@@ -37,25 +37,34 @@ export async function buildStandardTestManager(consumer: TestConsumerShape, run:
|
|
|
37
37
|
)
|
|
38
38
|
);
|
|
39
39
|
|
|
40
|
-
await channel.once(
|
|
41
|
-
await channel.send(
|
|
42
|
-
await channel.once(
|
|
40
|
+
await channel.once(TestWorkerEvents.READY); // Wait for the child to be ready
|
|
41
|
+
await channel.send(TestWorkerEvents.INIT); // Initialize
|
|
42
|
+
await channel.once(TestWorkerEvents.INIT_COMPLETE); // Wait for complete
|
|
43
43
|
|
|
44
|
-
channel.on('*', async
|
|
44
|
+
channel.on('*', async event => {
|
|
45
45
|
try {
|
|
46
|
-
|
|
46
|
+
const parsed: TestEvent | TestRemoveEvent | TestLogEvent = CommunicationUtil.deserializeFromObject(event);
|
|
47
|
+
if (parsed.type === 'log') {
|
|
48
|
+
log(parsed);
|
|
49
|
+
} else if (parsed.type === 'removeTest') {
|
|
50
|
+
log(`Received remove event ${JSON.stringify(event)}@${consumer.constructor.name}`);
|
|
51
|
+
await consumer.onRemoveEvent?.(parsed); // Forward remove events
|
|
52
|
+
} else {
|
|
53
|
+
await consumer.onEvent(parsed); // Forward standard events
|
|
54
|
+
}
|
|
47
55
|
} catch {
|
|
48
56
|
// Do nothing
|
|
49
57
|
}
|
|
50
58
|
});
|
|
51
59
|
|
|
52
60
|
// Listen for child to complete
|
|
53
|
-
const complete = channel.once(
|
|
61
|
+
const complete = channel.once(TestWorkerEvents.RUN_COMPLETE);
|
|
54
62
|
// Start test
|
|
55
|
-
channel.send(
|
|
63
|
+
channel.send(TestWorkerEvents.RUN, run);
|
|
56
64
|
|
|
57
65
|
// Wait for complete
|
|
58
|
-
const
|
|
66
|
+
const completedEvent = await complete;
|
|
67
|
+
const result: { error?: unknown } = await CommunicationUtil.deserializeFromObject(completedEvent);
|
|
59
68
|
|
|
60
69
|
// Kill on complete
|
|
61
70
|
await channel.destroy();
|
package/src/worker/types.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { TestEvent } from '../model/event.ts';
|
|
2
|
-
import { TestRun } from '../model/test.ts';
|
|
1
|
+
import { TestEvent, TestRemoveEvent } from '../model/event.ts';
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Test Run Event Keys
|
|
6
5
|
*/
|
|
7
|
-
export const
|
|
6
|
+
export const TestWorkerEvents = {
|
|
8
7
|
RUN: 'run',
|
|
9
8
|
RUN_COMPLETE: 'runComplete',
|
|
10
9
|
INIT: 'init',
|
|
@@ -12,12 +11,17 @@ export const Events = {
|
|
|
12
11
|
READY: 'ready'
|
|
13
12
|
};
|
|
14
13
|
|
|
15
|
-
export type
|
|
14
|
+
export type TestRunEvent = { type: 'runTest', import: string };
|
|
15
|
+
|
|
16
|
+
export const isTestRunEvent = (event: unknown): event is TestRunEvent =>
|
|
17
|
+
typeof event === 'object' && !!event && 'type' in event && event.type === 'runTest';
|
|
18
|
+
|
|
19
|
+
|
|
16
20
|
export type TestReadyEvent = { type: 'ready' };
|
|
17
21
|
export type TestLogEvent = { type: 'log', message: string };
|
|
18
22
|
|
|
19
23
|
export type TestWatchEvent =
|
|
20
24
|
TestEvent |
|
|
21
|
-
|
|
25
|
+
TestRemoveEvent |
|
|
22
26
|
TestReadyEvent |
|
|
23
27
|
TestLogEvent;
|
package/support/bin/run.ts
CHANGED
|
@@ -2,23 +2,23 @@ import { getClass, Runtime } from '@travetto/runtime';
|
|
|
2
2
|
import { SchemaRegistryIndex } from '@travetto/schema';
|
|
3
3
|
|
|
4
4
|
import { TestConsumerRegistryIndex } from '../../src/consumer/registry-index.ts';
|
|
5
|
-
import type {
|
|
5
|
+
import type { TestConsumerConfig } from '../../src/execute/types.ts';
|
|
6
|
+
import type { TestRunInput } from '../../src/model/test.ts';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Run tests given the input state
|
|
9
|
-
* @param
|
|
10
|
+
* @param state
|
|
10
11
|
*/
|
|
11
|
-
export async function runTests(
|
|
12
|
-
const {
|
|
13
|
-
const { Runner } = await import('../../src/execute/runner.ts');
|
|
12
|
+
export async function runTests(state: TestConsumerConfig, input: TestRunInput): Promise<void> {
|
|
13
|
+
const { RunUtil } = await import('../../src/execute/run.ts');
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
RunUtil.registerCleanup('runner');
|
|
16
16
|
|
|
17
17
|
try {
|
|
18
|
-
const
|
|
19
|
-
process.exitCode =
|
|
20
|
-
} catch (
|
|
21
|
-
console.error('Test Worker Failed', { error
|
|
18
|
+
const result = await RunUtil.runTests(state, input);
|
|
19
|
+
process.exitCode = result ? 0 : 1;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error('Test Worker Failed', { error });
|
|
22
22
|
process.exitCode = 1;
|
|
23
23
|
}
|
|
24
24
|
}
|
package/support/cli.test.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
|
-
import fs from 'node:fs/promises';
|
|
3
|
-
import path from 'node:path';
|
|
4
2
|
|
|
5
|
-
import { Env } from '@travetto/runtime';
|
|
6
|
-
import { CliCommandShape, CliCommand,
|
|
3
|
+
import { Env, RuntimeIndex } from '@travetto/runtime';
|
|
4
|
+
import { CliCommandShape, CliCommand, CliUtil } from '@travetto/cli';
|
|
7
5
|
import { WorkPool } from '@travetto/worker';
|
|
8
6
|
import { Max, Min } from '@travetto/schema';
|
|
9
7
|
|
|
@@ -20,11 +18,8 @@ export class TestCommand implements CliCommandShape {
|
|
|
20
18
|
@Min(1) @Max(WorkPool.MAX_SIZE)
|
|
21
19
|
concurrency: number = WorkPool.DEFAULT_SIZE;
|
|
22
20
|
|
|
23
|
-
/** Test run mode */
|
|
24
|
-
mode: 'single' | 'standard' = 'standard';
|
|
25
|
-
|
|
26
21
|
/**
|
|
27
|
-
* Tags to target or exclude
|
|
22
|
+
* Tags to target or exclude when using globs
|
|
28
23
|
* @alias env.TRV_TEST_TAGS
|
|
29
24
|
*/
|
|
30
25
|
tags?: string[];
|
|
@@ -44,46 +39,30 @@ export class TestCommand implements CliCommandShape {
|
|
|
44
39
|
Env.TRV_LOG_TIME.clear();
|
|
45
40
|
}
|
|
46
41
|
|
|
47
|
-
isFirstFile(first: string): Promise<boolean> {
|
|
48
|
-
return fs.stat(path.resolve(first ?? '')).then(x => x.isFile(), () => false);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async resolvedMode(first: string, rest: string[]): Promise<string> {
|
|
52
|
-
return (await this.isFirstFile(first)) && rest.length === 0 ? 'single' : this.mode;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
42
|
async preValidate(): Promise<void> {
|
|
56
43
|
const { selectConsumer } = await import('./bin/run.ts');
|
|
57
44
|
await selectConsumer(this);
|
|
58
45
|
}
|
|
59
46
|
|
|
60
|
-
async validate(first: string = '**/*', rest: string[]): Promise<CliValidationError | undefined> {
|
|
61
|
-
const mode = await this.resolvedMode(first, rest);
|
|
62
|
-
|
|
63
|
-
if (mode === 'single' && !await this.isFirstFile(first)) {
|
|
64
|
-
return { message: 'You must specify a proper test file to run in single mode', source: 'arg' };
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
47
|
async main(first: string = '**/*', globs: string[] = []): Promise<void> {
|
|
69
48
|
const { runTests } = await import('./bin/run.ts');
|
|
70
49
|
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
50
|
+
const importPath = RuntimeIndex.getFromImportOrSource(first)?.import;
|
|
51
|
+
|
|
52
|
+
return runTests(
|
|
53
|
+
{
|
|
54
|
+
concurrency: this.concurrency,
|
|
55
|
+
consumer: this.format,
|
|
56
|
+
consumerOptions: CliUtil.readExtendedOptions(this.formatOptions),
|
|
57
|
+
},
|
|
58
|
+
importPath ? {
|
|
59
|
+
import: importPath,
|
|
60
|
+
classId: globs[0],
|
|
61
|
+
methodNames: globs.slice(1),
|
|
62
|
+
} : {
|
|
63
|
+
globs: [first, ...globs],
|
|
64
|
+
tags: this.tags,
|
|
65
|
+
}
|
|
66
|
+
);
|
|
88
67
|
}
|
|
89
68
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Env, JSONUtil, RuntimeIndex } from '@travetto/runtime';
|
|
2
|
+
import { CliCommand, CliUtil } from '@travetto/cli';
|
|
3
|
+
import { IsPrivate } from '@travetto/schema';
|
|
4
|
+
|
|
5
|
+
import { runTests, selectConsumer } from './bin/run.ts';
|
|
6
|
+
import type { TestDiffSource } from '../src/model/test.ts';
|
|
7
|
+
|
|
8
|
+
/** Direct test invocation */
|
|
9
|
+
@CliCommand()
|
|
10
|
+
@IsPrivate()
|
|
11
|
+
export class TestDiffCommand {
|
|
12
|
+
|
|
13
|
+
format: string = 'tap';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Format options
|
|
17
|
+
* @alias o
|
|
18
|
+
*/
|
|
19
|
+
formatOptions?: string[];
|
|
20
|
+
|
|
21
|
+
async preValidate(): Promise<void> {
|
|
22
|
+
await selectConsumer(this);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
preMain(): void {
|
|
26
|
+
Env.TRV_ROLE.set('test');
|
|
27
|
+
Env.TRV_ENV.set('test');
|
|
28
|
+
Env.TRV_LOG_PLAIN.set(true);
|
|
29
|
+
Env.TRV_LOG_TIME.clear();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async main(importOrFile: string, diff: string): Promise<void> {
|
|
33
|
+
const diffSource: TestDiffSource = await JSONUtil.readFile(diff);
|
|
34
|
+
const importPath = RuntimeIndex.getFromImportOrSource(importOrFile)?.import!;
|
|
35
|
+
|
|
36
|
+
return runTests(
|
|
37
|
+
{
|
|
38
|
+
consumer: this.format,
|
|
39
|
+
consumerOptions: CliUtil.readExtendedOptions(this.formatOptions),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
import: importPath,
|
|
43
|
+
diffSource
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -4,7 +4,7 @@ import { Registry } from '@travetto/registry';
|
|
|
4
4
|
import { IsPrivate } from '@travetto/schema';
|
|
5
5
|
|
|
6
6
|
import { SuiteRegistryIndex } from '../src/registry/registry-index.ts';
|
|
7
|
-
import {
|
|
7
|
+
import { RunUtil } from '../src/execute/run.ts';
|
|
8
8
|
|
|
9
9
|
@CliCommand()
|
|
10
10
|
@IsPrivate()
|
|
@@ -19,11 +19,11 @@ export class TestDigestCommand {
|
|
|
19
19
|
|
|
20
20
|
async main(globs: string[] = ['**/*']) {
|
|
21
21
|
// Load all tests
|
|
22
|
-
for await (const imp of await
|
|
22
|
+
for await (const imp of await RunUtil.getTestImports(globs)) {
|
|
23
23
|
try {
|
|
24
24
|
await Runtime.importFrom(imp);
|
|
25
|
-
} catch (
|
|
26
|
-
console.error('Failed to import', imp,
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error('Failed to import', imp, error);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -31,9 +31,9 @@ export class TestDigestCommand {
|
|
|
31
31
|
|
|
32
32
|
const suites = SuiteRegistryIndex.getClasses();
|
|
33
33
|
const all = suites
|
|
34
|
-
.map(
|
|
35
|
-
.filter(
|
|
36
|
-
.flatMap(
|
|
34
|
+
.map(cls => SuiteRegistryIndex.getConfig(cls))
|
|
35
|
+
.filter(config => !describeFunction(config.class).abstract)
|
|
36
|
+
.flatMap(config => Object.values(config.tests))
|
|
37
37
|
.toSorted((a, b) => {
|
|
38
38
|
const classComp = a.classId.localeCompare(b.classId);
|
|
39
39
|
return classComp !== 0 ? classComp : a.methodName.localeCompare(b.methodName);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Env } from '@travetto/runtime';
|
|
2
|
-
import { CliCommand } from '@travetto/cli';
|
|
1
|
+
import { Env, RuntimeIndex } from '@travetto/runtime';
|
|
2
|
+
import { CliCommand, CliUtil } from '@travetto/cli';
|
|
3
3
|
import { IsPrivate } from '@travetto/schema';
|
|
4
4
|
|
|
5
5
|
import { runTests, selectConsumer } from './bin/run.ts';
|
|
@@ -9,7 +9,6 @@ import { runTests, selectConsumer } from './bin/run.ts';
|
|
|
9
9
|
@IsPrivate()
|
|
10
10
|
export class TestDirectCommand {
|
|
11
11
|
|
|
12
|
-
@IsPrivate()
|
|
13
12
|
format: string = 'tap';
|
|
14
13
|
|
|
15
14
|
/**
|
|
@@ -30,17 +29,19 @@ export class TestDirectCommand {
|
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
main(importOrFile: string, clsId?: string, methodsNames: string[] = []): Promise<void> {
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
return runTests(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
32
|
+
// Resolve to import
|
|
33
|
+
const importPath = RuntimeIndex.getFromImportOrSource(importOrFile)?.import!;
|
|
34
|
+
|
|
35
|
+
return runTests(
|
|
36
|
+
{
|
|
37
|
+
consumer: this.format,
|
|
38
|
+
consumerOptions: CliUtil.readExtendedOptions(this.formatOptions),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
import: importPath,
|
|
41
42
|
classId: clsId,
|
|
42
43
|
methodNames: methodsNames,
|
|
43
44
|
}
|
|
44
|
-
|
|
45
|
+
);
|
|
45
46
|
}
|
|
46
47
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Env } from '@travetto/runtime';
|
|
2
|
-
import { CliCommand
|
|
2
|
+
import { CliCommand } from '@travetto/cli';
|
|
3
3
|
|
|
4
4
|
import { selectConsumer } from './bin/run.ts';
|
|
5
5
|
|
|
@@ -18,19 +18,14 @@ export class TestWatcherCommand {
|
|
|
18
18
|
|
|
19
19
|
preMain(): void {
|
|
20
20
|
Env.TRV_ROLE.set('test');
|
|
21
|
-
Env.TRV_DYNAMIC.set(true);
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
async main(): Promise<void> {
|
|
25
|
-
if (await CliUtil.runWithRestart(this, true)) {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
24
|
try {
|
|
30
25
|
const { TestWatcher } = await import('../src/execute/watcher.ts');
|
|
31
26
|
await TestWatcher.watch(this.format, this.mode === 'all');
|
|
32
|
-
} catch (
|
|
33
|
-
console.error(
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error(error);
|
|
34
29
|
}
|
|
35
30
|
}
|
|
36
31
|
}
|
|
@@ -96,10 +96,10 @@ export class AssertTransformer {
|
|
|
96
96
|
static lookupOpToken(key: number): string | undefined {
|
|
97
97
|
if (OP_TOKEN_TO_NAME.size === 0) {
|
|
98
98
|
Object.keys(ts.SyntaxKind)
|
|
99
|
-
.filter(
|
|
100
|
-
.filter((
|
|
101
|
-
.forEach(
|
|
102
|
-
OP_TOKEN_TO_NAME.set(ts.SyntaxKind[
|
|
99
|
+
.filter(kind => !/^\d+$/.test(kind))
|
|
100
|
+
.filter((kind): kind is keyof typeof OPTOKEN_ASSERT => !/^(Last|First)/.test(kind))
|
|
101
|
+
.forEach(kind =>
|
|
102
|
+
OP_TOKEN_TO_NAME.set(ts.SyntaxKind[kind], kind));
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
const name = OP_TOKEN_TO_NAME.get(key)!;
|
|
@@ -123,10 +123,10 @@ export class AssertTransformer {
|
|
|
123
123
|
|
|
124
124
|
// If looking at an identifier, see if it's in a diff file or if its const
|
|
125
125
|
if (!found && ts.isIdentifier(node)) {
|
|
126
|
-
found = !!state.getDeclarations(node).find(
|
|
126
|
+
found = !!state.getDeclarations(node).find(declaration =>
|
|
127
127
|
// In a separate file or is const
|
|
128
|
-
|
|
129
|
-
DeclarationUtil.isConstantDeclaration(
|
|
128
|
+
declaration.getSourceFile().fileName !== state.source.fileName ||
|
|
129
|
+
DeclarationUtil.isConstantDeclaration(declaration));
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
return found;
|
|
@@ -137,7 +137,7 @@ export class AssertTransformer {
|
|
|
137
137
|
*/
|
|
138
138
|
static initState(state: TransformerState & AssertState): void {
|
|
139
139
|
if (!state[AssertSymbol]) {
|
|
140
|
-
const asrt = state.importFile('@travetto/test/src/assert/check.ts').
|
|
140
|
+
const asrt = state.importFile('@travetto/test/src/assert/check.ts').identifier;
|
|
141
141
|
state[AssertSymbol] = {
|
|
142
142
|
assert: asrt,
|
|
143
143
|
assertCheck: CoreUtil.createAccess(state.factory, asrt, ASSERT_UTIL, 'check'),
|
|
@@ -156,7 +156,7 @@ export class AssertTransformer {
|
|
|
156
156
|
const first = CoreUtil.firstArgument(node);
|
|
157
157
|
const firstText = first?.getText() ?? node.getText();
|
|
158
158
|
|
|
159
|
-
cmd.args = cmd.args.filter(
|
|
159
|
+
cmd.args = cmd.args.filter(arg => arg !== undefined && arg !== null);
|
|
160
160
|
const check = state.factory.createCallExpression(state[AssertSymbol]!.assertCheck, undefined, state.factory.createNodeArray([
|
|
161
161
|
state.fromLiteral({
|
|
162
162
|
module: state.getModuleIdentifier(),
|
|
@@ -236,7 +236,7 @@ export class AssertTransformer {
|
|
|
236
236
|
const matched = METHODS[key.text!];
|
|
237
237
|
if (matched) {
|
|
238
238
|
const resolved = state.resolveType(root);
|
|
239
|
-
if (resolved.key === 'literal' && matched.find(
|
|
239
|
+
if (resolved.key === 'literal' && matched.find(type => resolved.ctor === type)) { // Ensure method is against real type
|
|
240
240
|
switch (key.text) {
|
|
241
241
|
case 'includes': return { fn: key.text, args: [comp.expression.expression, comp.arguments[0], ...args.slice(1)] };
|
|
242
242
|
case 'test': return { fn: key.text, args: [comp.arguments[0], comp.expression.expression, ...args.slice(1)] };
|
|
@@ -298,9 +298,9 @@ export class AssertTransformer {
|
|
|
298
298
|
}
|
|
299
299
|
// If calling `assert.*`
|
|
300
300
|
} else if (ts.isPropertyAccessExpression(exp) && ts.isIdentifier(exp.expression)) { // Assert method call
|
|
301
|
-
const
|
|
301
|
+
const identifier = exp.expression;
|
|
302
302
|
const fn = exp.name.escapedText.toString();
|
|
303
|
-
if (
|
|
303
|
+
if (identifier.escapedText === ASSERT_CMD) {
|
|
304
304
|
// Look for reject/throw
|
|
305
305
|
if (fn === 'fail') {
|
|
306
306
|
node = this.doAssert(state, node, { fn: 'fail', args: node.arguments.slice() });
|
package/src/execute/runner.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
|
|
3
|
-
import { TimeUtil, Runtime, RuntimeIndex } from '@travetto/runtime';
|
|
4
|
-
import { WorkPool } from '@travetto/worker';
|
|
5
|
-
|
|
6
|
-
import { buildStandardTestManager } from '../worker/standard.ts';
|
|
7
|
-
import { RunnableTestConsumer } from '../consumer/types/runnable.ts';
|
|
8
|
-
import { TestRun } from '../model/test.ts';
|
|
9
|
-
|
|
10
|
-
import { TestExecutor } from './executor.ts';
|
|
11
|
-
import { RunnerUtil } from './util.ts';
|
|
12
|
-
import { RunState } from './types.ts';
|
|
13
|
-
import { TestConsumerRegistryIndex } from '../consumer/registry-index.ts';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Test Runner
|
|
17
|
-
*/
|
|
18
|
-
export class Runner {
|
|
19
|
-
|
|
20
|
-
#state: RunState;
|
|
21
|
-
|
|
22
|
-
constructor(state: RunState) {
|
|
23
|
-
this.#state = state;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Run all files
|
|
28
|
-
*/
|
|
29
|
-
async runFiles(globs?: string[]): Promise<boolean> {
|
|
30
|
-
const target = await TestConsumerRegistryIndex.getInstance(this.#state);
|
|
31
|
-
const consumer = new RunnableTestConsumer(target);
|
|
32
|
-
const tests = await RunnerUtil.getTestDigest(globs, this.#state.tags);
|
|
33
|
-
const testRuns = RunnerUtil.getTestRuns(tests)
|
|
34
|
-
.toSorted((a, b) => a.runId!.localeCompare(b.runId!));
|
|
35
|
-
|
|
36
|
-
await consumer.onStart({ testCount: tests.length });
|
|
37
|
-
await WorkPool.run(
|
|
38
|
-
f => buildStandardTestManager(consumer, f),
|
|
39
|
-
testRuns,
|
|
40
|
-
{
|
|
41
|
-
idleTimeoutMillis: TimeUtil.asMillis(10, 's'),
|
|
42
|
-
min: 1,
|
|
43
|
-
max: this.#state.concurrency
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
return consumer.summarizeAsBoolean();
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Run a single file
|
|
51
|
-
*/
|
|
52
|
-
async runSingle(run: TestRun): Promise<boolean> {
|
|
53
|
-
run.import =
|
|
54
|
-
RuntimeIndex.getFromImport(run.import)?.import ??
|
|
55
|
-
RuntimeIndex.getFromSource(path.resolve(run.import))?.import!;
|
|
56
|
-
|
|
57
|
-
const entry = RuntimeIndex.getFromImport(run.import)!;
|
|
58
|
-
|
|
59
|
-
if (entry.module !== Runtime.main.name) {
|
|
60
|
-
RuntimeIndex.reinitForModule(entry.module);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const target = await TestConsumerRegistryIndex.getInstance(this.#state);
|
|
64
|
-
|
|
65
|
-
const consumer = new RunnableTestConsumer(target)
|
|
66
|
-
.withTransformer(e => {
|
|
67
|
-
// Copy run metadata to event
|
|
68
|
-
e.metadata = run.metadata;
|
|
69
|
-
return e;
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
await consumer.onStart({});
|
|
73
|
-
await new TestExecutor(consumer).execute(run);
|
|
74
|
-
return consumer.summarizeAsBoolean();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Run the runner, based on the inputs passed to the constructor
|
|
79
|
-
*/
|
|
80
|
-
async run(): Promise<boolean | undefined> {
|
|
81
|
-
if ('import' in this.#state.target) {
|
|
82
|
-
return await this.runSingle(this.#state.target);
|
|
83
|
-
} else {
|
|
84
|
-
return await this.runFiles(this.#state.target.globs);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
package/src/execute/util.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { createReadStream } from 'node:fs';
|
|
3
|
-
import fs from 'node:fs/promises';
|
|
4
|
-
import readline from 'node:readline/promises';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
|
|
7
|
-
import { Env, ExecUtil, ShutdownManager, Util, RuntimeIndex, Runtime } from '@travetto/runtime';
|
|
8
|
-
import { TestConfig, TestRun } from '../model/test.ts';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Simple Test Utilities
|
|
12
|
-
*/
|
|
13
|
-
export class RunnerUtil {
|
|
14
|
-
/**
|
|
15
|
-
* Add 50 ms to the shutdown to allow for buffers to output properly
|
|
16
|
-
*/
|
|
17
|
-
static registerCleanup(scope: string): void {
|
|
18
|
-
ShutdownManager.onGracefulShutdown(() => Util.blockingTimeout(50), `test.${scope}.bufferOutput`);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Determine if a given file path is a valid test file
|
|
23
|
-
*/
|
|
24
|
-
static async isTestFile(file: string): Promise<boolean> {
|
|
25
|
-
const reader = readline.createInterface({ input: createReadStream(file) });
|
|
26
|
-
const state = { imp: false, suite: false };
|
|
27
|
-
for await (const line of reader) {
|
|
28
|
-
state.imp ||= line.includes('@travetto/test');
|
|
29
|
-
state.suite ||= line.includes('Suite'); // Decorator or name
|
|
30
|
-
if (state.imp && state.suite) {
|
|
31
|
-
reader.close();
|
|
32
|
-
return true;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Find all valid test files given the globs
|
|
40
|
-
*/
|
|
41
|
-
static async* getTestImports(globs?: string[]): AsyncIterable<string> {
|
|
42
|
-
const all = RuntimeIndex.find({
|
|
43
|
-
module: m => m.roles.includes('test') || m.roles.includes('std'),
|
|
44
|
-
folder: f => f === 'test',
|
|
45
|
-
file: f => f.role === 'test'
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
// Collect globs
|
|
49
|
-
if (globs?.length) {
|
|
50
|
-
const allFiles = new Map(all.map(x => [x.sourceFile, x]));
|
|
51
|
-
for await (const item of fs.glob(globs)) {
|
|
52
|
-
const src = Runtime.workspaceRelative(path.resolve(item));
|
|
53
|
-
const match = allFiles.get(src);
|
|
54
|
-
if (match && await this.isTestFile(match.sourceFile)) {
|
|
55
|
-
yield match.import;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
} else {
|
|
59
|
-
for await (const match of all) {
|
|
60
|
-
if (await this.isTestFile(match.sourceFile)) {
|
|
61
|
-
yield match.import;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Get count of tests for a given set of globs
|
|
69
|
-
* @param globs
|
|
70
|
-
* @returns
|
|
71
|
-
*/
|
|
72
|
-
static async getTestDigest(globs: string[] = ['**/*.ts'], tags?: string[]): Promise<TestConfig[]> {
|
|
73
|
-
const countRes = await ExecUtil.getResult(
|
|
74
|
-
spawn('npx', ['trv', 'test:digest', '-o', 'json', ...globs], {
|
|
75
|
-
env: { ...process.env, ...Env.FORCE_COLOR.export(0), ...Env.NO_COLOR.export(true) }
|
|
76
|
-
}),
|
|
77
|
-
{ catch: true }
|
|
78
|
-
);
|
|
79
|
-
if (!countRes.valid) {
|
|
80
|
-
throw new Error(countRes.stderr);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const testFilter = tags?.length ?
|
|
84
|
-
Util.allowDeny<string, [TestConfig]>(
|
|
85
|
-
tags,
|
|
86
|
-
rule => rule,
|
|
87
|
-
(rule, core) => core.tags?.includes(rule) ?? false
|
|
88
|
-
) :
|
|
89
|
-
((): boolean => true);
|
|
90
|
-
|
|
91
|
-
const parsed: TestConfig[] = countRes.valid ? JSON.parse(countRes.stdout) : [];
|
|
92
|
-
return parsed.filter(testFilter);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Get run events
|
|
97
|
-
*/
|
|
98
|
-
static getTestRuns(tests: TestConfig[]): TestRun[] {
|
|
99
|
-
const events = tests.reduce((acc, test) => {
|
|
100
|
-
if (!acc.has(test.classId)) {
|
|
101
|
-
acc.set(test.classId, { import: test.import, classId: test.classId, methodNames: [], runId: Util.uuid() });
|
|
102
|
-
}
|
|
103
|
-
acc.get(test.classId)!.methodNames!.push(test.methodName);
|
|
104
|
-
return acc;
|
|
105
|
-
}, new Map<string, TestRun>());
|
|
106
|
-
return [...events.values()];
|
|
107
|
-
}
|
|
108
|
-
}
|