@travetto/test
Advanced tools
Comparing version 4.1.1 to 5.0.0-rc.0
{ | ||
"name": "@travetto/test", | ||
"version": "4.1.1", | ||
"version": "5.0.0-rc.0", | ||
"description": "Declarative test framework", | ||
@@ -30,11 +30,11 @@ "keywords": [ | ||
"dependencies": { | ||
"@travetto/base": "^4.1.1", | ||
"@travetto/registry": "^4.1.1", | ||
"@travetto/terminal": "^4.1.1", | ||
"@travetto/worker": "^4.1.1", | ||
"@travetto/yaml": "^4.1.1" | ||
"@travetto/base": "^5.0.0-rc.0", | ||
"@travetto/registry": "^5.0.0-rc.0", | ||
"@travetto/terminal": "^5.0.0-rc.0", | ||
"@travetto/worker": "^5.0.0-rc.0", | ||
"yaml": "^2.4.5" | ||
}, | ||
"peerDependencies": { | ||
"@travetto/cli": "^4.1.1", | ||
"@travetto/transformer": "^4.1.1" | ||
"@travetto/cli": "^5.0.0-rc.0", | ||
"@travetto/transformer": "^5.0.0-rc.0" | ||
}, | ||
@@ -41,0 +41,0 @@ "peerDependenciesMeta": { |
import assert from 'node:assert'; | ||
import { RuntimeIndex } from '@travetto/manifest'; | ||
import { ObjectUtil, AppError, ClassInstance, Class } from '@travetto/base'; | ||
import { AppError, ClassInstance, Class } from '@travetto/base'; | ||
@@ -11,8 +11,2 @@ import { ThrowableError, TestConfig, Assertion } from '../model/test'; | ||
declare module 'assert' { | ||
interface AssertionError { | ||
toJSON(): Record<string, unknown>; | ||
} | ||
} | ||
type StringFields<T> = { | ||
@@ -34,2 +28,3 @@ [K in Extract<keyof T, string>]: | ||
static check(assertion: CaptureAssert, positive: boolean, ...args: unknown[]): void { | ||
/* eslint-disable @typescript-eslint/consistent-type-assertions */ | ||
assertion.file = RuntimeIndex.getSourceFile(assertion.file); | ||
@@ -51,6 +46,4 @@ | ||
if (args.length > 1) { | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
[assertion.actual, assertion.expected, assertion.message, assertion.operator] = args as [unknown, unknown, string, string]; | ||
} else { | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
[assertion.message] = args as [string]; | ||
@@ -61,6 +54,4 @@ } | ||
if (typeof args[1] !== 'string') { | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
[, assertion.expected, assertion.message] = args as [unknown, unknown, string]; | ||
} else { | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
[, assertion.message] = args as [unknown, string]; | ||
@@ -70,3 +61,2 @@ } | ||
fn = assertion.operator = 'ok'; | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
[assertion.actual, assertion.message] = args as [unknown, string]; | ||
@@ -77,13 +67,9 @@ assertion.expected = { toClean: (): string => positive ? 'truthy' : 'falsy' }; | ||
assertion.operator = fn; | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
[assertion.actual, assertion.expected, assertion.message] = args as [unknown, unknown, string]; | ||
} else if (fn === 'instanceof') { | ||
assertion.operator = fn; | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
[assertion.actual, assertion.expected, assertion.message] = args as [unknown, unknown, string]; | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
assertion.actual = (assertion.actual as ClassInstance)?.constructor; | ||
} else { // Handle unknown | ||
assertion.operator = fn ?? ''; | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
[assertion.actual, assertion.expected, assertion.message] = args as [unknown, unknown, string]; | ||
@@ -102,3 +88,2 @@ } | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
const [actual, expected, message] = args as [unknown, unknown, string]; | ||
@@ -108,22 +93,12 @@ | ||
switch (fn) { | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
case 'includes': assertFn((actual as unknown[]).includes(expected), message); break; | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
case 'test': assertFn((expected as RegExp).test(actual as string), message); break; | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
case 'instanceof': assertFn(actual instanceof (expected as Class), message); break; | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
case 'in': assertFn((actual as string) in (expected as object), message); break; | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
case 'lessThan': assertFn((actual as number) < (expected as number), message); break; | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
case 'lessThanEqual': assertFn((actual as number) <= (expected as number), message); break; | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
case 'greaterThan': assertFn((actual as number) > (expected as number), message); break; | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
case 'greaterThanEqual': assertFn((actual as number) >= (expected as number), message); break; | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
case 'ok': assertFn(...args as [unknown, string]); break; | ||
default: | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
if (fn && assert[fn as keyof typeof assert]) { // Assert call | ||
@@ -133,3 +108,2 @@ if (/not/i.test(fn)) { | ||
} | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
assert[fn as 'ok'].apply(null, args as [boolean, string | undefined]); | ||
@@ -156,2 +130,3 @@ } | ||
} | ||
/* eslint-enable @typescript-eslint/consistent-type-assertions */ | ||
} | ||
@@ -218,3 +193,3 @@ | ||
): void { | ||
if (ObjectUtil.isPrimitive(err)) { | ||
if (!(err instanceof Error)) { | ||
err = new Error(`${err}`); | ||
@@ -226,3 +201,4 @@ } | ||
if (positive) { | ||
missed = new AppError('Error thrown, but expected no errors', 'general', {}, err.stack); | ||
missed = new AppError('Error thrown, but expected no errors'); | ||
missed.stack = err.stack; | ||
} | ||
@@ -259,4 +235,4 @@ | ||
if (!positive) { | ||
if (!ObjectUtil.isPrimitive(shouldThrow)) { | ||
shouldThrow = shouldThrow?.name; | ||
if (typeof shouldThrow === 'function') { | ||
shouldThrow = shouldThrow.name; | ||
} | ||
@@ -298,4 +274,4 @@ throw (missed = new AppError(`No error thrown, but expected ${shouldThrow ?? 'an error'}`)); | ||
if (!positive) { | ||
if (!ObjectUtil.isPrimitive(shouldThrow)) { | ||
shouldThrow = shouldThrow?.name; | ||
if (typeof shouldThrow === 'function') { | ||
shouldThrow = shouldThrow.name; | ||
} | ||
@@ -302,0 +278,0 @@ throw (missed = new AppError(`No error thrown, but expected ${shouldThrow ?? 'an error'} `)); |
import util from 'node:util'; | ||
import { path, RuntimeIndex, RuntimeContext } from '@travetto/manifest'; | ||
import { Class, ClassInstance, ObjectUtil } from '@travetto/base'; | ||
@@ -22,18 +21,21 @@ import { TestConfig, Assertion, TestResult } from '../model/test'; | ||
static cleanValue(val: unknown): unknown { | ||
if (isCleanable(val)) { | ||
return val.toClean(); | ||
} else if (val === null || val === undefined | ||
|| (!(val instanceof RegExp) && ObjectUtil.isPrimitive(val)) | ||
|| ObjectUtil.isPlainObject(val) || Array.isArray(val) | ||
) { | ||
return JSON.stringify(val); | ||
} else { | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
const subV = val as (Class | ClassInstance); | ||
if (subV.Ⲑid || !subV.constructor || (!subV.constructor.Ⲑid && ObjectUtil.isFunction(subV))) { // If a function, show name | ||
return subV.name; | ||
} else { // Else inspect | ||
return util.inspect(val, false, 1).replace(/\n/g, ' '); | ||
switch (typeof val) { | ||
case 'object': { | ||
if (isCleanable(val)) { | ||
return val.toClean(); | ||
} else if (val === null || val.constructor === Object || Array.isArray(val) || val instanceof Date) { | ||
return JSON.stringify(val); | ||
} | ||
break; | ||
} | ||
case 'undefined': case 'string': case 'number': case 'bigint': case 'boolean': return JSON.stringify(val); | ||
case 'function': { | ||
if (val.Ⲑid || !val.constructor) { | ||
return val.name; | ||
} | ||
break; | ||
} | ||
} | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
return util.inspect(val, false, 1).replace(/\n/g, ' '); | ||
} | ||
@@ -40,0 +42,0 @@ |
@@ -1,2 +0,2 @@ | ||
import { TypedObject, ObjectUtil } from '@travetto/base'; | ||
import { AppError, TypedObject } from '@travetto/base'; | ||
@@ -28,3 +28,3 @@ import { TestEvent, } from '../model/event'; | ||
error.name = e.name; | ||
if (ObjectUtil.hasToJSON(e)) { | ||
if (e instanceof AppError) { | ||
Object.assign(error, e.toJSON()); | ||
@@ -48,4 +48,7 @@ } | ||
for (const k of TypedObject.keys<{ name: string }>(e)) { | ||
err[k] = e[k]; | ||
for (const k of TypedObject.keys(e)) { | ||
if (k === '$') { | ||
continue; | ||
} | ||
err[k] = e[k]!; | ||
} | ||
@@ -52,0 +55,0 @@ err.message = e.message; |
import { RuntimeIndex } from '@travetto/manifest'; | ||
import { Terminal } from '@travetto/terminal'; | ||
import { ObjectUtil, TimeUtil } from '@travetto/base'; | ||
import { YamlUtil } from '@travetto/yaml'; | ||
import { AppError, TimeUtil } from '@travetto/base'; | ||
import { stringify } from 'yaml'; | ||
@@ -47,3 +47,3 @@ import { TestEvent } from '../../model/event'; | ||
const lineLength = this.#terminal.width - 5; | ||
let body = YamlUtil.serialize(obj, { wordwrap: lineLength }); | ||
let body = stringify(obj, { lineWidth: lineLength }); | ||
body = body.split('\n').map(x => ` ${x}`).join('\n'); | ||
@@ -105,5 +105,5 @@ this.log(`---\n${this.#enhancer.objectInspect(body)}\n...`); | ||
if (test.status === 'failed') { | ||
if (test.error?.stack && !test.error.stack.includes('AssertionError')) { | ||
if (test.error && test.error.name !== 'AssertionError') { | ||
const err = ErrorUtil.deserializeError(test.error); | ||
this.logMeta({ error: ObjectUtil.hasToJSON(err) ? err.toJSON() : err }); | ||
this.logMeta({ error: err instanceof AppError ? err.toJSON() : err }); | ||
} | ||
@@ -132,4 +132,3 @@ } | ||
for (const err of summary.errors) { | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
this.log(this.#enhancer.failure(ObjectUtil.hasToJSON(err) ? err.toJSON() as string : `${err}`)); | ||
this.log(this.#enhancer.failure(err instanceof AppError ? JSON.stringify(err.toJSON(), null, 2) : `${err}`)); | ||
} | ||
@@ -147,5 +146,5 @@ } | ||
this.#enhancer.total(summary.skipped), | ||
`# (Total Test Time: ${TimeUtil.prettyDelta(summary.duration)}, Total Run Time: ${TimeUtil.prettyDeltaSinceTime(this.#start)})` | ||
`# (Total Test Time: ${TimeUtil.asClock(summary.duration)}, Total Run Time: ${TimeUtil.asClock(Date.now() - this.#start)})` | ||
].join(' ')); | ||
} | ||
} |
import { Writable } from 'node:stream'; | ||
import { YamlUtil } from '@travetto/yaml'; | ||
import { stringify } from 'yaml'; | ||
@@ -36,3 +36,3 @@ import { TestEvent } from '../../model/event'; | ||
if (Object.keys(obj).length) { | ||
let body = YamlUtil.serialize(obj); | ||
let body = stringify(obj); | ||
body = body.split('\n').map(x => ` ${x}`).join('\n'); | ||
@@ -39,0 +39,0 @@ return `<![CDATA[\n${body}\n]]>`; |
import util from 'node:util'; | ||
import { ConsoleEvent, ConsoleManager } from '@travetto/base'; | ||
import { ConsoleEvent, ConsoleListener, ConsoleManager } from '@travetto/base'; | ||
@@ -9,12 +9,14 @@ /** | ||
*/ | ||
export class ConsoleCapture { | ||
export class ConsoleCapture implements ConsoleListener { | ||
static #listener: ConsoleListener = ConsoleManager.get(); | ||
static out: Record<string, string[]>; | ||
out: Record<string, string[]>; | ||
static start(): void { | ||
start(): this { | ||
this.out = {}; | ||
ConsoleManager.set(this); | ||
return this; | ||
} | ||
static onLog({ level, args }: ConsoleEvent): void { | ||
log({ level, args }: ConsoleEvent): void { | ||
(this.out[level] = this.out[level] ?? []).push( | ||
@@ -27,8 +29,8 @@ args | ||
static end(): Record<string, string> { | ||
end(): Record<string, string> { | ||
const ret = this.out ?? {}; | ||
this.out = {}; | ||
ConsoleManager.clear(); | ||
ConsoleManager.set(ConsoleCapture.#listener); | ||
return Object.fromEntries(Object.entries(ret).map(([k, v]) => [k, v.join('\n')])); | ||
} | ||
} |
import { AssertionError } from 'node:assert'; | ||
import path from 'node:path'; | ||
import { path, RuntimeIndex, RuntimeContext } from '@travetto/manifest'; | ||
import { Env, Util } from '@travetto/base'; | ||
import { RuntimeIndex, RuntimeContext } from '@travetto/manifest'; | ||
import { Env, TimeUtil } from '@travetto/base'; | ||
import { Barrier, ExecutionError } from '@travetto/worker'; | ||
@@ -15,6 +16,6 @@ | ||
import { TestPhaseManager } from './phase'; | ||
import { PromiseCapture } from './promise'; | ||
import { PromiseCapturer } from './promise'; | ||
import { AssertUtil } from '../assert/util'; | ||
const TEST_TIMEOUT = Env.TRV_TEST_TIMEOUT.time ?? 5000; | ||
const TEST_TIMEOUT = TimeUtil.fromValue(Env.TRV_TEST_TIMEOUT.val) ?? 5000; | ||
@@ -33,18 +34,17 @@ /** | ||
const suite = SuiteRegistry.get(test.class); | ||
const promCleanup = Util.resolvablePromise(); | ||
// Ensure all the criteria below are satisfied before moving forward | ||
const barrier = new Barrier(test.timeout || TEST_TIMEOUT, true) | ||
.add(promCleanup, true) // If not timeout or unhandled, ensure all promises are cleaned up | ||
.add(async () => { | ||
const env = process.env; | ||
process.env = { ...env }; // Created an isolated environment | ||
const pCap = new PromiseCapturer(); | ||
try { | ||
PromiseCapture.start(); // Listen for all promises to detect any unfinished, only start once method is invoked | ||
process.env = { ...env }; // Created an isolated environment | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
await (suite.instance as Record<string, Function>)[test.methodName](); // Run | ||
await pCap.run(() => | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
(suite.instance as Record<string, Function>)[test.methodName]() | ||
); | ||
} finally { | ||
process.env = env; // Restore | ||
PromiseCapture.stop().then(() => Util.queueMacroTask().then(promCleanup.resolve), promCleanup.reject); | ||
} | ||
@@ -142,3 +142,3 @@ }); | ||
ConsoleCapture.start(); // Capture all output from transpiled code | ||
const consoleCapture = new ConsoleCapture().start(); // Capture all output from transpiled code | ||
@@ -164,3 +164,3 @@ // Run method and get result | ||
status: error ? 'failed' : 'passed', | ||
output: ConsoleCapture.end(), | ||
output: consoleCapture.end(), | ||
assertions: getAssertions(), | ||
@@ -167,0 +167,0 @@ duration: Date.now() - startTime, |
import { Barrier } from '@travetto/worker'; | ||
import { Env } from '@travetto/base'; | ||
import { Env, TimeUtil } from '@travetto/base'; | ||
@@ -11,3 +11,3 @@ import { TestConsumer } from '../consumer/types'; | ||
const TEST_PHASE_TIMEOUT = Env.TRV_TEST_PHASE_TIMEOUT.time ?? 15000; | ||
const TEST_PHASE_TIMEOUT = TimeUtil.fromValue(Env.TRV_TEST_PHASE_TIMEOUT.val) ?? 15000; | ||
@@ -14,0 +14,0 @@ /** |
@@ -0,72 +1,49 @@ | ||
import { createHook, executionAsyncId } from 'node:async_hooks'; | ||
import { ExecutionError } from '@travetto/worker'; | ||
import { Util } from '@travetto/base'; | ||
const og = Promise; | ||
declare global { | ||
interface Promise<T> { | ||
status: 'ok' | 'error'; | ||
} | ||
} | ||
/** | ||
* Promise stub to track creation | ||
* Promise watcher, to catch any unfinished promises | ||
*/ | ||
function Wrapped(this: Promise<unknown>, ex: (res: (v: unknown) => unknown, rej?: (err: unknown) => unknown) => void): Promise<unknown> { | ||
const prom = new og(ex); | ||
this.then = prom.then.bind(prom); | ||
this.catch = prom.catch.bind(prom); | ||
this.finally = prom.finally.bind(prom); | ||
this.then(() => prom.status = 'ok', | ||
() => prom.status = 'error'); | ||
export class PromiseCapturer { | ||
#pending = new Map<number, Promise<unknown>>(); | ||
#id: number = 0; | ||
if (PromiseCapture.pending) { | ||
PromiseCapture.pending.push(prom); | ||
#init(id: number, type: string, triggerId: number, resource: unknown): void { | ||
if (this.#id && type === 'PROMISE' && triggerId === this.#id) { | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
this.#pending.set(id, resource as Promise<unknown>); | ||
} | ||
} | ||
return this; | ||
} | ||
Wrapped.allSettled = Promise.allSettled.bind(Promise); | ||
Wrapped.race = Promise.race.bind(Promise); | ||
Wrapped.all = Promise.all.bind(Promise); | ||
Wrapped.resolve = Promise.resolve.bind(Promise); | ||
Wrapped.reject = Promise.reject.bind(Promise); | ||
#promiseResolve(asyncId: number): void { | ||
this.#pending.delete(asyncId); | ||
} | ||
/** | ||
* Promise watcher, to catch any unfinished promises | ||
*/ | ||
export class PromiseCapture { | ||
static pending: Promise<unknown>[]; | ||
async run(op: () => Promise<unknown> | unknown): Promise<unknown> { | ||
const hook = createHook({ | ||
init: (...args) => this.#init(...args), | ||
promiseResolve: (id) => this.#promiseResolve(id) | ||
}); | ||
/** | ||
* Swap method and track progress | ||
*/ | ||
static start(): void { | ||
this.pending = []; | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
global.Promise = Wrapped as unknown as typeof Promise; | ||
} | ||
hook.enable(); | ||
/** | ||
* Wait for all promises to resolve | ||
*/ | ||
static async resolvePending(pending: Promise<unknown>[]): Promise<void> { | ||
if (pending.length) { | ||
let final: Error | undefined; | ||
console.debug('Resolving', { pending: this.pending.length }); | ||
await Promise.all(pending).catch(err => final = err); | ||
// If any return in an error, make that the final result | ||
throw new ExecutionError(`Pending promises: ${pending.length}`, final?.stack); | ||
await Util.queueMacroTask(); | ||
this.#id = executionAsyncId(); | ||
try { | ||
const res = await op(); | ||
let i = 5; // Wait upto 5 macro tasks before continuing | ||
while (this.#pending.size) { | ||
await Util.queueMacroTask(); | ||
i -= 1; | ||
if (i === 0) { | ||
throw new ExecutionError(`Pending promises: ${this.#pending.size}`); | ||
} | ||
} | ||
return res; | ||
} finally { | ||
hook.disable(); | ||
} | ||
} | ||
/** | ||
* Stop the capture | ||
*/ | ||
static stop(): Promise<void> { | ||
console.debug('Stopping', { pending: this.pending.length }); | ||
// Restore the promise | ||
global.Promise = og; | ||
return this.resolvePending(this.pending.filter(x => x.status === undefined)); | ||
} | ||
} |
@@ -1,2 +0,4 @@ | ||
import { RuntimeContext, RuntimeIndex, path } from '@travetto/manifest'; | ||
import path from 'node:path'; | ||
import { path as mp, RuntimeContext, RuntimeIndex } from '@travetto/manifest'; | ||
import { TimeUtil } from '@travetto/base'; | ||
@@ -24,3 +26,3 @@ import { WorkPool } from '@travetto/worker'; | ||
get patterns(): RegExp[] { | ||
return this.#state.args.map(x => new RegExp(path.toPosix(x))); | ||
return this.#state.args.map(x => new RegExp(mp.toPosix(x))); | ||
} | ||
@@ -41,6 +43,6 @@ | ||
await WorkPool.run( | ||
() => buildStandardTestManager(consumer), | ||
buildStandardTestManager.bind(null, consumer), | ||
files, | ||
{ | ||
idleTimeoutMillis: TimeUtil.timeToMs('10s'), | ||
idleTimeoutMillis: TimeUtil.asMillis(10, 's'), | ||
min: 1, | ||
@@ -47,0 +49,0 @@ max: this.#state.concurrency, |
import { RootRegistry, MethodSource } from '@travetto/registry'; | ||
import { WorkPool, WorkQueue } from '@travetto/worker'; | ||
import { RuntimeIndex } from '@travetto/manifest'; | ||
import { ObjectUtil } from '@travetto/base'; | ||
@@ -15,3 +14,3 @@ import { SuiteRegistry } from '../registry/suite'; | ||
function isRunEvent(ev: unknown): ev is RunEvent { | ||
return ObjectUtil.isPlainObject(ev) && 'type' in ev && typeof ev.type === 'string' && ev.type === 'run-test'; | ||
return typeof ev === 'object' && !!ev && 'type' in ev && typeof ev.type === 'string' && ev.type === 'run-test'; | ||
} | ||
@@ -92,3 +91,3 @@ | ||
await WorkPool.run( | ||
() => buildStandardTestManager(consumer), | ||
buildStandardTestManager.bind(null, consumer), | ||
itr, | ||
@@ -95,0 +94,0 @@ { |
@@ -5,4 +5,4 @@ import { FileLoader } from '@travetto/base'; | ||
constructor(modules: string[] = []) { | ||
super(['@#test/fixtures', ...['@', ...modules.flat()].map(x => `${x}#support/fixtures`)]); | ||
super(['@#test/fixtures', ...['@', ...modules.flat(), '@@'].map(x => `${x}#support/fixtures`)]); | ||
} | ||
} |
import { createWriteStream } from 'node:fs'; | ||
import { RuntimeContext } from '@travetto/manifest'; | ||
import { ConsoleManager, Env, TimeUtil, Util } from '@travetto/base'; | ||
import { ConsoleManager, Env, Util } from '@travetto/base'; | ||
import { ChildCommChannel } from '@travetto/worker'; | ||
@@ -18,2 +18,4 @@ | ||
#done = Util.resolvablePromise(); | ||
async #exec(op: () => Promise<unknown>, type: string): Promise<void> { | ||
@@ -40,5 +42,5 @@ try { | ||
const c = new console.Console({ stdout, inspectOptions: { depth: 4, colors: false } }); | ||
ConsoleManager.set({ onLog: (ev) => c[ev.level](process.pid, ...ev.args) }); | ||
ConsoleManager.set({ log: (ev) => c[ev.level](process.pid, ...ev.args) }); | ||
} else { | ||
ConsoleManager.set({ onLog: () => { } }); | ||
ConsoleManager.set({ log: () => { } }); | ||
} | ||
@@ -54,3 +56,3 @@ | ||
await Util.blockingTimeout(TimeUtil.timeToMs('10m')); | ||
await this.#done.promise; | ||
} | ||
@@ -86,9 +88,13 @@ | ||
await new Runner({ | ||
format: 'exec', | ||
mode: 'single', | ||
args: [event.file!, event.class!, event.method!], | ||
concurrency: 1 | ||
}).run(); | ||
try { | ||
await new Runner({ | ||
format: 'exec', | ||
mode: 'single', | ||
args: [event.file!, event.class!, event.method!], | ||
concurrency: 1 | ||
}).run(); | ||
} finally { | ||
this.#done.resolve(); | ||
} | ||
} | ||
} |
@@ -5,3 +5,3 @@ import { fork } from 'node:child_process'; | ||
import { Env } from '@travetto/base'; | ||
import { ParentCommChannel, Worker } from '@travetto/worker'; | ||
import { ParentCommChannel } from '@travetto/worker'; | ||
@@ -13,79 +13,63 @@ import { Events, RunEvent } from './types'; | ||
let i = 0; | ||
function buildEvent(ev: string): RunEvent { | ||
if (ev.includes('#')) { | ||
const [file, cls, method] = ev.split('#'); | ||
return { file, class: cls, method }; | ||
} else { | ||
return { file: ev }; | ||
} | ||
} | ||
/** | ||
* Produce a handler for the child worker | ||
*/ | ||
export function buildStandardTestManager(consumer: TestConsumer): Worker<string> { | ||
/** | ||
* Spawn a child | ||
*/ | ||
return { | ||
id: i += 1, | ||
active: true, | ||
async destroy(): Promise<void> { }, | ||
async execute(file: string): Promise<void> { | ||
export async function buildStandardTestManager(consumer: TestConsumer, file: string): Promise<void> { | ||
process.send?.({ type: 'log', message: `Worker Executing ${file}` }); | ||
process.send?.({ type: 'log', message: `Worker Executing ${file}` }); | ||
let event: RunEvent; | ||
if (file.includes('#')) { | ||
const [f, cls, method] = file.split('#'); | ||
event = { file: f, class: cls, method }; | ||
} else { | ||
event = { file }; | ||
} | ||
const event = buildEvent(file); | ||
const { module } = RuntimeIndex.getEntry(event.file!)!; | ||
const cwd = RuntimeIndex.getModule(module)!.sourcePath; | ||
const { module } = RuntimeIndex.getEntry(event.file!)!; | ||
const cwd = RuntimeIndex.getModule(module)!.sourcePath; | ||
const channel = new ParentCommChannel<TestEvent & { error?: Error }>( | ||
fork( | ||
RuntimeIndex.resolveFileImport('@travetto/cli/support/entry.trv'), ['test:child'], | ||
{ | ||
cwd, | ||
env: { | ||
...process.env, | ||
...Env.TRV_MANIFEST.export(RuntimeIndex.getModule(module)!.outputPath), | ||
...Env.TRV_QUIET.export(true) | ||
}, | ||
stdio: ['ignore', 'ignore', 2, 'ipc'] | ||
} | ||
) | ||
); | ||
const channel = new ParentCommChannel<TestEvent & { error?: Error }>( | ||
fork( | ||
RuntimeIndex.resolveFileImport('@travetto/cli/support/entry.trv'), ['test:child'], | ||
{ | ||
cwd, | ||
env: { | ||
...process.env, | ||
...Env.TRV_MANIFEST.export(RuntimeIndex.getModule(module)!.outputPath), | ||
...Env.TRV_QUIET.export(true) | ||
}, | ||
stdio: ['ignore', 'ignore', 2, 'ipc'] | ||
} | ||
) | ||
); | ||
await channel.once(Events.READY); // Wait for the child to be ready | ||
await channel.send(Events.INIT); // Initialize | ||
await channel.once(Events.INIT_COMPLETE); // Wait for complete | ||
await channel.once(Events.READY); // Wait for the child to be ready | ||
await channel.send(Events.INIT); // Initialize | ||
await channel.once(Events.INIT_COMPLETE); // Wait for complete | ||
channel.on('*', async ev => { | ||
try { | ||
await consumer.onEvent(ev); // Connect the consumer with the event stream from the child | ||
} catch { | ||
// Do nothing | ||
} | ||
}); | ||
channel.on('*', async ev => { | ||
try { | ||
await consumer.onEvent(ev); // Connect the consumer with the event stream from the child | ||
} catch { | ||
// Do nothing | ||
} | ||
}); | ||
// Listen for child to complete | ||
const complete = channel.once(Events.RUN_COMPLETE); | ||
// Start test | ||
channel.send(Events.RUN, event); | ||
// Listen for child to complete | ||
const complete = channel.once(Events.RUN_COMPLETE); | ||
// Start test | ||
channel.send(Events.RUN, event); | ||
// Wait for complete | ||
const { error } = await complete; | ||
// Wait for complete | ||
const { error } = await complete; | ||
// Kill on complete | ||
await channel.destroy(); | ||
// Kill on complete | ||
await channel.destroy(); | ||
process.send?.({ type: 'log', message: `Worker Finished ${file}` }); | ||
process.send?.({ type: 'log', message: `Worker Finished ${file}` }); | ||
// If we received an error, throw it | ||
if (error) { | ||
throw ErrorUtil.deserializeError(error); | ||
} | ||
}, | ||
}; | ||
// If we received an error, throw it | ||
if (error) { | ||
throw ErrorUtil.deserializeError(error); | ||
} | ||
} |
import { EventEmitter } from 'node:events'; | ||
import { Env, ExecUtil } from '@travetto/base'; | ||
import { Env } from '@travetto/base'; | ||
import { CliCommand } from '@travetto/cli'; | ||
@@ -20,3 +20,3 @@ | ||
async main(): Promise<void> { | ||
ExecUtil.exitOnDisconnect(); | ||
process.once('disconnect', () => process.exit()); | ||
const { TestChildWorker } = await import('../src/worker/child'); | ||
@@ -23,0 +23,0 @@ return new TestChildWorker().activate(); |
import { EventEmitter } from 'node:events'; | ||
import fs from 'node:fs/promises'; | ||
import path from 'node:path'; | ||
import { path } from '@travetto/manifest'; | ||
import { Env } from '@travetto/base'; | ||
@@ -6,0 +6,0 @@ import { CliCommandShape, CliCommand, CliValidationError } from '@travetto/cli'; |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
111919
2971
1
+ Addedyaml@^2.4.5
+ Added@travetto/base@5.0.0-rc.1(transitive)
+ Added@travetto/cli@5.0.16(transitive)
+ Added@travetto/manifest@5.0.9(transitive)
+ Added@travetto/registry@5.0.13(transitive)
+ Added@travetto/runtime@5.0.13(transitive)
+ Added@travetto/schema@5.0.13(transitive)
+ Added@travetto/terminal@5.0.15(transitive)
+ Added@travetto/transformer@5.0.10(transitive)
+ Added@travetto/worker@5.0.15(transitive)
+ Added@types/node@22.10.1(transitive)
+ Addedundici-types@6.20.0(transitive)
+ Addedyaml@2.6.1(transitive)
- Removed@travetto/yaml@^4.1.1
- Removed@travetto/base@4.1.2(transitive)
- Removed@travetto/cli@4.1.2(transitive)
- Removed@travetto/manifest@4.1.0(transitive)
- Removed@travetto/registry@4.1.2(transitive)
- Removed@travetto/schema@4.1.1(transitive)
- Removed@travetto/terminal@4.1.1(transitive)
- Removed@travetto/transformer@4.1.2(transitive)
- Removed@travetto/worker@4.1.1(transitive)
- Removed@travetto/yaml@4.1.1(transitive)
Updated@travetto/base@^5.0.0-rc.0
Updated@travetto/worker@^5.0.0-rc.0