@travetto/test 5.0.0-rc.0 → 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.
Files changed (43) hide show
  1. package/README.md +12 -13
  2. package/package.json +8 -8
  3. package/src/assert/capture.ts +5 -4
  4. package/src/assert/check.ts +24 -33
  5. package/src/assert/util.ts +32 -17
  6. package/src/consumer/registry.ts +2 -3
  7. package/src/consumer/{error.ts → serialize.ts} +13 -22
  8. package/src/consumer/types/cumulative.ts +15 -22
  9. package/src/consumer/types/delegating.ts +58 -0
  10. package/src/consumer/types/event.ts +2 -4
  11. package/src/consumer/types/execution.ts +2 -4
  12. package/src/consumer/types/runnable.ts +12 -41
  13. package/src/consumer/types/tap-streamed.ts +9 -6
  14. package/src/consumer/types/tap.ts +5 -5
  15. package/src/consumer/types/xunit.ts +4 -2
  16. package/src/decorator/suite.ts +5 -7
  17. package/src/decorator/test.ts +2 -1
  18. package/src/execute/console.ts +1 -1
  19. package/src/execute/executor.ts +84 -104
  20. package/src/execute/phase.ts +20 -30
  21. package/src/execute/promise.ts +4 -4
  22. package/src/execute/runner.ts +34 -24
  23. package/src/execute/types.ts +12 -10
  24. package/src/execute/util.ts +61 -34
  25. package/src/execute/watcher.ts +34 -36
  26. package/src/fixture.ts +7 -2
  27. package/src/model/common.ts +11 -7
  28. package/src/model/event.ts +9 -5
  29. package/src/model/suite.ts +14 -4
  30. package/src/model/test.ts +30 -4
  31. package/src/registry/suite.ts +42 -39
  32. package/src/trv.d.ts +3 -3
  33. package/src/worker/child.ts +11 -18
  34. package/src/worker/standard.ts +18 -21
  35. package/src/worker/types.ts +13 -10
  36. package/support/cli.test.ts +20 -6
  37. package/support/cli.test_child.ts +1 -1
  38. package/support/cli.test_digest.ts +43 -0
  39. package/support/cli.test_direct.ts +10 -3
  40. package/support/cli.test_watch.ts +1 -1
  41. package/support/transformer.assert.ts +12 -12
  42. package/support/cli.test_count.ts +0 -39
  43. package/support/transformer.annotate.ts +0 -103
@@ -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/base';
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
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
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
 
@@ -1,7 +1,6 @@
1
1
  import path from 'node:path';
2
2
 
3
- import { path as mp, RuntimeContext, RuntimeIndex } from '@travetto/manifest';
4
- import { TimeUtil } 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
- const files = (await RunnerUtil.getTestFiles(this.patterns)).map(f => f.sourceFile);
31
+ console.debug('Running', { globs });
36
32
 
37
- console.debug('Running', { files, patterns: this.patterns });
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
- const testCount = await RunnerUtil.getTestCount(this.#state.args);
40
- await consumer.onStart({ testCount });
37
+ await consumer.onStart({ testCount: tests.length });
41
38
  await WorkPool.run(
42
- buildStandardTestManager.bind(null, consumer),
43
- files,
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
- const mod = RuntimeIndex.getEntry(path.resolve(this.#state.args[0]))!;
58
- if (mod.module !== RuntimeContext.main.name) {
59
- RuntimeIndex.reinitForModule(mod.module);
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 consumer = await RunnableTestConsumer.get(this.#state.consumer ?? this.#state.format);
64
+ const filter = (run.methodNames?.length) ?
65
+ (cfg: TestConfig): boolean => run.methodNames!.includes(cfg.methodName) :
66
+ undefined;
63
67
 
64
- const [file, ...args] = this.#state.args;
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.execute(consumer, file, ...args);
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
- switch (this.#state.mode) {
76
- case 'single': return await this.runSingle();
77
- case 'standard': return await this.runFiles();
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
  }
@@ -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
- * Test mode
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
- showProgress?: boolean;
19
+ concurrency?: number;
23
20
  /**
24
- * Number of test suites to run concurrently, when mode is not single
21
+ * The tags to include or exclude from testing
25
22
  */
26
- concurrency: number;
23
+ tags?: string[];
27
24
  /**
28
- * Input arguments
25
+ * target
29
26
  */
30
- args: string[];
27
+ target: TestRun | {
28
+ /**
29
+ * Globs to run
30
+ */
31
+ globs?: string[];
32
+ };
31
33
  }
@@ -1,9 +1,10 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { createReadStream } from 'node:fs';
3
- import readline from 'node:readline';
3
+ import fs from 'node:fs/promises';
4
+ import readline from 'node:readline/promises';
4
5
 
5
- import { Env, ExecUtil, ShutdownManager, Util } from '@travetto/base';
6
- import { IndexedFile, RuntimeIndex } from '@travetto/manifest';
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
- return new Promise<boolean>((resolve) => {
24
- const input = createReadStream(file);
25
- const reader = readline.createInterface({ input })
26
- .on('line', line => {
27
- if (line.includes('@Suite')) {
28
- resolve(true);
29
- reader.close();
30
- }
31
- })
32
- .on('end', resolve.bind(null, false))
33
- .on('close', resolve.bind(null, false));
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 getTestFiles(globs?: RegExp[]): Promise<IndexedFile[]> {
41
- const files = RuntimeIndex.find({
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
- return (await Promise.all(validFiles))
52
- .filter(x => x.valid)
53
- .map(x => x.file);
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 patterns
58
- * @param patterns
67
+ * Get count of tests for a given set of globs
68
+ * @param globs
59
69
  * @returns
60
70
  */
61
- static async getTestCount(patterns: string[]): Promise<number> {
71
+ static async getTestDigest(globs: string[] = ['**/*.ts'], tags?: string[]): Promise<TestConfig[]> {
62
72
  const countRes = await ExecUtil.getResult(
63
- spawn('npx', ['trv', 'test:count', ...patterns], {
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
- return countRes.valid ? +countRes.stdout : 0;
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
- * Determine if we should invoke the debugger
95
+ * Get run events
76
96
  */
77
- static get tryDebugger(): boolean {
78
- return Env.TRV_TEST_BREAK_ENTRY.isTrue;
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
  }
@@ -1,25 +1,14 @@
1
1
  import { RootRegistry, MethodSource } from '@travetto/registry';
2
- import { WorkPool, WorkQueue } from '@travetto/worker';
3
- import { RuntimeIndex } from '@travetto/manifest';
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 { RunEvent } from '../worker/types';
9
+ import { TestRun } from '../model/test';
10
10
  import { RunnerUtil } from './util';
11
- import { TestEvent } from '../model/event';
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 consumer = new CumulativeSummaryConsumer(await TestConsumerRegistry.getInstance(format));
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 || RuntimeIndex.getFunctionMetadata(cls)?.abstract) {
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 key = `${conf.file}#${conf.class.name}#${conf.methodName}`;
56
- itr.add(key, true); // Shift to front
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
- file: RuntimeIndex.getFunctionMetadata(cls)?.source
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
- await RootRegistry.init();
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 (isRunEvent(ev)) {
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([ev.file, ev.class, ev.method].filter(x => !!x).join('#'), true);
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/base';
1
+ import { FileLoader, Runtime } from '@travetto/runtime';
2
2
 
3
3
  export class TestFixtures extends FileLoader {
4
4
  constructor(modules: string[] = []) {
5
- super(['@#test/fixtures', ...['@', ...modules.flat(), '@@'].map(x => `${x}#support/fixtures`)]);
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
  }
@@ -1,14 +1,10 @@
1
1
  /** Configuration of a skip */
2
- export type Skip = boolean | ((instance: unknown) => boolean | Promise<boolean>) | (() => 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
- * It's file
17
+ * The import location for the suite
22
18
  */
23
- file: string;
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
  }
@@ -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
- { type: 'assertion', phase: 'after', assertion: Assertion } |
18
- { type: 'test', phase: 'before', test: TestConfig } |
19
- { type: 'test', phase: 'after', test: TestResult } |
20
- { type: 'suite', phase: 'before', suite: SuiteConfig } |
21
- { type: 'suite', phase: 'after', suite: SuiteResult };
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 }>;
@@ -1,6 +1,6 @@
1
- import type { Class } from '@travetto/base';
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
- * File suite is in
63
+ * Import for the suite
64
64
  */
65
- file: string;
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/base';
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
- * File of assertion
65
+ * Import of assertion
66
66
  */
67
- file: string;
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
+ };