Files
myagents/tests/test_entrypoints.py
T
Zhengshou Lai 5c3f90e910 feat: add --tmux/-t option to run agents in attachable tmux sessions
Wraps the backend in 'tmux new-session -A' with a per-backend, per-directory
session name, so agent sessions survive SSH drops and re-running the same
command reattaches. Falls back to a direct run when already inside tmux,
and errors clearly when tmux is not installed.
2026-07-15 20:03:28 +08:00

223 lines
8.7 KiB
Python

"""Tests for standalone myclaude / mykimi / mycodex / myhermes entrypoints."""
from pathlib import Path
from unittest.mock import MagicMock, patch
from click.testing import CliRunner
from myagents.entrypoints import claude_cli, codex_cli, hermes_cli, kimi_cli
class TestMyclaudeEntrypoint:
"""``myclaude`` standalone entrypoint."""
def test_runs_claude(self) -> None:
runner = CliRunner()
with (
patch("myagents.launcher.shutil.which", return_value="/usr/bin/claude"),
patch("myagents.launcher.subprocess.run") as mock_run,
):
mock_run.return_value = MagicMock(returncode=0)
result = runner.invoke(claude_cli, [])
assert result.exit_code == 0
assert mock_run.call_args[0][0] == [
"/usr/bin/claude",
"--dangerously-skip-permissions",
]
def test_version_shows_myclaude(self) -> None:
runner = CliRunner()
result = runner.invoke(claude_cli, ["--version"])
assert result.exit_code == 0
assert "myclaude" in result.output
def test_passthrough(self, tmp_path: Path) -> None:
runner = CliRunner()
with (
patch("myagents.launcher.shutil.which", return_value="/usr/bin/claude"),
patch("myagents.launcher.subprocess.run") as mock_run,
):
mock_run.return_value = MagicMock(returncode=0)
result = runner.invoke(claude_cli, ["--cwd", str(tmp_path), "--resume"])
assert result.exit_code == 0
assert "--resume" in mock_run.call_args[0][0]
class TestMykimiEntrypoint:
"""``mykimi`` standalone entrypoint."""
def test_runs_kimi(self) -> None:
runner = CliRunner()
with (
patch("myagents.launcher.shutil.which", return_value="/usr/bin/kimi"),
patch("myagents.launcher.subprocess.run") as mock_run,
):
mock_run.return_value = MagicMock(returncode=0)
result = runner.invoke(kimi_cli, [])
assert result.exit_code == 0
assert mock_run.call_args[0][0] == ["/usr/bin/kimi"]
def test_version_shows_mykimi(self) -> None:
runner = CliRunner()
result = runner.invoke(kimi_cli, ["--version"])
assert result.exit_code == 0
assert "mykimi" in result.output
def test_passthrough(self, tmp_path: Path) -> None:
runner = CliRunner()
with (
patch("myagents.launcher.shutil.which", return_value="/usr/bin/kimi"),
patch("myagents.launcher.subprocess.run") as mock_run,
):
mock_run.return_value = MagicMock(returncode=0)
result = runner.invoke(kimi_cli, ["--cwd", str(tmp_path), "--resume"])
assert result.exit_code == 0
assert "--resume" in mock_run.call_args[0][0]
class TestMycodexEntrypoint:
"""``mycodex`` standalone entrypoint."""
def test_runs_codex(self) -> None:
runner = CliRunner()
with (
patch("myagents.launcher.shutil.which", return_value="/usr/bin/codex"),
patch("myagents.launcher.subprocess.run") as mock_run,
):
mock_run.return_value = MagicMock(returncode=0)
result = runner.invoke(codex_cli, [])
assert result.exit_code == 0
assert mock_run.call_args[0][0] == ["/usr/bin/codex", "--dangerously-bypass-approvals-and-sandbox"]
def test_version_shows_mycodex(self) -> None:
runner = CliRunner()
result = runner.invoke(codex_cli, ["--version"])
assert result.exit_code == 0
assert "mycodex" in result.output
def test_passthrough(self, tmp_path: Path) -> None:
runner = CliRunner()
with (
patch("myagents.launcher.shutil.which", return_value="/usr/bin/codex"),
patch("myagents.launcher.subprocess.run") as mock_run,
):
mock_run.return_value = MagicMock(returncode=0)
result = runner.invoke(codex_cli, ["--cwd", str(tmp_path), "-r", "abc123"])
assert result.exit_code == 0
assert mock_run.call_args[0][0] == ["/usr/bin/codex", "--dangerously-bypass-approvals-and-sandbox", "resume", "abc123"]
class TestMyhermesEntrypoint:
"""``myhermes`` standalone entrypoint."""
def test_runs_hermes(self) -> None:
runner = CliRunner()
with (
patch("myagents.launcher.shutil.which", return_value="/usr/bin/hermes"),
patch("myagents.launcher.subprocess.run") as mock_run,
):
mock_run.return_value = MagicMock(returncode=0)
result = runner.invoke(hermes_cli, [])
assert result.exit_code == 0
assert mock_run.call_args[0][0] == ["/usr/bin/hermes", "--yolo"]
def test_version_shows_myhermes(self) -> None:
runner = CliRunner()
result = runner.invoke(hermes_cli, ["--version"])
assert result.exit_code == 0
assert "myhermes" in result.output
def test_passthrough(self, tmp_path: Path) -> None:
runner = CliRunner()
with (
patch("myagents.launcher.shutil.which", return_value="/usr/bin/hermes"),
patch("myagents.launcher.subprocess.run") as mock_run,
):
mock_run.return_value = MagicMock(returncode=0)
result = runner.invoke(
hermes_cli, ["--cwd", str(tmp_path), "--resume", "abc123"]
)
assert result.exit_code == 0
assert mock_run.call_args[0][0][-3:] == [
"--resume",
"abc123",
"--no-restore-cwd",
]
class TestTmuxOption:
"""``--tmux`` / ``-t`` wraps the backend in an attachable tmux session."""
def _which(self, name: str) -> str:
return f"/usr/bin/{name}"
def test_tmux_wraps_command(self, tmp_path: Path) -> None:
runner = CliRunner()
with (
patch("myagents.launcher.shutil.which", side_effect=self._which),
patch("myagents.launcher.subprocess.run") as mock_run,
patch.dict("os.environ", {}, clear=False) as env,
):
env.pop("TMUX", None)
mock_run.return_value = MagicMock(returncode=0)
result = runner.invoke(claude_cli, ["--cwd", str(tmp_path), "--tmux"])
assert result.exit_code == 0
argv = mock_run.call_args[0][0]
assert argv[:3] == ["/usr/bin/tmux", "new-session", "-A"]
assert "-c" in argv and str(tmp_path) in argv
assert argv[-1] == "/usr/bin/claude --dangerously-skip-permissions"
def test_tmux_session_name_deterministic(self, tmp_path: Path) -> None:
from myagents.launcher import _tmux_session_name
first = _tmux_session_name("claude", tmp_path)
second = _tmux_session_name("claude", tmp_path)
other = _tmux_session_name("kimi", tmp_path)
assert first == second
assert first != other
assert first.startswith("myclaude-")
def test_tmux_with_passthrough(self, tmp_path: Path) -> None:
runner = CliRunner()
with (
patch("myagents.launcher.shutil.which", side_effect=self._which),
patch("myagents.launcher.subprocess.run") as mock_run,
patch.dict("os.environ", {}, clear=False) as env,
):
env.pop("TMUX", None)
mock_run.return_value = MagicMock(returncode=0)
result = runner.invoke(
claude_cli, ["--cwd", str(tmp_path), "-t", "-r", "sess-1"]
)
assert result.exit_code == 0
argv = mock_run.call_args[0][0]
assert argv[0] == "/usr/bin/tmux"
assert argv[-1].endswith("-r sess-1")
def test_tmux_skipped_when_already_inside(self, tmp_path: Path) -> None:
runner = CliRunner()
with (
patch("myagents.launcher.shutil.which", side_effect=self._which),
patch("myagents.launcher.subprocess.run") as mock_run,
patch.dict("os.environ", {"TMUX": "/tmp/tmux-501/default,1,0"}),
):
mock_run.return_value = MagicMock(returncode=0)
result = runner.invoke(claude_cli, ["--cwd", str(tmp_path), "-t"])
assert result.exit_code == 0
argv = mock_run.call_args[0][0]
assert argv[0] == "/usr/bin/claude"
def test_tmux_missing_binary(self, tmp_path: Path) -> None:
runner = CliRunner()
def _which(name: str) -> str | None:
return None if name == "tmux" else f"/usr/bin/{name}"
with (
patch("myagents.launcher.shutil.which", side_effect=_which),
patch.dict("os.environ", {}, clear=False) as env,
):
env.pop("TMUX", None)
result = runner.invoke(claude_cli, ["--cwd", str(tmp_path), "-t"])
assert result.exit_code == 127