UNPKG

@travetto/test

Version:

Declarative test framework

108 lines (99 loc) 3.36 kB
import { spawn } from 'node:child_process'; import { createReadStream } from 'node:fs'; import fs from 'node:fs/promises'; import readline from 'node:readline/promises'; import path from 'node:path'; import { Env, ExecUtil, ShutdownManager, Util, RuntimeIndex, Runtime } from '@travetto/runtime'; import { TestConfig, TestRun } from '../model/test'; /** * Simple Test Utilities */ export class RunnerUtil { /** * Add 50 ms to the shutdown to allow for buffers to output properly */ static registerCleanup(scope: string): void { ShutdownManager.onGracefulShutdown(() => Util.blockingTimeout(50), `test.${scope}.bufferOutput`); } /** * Determine if a given file path is a valid test file */ 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; } /** * Find all valid test files given the globs */ static async* getTestImports(globs?: string[]): AsyncIterable<string> { const all = RuntimeIndex.find({ module: m => m.roles.includes('test') || m.roles.includes('std'), folder: f => f === 'test', file: f => f.role === 'test' }); // Collect globs if (globs?.length) { const allFiles = new Map(all.map(x => [x.sourceFile, x])); for await (const item of fs.glob(globs)) { const src = Runtime.workspaceRelative(path.resolve(item)); const match = allFiles.get(src); if (match && await this.isTestFile(match.sourceFile)) { yield match.import; } } } else { for await (const match of all) { if (await this.isTestFile(match.sourceFile)) { yield match.import; } } } } /** * Get count of tests for a given set of globs * @param globs * @returns */ static async getTestDigest(globs: string[] = ['**/*.ts'], tags?: string[]): Promise<TestConfig[]> { const countRes = await ExecUtil.getResult( spawn('npx', ['trv', 'test:digest', '-o', 'json', ...globs], { env: { ...process.env, ...Env.FORCE_COLOR.export(0), ...Env.NO_COLOR.export(true) } }), { catch: true } ); if (!countRes.valid) { throw new Error(countRes.stderr); } const testFilter = tags?.length ? Util.allowDeny<string, [TestConfig]>( tags, rule => rule, (rule, core) => core.tags?.includes(rule) ?? false ) : ((): boolean => true); const res: TestConfig[] = countRes.valid ? JSON.parse(countRes.stdout) : []; return res.filter(testFilter); } /** * Get run events */ static getTestRuns(tests: TestConfig[]): TestRun[] { const events = tests.reduce((acc, test) => { if (!acc.has(test.classId)) { acc.set(test.classId, { import: test.import, classId: test.classId, methodNames: [], runId: Util.uuid() }); } acc.get(test.classId)!.methodNames!.push(test.methodName); return acc; }, new Map<string, TestRun>()); return [...events.values()]; } }