Upload project files
This commit is contained in:
195
kdl-wasm/web/tests/workbench/debugWorkbench.test.ts
Normal file
195
kdl-wasm/web/tests/workbench/debugWorkbench.test.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
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"
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user