diff --git a/packages/cli/src/commands/benchmark.test.ts b/packages/cli/src/commands/benchmark.test.ts index 8e9a017fe8..793b9c617f 100644 --- a/packages/cli/src/commands/benchmark.test.ts +++ b/packages/cli/src/commands/benchmark.test.ts @@ -1,12 +1,15 @@ import { runCommand } from "citty"; -import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; -import { join } from "node:path"; +import { dirname, join } from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { consumeCommandResult } from "../utils/commandResult.js"; import benchmark from "./benchmark.js"; -const producer = vi.hoisted(() => ({ executeRenderJob: vi.fn<() => Promise>() })); +const producer = vi.hoisted(() => ({ + executeRenderJob: + vi.fn<(_job: unknown, _projectDir: string, outputPath: string) => Promise>(), +})); vi.mock("../utils/producer.js", () => ({ loadProducer: async () => ({ createRenderJob: (config: unknown) => ({ config }), @@ -90,4 +93,22 @@ describe.each([true, false])("benchmark (json=%s)", (json) => { expect(await runBenchmark(json)).toMatchObject({ exitCode: 0, kind: "success" }); }); + + it("isolates output between invocations and removes temporary renders", async () => { + producer.executeRenderJob.mockImplementation(async (_job, _projectDir, outputPath) => { + mkdirSync(dirname(outputPath), { recursive: true }); + writeFileSync(outputPath, "benchmark output"); + }); + + await runBenchmark(json); + await runBenchmark(json); + + const outputDirs = [0, 5].map((index) => { + const outputPath = producer.executeRenderJob.mock.calls[index]?.[2]; + expect(outputPath).toBeDefined(); + return dirname(outputPath ?? ""); + }); + expect(outputDirs[0]).not.toBe(outputDirs[1]); + expect(outputDirs.every((dir) => !existsSync(dir))).toBe(true); + }); }); diff --git a/packages/cli/src/commands/benchmark.ts b/packages/cli/src/commands/benchmark.ts index 7e42722123..79014df9d2 100644 --- a/packages/cli/src/commands/benchmark.ts +++ b/packages/cli/src/commands/benchmark.ts @@ -1,14 +1,15 @@ import { failCommand, setCommandExitCode } from "../utils/commandResult.js"; import { defineCommand } from "citty"; import type { Example } from "./_examples.js"; -import { existsSync, statSync } from "node:fs"; +import { existsSync, mkdtempSync, rmSync, statSync } from "node:fs"; +import { tmpdir } from "node:os"; export const examples: Example[] = [ ["Run benchmarks with default settings (3 runs)", "hyperframes benchmark"], ["Run 5 iterations per config", "hyperframes benchmark --runs 5"], ["Output results as JSON", "hyperframes benchmark --json"], ]; -import { resolve, join } from "node:path"; +import { join } from "node:path"; import { resolveProject } from "../utils/project.js"; import { loadProducer } from "../utils/producer.js"; import { c } from "../ui/colors.js"; @@ -71,7 +72,7 @@ export default defineCommand({ const jsonOutput = args.json ?? false; // ── Temp output for benchmark renders ──────────────────────────────── - const benchDir = resolve("renders", ".benchmark"); + const benchDir = mkdtempSync(join(tmpdir(), "hyperframes-benchmark-")); // ── Load producer ──────────────────────────────────────────────────── let producer: Awaited> | null = null; @@ -159,6 +160,8 @@ export default defineCommand({ results.push({ config, runs, failures, avgTime, avgSize }); } + rmSync(benchDir, { recursive: true, force: true }); + if (results.every((result) => result.runs.length === 0)) { setCommandExitCode(1); }