@travetto/test 8.0.0-alpha.9 → 8.0.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 +23 -21
- package/__index__.ts +9 -9
- package/package.json +20 -20
- package/src/assert/capture.ts +4 -5
- package/src/assert/check.ts +53 -26
- package/src/assert/types.ts +1 -1
- package/src/assert/util.ts +16 -12
- package/src/consumer/decorator.ts +2 -2
- package/src/consumer/enhancer.ts +3 -3
- package/src/consumer/registry-adapter.ts +3 -3
- package/src/consumer/registry-index.ts +6 -5
- package/src/consumer/types/cumulative.ts +6 -6
- package/src/consumer/types/delegating.ts +2 -2
- package/src/consumer/types/event.ts +2 -2
- package/src/consumer/types/exec.ts +3 -4
- package/src/consumer/types/json.ts +3 -4
- package/src/consumer/types/noop.ts +3 -3
- package/src/consumer/types/runnable.ts +4 -6
- package/src/consumer/types/summarizer.ts +2 -3
- package/src/consumer/types/tap-summary.ts +28 -25
- package/src/consumer/types/tap.ts +68 -40
- package/src/consumer/types/util.ts +8 -5
- package/src/consumer/types/xunit.ts +7 -6
- package/src/decorator/suite.ts +3 -3
- package/src/decorator/test.ts +6 -4
- package/src/execute/barrier.ts +15 -9
- package/src/execute/console.ts +3 -4
- package/src/execute/executor.ts +29 -25
- package/src/execute/phase.ts +1 -1
- package/src/execute/run.ts +38 -38
- package/src/execute/watcher.ts +12 -19
- package/src/fixture.ts +6 -7
- package/src/model/common.ts +1 -1
- package/src/model/error.ts +2 -2
- package/src/model/event.ts +14 -9
- package/src/model/suite.ts +2 -2
- package/src/model/test.ts +8 -5
- package/src/model/util.ts +25 -15
- package/src/registry/registry-adapter.ts +10 -13
- package/src/registry/registry-index.ts +13 -11
- package/src/trv.d.ts +2 -2
- package/src/worker/child.ts +13 -10
- package/src/worker/standard.ts +11 -14
- package/src/worker/types.ts +3 -7
- package/support/bin/run.ts +1 -1
- package/support/cli.test.ts +20 -15
- package/support/cli.test_child.ts +8 -3
- package/support/cli.test_diff.ts +11 -7
- package/support/cli.test_digest.ts +10 -4
- package/support/cli.test_direct.ts +11 -7
- package/support/cli.test_watch.ts +6 -4
- package/support/transformer.assert.ts +51 -43
|
@@ -1,20 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type
|
|
1
|
+
import { Registry, type RegistryIndex, RegistryIndexStore } from '@travetto/registry';
|
|
2
|
+
import { type Class, describeFunction, Runtime, RuntimeError } from '@travetto/runtime';
|
|
3
3
|
|
|
4
4
|
import type { SuiteConfig } from '../model/suite.ts';
|
|
5
5
|
import type { TestConfig, TestRun } from '../model/test.ts';
|
|
6
6
|
import { SuiteRegistryAdapter } from './registry-adapter.ts';
|
|
7
7
|
|
|
8
|
-
const sortedTests = (config: SuiteConfig): TestConfig[] =>
|
|
9
|
-
Object.values(config.tests).toSorted((a, b) => a.lineStart - b.lineStart);
|
|
8
|
+
const sortedTests = (config: SuiteConfig): TestConfig[] => Object.values(config.tests).toSorted((a, b) => a.lineStart - b.lineStart);
|
|
10
9
|
|
|
11
|
-
type SuiteTests = { suite: SuiteConfig
|
|
10
|
+
type SuiteTests = { suite: SuiteConfig; tests: TestConfig[] };
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* Test Suite registry
|
|
15
14
|
*/
|
|
16
15
|
export class SuiteRegistryIndex implements RegistryIndex {
|
|
17
|
-
|
|
18
16
|
static #instance = Registry.registerIndex(this);
|
|
19
17
|
|
|
20
18
|
static getForRegister(cls: Class): SuiteRegistryAdapter {
|
|
@@ -43,7 +41,9 @@ export class SuiteRegistryIndex implements RegistryIndex {
|
|
|
43
41
|
|
|
44
42
|
store = new RegistryIndexStore(SuiteRegistryAdapter);
|
|
45
43
|
|
|
46
|
-
/** @private */ constructor(source: unknown) {
|
|
44
|
+
/** @private */ constructor(source: unknown) {
|
|
45
|
+
Registry.validateConstructor(source);
|
|
46
|
+
}
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* Find all valid tests (ignoring abstract)
|
|
@@ -64,7 +64,8 @@ export class SuiteRegistryIndex implements RegistryIndex {
|
|
|
64
64
|
const imp = run.import;
|
|
65
65
|
const methodNames = run.methodNames ?? [];
|
|
66
66
|
|
|
67
|
-
if (clsId && /^\d+$/.test(clsId)) {
|
|
67
|
+
if (clsId && /^\d+$/.test(clsId)) {
|
|
68
|
+
// If we only have a line number
|
|
68
69
|
const line = parseInt(clsId, 10);
|
|
69
70
|
const suites = this.getValidClasses()
|
|
70
71
|
.filter(cls => Runtime.getImport(cls) === imp)
|
|
@@ -79,7 +80,8 @@ export class SuiteRegistryIndex implements RegistryIndex {
|
|
|
79
80
|
} else {
|
|
80
81
|
return suites.map(config => ({ suite: config, tests: sortedTests(config) }));
|
|
81
82
|
}
|
|
82
|
-
} else {
|
|
83
|
+
} else {
|
|
84
|
+
// Else lookup directly
|
|
83
85
|
if (methodNames.length) {
|
|
84
86
|
const cls = this.getValidClasses().find(type => type.Ⲑid === clsId);
|
|
85
87
|
if (!cls) {
|
|
@@ -98,7 +100,7 @@ export class SuiteRegistryIndex implements RegistryIndex {
|
|
|
98
100
|
} else {
|
|
99
101
|
const suites = this.getValidClasses()
|
|
100
102
|
.map(type => this.getConfig(type))
|
|
101
|
-
.filter(config => !describeFunction(config.class).abstract);
|
|
103
|
+
.filter(config => !describeFunction(config.class).abstract); // Do not run abstract suites
|
|
102
104
|
return suites.map(config => ({ suite: config, tests: sortedTests(config) }));
|
|
103
105
|
}
|
|
104
106
|
}
|
|
@@ -114,4 +116,4 @@ export class SuiteRegistryIndex implements RegistryIndex {
|
|
|
114
116
|
return Object.values(config.tests).find(item => item.methodName === methodName);
|
|
115
117
|
}
|
|
116
118
|
}
|
|
117
|
-
}
|
|
119
|
+
}
|
package/src/trv.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TimeSpan } from '@travetto/runtime';
|
|
1
|
+
import type { TimeSpan } from '@travetto/runtime';
|
|
2
2
|
|
|
3
3
|
declare module '@travetto/runtime' {
|
|
4
4
|
interface EnvData {
|
|
@@ -17,4 +17,4 @@ declare module '@travetto/runtime' {
|
|
|
17
17
|
*/
|
|
18
18
|
TRV_TEST_TAGS: string[];
|
|
19
19
|
}
|
|
20
|
-
}
|
|
20
|
+
}
|
package/src/worker/child.ts
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { createWriteStream } from 'node:fs';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { ConsoleManager, Env, JSONUtil, Runtime } from '@travetto/runtime';
|
|
4
4
|
import { IpcChannel } from '@travetto/worker';
|
|
5
5
|
|
|
6
6
|
import { RunUtil } from '../execute/run.ts';
|
|
7
|
-
import { TestWorkerEvents } from './types.ts';
|
|
8
7
|
import type { TestRun } from '../model/test.ts';
|
|
8
|
+
import { TestWorkerEvents } from './types.ts';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Child Worker for the Test Runner. Receives events as commands
|
|
12
12
|
* to run specific tests
|
|
13
13
|
*/
|
|
14
14
|
export class TestChildWorker extends IpcChannel<TestRun> {
|
|
15
|
-
|
|
16
15
|
#done = Promise.withResolvers<void>();
|
|
17
16
|
|
|
18
17
|
async #exec(operation: () => Promise<unknown>, type: string): Promise<void> {
|
|
@@ -20,7 +19,9 @@ export class TestChildWorker extends IpcChannel<TestRun> {
|
|
|
20
19
|
await operation();
|
|
21
20
|
this.send(type); // Respond
|
|
22
21
|
} catch (error) {
|
|
23
|
-
if (!(error instanceof Error)) {
|
|
22
|
+
if (!(error instanceof Error)) {
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
24
25
|
// Mark as errored out
|
|
25
26
|
this.send(type, JSONUtil.cloneForTransmit(error));
|
|
26
27
|
}
|
|
@@ -34,9 +35,9 @@ export class TestChildWorker extends IpcChannel<TestRun> {
|
|
|
34
35
|
const file = Runtime.toolPath(`test-worker.${process.pid}.log`);
|
|
35
36
|
const stdout = createWriteStream(file, { flags: 'a' });
|
|
36
37
|
const cons = new console.Console({ stdout, inspectOptions: { depth: 4, colors: false } });
|
|
37
|
-
ConsoleManager.set({ log:
|
|
38
|
+
ConsoleManager.set({ log: event => cons[event.level](process.pid, ...event.args) });
|
|
38
39
|
} else {
|
|
39
|
-
ConsoleManager.set({ log: () => {
|
|
40
|
+
ConsoleManager.set({ log: () => {} });
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
// Listen for inbound requests
|
|
@@ -54,9 +55,11 @@ export class TestChildWorker extends IpcChannel<TestRun> {
|
|
|
54
55
|
async onCommand(event: TestRun & { type: string }): Promise<boolean> {
|
|
55
56
|
console.debug('on message', { ...event });
|
|
56
57
|
|
|
57
|
-
if (event.type === TestWorkerEvents.INIT) {
|
|
58
|
+
if (event.type === TestWorkerEvents.INIT) {
|
|
59
|
+
// On request to init, start initialization
|
|
58
60
|
await this.#exec(() => this.onInitCommand(), TestWorkerEvents.INIT_COMPLETE);
|
|
59
|
-
} else if (event.type === TestWorkerEvents.RUN) {
|
|
61
|
+
} else if (event.type === TestWorkerEvents.RUN) {
|
|
62
|
+
// On request to run, start running
|
|
60
63
|
await this.#exec(() => this.onRunCommand(event), TestWorkerEvents.RUN_COMPLETE);
|
|
61
64
|
}
|
|
62
65
|
|
|
@@ -66,7 +69,7 @@ export class TestChildWorker extends IpcChannel<TestRun> {
|
|
|
66
69
|
/**
|
|
67
70
|
* In response to the initialization command
|
|
68
71
|
*/
|
|
69
|
-
async onInitCommand(): Promise<void> {
|
|
72
|
+
async onInitCommand(): Promise<void> {}
|
|
70
73
|
|
|
71
74
|
/**
|
|
72
75
|
* Run a specific test/suite
|
|
@@ -80,4 +83,4 @@ export class TestChildWorker extends IpcChannel<TestRun> {
|
|
|
80
83
|
this.#done.resolve();
|
|
81
84
|
}
|
|
82
85
|
}
|
|
83
|
-
}
|
|
86
|
+
}
|
package/src/worker/standard.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { fork } from 'node:child_process';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { Env, JSONUtil, RuntimeIndex } from '@travetto/runtime';
|
|
4
4
|
import { IpcChannel } from '@travetto/worker';
|
|
5
5
|
|
|
6
|
-
import { TestWorkerEvents, type TestLogEvent } from './types.ts';
|
|
7
6
|
import type { TestConsumerShape } from '../consumer/types.ts';
|
|
8
7
|
import type { TestEvent, TestRemoveEvent } from '../model/event.ts';
|
|
9
8
|
import type { TestDiffInput, TestRun } from '../model/test.ts';
|
|
9
|
+
import { type TestLogEvent, TestWorkerEvents } from './types.ts';
|
|
10
10
|
|
|
11
11
|
const log = (message: string | TestLogEvent): void => {
|
|
12
12
|
const event: TestLogEvent = typeof message === 'string' ? { type: 'log', message } : message;
|
|
@@ -20,16 +20,13 @@ export async function buildStandardTestManager(consumer: TestConsumerShape, run:
|
|
|
20
20
|
log(`Worker Input ${JSONUtil.toUTF8(run)}`);
|
|
21
21
|
|
|
22
22
|
const channel = new IpcChannel<TestEvent & { error?: Error }>(
|
|
23
|
-
fork(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
stdio: ['ignore', 'ignore', 2, 'ipc']
|
|
31
|
-
}
|
|
32
|
-
)
|
|
23
|
+
fork(RuntimeIndex.resolveFileImport('@travetto/cli/support/entry.trv.ts'), ['test:child'], {
|
|
24
|
+
env: {
|
|
25
|
+
...process.env,
|
|
26
|
+
...Env.TRV_QUIET.export(true)
|
|
27
|
+
},
|
|
28
|
+
stdio: ['ignore', 'ignore', 2, 'ipc']
|
|
29
|
+
})
|
|
33
30
|
);
|
|
34
31
|
|
|
35
32
|
await channel.once(TestWorkerEvents.READY); // Wait for the child to be ready
|
|
@@ -45,7 +42,7 @@ export async function buildStandardTestManager(consumer: TestConsumerShape, run:
|
|
|
45
42
|
log(`Received remove event ${JSONUtil.toUTF8(event)}@${consumer.constructor.name}`);
|
|
46
43
|
consumer.onRemoveEvent?.(parsed); // Forward remove events
|
|
47
44
|
} else {
|
|
48
|
-
consumer.onEvent(parsed);
|
|
45
|
+
consumer.onEvent(parsed); // Forward standard events
|
|
49
46
|
}
|
|
50
47
|
} catch {
|
|
51
48
|
// Do nothing
|
|
@@ -70,4 +67,4 @@ export async function buildStandardTestManager(consumer: TestConsumerShape, run:
|
|
|
70
67
|
if (result.error) {
|
|
71
68
|
throw result.error;
|
|
72
69
|
}
|
|
73
|
-
}
|
|
70
|
+
}
|
package/src/worker/types.ts
CHANGED
|
@@ -11,16 +11,12 @@ export const TestWorkerEvents = {
|
|
|
11
11
|
READY: 'ready'
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
export type TestRunEvent = { type: 'runTest'
|
|
14
|
+
export type TestRunEvent = { type: 'runTest'; import: string };
|
|
15
15
|
|
|
16
16
|
export const isTestRunEvent = (event: unknown): event is TestRunEvent =>
|
|
17
17
|
typeof event === 'object' && !!event && 'type' in event && event.type === 'runTest';
|
|
18
18
|
|
|
19
19
|
export type TestReadyEvent = { type: 'ready' };
|
|
20
|
-
export type TestLogEvent = { type: 'log'
|
|
20
|
+
export type TestLogEvent = { type: 'log'; message: string };
|
|
21
21
|
|
|
22
|
-
export type TestWatchEvent =
|
|
23
|
-
TestEvent |
|
|
24
|
-
TestRemoveEvent |
|
|
25
|
-
TestReadyEvent |
|
|
26
|
-
TestLogEvent;
|
|
22
|
+
export type TestWatchEvent = TestEvent | TestRemoveEvent | TestReadyEvent | TestLogEvent;
|
package/support/bin/run.ts
CHANGED
|
@@ -17,4 +17,4 @@ export async function runTests(state: TestConsumerConfig, input: TestRunInput):
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export type TestConsumerType = 'tap' | 'tap-summary' | 'json' | 'exec' | 'event' | 'xunit' | 'custom';
|
|
20
|
+
export type TestConsumerType = 'tap' | 'tap-summary' | 'json' | 'exec' | 'event' | 'xunit' | 'custom';
|
package/support/cli.test.ts
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
2
|
|
|
3
|
+
import { CliCommand, type CliCommandShape, CliUtil } from '@travetto/cli';
|
|
3
4
|
import { Env, RuntimeIndex } from '@travetto/runtime';
|
|
4
|
-
import { type CliCommandShape, CliCommand, CliUtil } from '@travetto/cli';
|
|
5
|
-
import { WorkPool } from '@travetto/worker';
|
|
6
5
|
import { Max, Min } from '@travetto/schema';
|
|
6
|
+
import { WorkPool } from '@travetto/worker';
|
|
7
7
|
|
|
8
8
|
import type { TestConsumerType } from './bin/run.ts';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Execute the test framework for targeted files, suites, or methods.
|
|
12
|
+
*
|
|
13
|
+
* Supports glob-based discovery, import-based targeting, tag filtering, and
|
|
14
|
+
* configurable output consumers for local and CI workflows.
|
|
12
15
|
*/
|
|
13
16
|
@CliCommand()
|
|
14
17
|
export class TestCommand implements CliCommandShape {
|
|
15
|
-
|
|
16
18
|
/** Output format for test results */
|
|
17
19
|
format: TestConsumerType = 'tap';
|
|
18
20
|
|
|
19
21
|
/** Number of tests to run concurrently */
|
|
20
|
-
@Min(1)
|
|
22
|
+
@Min(1)
|
|
23
|
+
@Max(WorkPool.MAX_SIZE)
|
|
21
24
|
concurrency: number = WorkPool.DEFAULT_SIZE;
|
|
22
25
|
|
|
23
26
|
/**
|
|
@@ -49,16 +52,18 @@ export class TestCommand implements CliCommandShape {
|
|
|
49
52
|
{
|
|
50
53
|
concurrency: this.concurrency,
|
|
51
54
|
consumer: this.format,
|
|
52
|
-
consumerOptions: CliUtil.readExtendedOptions(this.formatOptions)
|
|
55
|
+
consumerOptions: CliUtil.readExtendedOptions(this.formatOptions)
|
|
53
56
|
},
|
|
54
|
-
importPath
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
importPath
|
|
58
|
+
? {
|
|
59
|
+
import: importPath,
|
|
60
|
+
classId: globs[0],
|
|
61
|
+
methodNames: globs.slice(1)
|
|
62
|
+
}
|
|
63
|
+
: {
|
|
64
|
+
globs: [first, ...globs],
|
|
65
|
+
tags: this.tags
|
|
66
|
+
}
|
|
62
67
|
);
|
|
63
68
|
}
|
|
64
|
-
}
|
|
69
|
+
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
2
|
|
|
3
|
-
import { Env } from '@travetto/runtime';
|
|
4
3
|
import { CliCommand } from '@travetto/cli';
|
|
4
|
+
import { Env } from '@travetto/runtime';
|
|
5
5
|
import { IsPrivate } from '@travetto/schema';
|
|
6
6
|
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Internal command target for test child workers.
|
|
9
|
+
*
|
|
10
|
+
* Used by the test runner to bootstrap isolated worker processes with test-
|
|
11
|
+
* oriented runtime configuration.
|
|
12
|
+
*/
|
|
8
13
|
@CliCommand()
|
|
9
14
|
@IsPrivate()
|
|
10
15
|
export class TestChildWorkerCommand {
|
|
@@ -22,4 +27,4 @@ export class TestChildWorkerCommand {
|
|
|
22
27
|
const { TestChildWorker } = await import('../src/worker/child.ts');
|
|
23
28
|
return new TestChildWorker().activate();
|
|
24
29
|
}
|
|
25
|
-
}
|
|
30
|
+
}
|
package/support/cli.test_diff.ts
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
|
|
3
|
-
import { Env, JSONUtil, RuntimeIndex } from '@travetto/runtime';
|
|
4
3
|
import { CliCommand, CliUtil } from '@travetto/cli';
|
|
4
|
+
import { Env, JSONUtil, RuntimeIndex } from '@travetto/runtime';
|
|
5
5
|
import { IsPrivate } from '@travetto/schema';
|
|
6
6
|
|
|
7
|
-
import { runTests, type TestConsumerType } from './bin/run.ts';
|
|
8
7
|
import type { TestDiffSource } from '../src/model/test.ts';
|
|
8
|
+
import { runTests, type TestConsumerType } from './bin/run.ts';
|
|
9
9
|
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Run tests scoped by a precomputed diff source file.
|
|
12
|
+
*
|
|
13
|
+
* The first argument resolves the test import root, and the second argument is
|
|
14
|
+
* a JSON diff payload consumed by test-selection logic.
|
|
15
|
+
*/
|
|
11
16
|
@CliCommand()
|
|
12
17
|
@IsPrivate()
|
|
13
18
|
export class TestDiffCommand {
|
|
14
|
-
|
|
15
19
|
/** Output format for test results */
|
|
16
20
|
format: TestConsumerType = 'tap';
|
|
17
21
|
|
|
@@ -29,12 +33,12 @@ export class TestDiffCommand {
|
|
|
29
33
|
|
|
30
34
|
async main(importOrFile: string, diff: string): Promise<void> {
|
|
31
35
|
const diffSource = await fs.readFile(diff).then(JSONUtil.fromBinaryArray<TestDiffSource>);
|
|
32
|
-
const importPath = RuntimeIndex.getFromImportOrSource(importOrFile)
|
|
36
|
+
const importPath = RuntimeIndex.getFromImportOrSource(importOrFile)!.import;
|
|
33
37
|
|
|
34
38
|
return runTests(
|
|
35
39
|
{
|
|
36
40
|
consumer: this.format,
|
|
37
|
-
consumerOptions: CliUtil.readExtendedOptions(this.formatOptions)
|
|
41
|
+
consumerOptions: CliUtil.readExtendedOptions(this.formatOptions)
|
|
38
42
|
},
|
|
39
43
|
{
|
|
40
44
|
import: importPath,
|
|
@@ -42,4 +46,4 @@ export class TestDiffCommand {
|
|
|
42
46
|
}
|
|
43
47
|
);
|
|
44
48
|
}
|
|
45
|
-
}
|
|
49
|
+
}
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import { CliCommand } from '@travetto/cli';
|
|
2
|
-
import { JSONUtil, Env, Runtime, describeFunction } from '@travetto/runtime';
|
|
3
2
|
import { Registry } from '@travetto/registry';
|
|
3
|
+
import { describeFunction, Env, JSONUtil, Runtime } from '@travetto/runtime';
|
|
4
4
|
import { IsPrivate } from '@travetto/schema';
|
|
5
5
|
|
|
6
|
-
import { SuiteRegistryIndex } from '../src/registry/registry-index.ts';
|
|
7
6
|
import { RunUtil } from '../src/execute/run.ts';
|
|
7
|
+
import { SuiteRegistryIndex } from '../src/registry/registry-index.ts';
|
|
8
8
|
|
|
9
9
|
@CliCommand()
|
|
10
10
|
@IsPrivate()
|
|
11
|
+
/**
|
|
12
|
+
* Produce a deterministic digest of discovered test identifiers.
|
|
13
|
+
*
|
|
14
|
+
* This is an internal command used by tooling to enumerate tests in a stable
|
|
15
|
+
* order for planning, sharding, or change detection workflows.
|
|
16
|
+
*/
|
|
11
17
|
export class TestDigestCommand {
|
|
12
|
-
|
|
18
|
+
/** Output mode for digest emission. */
|
|
13
19
|
output: 'json' | 'text' = 'text';
|
|
14
20
|
|
|
15
21
|
preMain(): void {
|
|
@@ -47,4 +53,4 @@ export class TestDigestCommand {
|
|
|
47
53
|
}
|
|
48
54
|
}
|
|
49
55
|
}
|
|
50
|
-
}
|
|
56
|
+
}
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { Env, RuntimeIndex } from '@travetto/runtime';
|
|
2
1
|
import { CliCommand, CliUtil } from '@travetto/cli';
|
|
2
|
+
import { Env, RuntimeIndex } from '@travetto/runtime';
|
|
3
3
|
import { IsPrivate } from '@travetto/schema';
|
|
4
4
|
|
|
5
5
|
import { runTests, type TestConsumerType } from './bin/run.ts';
|
|
6
6
|
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Run tests directly from an import or source file with optional class/method filtering.
|
|
9
|
+
*
|
|
10
|
+
* This internal command bypasses discovery by targeting a specific test import
|
|
11
|
+
* and optional class or method names.
|
|
12
|
+
*/
|
|
8
13
|
@CliCommand()
|
|
9
14
|
@IsPrivate()
|
|
10
15
|
export class TestDirectCommand {
|
|
11
|
-
|
|
12
16
|
/** Output format for test results */
|
|
13
17
|
format: TestConsumerType = 'tap';
|
|
14
18
|
|
|
@@ -26,18 +30,18 @@ export class TestDirectCommand {
|
|
|
26
30
|
|
|
27
31
|
main(importOrFile: string, clsId?: string, methodsNames: string[] = []): Promise<void> {
|
|
28
32
|
// Resolve to import
|
|
29
|
-
const importPath = RuntimeIndex.getFromImportOrSource(importOrFile)
|
|
33
|
+
const importPath = RuntimeIndex.getFromImportOrSource(importOrFile)!.import;
|
|
30
34
|
|
|
31
35
|
return runTests(
|
|
32
36
|
{
|
|
33
37
|
consumer: this.format,
|
|
34
|
-
consumerOptions: CliUtil.readExtendedOptions(this.formatOptions)
|
|
38
|
+
consumerOptions: CliUtil.readExtendedOptions(this.formatOptions)
|
|
35
39
|
},
|
|
36
40
|
{
|
|
37
41
|
import: importPath,
|
|
38
42
|
classId: clsId,
|
|
39
|
-
methodNames: methodsNames
|
|
43
|
+
methodNames: methodsNames
|
|
40
44
|
}
|
|
41
45
|
);
|
|
42
46
|
}
|
|
43
|
-
}
|
|
47
|
+
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { Env } from '@travetto/runtime';
|
|
2
1
|
import { CliCommand } from '@travetto/cli';
|
|
2
|
+
import { Env } from '@travetto/runtime';
|
|
3
3
|
|
|
4
4
|
import type { TestConsumerType } from './bin/run.ts';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Start the test watcher for continuous test execution.
|
|
8
|
+
*
|
|
9
|
+
* Watches project changes and reruns either all tests or changed subsets,
|
|
10
|
+
* using the configured output format.
|
|
8
11
|
*/
|
|
9
12
|
@CliCommand()
|
|
10
13
|
export class TestWatcherCommand {
|
|
11
|
-
|
|
12
14
|
/** Output format for test results */
|
|
13
15
|
format: TestConsumerType = 'tap';
|
|
14
16
|
|
|
@@ -26,4 +28,4 @@ export class TestWatcherCommand {
|
|
|
26
28
|
console.error(error);
|
|
27
29
|
}
|
|
28
30
|
}
|
|
29
|
-
}
|
|
31
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { CoreUtil, DeclarationUtil, TransformerHandler, type TransformerState } from '@travetto/transformer';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Which types are candidates for deep literal checking
|
|
@@ -18,9 +18,9 @@ export const DEEP_EQUALS_MAPPING: Record<string, string> = {
|
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* Typescript
|
|
21
|
+
* Typescript op token to assert methods
|
|
22
22
|
*/
|
|
23
|
-
export const
|
|
23
|
+
export const OP_TOKEN_ASSERT = {
|
|
24
24
|
InKeyword: 'in',
|
|
25
25
|
EqualsEqualsToken: 'equal',
|
|
26
26
|
ExclamationEqualsToken: 'notEqual',
|
|
@@ -46,7 +46,7 @@ const METHODS: Record<string, Function[]> = {
|
|
|
46
46
|
test: [RegExp]
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
-
const OP_TOKEN_TO_NAME = new Map<number, keyof typeof
|
|
49
|
+
const OP_TOKEN_TO_NAME = new Map<number, keyof typeof OP_TOKEN_ASSERT>();
|
|
50
50
|
|
|
51
51
|
const AssertSymbol = Symbol();
|
|
52
52
|
const IsTestSymbol = Symbol();
|
|
@@ -89,7 +89,6 @@ interface Command {
|
|
|
89
89
|
* and result generation
|
|
90
90
|
*/
|
|
91
91
|
export class AssertTransformer {
|
|
92
|
-
|
|
93
92
|
static {
|
|
94
93
|
TransformerHandler(this, this.onAssertCheck, 'before', 'method', ['AssertCheck']);
|
|
95
94
|
TransformerHandler(this, this.afterAssertCheck, 'after', 'method', ['AssertCheck']);
|
|
@@ -97,20 +96,21 @@ export class AssertTransformer {
|
|
|
97
96
|
}
|
|
98
97
|
|
|
99
98
|
/**
|
|
100
|
-
* Resolves
|
|
99
|
+
* Resolves op token to syntax kind. Relies on `ts`
|
|
101
100
|
*/
|
|
102
101
|
static lookupOpToken(key: number): string | undefined {
|
|
103
102
|
if (OP_TOKEN_TO_NAME.size === 0) {
|
|
104
103
|
Object.keys(ts.SyntaxKind)
|
|
105
104
|
.filter(kind => !/^\d+$/.test(kind))
|
|
106
|
-
.filter((kind): kind is keyof typeof
|
|
107
|
-
.forEach(kind =>
|
|
108
|
-
OP_TOKEN_TO_NAME.set(ts.SyntaxKind[kind], kind)
|
|
105
|
+
.filter((kind): kind is keyof typeof OP_TOKEN_ASSERT => !/^(Last|First)/.test(kind))
|
|
106
|
+
.forEach(kind => {
|
|
107
|
+
OP_TOKEN_TO_NAME.set(ts.SyntaxKind[kind], kind);
|
|
108
|
+
});
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
const name = OP_TOKEN_TO_NAME.get(key)!;
|
|
112
|
-
if (name in
|
|
113
|
-
return
|
|
112
|
+
if (name in OP_TOKEN_ASSERT) {
|
|
113
|
+
return OP_TOKEN_ASSERT[name];
|
|
114
114
|
} else {
|
|
115
115
|
return;
|
|
116
116
|
}
|
|
@@ -120,19 +120,18 @@ export class AssertTransformer {
|
|
|
120
120
|
* Determine if element is a deep literal (should use deep comparison)
|
|
121
121
|
*/
|
|
122
122
|
static isDeepLiteral(state: TransformerState, node: ts.Expression): boolean {
|
|
123
|
-
let found =
|
|
123
|
+
let found =
|
|
124
|
+
ts.isArrayLiteralExpression(node) ||
|
|
124
125
|
ts.isObjectLiteralExpression(node) ||
|
|
125
|
-
(
|
|
126
|
-
ts.isNewExpression(node) &&
|
|
127
|
-
DEEP_LITERAL_TYPES.has(node.expression.getText())
|
|
128
|
-
);
|
|
126
|
+
(ts.isNewExpression(node) && DEEP_LITERAL_TYPES.has(node.expression.getText()));
|
|
129
127
|
|
|
130
128
|
// If looking at an identifier, see if it's in a diff file or if its const
|
|
131
129
|
if (!found && ts.isIdentifier(node)) {
|
|
132
|
-
found = !!state.getDeclarations(node).find(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
130
|
+
found = !!state.getDeclarations(node).find(
|
|
131
|
+
declaration =>
|
|
132
|
+
// In a separate file or is const
|
|
133
|
+
declaration.getSourceFile().fileName !== state.source.fileName || DeclarationUtil.isConstantDeclaration(declaration)
|
|
134
|
+
);
|
|
136
135
|
}
|
|
137
136
|
|
|
138
137
|
return found;
|
|
@@ -143,12 +142,12 @@ export class AssertTransformer {
|
|
|
143
142
|
*/
|
|
144
143
|
static initState(state: TransformerState & AssertState): void {
|
|
145
144
|
if (!state[AssertSymbol]) {
|
|
146
|
-
const
|
|
145
|
+
const assertion = state.importFile('@travetto/test/src/assert/check.ts').identifier;
|
|
147
146
|
state[AssertSymbol] = {
|
|
148
|
-
assert:
|
|
149
|
-
assertCheck: CoreUtil.createAccess(state.factory,
|
|
150
|
-
checkThrow: CoreUtil.createAccess(state.factory,
|
|
151
|
-
checkThrowAsync: CoreUtil.createAccess(state.factory,
|
|
147
|
+
assert: assertion,
|
|
148
|
+
assertCheck: CoreUtil.createAccess(state.factory, assertion, ASSERT_UTIL, 'check'),
|
|
149
|
+
checkThrow: CoreUtil.createAccess(state.factory, assertion, ASSERT_UTIL, 'checkThrow'),
|
|
150
|
+
checkThrowAsync: CoreUtil.createAccess(state.factory, assertion, ASSERT_UTIL, 'checkThrowAsync')
|
|
152
151
|
};
|
|
153
152
|
}
|
|
154
153
|
}
|
|
@@ -163,16 +162,20 @@ export class AssertTransformer {
|
|
|
163
162
|
const firstText = first?.getText() ?? node.getText();
|
|
164
163
|
|
|
165
164
|
cmd.args = cmd.args.filter(arg => arg !== undefined && arg !== null);
|
|
166
|
-
const check = state.factory.createCallExpression(
|
|
167
|
-
state
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
165
|
+
const check = state.factory.createCallExpression(
|
|
166
|
+
state[AssertSymbol]!.assertCheck,
|
|
167
|
+
undefined,
|
|
168
|
+
state.factory.createNodeArray([
|
|
169
|
+
state.fromLiteral({
|
|
170
|
+
module: state.getModuleIdentifier(),
|
|
171
|
+
line: state.fromLiteral(ts.getLineAndCharacterOfPosition(state.source, node.getStart()).line + 1),
|
|
172
|
+
text: state.fromLiteral(firstText),
|
|
173
|
+
operator: state.fromLiteral(cmd.fn)
|
|
174
|
+
}),
|
|
175
|
+
state.fromLiteral(!cmd.negate),
|
|
176
|
+
...cmd.args
|
|
177
|
+
])
|
|
178
|
+
);
|
|
176
179
|
|
|
177
180
|
return check;
|
|
178
181
|
}
|
|
@@ -197,7 +200,8 @@ export class AssertTransformer {
|
|
|
197
200
|
}),
|
|
198
201
|
state.fromLiteral(key.startsWith('doesNot')),
|
|
199
202
|
...args
|
|
200
|
-
])
|
|
203
|
+
])
|
|
204
|
+
);
|
|
201
205
|
}
|
|
202
206
|
|
|
203
207
|
/**
|
|
@@ -234,7 +238,6 @@ export class AssertTransformer {
|
|
|
234
238
|
* Check various `assert.*` method calls
|
|
235
239
|
*/
|
|
236
240
|
static doMethodCall(state: TransformerState, comp: ts.Expression, args: Args): Command {
|
|
237
|
-
|
|
238
241
|
if (ts.isCallExpression(comp) && ts.isPropertyAccessExpression(comp.expression)) {
|
|
239
242
|
const root = comp.expression.expression;
|
|
240
243
|
const key = comp.expression.name;
|
|
@@ -242,10 +245,13 @@ export class AssertTransformer {
|
|
|
242
245
|
const matched = METHODS[key.text!];
|
|
243
246
|
if (matched) {
|
|
244
247
|
const resolved = state.resolveType(root);
|
|
245
|
-
if (resolved.key === 'literal' && matched.find(type => resolved.ctor === type)) {
|
|
248
|
+
if (resolved.key === 'literal' && matched.find(type => resolved.ctor === type)) {
|
|
249
|
+
// Ensure method is against real type
|
|
246
250
|
switch (key.text) {
|
|
247
|
-
case 'includes':
|
|
248
|
-
|
|
251
|
+
case 'includes':
|
|
252
|
+
return { fn: key.text, args: [comp.expression.expression, comp.arguments[0], ...args.slice(1)] };
|
|
253
|
+
case 'test':
|
|
254
|
+
return { fn: key.text, args: [comp.arguments[0], comp.expression.expression, ...args.slice(1)] };
|
|
249
255
|
}
|
|
250
256
|
}
|
|
251
257
|
}
|
|
@@ -294,13 +300,15 @@ export class AssertTransformer {
|
|
|
294
300
|
const exp = node.expression;
|
|
295
301
|
|
|
296
302
|
// Determine if calling assert directly
|
|
297
|
-
if (ts.isIdentifier(exp) && exp.getSourceFile() && exp.getText() === ASSERT_CMD) {
|
|
303
|
+
if (ts.isIdentifier(exp) && exp.getSourceFile() && exp.getText() === ASSERT_CMD) {
|
|
304
|
+
// Straight assert
|
|
298
305
|
const cmd = this.getCommand(state, node.arguments);
|
|
299
306
|
if (cmd) {
|
|
300
307
|
node = this.doAssert(state, node, cmd);
|
|
301
308
|
}
|
|
302
309
|
// If calling `assert.*`
|
|
303
|
-
} else if (ts.isPropertyAccessExpression(exp) && ts.isIdentifier(exp.expression)) {
|
|
310
|
+
} else if (ts.isPropertyAccessExpression(exp) && ts.isIdentifier(exp.expression)) {
|
|
311
|
+
// Assert method call
|
|
304
312
|
const identifier = exp.expression;
|
|
305
313
|
const fn = exp.name.escapedText.toString();
|
|
306
314
|
if (identifier.escapedText === ASSERT_CMD) {
|
|
@@ -318,4 +326,4 @@ export class AssertTransformer {
|
|
|
318
326
|
|
|
319
327
|
return node;
|
|
320
328
|
}
|
|
321
|
-
}
|
|
329
|
+
}
|