@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.
Files changed (52) hide show
  1. package/README.md +24 -22
  2. package/__index__.ts +9 -9
  3. package/package.json +8 -8
  4. package/src/assert/capture.ts +1 -2
  5. package/src/assert/check.ts +56 -33
  6. package/src/assert/types.ts +1 -1
  7. package/src/assert/util.ts +26 -81
  8. package/src/consumer/decorator.ts +2 -2
  9. package/src/consumer/enhancer.ts +3 -3
  10. package/src/consumer/registry-adapter.ts +3 -3
  11. package/src/consumer/registry-index.ts +6 -5
  12. package/src/consumer/types/cumulative.ts +11 -23
  13. package/src/consumer/types/delegating.ts +8 -2
  14. package/src/consumer/types/event.ts +2 -2
  15. package/src/consumer/types/exec.ts +3 -4
  16. package/src/consumer/types/json.ts +3 -4
  17. package/src/consumer/types/noop.ts +3 -3
  18. package/src/consumer/types/runnable.ts +4 -5
  19. package/src/consumer/types/summarizer.ts +5 -18
  20. package/src/consumer/types/tap-summary.ts +39 -30
  21. package/src/consumer/types/tap.ts +64 -56
  22. package/src/consumer/types/util.ts +31 -0
  23. package/src/consumer/types/xunit.ts +10 -8
  24. package/src/consumer/types.ts +6 -10
  25. package/src/decorator/suite.ts +3 -3
  26. package/src/decorator/test.ts +6 -4
  27. package/src/execute/barrier.ts +15 -9
  28. package/src/execute/console.ts +3 -4
  29. package/src/execute/executor.ts +101 -157
  30. package/src/execute/phase.ts +12 -22
  31. package/src/execute/run.ts +41 -40
  32. package/src/execute/watcher.ts +12 -19
  33. package/src/fixture.ts +6 -7
  34. package/src/model/common.ts +1 -1
  35. package/src/model/error.ts +2 -2
  36. package/src/model/event.ts +14 -9
  37. package/src/model/suite.ts +15 -13
  38. package/src/model/test.ts +11 -8
  39. package/src/model/util.ts +102 -9
  40. package/src/registry/registry-adapter.ts +10 -13
  41. package/src/registry/registry-index.ts +13 -11
  42. package/src/worker/child.ts +10 -9
  43. package/src/worker/standard.ts +11 -14
  44. package/src/worker/types.ts +3 -7
  45. package/support/bin/run.ts +1 -1
  46. package/support/cli.test.ts +20 -15
  47. package/support/cli.test_child.ts +8 -3
  48. package/support/cli.test_diff.ts +11 -7
  49. package/support/cli.test_digest.ts +10 -4
  50. package/support/cli.test_direct.ts +11 -7
  51. package/support/cli.test_watch.ts +6 -4
  52. package/support/transformer.assert.ts +40 -32
@@ -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 { TestExecutor } from './executor.ts';
17
- import { buildStandardTestManager } from '../worker/standard.ts';
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* getTestImports(globs?: string[]): AsyncIterable<string> {
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
- tags,
95
- rule => rule,
96
- (rule, core) => core.tags?.includes(rule) ?? false
97
- ) :
98
- ((): boolean => true);
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.getOrInsert(test.classId,
104
- { import: test.import, classId: test.classId, methodNames: [], runId: Util.uuid(), metadata }
105
- ).methodNames!.push(test.methodName);
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) { // Removed classes
143
+ if (!local) {
144
+ // Removed classes
144
145
  removeTest(clsId);
145
- } else if (local.sourceHash !== config.sourceHash) { // Class changed or added
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) { // Test is removed
153
+ if (!localMethod) {
154
+ // Test is removed
152
155
  removeTest(clsId, key);
153
156
  didRemove = true;
154
- } else if (localMethod.sourceHash !== config.methods[key]) { // Method changed or added
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) { // Re-run entire file, classes unchanged
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
- run => buildStandardTestManager(consumer, run),
229
- runs,
230
- {
231
- idleTimeoutMillis: TimeUtil.duration('10s', 'ms'),
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
+ }
@@ -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 { RunUtil } from './run.ts';
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
- buildStandardTestManager.bind(null, consumer),
49
- queue,
50
- {
51
- idleTimeoutMillis: TimeUtil.duration('2m', 'ms'),
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 ((fileType === 'ts' || fileType === 'js')) {
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
- '@#support/fixtures',
8
- ...modules.flat().map(module => `${module}#support/fixtures`),
9
- '@@#support/fixtures'
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
+ }
@@ -47,4 +47,4 @@ export interface TestCore extends SuiteCore {
47
47
  * For extended suites, this is where the test is declared
48
48
  */
49
49
  declarationImport?: string;
50
- }
50
+ }
@@ -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 {}
@@ -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 = { type: 'removeTest', import: string, classId?: string, methodName?: string, metadata?: Record<string, unknown> };
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 }>;
@@ -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
- * All counts for the suite summary
33
+ * Test Counts
38
34
  */
39
- export interface Counts {
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 Counts, SuiteCore {
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<string, {
9
- sourceHash: number;
10
- methods: Record<string, number>;
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
- * Duration for the test
111
+ * Self Execution Duration
109
112
  */
110
- duration: number;
113
+ selfDuration: number;
111
114
  /**
112
115
  * Total duration including before/after
113
116
  */
114
- durationTotal: number;
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 type { Counts } from './suite.ts';
2
- import type { TestStatus } from './test.ts';
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 countsToTestStatus(counts: Counts): TestStatus {
9
+ static computeTestStatus(summary: ResultsSummary): TestStatus {
6
10
  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';
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 { RuntimeError, asFull, type Class, describeFunction, Runtime, safeAssign } from '@travetto/runtime';
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
+ }