Files
hive/tools/tests/test_terminal_tools_pty.py
2026-04-30 19:52:34 -07:00

110 lines
3.4 KiB
Python

"""PTY sessions: bash-on-macOS, prompt sentinel, raw I/O, zsh refusal."""
from __future__ import annotations
import sys
import time
import pytest
pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="PTY is POSIX-only")
@pytest.fixture
def pty_tools(mcp):
from terminal_tools.pty.tools import register_pty_tools
register_pty_tools(mcp)
return {
"open": mcp._tool_manager._tools["terminal_pty_open"].fn,
"run": mcp._tool_manager._tools["terminal_pty_run"].fn,
"close": mcp._tool_manager._tools["terminal_pty_close"].fn,
}
def test_open_close_basic(pty_tools):
opened = pty_tools["open"]()
assert "session_id" in opened
assert opened["shell"] == "/bin/bash", "terminal-tools must default to bash, not zsh"
closed = pty_tools["close"](session_id=opened["session_id"])
assert closed.get("already_closed") in (False, None)
def test_bash_on_darwin():
"""Even on macOS, the resolved shell is /bin/bash, not /bin/zsh."""
from terminal_tools.common.limits import _resolve_shell
assert _resolve_shell(True) == "/bin/bash"
def test_pty_run_command(pty_tools):
opened = pty_tools["open"]()
sid = opened["session_id"]
try:
result = pty_tools["run"](session_id=sid, command="echo hello-pty", timeout_sec=5)
assert result.get("timed_out") is False
assert "hello-pty" in result["output"]
assert result["prompt_after"] is True
finally:
pty_tools["close"](session_id=sid)
def test_pty_state_persists(pty_tools):
opened = pty_tools["open"]()
sid = opened["session_id"]
try:
pty_tools["run"](session_id=sid, command="MY_VAR=42")
result = pty_tools["run"](session_id=sid, command="echo $MY_VAR", timeout_sec=3)
assert "42" in result["output"]
finally:
pty_tools["close"](session_id=sid)
def test_raw_send_then_read_only(pty_tools):
"""Drive the python REPL via raw_send + read_only."""
opened = pty_tools["open"]()
sid = opened["session_id"]
try:
# Launch python with our own prompt regex
pty_tools["run"](
session_id=sid,
command="python3 -q",
expect=r">>>\s*$",
timeout_sec=10,
)
pty_tools["run"](session_id=sid, command="x = 7\n", raw_send=True)
pty_tools["run"](session_id=sid, command="print(x*x)\n", raw_send=True)
time.sleep(0.5)
drained = pty_tools["run"](session_id=sid, read_only=True, timeout_sec=2)
assert "49" in drained["output"]
finally:
pty_tools["close"](session_id=sid, force=True)
def test_session_busy(pty_tools):
"""Concurrent run() calls on the same session return 'session busy'."""
import threading
opened = pty_tools["open"]()
sid = opened["session_id"]
try:
results = []
def run_long():
results.append(pty_tools["run"](session_id=sid, command="sleep 2", timeout_sec=5))
t = threading.Thread(target=run_long)
t.start()
time.sleep(0.2)
# Concurrent call should fail
result = pty_tools["run"](session_id=sid, command="echo nope", timeout_sec=1)
assert "error" in result and "busy" in result["error"].lower()
t.join(timeout=10)
finally:
pty_tools["close"](session_id=sid, force=True)
def test_unknown_session(pty_tools):
result = pty_tools["run"](session_id="pty_doesnotexist", command="ls")
assert "error" in result