@travetto/test 5.0.0-rc.8 → 5.0.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,27 +1,14 @@
1
1
  import { RootRegistry, MethodSource } from '@travetto/registry';
2
- import { WorkPool, WorkQueue } from '@travetto/worker';
3
- import { Runtime, describeFunction } from '@travetto/runtime';
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 { RunRequest } from '../worker/types';
9
+ import { TestRun } from '../model/test';
10
10
  import { RunnerUtil } from './util';
11
- import { TestEvent } from '../model/event';
12
-
13
- function isRunRequest(ev: unknown): ev is RunRequest {
14
- return typeof ev === 'object' && !!ev && 'type' in ev && typeof ev.type === 'string' && ev.type === 'run-test';
15
- }
16
-
17
- type RemoveTestEvent = { type: 'removeTest', method: string, import: string, classId: string };
18
-
19
- export type TestWatchEvent =
20
- TestEvent |
21
- RemoveTestEvent |
22
- { type: 'ready' } |
23
- { type: 'log', message: string };
24
-
11
+ import { TestReadyEvent, TestRemovedEvent } from '../worker/types';
25
12
  /**
26
13
  * Test Watcher.
27
14
  *
@@ -35,12 +22,20 @@ export class TestWatcher {
35
22
  static async watch(format: string, runAllOnStart = true): Promise<void> {
36
23
  console.debug('Listening for changes');
37
24
 
38
- const itr = new WorkQueue<string | RunRequest>();
39
-
40
25
  await SuiteRegistry.init();
41
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
+ }
42
35
 
43
- 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');
44
39
 
45
40
  new MethodSource(RootRegistry).on(e => {
46
41
  const [cls, method] = (e.prev ?? e.curr ?? []);
@@ -54,39 +49,40 @@ export class TestWatcher {
54
49
  const conf = SuiteRegistry.getByClassAndMethod(cls, method)!;
55
50
  if (e.type !== 'removing') {
56
51
  if (conf) {
57
- const key = { import: conf.import, class: conf.class.name, method: conf.methodName };
58
- 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
59
57
  }
60
58
  } else {
61
59
  process.send?.({
62
60
  type: 'removeTest',
61
+ methodNames: method?.name ? [method.name!] : undefined!,
63
62
  method: method?.name,
64
63
  classId: cls?.Ⲑid,
65
64
  import: Runtime.getImport(cls)
66
- } satisfies RemoveTestEvent);
65
+ } satisfies TestRemovedEvent);
67
66
  }
68
67
  });
69
68
 
70
- // If a file is changed, but doesn't emit classes, re-run whole file
71
- RootRegistry.onNonClassChanges(imp => itr.add(imp));
72
69
 
73
- 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 }));
74
72
 
75
73
  process.on('message', ev => {
76
- if (isRunRequest(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
+ }
77
80
  console.debug('Manually triggered', ev);
78
- itr.add(ev, true);
81
+ itr.add(castTo(ev), true);
79
82
  }
80
83
  });
81
84
 
82
- process.send?.({ type: 'ready' });
83
-
84
- if (runAllOnStart) {
85
- for await (const imp of await RunnerUtil.getTestImports()) {
86
- await Runtime.importFrom(imp);
87
- itr.add(imp);
88
- }
89
- }
85
+ process.send?.({ type: 'ready' } satisfies TestReadyEvent);
90
86
 
91
87
  await WorkPool.run(
92
88
  buildStandardTestManager.bind(null, consumer),
@@ -1,5 +1,5 @@
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
@@ -25,6 +25,10 @@ export interface SuiteCore {
25
25
  * The last line of the unit
26
26
  */
27
27
  lineEnd: number;
28
+ /**
29
+ * Tags for a suite or a test
30
+ */
31
+ tags?: string[];
28
32
  }
29
33
 
30
34
  /**
@@ -35,4 +39,8 @@ export interface TestCore extends SuiteCore {
35
39
  * The first line of the unit body
36
40
  */
37
41
  lineBodyStart: number;
42
+ /**
43
+ * For extended suites, this is location of the actual file where the test exists
44
+ */
45
+ sourceImport?: string;
38
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
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
  /**
@@ -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
@@ -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
+ };
@@ -1,8 +1,8 @@
1
- import { Class, ConcreteClass, Runtime, describeFunction } from '@travetto/runtime';
1
+ import { Class, Runtime, classConstruct, describeFunction, asFull } from '@travetto/runtime';
2
2
  import { MetadataRegistry } from '@travetto/registry';
3
3
 
4
4
  import { SuiteConfig } from '../model/suite';
5
- import { TestConfig } from '../model/test';
5
+ import { TestConfig, TestRun } from '../model/test';
6
6
 
7
7
  /**
8
8
  * Test Suite registry
@@ -21,6 +21,7 @@ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> {
21
21
  return {
22
22
  class: cls,
23
23
  classId: cls.Ⲑid,
24
+ tags: [],
24
25
  import: Runtime.getImport(cls),
25
26
  lineStart: lines?.[0],
26
27
  lineEnd: lines?.[1],
@@ -36,6 +37,7 @@ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> {
36
37
  const lines = describeFunction(cls)?.methods?.[fn.name].lines;
37
38
  return {
38
39
  class: cls,
40
+ tags: [],
39
41
  import: Runtime.getImport(cls),
40
42
  lineStart: lines?.[0],
41
43
  lineEnd: lines?.[1],
@@ -57,8 +59,7 @@ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> {
57
59
  * a full projection of all listeners and tests.
58
60
  */
59
61
  onInstallFinalize<T>(cls: Class<T>): SuiteConfig {
60
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
61
- const config = this.getOrCreatePending(cls) as SuiteConfig;
62
+ const config = asFull(this.getOrCreatePending(cls));
62
63
  const tests = [...this.pendingFields.get(cls.Ⲑid)!.values()];
63
64
 
64
65
  const parent = this.getParentClass(cls);
@@ -71,20 +72,19 @@ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> {
71
72
  config.beforeEach.push(...pConf.beforeEach);
72
73
  tests.push(...[...pConf.tests.values()].map(t => ({
73
74
  ...t,
75
+ sourceImport: pConf.import,
74
76
  class: cls
75
77
  })));
76
78
  }
77
79
 
78
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
79
- config.instance = new (config.class as ConcreteClass)();
80
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
81
- config.tests = tests as TestConfig[];
80
+ config.instance = classConstruct(config.class);
81
+ config.tests = tests!.map(x => asFull(x));
82
+ config.description ||= config.classId;
82
83
 
83
- if (!config.description) {
84
- config.description = config.classId;
85
- }
86
84
  for (const t of config.tests) {
87
85
  t.classId = config.classId;
86
+ t.import = config.import;
87
+ t.tags = [...t.tags!, ...config.tags!];
88
88
  }
89
89
  return config;
90
90
  }
@@ -92,9 +92,13 @@ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> {
92
92
  /**
93
93
  * Get run parameters from provided input
94
94
  */
95
- getRunParams(imp: string, clsName?: string, method?: string): { suites: SuiteConfig[] } | { suite: SuiteConfig, test?: TestConfig } {
96
- if (clsName && /^\d+$/.test(clsName)) { // If we only have a line number
97
- const line = parseInt(clsName, 10);
95
+ getSuiteTests(run: TestRun): { suite: SuiteConfig, tests: TestConfig[] }[] {
96
+ const clsId = run.classId;
97
+ const imp = run.import;
98
+ const methodNames = run.methodNames ?? [];
99
+
100
+ if (clsId && /^\d+$/.test(clsId)) { // If we only have a line number
101
+ const line = parseInt(clsId, 10);
98
102
  const suites = this.getValidClasses()
99
103
  .filter(cls => Runtime.getImport(cls) === imp)
100
104
  .map(x => this.get(x)).filter(x => !x.skip);
@@ -102,25 +106,25 @@ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> {
102
106
 
103
107
  if (suite) {
104
108
  const test = suite.tests.find(x => line >= x.lineStart && line <= x.lineEnd);
105
- return test ? { suite, test } : { suite };
109
+ return test ? [{ suite, tests: [test] }] : [{ suite, tests: suite.tests }];
106
110
  } else {
107
- return { suites };
111
+ return suites.map(x => ({ suite: x, tests: x.tests }));
108
112
  }
109
113
  } else { // Else lookup directly
110
- if (method) {
111
- const cls = this.getValidClasses().find(x => x.name === clsName)!;
114
+ if (methodNames.length) {
115
+ const cls = this.getValidClasses().find(x => x.Ⲑid === clsId)!;
112
116
  const suite = this.get(cls);
113
- const test = suite.tests.find(x => x.methodName === method)!;
114
- return { suite, test };
115
- } else if (clsName) {
116
- const cls = this.getValidClasses().find(x => x.name === clsName)!;
117
+ const tests = suite.tests.filter(x => methodNames.includes(x.methodName))!;
118
+ return [{ suite, tests }];
119
+ } else if (clsId) {
120
+ const cls = this.getValidClasses().find(x => x.Ⲑid === clsId)!;
117
121
  const suite = this.get(cls);
118
- return { suite };
122
+ return [{ suite, tests: suite.tests }];
119
123
  } else {
120
124
  const suites = this.getValidClasses()
121
125
  .map(x => this.get(x))
122
126
  .filter(x => !describeFunction(x.class).abstract); // Do not run abstract suites
123
- return { suites };
127
+ return suites.map(x => ({ suite: x, tests: x.tests }));
124
128
  }
125
129
  }
126
130
  }
package/src/trv.d.ts CHANGED
@@ -12,5 +12,9 @@ declare global {
12
12
  * @default 5s
13
13
  */
14
14
  TRV_TEST_TIMEOUT: TimeSpan | number;
15
+ /**
16
+ * The tags to include or exclude during testing
17
+ */
18
+ TRV_TEST_TAGS: string[];
15
19
  }
16
20
  }
@@ -3,16 +3,17 @@ import { createWriteStream } from 'node:fs';
3
3
  import { ConsoleManager, Env, Util, Runtime } from '@travetto/runtime';
4
4
  import { ChildCommChannel } from '@travetto/worker';
5
5
 
6
- import { ErrorUtil } from '../consumer/error';
6
+ import { SerializeUtil } from '../consumer/serialize';
7
7
  import { RunnerUtil } from '../execute/util';
8
8
  import { Runner } from '../execute/runner';
9
- import { Events, RunEvent } from './types';
9
+ import { Events } from './types';
10
+ import { TestRun } from '../model/test';
10
11
 
11
12
  /**
12
13
  * Child Worker for the Test Runner. Receives events as commands
13
14
  * to run specific tests
14
15
  */
15
- export class TestChildWorker extends ChildCommChannel<RunEvent> {
16
+ export class TestChildWorker extends ChildCommChannel<TestRun> {
16
17
 
17
18
  #done = Util.resolvablePromise();
18
19
 
@@ -25,7 +26,7 @@ export class TestChildWorker extends ChildCommChannel<RunEvent> {
25
26
  throw err;
26
27
  }
27
28
  // Mark as errored out
28
- this.send(type, { error: ErrorUtil.serializeError(err) });
29
+ this.send(type, { error: SerializeUtil.serializeError(err) });
29
30
  }
30
31
  }
31
32
 
@@ -56,7 +57,7 @@ export class TestChildWorker extends ChildCommChannel<RunEvent> {
56
57
  /**
57
58
  * When we receive a command from the parent
58
59
  */
59
- async onCommand(event: RunEvent & { type: string }): Promise<boolean> {
60
+ async onCommand(event: TestRun & { type: string }): Promise<boolean> {
60
61
  console.debug('on message', { ...event });
61
62
 
62
63
  if (event.type === Events.INIT) { // On request to init, start initialization
@@ -76,18 +77,11 @@ export class TestChildWorker extends ChildCommChannel<RunEvent> {
76
77
  /**
77
78
  * Run a specific test/suite
78
79
  */
79
- async onRunCommand(event: RunEvent): Promise<void> {
80
- console.debug('Run');
81
-
82
- console.debug('Running', { import: event.import });
80
+ async onRunCommand(run: TestRun): Promise<void> {
81
+ console.debug('Running', { import: run.import });
83
82
 
84
83
  try {
85
- await new Runner({
86
- format: 'exec',
87
- mode: 'single',
88
- args: [event.import, event.class!, event.method!],
89
- concurrency: 1
90
- }).run();
84
+ await new Runner({ format: 'exec', target: run }).run();
91
85
  } finally {
92
86
  this.#done.resolve();
93
87
  }
@@ -3,39 +3,34 @@ import { fork } from 'node:child_process';
3
3
  import { Env, RuntimeIndex } from '@travetto/runtime';
4
4
  import { ParentCommChannel } from '@travetto/worker';
5
5
 
6
- import { Events, RunEvent, RunRequest } from './types';
6
+ import { Events, TestLogEvent } from './types';
7
7
  import { TestConsumer } from '../consumer/types';
8
- import { ErrorUtil } from '../consumer/error';
8
+ import { SerializeUtil } from '../consumer/serialize';
9
9
  import { TestEvent } from '../model/event';
10
+ import { TestRun } from '../model/test';
11
+
12
+ const log = (message: string): void => {
13
+ process.send?.({ type: 'log', message } satisfies TestLogEvent);
14
+ };
10
15
 
11
16
  /**
12
17
  * Produce a handler for the child worker
13
18
  */
14
- export async function buildStandardTestManager(consumer: TestConsumer, imp: string | RunRequest): Promise<void> {
15
- let event: RunEvent;
16
- process.send?.({ type: 'log', message: `Worker Input ${JSON.stringify(imp)}` });
17
-
18
- if (typeof imp === 'string') {
19
- event = { import: imp };
20
- } else if ('file' in imp) {
21
- event = { import: RuntimeIndex.getFromSource(imp.file)?.import!, class: imp.class, method: imp.method };
22
- } else {
23
- event = imp;
24
- }
25
-
26
- process.send?.({ type: 'log', message: `Worker Executing ${event.import}` });
19
+ export async function buildStandardTestManager(consumer: TestConsumer, run: TestRun): Promise<void> {
20
+ log(`Worker Input ${JSON.stringify(run)}`);
21
+ log(`Worker Executing ${run.import}`);
27
22
 
28
- const { module } = RuntimeIndex.getFromImport(event.import!)!;
29
- const suiteMod = RuntimeIndex.getModule(module);
23
+ const { module } = RuntimeIndex.getFromImport(run.import)!;
24
+ const suiteMod = RuntimeIndex.getModule(module)!;
30
25
 
31
26
  const channel = new ParentCommChannel<TestEvent & { error?: Error }>(
32
27
  fork(
33
28
  RuntimeIndex.resolveFileImport('@travetto/cli/support/entry.trv'), ['test:child'],
34
29
  {
35
- cwd: suiteMod!.sourcePath,
30
+ cwd: suiteMod.sourcePath,
36
31
  env: {
37
32
  ...process.env,
38
- ...Env.TRV_MANIFEST.export(suiteMod!.outputPath),
33
+ ...Env.TRV_MANIFEST.export(suiteMod.outputPath),
39
34
  ...Env.TRV_QUIET.export(true)
40
35
  },
41
36
  stdio: ['ignore', 'ignore', 2, 'ipc']
@@ -58,7 +53,7 @@ export async function buildStandardTestManager(consumer: TestConsumer, imp: stri
58
53
  // Listen for child to complete
59
54
  const complete = channel.once(Events.RUN_COMPLETE);
60
55
  // Start test
61
- channel.send(Events.RUN, event);
56
+ channel.send(Events.RUN, run);
62
57
 
63
58
  // Wait for complete
64
59
  const { error } = await complete;
@@ -66,10 +61,10 @@ export async function buildStandardTestManager(consumer: TestConsumer, imp: stri
66
61
  // Kill on complete
67
62
  await channel.destroy();
68
63
 
69
- process.send?.({ type: 'log', message: `Worker Finished ${event.import}` });
64
+ log(`Worker Finished ${run.import}`);
70
65
 
71
66
  // If we received an error, throw it
72
67
  if (error) {
73
- throw ErrorUtil.deserializeError(error);
68
+ throw SerializeUtil.deserializeError(error);
74
69
  }
75
70
  }
@@ -1,25 +1,5 @@
1
- /**
2
- * Test Run Request
3
- */
4
- export type RunRequest = {
5
- file: string;
6
- class?: string;
7
- method?: string;
8
- } | {
9
- import: string;
10
- class?: string;
11
- method?: string;
12
- };
13
-
14
- /**
15
- * Test Run Event
16
- */
17
- export type RunEvent = {
18
- import: string;
19
- error?: unknown;
20
- class?: string;
21
- method?: string;
22
- };
1
+ import { TestEvent } from '../model/event';
2
+ import { TestRun } from '../model/test';
23
3
 
24
4
  /**
25
5
  * Test Run Event Keys
@@ -30,4 +10,14 @@ export const Events = {
30
10
  INIT: 'init',
31
11
  INIT_COMPLETE: 'initComplete',
32
12
  READY: 'ready'
33
- };
13
+ };
14
+
15
+ export type TestRemovedEvent = { type: 'removeTest', method?: string } & TestRun;
16
+ export type TestReadyEvent = { type: 'ready' };
17
+ export type TestLogEvent = { type: 'log', message: string };
18
+
19
+ export type TestWatchEvent =
20
+ TestEvent |
21
+ TestRemovedEvent |
22
+ TestReadyEvent |
23
+ TestLogEvent;
@@ -21,6 +21,11 @@ export class TestCommand implements CliCommandShape {
21
21
  concurrency: number = WorkPool.DEFAULT_SIZE;
22
22
  /** Test run mode */
23
23
  mode: TestMode = 'standard';
24
+ /**
25
+ * Tags to target or exclude
26
+ * @alias env.TRV_TEST_TAGS
27
+ */
28
+ tags?: string[];
24
29
 
25
30
  preMain(): void {
26
31
  EventEmitter.defaultMaxListeners = 1000;
@@ -48,14 +53,23 @@ export class TestCommand implements CliCommandShape {
48
53
  }
49
54
  }
50
55
 
51
- async main(first: string = '**/*', regexes: string[] = []): Promise<void> {
56
+ async main(first: string = '**/*', globs: string[] = []): Promise<void> {
52
57
  const { runTests } = await import('./bin/run');
53
58
 
59
+ const isFirst = await this.isFirstFile(first);
60
+ const isSingle = this.mode === 'single' || (isFirst && globs.length === 0);
61
+
54
62
  return runTests({
55
- args: [first, ...regexes],
56
- mode: await this.resolvedMode(first, regexes),
57
63
  concurrency: this.concurrency,
58
- format: this.format
64
+ format: this.format,
65
+ tags: this.tags,
66
+ target: isSingle ?
67
+ {
68
+ import: first,
69
+ classId: globs[0],
70
+ methodNames: globs.slice(1),
71
+ } :
72
+ { globs: [first, ...globs], }
59
73
  });
60
74
  }
61
75
  }
@@ -5,16 +5,18 @@ import { SuiteRegistry } from '../src/registry/suite';
5
5
  import { RunnerUtil } from '../src/execute/util';
6
6
 
7
7
  @CliCommand({ hidden: true })
8
- export class TestCountCommand {
8
+ export class TestDigestCommand {
9
+
10
+ output: 'json' | 'text' = 'text';
9
11
 
10
12
  preMain(): void {
11
13
  Env.TRV_ROLE.set('test');
12
14
  Env.DEBUG.set(false);
13
15
  }
14
16
 
15
- async main(patterns: string[]) {
17
+ async main(globs: string[] = ['**/*.ts']) {
16
18
  // Load all tests
17
- for await (const imp of await RunnerUtil.getTestImports(patterns)) {
19
+ for await (const imp of await RunnerUtil.getTestImports(globs)) {
18
20
  try {
19
21
  await Runtime.importFrom(imp);
20
22
  } catch (err) {
@@ -25,11 +27,17 @@ export class TestCountCommand {
25
27
  await SuiteRegistry.init();
26
28
 
27
29
  const suites = SuiteRegistry.getClasses();
28
- const total = suites
30
+ const all = suites
29
31
  .map(c => SuiteRegistry.get(c))
30
32
  .filter(c => !describeFunction(c.class).abstract)
31
- .reduce((acc, c) => acc + (c.tests?.length ?? 0), 0);
33
+ .flatMap(c => c.tests);
32
34
 
33
- console.log(total);
35
+ if (this.output === 'json') {
36
+ console.log(JSON.stringify(all));
37
+ } else {
38
+ for (const item of all) {
39
+ console.log(`${item.classId}#${item.methodName}`, item.tags?.join('|') ?? '');
40
+ }
41
+ }
34
42
  }
35
43
  }
@@ -1,4 +1,4 @@
1
- import { Env } from '@travetto/runtime';
1
+ import { Env, RuntimeIndex } from '@travetto/runtime';
2
2
  import { CliCommand } from '@travetto/cli';
3
3
 
4
4
  import { runTests } from './bin/run';
@@ -17,7 +17,14 @@ export class TestDirectCommand {
17
17
  Env.TRV_LOG_TIME.clear();
18
18
  }
19
19
 
20
- main(file: string, args: string[]): Promise<void> {
21
- return runTests({ args: [file, ...args], format: this.format, mode: 'single', concurrency: 1 });
20
+ main(importOrFile: string, clsId?: string, methodsNames: string[] = []): Promise<void> {
21
+ return runTests({
22
+ format: this.format,
23
+ target: {
24
+ import: importOrFile,
25
+ classId: clsId,
26
+ methodNames: methodsNames,
27
+ }
28
+ });
22
29
  }
23
30
  }