diff --git a/.github/workflows/test-linux.yml b/.github/workflows/test-linux.yml index 6dbf9087e2e..964bdd82296 100644 --- a/.github/workflows/test-linux.yml +++ b/.github/workflows/test-linux.yml @@ -26,7 +26,7 @@ jobs: test-setup-minimal: strategy: matrix: - python_version: ["3.9", "3.14"] + python_version: ["3.10", "3.14"] fail-fast: false uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main with: diff --git a/test/test_collector.py b/test/test_collector.py index 8ac9b425033..c243067706c 100644 --- a/test/test_collector.py +++ b/test/test_collector.py @@ -3683,6 +3683,9 @@ def test_dynamic_multiasync_collector(self): TORCH_VERSION < version.parse("2.5.0"), reason="requires Torch >= 2.5.0" ) @pytest.mark.skipif(IS_WINDOWS, reason="windows is not supported for compile tests.") +@pytest.mark.skipif( + sys.version_info >= (3, 14), reason="torch.compile is not supported on Python 3.14+" +) class TestCompile: @pytest.mark.parametrize( "collector_cls", diff --git a/test/test_configs.py b/test/test_configs.py index 55e2ff5ce8a..575ebe1ca9e 100644 --- a/test/test_configs.py +++ b/test/test_configs.py @@ -1287,6 +1287,10 @@ def test_ppo_trainer_config_optional_fields(self): @pytest.mark.skipif( not _configs_available, reason="Config system requires hydra-core and omegaconf" ) +@pytest.mark.skipif( + sys.version_info >= (3, 14), + reason="hydra-core argparse integration is not compatible with Python 3.14+", +) class TestHydraParsing: @pytest.fixture(autouse=True, scope="module") def init_hydra(self): diff --git a/test/test_cost.py b/test/test_cost.py index c8f00a67941..b957216bf26 100644 --- a/test/test_cost.py +++ b/test/test_cost.py @@ -17875,6 +17875,9 @@ def __init__(self): TORCH_VERSION < version.parse("2.5.0"), reason="requires torch>=2.5" ) @pytest.mark.skipif(IS_WINDOWS, reason="windows tests do not support compile") +@pytest.mark.skipif( + sys.version_info >= (3, 14), reason="torch.compile is not supported on Python 3.14+" +) @set_composite_lp_aggregate(False) def test_exploration_compile(): try: diff --git a/test/test_env.py b/test/test_env.py index 9572c591613..7c9a619e7d5 100644 --- a/test/test_env.py +++ b/test/test_env.py @@ -100,6 +100,9 @@ pytest.mark.filterwarnings("ignore:unclosed file"), ] +_has_ale = importlib.util.find_spec("ale_py") is not None +_has_mujoco = importlib.util.find_spec("mujoco") is not None + @pytest.fixture(autouse=False) # Turn to True to enable def check_no_lingering_multiprocessing_resources(request): @@ -887,6 +890,8 @@ class TestRollout: @pytest.mark.parametrize("env_name", [PENDULUM_VERSIONED, PONG_VERSIONED]) @pytest.mark.parametrize("frame_skip", [1, 4]) def test_rollout(self, env_name, frame_skip, seed=0): + if env_name is PONG_VERSIONED and not _has_ale: + pytest.skip("ALE not available (missing ale_py); skipping Atari gym test.") if env_name is PONG_VERSIONED and version.parse( gym_backend().__version__ ) < version.parse("0.19"): @@ -2577,6 +2582,10 @@ class TestInfoDict: ) @pytest.mark.parametrize("device", get_default_devices()) def test_info_dict_reader(self, device, seed=0): + if not _has_mujoco: + pytest.skip( + "MuJoCo not available (missing mujoco); skipping MuJoCo gym test." + ) try: import gymnasium as gym except ModuleNotFoundError: @@ -2613,6 +2622,10 @@ def test_info_dict_reader(self, device, seed=0): ), [Unbounded((), dtype=torch.float64)], ): + if not _has_mujoco: + pytest.skip( + "MuJoCo not available (missing mujoco); skipping MuJoCo gym test." + ) env2 = GymWrapper(gym.make("HalfCheetah-v5")) env2.set_info_dict_reader( default_info_dict_reader(["x_position"], spec=spec) @@ -2635,6 +2648,10 @@ def test_info_dict_reader(self, device, seed=0): ) @pytest.mark.parametrize("device", get_default_devices()) def test_auto_register(self, device, maybe_fork_ParallelEnv): + if not _has_mujoco: + pytest.skip( + "MuJoCo not available (missing mujoco); skipping MuJoCo gym test." + ) try: import gymnasium as gym except ModuleNotFoundError: diff --git a/test/test_libs.py b/test/test_libs.py index 845d4e80309..8c302c8726a 100644 --- a/test/test_libs.py +++ b/test/test_libs.py @@ -134,6 +134,8 @@ ) _has_ray = importlib.util.find_spec("ray") is not None +_has_ale = importlib.util.find_spec("ale_py") is not None +_has_mujoco = importlib.util.find_spec("mujoco") is not None if os.getenv("PYTORCH_TEST_FBCODE"): from pytorch.rl.test._utils_internal import ( _make_multithreaded_env, @@ -820,6 +822,13 @@ def reset( ], ) def test_gym(self, env_name, frame_skip, from_pixels, pixels_only): + if env_name == PONG_VERSIONED() and not _has_ale: + pytest.skip("ALE not available (missing ale_py); skipping Atari gym test.") + if env_name == HALFCHEETAH_VERSIONED() and not _has_mujoco: + pytest.skip( + "MuJoCo not available (missing mujoco); skipping MuJoCo gym test." + ) + if env_name == PONG_VERSIONED() and not from_pixels: # raise pytest.skip("already pixel") # we don't skip because that would raise an exception @@ -937,6 +946,13 @@ def non_null_obs(batched_td): ], ) def test_gym_fake_td(self, env_name, frame_skip, from_pixels, pixels_only): + if env_name == PONG_VERSIONED() and not _has_ale: + pytest.skip("ALE not available (missing ale_py); skipping Atari gym test.") + if env_name == HALFCHEETAH_VERSIONED() and not _has_mujoco: + pytest.skip( + "MuJoCo not available (missing mujoco); skipping MuJoCo gym test." + ) + if env_name == PONG_VERSIONED() and not from_pixels: # raise pytest.skip("already pixel") return @@ -1069,6 +1085,13 @@ def test_one_hot_and_categorical(self): # noqa: F811 def test_vecenvs_wrapper(self, envname): import gymnasium + if envname.startswith("ALE/") and not _has_ale: + pytest.skip("ALE not available (missing ale_py); skipping Atari gym test.") + if "HalfCheetah" in envname and not _has_mujoco: + pytest.skip( + "MuJoCo not available (missing mujoco); skipping MuJoCo gym test." + ) + with set_gym_backend("gymnasium"): self._test_vecenvs_wrapper( envname, @@ -1083,6 +1106,13 @@ def test_vecenvs_wrapper(self, envname): ) @pytest.mark.flaky(reruns=5, reruns_delay=1) def test_vecenvs_wrapper(self, envname): # noqa + if envname.startswith("ALE/") and not _has_ale: + pytest.skip("ALE not available (missing ale_py); skipping Atari gym test.") + if "HalfCheetah" in envname and not _has_mujoco: + pytest.skip( + "MuJoCo not available (missing mujoco); skipping MuJoCo gym test." + ) + with set_gym_backend("gymnasium"): self._test_vecenvs_wrapper(envname) @@ -1116,6 +1146,12 @@ def _test_vecenvs_wrapper(self, envname, kwargs=None): ) @pytest.mark.flaky(reruns=5, reruns_delay=1) def test_vecenvs_env(self, envname): + if envname.startswith("ALE/") and not _has_ale: + pytest.skip("ALE not available (missing ale_py); skipping Atari gym test.") + if "HalfCheetah" in envname and not _has_mujoco: + pytest.skip( + "MuJoCo not available (missing mujoco); skipping MuJoCo gym test." + ) self._test_vecenvs_env(envname) @implement_for("gymnasium", None, "1.0.0") @@ -1127,6 +1163,12 @@ def test_vecenvs_env(self, envname): ) @pytest.mark.flaky(reruns=5, reruns_delay=1) def test_vecenvs_env(self, envname): # noqa + if envname.startswith("ALE/") and not _has_ale: + pytest.skip("ALE not available (missing ale_py); skipping Atari gym test.") + if "HalfCheetah" in envname and not _has_mujoco: + pytest.skip( + "MuJoCo not available (missing mujoco); skipping MuJoCo gym test." + ) self._test_vecenvs_env(envname) def _test_vecenvs_env(self, envname): @@ -1990,11 +2032,15 @@ def test_truncated(self): [DMControlEnv, ("cheetah", "run"), {"from_pixels": False}], ] if _has_gym: - params += [ - # [GymEnv, (HALFCHEETAH_VERSIONED,), {"from_pixels": True}], - [GymEnv, (HALFCHEETAH_VERSIONED(),), {"from_pixels": False}], - [GymEnv, (PONG_VERSIONED(),), {}], - ] + if _has_mujoco: + params += [ + # [GymEnv, (HALFCHEETAH_VERSIONED,), {"from_pixels": True}], + [GymEnv, (HALFCHEETAH_VERSIONED(),), {"from_pixels": False}], + ] + if _has_ale: + params += [ + [GymEnv, (PONG_VERSIONED(),), {}], + ] @pytest.mark.skipif( @@ -2037,11 +2083,12 @@ def test_td_creation_from_spec(env_lib, env_args, env_kwargs): [DMControlEnv, ("cheetah", "run"), {"from_pixels": False}], ] if _has_gym: - params += [ - # [GymEnv, (HALFCHEETAH_VERSIONED,), {"from_pixels": True}], - [GymEnv, (HALFCHEETAH_VERSIONED,), {"from_pixels": False}], - # [GymEnv, (PONG_VERSIONED,), {}], # 1226: skipping - ] + if _has_mujoco: + params += [ + # [GymEnv, (HALFCHEETAH_VERSIONED,), {"from_pixels": True}], + [GymEnv, (HALFCHEETAH_VERSIONED,), {"from_pixels": False}], + # [GymEnv, (PONG_VERSIONED,), {}], # 1226: skipping + ] # @pytest.mark.skipif(IS_OSX, reason="rendering unstable on osx, skipping") diff --git a/test/test_rb.py b/test/test_rb.py index c756c4e5417..1c66e9b0119 100644 --- a/test/test_rb.py +++ b/test/test_rb.py @@ -451,6 +451,10 @@ def data_iter(): # # Our Windows CI jobs do not have "cl", so skip this test. @pytest.mark.skipif(_os_is_windows, reason="windows tests do not support compile") + @pytest.mark.skipif( + sys.version_info >= (3, 14), + reason="torch.compile is not supported on Python 3.14+", + ) @pytest.mark.parametrize("avoid_max_size", [False, True]) def test_extend_sample_recompile( self, rb_type, sampler, writer, storage, size, datatype, avoid_max_size @@ -860,6 +864,10 @@ def test_storage_state_dict(self, storage_in, storage_out, init_out, backend): TORCH_VERSION < version.parse("2.5.0"), reason="requires Torch >= 2.5.0" ) @pytest.mark.skipif(_os_is_windows, reason="windows tests do not support compile") + @pytest.mark.skipif( + sys.version_info >= (3, 14), + reason="torch.compile is not supported on Python 3.14+", + ) # This test checks if the `torch._dynamo.disable` wrapper around # `TensorStorage._rand_given_ndim` is still necessary. def test__rand_given_ndim_recompile(self): diff --git a/test/test_trainer.py b/test/test_trainer.py index b19f2f95230..dd77db913c7 100644 --- a/test/test_trainer.py +++ b/test/test_trainer.py @@ -5,6 +5,7 @@ from __future__ import annotations import argparse +import importlib.util import os import tempfile from argparse import Namespace @@ -54,6 +55,8 @@ UpdateWeights, ) +_has_ale = importlib.util.find_spec("ale_py") is not None + def _fun_checker(fun, checker): def new_fun(*args, **kwargs): @@ -843,6 +846,10 @@ def test_subsampler_state_dict(self): @pytest.mark.skipif(not _has_gym, reason="No gym library") @pytest.mark.skipif(not _has_tb, reason="No tensorboard library") +@pytest.mark.skipif( + not _has_ale, + reason="ALE not available (missing ale_py); skipping Atari gym tests.", +) class TestRecorder: def _get_args(self): args = Namespace() diff --git a/test/test_transforms.py b/test/test_transforms.py index 0e0f7535e55..647efbdab61 100644 --- a/test/test_transforms.py +++ b/test/test_transforms.py @@ -193,6 +193,8 @@ ) _has_ray = importlib.util.find_spec("ray") is not None +_has_ale = importlib.util.find_spec("ale_py") is not None +_has_mujoco = importlib.util.find_spec("mujoco") is not None IS_WIN = platform == "win32" if IS_WIN: @@ -3684,6 +3686,8 @@ def test_trans_parallel_env_check(self): @pytest.mark.skipif(not _has_gym, reason="No Gym detected") @pytest.mark.parametrize("out_key", [None, ["outkey"], [("out", "key")]]) def test_transform_env(self, out_key): + if not _has_ale: + pytest.skip("ALE not available (missing ale_py); skipping Atari gym test.") keys = ["pixels"] ct = Compose(ToTensorImage(), Crop(out_keys=out_key, w=20, h=20, in_keys=keys)) env = TransformedEnv(GymEnv(PONG_VERSIONED()), ct) @@ -3893,6 +3897,8 @@ def test_trans_parallel_env_check(self, maybe_fork_ParallelEnv): @pytest.mark.skipif(not _has_gym, reason="No Gym detected") @pytest.mark.parametrize("out_key", [None, ["outkey"], [("out", "key")]]) def test_transform_env(self, out_key): + if not _has_ale: + pytest.skip("ALE not available (missing ale_py); skipping Atari gym test.") keys = ["pixels"] ct = Compose( ToTensorImage(), CenterCrop(out_keys=out_key, w=20, h=20, in_keys=keys) @@ -4932,6 +4938,8 @@ def test_transform_compose(self, keys, size, nchannels, batch, device): "out_keys", [None, ["stuff"], [("some_other", "nested_key")]] ) def test_transform_env(self, out_keys): + if not _has_ale: + pytest.skip("ALE not available (missing ale_py); skipping Atari gym test.") env = TransformedEnv( GymEnv(PONG_VERSIONED()), FlattenObservation(-3, -1, out_keys=out_keys) ) @@ -6135,6 +6143,8 @@ def test_trans_parallel_env_check(self, maybe_fork_ParallelEnv): @pytest.mark.skipif(not _has_gym, reason="No gym") @pytest.mark.parametrize("out_key", ["pixels", ("agents", "pixels")]) def test_transform_env(self, out_key): + if not _has_ale: + pytest.skip("ALE not available (missing ale_py); skipping Atari gym test.") env = TransformedEnv( GymEnv(PONG_VERSIONED()), Compose( @@ -7311,6 +7321,10 @@ def test_transform_rb(self, rbclass, out_keys, dim): ) @pytest.mark.skipif(not _has_gym, reason="No gym") def test_transform_inverse(self): + if not _has_mujoco: + pytest.skip( + "MuJoCo not available (missing mujoco); skipping MuJoCo gym test." + ) env = TransformedEnv( GymEnv(HALFCHEETAH_VERSIONED()), # the order is inverted @@ -7595,6 +7609,10 @@ def test_transform_rb(self, out_keys, rbclass): ) @pytest.mark.skipif(not _has_gym, reason="No Gym") def test_transform_inverse(self): + if not _has_mujoco: + pytest.skip( + "MuJoCo not available (missing mujoco); skipping MuJoCo gym test." + ) env = TransformedEnv( GymEnv(HALFCHEETAH_VERSIONED()), self._inv_circular_transform ) @@ -12592,6 +12610,10 @@ def test_transform_no_env(self, batch): not _has_gymnasium, reason="EndOfLifeTransform can only be tested when Gym is present.", ) +@pytest.mark.skipif( + not _has_ale, + reason="ALE not available (missing ale_py); skipping Atari gym tests.", +) class TestEndOfLife(TransformBase): pytest.mark.filterwarnings("ignore:The base_env is not a gym env") @@ -13782,6 +13804,10 @@ def test_transform_compose(self): def test_transform_env(self, env_cls, interval_as_tensor, categorical, sampling): device = torch.device("cuda" if torch.cuda.is_available() else "cpu") if env_cls == "cheetah": + if not _has_mujoco: + pytest.skip( + "MuJoCo not available (missing mujoco); skipping MuJoCo gym test." + ) base_env = GymEnv( HALFCHEETAH_VERSIONED(), device=device, @@ -15008,6 +15034,7 @@ def test_ray_extension(self): ray.stop() +@pytest.mark.skipif(not _has_ray, reason="ray required") class TestRayModuleTransform: @pytest.fixture(autouse=True, scope="function") def start_ray(self): diff --git a/test/test_utils.py b/test/test_utils.py index 99d7bd355c9..bbe6556e6cc 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -395,6 +395,10 @@ def test_rng_decorator(device): @pytest.mark.skipif( TORCH_VERSION < version.parse("2.5.0"), reason="requires Torch >= 2.5.0" ) +@pytest.mark.skipif( + sys.version_info >= (3, 14), + reason="torch.compile is not supported on Python 3.14+", +) def test_capture_log_records_recompile(): torch.compiler.reset()