Skip to content

Commit b72912f

Browse files
HJSangclaude
andcommitted
Accept the miles-train command where the fault injector needs the synchronous driver
The step-boundary fault actions recognized the synchronous driver only as train.py, so a run launched with the installed miles-train command aborted. The deployment-entrypoint test now reads the drivers' code where it lives, in miles.entrypoints, including the main() the root scripts call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent e50dd51 commit b72912f

3 files changed

Lines changed: 18 additions & 4 deletions

File tree

‎miles/utils/test_utils/fault_injector/actions/base.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,12 @@ async def __call__(self, *, context: FaultHookContext, resources: FaultHookResou
3939

4040

4141
PARKABLE_TRAIN_SCRIPT: str = "train.py"
42+
# The same driver installed as a command (setup.py's console_scripts).
43+
PARKABLE_TRAIN_COMMAND: str = "miles-train"
4244

4345

4446
def _assert_synchronous_orchestrator(args: object, trainer_model_id: str | None, action: str) -> None:
45-
assert (script := Path(sys.argv[0]).name) == PARKABLE_TRAIN_SCRIPT, (
47+
assert (script := Path(sys.argv[0]).name) in (PARKABLE_TRAIN_SCRIPT, PARKABLE_TRAIN_COMMAND), (
4648
f"{action} runs between synchronous steps, and only {PARKABLE_TRAIN_SCRIPT} stands still there; "
4749
f"{script} has already started the next rollout by the time it reaches here"
4850
)

‎tests/fast/utils/test_utils/fault_injector/test_frozen.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
PARKABLE_TRAIN_SCRIPT,
1313
FaultHookContext,
1414
FaultHookResources,
15+
_assert_synchronous_orchestrator,
1516
)
1617
from miles.utils.test_utils.fault_injector.actions.cell import RestartRolloutCellAction
1718
from miles.utils.test_utils.fault_injector.actions.frozen import (
@@ -166,6 +167,15 @@ def test_the_parkable_script_is_the_default_launch_script(self) -> None:
166167
"""The freeze guard and launch harness must agree on the synchronous script."""
167168
assert PARKABLE_TRAIN_SCRIPT == DEFAULT_TRAIN_SCRIPT
168169

170+
@pytest.mark.parametrize("script", ["/miles/train.py", "/venv/bin/miles-train"])
171+
async def test_the_synchronous_driver_passes_however_it_was_launched(
172+
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path, script: str
173+
) -> None:
174+
"""The checkout script and the installed command run the same synchronous loop."""
175+
monkeypatch.setattr(sys, "argv", [script])
176+
args = SimpleNamespace(ci_fault_hooks_path=str(tmp_path / "plan.json"), update_weights_interval=1)
177+
_assert_synchronous_orchestrator(args, trainer_model_id=None, action="sleep_forever")
178+
169179
async def test_an_unarmed_async_loop_is_not_subject_to_freeze_guards(
170180
self, configure_hooks: Callable[..., _FaultHookController], monkeypatch: pytest.MonkeyPatch
171181
) -> None:

‎tests/fast/utils/workers/test_deployment_entrypoint.py‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,10 @@ def test_a_deployment_that_carries_no_trainer_starts_nothing(self, monkeypatch):
122122
assert fake.calls == []
123123

124124

125+
# where each driver's code lives; train.py and train_async.py at the root only call these mains
125126
_DRIVER_SCRIPTS = [
126-
"train.py",
127-
"train_async.py",
127+
"miles/entrypoints/train.py",
128+
"miles/entrypoints/train_async.py",
128129
"train_multi_policy.py",
129130
]
130131

@@ -138,11 +139,12 @@ def test_a_driver_starts_its_training_without_asking_what_this_launch_deploys(se
138139

139140

140141
def _functions_called_in_main(script: str) -> set[str]:
142+
"""Calls in the script's ``if __name__ == "__main__"`` block, or in the ``main()`` that block runs."""
141143
tree = ast.parse((REPO_ROOT / script).read_text())
142144
return {
143145
ast.unparse(node.func)
144146
for statement in tree.body
145-
if isinstance(statement, ast.If)
147+
if isinstance(statement, ast.If) or (isinstance(statement, ast.FunctionDef) and statement.name == "main")
146148
for node in ast.walk(statement)
147149
if isinstance(node, ast.Call)
148150
}

0 commit comments

Comments
 (0)