llparse-test-fixture
Version:
A test fixture for llparse (and llparse-based modules)
183 lines • 7.08 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.FixtureResult = void 0;
const assert = require("node:assert");
const node_child_process_1 = require("node:child_process");
const path = require("node:path");
const WASM_RUNNER = path.join(__dirname, '..', 'src', 'wasm-runner.js');
class FixtureResult {
executables;
maxParallel;
constructor(executables, maxParallel) {
this.executables = executables;
this.maxParallel = maxParallel;
}
async check(input, expected, options = {}) {
const ranges = [];
const maxParallel = this.maxParallel;
const rawLength = Buffer.byteLength(input);
const len = Math.ceil(rawLength / maxParallel);
if (options.noScan === true) {
ranges.push({ from: rawLength, to: rawLength + 1 });
}
else if (options.scan) {
ranges.push({ from: options.scan, to: options.scan + 1 });
}
else {
for (let i = 1; i <= rawLength; i += len) {
ranges.push({
from: i,
to: Math.min(i + len, rawLength + 1),
});
}
}
await Promise.all(ranges.map(async (range) => {
for (const single of await this.spawn(range, input)) {
for (const [index, output] of single.outputs.entries()) {
this.checkScan(single.name, index + 1, output, expected);
}
}
}));
}
async spawn(range, input) {
return await Promise.all(this.executables.map((executable) => {
return this.spawnSingle(executable, range, input);
}));
}
async spawnSingle(executable, range, input) {
const name = path.basename(executable);
let bin = executable;
const args = [
`${range.from}:${range.to}`,
input,
];
if (executable.endsWith('.wasm')) {
bin = process.execPath;
args.unshift(executable);
args.unshift(WASM_RUNNER);
}
const proc = (0, node_child_process_1.spawn)(bin, args, {
shell: process.platform === 'win32',
stdio: [null, 'pipe', 'pipe'],
});
const stdout = [];
proc.stdout.on('data', (chunk) => stdout.push(chunk));
const stderr = [];
proc.stderr.on('data', (chunk) => stderr.push(chunk));
const onEnd = new Promise(resolve => proc.stdout.once('end', () => resolve()));
const { code, signal } = await new Promise((resolve) => {
proc.once('exit', (exitCode, exitSignal) => {
resolve({ code: exitCode, signal: exitSignal });
});
});
await onEnd;
const stdoutText = Buffer.concat(stdout).toString();
const stderrText = Buffer.concat(stderr).toString();
const stdOutErr = `stdout: ${stdoutText}\nstderr: ${stderrText}`;
if (signal) {
throw new Error(`Test "${name}" killed with signal: "${signal}".\n${stdOutErr}`);
}
if (code !== 0) {
throw new Error(`Test "${name}" exited with code: "${code}".\n${stdOutErr}`);
}
const out = stdoutText.split(/===== SCAN \d+ START =====\n/g).slice(1);
return {
name,
outputs: out.map(part => this.normalizeSpans(part)),
};
}
checkScan(name, scan, actual, expected) {
if (typeof expected === 'string') {
assert.strictEqual(actual, expected, `Executable: ${name}\n` +
`Scan value: ${scan}`);
return;
}
if (expected instanceof RegExp) {
expected.lastIndex = 0;
assert.ok(expected.test(actual), `Executable: ${name}\n` +
`Scan value: ${scan}\n` +
` got : ${JSON.stringify(actual)}\n` +
` against : ${expected}`);
return;
}
assert(Array.isArray(expected) &&
expected.every((line) => {
return typeof line === 'string' || line instanceof RegExp;
}), '`expected` must be a string, RegExp, or Array[String|RegExp]');
const lines = actual.split('\n');
while (lines.length && lines[lines.length - 1]) {
lines.pop();
}
// If they differ - we are going to fail
while (lines.length < expected.length) {
lines.push('');
}
// Just make it fail, there shouldn't be extra lines
const expectedArr = expected.slice();
while (expectedArr.length < lines.length) {
expectedArr.push(/$^/);
}
lines.forEach((line, lineNum) => {
const expectedLine = expectedArr[lineNum];
if (typeof expectedLine === 'string') {
assert.strictEqual(line, expectedLine, `Executable: ${name}\n` +
`Scan value: ${scan} at line: ${lineNum + 1}\n` +
` output : ${lines.join('\n')}`);
return;
}
expectedLine.lastIndex = 0;
assert.ok(expectedLine.test(line), `Executable: ${name}\n` +
`Scan value: ${scan} at line: ${lineNum + 1}\n` +
` got : ${JSON.stringify(line)}\n` +
` against : ${expectedLine}\n` +
` output : ${lines.join('\n')}`);
});
}
normalizeSpans(source) {
const lines = source.split(/\n/g);
const parse = (line) => {
const match = line.match(/^off=(\d+)\s+len=(\d+)\s+span\[([^\]]+)\]="(.*)"$/);
if (!match) {
return { type: 'raw', value: line };
}
return {
len: parseInt(match[2], 10),
off: parseInt(match[1], 10),
span: match[3],
type: 'span',
value: match[4],
};
};
const parsed = lines.filter(l => l).map(parse);
const lastMap = new Map();
const res = [];
parsed.forEach((obj) => {
if (obj.type === 'raw') {
res.push(obj);
return;
}
if (lastMap.has(obj.span)) {
const last = lastMap.get(obj.span);
if (last.off + last.len === obj.off) {
last.len += obj.len;
last.value += obj.value;
// Move it to the end
res.splice(res.indexOf(last), 1);
res.push(last);
return;
}
}
res.push(obj);
lastMap.set(obj.span, obj);
});
const stringify = (obj) => {
if (obj.type === 'raw') {
return obj.value;
}
return `off=${obj.off} len=${obj.len} span[${obj.span}]="${obj.value}"`;
};
return res.map(stringify).join('\n') + '\n';
}
}
exports.FixtureResult = FixtureResult;
//# sourceMappingURL=result.js.map
;