Files
gitea-axi/bench/run.test.ts
alexion 80a4fafa06
All checks were successful
CI / test (pull_request) Successful in 51s
CI / test (push) Successful in 52s
feat: add benchmark run-loop command (task 0029)
Add the maintainer-facing command that runs one chosen benchmark cell on
demand, so only the token budget available at that moment is spent.

runCells (bench/run-loop.ts) runs one (arm, task) cell for a batch of
trials — defaulting to five with a reporting floor of three — by driving
the existing single-cell runner and the append-only sample store rather
than reimplementing orchestration. Re-running a cell deepens it: trial
numbering continues past the highest trial the cell already holds and the
new samples append, so a cell's sample size grows across sittings without
overwriting prior runs.

bench/run.ts is the command: parseRunArgs is the pure, unit-tested
argument seam, and runBenchCommand is the live boundary that resolves host
access, resolves the scored suite against the host's self-review support,
selects the task, and drives the run loop. It is invoked via the new
bench:run npm script, run under tsx (a new devDependency) because the
harness's .js-specifier imports need a TypeScript-aware runner. The Claude
Agent SDK is now declared as an optional peerDependency — documented but
neither installed for package consumers nor pulled into CI.

Every arm runs on the driver's single fixed model; the command exposes no
per-cell model override that could break cross-arm comparability. The
default store root bench/results/ is gitignored.
2026-07-16 10:44:52 -04:00

122 lines
4.9 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
DEFAULT_STORE_ROOT,
DEFAULT_TURN_CAP,
DEFAULT_WALL_CLOCK_MS,
parseRunArgs,
} from "./run.js";
describe("parseRunArgs", () => {
// Behavior: parsing the required --arm and --task selection yields the resolved
// cell with defaults applied for everything else — five trials (spec: "Each cell
// defaults to five trials"), the documented turn-cap and wall-clock backstop, the
// default store root, and the login taken from the environment when --login is
// omitted. Trials is the independent literal 5; the other defaults are asserted
// against the module's documented default constants (the single source of truth
// for each default), which checks the parser wires them through on omission.
it("applies the documented defaults when only the required arm and task are given, taking the login from the environment", () => {
const result = parseRunArgs(
["--arm", "gitea-axi", "--task", "close-csv-export-issue"],
{ GITEA_AXI_BENCH_LOGIN: "alexion" },
);
expect(result.help).toBe(false);
if (result.help) return;
// The required selection resolves to the chosen cell.
expect(result.arm).toBe("gitea-axi");
expect(result.taskId).toBe("close-csv-export-issue");
// Trials default to five (independent literal from the spec).
expect(result.trials).toBe(5);
// The remaining bounds and store root fall back to the documented defaults.
expect(result.turnCap).toBe(DEFAULT_TURN_CAP);
expect(result.wallClockMs).toBe(DEFAULT_WALL_CLOCK_MS);
expect(result.storeRoot).toBe(DEFAULT_STORE_ROOT);
// The login comes from the environment.
expect(result.login).toBe("alexion");
});
// Behavior: every optional flag overrides its default, and an explicit --login
// takes precedence over the environment. The parser passes through whatever the
// maintainer supplies. All expected values are independent literals chosen apart
// from the code; login must be the explicit "explicit-login" even though the
// environment also sets GITEA_AXI_BENCH_LOGIN ("env-login").
it("passes every supplied flag through, with an explicit login overriding the environment", () => {
const result = parseRunArgs(
[
"--arm",
"tea",
"--task",
"read-open-issue-count",
"--trials",
"3",
"--turn-cap",
"12",
"--wall-clock-ms",
"90000",
"--store",
"/tmp/bench-out",
"--login",
"explicit-login",
],
{ GITEA_AXI_BENCH_LOGIN: "env-login" },
);
expect(result.help).toBe(false);
if (result.help) return;
expect(result.arm).toBe("tea");
expect(result.taskId).toBe("read-open-issue-count");
expect(result.trials).toBe(3);
expect(result.turnCap).toBe(12);
expect(result.wallClockMs).toBe(90000);
expect(result.storeRoot).toBe("/tmp/bench-out");
// The explicit --login beats the env-provided login.
expect(result.login).toBe("explicit-login");
});
// Behavior: malformed or incomplete input is rejected with a usage error. The
// four arms are exactly gitea-axi, tea, gitea-mcp, raw-api; --arm and --task are
// required; --trials must be a positive integer; unknown flags are not accepted;
// and a login must be resolvable (from --login or the environment). A non-empty
// env login is supplied where the tested defect is elsewhere, so the throw is the
// intended one rather than a missing login.
it("rejects malformed or incomplete input with a usage error", () => {
const env = { GITEA_AXI_BENCH_LOGIN: "alexion" };
// Unknown arm (not one of the four).
expect(() => parseRunArgs(["--arm", "github", "--task", "t"], env)).toThrow();
// Missing required --arm.
expect(() => parseRunArgs(["--task", "t"], env)).toThrow();
// Missing required --task.
expect(() => parseRunArgs(["--arm", "tea"], env)).toThrow();
// Non-numeric trials.
expect(() =>
parseRunArgs(["--arm", "tea", "--task", "t", "--trials", "abc"], env),
).toThrow();
// Unknown flag.
expect(() =>
parseRunArgs(["--arm", "tea", "--task", "t", "--frobnicate", "x"], env),
).toThrow();
// The removed --model flag is now unknown and rejected.
expect(() =>
parseRunArgs(["--arm", "tea", "--task", "t", "--model", "x"], env),
).toThrow();
// Login is required and here is resolvable from neither --login nor the env.
expect(() => parseRunArgs(["--arm", "tea", "--task", "t"], {})).toThrow();
});
// Behavior: --help short-circuits parsing and reports a help request, winning
// even alongside other arguments and via the -h alias.
it("short-circuits to a help request for --help and -h, even alongside other args", () => {
expect(parseRunArgs(["--help"], {}).help).toBe(true);
expect(parseRunArgs(["-h"], {}).help).toBe(true);
expect(parseRunArgs(["--arm", "tea", "--help"], {}).help).toBe(true);
});
});