UNPKG

@travetto/test

Version:

Declarative test framework

138 lines (117 loc) 4.66 kB
import { Util, AsyncQueue } from '@travetto/runtime'; import { StyleUtil, Terminal, TerminalUtil } from '@travetto/terminal'; import type { TestEvent } from '../../model/event'; import type { TestResult } from '../../model/test'; import type { SuitesSummary, TestConsumerShape, TestRunState } from '../types'; import { TestConsumer } from '../registry'; import { TapEmitter } from './tap'; import { CONSOLE_ENHANCER, TestResultsEnhancer } from '../enhancer'; type Result = { key: string; duration: number; tests: number; }; /** * Streamed summary results */ @TestConsumer() export class TapSummaryEmitter implements TestConsumerShape { #timings = new Map([ ['file', new Map<string, Result>()], ['module', new Map<string, Result>()], ['suite', new Map<string, Result>()], ['test', new Map<string, Result>()], ] as const); #terminal: Terminal; #results = new AsyncQueue<TestResult>(); #progress: Promise<unknown> | undefined; #consumer: TapEmitter; #enhancer: TestResultsEnhancer; #options?: Record<string, unknown>; constructor(terminal: Terminal = new Terminal(process.stderr)) { this.#terminal = terminal; this.#enhancer = CONSOLE_ENHANCER; this.#consumer = new TapEmitter(this.#terminal, this.#enhancer); } setOptions(options?: Record<string, unknown>): Promise<void> | void { this.#options = options; } async onStart(state: TestRunState): Promise<void> { this.#consumer.onStart(); let failed = 0; let skipped = 0; let completed = 0; const success = StyleUtil.getStyle({ text: '#e5e5e5', background: '#026020' }); // White on dark green const fail = StyleUtil.getStyle({ text: '#e5e5e5', background: '#8b0000' }); // White on dark red this.#progress = this.#terminal.streamToBottom( Util.mapAsyncItr( this.#results, (value, idx) => { failed += (value.status === 'failed' ? 1 : 0); skipped += (value.status === 'skipped' ? 1 : 0); completed += (value.status !== 'skipped' ? 1 : 0); return { value: `Tests %idx/%total [${failed} failed, ${skipped} skipped] -- ${value.classId}`, total: state.testCount, idx: completed }; }, TerminalUtil.progressBarUpdater(this.#terminal, { style: () => ({ complete: failed ? fail : success }) }) ), { minDelay: 100 } ); } onEvent(ev: TestEvent): void { if (ev.type === 'test' && ev.phase === 'after') { const { test } = ev; this.#results.add(test); if (test.status === 'failed') { this.#consumer.onEvent(ev); } const tests = this.#timings.get('test')!; tests.set(`${ev.test.classId}/${ev.test.methodName}`, { key: `${ev.test.classId}/${ev.test.methodName}`, duration: test.duration, tests: 1 }); } else if (ev.type === 'suite' && ev.phase === 'after') { const [module] = ev.suite.classId.split(/:/); const [file] = ev.suite.classId.split(/#/); const modules = this.#timings.get('module')!; const files = this.#timings.get('file')!; const suites = this.#timings.get('suite')!; if (!modules!.has(module)) { modules.set(module, { key: module, duration: 0, tests: 0 }); } if (!files.has(file)) { files.set(file, { key: file, duration: 0, tests: 0 }); } suites.set(ev.suite.classId, { key: ev.suite.classId, duration: ev.suite.duration, tests: ev.suite.tests.length }); files.get(file)!.duration += ev.suite.duration; files.get(file)!.tests += ev.suite.tests.length; modules.get(module)!.duration += ev.suite.duration; modules.get(module)!.tests += ev.suite.tests.length; } } /** * Summarize all results */ async onSummary(summary: SuitesSummary): Promise<void> { this.#results.close(); await this.#progress; await this.#consumer.onSummary?.(summary); if (this.#options?.timings) { const count = +(this.#options?.count ?? 5); await this.#consumer.log('\n---'); for (const [title, results] of [...this.#timings.entries()].sort((a, b) => a[0].localeCompare(b[0]))) { await this.#consumer.log(`${this.#enhancer.suiteName(`Top ${count} slowest ${title}s`)}: `); const top10 = [...results.values()].sort((a, b) => b.duration - a.duration).slice(0, count); for (const x of top10) { console.log(` * ${this.#enhancer.testName(x.key)} - ${this.#enhancer.total(x.duration)}ms / ${this.#enhancer.total(x.tests)} tests`); } await this.#consumer.log(''); } await this.#consumer.log('...'); } } }