@travetto/test 5.0.0-rc.1 → 5.0.0-rc.10
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 +12 -13
- package/package.json +8 -8
- package/src/assert/capture.ts +5 -4
- package/src/assert/check.ts +24 -33
- package/src/assert/util.ts +32 -18
- package/src/consumer/registry.ts +2 -3
- package/src/consumer/{error.ts → serialize.ts} +13 -22
- package/src/consumer/types/cumulative.ts +15 -22
- package/src/consumer/types/delegating.ts +58 -0
- package/src/consumer/types/event.ts +2 -4
- package/src/consumer/types/execution.ts +2 -4
- package/src/consumer/types/runnable.ts +12 -41
- package/src/consumer/types/tap-streamed.ts +9 -6
- package/src/consumer/types/tap.ts +5 -4
- package/src/consumer/types/xunit.ts +4 -2
- package/src/decorator/suite.ts +5 -7
- package/src/decorator/test.ts +2 -1
- package/src/execute/console.ts +1 -1
- package/src/execute/executor.ts +84 -104
- package/src/execute/phase.ts +20 -30
- package/src/execute/promise.ts +4 -4
- package/src/execute/runner.ts +34 -24
- package/src/execute/types.ts +12 -10
- package/src/execute/util.ts +61 -34
- package/src/execute/watcher.ts +34 -36
- package/src/fixture.ts +7 -2
- package/src/model/common.ts +11 -7
- package/src/model/event.ts +9 -5
- package/src/model/suite.ts +14 -4
- package/src/model/test.ts +30 -4
- package/src/registry/suite.ts +42 -39
- package/src/trv.d.ts +3 -3
- package/src/worker/child.ts +11 -17
- package/src/worker/standard.ts +18 -21
- package/src/worker/types.ts +13 -10
- package/support/cli.test.ts +20 -6
- package/support/cli.test_child.ts +1 -1
- package/support/cli.test_digest.ts +43 -0
- package/support/cli.test_direct.ts +10 -3
- package/support/cli.test_watch.ts +1 -1
- package/support/transformer.assert.ts +12 -12
- package/support/cli.test_count.ts +0 -39
- package/support/transformer.annotate.ts +0 -103
package/src/execute/promise.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createHook, executionAsyncId } from 'node:async_hooks';
|
|
2
|
+
import { isPromise } from 'node:util/types';
|
|
2
3
|
|
|
3
4
|
import { ExecutionError } from '@travetto/worker';
|
|
4
|
-
import { Util } from '@travetto/
|
|
5
|
+
import { Util } from '@travetto/runtime';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Promise watcher, to catch any unfinished promises
|
|
@@ -11,9 +12,8 @@ export class PromiseCapturer {
|
|
|
11
12
|
#id: number = 0;
|
|
12
13
|
|
|
13
14
|
#init(id: number, type: string, triggerId: number, resource: unknown): void {
|
|
14
|
-
if (this.#id && type === 'PROMISE' && triggerId === this.#id) {
|
|
15
|
-
|
|
16
|
-
this.#pending.set(id, resource as Promise<unknown>);
|
|
15
|
+
if (this.#id && type === 'PROMISE' && triggerId === this.#id && isPromise(resource)) {
|
|
16
|
+
this.#pending.set(id, resource);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
package/src/execute/runner.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { TimeUtil, RuntimeContext } from '@travetto/base';
|
|
3
|
+
import { TimeUtil, Runtime, RuntimeIndex } from '@travetto/runtime';
|
|
5
4
|
import { WorkPool } from '@travetto/worker';
|
|
6
5
|
|
|
7
6
|
import { buildStandardTestManager } from '../worker/standard';
|
|
@@ -10,6 +9,7 @@ import { RunnableTestConsumer } from '../consumer/types/runnable';
|
|
|
10
9
|
import { TestExecutor } from './executor';
|
|
11
10
|
import { RunnerUtil } from './util';
|
|
12
11
|
import { RunState } from './types';
|
|
12
|
+
import { TestConfig, TestRun } from '../model/test';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Test Runner
|
|
@@ -22,29 +22,26 @@ export class Runner {
|
|
|
22
22
|
this.#state = state;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
get patterns(): RegExp[] {
|
|
26
|
-
return this.#state.args.map(x => new RegExp(mp.toPosix(x)));
|
|
27
|
-
}
|
|
28
|
-
|
|
29
25
|
/**
|
|
30
26
|
* Run all files
|
|
31
27
|
*/
|
|
32
|
-
async runFiles(): Promise<boolean> {
|
|
28
|
+
async runFiles(globs?: string[]): Promise<boolean> {
|
|
33
29
|
const consumer = await RunnableTestConsumer.get(this.#state.consumer ?? this.#state.format);
|
|
34
30
|
|
|
35
|
-
|
|
31
|
+
console.debug('Running', { globs });
|
|
36
32
|
|
|
37
|
-
|
|
33
|
+
const tests = await RunnerUtil.getTestDigest(globs, this.#state.tags);
|
|
34
|
+
const testRuns = RunnerUtil.getTestRuns(tests)
|
|
35
|
+
.sort((a, b) => a.runId!.localeCompare(b.runId!));
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
await consumer.onStart({ testCount });
|
|
37
|
+
await consumer.onStart({ testCount: tests.length });
|
|
41
38
|
await WorkPool.run(
|
|
42
|
-
buildStandardTestManager
|
|
43
|
-
|
|
39
|
+
f => buildStandardTestManager(consumer, f),
|
|
40
|
+
testRuns,
|
|
44
41
|
{
|
|
45
42
|
idleTimeoutMillis: TimeUtil.asMillis(10, 's'),
|
|
46
43
|
min: 1,
|
|
47
|
-
max: this.#state.concurrency
|
|
44
|
+
max: this.#state.concurrency
|
|
48
45
|
});
|
|
49
46
|
|
|
50
47
|
return consumer.summarizeAsBoolean();
|
|
@@ -53,18 +50,30 @@ export class Runner {
|
|
|
53
50
|
/**
|
|
54
51
|
* Run a single file
|
|
55
52
|
*/
|
|
56
|
-
async runSingle(): Promise<boolean> {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
RuntimeIndex.
|
|
53
|
+
async runSingle(run: TestRun): Promise<boolean> {
|
|
54
|
+
run.import =
|
|
55
|
+
RuntimeIndex.getFromImport(run.import)?.import ??
|
|
56
|
+
RuntimeIndex.getFromSource(path.resolve(run.import))?.import!;
|
|
57
|
+
|
|
58
|
+
const entry = RuntimeIndex.getFromImport(run.import)!;
|
|
59
|
+
|
|
60
|
+
if (entry.module !== Runtime.main.name) {
|
|
61
|
+
RuntimeIndex.reinitForModule(entry.module);
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
const
|
|
64
|
+
const filter = (run.methodNames?.length) ?
|
|
65
|
+
(cfg: TestConfig): boolean => run.methodNames!.includes(cfg.methodName) :
|
|
66
|
+
undefined;
|
|
63
67
|
|
|
64
|
-
const
|
|
68
|
+
const consumer = (await RunnableTestConsumer.get(this.#state.consumer ?? this.#state.format))
|
|
69
|
+
.withTransformer(e => {
|
|
70
|
+
// Copy run metadata to event
|
|
71
|
+
e.metadata = run.metadata;
|
|
72
|
+
return e;
|
|
73
|
+
});
|
|
65
74
|
|
|
66
75
|
await consumer.onStart({});
|
|
67
|
-
await TestExecutor
|
|
76
|
+
await new TestExecutor(consumer, filter).execute(run);
|
|
68
77
|
return consumer.summarizeAsBoolean();
|
|
69
78
|
}
|
|
70
79
|
|
|
@@ -72,9 +81,10 @@ export class Runner {
|
|
|
72
81
|
* Run the runner, based on the inputs passed to the constructor
|
|
73
82
|
*/
|
|
74
83
|
async run(): Promise<boolean | undefined> {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
if ('import' in this.#state.target) {
|
|
85
|
+
return await this.runSingle(this.#state.target);
|
|
86
|
+
} else {
|
|
87
|
+
return await this.runFiles(this.#state.target.globs);
|
|
78
88
|
}
|
|
79
89
|
}
|
|
80
90
|
}
|
package/src/execute/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TestConsumer } from '../consumer/types';
|
|
2
|
+
import { TestRun } from '../model/test';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Run state
|
|
@@ -13,19 +14,20 @@ export interface RunState {
|
|
|
13
14
|
*/
|
|
14
15
|
consumer?: TestConsumer;
|
|
15
16
|
/**
|
|
16
|
-
*
|
|
17
|
-
*/
|
|
18
|
-
mode?: 'single' | 'watch' | 'standard';
|
|
19
|
-
/**
|
|
20
|
-
* Show progress to stderr
|
|
17
|
+
* Number of test suites to run concurrently, when mode is not single
|
|
21
18
|
*/
|
|
22
|
-
|
|
19
|
+
concurrency?: number;
|
|
23
20
|
/**
|
|
24
|
-
*
|
|
21
|
+
* The tags to include or exclude from testing
|
|
25
22
|
*/
|
|
26
|
-
|
|
23
|
+
tags?: string[];
|
|
27
24
|
/**
|
|
28
|
-
*
|
|
25
|
+
* target
|
|
29
26
|
*/
|
|
30
|
-
|
|
27
|
+
target: TestRun | {
|
|
28
|
+
/**
|
|
29
|
+
* Globs to run
|
|
30
|
+
*/
|
|
31
|
+
globs?: string[];
|
|
32
|
+
};
|
|
31
33
|
}
|
package/src/execute/util.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { createReadStream } from 'node:fs';
|
|
3
|
-
import
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import readline from 'node:readline/promises';
|
|
4
5
|
|
|
5
|
-
import { Env, ExecUtil, ShutdownManager, Util } from '@travetto/
|
|
6
|
-
import {
|
|
6
|
+
import { Env, ExecUtil, ShutdownManager, Util, RuntimeIndex, Runtime } from '@travetto/runtime';
|
|
7
|
+
import { TestConfig, TestRun } from '../model/test';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Simple Test Utilities
|
|
@@ -19,48 +20,57 @@ export class RunnerUtil {
|
|
|
19
20
|
/**
|
|
20
21
|
* Determine if a given file path is a valid test file
|
|
21
22
|
*/
|
|
22
|
-
static isTestFile(file: string): Promise<boolean> {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
});
|
|
23
|
+
static async isTestFile(file: string): Promise<boolean> {
|
|
24
|
+
const reader = readline.createInterface({ input: createReadStream(file) });
|
|
25
|
+
const state = { imp: false, suite: false };
|
|
26
|
+
for await (const line of reader) {
|
|
27
|
+
state.imp ||= line.includes('@travetto/test');
|
|
28
|
+
state.suite ||= line.includes('Suite'); // Decorator or name
|
|
29
|
+
if (state.imp && state.suite) {
|
|
30
|
+
reader.close();
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Find all valid test files given the globs
|
|
39
39
|
*/
|
|
40
|
-
static async
|
|
41
|
-
const
|
|
40
|
+
static async* getTestImports(globs?: string[]): AsyncIterable<string> {
|
|
41
|
+
const all = RuntimeIndex.find({
|
|
42
42
|
module: m => m.roles.includes('test') || m.roles.includes('std'),
|
|
43
43
|
folder: f => f === 'test',
|
|
44
44
|
file: f => f.role === 'test'
|
|
45
|
-
})
|
|
46
|
-
.filter(f => globs?.some(g => g.test(f.sourceFile)) ?? true);
|
|
47
|
-
|
|
48
|
-
const validFiles = files
|
|
49
|
-
.map(f => this.isTestFile(f.sourceFile).then(valid => ({ file: f, valid })));
|
|
45
|
+
});
|
|
50
46
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
.map(x => x.
|
|
47
|
+
// Collect globs
|
|
48
|
+
if (globs?.length) {
|
|
49
|
+
const allFiles = new Map(all.map(x => [x.sourceFile, x]));
|
|
50
|
+
for await (const item of fs.glob(globs)) {
|
|
51
|
+
const src = Runtime.workspaceRelative(item);
|
|
52
|
+
const match = allFiles.get(src);
|
|
53
|
+
if (match && await this.isTestFile(match.sourceFile)) {
|
|
54
|
+
yield match.import;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
for await (const match of all) {
|
|
59
|
+
if (await this.isTestFile(match.sourceFile)) {
|
|
60
|
+
yield match.import;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
54
64
|
}
|
|
55
65
|
|
|
56
66
|
/**
|
|
57
|
-
* Get count of tests for a given set of
|
|
58
|
-
* @param
|
|
67
|
+
* Get count of tests for a given set of globs
|
|
68
|
+
* @param globs
|
|
59
69
|
* @returns
|
|
60
70
|
*/
|
|
61
|
-
static async
|
|
71
|
+
static async getTestDigest(globs: string[] = ['**/*.ts'], tags?: string[]): Promise<TestConfig[]> {
|
|
62
72
|
const countRes = await ExecUtil.getResult(
|
|
63
|
-
spawn('npx', ['trv', 'test:
|
|
73
|
+
spawn('npx', ['trv', 'test:digest', '-o', 'json', ...globs], {
|
|
64
74
|
env: { ...process.env, ...Env.FORCE_COLOR.export(0), ...Env.NO_COLOR.export(true) }
|
|
65
75
|
}),
|
|
66
76
|
{ catch: true }
|
|
@@ -68,13 +78,30 @@ export class RunnerUtil {
|
|
|
68
78
|
if (!countRes.valid) {
|
|
69
79
|
throw new Error(countRes.stderr);
|
|
70
80
|
}
|
|
71
|
-
|
|
81
|
+
|
|
82
|
+
const testFilter = tags?.length ?
|
|
83
|
+
Util.allowDeny<string, [TestConfig]>(
|
|
84
|
+
tags,
|
|
85
|
+
rule => rule,
|
|
86
|
+
(rule, core) => core.tags?.includes(rule) ?? false
|
|
87
|
+
) :
|
|
88
|
+
((): boolean => true);
|
|
89
|
+
|
|
90
|
+
const res: TestConfig[] = countRes.valid ? JSON.parse(countRes.stdout) : [];
|
|
91
|
+
return res.filter(testFilter);
|
|
72
92
|
}
|
|
73
93
|
|
|
74
94
|
/**
|
|
75
|
-
*
|
|
95
|
+
* Get run events
|
|
76
96
|
*/
|
|
77
|
-
static
|
|
78
|
-
|
|
97
|
+
static getTestRuns(tests: TestConfig[]): TestRun[] {
|
|
98
|
+
const events = tests.reduce((acc, test) => {
|
|
99
|
+
if (!acc.has(test.classId)) {
|
|
100
|
+
acc.set(test.classId, { import: test.import, classId: test.classId, methodNames: [], runId: Util.uuid() });
|
|
101
|
+
}
|
|
102
|
+
acc.get(test.classId)!.methodNames!.push(test.methodName);
|
|
103
|
+
return acc;
|
|
104
|
+
}, new Map<string, TestRun>());
|
|
105
|
+
return [...events.values()];
|
|
79
106
|
}
|
|
80
107
|
}
|
package/src/execute/watcher.ts
CHANGED
|
@@ -1,25 +1,14 @@
|
|
|
1
1
|
import { RootRegistry, MethodSource } from '@travetto/registry';
|
|
2
|
-
import { WorkPool
|
|
3
|
-
import { RuntimeIndex } from '@travetto/
|
|
2
|
+
import { WorkPool } from '@travetto/worker';
|
|
3
|
+
import { AsyncQueue, Runtime, RuntimeIndex, castTo, describeFunction } from '@travetto/runtime';
|
|
4
4
|
|
|
5
5
|
import { SuiteRegistry } from '../registry/suite';
|
|
6
6
|
import { buildStandardTestManager } from '../worker/standard';
|
|
7
7
|
import { TestConsumerRegistry } from '../consumer/registry';
|
|
8
8
|
import { CumulativeSummaryConsumer } from '../consumer/types/cumulative';
|
|
9
|
-
import {
|
|
9
|
+
import { TestRun } from '../model/test';
|
|
10
10
|
import { RunnerUtil } from './util';
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
function isRunEvent(ev: unknown): ev is RunEvent {
|
|
14
|
-
return typeof ev === 'object' && !!ev && 'type' in ev && typeof ev.type === 'string' && ev.type === 'run-test';
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export type TestWatchEvent =
|
|
18
|
-
TestEvent |
|
|
19
|
-
{ type: 'removeTest', method: string, file: string, classId: string } |
|
|
20
|
-
{ type: 'ready' } |
|
|
21
|
-
{ type: 'log', message: string };
|
|
22
|
-
|
|
11
|
+
import { TestReadyEvent, TestRemovedEvent } from '../worker/types';
|
|
23
12
|
/**
|
|
24
13
|
* Test Watcher.
|
|
25
14
|
*
|
|
@@ -33,16 +22,24 @@ export class TestWatcher {
|
|
|
33
22
|
static async watch(format: string, runAllOnStart = true): Promise<void> {
|
|
34
23
|
console.debug('Listening for changes');
|
|
35
24
|
|
|
36
|
-
const itr = new WorkQueue<string>();
|
|
37
|
-
|
|
38
25
|
await SuiteRegistry.init();
|
|
39
26
|
SuiteRegistry.listen(RootRegistry);
|
|
27
|
+
await RootRegistry.init();
|
|
28
|
+
|
|
29
|
+
const events: TestRun[] = [];
|
|
30
|
+
|
|
31
|
+
if (runAllOnStart) {
|
|
32
|
+
const tests = await RunnerUtil.getTestDigest();
|
|
33
|
+
events.push(...RunnerUtil.getTestRuns(tests));
|
|
34
|
+
}
|
|
40
35
|
|
|
41
|
-
const
|
|
36
|
+
const itr = new AsyncQueue(events);
|
|
37
|
+
const consumer = new CumulativeSummaryConsumer(await TestConsumerRegistry.getInstance(format))
|
|
38
|
+
.withFilter(x => x.metadata?.partial !== true || x.type !== 'suite');
|
|
42
39
|
|
|
43
40
|
new MethodSource(RootRegistry).on(e => {
|
|
44
41
|
const [cls, method] = (e.prev ?? e.curr ?? []);
|
|
45
|
-
if (!cls ||
|
|
42
|
+
if (!cls || describeFunction(cls).abstract) {
|
|
46
43
|
return;
|
|
47
44
|
}
|
|
48
45
|
if (!method) {
|
|
@@ -52,39 +49,40 @@ export class TestWatcher {
|
|
|
52
49
|
const conf = SuiteRegistry.getByClassAndMethod(cls, method)!;
|
|
53
50
|
if (e.type !== 'removing') {
|
|
54
51
|
if (conf) {
|
|
55
|
-
const
|
|
56
|
-
|
|
52
|
+
const run: TestRun = {
|
|
53
|
+
import: conf.import, classId: conf.classId, methodNames: [conf.methodName], metadata: { partial: true }
|
|
54
|
+
};
|
|
55
|
+
console.log('Triggering', run);
|
|
56
|
+
itr.add(run, true); // Shift to front
|
|
57
57
|
}
|
|
58
58
|
} else {
|
|
59
59
|
process.send?.({
|
|
60
60
|
type: 'removeTest',
|
|
61
|
+
methodNames: method?.name ? [method.name!] : undefined!,
|
|
61
62
|
method: method?.name,
|
|
62
63
|
classId: cls?.Ⲑid,
|
|
63
|
-
|
|
64
|
-
});
|
|
64
|
+
import: Runtime.getImport(cls)
|
|
65
|
+
} satisfies TestRemovedEvent);
|
|
65
66
|
}
|
|
66
67
|
});
|
|
67
68
|
|
|
68
|
-
// If a file is changed, but doesn't emit classes, re-run whole file
|
|
69
|
-
RootRegistry.onNonClassChanges(file => itr.add(file));
|
|
70
69
|
|
|
71
|
-
|
|
70
|
+
// If a file is changed, but doesn't emit classes, re-run whole file
|
|
71
|
+
RootRegistry.onNonClassChanges(imp => itr.add({ import: imp }));
|
|
72
72
|
|
|
73
73
|
process.on('message', ev => {
|
|
74
|
-
if (
|
|
74
|
+
if (typeof ev === 'object' && ev && 'type' in ev && ev.type === 'run-test') {
|
|
75
|
+
console.log('Received message', ev);
|
|
76
|
+
// Legacy
|
|
77
|
+
if ('file' in ev && typeof ev.file === 'string') {
|
|
78
|
+
ev = { import: RuntimeIndex.getFromSource(ev.file)?.import! };
|
|
79
|
+
}
|
|
75
80
|
console.debug('Manually triggered', ev);
|
|
76
|
-
itr.add(
|
|
81
|
+
itr.add(castTo(ev), true);
|
|
77
82
|
}
|
|
78
83
|
});
|
|
79
84
|
|
|
80
|
-
process.send?.({ type: 'ready' });
|
|
81
|
-
|
|
82
|
-
if (runAllOnStart) {
|
|
83
|
-
for (const test of await RunnerUtil.getTestFiles()) {
|
|
84
|
-
await import(test.import);
|
|
85
|
-
itr.add(test.sourceFile);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
85
|
+
process.send?.({ type: 'ready' } satisfies TestReadyEvent);
|
|
88
86
|
|
|
89
87
|
await WorkPool.run(
|
|
90
88
|
buildStandardTestManager.bind(null, consumer),
|
package/src/fixture.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import { FileLoader } from '@travetto/
|
|
1
|
+
import { FileLoader, Runtime } from '@travetto/runtime';
|
|
2
2
|
|
|
3
3
|
export class TestFixtures extends FileLoader {
|
|
4
4
|
constructor(modules: string[] = []) {
|
|
5
|
-
super([
|
|
5
|
+
super([
|
|
6
|
+
'@#test/fixtures',
|
|
7
|
+
'@#support/fixtures',
|
|
8
|
+
...modules.flat().map(x => `${x}#support/fixtures`),
|
|
9
|
+
'@@#support/fixtures'
|
|
10
|
+
].map(v => Runtime.modulePath(v)));
|
|
6
11
|
}
|
|
7
12
|
}
|
package/src/model/common.ts
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
/** Configuration of a skip */
|
|
2
|
-
export type Skip = boolean | ((instance: unknown) => boolean | Promise<boolean>)
|
|
2
|
+
export type Skip = boolean | ((instance: unknown) => boolean | Promise<boolean>);
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Core Suite definition
|
|
6
6
|
*/
|
|
7
7
|
export interface SuiteCore {
|
|
8
|
-
/**
|
|
9
|
-
* The module the test is declared in
|
|
10
|
-
*/
|
|
11
|
-
module: string;
|
|
12
8
|
/**
|
|
13
9
|
* The class id
|
|
14
10
|
*/
|
|
@@ -18,9 +14,9 @@ export interface SuiteCore {
|
|
|
18
14
|
*/
|
|
19
15
|
description: string;
|
|
20
16
|
/**
|
|
21
|
-
*
|
|
17
|
+
* The import location for the suite
|
|
22
18
|
*/
|
|
23
|
-
|
|
19
|
+
import: string;
|
|
24
20
|
/**
|
|
25
21
|
* The first line of the unit
|
|
26
22
|
*/
|
|
@@ -29,6 +25,10 @@ export interface SuiteCore {
|
|
|
29
25
|
* The last line of the unit
|
|
30
26
|
*/
|
|
31
27
|
lineEnd: number;
|
|
28
|
+
/**
|
|
29
|
+
* Tags for a suite or a test
|
|
30
|
+
*/
|
|
31
|
+
tags?: string[];
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
@@ -39,4 +39,8 @@ export interface TestCore extends SuiteCore {
|
|
|
39
39
|
* The first line of the unit body
|
|
40
40
|
*/
|
|
41
41
|
lineBodyStart: number;
|
|
42
|
+
/**
|
|
43
|
+
* For extended suites, this is location of the actual file where the test exists
|
|
44
|
+
*/
|
|
45
|
+
sourceImport?: string;
|
|
42
46
|
}
|
package/src/model/event.ts
CHANGED
|
@@ -5,17 +5,21 @@ import { SuiteConfig, SuiteResult } from './suite';
|
|
|
5
5
|
* Targets
|
|
6
6
|
*/
|
|
7
7
|
export type EventEntity = 'test' | 'suite' | 'assertion';
|
|
8
|
+
|
|
8
9
|
/**
|
|
9
10
|
* Phases
|
|
10
11
|
*/
|
|
11
12
|
export type EventPhase = 'before' | 'after';
|
|
12
13
|
|
|
14
|
+
type EventTpl<T extends EventEntity, P extends EventPhase, V extends {}> =
|
|
15
|
+
{ type: T, phase: P, metadata?: Record<string, unknown> } & V;
|
|
16
|
+
|
|
13
17
|
/**
|
|
14
18
|
* Different test event shapes
|
|
15
19
|
*/
|
|
16
20
|
export type TestEvent =
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
EventTpl<'assertion', 'after', { assertion: Assertion }> |
|
|
22
|
+
EventTpl<'test', 'before', { test: TestConfig }> |
|
|
23
|
+
EventTpl<'test', 'after', { test: TestResult }> |
|
|
24
|
+
EventTpl<'suite', 'before', { suite: SuiteConfig }> |
|
|
25
|
+
EventTpl<'suite', 'after', { suite: SuiteResult }>;
|
package/src/model/suite.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { Class } from '@travetto/
|
|
1
|
+
import type { Class } from '@travetto/runtime';
|
|
2
2
|
|
|
3
|
-
import { TestConfig, TestResult } from './test';
|
|
3
|
+
import { Assertion, TestConfig, TestResult } from './test';
|
|
4
4
|
import { Skip, SuiteCore } from './common';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -60,9 +60,9 @@ export interface SuiteResult extends Counts {
|
|
|
60
60
|
*/
|
|
61
61
|
classId: string;
|
|
62
62
|
/**
|
|
63
|
-
*
|
|
63
|
+
* Import for the suite
|
|
64
64
|
*/
|
|
65
|
-
|
|
65
|
+
import: string;
|
|
66
66
|
/**
|
|
67
67
|
* Start of the suite
|
|
68
68
|
*/
|
|
@@ -80,3 +80,13 @@ export interface SuiteResult extends Counts {
|
|
|
80
80
|
*/
|
|
81
81
|
duration: number;
|
|
82
82
|
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A total suite failure
|
|
86
|
+
*/
|
|
87
|
+
export interface SuiteFailure {
|
|
88
|
+
assert: Assertion;
|
|
89
|
+
testResult: TestResult;
|
|
90
|
+
test: TestConfig;
|
|
91
|
+
suite: SuiteConfig;
|
|
92
|
+
}
|
package/src/model/test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Class, TimeSpan } from '@travetto/
|
|
1
|
+
import type { Class, TimeSpan } from '@travetto/runtime';
|
|
2
2
|
import { Skip, TestCore } from './common';
|
|
3
3
|
|
|
4
4
|
export type ThrowableError = string | RegExp | Function;
|
|
@@ -62,9 +62,9 @@ export interface Assertion {
|
|
|
62
62
|
*/
|
|
63
63
|
error?: Error;
|
|
64
64
|
/**
|
|
65
|
-
*
|
|
65
|
+
* Import of assertion
|
|
66
66
|
*/
|
|
67
|
-
|
|
67
|
+
import: string;
|
|
68
68
|
/**
|
|
69
69
|
* Line number
|
|
70
70
|
*/
|
|
@@ -107,4 +107,30 @@ export interface TestResult extends TestCore {
|
|
|
107
107
|
* Logging output
|
|
108
108
|
*/
|
|
109
109
|
output: Record<string, string>;
|
|
110
|
-
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Test Run
|
|
114
|
+
*/
|
|
115
|
+
export type TestRun = {
|
|
116
|
+
/**
|
|
117
|
+
* Import for run
|
|
118
|
+
*/
|
|
119
|
+
import: string;
|
|
120
|
+
/**
|
|
121
|
+
* Suite class id
|
|
122
|
+
*/
|
|
123
|
+
classId?: string;
|
|
124
|
+
/**
|
|
125
|
+
* Methods names we want to target
|
|
126
|
+
*/
|
|
127
|
+
methodNames?: string[];
|
|
128
|
+
/**
|
|
129
|
+
* Test run metadata
|
|
130
|
+
*/
|
|
131
|
+
metadata?: Record<string, unknown>;
|
|
132
|
+
/**
|
|
133
|
+
* unique id for the run
|
|
134
|
+
*/
|
|
135
|
+
runId?: string;
|
|
136
|
+
};
|