@travetto/test 6.0.2 → 7.0.0-rc.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 CHANGED
@@ -21,7 +21,7 @@ This module provides unit testing functionality that integrates with the framewo
21
21
  **Note**: All tests should be under the `**/*` folders. The pattern for tests is defined as as a standard glob using [Node](https://nodejs.org)'s built in globbing support.
22
22
 
23
23
  ## Definition
24
- A test suite is a collection of individual tests. All test suites are classes with the [@Suite](https://github.com/travetto/travetto/tree/main/module/test/src/decorator/suite.ts#L13) decorator. Tests are defined as methods on the suite class, using the [@Test](https://github.com/travetto/travetto/tree/main/module/test/src/decorator/test.ts#L20) decorator. All tests intrinsically support `async`/`await`.
24
+ A test suite is a collection of individual tests. All test suites are classes with the [@Suite](https://github.com/travetto/travetto/tree/main/module/test/src/decorator/suite.ts#L14) decorator. Tests are defined as methods on the suite class, using the [@Test](https://github.com/travetto/travetto/tree/main/module/test/src/decorator/test.ts#L23) decorator. All tests intrinsically support `async`/`await`.
25
25
 
26
26
  A simple example would be:
27
27
 
@@ -79,9 +79,11 @@ would translate to:
79
79
  "use strict";
80
80
  Object.defineProperty(exports, "__esModule", { value: true });
81
81
  const tslib_1 = require("tslib");
82
+ const Δmethod = tslib_1.__importStar(require("@travetto/schema/src/decorator/method.js"));
82
83
  const Δdebug = tslib_1.__importStar(require("@travetto/runtime/src/debug.js"));
83
84
  const Δcheck = tslib_1.__importStar(require("@travetto/test/src/assert/check.js"));
84
85
  const Δfunction = tslib_1.__importStar(require("@travetto/runtime/src/function.js"));
86
+ const Δschema = tslib_1.__importStar(require("@travetto/schema/src/decorator/schema.js"));
85
87
  var mod_1 = ["@travetto/test", "doc/assert-example.ts"];
86
88
  const node_assert_1 = tslib_1.__importDefault(require("node:assert"));
87
89
  const test_1 = require("@travetto/test");
@@ -94,10 +96,12 @@ let SimpleTest = class SimpleTest {
94
96
  }
95
97
  };
96
98
  tslib_1.__decorate([
97
- (0, test_1.Test)()
99
+ (0, test_1.Test)(),
100
+ Δmethod.Method({ returnType: {} })
98
101
  ], SimpleTest.prototype, "test", null);
99
102
  SimpleTest = tslib_1.__decorate([
100
- (0, test_1.Suite)()
103
+ (0, test_1.Suite)(),
104
+ Δschema.Schema()
101
105
  ], SimpleTest);
102
106
  ```
103
107
 
package/__index__.ts CHANGED
@@ -4,8 +4,10 @@ export * from './src/decorator/test.ts';
4
4
  export * from './src/model/suite.ts';
5
5
  export * from './src/model/test.ts';
6
6
  export * from './src/model/event.ts';
7
- export * from './src/registry/suite.ts';
7
+ export * from './src/registry/registry-index.ts';
8
+ export * from './src/registry/registry-adapter.ts';
8
9
  export * from './src/fixture.ts';
9
10
  export * from './src/consumer/types.ts';
11
+ export * from './src/consumer/registry-index.ts';
10
12
  export * from './src/execute/error.ts';
11
13
  export { TestWatchEvent } from './src/worker/types.ts';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/test",
3
- "version": "6.0.2",
3
+ "version": "7.0.0-rc.1",
4
4
  "description": "Declarative test framework",
5
5
  "keywords": [
6
6
  "unit-testing",
@@ -27,15 +27,15 @@
27
27
  "directory": "module/test"
28
28
  },
29
29
  "dependencies": {
30
- "@travetto/registry": "^6.0.1",
31
- "@travetto/runtime": "^6.0.1",
32
- "@travetto/terminal": "^6.0.1",
33
- "@travetto/worker": "^6.0.1",
30
+ "@travetto/registry": "^7.0.0-rc.1",
31
+ "@travetto/runtime": "^7.0.0-rc.1",
32
+ "@travetto/terminal": "^7.0.0-rc.1",
33
+ "@travetto/worker": "^7.0.0-rc.1",
34
34
  "yaml": "^2.8.1"
35
35
  },
36
36
  "peerDependencies": {
37
- "@travetto/cli": "^6.0.1",
38
- "@travetto/transformer": "^6.0.1"
37
+ "@travetto/cli": "^7.0.0-rc.1",
38
+ "@travetto/transformer": "^7.0.0-rc.1"
39
39
  },
40
40
  "peerDependenciesMeta": {
41
41
  "@travetto/transformer": {
@@ -0,0 +1,13 @@
1
+ import { Class } from '@travetto/runtime';
2
+
3
+ import { TestConsumerShape } from './types';
4
+ import { TestConsumerRegistryIndex } from './registry-index';
5
+
6
+ /**
7
+ * Registers a class a valid test consumer
8
+ */
9
+ export function TestConsumer(): (cls: Class<TestConsumerShape>) => void {
10
+ return function (cls: Class<TestConsumerShape>): void {
11
+ TestConsumerRegistryIndex.getForRegister(cls).register();
12
+ };
13
+ }
@@ -0,0 +1,43 @@
1
+ import path from 'node:path';
2
+
3
+ import { classConstruct, describeFunction, type Class } from '@travetto/runtime';
4
+ import { RegistryAdapter } from '@travetto/registry';
5
+
6
+
7
+ import type { TestConsumerShape, TestConsumerConfig } from './types.ts';
8
+
9
+ /**
10
+ * Test Results Handler Registry
11
+ */
12
+ export class TestConsumerRegistryAdapter implements RegistryAdapter<TestConsumerConfig> {
13
+ config: TestConsumerConfig;
14
+ #cls: Class<TestConsumerShape>;
15
+
16
+ constructor(cls: Class<TestConsumerShape>) {
17
+ this.#cls = cls;
18
+ }
19
+
20
+ register(...data: Partial<TestConsumerConfig>[]): TestConsumerConfig {
21
+ const desc = describeFunction(this.#cls);
22
+ this.config ??= {
23
+ cls: this.#cls,
24
+ type: desc.module?.includes('@travetto') ? path.basename(desc.modulePath) : desc.import
25
+ };
26
+ Object.assign(this.config, ...data);
27
+ return this.config;
28
+ }
29
+
30
+ finalize(): void {
31
+ // Do nothing for now
32
+ }
33
+
34
+ get(): TestConsumerConfig {
35
+ return this.config;
36
+ }
37
+
38
+ async instance(options?: Record<string, unknown>): Promise<TestConsumerShape> {
39
+ const inst = classConstruct(this.#cls);
40
+ await inst.setOptions?.(options);
41
+ return inst;
42
+ }
43
+ }
@@ -0,0 +1,82 @@
1
+ import { RuntimeIndex, type Class } from '@travetto/runtime';
2
+ import { Registry, RegistryIndex, RegistryIndexStore } from '@travetto/registry';
3
+
4
+ import type { TestConsumerShape } from './types.ts';
5
+ import type { RunState } from '../execute/types.ts';
6
+ import { TestConsumerRegistryAdapter } from './registry-adapter.ts';
7
+
8
+ /**
9
+ * Test Results Handler Registry
10
+ */
11
+ export class TestConsumerRegistryIndex implements RegistryIndex {
12
+
13
+ static #instance = Registry.registerIndex(this);
14
+
15
+ static getForRegister(cls: Class): TestConsumerRegistryAdapter {
16
+ return this.#instance.store.getForRegister(cls);
17
+ }
18
+
19
+ /**
20
+ * Get all registered types
21
+ */
22
+ static getTypes(): Promise<string[]> {
23
+ return this.#instance.getTypes();
24
+ }
25
+
26
+ /**
27
+ * Get a consumer instance that supports summarization
28
+ * @param consumer The consumer identifier or the actual consumer
29
+ */
30
+ static getInstance(state: Pick<RunState, 'consumer' | 'consumerOptions'>): Promise<TestConsumerShape> {
31
+ return this.#instance.getInstance(state);
32
+ }
33
+
34
+ #initialized: Promise<void>;
35
+ store = new RegistryIndexStore(TestConsumerRegistryAdapter);
36
+
37
+ /**
38
+ * Manual initialization when running outside of the bootstrap process
39
+ */
40
+ async #init(): Promise<void> {
41
+ const allFiles = RuntimeIndex.find({
42
+ module: m => m.name === '@travetto/test',
43
+ file: f => f.relativeFile.startsWith('src/consumer/types/')
44
+ });
45
+ for (const f of allFiles) {
46
+ await import(f.outputFile);
47
+ }
48
+ for (const cls of this.store.getClasses()) {
49
+ this.store.finalize(cls);
50
+ }
51
+ }
52
+
53
+ process(): void { }
54
+
55
+ /**
56
+ * Get types
57
+ */
58
+ async getTypes(): Promise<string[]> {
59
+ await (this.#initialized ??= this.#init());
60
+ const out: string[] = [];
61
+ for (const cls of this.store.getClasses()) {
62
+ const adapter = this.store.get(cls);
63
+ out.push(adapter.get().type);
64
+ }
65
+ return out;
66
+ }
67
+
68
+ /**
69
+ * Get a consumer instance that supports summarization
70
+ * @param consumer The consumer identifier or the actual consumer
71
+ */
72
+ async getInstance(state: Pick<RunState, 'consumer' | 'consumerOptions'>): Promise<TestConsumerShape> {
73
+ await (this.#initialized ??= this.#init());
74
+ for (const cls of this.store.getClasses()) {
75
+ const adapter = this.store.get(cls);
76
+ if (adapter.get().type === state.consumer) {
77
+ return adapter.instance(state.consumerOptions);
78
+ }
79
+ }
80
+ throw new Error(`No test consumer registered for type ${state.consumer}`);
81
+ }
82
+ }
@@ -6,8 +6,8 @@ import type { TestConsumerShape } from '../types.ts';
6
6
  import type { TestEvent } from '../../model/event.ts';
7
7
  import type { TestResult } from '../../model/test.ts';
8
8
  import type { SuiteResult } from '../../model/suite.ts';
9
- import { SuiteRegistry } from '../../registry/suite.ts';
10
9
  import { DelegatingConsumer } from './delegating.ts';
10
+ import { SuiteRegistryIndex } from '../../registry/registry-index.ts';
11
11
 
12
12
  /**
13
13
  * Cumulative Summary consumer
@@ -30,7 +30,7 @@ export class CumulativeSummaryConsumer extends DelegatingConsumer {
30
30
  // Was only loading to verify existence (TODO: double-check)
31
31
  if (existsSync(RuntimeIndex.getFromImport(test.import)!.sourceFile)) {
32
32
  (this.#state[test.classId] ??= {})[test.methodName] = test.status;
33
- const SuiteCls = SuiteRegistry.getClasses().find(x => x.Ⲑid === test.classId);
33
+ const SuiteCls = SuiteRegistryIndex.getClasses().find(x => x.Ⲑid === test.classId);
34
34
  return SuiteCls ? this.computeTotal(SuiteCls) : this.removeClass(test.classId);
35
35
  } else {
36
36
  return this.removeClass(test.classId);
@@ -51,8 +51,8 @@ export class CumulativeSummaryConsumer extends DelegatingConsumer {
51
51
  * Compute totals
52
52
  */
53
53
  computeTotal(cls: Class): SuiteResult {
54
- const suite = SuiteRegistry.get(cls);
55
- const total = suite.tests.reduce((acc, x) => {
54
+ const suite = SuiteRegistryIndex.getConfig(cls);
55
+ const total = Object.values(suite.tests).reduce((acc, x) => {
56
56
  const status = this.#state[x.classId][x.methodName] ?? 'unknown';
57
57
  acc[status] += 1;
58
58
  return acc;
@@ -4,7 +4,7 @@ import { Util } from '@travetto/runtime';
4
4
 
5
5
  import type { TestEvent } from '../../model/event.ts';
6
6
  import type { TestConsumerShape } from '../types.ts';
7
- import { TestConsumer } from '../registry.ts';
7
+ import { TestConsumer } from '../decorator.ts';
8
8
 
9
9
  /**
10
10
  * Streams all test events a JSON payload, in an nd-json format
@@ -3,7 +3,7 @@ import { Util } from '@travetto/runtime';
3
3
 
4
4
  import type { TestEvent } from '../../model/event.ts';
5
5
  import type { TestConsumerShape } from '../types.ts';
6
- import { TestConsumer } from '../registry.ts';
6
+ import { TestConsumer } from '../decorator.ts';
7
7
 
8
8
  /**
9
9
  * Triggers each event as an IPC command to a parent process
@@ -1,7 +1,7 @@
1
1
  import type { Writable } from 'node:stream';
2
2
 
3
3
  import type { SuitesSummary } from '../types.ts';
4
- import { TestConsumer } from '../registry.ts';
4
+ import { TestConsumer } from '../decorator.ts';
5
5
 
6
6
  /**
7
7
  * Returns the entire result set as a single JSON document
@@ -1,5 +1,5 @@
1
1
  import type { TestConsumerShape } from '../types.ts';
2
- import { TestConsumer } from '../registry.ts';
2
+ import { TestConsumer } from '../decorator.ts';
3
3
 
4
4
  /**
5
5
  * Does nothing consumer
@@ -5,7 +5,7 @@ import type { TestEvent } from '../../model/event.ts';
5
5
  import type { TestResult } from '../../model/test.ts';
6
6
 
7
7
  import type { SuitesSummary, TestConsumerShape, TestRunState } from '../types.ts';
8
- import { TestConsumer } from '../registry.ts';
8
+ import { TestConsumer } from '../decorator.ts';
9
9
 
10
10
  import { TapEmitter } from './tap.ts';
11
11
  import { CONSOLE_ENHANCER, TestResultsEnhancer } from '../enhancer.ts';
@@ -6,7 +6,7 @@ import { TimeUtil, RuntimeIndex, hasToJSON } from '@travetto/runtime';
6
6
 
7
7
  import type { TestEvent } from '../../model/event.ts';
8
8
  import type { SuitesSummary, TestConsumerShape } from '../types.ts';
9
- import { TestConsumer } from '../registry.ts';
9
+ import { TestConsumer } from '../decorator.ts';
10
10
  import { TestResultsEnhancer, CONSOLE_ENHANCER } from '../enhancer.ts';
11
11
 
12
12
  /**
@@ -91,7 +91,7 @@ export class TapEmitter implements TestConsumerShape {
91
91
  let subCount = 0;
92
92
  for (const asrt of test.assertions) {
93
93
  const text = asrt.message ? `${asrt.text} (${this.#enhancer.failure(asrt.message)})` : asrt.text;
94
- const pth = `./${path.relative(process.cwd(), RuntimeIndex.getFromImport(asrt.import)!.sourceFile)}`;
94
+ const pth = asrt.import ? `./${path.relative(process.cwd(), RuntimeIndex.getFromImport(asrt.import)!.sourceFile)}` : '<unknown>';
95
95
  let subMessage = [
96
96
  this.#enhancer.assertNumber(++subCount),
97
97
  '-',
@@ -6,7 +6,7 @@ import { RuntimeIndex } from '@travetto/runtime';
6
6
 
7
7
  import type { TestEvent } from '../../model/event.ts';
8
8
  import type { SuitesSummary, TestConsumerShape } from '../types.ts';
9
- import { TestConsumer } from '../registry.ts';
9
+ import { TestConsumer } from '../decorator.ts';
10
10
 
11
11
  /**
12
12
  * Xunit consumer, compatible with JUnit formatters
@@ -1,3 +1,5 @@
1
+ import { Class } from '@travetto/runtime';
2
+
1
3
  import { TestEvent } from '../model/event.ts';
2
4
  import { Counts, SuiteResult } from '../model/suite.ts';
3
5
 
@@ -23,6 +25,17 @@ export type TestRunState = {
23
25
  testCount?: number;
24
26
  };
25
27
 
28
+ /**
29
+ * Configuration for a test consumer
30
+ */
31
+ export interface TestConsumerConfig {
32
+ cls: Class<TestConsumerShape>;
33
+ /**
34
+ * Consumer type key
35
+ */
36
+ type: string;
37
+ }
38
+
26
39
  /**
27
40
  * A test consumer shape
28
41
  */
@@ -1,60 +1,74 @@
1
- import { castTo, Class, ClassInstance, describeFunction } from '@travetto/runtime';
1
+ import { castTo, Class, ClassInstance, describeFunction, getClass } from '@travetto/runtime';
2
2
 
3
- import { SuiteRegistry } from '../registry/suite.ts';
4
3
  import { SuiteConfig } from '../model/suite.ts';
4
+ import { SuiteRegistryIndex } from '../registry/registry-index.ts';
5
5
 
6
6
  export type SuitePhase = 'beforeAll' | 'beforeEach' | 'afterAll' | 'afterEach';
7
7
 
8
8
  /**
9
9
  * Register a class to be defined as a test suite, and a candidate for testing
10
10
  * @param description The Suite description
11
- * @augments `@travetto/test:Suite`
11
+ * @augments `@travetto/schema:Schema`
12
+ * @kind decorator
12
13
  */
13
14
  export function Suite(): ClassDecorator;
14
15
  export function Suite(...rest: Partial<SuiteConfig>[]): ClassDecorator;
15
16
  export function Suite(description: string, ...rest: Partial<SuiteConfig>[]): ClassDecorator;
16
17
  export function Suite(description?: string | Partial<SuiteConfig>, ...rest: Partial<SuiteConfig>[]): ClassDecorator {
17
- const extra: Partial<SuiteConfig> = {};
18
- const descriptionString = description && typeof description !== 'string' ?
19
- Object.assign(extra, description).description :
20
- description;
21
-
22
- for (const r of rest) {
23
- Object.assign(extra, r);
24
- }
25
-
26
- const dec = (target: Class): typeof target => {
27
- const cfg = { description: descriptionString, ...extra };
28
- if (describeFunction(target).abstract) {
29
- cfg.skip = true;
30
- }
31
- SuiteRegistry.register(target, cfg);
32
- return target;
18
+ const dec = (cls: Class): typeof cls => {
19
+ const isAbstract = describeFunction(cls).abstract;
20
+ SuiteRegistryIndex.getForRegister(cls).register(
21
+ ...(typeof description !== 'string' && description ? [description] : []),
22
+ ...rest,
23
+ ...isAbstract ? [{ skip: true }] : [],
24
+ ...(typeof description === 'string' ? [{ description }] : []),
25
+ );
26
+ return cls;
33
27
  };
34
28
 
35
29
  return castTo(dec);
36
30
  }
37
31
 
38
- function listener(phase: SuitePhase) {
39
- return (inst: ClassInstance, prop: string, descriptor: PropertyDescriptor): PropertyDescriptor => {
40
- SuiteRegistry.registerPendingListener(inst.constructor, descriptor.value, phase);
32
+ /**
33
+ * Registers function to run before any tests are run
34
+ * @kind decorator
35
+ */
36
+ export function BeforeAll() {
37
+ return (instance: ClassInstance, property: string, descriptor: PropertyDescriptor): PropertyDescriptor => {
38
+ SuiteRegistryIndex.getForRegister(getClass(instance)).register({ beforeAll: [descriptor.value] });
41
39
  return descriptor;
42
40
  };
43
41
  }
44
42
 
45
- /**
46
- * Registers function to run before any tests are run
47
- */
48
- export const BeforeAll = listener.bind(null, 'beforeAll');
49
43
  /**
50
44
  * Registers function to run before each test is run
45
+ * @kind decorator
51
46
  */
52
- export const BeforeEach = listener.bind(null, 'beforeEach');
47
+ export function BeforeEach() {
48
+ return (instance: ClassInstance, property: string, descriptor: PropertyDescriptor): PropertyDescriptor => {
49
+ SuiteRegistryIndex.getForRegister(getClass(instance)).register({ beforeEach: [descriptor.value] });
50
+ return descriptor;
51
+ };
52
+ }
53
+
53
54
  /**
54
55
  * Registers function to run after all tests are run
56
+ * @kind decorator
55
57
  */
56
- export const AfterAll = listener.bind(null, 'afterAll');
58
+ export function AfterAll() {
59
+ return (instance: ClassInstance, property: string, descriptor: PropertyDescriptor): PropertyDescriptor => {
60
+ SuiteRegistryIndex.getForRegister(getClass(instance)).register({ afterAll: [descriptor.value] });
61
+ return descriptor;
62
+ };
63
+ }
64
+
57
65
  /**
58
66
  * Registers function to run after each test is run
67
+ * @kind decorator
59
68
  */
60
- export const AfterEach = listener.bind(null, 'afterEach');
69
+ export function AfterEach() {
70
+ return (instance: ClassInstance, property: string, descriptor: PropertyDescriptor): PropertyDescriptor => {
71
+ SuiteRegistryIndex.getForRegister(getClass(instance)).register({ afterEach: [descriptor.value] });
72
+ return descriptor;
73
+ };
74
+ }
@@ -1,36 +1,35 @@
1
- import { ClassInstance } from '@travetto/runtime';
1
+ import { ClassInstance, getClass } from '@travetto/runtime';
2
2
 
3
- import { SuiteRegistry } from '../registry/suite.ts';
4
3
  import { TestConfig, ThrowableError } from '../model/test.ts';
4
+ import { SuiteRegistryIndex } from '../registry/registry-index.ts';
5
5
 
6
6
  /**
7
7
  * The `@AssertCheck` indicates that a function's assert calls should be transformed
8
+ * @augments `@travetto/test:AssertCheck`
9
+ * @kind decorator
8
10
  */
9
11
  export function AssertCheck(): MethodDecorator {
10
- return (inst: ClassInstance, prop: string | symbol, descriptor: PropertyDescriptor) => descriptor;
12
+ return (instance: ClassInstance, property: string | symbol, descriptor: PropertyDescriptor) => descriptor;
11
13
  }
12
14
 
13
15
  /**
14
16
  * The `@Test` decorator register a test to be run as part of the enclosing suite.
15
17
  * @param description The test description
16
- * @augments `@travetto/test:Test`
18
+ * @augments `@travetto/schema:Method`
17
19
  * @augments `@travetto/test:AssertCheck`
18
20
  * @augments `@travetto/runtime:DebugBreak`
21
+ * @kind decorator
19
22
  */
20
23
  export function Test(): MethodDecorator;
21
24
  export function Test(...rest: Partial<TestConfig>[]): MethodDecorator;
22
25
  export function Test(description: string, ...rest: Partial<TestConfig>[]): MethodDecorator;
23
26
  export function Test(description?: string | Partial<TestConfig>, ...rest: Partial<TestConfig>[]): MethodDecorator {
24
- const extra: Partial<TestConfig> = {};
25
- const descriptionString = (description && typeof description !== 'string') ?
26
- Object.assign(extra, description).description :
27
- description;
28
-
29
- for (const r of [...rest, { description: descriptionString }]) {
30
- Object.assign(extra, r);
31
- }
32
- return (inst: ClassInstance, prop: string | symbol, descriptor: PropertyDescriptor) => {
33
- SuiteRegistry.registerField(inst.constructor, descriptor.value, extra);
27
+ return (instance: ClassInstance, property: string | symbol, descriptor: PropertyDescriptor) => {
28
+ SuiteRegistryIndex.getForRegister(getClass(instance)).registerTest(property, descriptor.value,
29
+ ...(typeof description !== 'string' && description) ? [description] : [],
30
+ ...rest,
31
+ ...(typeof description === 'string') ? [{ description }] : []
32
+ );
34
33
  return descriptor;
35
34
  };
36
35
  }
@@ -38,10 +37,11 @@ export function Test(description?: string | Partial<TestConfig>, ...rest: Partia
38
37
  /**
39
38
  * Marks a method as should throw to indicate a lack of throwing is a problem
40
39
  * @param state The parameters to use for checking if the response is valid
40
+ * @kind decorator
41
41
  */
42
42
  export function ShouldThrow(state: ThrowableError): MethodDecorator {
43
- return (inst: ClassInstance, prop: string | symbol, descriptor: PropertyDescriptor) => {
44
- SuiteRegistry.registerField(inst.constructor, descriptor.value, { shouldThrow: state });
43
+ return (instance: ClassInstance, property: string | symbol, descriptor: PropertyDescriptor) => {
44
+ SuiteRegistryIndex.getForRegister(getClass(instance)).registerTest(property, descriptor.value, { shouldThrow: state });
45
45
  return descriptor;
46
46
  };
47
47
  }
@@ -49,10 +49,11 @@ export function ShouldThrow(state: ThrowableError): MethodDecorator {
49
49
  /**
50
50
  * Sets the full timeout window for a given test
51
51
  * @param ms Max time to wait
52
+ * @kind decorator
52
53
  */
53
54
  export function Timeout(ms: number): MethodDecorator {
54
- return (inst: ClassInstance, prop: string | symbol, descriptor: PropertyDescriptor) => {
55
- SuiteRegistry.registerField(inst.constructor, descriptor.value, { timeout: ms });
55
+ return (instance: ClassInstance, property: string | symbol, descriptor: PropertyDescriptor) => {
56
+ SuiteRegistryIndex.getForRegister(getClass(instance)).registerTest(property, descriptor.value, { timeout: ms });
56
57
  return descriptor;
57
58
  };
58
59
  }
@@ -1,8 +1,8 @@
1
1
  import { AssertionError } from 'node:assert';
2
2
 
3
- import { Env, TimeUtil, Runtime, castTo } from '@travetto/runtime';
3
+ import { Env, TimeUtil, Runtime, castTo, classConstruct } from '@travetto/runtime';
4
+ import { Registry } from '@travetto/registry';
4
5
 
5
- import { SuiteRegistry } from '../registry/suite.ts';
6
6
  import { TestConfig, TestResult, TestRun } from '../model/test.ts';
7
7
  import { SuiteConfig, SuiteFailure, SuiteResult } from '../model/suite.ts';
8
8
  import { TestConsumerShape } from '../consumer/types.ts';
@@ -13,6 +13,7 @@ import { TestPhaseManager } from './phase.ts';
13
13
  import { AssertUtil } from '../assert/util.ts';
14
14
  import { Barrier } from './barrier.ts';
15
15
  import { ExecutionError } from './error.ts';
16
+ import { SuiteRegistryIndex } from '../registry/registry-index.ts';
16
17
 
17
18
  const TEST_TIMEOUT = TimeUtil.fromValue(Env.TRV_TEST_TIMEOUT.val) ?? 5000;
18
19
 
@@ -55,7 +56,7 @@ export class TestExecutor {
55
56
  * This method should never throw under any circumstances.
56
57
  */
57
58
  async #executeTestMethod(test: TestConfig): Promise<Error | undefined> {
58
- const suite = SuiteRegistry.get(test.class);
59
+ const suite = SuiteRegistryIndex.getConfig(test.class);
59
60
 
60
61
  // Ensure all the criteria below are satisfied before moving forward
61
62
  return Barrier.awaitOperation(test.timeout || TEST_TIMEOUT, async () => {
@@ -175,6 +176,9 @@ export class TestExecutor {
175
176
  * Execute an entire suite
176
177
  */
177
178
  async executeSuite(suite: SuiteConfig, tests: TestConfig[]): Promise<void> {
179
+
180
+ suite.instance = classConstruct(suite.class);
181
+
178
182
  if (!tests.length || await this.#shouldSkip(suite, suite.instance)) {
179
183
  return;
180
184
  }
@@ -246,15 +250,16 @@ export class TestExecutor {
246
250
  if (!(err instanceof Error)) {
247
251
  throw err;
248
252
  }
253
+ console.error(err);
249
254
  this.#onSuiteFailure(AssertUtil.gernerateImportFailure(run.import, err));
250
255
  return;
251
256
  }
252
257
 
253
258
  // Initialize registry (after loading the above)
254
- await SuiteRegistry.init();
259
+ await Registry.finalizeForIndex(SuiteRegistryIndex);
255
260
 
256
261
  // Convert inbound arguments to specific tests to run
257
- const suites = SuiteRegistry.getSuiteTests(run);
262
+ const suites = SuiteRegistryIndex.getSuiteTests(run);
258
263
  if (!suites.length) {
259
264
  console.warn('Unable to find suites for ', run);
260
265
  }
@@ -10,7 +10,7 @@ import { TestRun } from '../model/test.ts';
10
10
  import { TestExecutor } from './executor.ts';
11
11
  import { RunnerUtil } from './util.ts';
12
12
  import { RunState } from './types.ts';
13
- import { TestConsumerRegistry } from '../consumer/registry.ts';
13
+ import { TestConsumerRegistryIndex } from '../consumer/registry-index.ts';
14
14
 
15
15
  /**
16
16
  * Test Runner
@@ -27,7 +27,7 @@ export class Runner {
27
27
  * Run all files
28
28
  */
29
29
  async runFiles(globs?: string[]): Promise<boolean> {
30
- const target = await TestConsumerRegistry.getInstance(this.#state);
30
+ const target = await TestConsumerRegistryIndex.getInstance(this.#state);
31
31
  const consumer = new RunnableTestConsumer(target);
32
32
  const tests = await RunnerUtil.getTestDigest(globs, this.#state.tags);
33
33
  const testRuns = RunnerUtil.getTestRuns(tests)
@@ -60,7 +60,7 @@ export class Runner {
60
60
  RuntimeIndex.reinitForModule(entry.module);
61
61
  }
62
62
 
63
- const target = await TestConsumerRegistry.getInstance(this.#state);
63
+ const target = await TestConsumerRegistryIndex.getInstance(this.#state);
64
64
 
65
65
  const consumer = new RunnableTestConsumer(target)
66
66
  .withTransformer(e => {