@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.
@@ -1,8 +1,12 @@
1
- import type { Class } from '@travetto/runtime';
1
+ import type { Any, Class } from '@travetto/runtime';
2
2
 
3
- import type { Assertion, TestConfig, TestResult, TestStatus } from './test.ts';
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
- * Before all handlers
28
- */
29
- beforeAll: Function[];
30
- /**
31
- * Before each handlers
31
+ * Phase handlers
32
32
  */
33
- beforeEach: Function[];
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
- return counts.failed ? 'failed' : (counts.passed ? 'passed' : counts.skipped ? 'skipped' : 'unknown');
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 { AppError, asFull, type Class, describeFunction, Runtime, safeAssign } from '@travetto/runtime';
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
- beforeAll: [],
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 AppError(`Test not registered: ${String(method)} on ${this.#cls.name}`);
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 { AppError, type Class, Runtime, describeFunction } from '@travetto/runtime';
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 AppError('Unable to find suite for class ID', { details: { classId: clsId } });
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 AppError('Unable to find suite for class ID', { details: { classId: clsId } });
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) }] : [];
@@ -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, CommunicationUtil.serializeToObject({ error }));
27
+ this.send(type, JSONUtil.cloneForTransmit(error));
29
28
  }
30
29
  }
31
30
 
@@ -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 ${JSON.stringify(run)}`);
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
- await channel.send(TestWorkerEvents.INIT); // Initialize
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 = CommunicationUtil.deserializeFromObject(event);
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 ${JSON.stringify(event)}@${consumer.constructor.name}`);
47
- await consumer.onRemoveEvent?.(parsed); // Forward remove events
45
+ log(`Received remove event ${JSONUtil.toUTF8(event)}@${consumer.constructor.name}`);
46
+ consumer.onRemoveEvent?.(parsed); // Forward remove events
48
47
  } else {
49
- await consumer.onEvent(parsed); // Forward standard events
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 } = await CommunicationUtil.deserializeFromObject(completedEvent);
62
+ const result: { error?: unknown } = JSONUtil.cloneFromTransmit(completedEvent);
64
63
 
65
64
  // Kill on complete
66
65
  await channel.destroy();
@@ -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: TestDiffSource = await JSONUtil.readFile(diff);
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 await RunUtil.getTestImports(globs)) {
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(JSON.stringify(all));
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('|') ?? '');
@@ -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
- }
@@ -1,11 +0,0 @@
1
- import { AppError } from '@travetto/runtime';
2
-
3
- /**
4
- * Represents an execution error
5
- */
6
- export class ExecutionError extends AppError { }
7
-
8
- /**
9
- * Timeout execution error
10
- */
11
- export class TimeoutError extends ExecutionError { }