111 lines
7.6 KiB
JavaScript
111 lines
7.6 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { createHash } from "node:crypto";
|
|
import { readFile } from "node:fs/promises";
|
|
import { HalRuntime, parseCompSource } from "./hal-runtime.mjs";
|
|
import { fnv1a64 } from "./replay.mjs";
|
|
|
|
const ROOT = new URL("../../", import.meta.url);
|
|
const CONTEXT_ID = "linuxcnc-2.10.0-pre0-wasm-m1-py-c1";
|
|
|
|
async function read(path) { return readFile(new URL(path, ROOT)); }
|
|
|
|
async function fixture(paths) {
|
|
const files = [];
|
|
for (const path of paths) {
|
|
const value = await read(path);
|
|
files.push({ path, bytes: value.byteLength, sha256: createHash("sha256").update(value).digest("hex") });
|
|
}
|
|
return files;
|
|
}
|
|
|
|
function pendingCommands(source) {
|
|
return source.split(/\r?\n/).map((line) => line.trim()).filter((line) => /^(?:loadusr|start|loadrt\s+(?:sampler|streamer)\b)/i.test(line));
|
|
}
|
|
|
|
function resultHash(value) { return fnv1a64(value); }
|
|
|
|
async function runAbs(path) {
|
|
const source = (await read(path)).toString("utf8");
|
|
const hal = new HalRuntime();
|
|
assert.equal(hal.configure({ threads: [{ name: "fast", period_ticks: 1 }], components: [{ instance: "abs.0", type: "abs", thread: "fast" }] }).status, "verified");
|
|
assert.equal(hal.setPin("abs.0.in", -0.25).status, "verified");
|
|
assert.equal(hal.step(1).status, "verified");
|
|
const result = { tick: hal.state.tick, out: hal.getPin("abs.0.out").value, sign: hal.getPin("abs.0.sign").value, positive: hal.getPin("abs.0.is-positive").value, negative: hal.getPin("abs.0.is-negative").value, state_hash: hal.hash(), pending_commands: pendingCommands(source) };
|
|
assert.equal(result.out, 0.25);
|
|
assert.equal(result.sign, 1);
|
|
assert.equal(result.negative, 1);
|
|
return result;
|
|
}
|
|
|
|
async function runEdge(path, startingHigh = false) {
|
|
const source = (await read(path)).toString("utf8");
|
|
const hal = new HalRuntime();
|
|
assert.equal(hal.configure({ threads: [{ name: "fast", period_ticks: 1 }], components: [{ instance: "edge.0", type: "edge", thread: "fast" }] }).status, "verified");
|
|
assert.equal(hal.setParam("edge.0.in-edge", 0).status, "verified");
|
|
assert.equal(hal.setPin("edge.0.in", startingHigh ? 1 : 0).status, "verified");
|
|
assert.equal(hal.step(1).status, "verified");
|
|
const first = hal.getPin("edge.0.out").value;
|
|
assert.equal(hal.setPin("edge.0.in", 1).status, "verified");
|
|
assert.equal(hal.step(2).status, "verified");
|
|
const result = { tick: hal.state.tick, first, rising_output: hal.getPin("edge.0.out").value, last_input: hal.getParam("edge.0.last-in").value, state_hash: hal.hash(), pending_commands: pendingCommands(source) };
|
|
assert.equal(result.rising_output, startingHigh ? 0 : 1);
|
|
return result;
|
|
}
|
|
|
|
async function parseComponent(path, expected, requireSupported = true) {
|
|
const source = (await read(path)).toString("utf8");
|
|
const parsed = parseCompSource(source);
|
|
if (requireSupported) assert.equal(parsed.status, "verified");
|
|
assert.equal(parsed.component, expected);
|
|
return { status: parsed.status, component: parsed.component, pins: parsed.pins.length, params: parsed.params.length, functions: parsed.functions.length, options: parsed.options, state_hash: resultHash(parsed) };
|
|
}
|
|
|
|
async function main() {
|
|
const cases = [];
|
|
const add = async (id, adapter, paths, result, status = "PASSED", reason = null) => cases.push({ id, adapter, status, ...(reason ? { reason } : {}), fixture: await fixture(paths), result, result_hash: resultHash(result) });
|
|
|
|
const absPath = "linuxcnc/tests/abs.0/test.hal";
|
|
await add("source-test:linuxcnc/tests/abs.0/test.hal", "WORKER_SIM_ADAPTER", [absPath, "linuxcnc/tests/abs.0/expected"], await runAbs(absPath));
|
|
const edgePath = "linuxcnc/tests/edge/rising/test.hal";
|
|
await add("source-test:linuxcnc/tests/edge/rising/test.hal", "WORKER_SIM_ADAPTER", [edgePath, "linuxcnc/tests/edge/rising/expected"], await runEdge(edgePath));
|
|
const edgeHighPath = "linuxcnc/tests/edge/rising-starting-high/test.hal";
|
|
await add("source-test:linuxcnc/tests/edge/rising-starting-high/test.hal", "WORKER_SIM_ADAPTER", [edgeHighPath, "linuxcnc/tests/edge/rising-starting-high/expected"], await runEdge(edgeHighPath, true));
|
|
|
|
const namesMatch = "linuxcnc/tests/halcompile/names/names_match.comp";
|
|
const namesDontMatch = "linuxcnc/tests/halcompile/names/names_dont_match.comp";
|
|
const namesResult = { matched: await parseComponent(namesMatch, "names_match", false), mismatch_parser: (await parseComponent(namesDontMatch, "wrong_name", false)).status, mismatch_expected: "compiler_component_name_mismatch" };
|
|
await add("source-test:linuxcnc/tests/halcompile/names/test.sh", "SOURCE_BLOCKED", [namesMatch, namesDontMatch, "linuxcnc/tests/halcompile/names/expected", "linuxcnc/tests/halcompile/names/test.sh"], namesResult, "SOURCE_BLOCKED", "custom .comp declarations require native halcompile and are not static Worker components");
|
|
|
|
const relative = "linuxcnc/tests/halcompile/relative-header/relative_header.comp";
|
|
const relativeParsed = parseCompSource((await read(relative)).toString("utf8"));
|
|
await add("source-test:linuxcnc/tests/halcompile/relative-header/test.sh", "SOURCE_BLOCKED", [relative, "linuxcnc/tests/halcompile/relative-header/local.h", "linuxcnc/tests/halcompile/relative-header/checkresult", "linuxcnc/tests/halcompile/relative-header/test.sh"], { parser_status: relativeParsed.status, parser_component: relativeParsed.component, reason: "native halcompile and relative header compiler unavailable" }, "SOURCE_BLOCKED", "native halcompile and relative header compiler unavailable");
|
|
|
|
const userspace = "linuxcnc/tests/halcompile/userspace-count-names/userspace_count_names.comp";
|
|
const userspaceParsed = parseCompSource((await read(userspace)).toString("utf8"));
|
|
await add("source-test:linuxcnc/tests/halcompile/userspace-count-names/test.sh", "HOST_EXCLUDED", [userspace, "linuxcnc/tests/halcompile/userspace-count-names/checkresult", "linuxcnc/tests/halcompile/userspace-count-names/test.sh"], { parser_status: userspaceParsed.status, parser_component: userspaceParsed.component, options: userspaceParsed.options, reason: "userspace component requires host process and signal APIs" }, "HOST_EXCLUDED", "userspace component requires host process and signal APIs");
|
|
|
|
for (const [directory, component] of [["symbols.0", "test_define"], ["symbols.1", "test_define1"]]) {
|
|
const path = `linuxcnc/tests/${directory}/test_${directory.slice(-1) === "0" ? "define" : "define1"}.comp`;
|
|
const parsed = parseCompSource((await read(path)).toString("utf8"));
|
|
await add(`source-test:linuxcnc/tests/${directory}/test.sh`, "HOST_EXCLUDED", [path, `linuxcnc/tests/${directory}/checkresult`, `linuxcnc/tests/${directory}/test.sh`], { parser_status: parsed.status, parser_component: parsed.component, reason: "source test invokes host halcompile/rtapi component compiler" }, "HOST_EXCLUDED", "source test invokes host halcompile/rtapi component compiler");
|
|
}
|
|
|
|
const counts = Object.fromEntries(["PASSED", "SOURCE_BLOCKED", "HOST_EXCLUDED"].map((status) => [status, cases.filter((item) => item.status === status).length]));
|
|
const output = {
|
|
schema: "linuxcnc-source-hal-adapter.v1",
|
|
context_id: CONTEXT_ID,
|
|
cases,
|
|
counts,
|
|
fixture_file_count: new Set(cases.flatMap((item) => item.fixture.map((file) => file.path))).size,
|
|
boundaries: [
|
|
"sampler/streamer/loadusr and realtime compiler paths remain explicit host/resource boundaries",
|
|
"userspace and dynamic .comp execution is HOST_EXCLUDED; relative-header compiler is SOURCE_BLOCKED",
|
|
"Worker static HAL behavior is compared with native/WASM SimHAL evidence by the report generator",
|
|
],
|
|
};
|
|
output.aggregate_hash = resultHash({ cases: output.cases, counts: output.counts });
|
|
console.log(JSON.stringify(output));
|
|
}
|
|
|
|
await main();
|