test: add isolated real-tmux smoke tests

Drive TmuxRunner against a throwaway tmux server (isolated via TMUX_TMPDIR)
so the tests never touch the user's live tmux. Register the 'tmux' pytest marker.
Skips when no tmux binary is present.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 20:56:58 -04:00
parent 8bfbead34f
commit d78d8ee149
2 changed files with 120 additions and 0 deletions
+5
View File
@@ -27,6 +27,11 @@ dev = [
"ty>=0.0.47",
]
[tool.pytest.ini_options]
markers = [
"tmux: real-tmux smoke tests; require a tmux binary and run on an isolated TMUX_TMPDIR server",
]
[tool.hqt.quality]
required_after_code_or_test_changes = [
"uv run ruff format src tests",
+115
View File
@@ -0,0 +1,115 @@
"""Real-tmux smoke tests.
These drive the actual TmuxRunner against a throwaway tmux server whose socket
lives in a temp TMUX_TMPDIR, so they never touch the user's live tmux sessions.
They skip when no tmux binary is available.
"""
import asyncio
import os
import shutil
import subprocess
import pytest
from hqt.tmux.runner import TmuxRunner
pytestmark = [
pytest.mark.tmux,
pytest.mark.skipif(shutil.which("tmux") is None, reason="tmux not installed"),
]
SESSION = "hqt-test"
def _tmux(env, *args):
"""Run a raw tmux command against the isolated server, return the result."""
return subprocess.run(
["tmux", *args],
env=env,
capture_output=True,
text=True,
check=False,
)
@pytest.fixture
def tmux_env(tmp_path, monkeypatch):
# Isolate tmux completely from the user's live server:
# - TMUX_TMPDIR points the default socket at a throwaway temp dir.
# - TMUX / TMUX_PANE are unset. When TMUX is set (i.e. the test runner is
# itself inside a tmux session), tmux talks to THAT server and ignores
# TMUX_TMPDIR, so new-session/kill-server would hit the user's live
# server and evict them. delenv removes them from os.environ so both the
# raw `_tmux` calls AND the TmuxRunner subprocesses (which inherit
# os.environ) stay on the isolated server.
monkeypatch.setenv("TMUX_TMPDIR", str(tmp_path))
monkeypatch.delenv("TMUX", raising=False)
monkeypatch.delenv("TMUX_PANE", raising=False)
env = {**os.environ, "TMUX_TMPDIR": str(tmp_path)}
env.pop("TMUX", None)
env.pop("TMUX_PANE", None)
_tmux(env, "new-session", "-d", "-s", SESSION, "-x", "200", "-y", "50")
# Tripwire: a freshly isolated server has exactly one session. If isolation
# silently failed we'd see the user's sessions here — fail loudly BEFORE any
# test does something destructive. (kill-server in teardown still targets
# only the isolated socket, because env has TMUX removed + TMUX_TMPDIR set.)
listed = _tmux(env, "list-sessions", "-F", "#{session_name}").stdout.split()
assert listed == [SESSION], f"tmux isolation failed; saw sessions: {listed}"
try:
yield env
finally:
_tmux(env, "kill-server")
@pytest.fixture
def runner(tmux_env):
return TmuxRunner(tmux_path="tmux", session_name=SESSION)
async def _wait_pane_dead(runner, window, timeout=2.0):
deadline = asyncio.get_running_loop().time() + timeout
while asyncio.get_running_loop().time() < deadline:
if await runner.is_pane_dead(window):
return True
await asyncio.sleep(0.05)
return await runner.is_pane_dead(window)
@pytest.mark.asyncio
async def test_new_window_appears_in_list(runner, tmp_path):
window_id = await runner.new_window("hqt-1", str(tmp_path), "sleep 60")
assert window_id is not None
assert "hqt-1" in await runner.list_windows()
@pytest.mark.asyncio
async def test_set_window_label_round_trips(runner, tmp_path, tmux_env):
await runner.new_window("hqt-1", str(tmp_path), "sleep 60")
await runner.set_window_label("hqt-1", "•hqt-1")
shown = _tmux(
tmux_env,
"show-options",
"-w",
"-t",
f"{SESSION}:=hqt-1",
"@hqt_label",
).stdout
assert "•hqt-1" in shown
@pytest.mark.asyncio
async def test_respawn_revives_dead_pane(runner, tmp_path):
# Window whose command exits immediately -> pane dies (remain-on-exit keeps it).
await runner.new_window("hqt-1", str(tmp_path), "true")
assert await _wait_pane_dead(runner, "hqt-1") is True
assert await runner.respawn_pane("hqt-1", "sleep 60", str(tmp_path)) is True
alive, _ = await runner.verify_window_alive("hqt-1", timeout=1.0)
assert alive is True
@pytest.mark.asyncio
async def test_apply_theme_sets_session_options(runner, tmux_env):
await runner.apply_theme()
shown = _tmux(tmux_env, "show-options", "-t", SESSION, "status-justify").stdout
assert "left" in shown