diff --git a/lib/clock.js b/lib/clock.js index d996f70..df37544 100644 --- a/lib/clock.js +++ b/lib/clock.js @@ -111,6 +111,7 @@ let i = 0; let ${varNames.context} = {}; `; + const hasAfterEach = !!bench.afterEach; let benchFnCall = `${awaitOrEmpty}${varNames.bench}.fn()`; const wrapFunctions = []; for (const p of bench.plugins) { @@ -126,11 +127,17 @@ let ${varNames.context} = {}; return `${n}(${prev})`; }, benchFnCall); + let awaitOrEmptyAfterEach = ""; + if (hasAfterEach && types.isAsyncFunction(bench.afterEach)) { + awaitOrEmptyAfterEach = "await "; + } code += ` const startedAt = ${varNames.timer}.now(); -for (; i < count; i++) +for (; i < count; i++) { ${benchFnCall}; + ${hasAfterEach ? `${awaitOrEmptyAfterEach}${varNames.bench}.afterEach();` : ""} +} const duration = Number(${varNames.timer}.now() - startedAt); `; diff --git a/lib/index.js b/lib/index.js index d7e7633..4c327f3 100644 --- a/lib/index.js +++ b/lib/index.js @@ -34,13 +34,14 @@ class Benchmark { plugins; repeatSuite; - constructor(name, fn, minTime, maxTime, plugins, repeatSuite) { + constructor(name, fn, { minTime, maxTime, plugins, repeatSuite, afterEach }) { this.name = name; this.fn = fn; this.minTime = minTime; this.maxTime = maxTime; this.plugins = plugins; this.repeatSuite = repeatSuite; + this.afterEach = afterEach; this.hasArg = this.fn.length >= 1; if (this.fn.length > 1) { @@ -130,14 +131,23 @@ class Suite { } validateFunction(fn, "fn"); - const benchmark = new Benchmark( - name, - fn, - options.minTime, - options.maxTime, - this.#plugins, - options.repeatSuite, - ); + // Validate afterEach usage with workers + if (options.afterEach) { + validateFunction(options.afterEach, "options.afterEach"); + if (this.#useWorkers) { + throw new Error( + `The "afterEach" hook is not supported when using Worker Threads. Remove "afterEach" from the benchmark "${name}" or set "useWorkers: false".`, + ); + } + } + + const benchmark = new Benchmark(name, fn, { + minTime: options.minTime, + maxTime: options.maxTime, + plugins: this.#plugins, + repeatSuite: options.repeatSuite, + afterEach: options.afterEach, + }); this.#benchmarks.push(benchmark); return this; } diff --git a/test/fixtures/cached.js b/test/fixtures/cached.js new file mode 100644 index 0000000..cabdf6a --- /dev/null +++ b/test/fixtures/cached.js @@ -0,0 +1,3 @@ +module.exports = { + foo: "bar", +}; diff --git a/test/hooks.js b/test/hooks.js new file mode 100644 index 0000000..2daa561 --- /dev/null +++ b/test/hooks.js @@ -0,0 +1,72 @@ +const { describe, it } = require("node:test"); +const assert = require("node:assert"); +const path = require("node:path"); +const { Suite } = require("../lib/index"); + +describe("afterEach hook", () => { + it("should call afterEach after every iteration in unmanaged benchmarks", async () => { + let afterEachCallCount = 0; + const suite = new Suite({ reporter: () => {} }); + + suite.add( + "unmanaged with afterEach", + { + afterEach: () => { + afterEachCallCount++; + }, + }, + () => { + const sum = 1 + 2; // Benchmark logic + return sum; + }, + ); + + await suite.run(); + + // Validate afterEach was called correctly + assert.ok(afterEachCallCount > 0, "afterEach was not called"); + }); + + it("should be called after each iteration of a benchmark", async () => { + const suite = new Suite({ reporter: () => {} }); + const cachedModule = path.resolve( + path.join(__dirname, "./fixtures/cached.js"), + ); + + suite.add( + "unmanaged with afterEach", + { + afterEach: () => { + delete require.cache[cachedModule]; + }, + }, + () => { + assert.ok(require.cache[cachedModule] === undefined); + require(cachedModule); + assert.ok(require.cache[cachedModule] !== undefined); + }, + ); + + await suite.run(); + assert.ok(require.cache[cachedModule] === undefined); + }); + + it("should throw error when afterEach is used with Worker Threads", () => { + const suite = new Suite({ reporter: () => {}, useWorkers: true }); + + assert.throws(() => { + suite.add( + "worker with afterEach", + { + afterEach: () => { + console.log("This will fail"); + }, + }, + () => { + const result = 42; // Some logic + return result; + }, + ); + }, /The "afterEach" hook is not supported when using Worker Threads/); + }); +}); diff --git a/test/plugin-api-doc.js b/test/plugin-api-doc.js index 04cad96..560bf35 100644 --- a/test/plugin-api-doc.js +++ b/test/plugin-api-doc.js @@ -67,7 +67,7 @@ describe("plugin API", async () => { "getReport(string)", "getResult(string)", "isSupported()", - "onCompleteBenchmark([number, number, object], {fn, fnStr, hasArg, isAsync, maxTime, minTime, name, plugins, repeatSuite})", + "onCompleteBenchmark([number, number, object], {afterEach, fn, fnStr, hasArg, isAsync, maxTime, minTime, name, plugins, repeatSuite})", "toJSON(string)", "toString()", ]);