From 929d9072250de0ac8e71553c1c91d011d03aae56 Mon Sep 17 00:00:00 2001 From: Daniel Bannert Date: Mon, 26 Oct 2026 11:18:54 +0100 Subject: [PATCH] fix(vitest-plugin): explain a missing benchmark function When the benchmark file and the plugin resolve different copies of vitest (pnpm installs one per set of peers, e.g. one per vite major), the plugin reads an empty benchmark registry and the analysis runner failed with a bare "TypeError: fn is not a function". Throw an error that names the benchmark, the likely cause and the fix instead. Co-Authored-By: Claude Opus 5.5 --- .../src/__tests__/instrumented.test.ts | 25 +++++++++++++++++++ packages/vitest-plugin/src/legacy/analysis.ts | 10 ++++++++ 2 files changed, 35 insertions(+) diff --git a/packages/vitest-plugin/src/__tests__/instrumented.test.ts b/packages/vitest-plugin/src/__tests__/instrumented.test.ts index ec1a1fd9..dd948df8 100644 --- a/packages/vitest-plugin/src/__tests__/instrumented.test.ts +++ b/packages/vitest-plugin/src/__tests__/instrumented.test.ts @@ -188,4 +188,29 @@ describe("CodSpeedRunner", () => { "teardown:run:test bench", ]); }); + it("should explain a benchmark function it cannot find", async () => { + // What the runner sees when the benches were registered in another + // vitest copy than the one this plugin imports. + mockedGetBenchFn.mockReturnValue( + undefined as unknown as ReturnType, + ); + + const runner = new CodSpeedRunner(fromPartial({})); + const suite = fromPartial({ + file: { filepath: __filename }, + name: "test suite", + tasks: [ + { + type: "test", + mode: "run", + meta: { benchmark: true }, + name: "test bench", + }, + ], + }); + + await expect(runner.runSuite(suite)).rejects.toThrow( + /no benchmark function found for ".*::test bench"\. The benchmark file and @codspeed\/vitest-plugin most likely resolved different copies of vitest/, + ); + }); }); diff --git a/packages/vitest-plugin/src/legacy/analysis.ts b/packages/vitest-plugin/src/legacy/analysis.ts index f71f0c0a..8214d817 100644 --- a/packages/vitest-plugin/src/legacy/analysis.ts +++ b/packages/vitest-plugin/src/legacy/analysis.ts @@ -45,6 +45,16 @@ async function runAnalysisBench( const uri = `${currentSuiteName}::${benchmark.name}`; const fn = getBenchFn(benchmark); + // The registry `getBenchFn` reads lives in the vitest instance this plugin + // imports. When the benchmark file registered its benches in another copy + // (pnpm installs one per set of peers, e.g. per vite major), it is empty + // here, and calling it fails with a bare "fn is not a function". + if (typeof fn !== "function") { + throw new Error( + `[CodSpeed] no benchmark function found for "${uri}". The benchmark file and @codspeed/vitest-plugin most likely resolved different copies of vitest, so the plugin cannot see the registered benchmarks. Install @codspeed/vitest-plugin in the package that runs the benchmarks and import it from that package's vitest config, so both resolve the same vitest.`, + ); + } + // Constructing a Bench applies tinybench's no-op defaults for the setup and // teardown hooks and gives them the Task they expect. The bench itself is // never run: this runner drives the benchmark function directly.