UNPKG

@dapplion/benchmark

Version:

Ensures that new code does not introduce performance regressions with CI. Tracks:

143 lines (142 loc) 4.87 kB
import fs from "node:fs"; import path from "node:path"; import { optsByRootSuite, optsMap, resultsByRootSuite } from "./globalState.js"; import { runBenchFn } from "./runBenchFn.js"; import { getRootSuite, getParentSuite } from "./utils.js"; const itBenchFn = function itBench(idOrOpts, fn) { // TODO: // Implement reporter // Implement grouping functionality // if (this.averageNs === null) this.averageNs = result.averageNs; // result.factor = result.averageNs / this.averageNs; let opts = coerceToOptsObj(idOrOpts, fn); // Apply mocha it opts const itFn = opts.only ? it.only : opts.skip ? it.skip : it; itFn(opts.id, async function () { const parent = getParentSuite(this); const optsParent = getOptsFromParent(parent); // Get results array from root suite const rootSuite = getRootSuite(parent); const results = resultsByRootSuite.get(rootSuite); const rootOpts = optsByRootSuite.get(rootSuite); if (!results || !rootOpts) throw Error("root suite not found"); opts = Object.assign({}, rootOpts, optsParent, opts); // Ensure bench id is unique if (results.has(opts.id)) { throw Error(`test titles must be unique, duplicated: '${opts.id}'`); } // Extend timeout if maxMs is set if (opts.timeoutBench !== undefined) { this.timeout(opts.timeoutBench); } else { const timeout = this.timeout(); if (opts.maxMs && opts.maxMs > timeout) { this.timeout(opts.maxMs * 1.5); } else if (opts.minMs && opts.minMs > timeout) { this.timeout(opts.minMs * 1.5); } } // Persist full results if requested. dir is created in `beforeAll` const benchmarkResultsCsvDir = process.env.BENCHMARK_RESULTS_CSV_DIR; const persistRunsNs = Boolean(benchmarkResultsCsvDir); const { result, runsNs } = await runBenchFn(opts, persistRunsNs); // Store result for: // - to persist benchmark data latter // - to render with the custom reporter results.set(opts.id, result); if (benchmarkResultsCsvDir) { fs.mkdirSync(benchmarkResultsCsvDir, { recursive: true }); const filename = `${result.id}.csv`; const filepath = path.join(benchmarkResultsCsvDir, filename); fs.writeFileSync(filepath, runsNs.join("\n")); } }); }; export const itBench = itBenchFn; itBench.only = function itBench(idOrOpts, fn) { const opts = coerceToOptsObj(idOrOpts, fn); opts.only = true; itBenchFn(opts); }; itBench.skip = function itBench(idOrOpts, fn) { const opts = coerceToOptsObj(idOrOpts, fn); opts.skip = true; itBenchFn(opts); }; function coerceToOptsObj(idOrOpts, fn) { let opts; if (typeof idOrOpts === "string") { if (!fn) throw Error("fn arg must be set"); opts = { id: idOrOpts, fn }; } else { if (fn) { opts = { ...idOrOpts, fn }; } else { const optsWithFn = idOrOpts; if (!optsWithFn.fn) throw Error("opts.fn arg must be set"); opts = optsWithFn; } } return opts; } /** * Customize benchmark opts for a describe block. Affects only tests within that Mocha.Suite * ```ts * describe("suite A1", function () { * setBenchOpts({runs: 100}); * // 100 runs * itBench("bench A1.1", function() {}); * itBench("bench A1.2", function() {}); * // 300 runs * itBench({id: "bench A1.3", runs: 300}, function() {}); * * // Supports nesting, child has priority over parent. * // Arrow functions can be used, won't break it. * describe("suite A2", () => { * setBenchOpts({runs: 200}); * // 200 runs. * itBench("bench A2.1", () => {}); * }) * }) * ``` */ export function setBenchOpts(opts) { before(function () { if (this.currentTest?.parent) { optsMap.set(this.currentTest?.parent, opts); } }); after(function () { // Clean-up to allow garbage collection if (this.currentTest?.parent) { optsMap.delete(this.currentTest?.parent); } }); } function getOptsFromParent(parent) { const optsArr = []; getOptsFromSuite(parent, optsArr); // Merge opts, highest parent = lowest priority return Object.assign({}, ...optsArr.reverse()); } /** * Recursively append suite opts from child to parent. * * @returns `[suiteChildOpts, suiteParentOpts, suiteParentParentOpts]` */ function getOptsFromSuite(suite, optsArr) { const suiteOpts = optsMap.get(suite); if (suiteOpts) { optsArr.push(suiteOpts); } if (suite.parent) { getOptsFromSuite(suite.parent, optsArr); } }