/** * MP 差分测试共用工具:读取 scripts/mp-sync/gen-differential.py 生成的期望 JSON,登记偏差集合与实际偏差集合比对。 * 偏差 key:`.status` / `.count` / `#<行号>.<字段>`。 */ import { readFileSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; export const HERE = dirname(fileURLToPath(import.meta.url)); export type DeviationKind = "intentional" | "missing" | "our_bug" | "mp_bug"; export interface Deviation { kind: DeviationKind; note: string; /** 仅在条件成立时才出现的偏差;local_tz_not_utc = 宿主本地时区偏移非 0 */ condition?: "local_tz_not_utc"; } function conditionHolds(d: Deviation): boolean { if (d.condition === "local_tz_not_utc") { return new Date("2026-09-26T00:00:00").getTimezoneOffset() !== 0; } return true; } export interface DifferentialDoc { kind: string; generatedFrom: Record; fields: string[]; samples: S[]; deviations: Record; } export function loadDoc(name: string): DifferentialDoc { return JSON.parse(readFileSync(join(HERE, name), "utf8")) as DifferentialDoc; } export function readInput(rel: string): string { return readFileSync(join(HERE, rel), "utf8"); } export interface Mismatch { key: string; mp: unknown; ours: unknown; } /** null / undefined 视为同一"缺省";其余按 JSON 结构比较 */ export function sameValue(a: unknown, b: unknown): boolean { if (a == null && b == null) return true; return JSON.stringify(a) === JSON.stringify(b); } export class FieldCollector { readonly mismatches: Mismatch[] = []; compared = 0; equal = 0; check(key: string, mp: unknown, ours: unknown): void { this.compared++; if (sameValue(mp, ours)) this.equal++; else this.mismatches.push({ key, mp, ours }); } } /** 实际不一致集合 vs 登记集合:返回两个方向的差集(均为空才通过)。 */ export function diffAgainstRegistry( actual: Mismatch[], registry: Record, ): { unregistered: Mismatch[]; stale: string[] } { const actualKeys = new Set(actual.map((m) => m.key)); return { unregistered: actual.filter((m) => !(m.key in registry)), stale: Object.entries(registry) .filter(([k, d]) => !actualKeys.has(k) && conditionHolds(d)) .map(([k]) => k), }; } /** 设置环境变量 DIFF_DUMP_DIR 时把实际不一致写成 .mismatches.json(整理 deviations 用) */ export function dumpMismatches(name: string, ms: Mismatch[], stats?: { compared: number; equal: number }): void { const dir = process.env.DIFF_DUMP_DIR; if (dir) { writeFileSync(join(dir, `${name}.mismatches.json`), `${JSON.stringify({ stats, mismatches: ms }, null, 2)}\n`); } } export function formatMismatches(ms: Mismatch[]): string { return ms.map((m) => `${m.key}\n mp: ${JSON.stringify(m.mp)}\n ours: ${JSON.stringify(m.ours)}`).join("\n"); }