@travetto/test
Version:
Declarative test framework
100 lines (88 loc) • 3.24 kB
text/typescript
import { RootRegistry, MethodSource } from '@travetto/registry';
import { WorkPool } from '@travetto/worker';
import { AsyncQueue, Runtime, RuntimeIndex, castTo, describeFunction } from '@travetto/runtime';
import { SuiteRegistry } from '../registry/suite.ts';
import { buildStandardTestManager } from '../worker/standard.ts';
import { TestConsumerRegistry } from '../consumer/registry.ts';
import { CumulativeSummaryConsumer } from '../consumer/types/cumulative.ts';
import { TestRun } from '../model/test.ts';
import { RunnerUtil } from './util.ts';
import { TestReadyEvent, TestRemovedEvent } from '../worker/types.ts';
/**
* Test Watcher.
*
* Runs all tests on startup, and then listens for changes to run tests again
*/
export class TestWatcher {
/**
* Start watching all test files
*/
static async watch(format: string, runAllOnStart = true): Promise<void> {
console.debug('Listening for changes');
await SuiteRegistry.init();
SuiteRegistry.listen(RootRegistry);
await RootRegistry.init();
const events: TestRun[] = [];
if (runAllOnStart) {
const tests = await RunnerUtil.getTestDigest();
events.push(...RunnerUtil.getTestRuns(tests));
}
const itr = new AsyncQueue(events);
const consumer = new CumulativeSummaryConsumer(
await TestConsumerRegistry.getInstance({ consumer: format })
)
.withFilter(x => x.metadata?.partial !== true || x.type !== 'suite');
new MethodSource(RootRegistry).on(e => {
const [cls, method] = (e.prev ?? e.curr ?? []);
if (!cls || describeFunction(cls).abstract) {
return;
}
const classId = cls.Ⲑid;
if (!method) {
consumer.removeClass(classId);
return;
}
const conf = SuiteRegistry.getByClassAndMethod(cls, method)!;
if (e.type !== 'removing') {
if (conf) {
const run: TestRun = {
import: conf.import, classId: conf.classId, methodNames: [conf.methodName], metadata: { partial: true }
};
console.log('Triggering', run);
itr.add(run, true); // Shift to front
}
} else {
process.send?.({
type: 'removeTest',
methodNames: method?.name ? [method.name!] : undefined!,
method: method?.name,
classId,
import: Runtime.getImport(cls)
} satisfies TestRemovedEvent);
}
});
// If a file is changed, but doesn't emit classes, re-run whole file
RootRegistry.onNonClassChanges(imp => itr.add({ import: imp }));
process.on('message', ev => {
if (typeof ev === 'object' && ev && 'type' in ev && ev.type === 'run-test') {
console.log('Received message', ev);
// Legacy
if ('file' in ev && typeof ev.file === 'string') {
ev = { import: RuntimeIndex.getFromSource(ev.file)?.import! };
}
console.debug('Manually triggered', ev);
itr.add(castTo(ev), true);
}
});
process.send?.({ type: 'ready' } satisfies TestReadyEvent);
await WorkPool.run(
buildStandardTestManager.bind(null, consumer),
itr,
{
idleTimeoutMillis: 120000,
min: 2,
max: WorkPool.DEFAULT_SIZE
}
);
}
}