import tempfile import unittest from pathlib import Path from sdf.source import SdfSourceError, parse_sdf_xml, read_sdf_source class SdfSourceTests(unittest.TestCase): def setUp(self) -> None: self._tempdir = tempfile.TemporaryDirectory(prefix="tmp-sdf-source-") self.temp_root = Path(self._tempdir.name) def tearDown(self) -> None: self._tempdir.cleanup() def _file_ref(self, name: str) -> str: return (self.temp_root / f"{name}.sdf").resolve().as_posix() def _write_mesh(self, name: str) -> Path: mesh_path = self.temp_root / "meshes" / name mesh_path.parent.mkdir(parents=True, exist_ok=True) mesh_path.write_text("solid empty\nendsolid empty\n", encoding="utf-8") return mesh_path def _write_sdf(self, name: str, body: str) -> Path: sdf_path = self.temp_root / f"{name}.sdf" sdf_path.write_text(body.strip() + "\n", encoding="utf-8") return sdf_path def test_read_sdf_source_accepts_minimal_model(self) -> None: source_path = self._write_sdf( "robot", """ """, ) source = read_sdf_source(source_path) self.assertEqual(self._file_ref("robot"), source.file_ref) self.assertEqual("1.12", source.version) self.assertEqual(("sample",), source.model_names) self.assertEqual((), source.world_names) self.assertEqual(("base_link",), source.links) self.assertEqual(0, len(source.joints)) def test_parse_sdf_xml_validates_without_existing_output_file(self) -> None: output_path = self.temp_root / "generated" / "robot.sdf" source = parse_sdf_xml( """ """, source_path=output_path, base_dir=output_path.parent, ) self.assertEqual(output_path.resolve().as_posix(), source.file_ref) self.assertEqual(("sample",), source.model_names) self.assertFalse(output_path.exists()) def test_read_sdf_source_accepts_local_mesh_references(self) -> None: mesh_path = self._write_mesh("base.stl") source_path = self._write_sdf( "robot", """ meshes/base.stl meshes/base.stl """, ) source = read_sdf_source(source_path) self.assertEqual((mesh_path.resolve(), mesh_path.resolve()), source.mesh_paths) self.assertEqual((mesh_path.resolve(),), source.visual_mesh_paths) self.assertEqual((mesh_path.resolve(),), source.collision_mesh_paths) def test_read_sdf_source_accepts_external_mesh_uri_without_resolution(self) -> None: source_path = self._write_sdf( "robot", """ model://sample/meshes/base.dae """, ) source = read_sdf_source(source_path) self.assertEqual((), source.mesh_paths) def test_read_sdf_source_accepts_world_models(self) -> None: source_path = self._write_sdf( "world", """ """, ) source = read_sdf_source(source_path) self.assertEqual(("sample_world",), source.world_names) self.assertEqual(("sample",), source.model_names) def test_read_sdf_source_accepts_world_only_scene(self) -> None: source_path = self._write_sdf( "world_only", """ model://sun """, ) source = read_sdf_source(source_path) self.assertEqual(("sample_world",), source.world_names) self.assertEqual((), source.model_names) def test_read_sdf_source_rejects_missing_root(self) -> None: source_path = self._write_sdf( "robot", """ """, ) with self.assertRaisesRegex(SdfSourceError, "root element must be "): read_sdf_source(source_path) def test_read_sdf_source_rejects_missing_version(self) -> None: source_path = self._write_sdf( "robot", """ """, ) with self.assertRaisesRegex(SdfSourceError, "version is required"): read_sdf_source(source_path) def test_read_sdf_source_rejects_missing_model_name(self) -> None: source_path = self._write_sdf( "robot", """ """, ) with self.assertRaisesRegex(SdfSourceError, "model name is required"): read_sdf_source(source_path) def test_read_sdf_source_rejects_duplicate_links(self) -> None: source_path = self._write_sdf( "robot", """ """, ) with self.assertRaisesRegex(SdfSourceError, "duplicates"): read_sdf_source(source_path) def test_read_sdf_source_rejects_missing_joint_child_link(self) -> None: source_path = self._write_sdf( "robot", """ base_link missing_link """, ) with self.assertRaisesRegex(SdfSourceError, "missing link"): read_sdf_source(source_path) def test_read_sdf_source_rejects_duplicate_joints(self) -> None: source_path = self._write_sdf( "robot", """ base_link arm_link base_link arm_link """, ) with self.assertRaisesRegex(SdfSourceError, "duplicates"): read_sdf_source(source_path) def test_read_sdf_source_rejects_missing_local_mesh(self) -> None: source_path = self._write_sdf( "robot", """ meshes/missing.stl """, ) with self.assertRaisesRegex(SdfSourceError, "missing mesh file"): read_sdf_source(source_path) if __name__ == "__main__": unittest.main()