@@ -410,3 +410,35 @@ def test_find_open_port_raises_after_max_retries(self, monkeypatch: MonkeyPatch)
410410 assert "Unable to find an open port" in str (exc_info .value )
411411 assert "after 5 attempts" in str (exc_info .value )
412412 assert socket_instance .getsockname .call_count == 5
413+
414+ def test_get_global_config_dict_prevents_port_conflict_with_head_server (self , monkeypatch : MonkeyPatch ) -> None :
415+ """Integration test: verify that child servers never get the head server port."""
416+ exists_mock = MagicMock ()
417+ exists_mock .return_value = False
418+ monkeypatch .setattr (nemo_gym .global_config .Path , "exists" , exists_mock )
419+
420+ def mock_find_open_port (head_server_host = None , head_server_port = None , max_retries = 50 ):
421+ return 12345 # safe port
422+
423+ monkeypatch .setattr (nemo_gym .global_config , "find_open_port" , mock_find_open_port )
424+
425+ hydra_main_mock = MagicMock ()
426+
427+ def hydra_main_wrapper (fn ):
428+ """Trigger find_open_port by excluding port from the config"""
429+ config_dict = DictConfig (
430+ {"test_resource" : {"resources_servers" : {"test_server" : {"entrypoint" : "app.py" }}}}
431+ )
432+ return lambda : fn (config_dict )
433+
434+ hydra_main_mock .return_value = hydra_main_wrapper
435+ monkeypatch .setattr (nemo_gym .global_config .hydra , "main" , hydra_main_mock )
436+
437+ global_config_dict = get_global_config_dict ()
438+
439+ resource_port = global_config_dict ["test_resource" ]["resources_servers" ]["test_server" ]["port" ]
440+ head_port = global_config_dict ["head_server" ]["port" ]
441+
442+ assert resource_port == 12345
443+ assert head_port == 11000
444+ assert resource_port != head_port
0 commit comments