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

196 lines
6.6 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { SemanticProgramIr } from "../../src/grl/ir/index.js";
import type { PathPlanResult, TrajectoryPoint } from "../../src/kdl/types.js";
import { IrExecutionRuntime } from "../../src/controller/index.js";
import { IoImageRuntime, MotionQueue, type MotionPlanner } from "../../src/runtime/index.js";
import { DebugFacade, WorkbenchFacade } from "../../src/workbench/index.js";
const POSE = {
position: [0, 0, 0] as [number, number, number],
quaternion: [0, 0, 0, 1] as [number, number, number, number]
};
function point(index: number, time: number, joints: number[]): TrajectoryPoint {
return {
index,
time,
dt: time,
s: time,
sd: 1,
sdd: 0,
joints,
jointVelocity: [0],
jointAcceleration: [0],
flange: POSE,
tcp: POSE,
motion: "MOVEJ",
diagnostics: []
};
}
function program(): SemanticProgramIr {
return {
moduleName: "Main",
symbols: [{ kind: "path", name: "path1" }, { kind: "operation", name: "op1" }],
semanticChecks: [],
diagnostics: [],
sourceMap: [
{ kind: "RUN_PATH", id: "run_path", procedureId: "main", pathId: "path1", sourceMap: { file: "main.grl", line: 5 } },
{ kind: "path_point", id: "p1", pathId: "path1", pointId: "p1", sourceMap: { file: "main.grl", line: 10 } },
{ kind: "operation_action", id: "op1_start_action_0", operationId: "op1", sourceMap: { file: "main.grl", line: 20 } }
],
procedures: [
{
name: "main",
sourceMap: { file: "main.grl", line: 1 },
instructions: [
{ kind: "RAW_STATEMENT", text: "part_ready = true", sourceMap: { file: "main.grl", line: 4 } },
{ kind: "RUN_PATH", pathId: "path1", sourceMap: { file: "main.grl", line: 5 } }
]
}
],
paths: [
{
pathId: "path1",
request: {
pathId: "path1",
startJoints: [0],
sampleTime: 0.1,
segments: [
{
id: "p1",
motion: "MOVEJ",
target: { joints: [1] },
speed: { kind: "joint_abs", velocity: 1 },
zone: { kind: "fine" },
sourceMap: { file: "main.grl", line: 10 }
}
]
},
motions: [
{
kind: "MOVEJ",
id: "p1",
pathId: "path1",
pointId: "p1",
target: { joints: [1] },
speed: { kind: "joint_abs", velocity: 1 },
zone: { kind: "fine" },
sourceMap: { file: "main.grl", line: 10 },
source: { brand: { vendor: "KUKA", file: "src.src", line: 99 } }
}
],
events: []
}
],
operations: [
{
operationId: "op1",
kind: "handling",
pathId: "path1",
process: {},
startActions: [],
endActions: []
}
],
kdlBridge: { motionRequests: [], pathRequests: [] }
};
}
describe("debug and workbench facades", () => {
it("models breakpoints, motion breakpoints, watches, replay, and cross-source lookup", async () => {
const ir = program();
const runtime = new IrExecutionRuntime();
runtime.load(ir);
runtime.step();
const planner: MotionPlanner = {
planPath: (_handle, request): PathPlanResult => ({
ok: true,
duration: 1,
segments: [],
points: [point(0, 0, request.startJoints), point(1, 1, [1])],
diagnostics: []
})
};
const queue = new MotionQueue({ startJoints: [0], sampleTime: 0.1, planner });
const item = queue.enqueueRunPath({ kind: "RUN_PATH", pathId: "path1", sourceMap: { file: "main.grl", line: 5 } }, ir.paths[0]!);
await queue.planAll();
queue.advance(1);
const io = new IoImageRuntime();
const debug = new DebugFacade({ runtime, motionQueue: queue, io, program: ir });
debug.addBreakpoint({ id: "bp-run-path", procedure: "main", source: { line: 5 }, enabled: true });
debug.addMotionBreakpoint({ id: "mb-path", pathId: "path1", enabled: true });
const watch = debug.addWatch("watch-ready", "part_ready");
expect(watch).toMatchObject({ value: true, source: { sourceMap: { line: 5 } } });
expect(debug.checkMotionBreakpoint(item)).toMatchObject({ id: "mb-path" });
expect(debug.locatePathPoint("path1", "p1")).toMatchObject({ sourceMap: { line: 10 } });
expect(debug.locateOperation("op1")).toMatchObject([{ sourceMap: { line: 20 } }]);
expect(debug.playbackAt(1)).toMatchObject({ point: { joints: [1] } });
expect(debug.replayTrace()).toEqual(expect.arrayContaining([expect.objectContaining({ kind: "load" })]));
expect(debug.snapshot()).toMatchObject({
breakpoints: [expect.objectContaining({ id: "bp-run-path" })],
motionBreakpoints: [expect.objectContaining({ id: "mb-path" })],
watches: [expect.objectContaining({ id: "watch-ready", value: true })]
});
});
it("builds object tree, editor, teach pendant, IO panel, and report entries", async () => {
const ir = program();
const runtime = new IrExecutionRuntime();
runtime.load(ir);
runtime.step();
const queue = new MotionQueue({
startJoints: [0],
sampleTime: 0.1,
planner: {
planPath: (_handle, request): PathPlanResult => ({
ok: true,
duration: 1,
segments: [],
points: [point(0, 0, request.startJoints), point(1, 1, [1])],
diagnostics: []
})
}
});
queue.enqueueRunPath({ kind: "RUN_PATH", pathId: "path1" }, ir.paths[0]!);
await queue.planAll();
queue.advance(1);
const io = new IoImageRuntime();
io.write({ domain: "do", index: 1, raw: "io.do[1]" }, true);
const workbench = new WorkbenchFacade();
const snapshot = workbench.snapshot({
program: ir,
runtime: runtime.snapshot(),
io: io.snapshot(),
motion: queue.snapshot(),
controllerState: "paused"
});
expect(snapshot.objectTree[0]).toMatchObject({
kind: "station",
children: [
expect.objectContaining({ id: "programs" }),
expect.objectContaining({ id: "paths" }),
expect.objectContaining({ id: "operations" }),
expect.objectContaining({ id: "reports" })
]
});
expect(snapshot.editor).toMatchObject({ activeFile: "main.grl", cursor: { line: 5 } });
expect(snapshot.teachPendant).toMatchObject({ state: "paused", procedure: "main", joints: [1] });
expect(snapshot.ioPanel.image).toMatchObject({ "io.do[1]": true });
expect(snapshot.reports.map((report) => report.kind)).toEqual([
"reachability",
"cycle_time",
"io_wait",
"post",
"import"
]);
});
});