# Copyright 2026 DeepMind Technologies Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== import textwrap from absl.testing import absltest import mujoco SIMPLE_XML = b"" XML_WITH_MESH = textwrap.dedent("""\ """).encode() BOX_OBJ = textwrap.dedent("""\ v -1 -1 -1 v 1 -1 -1 v 1 1 -1 v -1 1 -1 v -1 -1 1 v 1 -1 1 v 1 1 1 v -1 1 1 f 1 2 3 4 f 5 8 7 6 f 1 5 6 2 f 2 6 7 3 f 3 7 8 4 f 4 8 5 1 """).encode() class VfsLifecycleTest(absltest.TestCase): def test_create_and_close(self): vfs = mujoco.MjVfs() vfs.close() def test_context_manager(self): with mujoco.MjVfs() as vfs: self.assertIsNotNone(vfs) def test_double_close_is_safe(self): vfs = mujoco.MjVfs() vfs.close() vfs.close() def test_operations_after_close_raise(self): vfs = mujoco.MjVfs() vfs.close() with self.assertRaises(RuntimeError): vfs["model.xml"] = SIMPLE_XML class VfsBufferTest(absltest.TestCase): def test_add_buffer(self): with mujoco.MjVfs() as vfs: vfs["model.xml"] = SIMPLE_XML self.assertIn("model.xml", vfs) def test_add_duplicate_raises(self): with mujoco.MjVfs() as vfs: vfs["model.xml"] = SIMPLE_XML with self.assertRaises(ValueError): vfs["model.xml"] = SIMPLE_XML def test_delete_buffer(self): with mujoco.MjVfs() as vfs: vfs["model.xml"] = SIMPLE_XML self.assertIn("model.xml", vfs) del vfs["model.xml"] self.assertNotIn("model.xml", vfs) def test_delete_missing_raises(self): with mujoco.MjVfs() as vfs: with self.assertRaises(KeyError): del vfs["nonexistent"] def test_contains_missing(self): with mujoco.MjVfs() as vfs: self.assertNotIn("nonexistent", vfs) class VfsCompileTest(absltest.TestCase): def test_compile_simple_model(self): with mujoco.MjVfs() as vfs: vfs["model.xml"] = SIMPLE_XML model = mujoco.MjModel.from_xml_path("model.xml", vfs=vfs) self.assertEqual(model.nq, 0) def test_compile_model_with_mesh(self): with mujoco.MjVfs() as vfs: vfs["model.xml"] = XML_WITH_MESH vfs["box.obj"] = BOX_OBJ model = mujoco.MjModel.from_xml_path("model.xml", vfs=vfs) self.assertEqual(model.nmesh, 1) def test_spec_from_string_with_vfs(self): with mujoco.MjVfs() as vfs: vfs["box.obj"] = BOX_OBJ spec = mujoco.MjSpec.from_string(XML_WITH_MESH.decode(), vfs=vfs) model = spec.compile(vfs=vfs) self.assertEqual(model.nmesh, 1) def test_from_xml_string_with_vfs(self): with mujoco.MjVfs() as vfs: vfs["box.obj"] = BOX_OBJ model = mujoco.MjModel.from_xml_string( XML_WITH_MESH.decode(), vfs=vfs ) self.assertEqual(model.nmesh, 1) def test_spec_compile_with_vfs(self): with mujoco.MjVfs() as vfs: vfs["box.obj"] = BOX_OBJ spec = mujoco.MjSpec.from_string(XML_WITH_MESH.decode()) model = spec.compile(vfs=vfs) self.assertEqual(model.nmesh, 1) def test_spec_recompile_with_vfs(self): with mujoco.MjVfs() as vfs: vfs["box.obj"] = BOX_OBJ spec = mujoco.MjSpec.from_string(XML_WITH_MESH.decode()) model1 = spec.compile(vfs=vfs) self.assertEqual(model1.ngeom, 1) body = spec.worldbody.add_body() body.add_geom(size=[1, 0, 0]) model2 = spec.compile(vfs=vfs) self.assertEqual(model2.ngeom, 2) def test_long_lived_vfs_without_context(self): vfs = mujoco.MjVfs() vfs["box.obj"] = BOX_OBJ spec = mujoco.MjSpec.from_string(XML_WITH_MESH.decode()) model1 = spec.compile(vfs=vfs) self.assertEqual(model1.nmesh, 1) spec.worldbody.add_body().add_geom(size=[1, 0, 0]) model2 = spec.compile(vfs=vfs) self.assertEqual(model2.nmesh, 1) self.assertEqual(model2.ngeom, 2) vfs.close() def test_vfs_and_assets_raises(self): with mujoco.MjVfs() as vfs: vfs["model.xml"] = SIMPLE_XML with self.assertRaises(ValueError): mujoco.MjModel.from_xml_string( SIMPLE_XML.decode(), assets={"a": b"b"}, vfs=vfs ) def test_backward_compat_assets_dict(self): model = mujoco.MjModel.from_xml_string( XML_WITH_MESH.decode(), assets={"box.obj": BOX_OBJ} ) self.assertEqual(model.nmesh, 1) class VfsAttachTest(absltest.TestCase): def test_attach_shared_vfs(self): child_xml = textwrap.dedent("""\ """) parent_xml = textwrap.dedent("""\ """) with mujoco.MjVfs() as vfs: vfs["box.obj"] = BOX_OBJ parent = mujoco.MjSpec.from_string(parent_xml) child = mujoco.MjSpec.from_string(child_xml) parent.attach(child, site="mount") model = parent.compile(vfs=vfs) self.assertEqual(model.nmesh, 1) self.assertEmpty(parent.assets) def test_attach_no_asset_dict_needed(self): child_xml = textwrap.dedent("""\ """) parent_xml = textwrap.dedent("""\ """) with mujoco.MjVfs() as vfs: parent = mujoco.MjSpec.from_string(parent_xml) child = mujoco.MjSpec.from_string(child_xml) parent.attach(child, site="mount") model = parent.compile(vfs=vfs) self.assertGreater(model.nbody, 1) if __name__ == "__main__": absltest.main()