|
|
|
|
@ -1,6 +1,7 @@
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
import subprocess
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
@ -22,6 +23,7 @@ from iticli.cli import (
|
|
|
|
|
parse_run_args,
|
|
|
|
|
resolve_worker_module,
|
|
|
|
|
resolve_update_packages,
|
|
|
|
|
run,
|
|
|
|
|
update_sync_cmd,
|
|
|
|
|
update_pyproject_git_tags,
|
|
|
|
|
version_is_newer,
|
|
|
|
|
@ -33,6 +35,71 @@ def write(path: Path, text: str) -> None:
|
|
|
|
|
path.write_text(text, encoding="utf-8")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_run_returns_130_after_keyboard_interrupt(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
|
|
|
|
class DummyProcess:
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
|
self.wait_calls: list[int | None] = []
|
|
|
|
|
|
|
|
|
|
def wait(self, timeout: int | None = None) -> int:
|
|
|
|
|
self.wait_calls.append(timeout)
|
|
|
|
|
if timeout is None:
|
|
|
|
|
raise KeyboardInterrupt
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
def terminate(self) -> None:
|
|
|
|
|
raise AssertionError("terminate should not be called after graceful child exit")
|
|
|
|
|
|
|
|
|
|
def kill(self) -> None:
|
|
|
|
|
raise AssertionError("kill should not be called after graceful child exit")
|
|
|
|
|
|
|
|
|
|
process = DummyProcess()
|
|
|
|
|
|
|
|
|
|
def fake_popen(cmd: list[str], cwd: str, env: dict[str, str]) -> DummyProcess:
|
|
|
|
|
assert cmd == ["demo"]
|
|
|
|
|
assert cwd == str(tmp_path)
|
|
|
|
|
assert env["PYTHONPATH"]
|
|
|
|
|
return process
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(subprocess, "Popen", fake_popen)
|
|
|
|
|
|
|
|
|
|
assert run(["demo"], tmp_path, extra_env={"PYTHONPATH": "."}) == 130
|
|
|
|
|
assert process.wait_calls == [None, 30]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_run_terminates_child_when_interrupt_shutdown_times_out(
|
|
|
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
|
|
|
tmp_path: Path,
|
|
|
|
|
) -> None:
|
|
|
|
|
class DummyProcess:
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
|
self.terminated = False
|
|
|
|
|
self.killed = False
|
|
|
|
|
|
|
|
|
|
def wait(self, timeout: int | None = None) -> int:
|
|
|
|
|
if timeout is None:
|
|
|
|
|
raise KeyboardInterrupt
|
|
|
|
|
if timeout == 30:
|
|
|
|
|
raise subprocess.TimeoutExpired(["demo"], timeout)
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
def terminate(self) -> None:
|
|
|
|
|
self.terminated = True
|
|
|
|
|
|
|
|
|
|
def kill(self) -> None:
|
|
|
|
|
self.killed = True
|
|
|
|
|
|
|
|
|
|
process = DummyProcess()
|
|
|
|
|
|
|
|
|
|
def fake_popen(cmd: list[str], cwd: str, env: dict[str, str]) -> DummyProcess:
|
|
|
|
|
return process
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(subprocess, "Popen", fake_popen)
|
|
|
|
|
|
|
|
|
|
assert run(["demo"], tmp_path) == 130
|
|
|
|
|
assert process.terminated is True
|
|
|
|
|
assert process.killed is False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_detect_framework_project(tmp_path: Path) -> None:
|
|
|
|
|
write(
|
|
|
|
|
tmp_path / "pyproject.toml",
|
|
|
|
|
|