@travetto/test 8.0.0-alpha.2 → 8.0.0-alpha.21
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 +24 -22
- package/__index__.ts +9 -9
- package/package.json +8 -8
- package/src/assert/capture.ts +1 -2
- package/src/assert/check.ts +56 -33
- package/src/assert/types.ts +1 -1
- package/src/assert/util.ts +26 -81
- 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 +11 -23
- package/src/consumer/types/delegating.ts +8 -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 -5
- package/src/consumer/types/summarizer.ts +5 -18
- package/src/consumer/types/tap-summary.ts +39 -30
- package/src/consumer/types/tap.ts +64 -56
- package/src/consumer/types/util.ts +31 -0
- package/src/consumer/types/xunit.ts +10 -8
- package/src/consumer/types.ts +6 -10
- 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 +101 -157
- package/src/execute/phase.ts +12 -22
- package/src/execute/run.ts +41 -40
- 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 +15 -13
- package/src/model/test.ts +11 -8
- package/src/model/util.ts +102 -9
- package/src/registry/registry-adapter.ts +10 -13
- package/src/registry/registry-index.ts +13 -11
- package/src/worker/child.ts +10 -9
- 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 +40 -32
package/src/execute/run.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { createReadStream } from 'node:fs';
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
|
-
import readline from 'node:readline/promises';
|
|
4
3
|
import path from 'node:path';
|
|
4
|
+
import readline from 'node:readline/promises';
|
|
5
5
|
|
|
6
|
-
import { Env, ExecUtil, Util, RuntimeIndex, Runtime, TimeUtil, JSONUtil } from '@travetto/runtime';
|
|
7
|
-
import { WorkPool } from '@travetto/worker';
|
|
8
6
|
import { Registry } from '@travetto/registry';
|
|
7
|
+
import { describeFunction, Env, ExecUtil, JSONUtil, Runtime, RuntimeIndex, TimeUtil, Util } from '@travetto/runtime';
|
|
8
|
+
import { WorkPool } from '@travetto/worker';
|
|
9
9
|
|
|
10
|
-
import type { TestConfig, TestRunInput, TestRun, TestGlobInput, TestDiffInput } from '../model/test.ts';
|
|
11
|
-
import type { TestRemoveEvent } from '../model/event.ts';
|
|
12
|
-
import type { TestConsumerShape } from '../consumer/types.ts';
|
|
13
|
-
import { RunnableTestConsumer } from '../consumer/types/runnable.ts';
|
|
14
|
-
import type { TestConsumerConfig } from './types.ts';
|
|
15
10
|
import { TestConsumerRegistryIndex } from '../consumer/registry-index.ts';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
11
|
+
import { RunnableTestConsumer } from '../consumer/types/runnable.ts';
|
|
12
|
+
import type { TestConsumerShape } from '../consumer/types.ts';
|
|
13
|
+
import type { TestRemoveEvent } from '../model/event.ts';
|
|
14
|
+
import type { TestConfig, TestDiffInput, TestGlobInput, TestRun, TestRunInput } from '../model/test.ts';
|
|
18
15
|
import { SuiteRegistryIndex } from '../registry/registry-index.ts';
|
|
16
|
+
import { buildStandardTestManager } from '../worker/standard.ts';
|
|
17
|
+
import { TestExecutor } from './executor.ts';
|
|
18
|
+
import type { TestConsumerConfig } from './types.ts';
|
|
19
19
|
|
|
20
20
|
type RunState = {
|
|
21
21
|
runs: TestRun[];
|
|
@@ -26,7 +26,6 @@ type RunState = {
|
|
|
26
26
|
* Test Utilities for Running
|
|
27
27
|
*/
|
|
28
28
|
export class RunUtil {
|
|
29
|
-
|
|
30
29
|
/**
|
|
31
30
|
* Determine if a given file path is a valid test file
|
|
32
31
|
*/
|
|
@@ -47,7 +46,7 @@ export class RunUtil {
|
|
|
47
46
|
/**
|
|
48
47
|
* Find all valid test files given the globs
|
|
49
48
|
*/
|
|
50
|
-
static async*
|
|
49
|
+
static async *getTestImports(globs?: string[]): AsyncIterable<string> {
|
|
51
50
|
const all = RuntimeIndex.find({
|
|
52
51
|
module: module => module.roles.includes('test') || module.roles.includes('std'),
|
|
53
52
|
folder: folder => folder === 'test',
|
|
@@ -60,7 +59,7 @@ export class RunUtil {
|
|
|
60
59
|
for await (const item of fs.glob(globs)) {
|
|
61
60
|
const source = Runtime.workspaceRelative(path.resolve(item));
|
|
62
61
|
const match = allFiles.get(source);
|
|
63
|
-
if (match && await this.isTestFile(match.sourceFile)) {
|
|
62
|
+
if (match && (await this.isTestFile(match.sourceFile))) {
|
|
64
63
|
yield match.import;
|
|
65
64
|
}
|
|
66
65
|
}
|
|
@@ -80,7 +79,7 @@ export class RunUtil {
|
|
|
80
79
|
static async resolveGlobInput({ globs, tags, metadata }: TestGlobInput): Promise<TestRun[]> {
|
|
81
80
|
const digestProcess = await ExecUtil.getResult(
|
|
82
81
|
ExecUtil.spawnPackageCommand('trv', ['test:digest', '-o', 'json', ...globs], {
|
|
83
|
-
env: { ...process.env, ...Env.FORCE_COLOR.export(0), ...Env.NO_COLOR.export(true) }
|
|
82
|
+
env: { ...process.env, ...Env.FORCE_COLOR.export(0), ...Env.NO_COLOR.export(true) }
|
|
84
83
|
}),
|
|
85
84
|
{ catch: true }
|
|
86
85
|
);
|
|
@@ -89,20 +88,20 @@ export class RunUtil {
|
|
|
89
88
|
throw new Error(digestProcess.stderr);
|
|
90
89
|
}
|
|
91
90
|
|
|
92
|
-
const testFilter = tags?.length
|
|
93
|
-
Util.allowDeny<string, [TestConfig]>(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
(
|
|
91
|
+
const testFilter = tags?.length
|
|
92
|
+
? Util.allowDeny<string, [TestConfig]>(
|
|
93
|
+
tags,
|
|
94
|
+
rule => rule,
|
|
95
|
+
(rule, core) => core.tags?.includes(rule) ?? false
|
|
96
|
+
)
|
|
97
|
+
: (): boolean => true;
|
|
99
98
|
|
|
100
99
|
const parsed: TestConfig[] = JSONUtil.fromUTF8(digestProcess.stdout);
|
|
101
100
|
|
|
102
101
|
const events = parsed.filter(testFilter).reduce((runs, test) => {
|
|
103
|
-
runs
|
|
104
|
-
{ import: test.import, classId: test.classId, methodNames: [], runId: Util.uuid(), metadata }
|
|
105
|
-
|
|
102
|
+
runs
|
|
103
|
+
.getOrInsert(test.classId, { import: test.import, classId: test.classId, methodNames: [], runId: Util.uuid(), metadata })
|
|
104
|
+
.methodNames!.push(test.methodName);
|
|
106
105
|
return runs;
|
|
107
106
|
}, new Map<string, TestRun>());
|
|
108
107
|
|
|
@@ -126,6 +125,7 @@ export class RunUtil {
|
|
|
126
125
|
const imported = await Registry.manualInit([importPath]);
|
|
127
126
|
const classes = Object.fromEntries(
|
|
128
127
|
imported
|
|
128
|
+
.filter(cls => !describeFunction(cls).abstract)
|
|
129
129
|
.filter(cls => SuiteRegistryIndex.hasConfig(cls))
|
|
130
130
|
.map(cls => [cls.Ⲑid, SuiteRegistryIndex.getConfig(cls)])
|
|
131
131
|
);
|
|
@@ -140,18 +140,22 @@ export class RunUtil {
|
|
|
140
140
|
// Looking at Diff
|
|
141
141
|
for (const [clsId, config] of Object.entries(diff)) {
|
|
142
142
|
const local = classes[clsId];
|
|
143
|
-
if (!local) {
|
|
143
|
+
if (!local) {
|
|
144
|
+
// Removed classes
|
|
144
145
|
removeTest(clsId);
|
|
145
|
-
} else if (local.sourceHash !== config.sourceHash) {
|
|
146
|
+
} else if (local.sourceHash !== config.sourceHash) {
|
|
147
|
+
// Class changed or added
|
|
146
148
|
// Methods to run, defaults to newly added
|
|
147
149
|
const methods: string[] = Object.keys(local.tests ?? {}).filter(key => !config.methods[key]);
|
|
148
150
|
let didRemove = false;
|
|
149
151
|
for (const key of Object.keys(config.methods)) {
|
|
150
152
|
const localMethod = local.tests?.[key];
|
|
151
|
-
if (!localMethod) {
|
|
153
|
+
if (!localMethod) {
|
|
154
|
+
// Test is removed
|
|
152
155
|
removeTest(clsId, key);
|
|
153
156
|
didRemove = true;
|
|
154
|
-
} else if (localMethod.sourceHash !== config.methods[key]) {
|
|
157
|
+
} else if (localMethod.sourceHash !== config.methods[key]) {
|
|
158
|
+
// Method changed or added
|
|
155
159
|
methods.push(key);
|
|
156
160
|
}
|
|
157
161
|
}
|
|
@@ -161,7 +165,8 @@ export class RunUtil {
|
|
|
161
165
|
}
|
|
162
166
|
}
|
|
163
167
|
|
|
164
|
-
if (runs.length === 0 && removes.length === 0) {
|
|
168
|
+
if (runs.length === 0 && removes.length === 0) {
|
|
169
|
+
// Re-run entire file, classes unchanged
|
|
165
170
|
addRun(undefined);
|
|
166
171
|
}
|
|
167
172
|
|
|
@@ -222,19 +227,15 @@ export class RunUtil {
|
|
|
222
227
|
}
|
|
223
228
|
|
|
224
229
|
if (runs.length === 1) {
|
|
225
|
-
await new TestExecutor(consumer).execute(runs[0]);
|
|
230
|
+
await new TestExecutor(consumer).execute(runs[0], true);
|
|
226
231
|
} else {
|
|
227
|
-
await WorkPool.run(
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
min: 1,
|
|
233
|
-
max: consumerConfig.concurrency
|
|
234
|
-
}
|
|
235
|
-
);
|
|
232
|
+
await WorkPool.run(run => buildStandardTestManager(consumer, run), runs, {
|
|
233
|
+
idleTimeoutMillis: TimeUtil.duration('10s', 'ms'),
|
|
234
|
+
min: 1,
|
|
235
|
+
max: consumerConfig.concurrency
|
|
236
|
+
});
|
|
236
237
|
}
|
|
237
238
|
|
|
238
239
|
return consumer.summarizeAsBoolean();
|
|
239
240
|
}
|
|
240
|
-
}
|
|
241
|
+
}
|
package/src/execute/watcher.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { ManifestModuleUtil } from '@travetto/manifest';
|
|
2
2
|
import { Registry } from '@travetto/registry';
|
|
3
|
-
import { WorkPool } from '@travetto/worker';
|
|
4
3
|
import { AsyncQueue, TimeUtil, WatchUtil } from '@travetto/runtime';
|
|
4
|
+
import { WorkPool } from '@travetto/worker';
|
|
5
5
|
|
|
6
|
-
import { buildStandardTestManager } from '../worker/standard.ts';
|
|
7
6
|
import { TestConsumerRegistryIndex } from '../consumer/registry-index.ts';
|
|
8
7
|
import { CumulativeSummaryConsumer } from '../consumer/types/cumulative.ts';
|
|
9
8
|
import type { TestDiffInput, TestRun } from '../model/test.ts';
|
|
10
|
-
import {
|
|
9
|
+
import { buildStandardTestManager } from '../worker/standard.ts';
|
|
11
10
|
import { isTestRunEvent, type TestReadyEvent } from '../worker/types.ts';
|
|
11
|
+
import { RunUtil } from './run.ts';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Test Watcher.
|
|
@@ -16,7 +16,6 @@ import { isTestRunEvent, type TestReadyEvent } from '../worker/types.ts';
|
|
|
16
16
|
* Runs all tests on startup, and then listens for changes to run tests again
|
|
17
17
|
*/
|
|
18
18
|
export class TestWatcher {
|
|
19
|
-
|
|
20
19
|
/**
|
|
21
20
|
* Start watching all test files
|
|
22
21
|
*/
|
|
@@ -28,13 +27,11 @@ export class TestWatcher {
|
|
|
28
27
|
const events: (TestRun | TestDiffInput)[] = [];
|
|
29
28
|
|
|
30
29
|
if (runAllOnStart) {
|
|
31
|
-
events.push(...await RunUtil.resolveGlobInput({ globs: [] }));
|
|
30
|
+
events.push(...(await RunUtil.resolveGlobInput({ globs: [] })));
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
const queue = new AsyncQueue(events);
|
|
35
|
-
const consumer = new CumulativeSummaryConsumer(
|
|
36
|
-
await TestConsumerRegistryIndex.getInstance({ consumer: format })
|
|
37
|
-
);
|
|
34
|
+
const consumer = new CumulativeSummaryConsumer(await TestConsumerRegistryIndex.getInstance({ consumer: format }));
|
|
38
35
|
|
|
39
36
|
process.on('message', event => {
|
|
40
37
|
if (isTestRunEvent(event)) {
|
|
@@ -44,19 +41,15 @@ export class TestWatcher {
|
|
|
44
41
|
|
|
45
42
|
process.send?.({ type: 'ready' } satisfies TestReadyEvent);
|
|
46
43
|
|
|
47
|
-
const queueProcessor = WorkPool.run(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
min: 2,
|
|
53
|
-
max: WorkPool.DEFAULT_SIZE
|
|
54
|
-
}
|
|
55
|
-
);
|
|
44
|
+
const queueProcessor = WorkPool.run(buildStandardTestManager.bind(null, consumer), queue, {
|
|
45
|
+
idleTimeoutMillis: TimeUtil.duration('2m', 'ms'),
|
|
46
|
+
min: 2,
|
|
47
|
+
max: WorkPool.DEFAULT_SIZE
|
|
48
|
+
});
|
|
56
49
|
|
|
57
50
|
await WatchUtil.watchCompilerEvents('change', event => {
|
|
58
51
|
const fileType = ManifestModuleUtil.getFileType(event.file);
|
|
59
|
-
if (
|
|
52
|
+
if (fileType === 'ts' || fileType === 'js') {
|
|
60
53
|
if (event.action === 'delete') {
|
|
61
54
|
consumer.removeTest(event.import);
|
|
62
55
|
} else {
|
|
@@ -69,4 +62,4 @@ export class TestWatcher {
|
|
|
69
62
|
// Cleanup
|
|
70
63
|
await queueProcessor;
|
|
71
64
|
}
|
|
72
|
-
}
|
|
65
|
+
}
|
package/src/fixture.ts
CHANGED
|
@@ -2,11 +2,10 @@ import { FileLoader, Runtime } from '@travetto/runtime';
|
|
|
2
2
|
|
|
3
3
|
export class TestFixtures extends FileLoader {
|
|
4
4
|
constructor(modules: string[] = []) {
|
|
5
|
-
super(
|
|
6
|
-
'@#test/fixtures',
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
].map(value => Runtime.modulePath(value)));
|
|
5
|
+
super(
|
|
6
|
+
['@#test/fixtures', '@#support/fixtures', ...modules.flat().map(module => `${module}#support/fixtures`), '@@#support/fixtures'].map(
|
|
7
|
+
value => Runtime.modulePath(value)
|
|
8
|
+
)
|
|
9
|
+
);
|
|
11
10
|
}
|
|
12
|
-
}
|
|
11
|
+
}
|
package/src/model/common.ts
CHANGED
package/src/model/error.ts
CHANGED
|
@@ -3,9 +3,9 @@ import { RuntimeError } from '@travetto/runtime';
|
|
|
3
3
|
/**
|
|
4
4
|
* Represents an execution error
|
|
5
5
|
*/
|
|
6
|
-
export class TestExecutionError extends RuntimeError {
|
|
6
|
+
export class TestExecutionError extends RuntimeError {}
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Timeout execution error
|
|
10
10
|
*/
|
|
11
|
-
export class TimeoutError extends TestExecutionError {
|
|
11
|
+
export class TimeoutError extends TestExecutionError {}
|
package/src/model/event.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Assertion, TestConfig, TestResult } from './test.ts';
|
|
2
1
|
import type { SuiteConfig, SuiteResult } from './suite.ts';
|
|
2
|
+
import type { Assertion, TestConfig, TestResult } from './test.ts';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Targets
|
|
@@ -11,17 +11,22 @@ export type EventEntity = 'test' | 'suite' | 'assertion';
|
|
|
11
11
|
*/
|
|
12
12
|
export type EventPhase = 'before' | 'after';
|
|
13
13
|
|
|
14
|
-
type EventTpl<T extends EventEntity, P extends EventPhase, V extends {}> =
|
|
15
|
-
{ type: T, phase: P, metadata?: Record<string, unknown> } & V;
|
|
14
|
+
type EventTpl<T extends EventEntity, P extends EventPhase, V extends {}> = { type: T; phase: P; metadata?: Record<string, unknown> } & V;
|
|
16
15
|
|
|
17
|
-
export type TestRemoveEvent = {
|
|
16
|
+
export type TestRemoveEvent = {
|
|
17
|
+
type: 'removeTest';
|
|
18
|
+
import: string;
|
|
19
|
+
classId?: string;
|
|
20
|
+
methodName?: string;
|
|
21
|
+
metadata?: Record<string, unknown>;
|
|
22
|
+
};
|
|
18
23
|
|
|
19
24
|
/**
|
|
20
25
|
* Different test event shapes
|
|
21
26
|
*/
|
|
22
27
|
export type TestEvent =
|
|
23
|
-
EventTpl<'assertion', 'after', { assertion: Assertion }>
|
|
24
|
-
EventTpl<'test', 'before', { test: TestConfig }>
|
|
25
|
-
EventTpl<'test', 'after', { test: TestResult }>
|
|
26
|
-
EventTpl<'suite', 'before', { suite: SuiteConfig }>
|
|
27
|
-
EventTpl<'suite', 'after', { suite: SuiteResult }>;
|
|
28
|
+
| EventTpl<'assertion', 'after', { assertion: Assertion }>
|
|
29
|
+
| EventTpl<'test', 'before', { test: TestConfig }>
|
|
30
|
+
| EventTpl<'test', 'after', { test: TestResult }>
|
|
31
|
+
| EventTpl<'suite', 'before', { suite: SuiteConfig }>
|
|
32
|
+
| EventTpl<'suite', 'after', { suite: SuiteResult }>;
|
package/src/model/suite.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Any, Class } from '@travetto/runtime';
|
|
2
2
|
|
|
3
|
-
import type { TestConfig, TestResult, TestStatus } from './test.ts';
|
|
4
3
|
import type { Skip, SuiteCore } from './common.ts';
|
|
4
|
+
import type { TestConfig, TestResult, TestStatus } from './test.ts';
|
|
5
5
|
|
|
6
6
|
export type SuitePhase = 'beforeAll' | 'beforeEach' | 'afterAll' | 'afterEach';
|
|
7
7
|
|
|
@@ -19,10 +19,6 @@ export interface SuiteConfig extends SuiteCore {
|
|
|
19
19
|
* Should this be skipped
|
|
20
20
|
*/
|
|
21
21
|
skip: Skip;
|
|
22
|
-
/**
|
|
23
|
-
* Actual class instance
|
|
24
|
-
*/
|
|
25
|
-
instance?: unknown;
|
|
26
22
|
/**
|
|
27
23
|
* Tests to run
|
|
28
24
|
*/
|
|
@@ -34,31 +30,37 @@ export interface SuiteConfig extends SuiteCore {
|
|
|
34
30
|
}
|
|
35
31
|
|
|
36
32
|
/**
|
|
37
|
-
*
|
|
33
|
+
* Test Counts
|
|
38
34
|
*/
|
|
39
|
-
export interface
|
|
35
|
+
export interface ResultsSummary {
|
|
36
|
+
/** Passing Test Count */
|
|
40
37
|
passed: number;
|
|
38
|
+
/** Skipped Test Count */
|
|
41
39
|
skipped: number;
|
|
40
|
+
/** Failed Test Count */
|
|
42
41
|
failed: number;
|
|
42
|
+
/** Errored Test Count */
|
|
43
43
|
errored: number;
|
|
44
|
+
/** Unknown Test Count */
|
|
44
45
|
unknown: number;
|
|
46
|
+
/** Total Test Count */
|
|
45
47
|
total: number;
|
|
48
|
+
/** Test Self Execution Duration */
|
|
49
|
+
selfDuration: number;
|
|
50
|
+
/** Total Duration */
|
|
51
|
+
duration: number;
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
/**
|
|
49
55
|
* Results of a suite run
|
|
50
56
|
*/
|
|
51
|
-
export interface SuiteResult extends
|
|
57
|
+
export interface SuiteResult extends ResultsSummary, SuiteCore {
|
|
52
58
|
/**
|
|
53
59
|
* All test results
|
|
54
60
|
*/
|
|
55
61
|
tests: Record<string, TestResult>;
|
|
56
|
-
/**
|
|
57
|
-
* Suite duration
|
|
58
|
-
*/
|
|
59
|
-
duration: number;
|
|
60
62
|
/**
|
|
61
63
|
* Overall status
|
|
62
64
|
*/
|
|
63
65
|
status: TestStatus;
|
|
64
|
-
}
|
|
66
|
+
}
|
package/src/model/test.ts
CHANGED
|
@@ -5,10 +5,13 @@ import type { Skip, TestCore } from './common.ts';
|
|
|
5
5
|
export type ThrowableError = string | RegExp | Class<Error> | ((error: Error | string) => boolean | void | undefined);
|
|
6
6
|
export type TestLog = Omit<ConsoleEvent, 'args' | 'scope'> & { message: string };
|
|
7
7
|
|
|
8
|
-
export type TestDiffSource = Record<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
export type TestDiffSource = Record<
|
|
9
|
+
string,
|
|
10
|
+
{
|
|
11
|
+
sourceHash: number;
|
|
12
|
+
methods: Record<string, number>;
|
|
13
|
+
}
|
|
14
|
+
>;
|
|
12
15
|
|
|
13
16
|
export type TestStatus = 'passed' | 'skipped' | 'errored' | 'failed' | 'unknown';
|
|
14
17
|
|
|
@@ -105,13 +108,13 @@ export interface TestResult extends TestCore {
|
|
|
105
108
|
*/
|
|
106
109
|
assertions: Assertion[];
|
|
107
110
|
/**
|
|
108
|
-
*
|
|
111
|
+
* Self Execution Duration
|
|
109
112
|
*/
|
|
110
|
-
|
|
113
|
+
selfDuration: number;
|
|
111
114
|
/**
|
|
112
115
|
* Total duration including before/after
|
|
113
116
|
*/
|
|
114
|
-
|
|
117
|
+
duration: number;
|
|
115
118
|
/**
|
|
116
119
|
* Logging output
|
|
117
120
|
*/
|
|
@@ -184,4 +187,4 @@ export type TestGlobInput = {
|
|
|
184
187
|
metadata?: Record<string, unknown>;
|
|
185
188
|
};
|
|
186
189
|
|
|
187
|
-
export type TestRunInput = TestRun | TestDiffInput | TestGlobInput;
|
|
190
|
+
export type TestRunInput = TestRun | TestDiffInput | TestGlobInput;
|
package/src/model/util.ts
CHANGED
|
@@ -1,14 +1,107 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
import { asFull, RuntimeIndex } from '@travetto/runtime';
|
|
4
|
+
|
|
5
|
+
import type { ResultsSummary, SuiteConfig, SuiteResult } from './suite.ts';
|
|
6
|
+
import type { TestConfig, TestResult, TestRun, TestStatus } from './test.ts';
|
|
3
7
|
|
|
4
8
|
export class TestModelUtil {
|
|
5
|
-
static
|
|
9
|
+
static computeTestStatus(summary: ResultsSummary): TestStatus {
|
|
6
10
|
switch (true) {
|
|
7
|
-
case
|
|
8
|
-
|
|
9
|
-
case
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
case summary.errored > 0:
|
|
12
|
+
return 'errored';
|
|
13
|
+
case summary.failed > 0:
|
|
14
|
+
return 'failed';
|
|
15
|
+
case summary.skipped > 0:
|
|
16
|
+
return 'skipped';
|
|
17
|
+
case summary.unknown > 0:
|
|
18
|
+
return 'unknown';
|
|
19
|
+
default:
|
|
20
|
+
return 'passed';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static buildSummary(): ResultsSummary {
|
|
25
|
+
return { passed: 0, failed: 0, skipped: 0, errored: 0, unknown: 0, total: 0, duration: 0, selfDuration: 0 };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static countTestResult<T extends ResultsSummary>(summary: T, tests: Pick<TestResult, 'status' | 'selfDuration' | 'duration'>[]): T {
|
|
29
|
+
for (const test of tests) {
|
|
30
|
+
summary[test.status] += 1;
|
|
31
|
+
summary.total += 1;
|
|
32
|
+
summary.selfDuration += test.selfDuration ?? 0;
|
|
33
|
+
summary.duration += test.duration ?? 0;
|
|
12
34
|
}
|
|
35
|
+
return summary;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* An empty suite result based on a suite config
|
|
40
|
+
*/
|
|
41
|
+
static createSuiteResult(suite: SuiteConfig, override?: Partial<SuiteResult>): SuiteResult {
|
|
42
|
+
return {
|
|
43
|
+
...TestModelUtil.buildSummary(),
|
|
44
|
+
status: 'unknown',
|
|
45
|
+
lineStart: suite.lineStart,
|
|
46
|
+
lineEnd: suite.lineEnd,
|
|
47
|
+
import: suite.import,
|
|
48
|
+
classId: suite.classId,
|
|
49
|
+
sourceHash: suite.sourceHash,
|
|
50
|
+
tests: {},
|
|
51
|
+
duration: 0,
|
|
52
|
+
selfDuration: 0,
|
|
53
|
+
...override
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* An empty test result based on a suite and test config
|
|
59
|
+
*/
|
|
60
|
+
static createTestResult(suite: SuiteConfig, test: TestConfig, override?: Partial<TestResult>): TestResult {
|
|
61
|
+
return {
|
|
62
|
+
methodName: test.methodName,
|
|
63
|
+
description: test.description,
|
|
64
|
+
classId: test.classId,
|
|
65
|
+
tags: test.tags,
|
|
66
|
+
suiteLineStart: suite.lineStart,
|
|
67
|
+
lineStart: test.lineStart,
|
|
68
|
+
lineEnd: test.lineEnd,
|
|
69
|
+
lineBodyStart: test.lineBodyStart,
|
|
70
|
+
import: test.import,
|
|
71
|
+
declarationImport: test.declarationImport,
|
|
72
|
+
sourceHash: test.sourceHash,
|
|
73
|
+
status: 'unknown',
|
|
74
|
+
assertions: [],
|
|
75
|
+
duration: 0,
|
|
76
|
+
selfDuration: 0,
|
|
77
|
+
output: [],
|
|
78
|
+
...override
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static createImportErrorSuiteResult(run: TestRun): SuiteResult {
|
|
83
|
+
const name = path.basename(run.import);
|
|
84
|
+
const classId = `${RuntimeIndex.getFromImport(run.import)?.id}#${name}`;
|
|
85
|
+
const common = { classId, duration: 0, lineStart: 1, lineEnd: 1, import: run.import } as const;
|
|
86
|
+
return asFull<SuiteResult>({
|
|
87
|
+
...common,
|
|
88
|
+
status: 'errored',
|
|
89
|
+
errored: 1,
|
|
90
|
+
tests: {
|
|
91
|
+
impport: asFull<TestResult>({
|
|
92
|
+
...common,
|
|
93
|
+
status: 'errored',
|
|
94
|
+
assertions: [
|
|
95
|
+
{
|
|
96
|
+
...common,
|
|
97
|
+
line: common.lineStart,
|
|
98
|
+
methodName: 'import',
|
|
99
|
+
operator: 'import',
|
|
100
|
+
text: `Failed to import ${run.import}`
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
});
|
|
13
106
|
}
|
|
14
|
-
}
|
|
107
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RegistryAdapter } from '@travetto/registry';
|
|
2
|
-
import {
|
|
2
|
+
import { asFull, type Class, describeFunction, Runtime, RuntimeError, safeAssign } from '@travetto/runtime';
|
|
3
3
|
import { SchemaRegistryIndex } from '@travetto/schema';
|
|
4
4
|
|
|
5
5
|
import type { SuiteConfig } from '../model/suite.ts';
|
|
@@ -8,7 +8,7 @@ import type { TestConfig } from '../model/test.ts';
|
|
|
8
8
|
function combineClasses(baseConfig: SuiteConfig, ...subConfig: Partial<SuiteConfig>[]): SuiteConfig {
|
|
9
9
|
for (const config of subConfig) {
|
|
10
10
|
if (config.tags) {
|
|
11
|
-
baseConfig.tags = [...new Set([...baseConfig.tags ?? [], ...config.tags])];
|
|
11
|
+
baseConfig.tags = [...new Set([...(baseConfig.tags ?? []), ...config.tags])];
|
|
12
12
|
}
|
|
13
13
|
baseConfig.skip = config.skip ?? baseConfig.skip;
|
|
14
14
|
|
|
@@ -22,7 +22,7 @@ function combineClasses(baseConfig: SuiteConfig, ...subConfig: Partial<SuiteConf
|
|
|
22
22
|
...test,
|
|
23
23
|
class: baseConfig.class,
|
|
24
24
|
classId: baseConfig.classId,
|
|
25
|
-
import: baseConfig.import
|
|
25
|
+
import: baseConfig.import
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -31,7 +31,7 @@ function combineClasses(baseConfig: SuiteConfig, ...subConfig: Partial<SuiteConf
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
function combineWithParent(baseConfig: SuiteConfig, parentConfig: SuiteConfig): SuiteConfig {
|
|
34
|
-
baseConfig.tags = [...parentConfig.tags ?? [], ...baseConfig.tags ?? []];
|
|
34
|
+
baseConfig.tags = [...(parentConfig.tags ?? []), ...(baseConfig.tags ?? [])];
|
|
35
35
|
baseConfig.skip = baseConfig.skip ?? parentConfig.skip;
|
|
36
36
|
baseConfig.phaseHandlers = [...(parentConfig.phaseHandlers ?? []), ...(baseConfig.phaseHandlers ?? [])];
|
|
37
37
|
for (const [key, test] of Object.entries(parentConfig.tests ?? {})) {
|
|
@@ -39,7 +39,7 @@ function combineWithParent(baseConfig: SuiteConfig, parentConfig: SuiteConfig):
|
|
|
39
39
|
...test,
|
|
40
40
|
class: baseConfig.class,
|
|
41
41
|
classId: baseConfig.classId,
|
|
42
|
-
import: baseConfig.import
|
|
42
|
+
import: baseConfig.import
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
return baseConfig;
|
|
@@ -50,10 +50,7 @@ function combineMethods(suite: SuiteConfig, baseConfig: TestConfig, ...subConfig
|
|
|
50
50
|
baseConfig.import = suite.import;
|
|
51
51
|
for (const config of subConfig) {
|
|
52
52
|
safeAssign(baseConfig, config, {
|
|
53
|
-
tags: [
|
|
54
|
-
...baseConfig.tags ?? [],
|
|
55
|
-
...config.tags ?? []
|
|
56
|
-
]
|
|
53
|
+
tags: [...(baseConfig.tags ?? []), ...(config.tags ?? [])]
|
|
57
54
|
});
|
|
58
55
|
}
|
|
59
56
|
return baseConfig;
|
|
@@ -80,7 +77,7 @@ export class SuiteRegistryAdapter implements RegistryAdapter<SuiteConfig> {
|
|
|
80
77
|
lineEnd: lines?.[1],
|
|
81
78
|
sourceHash: hash,
|
|
82
79
|
tests: {},
|
|
83
|
-
phaseHandlers: []
|
|
80
|
+
phaseHandlers: []
|
|
84
81
|
});
|
|
85
82
|
}
|
|
86
83
|
combineClasses(this.#config, ...data);
|
|
@@ -102,7 +99,7 @@ export class SuiteRegistryAdapter implements RegistryAdapter<SuiteConfig> {
|
|
|
102
99
|
lineEnd: lines?.[1],
|
|
103
100
|
lineBodyStart: lines?.[2],
|
|
104
101
|
methodName: method,
|
|
105
|
-
sourceHash: hash
|
|
102
|
+
sourceHash: hash
|
|
106
103
|
});
|
|
107
104
|
this.#config.tests[method] = config;
|
|
108
105
|
}
|
|
@@ -118,7 +115,7 @@ export class SuiteRegistryAdapter implements RegistryAdapter<SuiteConfig> {
|
|
|
118
115
|
}
|
|
119
116
|
|
|
120
117
|
for (const test of Object.values(this.#config.tests)) {
|
|
121
|
-
test.tags = [...new Set([...test.tags ?? [], ...this.#config.tags ?? []])];
|
|
118
|
+
test.tags = [...new Set([...(test.tags ?? []), ...(this.#config.tags ?? [])])];
|
|
122
119
|
test.description ||= SchemaRegistryIndex.get(this.#cls).getMethod(test.methodName).description;
|
|
123
120
|
}
|
|
124
121
|
}
|
|
@@ -134,4 +131,4 @@ export class SuiteRegistryAdapter implements RegistryAdapter<SuiteConfig> {
|
|
|
134
131
|
}
|
|
135
132
|
return test;
|
|
136
133
|
}
|
|
137
|
-
}
|
|
134
|
+
}
|