133 lines
6.5 KiB
TypeScript
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);
|
|
});
|
|
});
|