@travetto/test
Advanced tools
Comparing version 5.0.0-rc.3 to 5.0.0-rc.4
{ | ||
"name": "@travetto/test", | ||
"version": "5.0.0-rc.3", | ||
"version": "5.0.0-rc.4", | ||
"description": "Declarative test framework", | ||
@@ -30,11 +30,11 @@ "keywords": [ | ||
"dependencies": { | ||
"@travetto/runtime": "^5.0.0-rc.3", | ||
"@travetto/registry": "^5.0.0-rc.3", | ||
"@travetto/terminal": "^5.0.0-rc.3", | ||
"@travetto/worker": "^5.0.0-rc.3", | ||
"@travetto/runtime": "^5.0.0-rc.4", | ||
"@travetto/registry": "^5.0.0-rc.4", | ||
"@travetto/terminal": "^5.0.0-rc.4", | ||
"@travetto/worker": "^5.0.0-rc.4", | ||
"yaml": "^2.4.5" | ||
}, | ||
"peerDependencies": { | ||
"@travetto/cli": "^5.0.0-rc.3", | ||
"@travetto/transformer": "^5.0.0-rc.2" | ||
"@travetto/cli": "^5.0.0-rc.4", | ||
"@travetto/transformer": "^5.0.0-rc.3" | ||
}, | ||
@@ -41,0 +41,0 @@ "peerDependenciesMeta": { |
@@ -24,3 +24,3 @@ <!-- This file was generated by @travetto/doc and should not be modified directly --> | ||
## Definition | ||
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#L19) decorator. All tests intrinsically support `async`/`await`. | ||
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`. | ||
@@ -83,5 +83,4 @@ A simple example would be: | ||
const tslib_1 = require("tslib"); | ||
const Ⲑ_util_1 = tslib_1.__importStar(require("@travetto/test/src/execute/util.js")); | ||
const Ⲑ_debug_1 = tslib_1.__importStar(require("@travetto/runtime/src/debug.js")); | ||
const Ⲑ_check_1 = tslib_1.__importStar(require("@travetto/test/src/assert/check.js")); | ||
const Ⲑ_decorator_1 = tslib_1.__importStar(require("@travetto/registry/src/decorator.js")); | ||
const Ⲑ_function_1 = tslib_1.__importStar(require("@travetto/runtime/src/function.js")); | ||
@@ -92,5 +91,5 @@ var ᚕm = ["@travetto/test", "doc/assert-example"]; | ||
let SimpleTest = class SimpleTest { | ||
static Ⲑinit = Ⲑ_function_1.register(SimpleTest, ᚕm, { hash: 1887908328, lines: [5, 12] }, { test: { hash: 102834457, lines: [8, 11] } }, false, false); | ||
static Ⲑinit = Ⲑ_function_1.registerFunction(SimpleTest, ᚕm, { hash: 1887908328, lines: [5, 12] }, { test: { hash: 102834457, lines: [8, 11, 10] } }, false, false); | ||
async test() { | ||
if (Ⲑ_util_1.RunnerUtil.tryDebugger) | ||
if (Ⲑ_debug_1.tryDebugger) | ||
debugger; | ||
@@ -101,7 +100,6 @@ Ⲑ_check_1.AssertCheck.check({ module: ᚕm, line: 10, text: "{ size: 20, address: { state: 'VA' } }", operator: "deepStrictEqual" }, true, { size: 20, address: { state: 'VA' } }, {}); | ||
tslib_1.__decorate([ | ||
(0, test_1.Test)({ ident: "@Test()", lineBodyStart: 10 }) | ||
(0, test_1.Test)() | ||
], SimpleTest.prototype, "test", null); | ||
SimpleTest = tslib_1.__decorate([ | ||
Ⲑ_decorator_1.Register(), | ||
(0, test_1.Suite)({ ident: "@Suite()" }) | ||
(0, test_1.Suite)() | ||
], SimpleTest); | ||
@@ -108,0 +106,0 @@ ``` |
import { EventEmitter } from 'node:events'; | ||
import { RuntimeIndex } from '@travetto/runtime'; | ||
@@ -7,4 +6,3 @@ import { Assertion, TestConfig } from '../model/test'; | ||
export interface CaptureAssert extends Partial<Assertion> { | ||
module: [string, string]; | ||
file: string; | ||
module?: [string, string]; | ||
line: number; | ||
@@ -32,5 +30,5 @@ text: string; | ||
const handler = (a: CaptureAssert): void => { | ||
const assrt = { | ||
const assrt: Assertion = { | ||
...a, | ||
file: RuntimeIndex.getSourceFile(a.module), | ||
import: a.import ?? a.module!.join('/'), | ||
classId: test.classId, | ||
@@ -37,0 +35,0 @@ methodName: test.methodName |
import assert from 'node:assert'; | ||
import { AppError, ClassInstance, Class, RuntimeIndex } from '@travetto/runtime'; | ||
import { AppError, ClassInstance, Class } from '@travetto/runtime'; | ||
@@ -26,5 +26,2 @@ import { ThrowableError, TestConfig, Assertion } from '../model/test'; | ||
static check(assertion: CaptureAssert, positive: boolean, ...args: unknown[]): void { | ||
/* eslint-disable @typescript-eslint/consistent-type-assertions */ | ||
assertion.file = RuntimeIndex.getSourceFile(assertion.module); | ||
let fn = assertion.operator; | ||
@@ -41,2 +38,3 @@ assertion.operator = ASSERT_FN_OPERATOR[fn]; | ||
/* eslint-disable @typescript-eslint/consistent-type-assertions */ | ||
// Check fn to call | ||
@@ -219,4 +217,2 @@ if (fn === 'fail') { | ||
assertion.file = RuntimeIndex.getSourceFile(assertion.module); | ||
try { | ||
@@ -254,4 +250,2 @@ action(); | ||
assertion.file = RuntimeIndex.getSourceFile(assertion.module); | ||
try { | ||
@@ -280,3 +274,3 @@ if ('then' in action) { | ||
static checkUnhandled(test: TestConfig, err: Error | assert.AssertionError): void { | ||
let line = AssertUtil.getPositionOfError(err, test.file).line; | ||
let line = AssertUtil.getPositionOfError(err, test.import).line; | ||
if (line === 1) { | ||
@@ -286,6 +280,4 @@ line = test.lineStart; | ||
const entry = RuntimeIndex.getFromSource(test.file)!; | ||
AssertCapture.add({ | ||
module: [entry.module, entry.relativeFile], | ||
file: test.file, | ||
import: test.import, | ||
line, | ||
@@ -292,0 +284,0 @@ operator: 'throws', |
import util from 'node:util'; | ||
import { Runtime } from '@travetto/runtime'; | ||
import { Runtime, RuntimeIndex } from '@travetto/runtime'; | ||
@@ -45,3 +45,3 @@ import { TestConfig, Assertion, TestResult } from '../model/test'; | ||
*/ | ||
static getPositionOfError(err: Error, filename: string): { file: string, line: number } { | ||
static getPositionOfError(err: Error, imp: string): { import: string, line: number } { | ||
const cwd = Runtime.mainSourcePath; | ||
@@ -54,2 +54,4 @@ const lines = (err.stack ?? new Error().stack!) | ||
const filename = RuntimeIndex.getFromImport(imp)?.sourceFile!; | ||
let best = lines.filter(x => x.includes(filename))[0]; | ||
@@ -62,3 +64,3 @@ | ||
if (!best) { | ||
return { file: filename, line: 1 }; | ||
return { import: imp, line: 1 }; | ||
} | ||
@@ -68,3 +70,3 @@ | ||
if (!pth) { | ||
return { file: filename, line: 1 }; | ||
return { import: imp, line: 1 }; | ||
} | ||
@@ -86,3 +88,3 @@ | ||
const res = { file: outFile, line }; | ||
const res = { import: RuntimeIndex.getFromSource(outFile)?.import!, line }; | ||
@@ -96,3 +98,3 @@ return res; | ||
static generateSuiteError(suite: SuiteConfig, methodName: string, error: Error): { assert: Assertion, testResult: TestResult, testConfig: TestConfig } { | ||
const { file, ...pos } = this.getPositionOfError(error, suite.file); | ||
const { import: imp, ...pos } = this.getPositionOfError(error, suite.import); | ||
let line = pos.line; | ||
@@ -106,3 +108,3 @@ | ||
const core = { file, classId: suite.classId, methodName, module: Runtime.main.name }; | ||
const core = { import: imp, classId: suite.classId, methodName }; | ||
const coreAll = { ...core, description: msg, lineStart: line, lineEnd: line, lineBodyStart: line }; | ||
@@ -109,0 +111,0 @@ |
import { existsSync } from 'node:fs'; | ||
import { Class } from '@travetto/runtime'; | ||
import { Class, RuntimeIndex } from '@travetto/runtime'; | ||
@@ -31,3 +31,3 @@ import { TestConsumer } from '../types'; | ||
// Was only loading to verify existence (TODO: double-check) | ||
if (existsSync(test.file)) { | ||
if (existsSync(RuntimeIndex.getFromImport(test.import)!.sourceFile)) { | ||
this.#state[test.classId] = this.#state[test.classId] ?? {}; | ||
@@ -54,3 +54,3 @@ this.#state[test.classId][test.methodName] = test.status; | ||
return { | ||
classId: clsId, passed: 0, failed: 0, skipped: 0, total: 0, tests: [], duration: 0, file: '', lines: { start: 0, end: 0 } | ||
classId: clsId, passed: 0, failed: 0, skipped: 0, total: 0, tests: [], duration: 0, import: '', lineStart: 0, lineEnd: 0 | ||
}; | ||
@@ -75,4 +75,5 @@ } | ||
skipped: total.skipped, | ||
file: suite.file, | ||
lines: suite.lines, | ||
import: suite.import, | ||
lineStart: suite.lineStart, | ||
lineEnd: suite.lineEnd, | ||
total: total.failed + total.passed, | ||
@@ -79,0 +80,0 @@ tests: [], |
import { Terminal } from '@travetto/terminal'; | ||
import { AppError, TimeUtil, Runtime } from '@travetto/runtime'; | ||
import { AppError, TimeUtil, Runtime, RuntimeIndex } from '@travetto/runtime'; | ||
import { stringify } from 'yaml'; | ||
@@ -69,2 +69,3 @@ | ||
const text = asrt.message ? `${asrt.text} (${this.#enhancer.failure(asrt.message)})` : asrt.text; | ||
const pth = RuntimeIndex.getFromImport(asrt.import)!.sourceFile.replace(Runtime.mainSourcePath, '.'); | ||
let subMessage = [ | ||
@@ -74,3 +75,3 @@ this.#enhancer.assertNumber(++subCount), | ||
this.#enhancer.assertDescription(text), | ||
`${this.#enhancer.assertFile(asrt.file.replace(Runtime.mainSourcePath, '.'))}:${this.#enhancer.assertLine(asrt.line)}` | ||
`${this.#enhancer.assertFile(pth)}:${this.#enhancer.assertLine(asrt.line)}` | ||
].join(' '); | ||
@@ -77,0 +78,0 @@ |
@@ -5,2 +5,4 @@ import { Writable } from 'node:stream'; | ||
import { RuntimeIndex } from '@travetto/runtime'; | ||
import { TestEvent } from '../../model/event'; | ||
@@ -89,3 +91,3 @@ import { SuitesSummary, TestConsumer } from '../types'; | ||
skipped="${suite.skipped}" | ||
file="${suite.file}" | ||
file="${RuntimeIndex.getFromImport(suite.import)!.sourceFile}" | ||
> | ||
@@ -106,3 +108,3 @@ ${testBodies.join('\n')} | ||
<testsuites | ||
name="${summary.suites.length ? summary.suites[0].file : 'nameless'}" | ||
name="${summary.suites.length ? RuntimeIndex.getFromImport(summary.suites[0].import)?.sourceFile : 'nameless'}" | ||
time="${summary.duration}" | ||
@@ -109,0 +111,0 @@ tests="${summary.total}" |
@@ -18,2 +18,3 @@ import { ClassInstance } from '@travetto/runtime'; | ||
* @augments `@travetto/test:AssertCheck` | ||
* @augments `@travetto/runtime:DebugBreak` | ||
*/ | ||
@@ -20,0 +21,0 @@ export function Test(): MethodDecorator; |
@@ -68,7 +68,7 @@ import { AssertionError } from 'node:assert'; | ||
*/ | ||
static failFile(consumer: TestConsumer, file: string, err: Error): void { | ||
const name = path.basename(file); | ||
const classId = `${RuntimeIndex.getEntry(file)?.id}○${name}`; | ||
static failFile(consumer: TestConsumer, imp: string, err: Error): void { | ||
const name = path.basename(imp); | ||
const classId = `${RuntimeIndex.getFromImport(imp)?.id}○${name}`; | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
const suite = { class: { name }, classId, duration: 0, lineStart: 1, lineEnd: 1, file, } as SuiteConfig & SuiteResult; | ||
const suite = { class: { name }, classId, duration: 0, lineStart: 1, lineEnd: 1, import: imp, } as SuiteConfig & SuiteResult; | ||
err.message = err.message.replaceAll(Runtime.mainSourcePath, '.'); | ||
@@ -94,3 +94,3 @@ const res = AssertUtil.generateSuiteError(suite, 'require', err); | ||
lineEnd: suite.lineEnd, | ||
file: suite.file, | ||
import: suite.import, | ||
classId: suite.classId, | ||
@@ -114,3 +114,2 @@ duration: 0, | ||
methodName: test.methodName, | ||
module: Runtime.main.name, | ||
description: test.description, | ||
@@ -121,3 +120,3 @@ classId: test.classId, | ||
lineBodyStart: test.lineBodyStart, | ||
file: test.file, | ||
import: test.import, | ||
status: 'skipped', | ||
@@ -265,10 +264,6 @@ assertions: [], | ||
*/ | ||
static async execute(consumer: TestConsumer, file: string, ...args: string[]): Promise<void> { | ||
static async execute(consumer: TestConsumer, imp: string, ...args: string[]): Promise<void> { | ||
file = path.resolve(file); | ||
const entry = RuntimeIndex.getEntry(file)!; | ||
try { | ||
await import(entry.import); | ||
await import(imp); | ||
} catch (err) { | ||
@@ -278,3 +273,3 @@ if (!(err instanceof Error)) { | ||
} | ||
this.failFile(consumer, file, err); | ||
this.failFile(consumer, imp, err); | ||
return; | ||
@@ -287,3 +282,3 @@ } | ||
// Convert inbound arguments to specific tests to run | ||
const params = SuiteRegistry.getRunParams(file, ...args); | ||
const params = SuiteRegistry.getRunParams(imp, ...args); | ||
@@ -290,0 +285,0 @@ // If running specific suites |
import path from 'node:path'; | ||
import { path as mp } from '@travetto/manifest'; | ||
import { TimeUtil, Runtime, RuntimeIndex } from '@travetto/runtime'; | ||
@@ -25,6 +24,2 @@ import { WorkPool } from '@travetto/worker'; | ||
get patterns(): RegExp[] { | ||
return this.#state.args.map(x => new RegExp(mp.toPosix(x))); | ||
} | ||
/** | ||
@@ -36,5 +31,5 @@ * Run all files | ||
const files = (await RunnerUtil.getTestFiles(this.patterns)).map(f => f.sourceFile); | ||
const imports = await RunnerUtil.getTestImports(this.#state.args); | ||
console.debug('Running', { files, patterns: this.patterns }); | ||
console.debug('Running', { imports, patterns: this.#state.args }); | ||
@@ -45,3 +40,3 @@ const testCount = await RunnerUtil.getTestCount(this.#state.args); | ||
buildStandardTestManager.bind(null, consumer), | ||
files, | ||
imports, | ||
{ | ||
@@ -60,13 +55,19 @@ idleTimeoutMillis: TimeUtil.asMillis(10, 's'), | ||
async runSingle(): Promise<boolean> { | ||
const mod = RuntimeIndex.getEntry(path.resolve(this.#state.args[0]))!; | ||
if (mod.module !== Runtime.main.name) { | ||
RuntimeIndex.reinitForModule(mod.module); | ||
let imp = RuntimeIndex.getFromImport(this.#state.args[0])?.import; | ||
if (!imp) { | ||
imp = RuntimeIndex.getFromSource(path.resolve(this.#state.args[0]))?.import; | ||
} | ||
const entry = RuntimeIndex.getFromImport(imp!)!; | ||
if (entry.module !== Runtime.main.name) { | ||
RuntimeIndex.reinitForModule(entry.module); | ||
} | ||
const consumer = await RunnableTestConsumer.get(this.#state.consumer ?? this.#state.format); | ||
const [file, ...args] = this.#state.args; | ||
const [, ...args] = this.#state.args; | ||
await consumer.onStart({}); | ||
await TestExecutor.execute(consumer, file, ...args); | ||
await TestExecutor.execute(consumer, imp!, ...args); | ||
return consumer.summarizeAsBoolean(); | ||
@@ -73,0 +74,0 @@ } |
import { spawn } from 'node:child_process'; | ||
import { createReadStream } from 'node:fs'; | ||
import readline from 'node:readline'; | ||
import fs from 'node:fs/promises'; | ||
import readline from 'node:readline/promises'; | ||
import { Env, ExecUtil, ShutdownManager, Util, RuntimeIndex } from '@travetto/runtime'; | ||
import type { IndexedFile } from '@travetto/manifest'; | ||
import { Env, ExecUtil, ShutdownManager, Util, RuntimeIndex, Runtime } from '@travetto/runtime'; | ||
@@ -22,15 +22,14 @@ /** | ||
*/ | ||
static isTestFile(file: string): Promise<boolean> { | ||
return new Promise<boolean>((resolve) => { | ||
const input = createReadStream(file); | ||
const reader = readline.createInterface({ input }) | ||
.on('line', line => { | ||
if (line.includes('@Suite')) { | ||
resolve(true); | ||
reader.close(); | ||
} | ||
}) | ||
.on('end', resolve.bind(null, false)) | ||
.on('close', resolve.bind(null, false)); | ||
}); | ||
static async isTestFile(file: string): Promise<boolean> { | ||
const reader = readline.createInterface({ input: createReadStream(file) }); | ||
const state = { imp: false, suite: false }; | ||
for await (const line of reader) { | ||
state.imp ||= line.includes('@travetto/test'); | ||
state.suite ||= line.includes('Suite'); // Decorator or name | ||
if (state.imp && state.suite) { | ||
reader.close(); | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
@@ -41,4 +40,12 @@ | ||
*/ | ||
static async getTestFiles(globs?: RegExp[]): Promise<IndexedFile[]> { | ||
const files = RuntimeIndex.find({ | ||
static async getTestImports(globs?: string[]): Promise<string[]> { | ||
const files = new Set<string>(); | ||
// Collect globs | ||
if (globs) { | ||
for await (const item of fs.glob(globs)) { | ||
files.add(Runtime.workspaceRelative(item)); | ||
} | ||
} | ||
const found = RuntimeIndex.find({ | ||
module: m => m.roles.includes('test') || m.roles.includes('std'), | ||
@@ -48,10 +55,10 @@ folder: f => f === 'test', | ||
}) | ||
.filter(f => globs?.some(g => g.test(f.sourceFile)) ?? true); | ||
.filter(f => files.size === 0 || files.has(f.sourceFile)); | ||
const validFiles = files | ||
.map(f => this.isTestFile(f.sourceFile).then(valid => ({ file: f, valid }))); | ||
const validImports = found | ||
.map(f => this.isTestFile(f.sourceFile).then(valid => ({ import: f.import, valid }))); | ||
return (await Promise.all(validFiles)) | ||
return (await Promise.all(validImports)) | ||
.filter(x => x.valid) | ||
.map(x => x.file); | ||
.map(x => x.import); | ||
} | ||
@@ -76,9 +83,2 @@ | ||
} | ||
/** | ||
* Determine if we should invoke the debugger | ||
*/ | ||
static get tryDebugger(): boolean { | ||
return Env.TRV_TEST_BREAK_ENTRY.isTrue; | ||
} | ||
} |
@@ -9,13 +9,15 @@ import { RootRegistry, MethodSource } from '@travetto/registry'; | ||
import { CumulativeSummaryConsumer } from '../consumer/types/cumulative'; | ||
import { RunEvent } from '../worker/types'; | ||
import { RunRequest } from '../worker/types'; | ||
import { RunnerUtil } from './util'; | ||
import { TestEvent } from '../model/event'; | ||
function isRunEvent(ev: unknown): ev is RunEvent { | ||
function isRunRequest(ev: unknown): ev is RunRequest { | ||
return typeof ev === 'object' && !!ev && 'type' in ev && typeof ev.type === 'string' && ev.type === 'run-test'; | ||
} | ||
type RemoveTestEvent = { type: 'removeTest', method: string, import: string, classId: string }; | ||
export type TestWatchEvent = | ||
TestEvent | | ||
{ type: 'removeTest', method: string, file: string, classId: string } | | ||
RemoveTestEvent | | ||
{ type: 'ready' } | | ||
@@ -37,3 +39,3 @@ { type: 'log', message: string }; | ||
const itr = new WorkQueue<string>(); | ||
const itr = new WorkQueue<string | RunRequest>(); | ||
@@ -57,3 +59,3 @@ await SuiteRegistry.init(); | ||
if (conf) { | ||
const key = `${conf.file}#${conf.class.name}#${conf.methodName}`; | ||
const key = { import: conf.import, class: conf.class.name, method: conf.methodName }; | ||
itr.add(key, true); // Shift to front | ||
@@ -66,4 +68,4 @@ } | ||
classId: cls?.Ⲑid, | ||
file: Runtime.getSource(cls) | ||
}); | ||
import: Runtime.getImport(cls) | ||
} satisfies RemoveTestEvent); | ||
} | ||
@@ -73,3 +75,3 @@ }); | ||
// If a file is changed, but doesn't emit classes, re-run whole file | ||
RootRegistry.onNonClassChanges(file => itr.add(file)); | ||
RootRegistry.onNonClassChanges(imp => itr.add(imp)); | ||
@@ -79,5 +81,5 @@ await RootRegistry.init(); | ||
process.on('message', ev => { | ||
if (isRunEvent(ev)) { | ||
if (isRunRequest(ev)) { | ||
console.debug('Manually triggered', ev); | ||
itr.add([ev.file, ev.class, ev.method].filter(x => !!x).join('#'), true); | ||
itr.add(ev, true); | ||
} | ||
@@ -89,5 +91,5 @@ }); | ||
if (runAllOnStart) { | ||
for (const test of await RunnerUtil.getTestFiles()) { | ||
await import(test.import); | ||
itr.add(test.sourceFile); | ||
for (const imp of await RunnerUtil.getTestImports()) { | ||
await import(imp); | ||
itr.add(imp); | ||
} | ||
@@ -94,0 +96,0 @@ } |
@@ -9,6 +9,2 @@ /** Configuration of a skip */ | ||
/** | ||
* The module the test is declared in | ||
*/ | ||
module: string; | ||
/** | ||
* The class id | ||
@@ -22,5 +18,5 @@ */ | ||
/** | ||
* It's file | ||
* The import location for the suite | ||
*/ | ||
file: string; | ||
import: string; | ||
/** | ||
@@ -27,0 +23,0 @@ * The first line of the unit |
@@ -63,5 +63,5 @@ import type { Class } from '@travetto/runtime'; | ||
/** | ||
* File suite is in | ||
* Import for the suite | ||
*/ | ||
file: string; | ||
import: string; | ||
/** | ||
@@ -68,0 +68,0 @@ * Start of the suite |
@@ -65,5 +65,5 @@ import type { Class, TimeSpan } from '@travetto/runtime'; | ||
/** | ||
* File of assertion | ||
* Import of assertion | ||
*/ | ||
file: string; | ||
import: string; | ||
/** | ||
@@ -70,0 +70,0 @@ * Line number |
@@ -23,5 +23,4 @@ import { Class, ConcreteClass, Runtime, describeFunction } from '@travetto/runtime'; | ||
class: cls, | ||
module: Runtime.main.name, | ||
classId: cls.Ⲑid, | ||
file: Runtime.getSource(cls), | ||
import: Runtime.getImport(cls), | ||
lineStart: lines?.[0], | ||
@@ -41,6 +40,6 @@ lineEnd: lines?.[1], | ||
class: cls, | ||
module: Runtime.main.name, | ||
file: Runtime.getSource(cls), | ||
import: Runtime.getImport(cls), | ||
lineStart: lines?.[0], | ||
lineEnd: lines?.[1], | ||
lineBodyStart: lines?.[2], | ||
methodName: fn.name | ||
@@ -98,7 +97,7 @@ }; | ||
*/ | ||
getRunParams(file: string, clsName?: string, method?: string): { suites: SuiteConfig[] } | { suite: SuiteConfig, test?: TestConfig } { | ||
getRunParams(imp: string, clsName?: string, method?: string): { suites: SuiteConfig[] } | { suite: SuiteConfig, test?: TestConfig } { | ||
if (clsName && /^\d+$/.test(clsName)) { // If we only have a line number | ||
const line = parseInt(clsName, 10); | ||
const suites = this.getValidClasses() | ||
.filter(cls => Runtime.getSource(cls) === file) | ||
.filter(cls => Runtime.getImport(cls) === imp) | ||
.map(x => this.get(x)).filter(x => !x.skip); | ||
@@ -105,0 +104,0 @@ const suite = suites.find(x => line >= x.lineStart && line <= x.lineEnd); |
@@ -15,7 +15,3 @@ import { TimeSpan } from '@travetto/runtime'; | ||
TRV_TEST_TIMEOUT: TimeSpan | number; | ||
/** | ||
* Should the test break on the first line of debugging | ||
*/ | ||
TRV_TEST_BREAK_ENTRY: boolean; | ||
} | ||
} |
@@ -82,3 +82,3 @@ import { createWriteStream } from 'node:fs'; | ||
console.debug('Running', { file: event.file }); | ||
console.debug('Running', { import: event.import }); | ||
@@ -89,3 +89,3 @@ try { | ||
mode: 'single', | ||
args: [event.file!, event.class!, event.method!], | ||
args: [event.import, event.class!, event.method!], | ||
concurrency: 1 | ||
@@ -92,0 +92,0 @@ }).run(); |
@@ -6,3 +6,3 @@ import { fork } from 'node:child_process'; | ||
import { Events, RunEvent } from './types'; | ||
import { Events, RunEvent, RunRequest } from './types'; | ||
import { TestConsumer } from '../consumer/types'; | ||
@@ -15,15 +15,16 @@ import { ErrorUtil } from '../consumer/error'; | ||
*/ | ||
export async function buildStandardTestManager(consumer: TestConsumer, file: string): Promise<void> { | ||
process.send?.({ type: 'log', message: `Worker Executing ${file}` }); | ||
export async function buildStandardTestManager(consumer: TestConsumer, imp: string | RunRequest): Promise<void> { | ||
process.send?.({ type: 'log', message: `Worker Executing ${imp}` }); | ||
let event: RunEvent; | ||
if (file.includes('#')) { | ||
const [f, cls, method] = file.split('#'); | ||
event = { file: f, class: cls, method }; | ||
if (typeof imp === 'string') { | ||
event = { import: imp }; | ||
} else if ('file' in imp) { | ||
event = { import: RuntimeIndex.getFromSource(imp.file)?.sourceFile!, class: imp.class, method: imp.method }; | ||
} else { | ||
event = { file }; | ||
event = imp; | ||
} | ||
const { module } = RuntimeIndex.getEntry(event.file!)!; | ||
const cwd = RuntimeIndex.getModule(module)!.sourcePath; | ||
const { module } = RuntimeIndex.getFromImport(event.import!)!; | ||
const suiteMod = RuntimeIndex.getModule(module); | ||
@@ -34,6 +35,6 @@ const channel = new ParentCommChannel<TestEvent & { error?: Error }>( | ||
{ | ||
cwd, | ||
cwd: suiteMod!.sourcePath, | ||
env: { | ||
...process.env, | ||
...Env.TRV_MANIFEST.export(RuntimeIndex.getModule(module)!.outputPath), | ||
...Env.TRV_MANIFEST.export(suiteMod!.outputPath), | ||
...Env.TRV_QUIET.export(true) | ||
@@ -69,3 +70,3 @@ }, | ||
process.send?.({ type: 'log', message: `Worker Finished ${file}` }); | ||
process.send?.({ type: 'log', message: `Worker Finished ${imp}` }); | ||
@@ -72,0 +73,0 @@ // If we received an error, throw it |
/** | ||
* Test Run Request | ||
*/ | ||
export type RunRequest = { | ||
file: string; | ||
class?: string; | ||
method?: string; | ||
} | { | ||
import: string; | ||
class?: string; | ||
method?: string; | ||
}; | ||
/** | ||
* Test Run Event | ||
*/ | ||
export type RunEvent = { | ||
file?: string; | ||
import: string; | ||
error?: unknown; | ||
@@ -7,0 +20,0 @@ class?: string; |
@@ -16,11 +16,10 @@ import { CliCommand } from '@travetto/cli'; | ||
async main(patterns: string[]) { | ||
const regexes = patterns.map(x => new RegExp(x)); | ||
const files = await RunnerUtil.getTestFiles(regexes); | ||
const imports = await RunnerUtil.getTestImports(patterns); | ||
// Load all tests | ||
for (const file of files) { | ||
for (const imp of imports) { | ||
try { | ||
await import(file.import); | ||
await import(imp); | ||
} catch (err) { | ||
console.error('Failed to import', file.sourceFile, err); | ||
console.error('Failed to import', imp, err); | ||
} | ||
@@ -27,0 +26,0 @@ } |
108592
52
2886
242