import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import type { Range } from "../src/vocab/index.ts"; // tests/fixtures/rules/ 下的预期值由独立的 Python 脚本照 docs/方法.md 和约定表算出(脚本开头注释写了格式)。 // 这里只负责读文件和比较数字;把场景映射到 rules 的函数上,在各个 rules-fixtures-*.test.ts 里做。 export interface Scenario { name: string; section: string; params: Record; input: Record; expected: any; note: string; } export interface FixtureFile { generator: string; tolerance: Record; models?: Record; input: Record }>; scenarios: Scenario[]; } export function loadFixture(file: string): FixtureFile { return JSON.parse(readFileSync(new URL(`./fixtures/rules/${file}`, import.meta.url), "utf8")) as FixtureFile; } /** 按名字取场景,并登记"已覆盖";每个测试文件最后核对 JSON 里的场景都被取过。 */ export function scenarioPicker(fixture: FixtureFile): { get: (name: string) => Scenario; unused: () => string[]; } { const used = new Set(); return { get(name) { const s = fixture.scenarios.find((x) => x.name === name); assert.ok(s, `JSON 里没有场景 ${name}`); used.add(name); return s; }, unused: () => fixture.scenarios.map((s) => s.name).filter((n) => !used.has(n)), }; } /** 绝对误差 tol 以内算相同。 */ export function close(actual: number, expected: number, tol: number, what: string): void { assert.ok( Number.isFinite(actual) && Math.abs(actual - expected) <= tol, `${what}:实现 ${actual},预期 ${expected},差 ${actual - expected},容差 ${tol}`, ); } /** 网格值:|差| ≤ 1e-12 + 1e-9·|值|(ablation.json tolerance.grid)。 */ export function closeGrid(actual: number, expected: number, what: string): void { close(actual, expected, 1e-12 + 1e-9 * Math.abs(expected), what); } export function closeArray(actual: readonly number[], expected: readonly number[], tol: number, what: string): void { assert.equal(actual.length, expected.length, `${what}:长度`); actual.forEach((a, i) => close(a, expected[i]!, tol, `${what}[${i}]`)); } export function range(r: { q1: number; q5: number; q9: number }): Range { return { q1: r.q1, q5: r.q5, q9: r.q9 }; }