diff --git a/src/compare.ts b/src/compare.ts index df7726c..071d160 100644 --- a/src/compare.ts +++ b/src/compare.ts @@ -36,6 +36,14 @@ export interface CompareResult { cases: CaseDelta[]; } +/** Raised when compare options fail validation. */ +export class CompareValidationError extends Error { + constructor(message: string) { + super(`[evalgate] invalid compare options: ${message}`); + this.name = "CompareValidationError"; + } +} + /** Options for {@link compareRuns}. */ export interface CompareOptions { /** @@ -65,6 +73,9 @@ export function compareRuns( options: CompareOptions = {}, ): CompareResult { const tolerance = options.tolerance ?? 0; + if (!Number.isFinite(tolerance) || tolerance < 0) { + throw new CompareValidationError("tolerance must be a non-negative finite number"); + } const failOnPassFlip = options.failOnPassFlip !== false; const baseMap = byId(baseline.cases); diff --git a/tests/compare.test.ts b/tests/compare.test.ts index 093f6c6..67ac3d2 100644 --- a/tests/compare.test.ts +++ b/tests/compare.test.ts @@ -74,4 +74,16 @@ describe("compareRuns", () => { expect(changes.a).toBe("removed"); expect(changes.b).toBe("added"); }); + + it("rejects negative tolerance", () => { + const base = run([caseResult("a", 1)]); + const head = run([caseResult("a", 0.5)]); + expect(() => compareRuns(base, head, { tolerance: -0.5 })).toThrow(/tolerance/); + }); + + it("rejects NaN tolerance", () => { + const base = run([caseResult("a", 1)]); + const head = run([caseResult("a", 0.5)]); + expect(() => compareRuns(base, head, { tolerance: NaN })).toThrow(/tolerance/); + }); });