89 lines
3.2 KiB
TypeScript
89 lines
3.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { IoImageRuntime, ioKey } from "../../src/runtime/index.js";
|
|
import type { IoReference, PulseInstruction, WaitInstruction } from "../../src/grl/ir/index.js";
|
|
|
|
const DO1: IoReference = { domain: "do", index: 1, raw: "io.do[1]" };
|
|
const DI1: IoReference = { domain: "di", index: 1, raw: "io.di[1]" };
|
|
const DI2: IoReference = { domain: "di", index: 2, raw: "io.di[2]" };
|
|
const AI1: IoReference = { domain: "ai", index: 1, raw: "io.ai[1]" };
|
|
|
|
describe("virtual IO image, waits, pulses, edges, and scripts", () => {
|
|
it("enforces write permissions and records IO events", () => {
|
|
const io = new IoImageRuntime({
|
|
permissions: [
|
|
{ writer: "program", domains: ["do"], access: "write" },
|
|
{ writer: "script", domains: ["di", "ai"], access: "write" }
|
|
]
|
|
});
|
|
|
|
expect(io.write(DO1, true, "program")).toMatchObject({ kind: "write", value: true });
|
|
expect(io.write(DI1, true, "program")).toBeUndefined();
|
|
expect(io.snapshot()).toMatchObject({
|
|
image: { [ioKey(DO1)]: true },
|
|
diagnostics: [expect.objectContaining({ code: "VC_IO_PERMISSION_DENIED" })]
|
|
});
|
|
});
|
|
|
|
it("evaluates waits, timeouts, on_timeout hold-stop, and edge conditions", () => {
|
|
const io = new IoImageRuntime({
|
|
permissions: [{ writer: "script", domains: ["di", "ai"], access: "write" }]
|
|
});
|
|
const wait: WaitInstruction = {
|
|
kind: "WAIT",
|
|
condition: "all ( io . di [ 1 ] == true , io . di [ 2 ] == false )",
|
|
timeout: 1,
|
|
onTimeout: { kind: "call", value: "recover" }
|
|
};
|
|
|
|
expect(io.evaluateWait(wait, 0)).toMatchObject({ status: "waiting" });
|
|
expect(io.evaluateWait(wait, 1)).toMatchObject({
|
|
status: "hold-stop",
|
|
onTimeout: { kind: "call", value: "recover" },
|
|
diagnostic: { code: "VC_WAIT_TIMEOUT" }
|
|
});
|
|
|
|
io.write(DI1, true, "script");
|
|
io.write(DI2, false, "script");
|
|
expect(io.evaluateWait(wait, 0)).toMatchObject({ status: "satisfied" });
|
|
expect(io.matchesCondition("rising ( io . di [ 1 ] )")).toBe(true);
|
|
io.write(DI1, false, "script");
|
|
expect(io.matchesCondition("falling ( io . di [ 1 ] )")).toBe(true);
|
|
io.write(AI1, 5, "script");
|
|
expect(io.matchesCondition("changed ( io . ai [ 1 ] )")).toBe(true);
|
|
});
|
|
|
|
it("auto-resets pulses and executes delayed IO scripts", () => {
|
|
const io = new IoImageRuntime({
|
|
permissions: [
|
|
{ writer: "program", domains: ["do"], access: "write" },
|
|
{ writer: "script", domains: ["di"], access: "write" }
|
|
]
|
|
});
|
|
const pulse: PulseInstruction = {
|
|
kind: "PULSE",
|
|
target: DO1,
|
|
duration: 0.2,
|
|
trace: [
|
|
{ time: 0, action: "set", target: DO1, value: true },
|
|
{ time: 0.2, action: "reset", target: DO1, value: false }
|
|
]
|
|
};
|
|
|
|
io.executePulse(pulse);
|
|
expect(io.read(DO1)).toBe(true);
|
|
io.advance(0.2);
|
|
expect(io.read(DO1)).toBe(false);
|
|
|
|
io.addScript("part-arrival", [{ delay: 0.5, target: DI1, value: true }]);
|
|
io.advance(0.49);
|
|
expect(io.read(DI1)).toBeUndefined();
|
|
io.advance(0.01);
|
|
expect(io.read(DI1)).toBe(true);
|
|
expect(io.snapshot().events.map((event) => event.kind)).toEqual([
|
|
"pulse_set",
|
|
"pulse_reset",
|
|
"script"
|
|
]);
|
|
});
|
|
});
|