@travetto/test 6.0.2 → 7.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/__index__.ts +3 -1
- package/package.json +7 -7
- package/src/consumer/decorator.ts +13 -0
- package/src/consumer/registry-adapter.ts +43 -0
- package/src/consumer/registry-index.ts +82 -0
- package/src/consumer/types/cumulative.ts +4 -4
- package/src/consumer/types/event.ts +1 -1
- package/src/consumer/types/exec.ts +1 -1
- package/src/consumer/types/json.ts +1 -1
- package/src/consumer/types/noop.ts +1 -1
- package/src/consumer/types/tap-summary.ts +1 -1
- package/src/consumer/types/tap.ts +2 -2
- package/src/consumer/types/xunit.ts +1 -1
- package/src/consumer/types.ts +13 -0
- package/src/decorator/suite.ts +43 -29
- package/src/decorator/test.ts +19 -18
- package/src/execute/executor.ts +10 -5
- package/src/execute/runner.ts +3 -3
- package/src/execute/watcher.ts +15 -13
- package/src/model/common.ts +4 -4
- package/src/model/suite.ts +5 -5
- package/src/registry/registry-adapter.ts +127 -0
- package/src/registry/registry-index.ts +117 -0
- package/support/bin/run.ts +13 -16
- package/support/cli.test_child.ts +3 -1
- package/support/cli.test_digest.ts +13 -6
- package/support/cli.test_direct.ts +14 -1
- package/src/consumer/registry.ts +0 -68
- package/src/consumer/types/all.ts +0 -10
- package/src/registry/suite.ts +0 -143
package/src/execute/watcher.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Registry } from '@travetto/registry';
|
|
2
2
|
import { WorkPool } from '@travetto/worker';
|
|
3
3
|
import { AsyncQueue, Runtime, RuntimeIndex, castTo, describeFunction } from '@travetto/runtime';
|
|
4
4
|
|
|
5
|
-
import { SuiteRegistry } from '../registry/suite.ts';
|
|
6
5
|
import { buildStandardTestManager } from '../worker/standard.ts';
|
|
7
|
-
import {
|
|
6
|
+
import { TestConsumerRegistryIndex } from '../consumer/registry-index.ts';
|
|
8
7
|
import { CumulativeSummaryConsumer } from '../consumer/types/cumulative.ts';
|
|
9
8
|
import { TestRun } from '../model/test.ts';
|
|
10
9
|
import { RunnerUtil } from './util.ts';
|
|
11
10
|
import { TestReadyEvent, TestRemovedEvent } from '../worker/types.ts';
|
|
11
|
+
import { SuiteRegistryIndex } from '../registry/registry-index.ts';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Test Watcher.
|
|
@@ -23,9 +23,7 @@ export class TestWatcher {
|
|
|
23
23
|
static async watch(format: string, runAllOnStart = true): Promise<void> {
|
|
24
24
|
console.debug('Listening for changes');
|
|
25
25
|
|
|
26
|
-
await
|
|
27
|
-
SuiteRegistry.listen(RootRegistry);
|
|
28
|
-
await RootRegistry.init();
|
|
26
|
+
await Registry.init();
|
|
29
27
|
|
|
30
28
|
const events: TestRun[] = [];
|
|
31
29
|
|
|
@@ -36,22 +34,26 @@ export class TestWatcher {
|
|
|
36
34
|
|
|
37
35
|
const itr = new AsyncQueue(events);
|
|
38
36
|
const consumer = new CumulativeSummaryConsumer(
|
|
39
|
-
await
|
|
37
|
+
await TestConsumerRegistryIndex.getInstance({ consumer: format })
|
|
40
38
|
)
|
|
41
39
|
.withFilter(x => x.metadata?.partial !== true || x.type !== 'suite');
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
const [cls, method] = (
|
|
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
45
|
if (!cls || describeFunction(cls).abstract) {
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
|
+
|
|
48
49
|
const classId = cls.Ⲑid;
|
|
49
50
|
if (!method) {
|
|
50
51
|
consumer.removeClass(classId);
|
|
51
52
|
return;
|
|
52
53
|
}
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
|
|
55
|
+
const conf = SuiteRegistryIndex.getTestConfig(cls, method)!;
|
|
56
|
+
if (event.type !== 'removing') {
|
|
55
57
|
if (conf) {
|
|
56
58
|
const run: TestRun = {
|
|
57
59
|
import: conf.import, classId: conf.classId, methodNames: [conf.methodName], metadata: { partial: true }
|
|
@@ -68,10 +70,10 @@ export class TestWatcher {
|
|
|
68
70
|
import: Runtime.getImport(cls)
|
|
69
71
|
} satisfies TestRemovedEvent);
|
|
70
72
|
}
|
|
71
|
-
});
|
|
73
|
+
}, SuiteRegistryIndex);
|
|
72
74
|
|
|
73
75
|
// If a file is changed, but doesn't emit classes, re-run whole file
|
|
74
|
-
|
|
76
|
+
Registry.onNonClassChanges(imp => itr.add({ import: imp }));
|
|
75
77
|
|
|
76
78
|
process.on('message', ev => {
|
|
77
79
|
if (typeof ev === 'object' && ev && 'type' in ev && ev.type === 'run-test') {
|
package/src/model/common.ts
CHANGED
|
@@ -9,10 +9,6 @@ export interface SuiteCore {
|
|
|
9
9
|
* The class id
|
|
10
10
|
*/
|
|
11
11
|
classId: string;
|
|
12
|
-
/**
|
|
13
|
-
* The tests' description
|
|
14
|
-
*/
|
|
15
|
-
description: string;
|
|
16
12
|
/**
|
|
17
13
|
* The import location for the suite
|
|
18
14
|
*/
|
|
@@ -29,6 +25,10 @@ export interface SuiteCore {
|
|
|
29
25
|
* Tags for a suite or a test
|
|
30
26
|
*/
|
|
31
27
|
tags?: string[];
|
|
28
|
+
/**
|
|
29
|
+
* Description
|
|
30
|
+
*/
|
|
31
|
+
description?: string;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
package/src/model/suite.ts
CHANGED
|
@@ -6,11 +6,11 @@ import { Skip, SuiteCore } from './common.ts';
|
|
|
6
6
|
/**
|
|
7
7
|
* Suite configuration
|
|
8
8
|
*/
|
|
9
|
-
export interface SuiteConfig
|
|
9
|
+
export interface SuiteConfig extends SuiteCore {
|
|
10
10
|
/**
|
|
11
11
|
* Class suite is in
|
|
12
12
|
*/
|
|
13
|
-
class: Class
|
|
13
|
+
class: Class;
|
|
14
14
|
/**
|
|
15
15
|
* Should this be skipped
|
|
16
16
|
*/
|
|
@@ -18,11 +18,11 @@ export interface SuiteConfig<T = unknown> extends SuiteCore {
|
|
|
18
18
|
/**
|
|
19
19
|
* Actual class instance
|
|
20
20
|
*/
|
|
21
|
-
instance
|
|
21
|
+
instance?: unknown;
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
23
|
+
* Tests to run
|
|
24
24
|
*/
|
|
25
|
-
tests: TestConfig
|
|
25
|
+
tests: Record<string | symbol, TestConfig>;
|
|
26
26
|
/**
|
|
27
27
|
* Before all handlers
|
|
28
28
|
*/
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { RegistryAdapter } from '@travetto/registry';
|
|
2
|
+
import { AppError, asFull, Class, describeFunction, Runtime, safeAssign } from '@travetto/runtime';
|
|
3
|
+
import { SchemaRegistryIndex } from '@travetto/schema';
|
|
4
|
+
|
|
5
|
+
import { SuiteConfig } from '../model/suite';
|
|
6
|
+
import { TestConfig } from '../model/test';
|
|
7
|
+
|
|
8
|
+
function combineClasses(baseConfig: SuiteConfig, ...subConfig: Partial<SuiteConfig>[]): SuiteConfig {
|
|
9
|
+
for (const cfg of subConfig) {
|
|
10
|
+
if (cfg.beforeAll) {
|
|
11
|
+
baseConfig.beforeAll = [...baseConfig.beforeAll, ...cfg.beforeAll];
|
|
12
|
+
}
|
|
13
|
+
if (cfg.beforeEach) {
|
|
14
|
+
baseConfig.beforeEach = [...baseConfig.beforeEach, ...cfg.beforeEach];
|
|
15
|
+
}
|
|
16
|
+
if (cfg.afterAll) {
|
|
17
|
+
baseConfig.afterAll = [...baseConfig.afterAll, ...cfg.afterAll];
|
|
18
|
+
}
|
|
19
|
+
if (cfg.afterEach) {
|
|
20
|
+
baseConfig.afterEach = [...baseConfig.afterEach, ...cfg.afterEach];
|
|
21
|
+
}
|
|
22
|
+
if (cfg.tags) {
|
|
23
|
+
baseConfig.tags = [...baseConfig.tags ?? [], ...cfg.tags];
|
|
24
|
+
}
|
|
25
|
+
if (cfg.tests) {
|
|
26
|
+
for (const [key, test] of Object.entries(cfg.tests ?? {})) {
|
|
27
|
+
baseConfig.tests[key] = {
|
|
28
|
+
...test,
|
|
29
|
+
sourceImport: Runtime.getImport(baseConfig.class),
|
|
30
|
+
class: baseConfig.class,
|
|
31
|
+
classId: baseConfig.classId,
|
|
32
|
+
import: baseConfig.import,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return baseConfig;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function combineMethods(suite: SuiteConfig, baseConfig: TestConfig, ...subConfig: Partial<TestConfig>[]): TestConfig {
|
|
41
|
+
baseConfig.classId = suite.classId;
|
|
42
|
+
baseConfig.import = suite.import;
|
|
43
|
+
for (const cfg of subConfig) {
|
|
44
|
+
safeAssign(baseConfig, cfg, {
|
|
45
|
+
tags: [
|
|
46
|
+
...baseConfig.tags ?? [],
|
|
47
|
+
...cfg.tags ?? []
|
|
48
|
+
]
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return baseConfig;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export class SuiteRegistryAdapter implements RegistryAdapter<SuiteConfig> {
|
|
55
|
+
#cls: Class;
|
|
56
|
+
#config: SuiteConfig;
|
|
57
|
+
|
|
58
|
+
constructor(cls: Class) {
|
|
59
|
+
this.#cls = cls;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
register(...data: Partial<SuiteConfig>[]): SuiteConfig {
|
|
63
|
+
if (!this.#config) {
|
|
64
|
+
const lines = describeFunction(this.#cls)?.lines;
|
|
65
|
+
this.#config = asFull<SuiteConfig>({
|
|
66
|
+
class: this.#cls,
|
|
67
|
+
classId: this.#cls.Ⲑid,
|
|
68
|
+
tags: [],
|
|
69
|
+
import: Runtime.getImport(this.#cls),
|
|
70
|
+
lineStart: lines?.[0],
|
|
71
|
+
lineEnd: lines?.[1],
|
|
72
|
+
tests: {},
|
|
73
|
+
beforeAll: [],
|
|
74
|
+
beforeEach: [],
|
|
75
|
+
afterAll: [],
|
|
76
|
+
afterEach: []
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
combineClasses(this.#config, ...data);
|
|
80
|
+
return this.#config;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
registerTest(method: string | symbol, ...data: Partial<TestConfig>[]): TestConfig {
|
|
84
|
+
const suite = this.register();
|
|
85
|
+
|
|
86
|
+
if (!(method in this.#config.tests)) {
|
|
87
|
+
const lines = describeFunction(this.#cls)?.methods?.[method]?.lines;
|
|
88
|
+
const config = asFull<TestConfig>({
|
|
89
|
+
class: this.#cls,
|
|
90
|
+
tags: [],
|
|
91
|
+
import: Runtime.getImport(this.#cls),
|
|
92
|
+
lineStart: lines?.[0],
|
|
93
|
+
lineEnd: lines?.[1],
|
|
94
|
+
lineBodyStart: lines?.[2],
|
|
95
|
+
methodName: method.toString(),
|
|
96
|
+
});
|
|
97
|
+
this.#config.tests[method] = config;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const result = this.#config.tests[method];
|
|
101
|
+
combineMethods(suite, result, ...data);
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
finalize(parent?: SuiteConfig): void {
|
|
106
|
+
if (parent) {
|
|
107
|
+
combineClasses(this.#config, parent);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (const test of Object.values(this.#config.tests)) {
|
|
111
|
+
test.tags = [...test.tags ?? [], ...this.#config.tags ?? []];
|
|
112
|
+
test.description ||= SchemaRegistryIndex.get(this.#cls).getMethod(test.methodName).description;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
get(): SuiteConfig {
|
|
117
|
+
return this.#config;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
getMethod(method: string | symbol): TestConfig {
|
|
121
|
+
const test = this.#config.tests[method];
|
|
122
|
+
if (!test) {
|
|
123
|
+
throw new AppError(`Test not registered: ${String(method)} on ${this.#cls.name}`);
|
|
124
|
+
}
|
|
125
|
+
return test;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { AppError, Class, Runtime, describeFunction } from '@travetto/runtime';
|
|
2
|
+
import { ChangeEvent, RegistryIndex, RegistryIndexStore, Registry } from '@travetto/registry';
|
|
3
|
+
|
|
4
|
+
import { SuiteConfig } from '../model/suite.ts';
|
|
5
|
+
import { TestConfig, TestRun } from '../model/test.ts';
|
|
6
|
+
import { SuiteRegistryAdapter } from './registry-adapter.ts';
|
|
7
|
+
|
|
8
|
+
const sortedTests = (cfg: SuiteConfig): TestConfig[] =>
|
|
9
|
+
Object.values(cfg.tests).toSorted((a, b) => a.lineStart - b.lineStart);
|
|
10
|
+
|
|
11
|
+
type SuiteTests = { suite: SuiteConfig, tests: TestConfig[] };
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Test Suite registry
|
|
15
|
+
*/
|
|
16
|
+
export class SuiteRegistryIndex implements RegistryIndex {
|
|
17
|
+
|
|
18
|
+
static #instance = Registry.registerIndex(this);
|
|
19
|
+
|
|
20
|
+
static getForRegister(cls: Class): SuiteRegistryAdapter {
|
|
21
|
+
return this.#instance.store.getForRegister(cls);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static has(cls: Class): boolean {
|
|
25
|
+
return this.#instance.store.has(cls);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static getTestConfig(cls: Class, method: Function): TestConfig | undefined {
|
|
29
|
+
return this.#instance.getTestConfig(cls, method);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static getSuiteTests(run: TestRun): SuiteTests[] {
|
|
33
|
+
return this.#instance.getSuiteTests(run);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static getConfig(cls: Class): SuiteConfig {
|
|
37
|
+
return this.#instance.store.get(cls).get();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static getClasses(): Class[] {
|
|
41
|
+
return this.#instance.store.getClasses();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
store = new RegistryIndexStore(SuiteRegistryAdapter);
|
|
45
|
+
|
|
46
|
+
process(_events: ChangeEvent<Class>[]): void {
|
|
47
|
+
// No-op for now
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Find all valid tests (ignoring abstract)
|
|
52
|
+
*/
|
|
53
|
+
getValidClasses(): Class[] {
|
|
54
|
+
return this.store.getClasses().filter(c => !describeFunction(c).abstract);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getConfig(cls: Class): SuiteConfig {
|
|
58
|
+
return this.store.get(cls).get();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get run parameters from provided input
|
|
63
|
+
*/
|
|
64
|
+
getSuiteTests(run: TestRun): SuiteTests[] {
|
|
65
|
+
const clsId = run.classId;
|
|
66
|
+
const imp = run.import;
|
|
67
|
+
const methodNames = run.methodNames ?? [];
|
|
68
|
+
|
|
69
|
+
if (clsId && /^\d+$/.test(clsId)) { // If we only have a line number
|
|
70
|
+
const line = parseInt(clsId, 10);
|
|
71
|
+
const suites = this.getValidClasses()
|
|
72
|
+
.filter(cls => Runtime.getImport(cls) === imp)
|
|
73
|
+
.map(x => this.getConfig(x)).filter(x => !x.skip);
|
|
74
|
+
const suite = suites.find(x => line >= x.lineStart && line <= x.lineEnd);
|
|
75
|
+
|
|
76
|
+
if (suite) {
|
|
77
|
+
const tests = sortedTests(suite);
|
|
78
|
+
const test = tests.find(x => line >= x.lineStart && line <= x.lineEnd);
|
|
79
|
+
return test ? [{ suite, tests: [test] }] : [{ suite, tests }];
|
|
80
|
+
} else {
|
|
81
|
+
return suites.map(x => ({ suite: x, tests: sortedTests(x) }));
|
|
82
|
+
}
|
|
83
|
+
} else { // Else lookup directly
|
|
84
|
+
if (methodNames.length) {
|
|
85
|
+
const cls = this.getValidClasses().find(x => x.Ⲑid === clsId);
|
|
86
|
+
if (!cls) {
|
|
87
|
+
throw new AppError('Unable to find suite for class ID', { details: { classId: clsId } });
|
|
88
|
+
}
|
|
89
|
+
const suite = this.getConfig(cls);
|
|
90
|
+
const tests = sortedTests(suite).filter(x => methodNames.includes(x.methodName));
|
|
91
|
+
return [{ suite, tests }];
|
|
92
|
+
} else if (clsId) {
|
|
93
|
+
const cls = this.getValidClasses().find(x => x.Ⲑid === clsId)!;
|
|
94
|
+
if (!cls) {
|
|
95
|
+
throw new AppError('Unable to find suite for class ID', { details: { classId: clsId } });
|
|
96
|
+
}
|
|
97
|
+
const suite = this.getConfig(cls);
|
|
98
|
+
return suite ? [{ suite, tests: sortedTests(suite) }] : [];
|
|
99
|
+
} else {
|
|
100
|
+
const suites = this.getValidClasses()
|
|
101
|
+
.map(x => this.getConfig(x))
|
|
102
|
+
.filter(x => !describeFunction(x.class).abstract); // Do not run abstract suites
|
|
103
|
+
return suites.map(x => ({ suite: x, tests: sortedTests(x) }));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Find a test configuration given class and optionally a method
|
|
110
|
+
*/
|
|
111
|
+
getTestConfig(cls: Class, method: Function): TestConfig | undefined {
|
|
112
|
+
if (this.store.has(cls)) {
|
|
113
|
+
const conf = this.getConfig(cls);
|
|
114
|
+
return Object.values(conf.tests).find(x => x.methodName === method.name);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
package/support/bin/run.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { getClass, Runtime } from '@travetto/runtime';
|
|
2
|
+
import { SchemaRegistryIndex } from '@travetto/schema';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { TestConsumerRegistryIndex } from '../../src/consumer/registry-index.ts';
|
|
5
5
|
import type { RunState } from '../../src/execute/types.ts';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -23,20 +23,17 @@ export async function runTests(opts: RunState): Promise<void> {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export async function selectConsumer(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
let types = TestConsumerRegistry.getTypes();
|
|
30
|
-
|
|
31
|
-
if (inst.format?.includes('/')) {
|
|
32
|
-
await Runtime.importFrom(inst.format);
|
|
33
|
-
types = TestConsumerRegistry.getTypes();
|
|
26
|
+
export async function selectConsumer(instance: { format?: string }) {
|
|
27
|
+
if (instance.format?.includes('/')) {
|
|
28
|
+
await Runtime.importFrom(instance.format);
|
|
34
29
|
}
|
|
35
30
|
|
|
36
|
-
const
|
|
31
|
+
const types = await TestConsumerRegistryIndex.getTypes();
|
|
37
32
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
SchemaRegistryIndex.getForRegister(getClass(instance), true).registerField('format', {
|
|
34
|
+
enum: {
|
|
35
|
+
message: `{path} is only allowed to be "${types.join('" or "')}"`,
|
|
36
|
+
values: types
|
|
37
|
+
}
|
|
38
|
+
});
|
|
42
39
|
}
|
|
@@ -2,9 +2,11 @@ import { EventEmitter } from 'node:events';
|
|
|
2
2
|
|
|
3
3
|
import { Env } from '@travetto/runtime';
|
|
4
4
|
import { CliCommand } from '@travetto/cli';
|
|
5
|
+
import { IsPrivate } from '@travetto/schema';
|
|
5
6
|
|
|
6
7
|
/** Test child worker target */
|
|
7
|
-
@CliCommand(
|
|
8
|
+
@CliCommand()
|
|
9
|
+
@IsPrivate()
|
|
8
10
|
export class TestChildWorkerCommand {
|
|
9
11
|
preMain(): void {
|
|
10
12
|
EventEmitter.defaultMaxListeners = 1000;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { CliCommand } from '@travetto/cli';
|
|
2
2
|
import { Env, Runtime, describeFunction } from '@travetto/runtime';
|
|
3
|
+
import { Registry } from '@travetto/registry';
|
|
4
|
+
import { IsPrivate } from '@travetto/schema';
|
|
3
5
|
|
|
4
|
-
import {
|
|
6
|
+
import { SuiteRegistryIndex } from '../src/registry/registry-index.ts';
|
|
5
7
|
import { RunnerUtil } from '../src/execute/util.ts';
|
|
6
8
|
|
|
7
|
-
@CliCommand(
|
|
9
|
+
@CliCommand()
|
|
10
|
+
@IsPrivate()
|
|
8
11
|
export class TestDigestCommand {
|
|
9
12
|
|
|
10
13
|
output: 'json' | 'text' = 'text';
|
|
@@ -24,13 +27,17 @@ export class TestDigestCommand {
|
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
29
|
|
|
27
|
-
await
|
|
30
|
+
await Registry.init();
|
|
28
31
|
|
|
29
|
-
const suites =
|
|
32
|
+
const suites = SuiteRegistryIndex.getClasses();
|
|
30
33
|
const all = suites
|
|
31
|
-
.map(c =>
|
|
34
|
+
.map(c => SuiteRegistryIndex.getConfig(c))
|
|
32
35
|
.filter(c => !describeFunction(c.class).abstract)
|
|
33
|
-
.flatMap(c => c.tests)
|
|
36
|
+
.flatMap(c => Object.values(c.tests))
|
|
37
|
+
.toSorted((a, b) => {
|
|
38
|
+
const classComp = a.classId.localeCompare(b.classId);
|
|
39
|
+
return classComp !== 0 ? classComp : a.methodName.localeCompare(b.methodName);
|
|
40
|
+
});
|
|
34
41
|
|
|
35
42
|
if (this.output === 'json') {
|
|
36
43
|
console.log(JSON.stringify(all));
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import { Env } from '@travetto/runtime';
|
|
2
2
|
import { CliCommand } from '@travetto/cli';
|
|
3
|
+
import { IsPrivate } from '@travetto/schema';
|
|
3
4
|
|
|
4
5
|
import { runTests, selectConsumer } from './bin/run.ts';
|
|
5
6
|
|
|
6
7
|
/** Direct test invocation */
|
|
7
|
-
@CliCommand(
|
|
8
|
+
@CliCommand()
|
|
9
|
+
@IsPrivate()
|
|
8
10
|
export class TestDirectCommand {
|
|
9
11
|
|
|
12
|
+
@IsPrivate()
|
|
10
13
|
format: string = 'tap';
|
|
11
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Format options
|
|
17
|
+
* @alias o
|
|
18
|
+
*/
|
|
19
|
+
formatOptions?: string[];
|
|
20
|
+
|
|
12
21
|
async preValidate(): Promise<void> {
|
|
13
22
|
await selectConsumer(this);
|
|
14
23
|
}
|
|
@@ -21,8 +30,12 @@ export class TestDirectCommand {
|
|
|
21
30
|
}
|
|
22
31
|
|
|
23
32
|
main(importOrFile: string, clsId?: string, methodsNames: string[] = []): Promise<void> {
|
|
33
|
+
|
|
34
|
+
const options = Object.fromEntries((this.formatOptions ?? [])?.map(f => [...f.split(':'), true]));
|
|
35
|
+
|
|
24
36
|
return runTests({
|
|
25
37
|
consumer: this.format,
|
|
38
|
+
consumerOptions: options,
|
|
26
39
|
target: {
|
|
27
40
|
import: importOrFile,
|
|
28
41
|
classId: clsId,
|
package/src/consumer/registry.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
|
|
3
|
-
import { classConstruct, describeFunction, type Class } from '@travetto/runtime';
|
|
4
|
-
|
|
5
|
-
import type { TestConsumerShape } from './types.ts';
|
|
6
|
-
import type { RunState } from '../execute/types.ts';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Test Results Handler Registry
|
|
10
|
-
*/
|
|
11
|
-
class $TestConsumerRegistry {
|
|
12
|
-
#registered = new Map<string, Class<TestConsumerShape>>();
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Manual initialization when running outside of the bootstrap process
|
|
16
|
-
*/
|
|
17
|
-
async manualInit(): Promise<void> {
|
|
18
|
-
await import('./types/all.ts');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Add a new consumer
|
|
23
|
-
* @param cls The consumer class
|
|
24
|
-
*/
|
|
25
|
-
add(cls: Class<TestConsumerShape>): void {
|
|
26
|
-
const desc = describeFunction(cls);
|
|
27
|
-
const key = desc.module?.includes('@travetto') ? path.basename(desc.modulePath) : desc.import;
|
|
28
|
-
this.#registered.set(key, cls);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Retrieve a registered consumer
|
|
33
|
-
* @param type The unique identifier
|
|
34
|
-
*/
|
|
35
|
-
get(type: string): Class<TestConsumerShape> {
|
|
36
|
-
return this.#registered.get(type)!;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Get types
|
|
41
|
-
*/
|
|
42
|
-
getTypes(): string[] {
|
|
43
|
-
return [...this.#registered.keys()];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Get a consumer instance that supports summarization
|
|
48
|
-
* @param consumer The consumer identifier or the actual consumer
|
|
49
|
-
*/
|
|
50
|
-
async getInstance(state: Pick<RunState, 'consumer' | 'consumerOptions'>): Promise<TestConsumerShape> {
|
|
51
|
-
// TODO: Fix consumer registry init
|
|
52
|
-
await this.manualInit();
|
|
53
|
-
const inst = classConstruct(this.get(state.consumer));
|
|
54
|
-
await inst.setOptions?.(state.consumerOptions ?? {});
|
|
55
|
-
return inst;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export const TestConsumerRegistry = new $TestConsumerRegistry();
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Registers a class a valid test consumer
|
|
63
|
-
*/
|
|
64
|
-
export function TestConsumer(): (cls: Class<TestConsumerShape>) => void {
|
|
65
|
-
return function (cls: Class<TestConsumerShape>): void {
|
|
66
|
-
TestConsumerRegistry.add(cls);
|
|
67
|
-
};
|
|
68
|
-
}
|