69 lines
4.0 KiB
JavaScript
69 lines
4.0 KiB
JavaScript
// Golden results (test/golden/README.md, tools/golden.mjs): recompute the deterministic scenario corpus and compare
|
||
// every digest with test/golden/*.json — battles (every chess record × skill × module, every bond, the leader and 联防
|
||
// fields) and bot-only matches. A behaviour-preserving refactor must leave every digest unchanged; a failure names the
|
||
// scenario, the field and old → new. Default: the fast subset (scenarios marked "fast": true in the golden files);
|
||
// GOLDEN_FULL=1 checks the whole corpus. GOLDEN_JOBS=N sets the worker threads (default: up to 4).
|
||
// After an intended gameplay change: `npm run golden:update`, review the diff, commit the files with the change.
|
||
import { test, before } from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import { availableParallelism } from 'node:os';
|
||
import { FAMILY_NAMES, scenariosOf, computeDigests, loadGolden, storedDigests, compareFamily, formatDiffs } from '../tools/golden.mjs';
|
||
|
||
const FULL = process.env.GOLDEN_FULL === '1';
|
||
const JOBS = Math.max(1, Number(process.env.GOLDEN_JOBS) || Math.min(4, Math.max(1, availableParallelism() - 1)));
|
||
|
||
let digests = null;
|
||
// ≈ 7 s (fast) / 20 s (full) with 4 worker threads here; the timeout only stops a hang (a step that never returns)
|
||
before(async () => {
|
||
digests = await computeDigests(FAMILY_NAMES, { select: (sc) => FULL || sc.fast, jobs: JOBS });
|
||
}, { timeout: 15 * 60_000 });
|
||
|
||
test('a changed digest value is reported with its scenario, field and old → new', () => {
|
||
const stored = storedDigests(loadGolden('roster'));
|
||
const [id, dg] = Object.entries(stored)[0];
|
||
const changed = structuredClone(dg);
|
||
const row = changed.units[0];
|
||
row[10] += 1; // dmg
|
||
changed.hooks[2] += 1; // tick
|
||
changed.enemies[Object.keys(changed.enemies)[0]][1] += 1; // killed
|
||
const diffs = compareFamily(stored, { [id]: changed });
|
||
assert.equal(diffs.length, 1);
|
||
const text = formatDiffs('roster', diffs);
|
||
assert.match(text, new RegExp(`${id}:`));
|
||
assert.match(text, new RegExp(`\\.units\\[${row[0]}#${row[1]}:${row[3]}\\]\\.dmg: ${row[10] - 1} → ${row[10]}`));
|
||
assert.match(text, /\.hooks\.tick: \d+ → \d+/);
|
||
assert.match(text, /\.enemies\.enemy_\w+\.killed: \d+ → \d+/);
|
||
});
|
||
|
||
test('golden files list exactly the corpus scenarios (regenerate with npm run golden:update after a corpus change)', () => {
|
||
for (const f of FAMILY_NAMES) {
|
||
const doc = loadGolden(f);
|
||
assert.ok(doc, `test/golden/${f}.json is missing: run npm run golden:update`);
|
||
assert.deepEqual(Object.keys(doc.scenarios), scenariosOf(f).map((sc) => sc.id), `test/golden/${f}.json scenario list`);
|
||
for (const sc of scenariosOf(f)) assert.equal(doc.scenarios[sc.id].fast, sc.fast, `${sc.id}: fast flag`);
|
||
}
|
||
});
|
||
|
||
// a stored digest that records engine errors means the corpus was regenerated over a crash (0.2.0: a merge dropped an
|
||
// import a kit's tick handler used, and `npm run golden:update` stored 9288 swallowed errors as "the expected result")
|
||
test('no stored scenario records engine errors (errors.count is 0 everywhere)', () => {
|
||
for (const f of FAMILY_NAMES) {
|
||
for (const [id, dg] of Object.entries(storedDigests(loadGolden(f)))) {
|
||
if (!dg || typeof dg !== 'object' || !dg.errors) continue;
|
||
// battles: { count, kinds }; matches: { engine, logged, sim, dispatcher }
|
||
const e = dg.errors;
|
||
const n = typeof e.count === 'number' ? e.count : Object.values(e).reduce((a, v) => a + (typeof v === 'number' ? v : 0), 0);
|
||
assert.equal(n, 0, `${f} ${id}: ${n} engine error(s) ${JSON.stringify(e)} — fix the crash, then regenerate`);
|
||
}
|
||
}
|
||
});
|
||
|
||
for (const f of FAMILY_NAMES) {
|
||
test(`golden ${f}: ${FULL ? 'every scenario' : 'the fast subset (GOLDEN_FULL=1 for all)'} reproduces its stored digest`, () => {
|
||
const computed = digests[f];
|
||
assert.ok(Object.keys(computed).length > 0, `no ${f} scenario computed`);
|
||
const diffs = compareFamily(storedDigests(loadGolden(f)), computed);
|
||
assert.equal(diffs.length, 0, `${formatDiffs(f, diffs)}\n(an intended gameplay change: npm run golden:update and review the diff — test/golden/README.md)`);
|
||
});
|
||
}
|