Files
KDL_WORK/kdl-wasm/web/tests/controller/virtualController.test.ts
2026-06-28 08:20:33 +08:00

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" } })
])
);
});
});