Files
KDL_WORK/kdl-wasm/web/tests/integration/abb120SpecSuite.test.ts
2026-06-28 20:58:18 +08:00

133 lines
6.5 KiB
TypeScript

import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
import {
runAbb120SpecSuite,
type Abb120SpecManifest,
type Abb120SpecProgramSource
} from "../../src/suites/abb120SpecSuite.js";
const WEB_ROOT = join(dirname(fileURLToPath(import.meta.url)), "../..");
const SPEC_ROOT = join(WEB_ROOT, "tests", "fixtures", "abb120", "spec-programs");
function loadManifest(): Abb120SpecManifest {
return JSON.parse(readFileSync(join(SPEC_ROOT, "manifest.json"), "utf8")) as Abb120SpecManifest;
}
function loadPrograms(manifest = loadManifest()): Abb120SpecProgramSource[] {
return manifest.programs.map((entry) => ({
program_id: entry.program_id,
file: entry.file,
text: readFileSync(join(SPEC_ROOT, entry.file), "utf8")
}));
}
describe("working2 ABB120 GRL spec suite", () => {
it("maps grammar sections 4 through 25 to concrete Runtime, Static, or Post programs", () => {
const manifest = loadManifest();
const sections = manifest.coverage_matrix.map((item) => item.spec_section);
expect(sections).toEqual(Array.from({ length: 22 }, (_value, index) => index + 4));
expect(manifest.programs).toHaveLength(19);
expect(manifest.programs.filter((program) => program.coverage_level === "Runtime")).toHaveLength(12);
expect(manifest.programs.filter((program) => program.coverage_level === "Static")).toHaveLength(4);
expect(manifest.programs.filter((program) => program.coverage_level === "Post")).toHaveLength(3);
const programById = new Map(manifest.programs.map((program) => [program.program_id, program]));
for (const item of manifest.coverage_matrix) {
expect(item.program_ids.length).toBeGreaterThan(0);
for (const programId of item.program_ids) {
const program = programById.get(programId);
expect(program, `${item.spec_section} references missing ${programId}`).toBeDefined();
expect(
item.coverage_level.split("/"),
`${item.spec_section} coverage level must include ${programId}`
).toContain(program!.coverage_level);
}
}
});
it("builds a W2 job with compile, controller, queue, trace, IO, post, and roundtrip evidence", () => {
const manifest = loadManifest();
const job = runAbb120SpecSuite(manifest, loadPrograms(manifest), {
now: "2026-06-28T01:30:00.000Z",
gitRevision: "test-revision"
});
expect(job.job_id).toMatch(/^W2-JOB-20260628013000-[a-f0-9]{8}$/);
expect(job.suite_id).toBe("working2-abb120-spec");
expect(job.robot_id).toBe("abb_irb120_3_58");
expect(job.coverage.missingSections).toEqual([]);
expect(job.programs.map((program) => program.program_id)).toEqual(manifest.programs.map((program) => program.program_id));
const runtime = job.programs.filter((program) => program.coverage_level === "Runtime");
expect(runtime.every((program) => program.compile.parsed)).toBe(true);
expect(runtime.every((program) => program.compile.sourceMapEntries > 0)).toBe(true);
expect(runtime.every((program) => program.controller?.commands.map((command) => command.command).includes("loadProgram"))).toBe(true);
expect(runtime.every((program) => program.controller?.commands.map((command) => command.command).includes("resetFault"))).toBe(true);
expect(runtime.some((program) => program.program_id === "W2_60_IOWaitPulse" && Object.keys(program.io?.image ?? {}).length > 0)).toBe(true);
expect(runtime.some((program) => (program.trace ?? []).some((event) => event.kind === "wait"))).toBe(true);
expect(runtime.some((program) => (program.motionQueue?.items.length ?? 0) > 0)).toBe(true);
expect(runtime.some((program) =>
program.program_id === "W2_99_FullSpecExample" &&
program.compile.pathCount === 2 &&
program.compile.operationCount === 2 &&
(program.motionQueue?.items.some((item) => item.source.kind === "path" && item.source.operationId === "pick_op") ?? false)
)).toBe(true);
expect(runtime.every((program) => (program.trajectory?.sampleCount ?? 0) >= 0)).toBe(true);
expect(runtime.some((program) =>
(program.trajectory?.frames ?? []).some((frame) =>
frame.joints.length === 6 &&
frame.tcp.position.length === 3 &&
frame.tcp.quaternion.length === 4
)
)).toBe(true);
for (const program of job.programs) {
const expected = manifest.programs.find((entry) => entry.program_id === program.program_id)!;
expect(program.status, program.program_id).toBe(expected.expected_status);
const codes = program.diagnostics.map((diagnostic) => diagnostic.code);
for (const code of expected.expected_diagnostics) {
expect(codes, program.program_id).toContain(code);
}
expect(codes, program.program_id).not.toContain("W2_EXPECTED_DIAGNOSTIC_MISSING");
expect(codes, program.program_id).not.toContain("W2_EXPECTED_STATUS_MISMATCH");
}
const diagnostics = Object.fromEntries(job.programs.map((program) => [
program.program_id,
program.diagnostics.map((diagnostic) => diagnostic.code)
]));
expect(diagnostics.W2_E10_ExpressionDiagnostics).toEqual(expect.arrayContaining([
"GRL_EXPR_UNKNOWN_SYMBOL",
"GRL_EXPR_UNKNOWN_FUNCTION",
"GRL_EXPR_ARITY",
"GRL_EXPR_DOMAIN",
"GRL_EXPR_DIV_ZERO",
"GRL_EXPR_UNIT_MISMATCH"
]));
expect(diagnostics.W2_E20_SemanticDiagnostics).toEqual(expect.arrayContaining([
"GRL_SYMBOL_DUPLICATE",
"GRL_ARGUMENT_NOT_LVALUE"
]));
expect(diagnostics.W2_E30_MotionDiagnostics).toEqual(expect.arrayContaining([
"GRL_PATH_EMPTY",
"KDL_JOINT_LIMIT",
"KDL_TARGET_UNREACHABLE"
]));
expect(diagnostics.W2_E40_ControlFlowDiagnostics).toEqual(expect.arrayContaining([
"GRL_BREAK_OUTSIDE_FLOW"
]));
expect(diagnostics.W2_E50_RuntimeTimeout).toEqual(expect.arrayContaining([
"VC_WAIT_TIMEOUT"
]));
const post = job.programs.filter((program) => program.coverage_level === "Post");
expect(post.every((program) => program.post?.filenames.some((filename) => filename.endsWith(".mod")))).toBe(true);
expect(post.every((program) => program.post?.filenames.some((filename) => filename.endsWith(".ls")))).toBe(true);
expect(post.every((program) => program.post?.filenames.some((filename) => filename.endsWith(".src")))).toBe(true);
expect(post.every((program) => program.roundtrip?.status.match(/pass|warn/))).toBe(true);
expect(job.artifacts.jobJson).toContain(job.job_id);
});
});