diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7177ceee..ed70cdff 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -65,7 +65,7 @@ jobs: strategy: matrix: python-version: ["3.9"] - loader: ["idyntree", "mujoco", "pinocchio", "pybullet", "robomeshcat", "yourdfpy"] + loader: ["idyntree", "mujoco", "pybullet", "robomeshcat", "yourdfpy"] steps: - name: "Checkout sources" @@ -85,6 +85,40 @@ jobs: run: | tox -e loader-${{ matrix.loader }} + loader-pinocchio: + name: "Loader: pinocchio with Python ${{ matrix.python-version }}" + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: ["3.9"] + + defaults: + run: + # See https://github.com/mamba-org/setup-micromamba?tab=readme-ov-file#about-login-shells + shell: bash -leo pipefail {0} + + steps: + - name: "Checkout sources" + uses: actions/checkout@v4 + + - name: "Install Conda environment with Micromamba" + uses: mamba-org/setup-micromamba@v1 + with: + micromamba-version: '1.5.8-0' + environment-name: pinocchio_test_env + create-args: >- + python=${{ matrix.python-version }} + gitpython + pinocchio>=3.1.0 + tqdm + cache-environment: true + post-cleanup: 'all' + + - name: "Run loader tests" + run: | + python -m unittest tests/loaders/test_pinocchio.py --failfast + ci_success: name: "CI success" runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index fef747fc..153d45c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,9 +25,15 @@ All notable changes to this project will be documented in this file. ### Changed +- CICD: Isolate loader tests in testing submodules +- CICD: Remove version pin on NumPy < 2 - Update `baxter_common` repository with a commit ID rather than a tag - Update `talos-data` repository with a commit ID rather than a tag +### Fixed + +- CICD: Switch to conda-forge dependencies in Pinocchio loader test environment + ## [1.16.0] - 2025-04-09 ### Added diff --git a/pyproject.toml b/pyproject.toml index b9dbf939..f569fed1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,9 +33,9 @@ keywords = ["robot", "description", "urdf", "mjcf"] [project.optional-dependencies] opts = [ - "idyntree >= 8.0.0", + "idyntree >=8.0.0", "mujoco >=3.2.0", - "pin >=2.6.10", + "pin >=3.1.0", "pybullet >=3.2.6", "robomeshcat >=1.0.4", "yourdfpy >=0.0.56", diff --git a/tests/__init__.py b/tests/__init__.py index edf9a574..c89ee7a0 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -7,13 +7,11 @@ from .test_clone_to_cache import TestCloneToCache from .test_clone_to_directory import TestCloneToDirectory from .test_descriptions import TestDescriptions -from .test_loaders import TestLoaders from .test_progress_bar import TestProgressBar __all__ = [ "TestCloneToCache", "TestCloneToDirectory", "TestDescriptions", - "TestLoaders", "TestProgressBar", ] diff --git a/tests/loaders/test_idyntree.py b/tests/loaders/test_idyntree.py index dbecadf2..8e00b356 100644 --- a/tests/loaders/test_idyntree.py +++ b/tests/loaders/test_idyntree.py @@ -7,15 +7,24 @@ import unittest from robot_descriptions._descriptions import DESCRIPTIONS +from robot_descriptions.loaders.idyntree import ( + load_robot_description as load_idyntree, +) -from robot_descriptions.loaders.idyntree import load_robot_description class TestiDynTree(unittest.TestCase): - """ Check that all descriptions are loaded properly in iDynTree. """ + def test_idyntree(self): + self.assertIsNotNone( + load_idyntree( + "upkie_description", + commit="98502d5b175c3d6b60b3cf475b7eeef9fd290c43", + ) + ) + @staticmethod def get_test_for_description(description: str): """ @@ -29,10 +38,11 @@ def get_test_for_description(description: str): """ def test(self): - load_robot_description(description) + load_idyntree(description) return test + # Add a test function for each URDF description for name, description in DESCRIPTIONS.items(): if description.has_urdf: diff --git a/tests/loaders/test_mujoco.py b/tests/loaders/test_mujoco.py index 731eecc9..9de03486 100644 --- a/tests/loaders/test_mujoco.py +++ b/tests/loaders/test_mujoco.py @@ -7,15 +7,19 @@ import unittest from robot_descriptions._descriptions import DESCRIPTIONS -from robot_descriptions.loaders.mujoco import load_robot_description +from robot_descriptions.loaders.mujoco import ( + load_robot_description as load_mujoco, +) class TestMuJoCo(unittest.TestCase): - """ Check that all MJCF descriptions are loaded properly in MuJoCo. """ + def test_mujoco(self): + self.assertIsNotNone(load_mujoco("cassie_mj_description")) + @staticmethod def get_test_for_description(description: str): """ @@ -29,7 +33,7 @@ def get_test_for_description(description: str): """ def test(self): - load_robot_description(description) + load_mujoco(description) return test diff --git a/tests/loaders/test_pinocchio.py b/tests/loaders/test_pinocchio.py index 84ae5808..889db012 100644 --- a/tests/loaders/test_pinocchio.py +++ b/tests/loaders/test_pinocchio.py @@ -7,15 +7,24 @@ import unittest from robot_descriptions._descriptions import DESCRIPTIONS -from robot_descriptions.loaders.pinocchio import load_robot_description +from robot_descriptions.loaders.pinocchio import ( + load_robot_description as load_pinocchio, +) class TestPinocchio(unittest.TestCase): - """ Check that all descriptions are loaded properly in Pinocchio. """ + def test_pinocchio(self): + self.assertIsNotNone( + load_pinocchio( + "upkie_description", + commit="98502d5b175c3d6b60b3cf475b7eeef9fd290c43", + ) + ) + @staticmethod def get_test_for_description(description: str): """ @@ -29,16 +38,24 @@ def get_test_for_description(description: str): """ def test(self): - load_robot_description(description) + load_pinocchio(description) return test -# Add a test function for each URDF description +# Add a test function for each description for name, description in DESCRIPTIONS.items(): - if description.has_urdf: - setattr( - TestPinocchio, - f"test_{name}", - TestPinocchio.get_test_for_description(name), - ) + if name == "a1_mj_description": + # See https://github.com/stack-of-tasks/pinocchio/issues/2613 + continue + if name == "aloha_mj_description": + # See https://github.com/stack-of-tasks/pinocchio/issues/2610 + continue + if name == "talos_mj_description": + # See https://github.com/stack-of-tasks/pinocchio/issues/2612 + continue + setattr( + TestPinocchio, + f"test_{name}", + TestPinocchio.get_test_for_description(name), + ) diff --git a/tests/loaders/test_pybullet.py b/tests/loaders/test_pybullet.py index 1a6c934e..1eae9239 100644 --- a/tests/loaders/test_pybullet.py +++ b/tests/loaders/test_pybullet.py @@ -9,12 +9,12 @@ import pybullet from robot_descriptions._descriptions import DESCRIPTIONS - -from robot_descriptions.loaders.pybullet import load_robot_description +from robot_descriptions.loaders.pybullet import ( + load_robot_description as load_pybullet, +) class TestPyBullet(unittest.TestCase): - """ Check that all descriptions are loaded properly in PyBullet. """ @@ -31,12 +31,20 @@ def tearDown(self): """ pybullet.disconnect() + def test_pybullet(self): + self.assertIsNotNone( + load_pybullet( + "upkie_description", + commit="98502d5b175c3d6b60b3cf475b7eeef9fd290c43", + ) + ) + def test_value_error_when_no_urdf(self): """ Test exception raised when a description has no URDF_PATH. """ with self.assertRaises(ValueError): - load_robot_description("_empty_description") + load_pybullet("_empty_description") @staticmethod def get_test_for_description(description: str): @@ -51,7 +59,7 @@ def get_test_for_description(description: str): """ def test(self): - load_robot_description(description) + load_pybullet(description) return test diff --git a/tests/loaders/test_robomeshcat.py b/tests/loaders/test_robomeshcat.py index 32b9e473..0121b417 100644 --- a/tests/loaders/test_robomeshcat.py +++ b/tests/loaders/test_robomeshcat.py @@ -7,15 +7,24 @@ import unittest from robot_descriptions._descriptions import DESCRIPTIONS -from robot_descriptions.loaders.robomeshcat import load_robot_description +from robot_descriptions.loaders.robomeshcat import ( + load_robot_description as load_robomeshcat, +) class TestRoboMeshCat(unittest.TestCase): - """ Check that all descriptions are loaded properly in RoboMeshCat. """ + def test_robomeshcat(self): + self.assertIsNotNone( + load_robomeshcat( + "upkie_description", + commit="98502d5b175c3d6b60b3cf475b7eeef9fd290c43", + ) + ) + @staticmethod def get_test_for_description(description: str): """ @@ -29,7 +38,7 @@ def get_test_for_description(description: str): """ def test(self): - load_robot_description(description) + load_robomeshcat(description) return test diff --git a/tests/loaders/test_yourdfpy.py b/tests/loaders/test_yourdfpy.py index c41f5501..e1c75e76 100644 --- a/tests/loaders/test_yourdfpy.py +++ b/tests/loaders/test_yourdfpy.py @@ -7,15 +7,24 @@ import unittest from robot_descriptions._descriptions import DESCRIPTIONS -from robot_descriptions.loaders.yourdfpy import load_robot_description +from robot_descriptions.loaders.yourdfpy import ( + load_robot_description as load_yourdfpy, +) class TestYourdfpy(unittest.TestCase): - """ Check that all URDF descriptions are loaded properly in yourdfpy. """ + def test_yourdfpy(self): + self.assertIsNotNone( + load_yourdfpy( + "upkie_description", + commit="98502d5b175c3d6b60b3cf475b7eeef9fd290c43", + ) + ) + @staticmethod def get_test_for_description(description: str): """ @@ -29,7 +38,7 @@ def get_test_for_description(description: str): """ def test(self): - load_robot_description(description) + load_yourdfpy(description) return test diff --git a/tests/test_descriptions.py b/tests/test_descriptions.py index 78850cac..86475b74 100644 --- a/tests/test_descriptions.py +++ b/tests/test_descriptions.py @@ -12,6 +12,9 @@ import git from robot_descriptions._descriptions import DESCRIPTIONS +from robot_descriptions.loaders.pinocchio import ( + load_robot_description as load_pinocchio, +) class TestDescriptions(unittest.TestCase): @@ -46,6 +49,13 @@ def test_all_descriptions(self): f"in {description}", ) + def test_cache_path_package_name(self): + """Check a description with package:// URIs and a custom commit ID.""" + load_pinocchio( + "draco3_description", + commit="5afd19733d7b3e9f1135ba93e0aad90ed1a24cc7", + ) + def test_invalid_description_commit(self): invalid_commit = "foobar" os.environ["ROBOT_DESCRIPTION_COMMIT"] = invalid_commit @@ -55,3 +65,11 @@ def test_invalid_description_commit(self): if description_name in sys.modules: del sys.modules[description_name] import_module(description_name) + + def test_load_with_commit_then_without(self): + # https://github.com/robot-descriptions/robot_descriptions.py/issues/67 + load_pinocchio( + "draco3_description", + commit="5afd19733d7b3e9f1135ba93e0aad90ed1a24cc7", + ) + load_pinocchio("baxter_description") diff --git a/tests/test_loaders.py b/tests/test_loaders.py deleted file mode 100644 index 0556b0a5..00000000 --- a/tests/test_loaders.py +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# SPDX-License-Identifier: Apache-2.0 -# Copyright 2022 Stéphane Caron - -import unittest - -import pybullet - -from robot_descriptions.loaders.idyntree import ( - load_robot_description as load_idyntree, -) -from robot_descriptions.loaders.mujoco import ( - load_robot_description as load_mujoco, -) -from robot_descriptions.loaders.pinocchio import ( - load_robot_description as load_pinocchio, -) -from robot_descriptions.loaders.pybullet import ( - load_robot_description as load_pybullet, -) -from robot_descriptions.loaders.robomeshcat import ( - load_robot_description as load_robomeshcat, -) -from robot_descriptions.loaders.yourdfpy import ( - load_robot_description as load_yourdfpy, -) - - -class TestLoaders(unittest.TestCase): - """ - Test loaders. - """ - - upkie_description_commit = "98502d5b175c3d6b60b3cf475b7eeef9fd290c43" - - def test_mujoco(self): - self.assertIsNotNone(load_mujoco("cassie_mj_description")) - - def test_pinocchio(self): - self.assertIsNotNone( - load_pinocchio( - "upkie_description", - commit=self.upkie_description_commit, - ) - ) - - def test_pybullet(self): - pybullet.connect(pybullet.DIRECT) - self.assertIsNotNone( - load_pybullet( - "upkie_description", - commit=self.upkie_description_commit, - ) - ) - pybullet.disconnect() - - def test_robomeshcat(self): - self.assertIsNotNone( - load_robomeshcat( - "upkie_description", - commit=self.upkie_description_commit, - ) - ) - - def test_yourdfpy(self): - self.assertIsNotNone( - load_yourdfpy( - "upkie_description", - commit=self.upkie_description_commit, - ) - ) - - def test_idyntree(self): - self.assertIsNotNone( - load_idyntree( - "upkie_description", - commit=self.upkie_description_commit, - ) - ) - - def test_cache_path_package_name(self): - """Check a description with package:// URIs and a custom commit ID.""" - load_pinocchio( - "draco3_description", - commit="5afd19733d7b3e9f1135ba93e0aad90ed1a24cc7", - ) - - def test_load_with_commit_then_without(self): - # https://github.com/robot-descriptions/robot_descriptions.py/issues/67 - load_pinocchio( - "draco3_description", - commit="5afd19733d7b3e9f1135ba93e0aad90ed1a24cc7", - ) - load_pinocchio("baxter_description") diff --git a/tox.ini b/tox.ini index 4685405a..e2a7fbf3 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] isolated_build = True -envlist = coverage,lint,loader-{mujoco,pinocchio,pybullet,robomeshcat,yourdfpy,idyntree} +envlist = coverage,lint,loader-{mujoco,pybullet,robomeshcat,yourdfpy,idyntree} [gh-actions] python = @@ -14,14 +14,14 @@ deps = coverage >=5.5 idyntree >= 8.0.0 mujoco >=2.3.5 - numpy >=1.23.4,<2 - pin >=2.6.14 + numpy >=1.23.4 + pin >= 3.1.0 pybullet >=3.2.5 robomeshcat >= 1.0.4 yourdfpy >=0.0.56 commands = coverage erase - coverage run -m unittest tests + coverage run -m unittest tests --failfast coverage report --include="robot_descriptions/**" [testenv:lint] @@ -31,8 +31,8 @@ deps = mccabe mujoco >=2.3.5 mypy - numpy >=1.23.4,<2 - pin >=2.6.14 + numpy >=1.23.4 + pin >= 3.1.0 pybullet >=3.2.5 pylint robomeshcat >= 1.0.4 @@ -50,29 +50,22 @@ changedir = {toxinidir}/tests/loaders deps = idyntree >= 8.0.0 commands = - python -m unittest test_idyntree.py + python -m unittest test_idyntree.py --failfast [testenv:loader-mujoco] changedir = {toxinidir}/tests/loaders deps = mujoco >=2.3.5 commands = - python -m unittest test_mujoco.py - -[testenv:loader-pinocchio] -changedir = {toxinidir}/tests/loaders -deps = - pin >=2.6.14 -commands = - python -m unittest test_pinocchio.py + python -m unittest test_mujoco.py --failfast [testenv:loader-pybullet] changedir = {toxinidir}/tests/loaders deps = - numpy >=1.23.4,<2 + numpy >=1.23.4 pybullet >=3.2.5 commands = - python -m unittest test_pybullet.py + python -m unittest test_pybullet.py --failfast [testenv:loader-robomeshcat] changedir = {toxinidir}/tests/loaders @@ -80,16 +73,16 @@ deps = pycollada >=0.6 robomeshcat >= 1.0.4 commands = - python -m unittest test_robomeshcat.py + python -m unittest test_robomeshcat.py --failfast [testenv:loader-yourdfpy] changedir = {toxinidir}/tests/loaders deps = lxml <=4.9.4 - numpy >=1.23.4,<2 + numpy >=1.23.4 yourdfpy >=0.0.56 commands = - python -m unittest test_yourdfpy.py + python -m unittest test_yourdfpy.py --failfast [pylint] generated-members=mujoco.MjModel, pin.JointModelFreeFlyer, pin.JointModelPX, pin.JointModelPY, pin.JointModelPZ, pin.JointModelPlanar, pin.JointModelRX, pin.JointModelRY, pin.JointModelRZ, pin.JointModelSpherical, pin.JointModelSphericalZYX, pin.JointModelTranslation, pin.Model, pybullet.loadURDF, pybullet.setAdditionalSearchPath