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