From 4bf82c7a683728a506eccb080466d8cf6c31e065 Mon Sep 17 00:00:00 2001 From: Antonin RAFFIN Date: Sat, 18 Jul 2026 16:04:34 +0200 Subject: [PATCH 1/7] Fix moviepy test --- tests/test_logger.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_logger.py b/tests/test_logger.py index 8e38cf556..5790e73c3 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -1,4 +1,3 @@ -import importlib.util import json import os import sys @@ -273,7 +272,13 @@ def test_report_video_to_tensorboard(tmp_path, read_log, capsys): def is_moviepy_installed(): - return importlib.util.find_spec("moviepy") is not None + try: + import moviepy + # PyTorch doesn't support moviepy >= 2.0 + # See: https://github.com/pytorch/pytorch/issues/147317 + return moviepy.__version__ < "2.0" + except (ImportError, AttributeError): + return False @pytest.mark.parametrize("unsupported_format", ["stdout", "log", "json", "csv"]) From 8f980c0d0912ef1a78b6aabd86dcbe6017ab56c0 Mon Sep 17 00:00:00 2001 From: Antonin RAFFIN Date: Sat, 18 Jul 2026 16:13:49 +0200 Subject: [PATCH 2/7] Ignore tensorboard warning --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index ae67ce959..cdfc0754d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,8 @@ filterwarnings = [ "ignore:You are trying to run (PPO|A2C) on the GPU", # Tensorboard warnings "ignore::DeprecationWarning:tensorboard", + # Torch tensorboard numpy newshape deprecation + "ignore:`newshape` keyword argument is deprecated:DeprecationWarning:torch.utils.tensorboard._utils", # Gymnasium warnings "ignore::UserWarning:gymnasium", # Taxi-v3 and CliffWalking-v0 From 4956cd7245e541c08bfb71e94520194840abae4e Mon Sep 17 00:00:00 2001 From: Antonin RAFFIN Date: Fri, 19 Jun 2026 13:40:10 +0200 Subject: [PATCH 3/7] Update test_vec_envs.py --- tests/test_vec_envs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_vec_envs.py b/tests/test_vec_envs.py index 52e0ebba1..c5aa72827 100644 --- a/tests/test_vec_envs.py +++ b/tests/test_vec_envs.py @@ -317,7 +317,7 @@ def check_vecenv_obs(obs, space): assert space.contains(value) -@pytest.mark.parametrize("vec_env_class,space", itertools.product(VEC_ENV_CLASSES, SPACES.values())) +@pytest.mark.parametrize("vec_env_class,space", list(itertools.product(VEC_ENV_CLASSES, SPACES.values()))) def test_vecenv_single_space(vec_env_class, space): def obs_assert(obs): return check_vecenv_obs(obs, space) From fc3e8c82b33908470ff016a20e4707132b48ee2d Mon Sep 17 00:00:00 2001 From: Antonin RAFFIN Date: Sat, 18 Jul 2026 16:29:19 +0200 Subject: [PATCH 4/7] Add tests for rmsprop --- tests/test_utils.py | 56 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/tests/test_utils.py b/tests/test_utils.py index 6e0672f64..8edf67dd0 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,4 +1,5 @@ import os +import pickle import shutil import ale_py @@ -15,6 +16,7 @@ from stable_baselines3.common.evaluation import evaluate_policy from stable_baselines3.common.monitor import Monitor from stable_baselines3.common.noise import OrnsteinUhlenbeckActionNoise, VectorizedActionNoise +from stable_baselines3.common.sb2_compat.rmsprop_tf_like import RMSpropTFLike from stable_baselines3.common.utils import ( ConstantSchedule, FloatSchedule, @@ -628,3 +630,57 @@ def test_deprecated_schedules(): assert np.allclose(fn(0.5), schedule(0.5)) assert np.allclose(fn(0.5), float_schedule(0.5)) assert np.allclose(fn(0.5), float_schedule_2(0.5)) + + +class TestRMSpropTFLike: + """Tests for the RMSpropTFLike optimizer (sb2_compat).""" + + def test_invalid_params(self): + """Test that invalid optimizer parameters raise ValueError.""" + param = th.nn.Parameter(th.zeros(2)) + with pytest.raises(ValueError, match="Invalid learning rate"): + RMSpropTFLike([param], lr=-1.0) + with pytest.raises(ValueError, match="Invalid epsilon"): + RMSpropTFLike([param], eps=-1.0) + with pytest.raises(ValueError, match="Invalid momentum"): + RMSpropTFLike([param], momentum=-1.0) + with pytest.raises(ValueError, match="Invalid weight_decay"): + RMSpropTFLike([param], weight_decay=-1.0) + with pytest.raises(ValueError, match="Invalid alpha"): + RMSpropTFLike([param], alpha=-1.0) + + def test_centered_momentum_weight_decay(self): + """Test centered=True, momentum>0, weight_decay>0 branches in step().""" + param = th.nn.Parameter(th.tensor([1.0, 2.0])) + opt = RMSpropTFLike( + [param], + lr=0.1, + momentum=0.9, + alpha=0.99, + eps=1e-8, + centered=True, + weight_decay=0.01, + ) + # Compute gradient + loss = (param**2).sum() + loss.backward() + + # Run step with closure (covers closure branch) + def closure(): + param.grad.zero_() + return float((param**2).sum().detach()) + + opt.step(closure=closure) + # Check state keys exist (covers centered + momentum init) + state = opt.state[param] + assert "grad_avg" in state # centered=True + assert "momentum_buffer" in state # momentum > 0 + + def test_pickle_roundtrip(self): + """Test that optimizer survives pickle roundtrip (covers __setstate__).""" + param = th.nn.Parameter(th.tensor([1.0, 2.0])) + opt = RMSpropTFLike([param], lr=0.1, momentum=0.5, centered=True) + opt2 = pickle.loads(pickle.dumps(opt)) + assert opt2.param_groups[0]["lr"] == 0.1 + assert opt2.param_groups[0]["momentum"] == 0.5 + assert opt2.param_groups[0]["centered"] is True From b2b5b1a95963aeb5faabbfec6fdbd320b8b900a1 Mon Sep 17 00:00:00 2001 From: Antonin RAFFIN Date: Sat, 18 Jul 2026 16:29:26 +0200 Subject: [PATCH 5/7] Update version --- docs/misc/changelog.md | 25 +++++++++++++++++++++++++ stable_baselines3/version.txt | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/misc/changelog.md b/docs/misc/changelog.md index e72d223e3..a6c59efce 100644 --- a/docs/misc/changelog.md +++ b/docs/misc/changelog.md @@ -2,6 +2,31 @@ # Changelog +## Release 2.9.2a0 (2026-07-18) + +### Breaking Changes: + +### New Features: + +### Bug Fixes: + +### [SB3-Contrib] + +### [RL Zoo] + +### [SBX] (SB3 + Jax) + +### Deprecations: + +### Others: + +- Fixed moviepy test compatibility with moviepy >= 2.0 (PyTorch doesn't support it yet) +- Added filter for numpy `newshape` deprecation warning from torch tensorboard +- Fixed mypy error in test_vec_envs.py by wrapping `itertools.product` with `list()` +- Added tests for RMSpropTFLike optimizer (invalid params, centered/momentum/weight_decay branches, pickle roundtrip) + +### Documentation: + ## Release 2.9.0 (2026-06-15) **Updated dependencies (pandas is now optional, gymnasium 1.3.0 support, torch>=2.8)** diff --git a/stable_baselines3/version.txt b/stable_baselines3/version.txt index c8e38b614..a36506279 100644 --- a/stable_baselines3/version.txt +++ b/stable_baselines3/version.txt @@ -1 +1 @@ -2.9.0 +2.9.2a0 From 945a20527ce9fe566aa849da1b50bab861a1d21f Mon Sep 17 00:00:00 2001 From: Antonin RAFFIN Date: Sat, 18 Jul 2026 16:34:46 +0200 Subject: [PATCH 6/7] Improved test coverage for `save_util.py` --- docs/misc/changelog.md | 3 ++- tests/test_save_load.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/docs/misc/changelog.md b/docs/misc/changelog.md index a6c59efce..73d6797b1 100644 --- a/docs/misc/changelog.md +++ b/docs/misc/changelog.md @@ -23,7 +23,8 @@ - Fixed moviepy test compatibility with moviepy >= 2.0 (PyTorch doesn't support it yet) - Added filter for numpy `newshape` deprecation warning from torch tensorboard - Fixed mypy error in test_vec_envs.py by wrapping `itertools.product` with `list()` -- Added tests for RMSpropTFLike optimizer (invalid params, centered/momentum/weight_decay branches, pickle roundtrip) +- Improved tests coverage for RMSpropTFLike optimizer (invalid params, centered/momentum/weight_decay branches, pickle roundtrip) +- Improved test coverage for `save_util.py`: added tests for `BadZipFile` error handling in `load_from_zip_file` and `IsADirectoryError`/`FileNotFoundError` handling in `open_path` ### Documentation: diff --git a/tests/test_save_load.py b/tests/test_save_load.py index 89aad47e2..7a65dcbc5 100644 --- a/tests/test_save_load.py +++ b/tests/test_save_load.py @@ -891,3 +891,34 @@ def test_save_load_clip_range_portable(tmp_path, model_class): assert isinstance(model.clip_range, FloatSchedule) assert isinstance(model.clip_range.value_schedule, ConstantSchedule) assert model.clip_range.value_schedule.val == 0.2 + + +def test_load_bad_zip(tmp_path): + """Test that loading a non-zip file raises a ValueError.""" + # Write a plain text file to the path + bad_path = tmp_path / "not_a_zip.zip" + bad_path.write_text("this is not a zip file") + + with pytest.raises(ValueError, match="wasn't a zip-file"): + PPO.load(str(bad_path)) + + +@pytest.mark.filterwarnings("error::ResourceWarning") +def test_open_path_directory_and_missing_parent(tmp_path): + """Test IsADirectoryError and FileNotFoundError handling in open_path.""" + # Test IsADirectoryError: path already has the suffix and is a folder + dir_path = tmp_path / "is_a_folder.pkl" + dir_path.mkdir() + with warnings.catch_warnings(record=True) as record: + open_path(dir_path, "w", suffix="pkl").close() + assert any("is a folder" in str(warning.message) for warning in record) + # Verify the fallback file was created + assert (tmp_path / "is_a_folder.pkl_2").exists() + + # Test FileNotFoundError: parent folder doesn't exist + missing_parent_path = tmp_path / "nonexistent_parent" / "sub" / "file" + with warnings.catch_warnings(record=True) as record: + open_path(missing_parent_path, "w", suffix="pkl").close() + assert any("does not exist" in str(warning.message) for warning in record) + # Verify the parent was created and file was written + assert (tmp_path / "nonexistent_parent" / "sub" / "file.pkl").exists() From 887c6062aa48e4f23ba62b747ad64c94e5102e44 Mon Sep 17 00:00:00 2001 From: Antonin RAFFIN Date: Sat, 18 Jul 2026 16:36:08 +0200 Subject: [PATCH 7/7] Fixes for mypy --- stable_baselines3/common/policies.py | 6 +++--- tests/test_logger.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/stable_baselines3/common/policies.py b/stable_baselines3/common/policies.py index 0f75c5327..93f0a254f 100644 --- a/stable_baselines3/common/policies.py +++ b/stable_baselines3/common/policies.py @@ -257,7 +257,7 @@ def obs_to_tensor(self, observation: np.ndarray | dict[str, np.ndarray]) -> tupl obs_ = np.array(obs) vectorized_env = vectorized_env or is_vectorized_observation(obs_, obs_space) # Add batch dimension if needed - observation[key] = obs_.reshape((-1, *self.observation_space[key].shape)) # type: ignore[misc] + observation[key] = obs_.reshape((-1, *self.observation_space[key].shape)) # type: ignore[misc, arg-type] elif is_image_space(self.observation_space): # Handle the different cases for images @@ -271,7 +271,7 @@ def obs_to_tensor(self, observation: np.ndarray | dict[str, np.ndarray]) -> tupl # Dict obs need to be handled separately vectorized_env = is_vectorized_observation(observation, self.observation_space) # Add batch dimension if needed - observation = observation.reshape((-1, *self.observation_space.shape)) # type: ignore[misc] + observation = observation.reshape((-1, *self.observation_space.shape)) # type: ignore[misc, arg-type] obs_tensor = obs_as_tensor(observation, self.device) return obs_tensor, vectorized_env @@ -367,7 +367,7 @@ def predict( with th.no_grad(): actions = self._predict(obs_tensor, deterministic=deterministic) # Convert to numpy, and reshape to the original action shape - actions = actions.cpu().numpy().reshape((-1, *self.action_space.shape)) # type: ignore[misc, assignment] + actions = actions.cpu().numpy().reshape((-1, *self.action_space.shape)) # type: ignore[misc, assignment, arg-type] if isinstance(self.action_space, spaces.Box): if self.squash_output: diff --git a/tests/test_logger.py b/tests/test_logger.py index 5790e73c3..ef2a5a020 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -274,6 +274,7 @@ def test_report_video_to_tensorboard(tmp_path, read_log, capsys): def is_moviepy_installed(): try: import moviepy + # PyTorch doesn't support moviepy >= 2.0 # See: https://github.com/pytorch/pytorch/issues/147317 return moviepy.__version__ < "2.0"