Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@
_Notes on upcoming releases will be added here_
<!-- END PLACEHOLDER - ADD NEW CHANGELOG ENTRIES BELOW THIS LINE -->

### Testing

- Added a session-scoped autouse fixture in `tests/conftest.py` that
reaps leaked `libtmux_test*` tmux daemons and socket files under
`/tmp/tmux-<uid>/` after every test run. Works around upstream
[tmux-python/libtmux#661](https://github.com/tmux-python/libtmux/pull/661),
whose pytest plugin does not reliably kill servers or unlink socket
files during teardown. Idempotent and safe under `pytest-xdist`
(#20).

## libtmux-mcp 0.1.0a2 (2026-04-19)

_FastMCP alignment: new tools, prompts, and middleware (#15)_
Expand Down
50 changes: 49 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,67 @@

from __future__ import annotations

import contextlib
import os
import pathlib
import typing as t

import pytest
from libtmux.server import Server

from libtmux_mcp._utils import _server_cache

if t.TYPE_CHECKING:
from libtmux.pane import Pane
from libtmux.server import Server
from libtmux.session import Session
from libtmux.window import Window


@pytest.fixture(scope="session", autouse=True)
def _reap_leaked_libtmux_test_sockets() -> t.Generator[None, None, None]:
"""Reap leaked ``libtmux_test*`` daemons and socket files post-suite.

libtmux's pytest plugin creates per-test tmux servers on
``libtmux_test<N>`` sockets but does not reliably kill the daemons
or ``unlink`` the socket files on teardown — see
`tmux-python/libtmux#660 <https://github.com/tmux-python/libtmux/issues/660>`_.
Without this finalizer ``/tmp/tmux-<uid>/`` accumulates hundreds of
stale socket entries across test runs (10k+ on long-lived dev
machines per the #20 report).

Scope is ``session``: runs after every ``pytest`` invocation. Prefix
match on ``libtmux_test`` only — matches the literal prefix set by
libtmux's ``pytest_plugin.py`` and never touches the developer's
real ``default`` socket or any non-test socket. Safe under ``xdist``:
each worker is its own pytest session and the socket operations
(``kill_server`` / ``unlink``) are idempotent.
"""
yield

# ``geteuid`` is Unix-only; the tmux server socket directory only
# exists on POSIX. Skip on platforms without it rather than erroring.
if not hasattr(os, "geteuid"):
return

tmpdir = pathlib.Path(f"/tmp/tmux-{os.geteuid()}")
if not tmpdir.is_dir():
return

for socket_path in tmpdir.glob("libtmux_test*"):
# Defensive cleanup: if the server is still alive, kill it; then
# unlink the socket file whether or not kill succeeded (tmux
# sometimes leaves the file on disk after the daemon exits).
# Any step may fail because the socket has already vanished,
# permissions changed, or a concurrent run raced us — none of
# that is actionable here, so swallow the error and move on.
with contextlib.suppress(Exception):
server = Server(socket_name=socket_path.name)
if server.is_alive():
server.kill()
with contextlib.suppress(OSError):
socket_path.unlink(missing_ok=True)


@pytest.fixture(autouse=True)
def _clear_server_cache() -> t.Generator[None, None, None]:
"""Clear the MCP server cache between tests."""
Expand Down
Loading