from __future__ import annotations from pathlib import Path import tempfile import unittest from tests.python.support.paths import add_repo_path add_repo_path("skills/srdf/scripts") from srdf import cli SAMPLE_URDF = """\ """ SAMPLE_SRDF = """\ """ def write_source(path: Path, payload: object | None = None, *, function_name: str = "gen_srdf") -> None: if payload is None: payload = {"xml": SAMPLE_SRDF, "urdf": "robot.urdf"} path.write_text( f"def {function_name}():\n" f" return {payload!r}\n", encoding="utf-8", ) class SrdfCliTests(unittest.TestCase): def test_writes_sibling_srdf_without_hidden_artifact(self) -> None: with tempfile.TemporaryDirectory(prefix="tmp-gen-srdf-") as tempdir: root = Path(tempdir) (root / "robot.urdf").write_text(SAMPLE_URDF, encoding="utf-8") source_path = root / "robot_srdf.py" write_source(source_path) self.assertEqual(0, cli.generate_srdf_targets([str(source_path)])) output_path = root / "robot_srdf.srdf" self.assertTrue(output_path.is_file()) output_text = output_path.read_text(encoding="utf-8") self.assertIn("tcad:urdf", output_text) self.assertIn("path=\"robot.urdf\"", output_text) self.assertIn("", output_text) self.assertFalse((root / ".robot.urdf").exists()) def test_injects_tcad_urdf_link(self) -> None: srdf = """\ """ with tempfile.TemporaryDirectory(prefix="tmp-gen-srdf-") as tempdir: root = Path(tempdir) (root / "robot.urdf").write_text(SAMPLE_URDF, encoding="utf-8") source_path = root / "robot_srdf.py" write_source(source_path, {"xml": srdf, "urdf": "robot.urdf"}) self.assertEqual(0, cli.generate_srdf_targets([str(source_path)])) output_text = (root / "robot_srdf.srdf").read_text(encoding="utf-8") self.assertIn("tcad:urdf", output_text) self.assertIn("path=\"robot.urdf\"", output_text) def test_updates_legacy_explorer_urdf_link(self) -> None: legacy_srdf = SAMPLE_SRDF.replace( 'xmlns:tcad="https://text-to-cad.dev/srdf"', 'xmlns:explorer="https://text-to-cad.dev/explorer"', ).replace("tcad:urdf", "explorer:urdf") with tempfile.TemporaryDirectory(prefix="tmp-gen-srdf-") as tempdir: root = Path(tempdir) (root / "robot.urdf").write_text(SAMPLE_URDF, encoding="utf-8") source_path = root / "robot_srdf.py" write_source(source_path, {"xml": legacy_srdf, "urdf": "robot.urdf"}) self.assertEqual(0, cli.generate_srdf_targets([str(source_path)])) output_text = (root / "robot_srdf.srdf").read_text(encoding="utf-8") self.assertIn("tcad:urdf", output_text) self.assertNotIn("explorer:urdf", output_text) self.assertIn("path=\"robot.urdf\"", output_text) def test_writes_srdf_from_element_root(self) -> None: with tempfile.TemporaryDirectory(prefix="tmp-gen-srdf-") as tempdir: root = Path(tempdir) (root / "robot.urdf").write_text(SAMPLE_URDF, encoding="utf-8") source_path = root / "robot_srdf.py" source_path.write_text( "\n".join( [ "import xml.etree.ElementTree as ET", "", "def gen_srdf():", " robot = ET.Element('robot', {'name': 'sample'})", " group = ET.SubElement(robot, 'group', {'name': 'arm'})", " ET.SubElement(group, 'joint', {'name': 'shoulder'})", " ET.SubElement(group, 'joint', {'name': 'elbow'})", " return {'xml': robot, 'urdf': 'robot.urdf'}", "", ] ), encoding="utf-8", ) self.assertEqual(0, cli.generate_srdf_targets([str(source_path)])) output_text = (root / "robot_srdf.srdf").read_text(encoding="utf-8") self.assertTrue(output_text.startswith('\n')) self.assertIn("tcad:urdf", output_text) self.assertIn("", output_text) self.assertIn("", output_text) def test_supports_output_option(self) -> None: with tempfile.TemporaryDirectory(prefix="tmp-gen-srdf-") as tempdir: root = Path(tempdir) (root / "robot.urdf").write_text(SAMPLE_URDF, encoding="utf-8") source_path = root / "source.py" output_path = root / "out" / "robot.srdf" write_source(source_path) self.assertEqual(0, cli.generate_srdf_targets([str(source_path)], output=str(output_path))) self.assertTrue(output_path.is_file()) def test_supports_source_output_pairs(self) -> None: with tempfile.TemporaryDirectory(prefix="tmp-gen-srdf-") as tempdir: root = Path(tempdir) (root / "robot.urdf").write_text(SAMPLE_URDF, encoding="utf-8") source_path = root / "source.py" output_path = root / "out" / "robot.srdf" write_source(source_path) self.assertEqual(0, cli.generate_srdf_targets([f"{source_path}={output_path}"])) self.assertTrue(output_path.is_file()) def test_rejects_missing_gen_srdf(self) -> None: with tempfile.TemporaryDirectory(prefix="tmp-gen-srdf-") as tempdir: root = Path(tempdir) (root / "robot.urdf").write_text(SAMPLE_URDF, encoding="utf-8") source_path = root / "source.py" write_source(source_path, function_name="gen_urdf") with self.assertRaisesRegex(RuntimeError, "gen_srdf"): cli.generate_srdf_targets([str(source_path)]) def test_rejects_missing_xml_or_urdf(self) -> None: with tempfile.TemporaryDirectory(prefix="tmp-gen-srdf-") as tempdir: root = Path(tempdir) source_path = root / "source.py" write_source(source_path, {"xml": SAMPLE_SRDF}) with self.assertRaisesRegex(TypeError, "urdf"): cli.generate_srdf_targets([str(source_path)]) write_source(source_path, {"urdf": "robot.urdf"}) with self.assertRaisesRegex(TypeError, "xml"): cli.generate_srdf_targets([str(source_path)]) def test_rejects_invalid_xml_or_urdf_reference(self) -> None: with tempfile.TemporaryDirectory(prefix="tmp-gen-srdf-") as tempdir: root = Path(tempdir) (root / "robot.urdf").write_text(SAMPLE_URDF, encoding="utf-8") source_path = root / "source.py" write_source(source_path, {"xml": "", "urdf": "robot.urdf"}) with self.assertRaisesRegex(Exception, "root element must be "): cli.generate_srdf_targets([str(source_path)]) write_source(source_path, {"xml": SAMPLE_SRDF, "urdf": "missing.urdf"}) with self.assertRaisesRegex(FileNotFoundError, "urdf file does not exist"): cli.generate_srdf_targets([str(source_path)]) def test_rejects_group_state_values_outside_group_or_limits(self) -> None: with tempfile.TemporaryDirectory(prefix="tmp-gen-srdf-") as tempdir: root = Path(tempdir) (root / "robot.urdf").write_text(SAMPLE_URDF, encoding="utf-8") source_path = root / "source.py" write_source( source_path, { "xml": SAMPLE_SRDF.replace('', ''), "urdf": "robot.urdf", }, ) with self.assertRaisesRegex(Exception, "above its URDF upper limit"): cli.generate_srdf_targets([str(source_path)]) write_source( source_path, { "xml": SAMPLE_SRDF.replace('', ''), "urdf": "robot.urdf", }, ) with self.assertRaisesRegex(Exception, "not in group"): cli.generate_srdf_targets([str(source_path)]) def test_rejects_overlapping_end_effector_group(self) -> None: srdf = SAMPLE_SRDF.replace('', '') with tempfile.TemporaryDirectory(prefix="tmp-gen-srdf-") as tempdir: root = Path(tempdir) (root / "robot.urdf").write_text(SAMPLE_URDF, encoding="utf-8") source_path = root / "source.py" write_source(source_path, {"xml": srdf, "urdf": "robot.urdf"}) with self.assertRaisesRegex(Exception, "shares link"): cli.generate_srdf_targets([str(source_path)]) def test_rejects_disabled_collision_without_reason(self) -> None: srdf = SAMPLE_SRDF.replace(' reason="Adjacent"', "") with tempfile.TemporaryDirectory(prefix="tmp-gen-srdf-") as tempdir: root = Path(tempdir) (root / "robot.urdf").write_text(SAMPLE_URDF, encoding="utf-8") source_path = root / "source.py" write_source(source_path, {"xml": srdf, "urdf": "robot.urdf"}) with self.assertRaisesRegex(Exception, "requires a reason"): cli.generate_srdf_targets([str(source_path)]) def test_output_must_be_srdf(self) -> None: with self.assertRaisesRegex(ValueError, "must end in .srdf"): cli.generate_srdf_targets(["source.py=out.xml"]) if __name__ == "__main__": unittest.main()