|
14 | 14 | # limitations under the License. |
15 | 15 | import json |
16 | 16 | import shutil |
| 17 | +import sys |
17 | 18 | import tomllib |
18 | 19 | from importlib import import_module |
19 | 20 | from pathlib import Path |
|
35 | 36 | exit_cleanly_on_config_error, |
36 | 37 | init_resources_server, |
37 | 38 | list_environments, |
| 39 | + validate, |
38 | 40 | ) |
39 | 41 | from nemo_gym.config_types import ConfigError, NoServerInstancesError, ResourcesServerInstanceConfig |
40 | 42 | from nemo_gym.registry import EnvironmentEntry |
@@ -290,6 +292,53 @@ def test_config_error_base_catches_subclasses(self) -> None: |
290 | 292 | assert issubclass(NoServerInstancesError, ConfigError) |
291 | 293 |
|
292 | 294 |
|
| 295 | +class TestValidate: |
| 296 | + def _validate_config(self, monkeypatch: MonkeyPatch, tmp_path: Path, config_yaml: str) -> None: |
| 297 | + """Run the real `validate()` against a config file (no mocking of the parse path).""" |
| 298 | + config_file = tmp_path / "config.yaml" |
| 299 | + config_file.write_text(config_yaml) |
| 300 | + # chdir to a clean dir so a repo-local env.yaml isn't picked up; clear the parse cache. |
| 301 | + monkeypatch.chdir(tmp_path) |
| 302 | + monkeypatch.setattr(sys, "argv", ["gym", f"+config_paths=[{config_file}]"]) |
| 303 | + monkeypatch.setattr(nemo_gym.global_config, "_GLOBAL_CONFIG_DICT", None) |
| 304 | + validate() |
| 305 | + |
| 306 | + def test_valid_config_passes(self, monkeypatch: MonkeyPatch, tmp_path, capsys) -> None: |
| 307 | + # A well-formed config (real parse, real checks) -> "valid". |
| 308 | + self._validate_config( |
| 309 | + monkeypatch, |
| 310 | + tmp_path, |
| 311 | + "my_server:\n resources_servers:\n my_server:\n entrypoint: app.py\n domain: other\n", |
| 312 | + ) |
| 313 | + assert "valid" in capsys.readouterr().out.lower() |
| 314 | + |
| 315 | + def test_unknown_cross_reference_exits_nonzero(self, monkeypatch: MonkeyPatch, tmp_path) -> None: |
| 316 | + # An agent referencing a resources server that isn't defined -> ServerRefNotFoundError -> |
| 317 | + # clean exit(1) (real cross-reference validation, not a mock). |
| 318 | + bad = ( |
| 319 | + "my_agent:\n" |
| 320 | + " responses_api_agents:\n" |
| 321 | + " a:\n" |
| 322 | + " entrypoint: app.py\n" |
| 323 | + " resources_server:\n type: resources_servers\n name: does_not_exist\n" |
| 324 | + " model_server:\n type: responses_api_models\n name: policy_model\n" |
| 325 | + ) |
| 326 | + with raises(SystemExit) as exc_info: |
| 327 | + self._validate_config(monkeypatch, tmp_path, bad) |
| 328 | + assert exc_info.value.code == 1 |
| 329 | + |
| 330 | + def test_config_error_becomes_clean_exit(self, monkeypatch: MonkeyPatch) -> None: |
| 331 | + # Fast unit check of the decorator path: any ConfigError -> exit(1), no traceback. |
| 332 | + def _raise(**kwargs): |
| 333 | + raise NoServerInstancesError("nothing configured to run") |
| 334 | + |
| 335 | + monkeypatch.setattr(nemo_gym.cli.env, "get_global_config_dict", _raise) |
| 336 | + |
| 337 | + with raises(SystemExit) as exc_info: |
| 338 | + validate() |
| 339 | + assert exc_info.value.code == 1 |
| 340 | + |
| 341 | + |
293 | 342 | class TestListEnvironments: |
294 | 343 | _ALPHA = EnvironmentEntry( |
295 | 344 | name="alpha", |
|
0 commit comments