diff --git a/ur_simulation_gazebo/CMakeLists.txt b/ur_simulation_gazebo/CMakeLists.txt
index 63e3125..2feeb10 100644
--- a/ur_simulation_gazebo/CMakeLists.txt
+++ b/ur_simulation_gazebo/CMakeLists.txt
@@ -7,4 +7,10 @@ install(DIRECTORY config launch urdf
DESTINATION share/${PROJECT_NAME}
)
+if(BUILD_TESTING)
+ find_package(ament_cmake_pytest REQUIRED)
+
+ ament_add_pytest_test(description test/test_description.py)
+endif()
+
ament_package()
diff --git a/ur_simulation_gazebo/package.xml b/ur_simulation_gazebo/package.xml
index 5c5a944..2802764 100644
--- a/ur_simulation_gazebo/package.xml
+++ b/ur_simulation_gazebo/package.xml
@@ -30,6 +30,12 @@
urdf
xacro
+ ament_cmake_pytest
+ launch_testing_ament_cmake
+ launch_testing_ros
+ liburdfdom-tools
+ xacro
+
ament_cmake
diff --git a/ur_simulation_gazebo/test/test_description.py b/ur_simulation_gazebo/test/test_description.py
new file mode 100644
index 0000000..7db63b7
--- /dev/null
+++ b/ur_simulation_gazebo/test/test_description.py
@@ -0,0 +1,118 @@
+# Copyright (c) 2023 FZI Forschungszentrum Informatik
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of the {copyright_holder} nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# Author: Lukas Sackewitz
+
+import os
+import shutil
+import subprocess
+import tempfile
+import pytest
+
+from ament_index_python.packages import get_package_share_directory
+
+
+@pytest.mark.parametrize(
+ "ur_type", ["ur3", "ur3e", "ur5", "ur5e", "ur10", "ur10e", "ur16e", "ur20"]
+)
+@pytest.mark.parametrize("prefix", ["", "my_ur_"])
+def test_ur_urdf_xacro(ur_type, prefix):
+ # Initialize Arguments
+ safety_limits = "true"
+ safety_pos_margin = "0.15"
+ safety_k_position = "20"
+ # General Arguments
+ description_package = "ur_description"
+
+ joint_limit_params = os.path.join(
+ get_package_share_directory(description_package), "config", ur_type, "joint_limits.yaml"
+ )
+ kinematics_params = os.path.join(
+ get_package_share_directory(description_package),
+ "config",
+ ur_type,
+ "default_kinematics.yaml",
+ )
+ physical_params = os.path.join(
+ get_package_share_directory(description_package),
+ "config",
+ ur_type,
+ "physical_parameters.yaml",
+ )
+ visual_params = os.path.join(
+ get_package_share_directory(description_package),
+ "config",
+ ur_type,
+ "visual_parameters.yaml",
+ )
+
+ description_file_path = os.path.join(
+ get_package_share_directory("ur_simulation_gazebo"), "urdf", "ur_gazebo.urdf.xacro"
+ )
+
+ (_, tmp_urdf_output_file) = tempfile.mkstemp(suffix=".urdf")
+
+ # Compose `xacro` and `check_urdf` command
+ xacro_command = (
+ f"{shutil.which('xacro')}"
+ f" {description_file_path}"
+ f" joint_limit_params:={joint_limit_params}"
+ f" kinematics_params:={kinematics_params}"
+ f" physical_params:={physical_params}"
+ f" visual_params:={visual_params}"
+ f" safety_limits:={safety_limits}"
+ f" safety_pos_margin:={safety_pos_margin}"
+ f" safety_k_position:={safety_k_position}"
+ f" name:={ur_type}"
+ f" prefix:={prefix}"
+ f" > {tmp_urdf_output_file}"
+ )
+ check_urdf_command = f"{shutil.which('check_urdf')} {tmp_urdf_output_file}"
+
+ # Try to call processes but finally remove the temp file
+ try:
+ xacro_process = subprocess.run(
+ xacro_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True
+ )
+
+ assert xacro_process.returncode == 0, " --- XACRO command failed ---"
+
+ check_urdf_process = subprocess.run(
+ check_urdf_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True
+ )
+
+ assert (
+ check_urdf_process.returncode == 0
+ ), "\n --- URDF check failed! --- \nYour xacro does not unfold into a proper urdf robot description. Please check your xacro file."
+
+ finally:
+ os.remove(tmp_urdf_output_file)
+
+
+if __name__ == "__main__":
+ test_ur_urdf_xacro()