|
14 | 14 | from typing import Any, cast |
15 | 15 | from unittest.mock import MagicMock |
16 | 16 |
|
| 17 | +import numpy as np |
17 | 18 | import pytest |
18 | 19 | from hydra import compose, initialize_config_dir |
19 | 20 | from hydra.core.global_hydra import GlobalHydra |
@@ -1051,6 +1052,131 @@ def test_play_resolve_checkpoint_uses_algo_log_name(tmp_path): |
1051 | 1052 | mod.ROOT_DIR = original_root |
1052 | 1053 |
|
1053 | 1054 |
|
| 1055 | +def test_play_interactive_runner_log_dir_uses_algo_log_name(monkeypatch: pytest.MonkeyPatch): |
| 1056 | + import types |
| 1057 | + |
| 1058 | + mod = _play_interactive() |
| 1059 | + captured: dict[str, object] = {} |
| 1060 | + |
| 1061 | + class FakeWrapper: |
| 1062 | + def __init__(self, env, device, policy_obs_mode): |
| 1063 | + self.env = env |
| 1064 | + captured["policy_obs_mode"] = policy_obs_mode |
| 1065 | + |
| 1066 | + def reset(self): |
| 1067 | + return None, {} |
| 1068 | + |
| 1069 | + class FakeRunner: |
| 1070 | + def __init__(self, wrapped_env, train_cfg, log_dir, device): |
| 1071 | + del wrapped_env, train_cfg, device |
| 1072 | + captured["log_dir"] = log_dir |
| 1073 | + |
| 1074 | + def load(self, ckpt, load_cfg): |
| 1075 | + captured["ckpt"] = ckpt |
| 1076 | + captured["load_cfg"] = load_cfg |
| 1077 | + |
| 1078 | + def get_inference_policy(self, device): |
| 1079 | + del device |
| 1080 | + return object() |
| 1081 | + |
| 1082 | + class FakeViewer: |
| 1083 | + def __enter__(self): |
| 1084 | + return self |
| 1085 | + |
| 1086 | + def __exit__(self, exc_type, exc, tb): |
| 1087 | + return False |
| 1088 | + |
| 1089 | + def is_running(self): |
| 1090 | + return False |
| 1091 | + |
| 1092 | + def sync(self): |
| 1093 | + pass |
| 1094 | + |
| 1095 | + user_scn = type("Scene", (), {"ngeom": 0})() |
| 1096 | + |
| 1097 | + fake_env = types.SimpleNamespace( |
| 1098 | + obs_groups_spec={"obs": 5}, |
| 1099 | + action_space=types.SimpleNamespace(shape=(3,), low=np.full((3,), -1.0), high=np.ones((3,))), |
| 1100 | + cfg=types.SimpleNamespace(ctrl_dt=0.02), |
| 1101 | + _backend=types.SimpleNamespace(model=object()), |
| 1102 | + ) |
| 1103 | + |
| 1104 | + monkeypatch.setattr(mod.registry, "make", lambda *args, **kwargs: fake_env) |
| 1105 | + monkeypatch.setattr(mod, "resolve_checkpoint", lambda *args, **kwargs: "/tmp/model_10.pt") |
| 1106 | + monkeypatch.setattr( |
| 1107 | + mod, |
| 1108 | + "get_entrypoint_log_root", |
| 1109 | + lambda root_dir, *, algo_log_name, log_root=None: Path("/tmp") / algo_log_name, |
| 1110 | + ) |
| 1111 | + monkeypatch.setattr(mod, "RslRlVecEnvWrapper", FakeWrapper) |
| 1112 | + monkeypatch.setattr(mod, "OnPolicyRunner", FakeRunner) |
| 1113 | + monkeypatch.setattr(mod, "PPOConfig", lambda: types.SimpleNamespace(to_dict=lambda: {})) |
| 1114 | + monkeypatch.setattr(mod, "is_rsl_rl_v4", lambda: False) |
| 1115 | + monkeypatch.setattr(mod, "convert_config_v3_to_v4", lambda cfg: cfg) |
| 1116 | + monkeypatch.setattr(mod.mujoco, "MjData", lambda model: object()) |
| 1117 | + monkeypatch.setattr(mod.mujoco, "mj_setState", lambda *args, **kwargs: None) |
| 1118 | + monkeypatch.setattr(mod.mujoco, "mj_forward", lambda *args, **kwargs: None) |
| 1119 | + monkeypatch.setattr(mod.mujoco, "mjtState", types.SimpleNamespace(mjSTATE_FULLPHYSICS=0)) |
| 1120 | + monkeypatch.setattr(mod.mujoco.viewer, "launch_passive", lambda *args, **kwargs: FakeViewer()) |
| 1121 | + |
| 1122 | + args = types.SimpleNamespace( |
| 1123 | + task="MyTask", |
| 1124 | + load_run="-1", |
| 1125 | + checkpoint=None, |
| 1126 | + action_mode="policy", |
| 1127 | + policy_obs_mode="flat", |
| 1128 | + algo_log_name="custom_ppo", |
| 1129 | + show_target_bodies=False, |
| 1130 | + show_reward_debug=False, |
| 1131 | + target_body_names="", |
| 1132 | + target_max_bodies=0, |
| 1133 | + target_marker_radius=0.02, |
| 1134 | + target_axis_length=0.08, |
| 1135 | + target_marker_alpha=0.75, |
| 1136 | + target_show_axes=False, |
| 1137 | + reward_debug_show_velocity=False, |
| 1138 | + reward_debug_lin_vel_scale=0.08, |
| 1139 | + reward_debug_ang_vel_scale=0.05, |
| 1140 | + reward_debug_show_connectors=False, |
| 1141 | + reward_debug_show_global_anchor=False, |
| 1142 | + ) |
| 1143 | + |
| 1144 | + mod.play_interactive(args) |
| 1145 | + |
| 1146 | + assert captured["ckpt"] == "/tmp/model_10.pt" |
| 1147 | + assert captured["log_dir"] == "/tmp/custom_ppo/MyTask/play_temp" |
| 1148 | + |
| 1149 | + |
| 1150 | +def test_play_interactive_import_does_not_swallow_registry_bootstrap_errors( |
| 1151 | + monkeypatch: pytest.MonkeyPatch, |
| 1152 | +): |
| 1153 | + import types |
| 1154 | + |
| 1155 | + play_interactive_path = _SCRIPTS_DIR / "play_interactive.py" |
| 1156 | + training_mod = cast(Any, types.ModuleType("unilab.training")) |
| 1157 | + |
| 1158 | + def _fail_bootstrap() -> None: |
| 1159 | + raise RuntimeError("bootstrap failed") |
| 1160 | + |
| 1161 | + training_mod.ensure_registries = _fail_bootstrap |
| 1162 | + training_mod.get_entrypoint_log_root = lambda *args, **kwargs: Path("/tmp") |
| 1163 | + training_mod.resolve_task_checkpoint_path = lambda *args, **kwargs: (None, None) |
| 1164 | + monkeypatch.setitem(sys.modules, "unilab.training", training_mod) |
| 1165 | + |
| 1166 | + mujoco_mod = cast(Any, types.ModuleType("mujoco")) |
| 1167 | + mujoco_mod.viewer = cast(Any, types.ModuleType("mujoco.viewer")) |
| 1168 | + monkeypatch.setitem(sys.modules, "mujoco", mujoco_mod) |
| 1169 | + monkeypatch.setitem(sys.modules, "mujoco.viewer", mujoco_mod.viewer) |
| 1170 | + |
| 1171 | + spec = importlib.util.spec_from_file_location( |
| 1172 | + "play_interactive_test_module", play_interactive_path |
| 1173 | + ) |
| 1174 | + mod = importlib.util.module_from_spec(spec) # type: ignore[arg-type] |
| 1175 | + |
| 1176 | + with pytest.raises(RuntimeError, match="bootstrap failed"): |
| 1177 | + spec.loader.exec_module(mod) # type: ignore[union-attr] |
| 1178 | + |
| 1179 | + |
1054 | 1180 | def test_gen_grasp_import_does_not_swallow_registry_bootstrap_errors( |
1055 | 1181 | monkeypatch: pytest.MonkeyPatch, |
1056 | 1182 | ): |
|
0 commit comments