Skip to content

Commit bf2756b

Browse files
committed
pytest(fix[fixtures]): move control fixtures into plugin
why: keep control-mode test helpers available when libtmux is used as a plugin dependency. what: - Relocate control_sandbox/control_client_logs fixtures into pytest_plugin - Drop duplicate fixtures from top-level conftest
1 parent 36dceb7 commit bf2756b

2 files changed

Lines changed: 97 additions & 105 deletions

File tree

conftest.py

Lines changed: 0 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,13 @@
1010

1111
from __future__ import annotations
1212

13-
import contextlib
1413
import pathlib
1514
import shutil
16-
import subprocess
1715
import typing as t
18-
import uuid
1916

2017
import pytest
2118
from _pytest.doctest import DoctestItem
2219

23-
from libtmux._internal.engines.control_protocol import CommandContext, ControlProtocol
2420
from libtmux.pane import Pane
2521
from libtmux.pytest_plugin import USING_ZSH
2622
from libtmux.server import Server
@@ -78,104 +74,3 @@ def setup_session(
7874
"""Session-level test configuration for pytest."""
7975
if USING_ZSH:
8076
request.getfixturevalue("zshrc")
81-
82-
83-
# ---------------------------------------------------------------------------
84-
# Control-mode sandbox helper
85-
# ---------------------------------------------------------------------------
86-
87-
88-
@pytest.fixture
89-
@contextlib.contextmanager
90-
def control_sandbox(
91-
monkeypatch: pytest.MonkeyPatch,
92-
tmp_path_factory: pytest.TempPathFactory,
93-
) -> t.Iterator[Server]:
94-
"""Provide an isolated control-mode server for a test.
95-
96-
- Creates a unique tmux socket name per invocation
97-
- Isolates HOME and TMUX_TMPDIR under a per-test temp directory
98-
- Clears TMUX env var to avoid inheriting user sessions
99-
- Uses ControlModeEngine; on exit, kills the server best-effort
100-
"""
101-
socket_name = f"libtmux_test_{uuid.uuid4().hex[:8]}"
102-
base = tmp_path_factory.mktemp("ctrl_sandbox")
103-
home = base / "home"
104-
tmux_tmpdir = base / "tmux"
105-
home.mkdir()
106-
tmux_tmpdir.mkdir()
107-
108-
monkeypatch.setenv("HOME", str(home))
109-
monkeypatch.setenv("TMUX_TMPDIR", str(tmux_tmpdir))
110-
monkeypatch.delenv("TMUX", raising=False)
111-
112-
from libtmux._internal.engines.control_mode import ControlModeEngine
113-
114-
server = Server(socket_name=socket_name, engine=ControlModeEngine())
115-
116-
try:
117-
yield server
118-
finally:
119-
with contextlib.suppress(Exception):
120-
server.kill()
121-
with contextlib.suppress(Exception):
122-
server.engine.close()
123-
124-
125-
@pytest.fixture
126-
def control_client_logs(
127-
control_sandbox: t.ContextManager[Server],
128-
tmp_path_factory: pytest.TempPathFactory,
129-
) -> t.Iterator[tuple[subprocess.Popen[str], ControlProtocol, pathlib.Path]]:
130-
"""Spawn a raw tmux -C client against the sandbox and log stdout/stderr."""
131-
base = tmp_path_factory.mktemp("ctrl_logs")
132-
stdout_path = base / "control_stdout.log"
133-
stderr_path = base / "control_stderr.log"
134-
135-
with control_sandbox as server:
136-
cmd = [
137-
"tmux",
138-
"-L",
139-
server.socket_name or "",
140-
"-C",
141-
"attach-session",
142-
"-t",
143-
"ctrltest",
144-
]
145-
# Ensure ctrltest exists
146-
server.cmd("new-session", "-d", "-s", "ctrltest")
147-
stdout_f = stdout_path.open("w+", buffering=1)
148-
stderr_f = stderr_path.open("w+", buffering=1)
149-
proc = subprocess.Popen(
150-
cmd,
151-
stdin=subprocess.PIPE,
152-
stdout=stdout_f,
153-
stderr=stderr_f,
154-
text=True,
155-
bufsize=1,
156-
)
157-
proto = ControlProtocol()
158-
# tmux -C will emit a %begin/%end pair for this initial attach-session;
159-
# queue a matching context so the parser has a pending command.
160-
proto.register_command(CommandContext(argv=list(cmd)))
161-
try:
162-
yield proc, proto, stdout_path
163-
finally:
164-
with contextlib.suppress(Exception):
165-
if proc.stdin:
166-
proc.stdin.write("kill-session -t ctrltest\n")
167-
proc.stdin.flush()
168-
with contextlib.suppress(Exception):
169-
if proc.stdin:
170-
proc.stdin.close()
171-
with contextlib.suppress(Exception):
172-
proc.terminate()
173-
try:
174-
proc.wait(timeout=2)
175-
except subprocess.TimeoutExpired:
176-
proc.kill()
177-
proc.wait(timeout=2)
178-
with contextlib.suppress(Exception):
179-
stdout_f.close()
180-
with contextlib.suppress(Exception):
181-
stderr_f.close()

src/libtmux/pytest_plugin.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@
88
import logging
99
import os
1010
import pathlib
11+
import subprocess
1112
import typing as t
13+
import uuid
1214

1315
import pytest
1416

1517
from libtmux import exc
1618
from libtmux._internal.engines.base import Engine
1719
from libtmux._internal.engines.control_mode import ControlModeEngine
20+
from libtmux._internal.engines.control_protocol import CommandContext, ControlProtocol
1821
from libtmux._internal.engines.subprocess_engine import SubprocessEngine
1922
from libtmux.server import Server
2023
from libtmux.test.constants import TEST_SESSION_PREFIX
@@ -338,6 +341,100 @@ def fin() -> None:
338341
)
339342

340343

344+
@pytest.fixture
345+
@contextlib.contextmanager
346+
def control_sandbox(
347+
monkeypatch: pytest.MonkeyPatch,
348+
tmp_path_factory: pytest.TempPathFactory,
349+
) -> t.Iterator[Server]:
350+
"""Provide an isolated control-mode server for a test.
351+
352+
- Creates a unique tmux socket name per invocation
353+
- Isolates HOME and TMUX_TMPDIR under a per-test temp directory
354+
- Clears TMUX env var to avoid inheriting user sessions
355+
- Uses ControlModeEngine; on exit, kills the server best-effort
356+
"""
357+
socket_name = f"libtmux_test_{uuid.uuid4().hex[:8]}"
358+
base = tmp_path_factory.mktemp("ctrl_sandbox")
359+
home = base / "home"
360+
tmux_tmpdir = base / "tmux"
361+
home.mkdir()
362+
tmux_tmpdir.mkdir()
363+
364+
monkeypatch.setenv("HOME", str(home))
365+
monkeypatch.setenv("TMUX_TMPDIR", str(tmux_tmpdir))
366+
monkeypatch.delenv("TMUX", raising=False)
367+
368+
server = Server(socket_name=socket_name, engine=ControlModeEngine())
369+
370+
try:
371+
yield server
372+
finally:
373+
with contextlib.suppress(Exception):
374+
server.kill()
375+
with contextlib.suppress(Exception):
376+
server.engine.close()
377+
378+
379+
@pytest.fixture
380+
def control_client_logs(
381+
control_sandbox: t.ContextManager[Server],
382+
tmp_path_factory: pytest.TempPathFactory,
383+
) -> t.Iterator[tuple[subprocess.Popen[str], ControlProtocol, pathlib.Path]]:
384+
"""Spawn a raw tmux -C client against the sandbox and log stdout/stderr."""
385+
base = tmp_path_factory.mktemp("ctrl_logs")
386+
stdout_path = base / "control_stdout.log"
387+
stderr_path = base / "control_stderr.log"
388+
389+
with control_sandbox as server:
390+
cmd = [
391+
"tmux",
392+
"-L",
393+
server.socket_name or "",
394+
"-C",
395+
"attach-session",
396+
"-t",
397+
"ctrltest",
398+
]
399+
# Ensure ctrltest exists
400+
server.cmd("new-session", "-d", "-s", "ctrltest")
401+
stdout_f = stdout_path.open("w+", buffering=1)
402+
stderr_f = stderr_path.open("w+", buffering=1)
403+
proc = subprocess.Popen(
404+
cmd,
405+
stdin=subprocess.PIPE,
406+
stdout=stdout_f,
407+
stderr=stderr_f,
408+
text=True,
409+
bufsize=1,
410+
)
411+
proto = ControlProtocol()
412+
# tmux -C will emit a %begin/%end pair for this initial attach-session;
413+
# queue a matching context so the parser has a pending command.
414+
proto.register_command(CommandContext(argv=list(cmd)))
415+
try:
416+
yield proc, proto, stdout_path
417+
finally:
418+
with contextlib.suppress(Exception):
419+
if proc.stdin:
420+
proc.stdin.write("kill-session -t ctrltest\n")
421+
proc.stdin.flush()
422+
with contextlib.suppress(Exception):
423+
if proc.stdin:
424+
proc.stdin.close()
425+
with contextlib.suppress(Exception):
426+
proc.terminate()
427+
try:
428+
proc.wait(timeout=2)
429+
except subprocess.TimeoutExpired:
430+
proc.kill()
431+
proc.wait(timeout=2)
432+
with contextlib.suppress(Exception):
433+
stdout_f.close()
434+
with contextlib.suppress(Exception):
435+
stderr_f.close()
436+
437+
341438
@pytest.fixture
342439
def engine_name(request: pytest.FixtureRequest) -> str:
343440
"""Engine selector fixture, driven by CLI or parametrization."""

0 commit comments

Comments
 (0)