File size: 6,872 Bytes
f0634fb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | // test/differential.test.ts
//
// Differential suite: every sample in test/fixtures/differential/ and every
// official tree-sitter-bash 0.25.0 corpus case in test/fixtures/corpus/ is
// parsed by BOTH this package and the reference (web-tree-sitter + the
// official wasm build) and the normalized trees are compared byte-for-byte.
//
// - @match samples must produce a tree identical to the reference;
// - @known-diff samples must (a) still differ from the reference and
// (b) match the stored dump of our parser exactly — so a documented
// deviation cannot silently drift, and a fixed deviation fails loudly
// ("remove it from the known list").
//
// The known-difference registry (test/helpers/known-differences.ts) is the
// single source of truth and is cross-checked against README.md's Known
// differences section here.
import { readFileSync, readdirSync } from 'node:fs';
import path from 'node:path';
import { beforeAll, describe, expect, it } from 'vitest';
import { compareSource, diffDumps, ourDump, parseCorpusFile, parseFixtureFile, referenceDump } from './helpers/differential';
import type { FixtureSample } from './helpers/differential';
import { KNOWN_DIFFERENCES, isKnownDifferenceId } from './helpers/known-differences';
const PKG_ROOT = path.resolve(import.meta.dirname, '..');
const DIFF_DIR = path.join(PKG_ROOT, 'test/fixtures/differential');
const CORPUS_DIR = path.join(PKG_ROOT, 'test/fixtures/corpus');
interface LocatedSample extends FixtureSample {
file: string;
}
function loadDifferentialFixtures(): LocatedSample[] {
const out: LocatedSample[] = [];
for (const file of readdirSync(DIFF_DIR).filter((f) => f.endsWith('.txt')).toSorted()) {
const content = readFileSync(path.join(DIFF_DIR, file), 'utf8');
for (const sample of parseFixtureFile(`${DIFF_DIR}/${file}`, content)) {
out.push({ ...sample, file });
}
}
return out;
}
function loadCorpusKnownDiffs(): Map<string, LocatedSample> {
const file = 'known-diffs.txt';
const content = readFileSync(path.join(CORPUS_DIR, file), 'utf8');
const map = new Map<string, LocatedSample>();
for (const sample of parseFixtureFile(`${CORPUS_DIR}/${file}`, content)) {
// description: "corpus <file>::<case name>"
const key = sample.description.replace(/^corpus /, '');
map.set(key, { ...sample, file });
}
return map;
}
function label(sample: LocatedSample): string {
return `${sample.file}:${sample.line} ${sample.kind === 'match' ? 'match' : `known-diff ${sample.id}`} — ${sample.description.slice(0, 60)}`;
}
const fixtures = loadDifferentialFixtures();
const corpusKnownDiffs = loadCorpusKnownDiffs();
const corpusCases = readdirSync(CORPUS_DIR)
.filter((f) => f.endsWith('.txt') && f !== 'known-diffs.txt')
.toSorted()
.flatMap((file) => parseCorpusFile(readFileSync(path.join(CORPUS_DIR, file), 'utf8')).map((c) => ({ ...c, file })));
beforeAll(async () => {
// Fails with a descriptive error when the wasm reference cannot be loaded.
await compareSource('echo sanity');
});
describe('differential fixtures', () => {
for (const sample of fixtures) registerFixtureTest(sample);
});
/** Register one fixture test (lookup table instead of conditionals — the
* vitest lint rules reject conditionals around `it`). */
function registerFixtureTest(sample: LocatedSample): void {
const register: Record<FixtureSample['kind'], () => void> = {
match: () => {
it(label(sample), async () => {
const cmp = await compareSource(sample.source);
expect(cmp.diff).toBe('');
});
},
'known-diff': () => {
it(label(sample), async () => {
const [ours, reference] = [ourDump(sample.source), await referenceDump(sample.source)];
expect(
ours !== reference,
'this sample now matches the reference — remove it from the known-difference list (fixture + registry + README)',
).toBe(true);
expect(ours === sample.expectedOurs, `deviation shape drifted:\n${diffDumps(ours, sample.expectedOurs!)}`).toBe(
true,
);
});
},
};
register[sample.kind]();
}
describe('official tree-sitter-bash 0.25.0 corpus', () => {
for (const corpusCase of corpusCases) registerCorpusTest(corpusCase);
});
/** Register one corpus case test. */
function registerCorpusTest(corpusCase: { file: string; name: string; input: string }): void {
const key = `${corpusCase.file}::${corpusCase.name}`;
const known = corpusKnownDiffs.get(key) ?? null;
const register: Record<'match' | 'known-diff', () => void> = {
match: () => {
it(`${key} (match)`, async () => {
const cmp = await compareSource(corpusCase.input);
expect(cmp.diff).toBe('');
});
},
'known-diff': () => {
it(`${key} (known-diff ${known!.id})`, async () => {
const [ours, reference] = [ourDump(corpusCase.input), await referenceDump(corpusCase.input)];
expect(
ours !== reference,
'this corpus case now matches the reference — remove it from known-diffs.txt, the registry and the README',
).toBe(true);
expect(ours === known!.expectedOurs, `deviation shape drifted:\n${diffDumps(ours, known!.expectedOurs!)}`).toBe(
true,
);
});
},
};
register[known === null ? 'match' : 'known-diff']();
}
describe('known-difference registry consistency', () => {
const usedIds = new Set<string>();
for (const sample of fixtures) {
if (sample.id !== undefined) usedIds.add(sample.id);
}
for (const sample of corpusKnownDiffs.values()) {
if (sample.id !== undefined) usedIds.add(sample.id);
}
it('every @known-diff id used in fixtures exists in the registry', () => {
for (const id of usedIds) {
expect(isKnownDifferenceId(id), `fixture uses unregistered id '${id}'`).toBe(true);
}
});
it('every registry id is exercised by at least one fixture or corpus case', () => {
for (const entry of KNOWN_DIFFERENCES) {
expect(usedIds.has(entry.id), `registry id '${entry.id}' has no fixture sample`).toBe(true);
}
});
it('every registry entry is anchored in README.md Known differences', () => {
const readme = readFileSync(path.join(PKG_ROOT, 'README.md'), 'utf8');
const sectionMatch = /## Known differences[^\n]*\n([\s\S]*?)(?=\n## |\s*$)/.exec(readme);
expect(sectionMatch, 'README.md has no "## Known differences" section').not.toBeNull();
// Whitespace-normalized so anchors may span line breaks in the README.
const section = sectionMatch![1]!.replaceAll(/\s+/g, ' ');
for (const entry of KNOWN_DIFFERENCES) {
expect(
section.includes(entry.readmeAnchor.replaceAll(/\s+/g, ' ')),
`README.md Known differences does not contain the anchor for '${entry.id}': ${entry.readmeAnchor}`,
).toBe(true);
}
});
});
|