128 lines
4.5 KiB
TypeScript
128 lines
4.5 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { SemanticProgramIr } from "../../src/grl/ir/index.js";
|
|
import { IrExecutionRuntime, VirtualControllerStateMachine } from "../../src/controller/index.js";
|
|
|
|
function program(): SemanticProgramIr {
|
|
return {
|
|
moduleName: "Main",
|
|
symbols: [],
|
|
semanticChecks: [],
|
|
paths: [],
|
|
operations: [],
|
|
diagnostics: [],
|
|
sourceMap: [
|
|
{
|
|
kind: "CALL",
|
|
id: "call_helper",
|
|
procedureId: "main",
|
|
sourceMap: { file: "main.grl", line: 2, column: 5 }
|
|
},
|
|
{
|
|
kind: "ALARM",
|
|
id: "helper_alarm",
|
|
procedureId: "helper",
|
|
sourceMap: { file: "main.grl", line: 6, column: 5 }
|
|
}
|
|
],
|
|
procedures: [
|
|
{
|
|
name: "main",
|
|
sourceMap: { file: "main.grl", line: 1, column: 1 },
|
|
instructions: [
|
|
{
|
|
kind: "RAW_STATEMENT",
|
|
text: "ready = true",
|
|
sourceMap: { file: "main.grl", line: 1, column: 5 }
|
|
},
|
|
{
|
|
kind: "CALL",
|
|
target: "helper",
|
|
args: [],
|
|
sourceMap: { file: "main.grl", line: 2, column: 5 }
|
|
},
|
|
{
|
|
kind: "RETURN",
|
|
sourceMap: { file: "main.grl", line: 3, column: 5 }
|
|
}
|
|
]
|
|
},
|
|
{
|
|
name: "helper",
|
|
sourceMap: { file: "main.grl", line: 5, column: 1 },
|
|
instructions: [
|
|
{
|
|
kind: "ALARM",
|
|
alarmId: "A1",
|
|
message: "helper alarm",
|
|
severity: "warning",
|
|
sourceMap: { file: "main.grl", line: 6, column: 5 }
|
|
},
|
|
{
|
|
kind: "RETURN",
|
|
sourceMap: { file: "main.grl", line: 7, column: 5 }
|
|
}
|
|
]
|
|
}
|
|
],
|
|
kdlBridge: {
|
|
motionRequests: [],
|
|
pathRequests: []
|
|
}
|
|
};
|
|
}
|
|
|
|
describe("virtual controller state machine and IR execution runtime", () => {
|
|
it("reports legal and illegal controller command transitions", () => {
|
|
const machine = new VirtualControllerStateMachine();
|
|
|
|
expect(machine.dispatch("run")).toMatchObject({
|
|
ok: false,
|
|
state: "unloaded",
|
|
diagnostic: { code: "VC_INVALID_STATE_TRANSITION" }
|
|
});
|
|
expect(machine.dispatch("load")).toMatchObject({ ok: true, previous: "unloaded", state: "stopped" });
|
|
expect(machine.dispatch("run")).toMatchObject({ ok: true, previous: "stopped", state: "running" });
|
|
expect(machine.dispatch("pause")).toMatchObject({ ok: true, previous: "running", state: "paused" });
|
|
expect(machine.dispatch("step")).toMatchObject({ ok: true, previous: "paused", state: "paused" });
|
|
expect(machine.dispatch("stop")).toMatchObject({ ok: true, previous: "paused", state: "stopped" });
|
|
expect(machine.snapshot().diagnostics).toHaveLength(1);
|
|
});
|
|
|
|
it("executes PC, calls, scopes, alarms, trace, source map, and breakpoints", () => {
|
|
const runtime = new IrExecutionRuntime();
|
|
runtime.load(program());
|
|
|
|
expect(runtime.snapshot()).toMatchObject({
|
|
procedure: "main",
|
|
pc: 0,
|
|
scopeStack: [{ id: "global" }, { id: "main" }]
|
|
});
|
|
|
|
runtime.setBreakpoint({ id: "helper-alarm", procedure: "helper", source: { line: 6 }, enabled: true });
|
|
expect(runtime.step()).toMatchObject({ status: "executed", instruction: { kind: "RAW_STATEMENT" } });
|
|
expect(runtime.getVariable("ready")).toBe(true);
|
|
expect(runtime.step()).toMatchObject({ status: "executed", instruction: { kind: "CALL" } });
|
|
expect(runtime.snapshot()).toMatchObject({
|
|
procedure: "helper",
|
|
pc: 0,
|
|
callStack: [expect.objectContaining({ procedure: "helper", returnTo: { procedure: "main", pc: 2 } })]
|
|
});
|
|
|
|
expect(runtime.step()).toMatchObject({ status: "breakpoint", instruction: { kind: "ALARM" } });
|
|
expect(runtime.removeBreakpoint("helper-alarm")).toBe(true);
|
|
expect(runtime.step()).toMatchObject({ status: "executed", instruction: { kind: "ALARM" } });
|
|
expect(runtime.snapshot().alarmQueue).toEqual([
|
|
expect.objectContaining({ id: "A1", message: "helper alarm", severity: "warning" })
|
|
]);
|
|
expect(runtime.currentSource()).toMatchObject({ sourceMap: { line: 7 } });
|
|
expect(runtime.run()).toMatchObject({ status: "completed" });
|
|
expect(runtime.snapshot().trace).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({ kind: "load" }),
|
|
expect.objectContaining({ kind: "breakpoint", data: { breakpointId: "helper-alarm" } }),
|
|
expect.objectContaining({ kind: "alarm", data: { alarmId: "A1", severity: "warning" } })
|
|
])
|
|
);
|
|
});
|
|
});
|