@travetto/test 7.1.4 → 8.0.0-alpha.0
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 +1 -1
- package/__index__.ts +2 -1
- package/package.json +7 -7
- package/src/assert/capture.ts +1 -0
- package/src/assert/check.ts +26 -9
- package/src/assert/util.ts +75 -73
- package/src/consumer/types/cumulative.ts +9 -1
- package/src/consumer/types/event.ts +3 -2
- package/src/consumer/types/exec.ts +2 -2
- package/src/consumer/types/json.ts +3 -1
- package/src/consumer/types/summarizer.ts +2 -0
- package/src/consumer/types/tap-summary.ts +59 -56
- package/src/consumer/types/tap.ts +35 -17
- package/src/consumer/types/xunit.ts +12 -10
- package/src/decorator/suite.ts +12 -6
- package/src/execute/barrier.ts +3 -3
- package/src/execute/executor.ts +63 -61
- package/src/execute/phase.ts +26 -41
- package/src/execute/run.ts +6 -7
- package/src/execute/watcher.ts +1 -1
- package/src/model/common.ts +2 -2
- package/src/model/error.ts +11 -0
- package/src/model/suite.ts +10 -27
- package/src/model/test.ts +5 -1
- package/src/model/util.ts +7 -1
- package/src/registry/registry-adapter.ts +8 -19
- package/src/registry/registry-index.ts +3 -3
- package/src/worker/child.ts +2 -3
- package/src/worker/standard.ts +8 -9
- package/support/cli.test_diff.ts +3 -1
- package/support/cli.test_digest.ts +3 -3
- package/src/communication.ts +0 -66
- package/src/execute/error.ts +0 -11
package/src/model/suite.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import type { Class } from '@travetto/runtime';
|
|
1
|
+
import type { Any, Class } from '@travetto/runtime';
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type { TestConfig, TestResult, TestStatus } from './test.ts';
|
|
4
4
|
import type { Skip, SuiteCore } from './common.ts';
|
|
5
5
|
|
|
6
|
+
export type SuitePhase = 'beforeAll' | 'beforeEach' | 'afterAll' | 'afterEach';
|
|
7
|
+
|
|
8
|
+
export type SuitePhaseHandler<T extends object> = Partial<Record<SuitePhase, (instance: T) => Promise<unknown> | unknown>>;
|
|
9
|
+
|
|
6
10
|
/**
|
|
7
11
|
* Suite configuration
|
|
8
12
|
*/
|
|
@@ -24,21 +28,9 @@ export interface SuiteConfig extends SuiteCore {
|
|
|
24
28
|
*/
|
|
25
29
|
tests: Record<string, TestConfig>;
|
|
26
30
|
/**
|
|
27
|
-
*
|
|
28
|
-
*/
|
|
29
|
-
beforeAll: Function[];
|
|
30
|
-
/**
|
|
31
|
-
* Before each handlers
|
|
31
|
+
* Phase handlers
|
|
32
32
|
*/
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* After each handlers
|
|
36
|
-
*/
|
|
37
|
-
afterEach: Function[];
|
|
38
|
-
/**
|
|
39
|
-
* After all handlers
|
|
40
|
-
*/
|
|
41
|
-
afterAll: Function[];
|
|
33
|
+
phaseHandlers: SuitePhaseHandler<Any>[];
|
|
42
34
|
}
|
|
43
35
|
|
|
44
36
|
/**
|
|
@@ -48,6 +40,7 @@ export interface Counts {
|
|
|
48
40
|
passed: number;
|
|
49
41
|
skipped: number;
|
|
50
42
|
failed: number;
|
|
43
|
+
errored: number;
|
|
51
44
|
unknown: number;
|
|
52
45
|
total: number;
|
|
53
46
|
}
|
|
@@ -68,14 +61,4 @@ export interface SuiteResult extends Counts, SuiteCore {
|
|
|
68
61
|
* Overall status
|
|
69
62
|
*/
|
|
70
63
|
status: TestStatus;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* A total suite failure
|
|
75
|
-
*/
|
|
76
|
-
export interface SuiteFailure {
|
|
77
|
-
assert: Assertion;
|
|
78
|
-
testResult: TestResult;
|
|
79
|
-
test: TestConfig;
|
|
80
|
-
suite: SuiteConfig;
|
|
81
|
-
}
|
|
64
|
+
}
|
package/src/model/test.ts
CHANGED
|
@@ -10,7 +10,7 @@ export type TestDiffSource = Record<string, {
|
|
|
10
10
|
methods: Record<string, number>;
|
|
11
11
|
}>;
|
|
12
12
|
|
|
13
|
-
export type TestStatus = 'passed' | 'skipped' | 'failed' | 'unknown';
|
|
13
|
+
export type TestStatus = 'passed' | 'skipped' | 'errored' | 'failed' | 'unknown';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Specific configuration for a test
|
|
@@ -116,6 +116,10 @@ export interface TestResult extends TestCore {
|
|
|
116
116
|
* Logging output
|
|
117
117
|
*/
|
|
118
118
|
output: TestLog[];
|
|
119
|
+
/**
|
|
120
|
+
* Know where the suite started for this test
|
|
121
|
+
*/
|
|
122
|
+
suiteLineStart: number;
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
/**
|
package/src/model/util.ts
CHANGED
|
@@ -3,6 +3,12 @@ import type { TestStatus } from './test.ts';
|
|
|
3
3
|
|
|
4
4
|
export class TestModelUtil {
|
|
5
5
|
static countsToTestStatus(counts: Counts): TestStatus {
|
|
6
|
-
|
|
6
|
+
switch (true) {
|
|
7
|
+
case counts.errored > 0: return 'errored';
|
|
8
|
+
case counts.failed > 0: return 'failed';
|
|
9
|
+
case counts.skipped > 0: return 'skipped';
|
|
10
|
+
case counts.unknown > 0: return 'unknown';
|
|
11
|
+
default: return 'passed';
|
|
12
|
+
}
|
|
7
13
|
}
|
|
8
14
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RegistryAdapter } from '@travetto/registry';
|
|
2
|
-
import {
|
|
2
|
+
import { RuntimeError, asFull, type Class, describeFunction, Runtime, safeAssign } from '@travetto/runtime';
|
|
3
3
|
import { SchemaRegistryIndex } from '@travetto/schema';
|
|
4
4
|
|
|
5
5
|
import type { SuiteConfig } from '../model/suite.ts';
|
|
@@ -7,28 +7,19 @@ import type { TestConfig } from '../model/test.ts';
|
|
|
7
7
|
|
|
8
8
|
function combineClasses(baseConfig: SuiteConfig, ...subConfig: Partial<SuiteConfig>[]): SuiteConfig {
|
|
9
9
|
for (const config of subConfig) {
|
|
10
|
-
if (config.beforeAll) {
|
|
11
|
-
baseConfig.beforeAll = [...baseConfig.beforeAll, ...config.beforeAll];
|
|
12
|
-
}
|
|
13
|
-
if (config.beforeEach) {
|
|
14
|
-
baseConfig.beforeEach = [...baseConfig.beforeEach, ...config.beforeEach];
|
|
15
|
-
}
|
|
16
|
-
if (config.afterAll) {
|
|
17
|
-
baseConfig.afterAll = [...baseConfig.afterAll, ...config.afterAll];
|
|
18
|
-
}
|
|
19
|
-
if (config.afterEach) {
|
|
20
|
-
baseConfig.afterEach = [...baseConfig.afterEach, ...config.afterEach];
|
|
21
|
-
}
|
|
22
10
|
if (config.tags) {
|
|
23
11
|
baseConfig.tags = [...baseConfig.tags ?? [], ...config.tags];
|
|
24
12
|
}
|
|
25
13
|
baseConfig.skip = config.skip ?? baseConfig.skip;
|
|
26
14
|
|
|
15
|
+
if (config.phaseHandlers) {
|
|
16
|
+
baseConfig.phaseHandlers = [...(baseConfig.phaseHandlers ?? []), ...config.phaseHandlers];
|
|
17
|
+
}
|
|
18
|
+
|
|
27
19
|
if (config.tests) {
|
|
28
20
|
for (const [key, test] of Object.entries(config.tests ?? {})) {
|
|
29
21
|
baseConfig.tests[key] = {
|
|
30
22
|
...test,
|
|
31
|
-
sourceImport: Runtime.getImport(baseConfig.class),
|
|
32
23
|
class: baseConfig.class,
|
|
33
24
|
classId: baseConfig.classId,
|
|
34
25
|
import: baseConfig.import,
|
|
@@ -74,10 +65,7 @@ export class SuiteRegistryAdapter implements RegistryAdapter<SuiteConfig> {
|
|
|
74
65
|
lineEnd: lines?.[1],
|
|
75
66
|
sourceHash: hash,
|
|
76
67
|
tests: {},
|
|
77
|
-
|
|
78
|
-
beforeEach: [],
|
|
79
|
-
afterAll: [],
|
|
80
|
-
afterEach: []
|
|
68
|
+
phaseHandlers: [],
|
|
81
69
|
});
|
|
82
70
|
}
|
|
83
71
|
combineClasses(this.#config, ...data);
|
|
@@ -93,6 +81,7 @@ export class SuiteRegistryAdapter implements RegistryAdapter<SuiteConfig> {
|
|
|
93
81
|
class: this.#cls,
|
|
94
82
|
tags: [],
|
|
95
83
|
skip: false,
|
|
84
|
+
declarationImport: Runtime.getImport(this.#cls),
|
|
96
85
|
import: Runtime.getImport(this.#cls),
|
|
97
86
|
lineStart: lines?.[0],
|
|
98
87
|
lineEnd: lines?.[1],
|
|
@@ -126,7 +115,7 @@ export class SuiteRegistryAdapter implements RegistryAdapter<SuiteConfig> {
|
|
|
126
115
|
getMethod(method: string): TestConfig {
|
|
127
116
|
const test = this.#config.tests[method];
|
|
128
117
|
if (!test) {
|
|
129
|
-
throw new
|
|
118
|
+
throw new RuntimeError(`Test not registered: ${String(method)} on ${this.#cls.name}`);
|
|
130
119
|
}
|
|
131
120
|
return test;
|
|
132
121
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RuntimeError, type Class, Runtime, describeFunction } from '@travetto/runtime';
|
|
2
2
|
import { type RegistryIndex, RegistryIndexStore, Registry } from '@travetto/registry';
|
|
3
3
|
|
|
4
4
|
import type { SuiteConfig } from '../model/suite.ts';
|
|
@@ -83,7 +83,7 @@ export class SuiteRegistryIndex implements RegistryIndex {
|
|
|
83
83
|
if (methodNames.length) {
|
|
84
84
|
const cls = this.getValidClasses().find(type => type.Ⲑid === clsId);
|
|
85
85
|
if (!cls) {
|
|
86
|
-
throw new
|
|
86
|
+
throw new RuntimeError('Unable to find suite for class ID', { details: { classId: clsId } });
|
|
87
87
|
}
|
|
88
88
|
const suite = this.getConfig(cls);
|
|
89
89
|
const tests = sortedTests(suite).filter(config => methodNames.includes(config.methodName));
|
|
@@ -91,7 +91,7 @@ export class SuiteRegistryIndex implements RegistryIndex {
|
|
|
91
91
|
} else if (clsId) {
|
|
92
92
|
const cls = this.getValidClasses().find(type => type.Ⲑid === clsId)!;
|
|
93
93
|
if (!cls) {
|
|
94
|
-
throw new
|
|
94
|
+
throw new RuntimeError('Unable to find suite for class ID', { details: { classId: clsId } });
|
|
95
95
|
}
|
|
96
96
|
const suite = this.getConfig(cls);
|
|
97
97
|
return suite ? [{ suite, tests: sortedTests(suite) }] : [];
|
package/src/worker/child.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { createWriteStream } from 'node:fs';
|
|
2
2
|
|
|
3
|
-
import { ConsoleManager, Env, Runtime } from '@travetto/runtime';
|
|
3
|
+
import { JSONUtil, ConsoleManager, Env, Runtime } from '@travetto/runtime';
|
|
4
4
|
import { IpcChannel } from '@travetto/worker';
|
|
5
5
|
|
|
6
6
|
import { RunUtil } from '../execute/run.ts';
|
|
7
7
|
import { TestWorkerEvents } from './types.ts';
|
|
8
8
|
import type { TestRun } from '../model/test.ts';
|
|
9
|
-
import { CommunicationUtil } from '../communication.ts';
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* Child Worker for the Test Runner. Receives events as commands
|
|
@@ -25,7 +24,7 @@ export class TestChildWorker extends IpcChannel<TestRun> {
|
|
|
25
24
|
throw error;
|
|
26
25
|
}
|
|
27
26
|
// Mark as errored out
|
|
28
|
-
this.send(type,
|
|
27
|
+
this.send(type, JSONUtil.cloneForTransmit(error));
|
|
29
28
|
}
|
|
30
29
|
}
|
|
31
30
|
|
package/src/worker/standard.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { fork } from 'node:child_process';
|
|
2
2
|
|
|
3
|
-
import { Env, RuntimeIndex } from '@travetto/runtime';
|
|
3
|
+
import { JSONUtil, Env, RuntimeIndex } from '@travetto/runtime';
|
|
4
4
|
import { IpcChannel } from '@travetto/worker';
|
|
5
5
|
|
|
6
6
|
import { TestWorkerEvents, type TestLogEvent } from './types.ts';
|
|
7
7
|
import type { TestConsumerShape } from '../consumer/types.ts';
|
|
8
8
|
import type { TestEvent, TestRemoveEvent } from '../model/event.ts';
|
|
9
9
|
import type { TestDiffInput, TestRun } from '../model/test.ts';
|
|
10
|
-
import { CommunicationUtil } from '../communication.ts';
|
|
11
10
|
|
|
12
11
|
const log = (message: string | TestLogEvent): void => {
|
|
13
12
|
const event: TestLogEvent = typeof message === 'string' ? { type: 'log', message } : message;
|
|
@@ -18,7 +17,7 @@ const log = (message: string | TestLogEvent): void => {
|
|
|
18
17
|
* Produce a handler for the child worker
|
|
19
18
|
*/
|
|
20
19
|
export async function buildStandardTestManager(consumer: TestConsumerShape, run: TestRun | TestDiffInput): Promise<void> {
|
|
21
|
-
log(`Worker Input ${
|
|
20
|
+
log(`Worker Input ${JSONUtil.toUTF8(run)}`);
|
|
22
21
|
|
|
23
22
|
const channel = new IpcChannel<TestEvent & { error?: Error }>(
|
|
24
23
|
fork(
|
|
@@ -34,19 +33,19 @@ export async function buildStandardTestManager(consumer: TestConsumerShape, run:
|
|
|
34
33
|
);
|
|
35
34
|
|
|
36
35
|
await channel.once(TestWorkerEvents.READY); // Wait for the child to be ready
|
|
37
|
-
|
|
36
|
+
channel.send(TestWorkerEvents.INIT); // Initialize
|
|
38
37
|
await channel.once(TestWorkerEvents.INIT_COMPLETE); // Wait for complete
|
|
39
38
|
|
|
40
39
|
channel.on('*', async event => {
|
|
41
40
|
try {
|
|
42
|
-
const parsed: TestEvent | TestRemoveEvent | TestLogEvent =
|
|
41
|
+
const parsed: TestEvent | TestRemoveEvent | TestLogEvent = JSONUtil.cloneFromTransmit(event);
|
|
43
42
|
if (parsed.type === 'log') {
|
|
44
43
|
log(parsed);
|
|
45
44
|
} else if (parsed.type === 'removeTest') {
|
|
46
|
-
log(`Received remove event ${
|
|
47
|
-
|
|
45
|
+
log(`Received remove event ${JSONUtil.toUTF8(event)}@${consumer.constructor.name}`);
|
|
46
|
+
consumer.onRemoveEvent?.(parsed); // Forward remove events
|
|
48
47
|
} else {
|
|
49
|
-
|
|
48
|
+
consumer.onEvent(parsed); // Forward standard events
|
|
50
49
|
}
|
|
51
50
|
} catch {
|
|
52
51
|
// Do nothing
|
|
@@ -60,7 +59,7 @@ export async function buildStandardTestManager(consumer: TestConsumerShape, run:
|
|
|
60
59
|
|
|
61
60
|
// Wait for complete
|
|
62
61
|
const completedEvent = await complete;
|
|
63
|
-
const result: { error?: unknown } =
|
|
62
|
+
const result: { error?: unknown } = JSONUtil.cloneFromTransmit(completedEvent);
|
|
64
63
|
|
|
65
64
|
// Kill on complete
|
|
66
65
|
await channel.destroy();
|
package/support/cli.test_diff.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
|
|
1
3
|
import { Env, JSONUtil, RuntimeIndex } from '@travetto/runtime';
|
|
2
4
|
import { CliCommand, CliUtil } from '@travetto/cli';
|
|
3
5
|
import { IsPrivate } from '@travetto/schema';
|
|
@@ -29,7 +31,7 @@ export class TestDiffCommand {
|
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
async main(importOrFile: string, diff: string): Promise<void> {
|
|
32
|
-
const diffSource
|
|
34
|
+
const diffSource = await fs.readFile(diff).then(JSONUtil.fromBinaryArray<TestDiffSource>);
|
|
33
35
|
const importPath = RuntimeIndex.getFromImportOrSource(importOrFile)?.import!;
|
|
34
36
|
|
|
35
37
|
return runTests(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CliCommand } from '@travetto/cli';
|
|
2
|
-
import { Env, Runtime, describeFunction } from '@travetto/runtime';
|
|
2
|
+
import { JSONUtil, Env, Runtime, describeFunction } from '@travetto/runtime';
|
|
3
3
|
import { Registry } from '@travetto/registry';
|
|
4
4
|
import { IsPrivate } from '@travetto/schema';
|
|
5
5
|
|
|
@@ -19,7 +19,7 @@ export class TestDigestCommand {
|
|
|
19
19
|
|
|
20
20
|
async main(globs: string[] = ['**/*']) {
|
|
21
21
|
// Load all tests
|
|
22
|
-
for await (const imp of
|
|
22
|
+
for await (const imp of RunUtil.getTestImports(globs)) {
|
|
23
23
|
try {
|
|
24
24
|
await Runtime.importFrom(imp);
|
|
25
25
|
} catch (error) {
|
|
@@ -40,7 +40,7 @@ export class TestDigestCommand {
|
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
if (this.output === 'json') {
|
|
43
|
-
console.log(
|
|
43
|
+
console.log(JSONUtil.toUTF8(all));
|
|
44
44
|
} else {
|
|
45
45
|
for (const item of all) {
|
|
46
46
|
console.log(`${item.classId}#${item.methodName}`, item.tags?.join('|') ?? '');
|
package/src/communication.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { AppError, hasToJSON, JSONUtil } from '@travetto/runtime';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Tools for communication serialization/deserialization especially with errors
|
|
5
|
-
*/
|
|
6
|
-
export class CommunicationUtil {
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Serialize to JSON
|
|
10
|
-
*/
|
|
11
|
-
static serialize<T>(out: T): string {
|
|
12
|
-
return JSON.stringify(out, function (key, value) {
|
|
13
|
-
const objectValue = this[key];
|
|
14
|
-
if (objectValue && objectValue instanceof Error) {
|
|
15
|
-
return {
|
|
16
|
-
$: true,
|
|
17
|
-
...hasToJSON(objectValue) ? objectValue.toJSON() : objectValue,
|
|
18
|
-
name: objectValue.name,
|
|
19
|
-
message: objectValue.message,
|
|
20
|
-
stack: objectValue.stack,
|
|
21
|
-
};
|
|
22
|
-
} else if (typeof value === 'bigint') {
|
|
23
|
-
return `${value.toString()}$n`;
|
|
24
|
-
} else {
|
|
25
|
-
return value;
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Serialize to a standard object, instead of a string
|
|
32
|
-
*/
|
|
33
|
-
static serializeToObject<R = Record<string, unknown>, T = unknown>(out: T): R {
|
|
34
|
-
return JSONUtil.parseSafe(this.serialize(out));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Deserialize from JSON
|
|
39
|
-
*/
|
|
40
|
-
static deserialize<T = unknown>(input: string): T {
|
|
41
|
-
return JSONUtil.parseSafe(input, function (key, value) {
|
|
42
|
-
if (value && typeof value === 'object' && '$' in value) {
|
|
43
|
-
const error = AppError.fromJSON(value) ?? new Error();
|
|
44
|
-
if (!(error instanceof AppError)) {
|
|
45
|
-
const { $: _, ...rest } = value;
|
|
46
|
-
Object.assign(error, rest);
|
|
47
|
-
}
|
|
48
|
-
error.message = value.message;
|
|
49
|
-
error.stack = value.stack;
|
|
50
|
-
error.name = value.name;
|
|
51
|
-
return error;
|
|
52
|
-
} else if (typeof value === 'string' && /^\d+[$]n$/.test(value)) {
|
|
53
|
-
return BigInt(value.split('$')[0]);
|
|
54
|
-
} else {
|
|
55
|
-
return value;
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Deserialize from a standard object, instead of a string
|
|
62
|
-
*/
|
|
63
|
-
static deserializeFromObject<R = unknown, T = R>(input: T): R {
|
|
64
|
-
return this.deserialize<R>(JSON.stringify(input));
|
|
65
|
-
}
|
|
66
|
-
}
|
package/src/execute/error.ts
DELETED