mono/packages/xblox/tests/test-commons.ts
2026-04-07 19:41:32 +02:00

110 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Shared paths, performance measurement, and optional JSON reports for vitest.
*/
import { mkdirSync, writeFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
/** Directory of this file (`tests/`). */
export const testsDir = __dirname;
export const fixturesDir = path.join(testsDir, 'fixtures');
/** Default JSON output: `packages/xblox/reports/perf-latest.json` (gitignored). */
export const defaultPerfReportPath = path.join(testsDir, '..', 'reports', 'perf-latest.json');
export function fixture(...parts: string[]): string {
return path.join(fixturesDir, ...parts);
}
// ——— performance ———
export type PerfEntry = {
/** Logical label (e.g. suite step name). */
name: string;
/** Duration in milliseconds (`performance.now()` delta). */
ms: number;
/** ISO timestamp when the measurement finished. */
at: string;
};
/** When set to `1`, `PerfReporter.writeJsonFile()` writes to disk (see `npm run test:perf`). */
export function isPerfReportEnabled(): boolean {
return process.env.XBLOX_PERF_REPORT === '1';
}
/**
* Measure sync or async work; returns the function result and duration.
* Does not record to a report — use `PerfReporter.measure` for that.
*/
export async function measureMs<T>(fn: () => T | Promise<T>): Promise<{ result: T; ms: number }> {
const t0 = performance.now();
const result = await fn();
const ms = performance.now() - t0;
return { result, ms };
}
/**
* Collect timings for a test file or suite. Call `printSummary()` in `afterAll`,
* and optionally `writeJsonFile()` when `XBLOX_PERF_REPORT=1`.
*/
export class PerfReporter {
private readonly entries: PerfEntry[] = [];
get snapshot(): readonly PerfEntry[] {
return [...this.entries];
}
/** Run `fn`, record duration under `name`, return `fn`s result. */
async measure<T>(name: string, fn: () => T | Promise<T>): Promise<T> {
const { result, ms } = await measureMs(fn);
this.entries.push({
name,
ms,
at: new Date().toISOString(),
});
return result;
}
/** Log a compact table to stdout (Vitest / CI). */
printSummary(title = 'xblox perf'): void {
if (this.entries.length === 0) {
return;
}
const rows = this.entries.map((e) => ({
name: e.name,
ms: Number(e.ms.toFixed(3)),
at: e.at,
}));
console.info(`\n[${title}]`);
console.table(rows);
const total = this.entries.reduce((s, e) => s + e.ms, 0);
console.info(`[${title}] total: ${total.toFixed(3)} ms\n`);
}
/**
* Write `reports/perf-latest.json` (or `path`). Safe to call from `afterAll`.
* Set env `XBLOX_PERF_REPORT=1` to enable writes; otherwise no-op (avoids churn in CI).
*/
writeJsonFile(filePath: string = defaultPerfReportPath): string | undefined {
if (!isPerfReportEnabled()) {
return undefined;
}
mkdirSync(path.dirname(filePath), { recursive: true });
const payload = {
generatedAt: new Date().toISOString(),
entries: this.entries.map((e) => ({
name: e.name,
ms: e.ms,
at: e.at,
})),
totalMs: this.entries.reduce((s, e) => s + e.ms, 0),
};
writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
return filePath;
}
}