diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2b5cb7d41226..3b51125704ee 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -966,8 +966,7 @@ jobs: # (dexterous, many contacts). Deformable/MPM kernels are not covered; # widen if a test job reports large cache growth. include-files: >- - test_environments.py, - test_environments_newton.py + test_environments.py # No Soft/Cloth: the deformable envs depend on optional extras the CI # image does not install (Soft needs pytetwild), so they only ever fail. test-k-expr: "Cartpole or Drawer or AnymalD or Handover" diff --git a/source/isaaclab_tasks/changelog.d/test-contrib-environments.skip b/source/isaaclab_tasks/changelog.d/test-contrib-environments.skip new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/source/isaaclab_tasks/test/contrib/test_cartpole_showcase_environments.py b/source/isaaclab_tasks/test/contrib/test_cartpole_showcase_environments.py index 38b2ce277a19..207f8ec10753 100644 --- a/source/isaaclab_tasks/test/contrib/test_cartpole_showcase_environments.py +++ b/source/isaaclab_tasks/test/contrib/test_cartpole_showcase_environments.py @@ -25,9 +25,7 @@ @pytest.mark.parametrize("num_envs, device", [(2, "cuda"), (1, "cuda")]) @pytest.mark.parametrize( "task_name", - setup_environment( - include_play=False, factory_envs=False, multi_agent=False, teleop_envs=False, cartpole_showcase_envs=True - ), + setup_environment(factory_envs=False, multi_agent=False, teleop_envs=False, cartpole_showcase_envs=True), ) @pytest.mark.isaacsim_ci def test_cartpole_showcase_environments(task_name, num_envs, device): diff --git a/source/isaaclab_tasks/test/contrib/test_cartpole_showcase_environments_with_stage_in_memory.py b/source/isaaclab_tasks/test/contrib/test_cartpole_showcase_environments_with_stage_in_memory.py index 536b209dbd47..2ac712a34ba3 100644 --- a/source/isaaclab_tasks/test/contrib/test_cartpole_showcase_environments_with_stage_in_memory.py +++ b/source/isaaclab_tasks/test/contrib/test_cartpole_showcase_environments_with_stage_in_memory.py @@ -26,9 +26,7 @@ @pytest.mark.parametrize("num_envs, device", [(2, "cuda")]) @pytest.mark.parametrize( "task_name", - setup_environment( - include_play=False, factory_envs=False, multi_agent=False, teleop_envs=False, cartpole_showcase_envs=True - ), + setup_environment(factory_envs=False, multi_agent=False, teleop_envs=False, cartpole_showcase_envs=True), ) def test_cartpole_showcase_environments_with_stage_in_memory_and_clone_in_fabric_disabled(task_name, num_envs, device): # skip test if stage in memory is not supported diff --git a/source/isaaclab_tasks/test/contrib/test_contrib_environments_smoke.py b/source/isaaclab_tasks/test/contrib/test_contrib_environments_smoke.py deleted file mode 100644 index c4551fa8cea8..000000000000 --- a/source/isaaclab_tasks/test/contrib/test_contrib_environments_smoke.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Basic smoke test for contributed environments. - -Instantiates each contributed task (registered under ``isaaclab_tasks.contrib``) and steps -it with random actions to verify it loads and runs without error. Environment families that -require dedicated handling are covered by their own test files in this directory and are -excluded here to avoid duplication: - -- Factory / Forge: ``test_factory_environments.py`` -- Cartpole Showcase: ``test_cartpole_showcase_environments.py`` -- PickPlace / Stack / Place: ``test_pickplace_stack_environments.py`` -- Teleop: ``test_teleop_environments.py`` -- AutoMate: ``test_environments_automate.py`` -- Skillgen: ``test_environments_skillgen.py`` -- Franka Pour: requires an external reset-dataset artifact -""" - -"""Launch Isaac Sim Simulator first.""" - -from isaaclab.app import AppLauncher - -# launch the simulator -app_launcher = AppLauncher(headless=True, enable_cameras=True) -simulation_app = app_launcher.app - - -"""Rest everything follows.""" - -import pytest - -import isaaclab_tasks # noqa: F401 - -# Local imports should be imported last -from env_test_utils import _run_environments, setup_environment # isort: skip - - -@pytest.mark.smoke -@pytest.mark.parametrize("num_envs, device", [(2, "cuda")]) -@pytest.mark.parametrize( - "task_name", - setup_environment( - include_play=False, - multi_agent=False, - factory_envs=False, - cartpole_showcase_envs=False, - pickplace_stack_envs=False, - teleop_envs=False, - tier="contrib", - ), -) -def test_contrib_environments_smoke(task_name, num_envs, device): - if task_name == "IsaacContrib-Franka-Pour": - pytest.skip("Requires an external reset-dataset artifact.") - - # run a short rollout with random actions to verify the environment loads and steps - _run_environments(task_name, device, num_envs, create_stage_in_memory=False) diff --git a/source/isaaclab_tasks/test/contrib/test_pickplace_stack_environments.py b/source/isaaclab_tasks/test/contrib/test_pickplace_stack_environments.py index cbc10c272b73..8801aea9f71a 100644 --- a/source/isaaclab_tasks/test/contrib/test_pickplace_stack_environments.py +++ b/source/isaaclab_tasks/test/contrib/test_pickplace_stack_environments.py @@ -36,7 +36,6 @@ @pytest.mark.parametrize( "task_name", setup_environment( - include_play=False, factory_envs=False, multi_agent=False, teleop_envs=False, diff --git a/source/isaaclab_tasks/test/contrib/test_teleop_environments.py b/source/isaaclab_tasks/test/contrib/test_teleop_environments.py index c30af100038a..11e0fab76008 100644 --- a/source/isaaclab_tasks/test/contrib/test_teleop_environments.py +++ b/source/isaaclab_tasks/test/contrib/test_teleop_environments.py @@ -36,9 +36,7 @@ @pytest.mark.parametrize("num_envs, device", [(2, "cuda"), (1, "cuda")]) -@pytest.mark.parametrize( - "task_name", setup_environment(include_play=False, factory_envs=False, multi_agent=False, teleop_envs=True) -) +@pytest.mark.parametrize("task_name", setup_environment(factory_envs=False, multi_agent=False, teleop_envs=True)) @pytest.mark.isaacsim_ci def test_teleop_environments(task_name, num_envs, device): # run teleop environments without stage in memory diff --git a/source/isaaclab_tasks/test/contrib/test_teleop_environments_with_stage_in_memory.py b/source/isaaclab_tasks/test/contrib/test_teleop_environments_with_stage_in_memory.py index 155543d0ed70..f2cfbd83f2b2 100644 --- a/source/isaaclab_tasks/test/contrib/test_teleop_environments_with_stage_in_memory.py +++ b/source/isaaclab_tasks/test/contrib/test_teleop_environments_with_stage_in_memory.py @@ -37,9 +37,7 @@ @pytest.mark.parametrize("num_envs, device", [(2, "cuda")]) -@pytest.mark.parametrize( - "task_name", setup_environment(include_play=False, factory_envs=False, multi_agent=False, teleop_envs=True) -) +@pytest.mark.parametrize("task_name", setup_environment(factory_envs=False, multi_agent=False, teleop_envs=True)) def test_teleop_environments_with_stage_in_memory_and_clone_in_fabric_disabled(task_name, num_envs, device): # skip test if stage in memory is not supported if get_isaac_sim_version().major < 5: diff --git a/source/isaaclab_tasks/test/core/test_environments.py b/source/isaaclab_tasks/test/core/test_environments.py index 5e37814c2058..2b7dc2955b7e 100644 --- a/source/isaaclab_tasks/test/core/test_environments.py +++ b/source/isaaclab_tasks/test/core/test_environments.py @@ -14,6 +14,10 @@ """Rest everything follows.""" +import subprocess +import sys +from pathlib import Path + import pytest import isaaclab_tasks # noqa: F401 @@ -22,16 +26,62 @@ from env_test_utils import _run_environments, setup_environment # isort: skip +def _ensure_franka_pour_reset_dataset() -> None: + """Generate the smallest valid reset dataset when the Pour smoke test needs it.""" + repo_root = Path(__file__).resolve().parents[4] + dataset_path = repo_root / "datasets/franka_pour/reset_dataset.pt" + if dataset_path.is_file(): + return + + subprocess.run( + [ + sys.executable, + str(repo_root / "scripts/tools/generate_franka_pour_reset_dataset.py"), + "--device", + "cuda:0", + "--grasping_count", + "100", + "--non_grasping_count", + "6", + "--batch_size", + "128", + ], + cwd=repo_root, + check=True, + ) + + +@pytest.mark.parametrize("physics_preset_name", ["newton_mjwarp", "physx", "isaacsim_physx"]) @pytest.mark.parametrize("num_envs, device", [(2, "cuda"), (1, "cuda")]) @pytest.mark.parametrize( "task_name", setup_environment( - include_play=False, multi_agent=False, tier="core", ), ) @pytest.mark.isaacsim_ci -def test_environments(task_name, num_envs, device): +def test_environments(task_name, physics_preset_name, num_envs, device): # run environments without stage in memory + _run_environments( + task_name, device, num_envs, create_stage_in_memory=False, physics_preset_name=physics_preset_name + ) + + +@pytest.mark.parametrize("num_envs, device", [(2, "cuda")]) +@pytest.mark.parametrize( + "task_name", + setup_environment( + multi_agent=False, + factory_envs=False, + cartpole_showcase_envs=False, + pickplace_stack_envs=False, + teleop_envs=False, + tier="contrib", + ), +) +@pytest.mark.isaacsim_ci +def test_contrib_environments(task_name, num_envs, device): + if task_name == "IsaacContrib-Franka-Pour": + _ensure_franka_pour_reset_dataset() _run_environments(task_name, device, num_envs, create_stage_in_memory=False) diff --git a/source/isaaclab_tasks/test/core/test_environments_newton.py b/source/isaaclab_tasks/test/core/test_environments_newton.py deleted file mode 100644 index 8618b04c6c34..000000000000 --- a/source/isaaclab_tasks/test/core/test_environments_newton.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Launch Isaac Sim Simulator first.""" - -from isaaclab.app import AppLauncher - -# launch the simulator -app_launcher = AppLauncher(headless=True, enable_cameras=True, limit_cpu_threads=1) -simulation_app = app_launcher.app - - -"""Rest everything follows.""" - -import pytest - -import isaaclab_tasks # noqa: F401 - -# Local imports should be imported last -from env_test_utils import _run_environments, setup_environment # isort: skip - - -@pytest.mark.parametrize("num_envs, device", [(2, "cuda"), (1, "cuda")]) -@pytest.mark.parametrize( - "task_name", - setup_environment( - include_play=False, - multi_agent=False, - newton_mjwarp_envs=True, - tier="core", - ), -) -@pytest.mark.newton_ci -def test_environments_newton(task_name, num_envs, device): - # run environments with MJWarp physics preset - _run_environments(task_name, device, num_envs, physics_preset_name="newton_mjwarp", create_stage_in_memory=False) diff --git a/source/isaaclab_tasks/test/core/test_environments_with_stage_in_memory.py b/source/isaaclab_tasks/test/core/test_environments_with_stage_in_memory.py index 1457b01f9c96..ad6185ed45a6 100644 --- a/source/isaaclab_tasks/test/core/test_environments_with_stage_in_memory.py +++ b/source/isaaclab_tasks/test/core/test_environments_with_stage_in_memory.py @@ -29,7 +29,7 @@ # TODO(mtrepte): re-enable with fabric cloning fix # @pytest.mark.parametrize("num_envs, device", [(2, "cuda")]) -# @pytest.mark.parametrize("task_name", setup_environment(include_play=False,factory_envs=False, multi_agent=False)) +# @pytest.mark.parametrize("task_name", setup_environment(factory_envs=False, multi_agent=False)) # def test_environments_with_stage_in_memory_and_clone_in_fabric_disabled(task_name, num_envs, device): # # skip test if stage in memory is not supported # if get_isaac_sim_version().major < 5: @@ -43,7 +43,6 @@ @pytest.mark.parametrize( "task_name", setup_environment( - include_play=False, multi_agent=False, tier="core", ), diff --git a/source/isaaclab_tasks/test/core/test_preset_kit_decision.py b/source/isaaclab_tasks/test/core/test_preset_kit_decision.py index 73a119182f3c..515ca8186f99 100644 --- a/source/isaaclab_tasks/test/core/test_preset_kit_decision.py +++ b/source/isaaclab_tasks/test/core/test_preset_kit_decision.py @@ -88,10 +88,11 @@ def test_isaacsim_physx_is_physics_selector(): def test_registered_task_physx_presets_keep_auto_selection_explicit(): - """PhysX defaults are concrete while ``physx`` remains the automatic selector.""" + """Core tasks retain explicit PhysX variants alongside automatic ``physx``.""" for task_id, task_spec in gym.registry.items(): - if not task_id.startswith(("Isaac-", "IsaacContrib-")) or "env_cfg_entry_point" not in task_spec.kwargs: + entry_point = task_spec.kwargs.get("env_cfg_entry_point", "") + if not task_id.startswith("Isaac-") or "isaaclab_tasks.core" not in str(entry_point): continue env_cfg = load_cfg_from_registry(task_id, "env_cfg_entry_point") presets = collect_presets(env_cfg) diff --git a/source/isaaclab_tasks/test/core/test_record_video.py b/source/isaaclab_tasks/test/core/test_record_video.py index 329d42b5249a..46748e97cc8f 100644 --- a/source/isaaclab_tasks/test/core/test_record_video.py +++ b/source/isaaclab_tasks/test/core/test_record_video.py @@ -41,7 +41,7 @@ def setup_video_params(): return num_envs, device, video_length -@pytest.mark.parametrize("task_name", setup_environment(include_play=True, tier="core")) +@pytest.mark.parametrize("task_name", setup_environment(tier="core")) def test_record_video(task_name, setup_video_params): """Run random actions agent with internal VideoRecorder capturing from the active visualizer.""" num_envs, device, video_length = setup_video_params diff --git a/source/isaaclab_tasks/test/env_test_utils.py b/source/isaaclab_tasks/test/env_test_utils.py index 3547c175fd66..880a8668bedc 100644 --- a/source/isaaclab_tasks/test/env_test_utils.py +++ b/source/isaaclab_tasks/test/env_test_utils.py @@ -5,6 +5,7 @@ """Shared test utilities for Isaac Lab environments.""" +import gc import importlib import os import sys @@ -76,49 +77,18 @@ def _task_tier(task_spec) -> str | None: return None -def _has_physics_preset(raw_cfg, preset_name: str) -> bool: - """Check if a raw (unresolved) env config has a named physics preset. - - Must be called with the result of :func:`load_cfg_from_registry`, not - :func:`parse_env_cfg`, because the latter resolves all PresetCfg wrappers - to their default before returning. - - Args: - raw_cfg: Raw env config from :func:`load_cfg_from_registry`. - preset_name: Name of the preset to check for (e.g., 'newton_mjwarp'). - - Returns: - True if ``raw_cfg.sim.physics`` is a PresetCfg with the given preset field. - """ - if isinstance(raw_cfg, dict): - return False - # If the top-level cfg is itself a PresetCfg wrapper, unwrap to its default. - env_cfg = raw_cfg - if ( - hasattr(env_cfg, "__dataclass_fields__") - and hasattr(env_cfg, "default") - and not hasattr(type(env_cfg), "class_type") - ): - env_cfg = env_cfg.default - physics = getattr(getattr(env_cfg, "sim", None), "physics", None) - return physics is not None and hasattr(physics, preset_name) - - def setup_environment( - include_play: bool = False, factory_envs: bool | None = None, multi_agent: bool | None = None, teleop_envs: bool | None = None, cartpole_showcase_envs: bool | None = None, pickplace_stack_envs: bool | None = None, - newton_mjwarp_envs: bool | None = None, tier: str | None = None, ) -> list[str]: """ Acquire all registered Isaac environment task IDs with optional filters. Args: - include_play: If True, include environments ending in 'Play-v0'. factory_envs: - True: include only Factory environments - False: exclude Factory environments @@ -139,10 +109,6 @@ def setup_environment( - True: include only PickPlace/Stack environments - False: exclude PickPlace/Stack environments - None: include all environments regardless of pick-place/stack type - newton_mjwarp_envs: - - True: include only environments that have an MJWarp physics preset. - - False: exclude environments that have an MJWarp physics preset. - - None: include all environments regardless of MJWarp preset availability. tier: - "core": include only core environments (registered under ``isaaclab_tasks.core``). - "contrib": include only contributed environments (registered under ``isaaclab_tasks.contrib``). @@ -161,10 +127,6 @@ def setup_environment( if "Isaac" not in task_spec.id: continue - # filter Play environments, if needed - if not include_play and task_spec.id.endswith("Play-v0"): - continue - # apply core/contrib tier filter if tier is not None and _task_tier(task_spec) != tier: continue @@ -211,18 +173,6 @@ def setup_environment( continue # if None: no filter - # apply MJWarp preset filter - if newton_mjwarp_envs is not None: - # Use load_cfg_from_registry (not parse_env_cfg) so that the PresetCfg - # wrapper on sim.physics is not yet resolved to its default. - raw_cfg = load_cfg_from_registry(task_spec.id, "env_cfg_entry_point") - has_newton_mjwarp = _has_physics_preset(raw_cfg, "newton_mjwarp") - if (newton_mjwarp_envs is True and not has_newton_mjwarp) or ( - newton_mjwarp_envs is False and has_newton_mjwarp - ): - continue - # if None: no filter - registered_tasks.append(task_spec.id) # sort environments alphabetically @@ -521,6 +471,11 @@ def _check_random_actions( if env is not None: env.close() + # Drop unreachable environment objects while the device is still alive. Warp arrays + # free device memory from a finalizer, so collect them before the simulation teardown + # destroys their streams. + gc.collect() + # Clear the simulation context singleton (also closes the USD context stage) SimulationContext.clear_instance()