@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
package/src/execute/executor.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { Env, TimeUtil, Runtime, castTo, classConstruct } from '@travetto/runtime';
|
|
2
1
|
import { Registry } from '@travetto/registry';
|
|
2
|
+
import { castTo, classConstruct, Env, Runtime, TimeUtil } from '@travetto/runtime';
|
|
3
3
|
|
|
4
|
-
import type { TestConfig, TestResult, TestRun } from '../model/test.ts';
|
|
5
|
-
import type { SuiteConfig, SuiteResult } from '../model/suite.ts';
|
|
6
|
-
import type { TestConsumerShape } from '../consumer/types.ts';
|
|
7
|
-
import { AssertCheck } from '../assert/check.ts';
|
|
8
4
|
import { AssertCapture } from '../assert/capture.ts';
|
|
9
|
-
import {
|
|
10
|
-
import { TestPhaseManager } from './phase.ts';
|
|
5
|
+
import { AssertCheck } from '../assert/check.ts';
|
|
11
6
|
import { AssertUtil } from '../assert/util.ts';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
7
|
+
import type { TestConsumerShape } from '../consumer/types.ts';
|
|
8
|
+
import type { SuiteConfig, SuiteResult } from '../model/suite.ts';
|
|
9
|
+
import type { TestConfig, TestResult, TestRun } from '../model/test.ts';
|
|
14
10
|
import { TestModelUtil } from '../model/util.ts';
|
|
11
|
+
import { SuiteRegistryIndex } from '../registry/registry-index.ts';
|
|
12
|
+
import { Barrier } from './barrier.ts';
|
|
13
|
+
import { ConsoleCapture } from './console.ts';
|
|
14
|
+
import { TestPhaseManager } from './phase.ts';
|
|
15
15
|
|
|
16
16
|
const TEST_TIMEOUT = TimeUtil.duration(Env.TRV_TEST_TIMEOUT.value || 5000, 'ms');
|
|
17
17
|
|
|
@@ -19,7 +19,6 @@ const TEST_TIMEOUT = TimeUtil.duration(Env.TRV_TEST_TIMEOUT.value || 5000, 'ms')
|
|
|
19
19
|
* Support execution of the tests
|
|
20
20
|
*/
|
|
21
21
|
export class TestExecutor {
|
|
22
|
-
|
|
23
22
|
#consumer: TestConsumerShape;
|
|
24
23
|
|
|
25
24
|
constructor(consumer: TestConsumerShape) {
|
|
@@ -57,16 +56,15 @@ export class TestExecutor {
|
|
|
57
56
|
* Execute the test, capture output, assertions and promises
|
|
58
57
|
*/
|
|
59
58
|
async executeTest(instance: unknown, test: TestConfig, suite: SuiteConfig, override?: Partial<TestResult>): Promise<TestResult> {
|
|
60
|
-
|
|
61
59
|
const result = TestModelUtil.createTestResult(suite, test, override);
|
|
62
60
|
|
|
63
61
|
// Mark test start
|
|
64
62
|
this.#consumer.onEvent({ type: 'test', phase: 'before', test });
|
|
65
63
|
|
|
66
|
-
|
|
67
64
|
// Emit every assertion as it occurs
|
|
68
65
|
const getAssertions = AssertCapture.collector(test, item =>
|
|
69
|
-
this.#consumer.onEvent({ type: 'assertion', phase: 'after', assertion: item })
|
|
66
|
+
this.#consumer.onEvent({ type: 'assertion', phase: 'after', assertion: item })
|
|
67
|
+
);
|
|
70
68
|
|
|
71
69
|
const consoleCapture = new ConsoleCapture().start(); // Capture all output from transpiled code
|
|
72
70
|
|
|
@@ -75,7 +73,9 @@ export class TestExecutor {
|
|
|
75
73
|
if (result.error) {
|
|
76
74
|
result.assertions.push(AssertUtil.generateAssertion({ suite, test, error: result.error }));
|
|
77
75
|
}
|
|
78
|
-
for (const item of result.assertions ?? []) {
|
|
76
|
+
for (const item of result.assertions ?? []) {
|
|
77
|
+
AssertCapture.add(item);
|
|
78
|
+
}
|
|
79
79
|
} else {
|
|
80
80
|
// Run method and get result
|
|
81
81
|
const startTime = Date.now();
|
|
@@ -101,7 +101,6 @@ export class TestExecutor {
|
|
|
101
101
|
* Execute an entire suite
|
|
102
102
|
*/
|
|
103
103
|
async executeSuite(suite: SuiteConfig, tests: TestConfig[]): Promise<void> {
|
|
104
|
-
|
|
105
104
|
const instance = classConstruct(suite.class);
|
|
106
105
|
|
|
107
106
|
const shouldSkip = await this.#shouldSkip(suite, instance);
|
|
@@ -110,7 +109,8 @@ export class TestExecutor {
|
|
|
110
109
|
|
|
111
110
|
if (shouldSkip) {
|
|
112
111
|
this.#consumer.onEvent({
|
|
113
|
-
phase: 'after',
|
|
112
|
+
phase: 'after',
|
|
113
|
+
type: 'suite',
|
|
114
114
|
suite: {
|
|
115
115
|
...result,
|
|
116
116
|
status: 'skipped',
|
|
@@ -130,9 +130,7 @@ export class TestExecutor {
|
|
|
130
130
|
const testResultOverrides: Record<string, Partial<TestResult>> = {};
|
|
131
131
|
|
|
132
132
|
const validTestMethodNames = new Set(tests.map(t => t.methodName));
|
|
133
|
-
const testConfigs = Object.fromEntries(
|
|
134
|
-
Object.entries(suite.tests).filter(([key]) => validTestMethodNames.has(key))
|
|
135
|
-
);
|
|
133
|
+
const testConfigs = Object.fromEntries(Object.entries(suite.tests).filter(([key]) => validTestMethodNames.has(key)));
|
|
136
134
|
|
|
137
135
|
// Mark suite start
|
|
138
136
|
this.#consumer.onEvent({ phase: 'before', type: 'suite', suite: { ...suite, tests: testConfigs } });
|
|
@@ -162,7 +160,7 @@ export class TestExecutor {
|
|
|
162
160
|
|
|
163
161
|
try {
|
|
164
162
|
// Handle BeforeEach
|
|
165
|
-
testResultOverride.status || await manager.startPhase('each');
|
|
163
|
+
testResultOverride.status || (await manager.startPhase('each'));
|
|
166
164
|
} catch (someError) {
|
|
167
165
|
const testError = await manager.onError('each', someError);
|
|
168
166
|
testResultOverride.error = testError;
|
|
@@ -174,9 +172,11 @@ export class TestExecutor {
|
|
|
174
172
|
|
|
175
173
|
// Handle after each
|
|
176
174
|
try {
|
|
177
|
-
testResultOverride.status || await manager.endPhase('each');
|
|
175
|
+
testResultOverride.status || (await manager.endPhase('each'));
|
|
178
176
|
} catch (testError) {
|
|
179
|
-
if (!(testError instanceof Error)) {
|
|
177
|
+
if (!(testError instanceof Error)) {
|
|
178
|
+
throw testError;
|
|
179
|
+
}
|
|
180
180
|
console.error('Failed to properly shutdown test', testError.message);
|
|
181
181
|
}
|
|
182
182
|
|
|
@@ -189,7 +189,9 @@ export class TestExecutor {
|
|
|
189
189
|
// Handle after all
|
|
190
190
|
await manager.endPhase('all');
|
|
191
191
|
} catch (suiteError) {
|
|
192
|
-
if (!(suiteError instanceof Error)) {
|
|
192
|
+
if (!(suiteError instanceof Error)) {
|
|
193
|
+
throw suiteError;
|
|
194
|
+
}
|
|
193
195
|
console.error('Failed to properly shutdown test', suiteError.message);
|
|
194
196
|
}
|
|
195
197
|
|
|
@@ -210,7 +212,9 @@ export class TestExecutor {
|
|
|
210
212
|
try {
|
|
211
213
|
await Runtime.importFrom(run.import);
|
|
212
214
|
} catch (error) {
|
|
213
|
-
if (!(error instanceof Error)) {
|
|
215
|
+
if (!(error instanceof Error)) {
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
214
218
|
const suite = TestModelUtil.createImportErrorSuiteResult(run);
|
|
215
219
|
console.error(error);
|
|
216
220
|
this.#consumer.onEvent({ type: 'suite', phase: 'after', suite });
|
|
@@ -235,4 +239,4 @@ export class TestExecutor {
|
|
|
235
239
|
await this.executeSuite(suite, tests);
|
|
236
240
|
}
|
|
237
241
|
}
|
|
238
|
-
}
|
|
242
|
+
}
|
package/src/execute/phase.ts
CHANGED
|
@@ -60,7 +60,7 @@ export class TestPhaseManager {
|
|
|
60
60
|
*/
|
|
61
61
|
async onError(phase: 'all' | 'each', error: unknown): Promise<Error> {
|
|
62
62
|
if (!(error instanceof Error)) {
|
|
63
|
-
await this.endPhase(phase).catch(() => {
|
|
63
|
+
await this.endPhase(phase).catch(() => {});
|
|
64
64
|
throw error;
|
|
65
65
|
}
|
|
66
66
|
return error;
|
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, describeFunction } 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
|
|
11
|
-
import type { TestRemoveEvent } from '../model/event.ts';
|
|
10
|
+
import { TestConsumerRegistryIndex } from '../consumer/registry-index.ts';
|
|
12
11
|
import type { TestConsumerShape } from '../consumer/types.ts';
|
|
13
12
|
import { RunnableTestConsumer } from '../consumer/types/runnable.ts';
|
|
14
|
-
import type {
|
|
15
|
-
import {
|
|
16
|
-
import { TestExecutor } from './executor.ts';
|
|
17
|
-
import { buildStandardTestManager } from '../worker/standard.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
|
|
|
@@ -141,18 +140,22 @@ export class RunUtil {
|
|
|
141
140
|
// Looking at Diff
|
|
142
141
|
for (const [clsId, config] of Object.entries(diff)) {
|
|
143
142
|
const local = classes[clsId];
|
|
144
|
-
if (!local) {
|
|
143
|
+
if (!local) {
|
|
144
|
+
// Removed classes
|
|
145
145
|
removeTest(clsId);
|
|
146
|
-
} else if (local.sourceHash !== config.sourceHash) {
|
|
146
|
+
} else if (local.sourceHash !== config.sourceHash) {
|
|
147
|
+
// Class changed or added
|
|
147
148
|
// Methods to run, defaults to newly added
|
|
148
149
|
const methods: string[] = Object.keys(local.tests ?? {}).filter(key => !config.methods[key]);
|
|
149
150
|
let didRemove = false;
|
|
150
151
|
for (const key of Object.keys(config.methods)) {
|
|
151
152
|
const localMethod = local.tests?.[key];
|
|
152
|
-
if (!localMethod) {
|
|
153
|
+
if (!localMethod) {
|
|
154
|
+
// Test is removed
|
|
153
155
|
removeTest(clsId, key);
|
|
154
156
|
didRemove = true;
|
|
155
|
-
} else if (localMethod.sourceHash !== config.methods[key]) {
|
|
157
|
+
} else if (localMethod.sourceHash !== config.methods[key]) {
|
|
158
|
+
// Method changed or added
|
|
156
159
|
methods.push(key);
|
|
157
160
|
}
|
|
158
161
|
}
|
|
@@ -162,7 +165,8 @@ export class RunUtil {
|
|
|
162
165
|
}
|
|
163
166
|
}
|
|
164
167
|
|
|
165
|
-
if (runs.length === 0 && removes.length === 0) {
|
|
168
|
+
if (runs.length === 0 && removes.length === 0) {
|
|
169
|
+
// Re-run entire file, classes unchanged
|
|
166
170
|
addRun(undefined);
|
|
167
171
|
}
|
|
168
172
|
|
|
@@ -225,17 +229,13 @@ export class RunUtil {
|
|
|
225
229
|
if (runs.length === 1) {
|
|
226
230
|
await new TestExecutor(consumer).execute(runs[0], true);
|
|
227
231
|
} else {
|
|
228
|
-
await WorkPool.run(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
min: 1,
|
|
234
|
-
max: consumerConfig.concurrency
|
|
235
|
-
}
|
|
236
|
-
);
|
|
232
|
+
await WorkPool.run(run => buildStandardTestManager(consumer, run), runs, {
|
|
233
|
+
idleTimeoutMillis: TimeUtil.duration('10s', 'ms'),
|
|
234
|
+
min: 1,
|
|
235
|
+
max: consumerConfig.concurrency
|
|
236
|
+
});
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
return consumer.summarizeAsBoolean();
|
|
240
240
|
}
|
|
241
|
-
}
|
|
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
|
|
|
@@ -63,4 +63,4 @@ export interface SuiteResult extends ResultsSummary, SuiteCore {
|
|
|
63
63
|
* Overall status
|
|
64
64
|
*/
|
|
65
65
|
status: TestStatus;
|
|
66
|
-
}
|
|
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
|
|
|
@@ -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
|
@@ -8,11 +8,16 @@ import type { TestConfig, TestResult, TestRun, TestStatus } from './test.ts';
|
|
|
8
8
|
export class TestModelUtil {
|
|
9
9
|
static computeTestStatus(summary: ResultsSummary): TestStatus {
|
|
10
10
|
switch (true) {
|
|
11
|
-
case summary.errored > 0:
|
|
12
|
-
|
|
13
|
-
case summary.
|
|
14
|
-
|
|
15
|
-
|
|
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';
|
|
16
21
|
}
|
|
17
22
|
}
|
|
18
23
|
|
|
@@ -24,13 +29,12 @@ export class TestModelUtil {
|
|
|
24
29
|
for (const test of tests) {
|
|
25
30
|
summary[test.status] += 1;
|
|
26
31
|
summary.total += 1;
|
|
27
|
-
summary.selfDuration +=
|
|
28
|
-
summary.duration +=
|
|
32
|
+
summary.selfDuration += test.selfDuration ?? 0;
|
|
33
|
+
summary.duration += test.duration ?? 0;
|
|
29
34
|
}
|
|
30
35
|
return summary;
|
|
31
36
|
}
|
|
32
37
|
|
|
33
|
-
|
|
34
38
|
/**
|
|
35
39
|
* An empty suite result based on a suite config
|
|
36
40
|
*/
|
|
@@ -81,17 +85,23 @@ export class TestModelUtil {
|
|
|
81
85
|
const common = { classId, duration: 0, lineStart: 1, lineEnd: 1, import: run.import } as const;
|
|
82
86
|
return asFull<SuiteResult>({
|
|
83
87
|
...common,
|
|
84
|
-
status: 'errored',
|
|
88
|
+
status: 'errored',
|
|
89
|
+
errored: 1,
|
|
85
90
|
tests: {
|
|
86
|
-
|
|
91
|
+
import: asFull<TestResult>({
|
|
87
92
|
...common,
|
|
88
93
|
status: 'errored',
|
|
89
|
-
assertions: [
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
assertions: [
|
|
95
|
+
{
|
|
96
|
+
...common,
|
|
97
|
+
line: common.lineStart,
|
|
98
|
+
methodName: 'import',
|
|
99
|
+
operator: 'import',
|
|
100
|
+
text: `Failed to import ${run.import}`
|
|
101
|
+
}
|
|
102
|
+
]
|
|
93
103
|
})
|
|
94
104
|
}
|
|
95
105
|
});
|
|
96
106
|
}
|
|
97
|
-
}
|
|
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
|
+
}
|