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