Skip to content

test(conftest): reap leaked libtmux_test* sockets after session#23

Closed
tony wants to merge 2 commits intomainfrom
fix/pytest-reap-leaked-sockets
Closed

test(conftest): reap leaked libtmux_test* sockets after session#23
tony wants to merge 2 commits intomainfrom
fix/pytest-reap-leaked-sockets

Conversation

@tony
Copy link
Copy Markdown
Member

@tony tony commented Apr 19, 2026

Summary

Add 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.

Upstream libtmux's pytest plugin (tmux-python/libtmux#660) does not reliably kill the per-test tmux daemons or unlink the socket files on teardown. Over repeated runs, /tmp/tmux-<uid>/ accumulates stale entries indefinitely — 10,693 were observed on a dev machine before this fixture ran.

Root-cause fix filed upstream at tmux-python/libtmux#661. Once that lands and the downstream libtmux dependency is bumped, this local mitigation can be reverted — the upstream fixture finalizers will do the reaping at the source.

Until then this provides a local safety net so CI and dev machines stay clean regardless of which libtmux version the working checkout pulls in.

Safe properties:

  • Prefix match on libtmux_test* only — never touches the developer's real default socket or any non-test socket.
  • Scope session + autouse — runs exactly once after every pytest invocation without opt-in.
  • Idempotent under xdist (each worker is its own session; kill() and unlink(missing_ok=True) are safe on already-gone sockets).
  • POSIX-guarded via hasattr(os, 'geteuid'); errors suppressed because any failure is a race with a concurrent worker or a vanished socket — neither actionable.

Test plan

  • uv run ruff check . --fix --show-fixes
  • uv run ruff format .
  • uv run mypy
  • uv run py.test --reruns 0 -vvv (363 passed)
  • just build-docs

End-to-end verification: ls /tmp/tmux-\$(id -u) | grep -c libtmux_test dropped 10693 → 0 after a single uv run pytest on this branch.

Companion PRs

Part of a batch of post-0.1.0a2 smoke-test fixes. Each PR is independent and can land in any order:

Closes #20

Upstream libtmux's pytest plugin (tmux-python/libtmux#660) creates
per-test tmux servers on `libtmux_test<N>` sockets but does not reliably
kill the daemons or unlink the socket files on teardown. Over repeated
runs, `/tmp/tmux-<uid>/` accumulates stale socket entries indefinitely
(observed: 10k+ on a dev machine after normal activity, surfaced by
the new `list_servers` tool).

Add a session-scoped autouse fixture that, after every pytest run,
globs `/tmp/tmux-<uid>/libtmux_test*`, kills live daemons, and unlinks
stale socket files. Prefix match keeps the reaper away from the
developer's real `default` socket. Errors are suppressed — the cleanup
is advisory and any failure is a race with a concurrent xdist worker
or a vanished socket, neither actionable.

Verified locally: `ls /tmp/tmux-$(id -u) | grep -c libtmux_test`
dropped from 10693 to 0 after a single `uv run pytest`.

Closes #20
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 19, 2026

Codecov Report

❌ Patch coverage is 73.33333% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.89%. Comparing base (58af71f) to head (5e2f949).

Files with missing lines Patch % Lines
tests/conftest.py 73.33% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #23      +/-   ##
==========================================
- Coverage   87.01%   86.89%   -0.12%     
==========================================
  Files          38       38              
  Lines        1710     1725      +15     
  Branches      201      205       +4     
==========================================
+ Hits         1488     1499      +11     
- Misses        164      166       +2     
- Partials       58       60       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

tony added a commit to tmux-python/libtmux that referenced this pull request Apr 19, 2026
why: The `server` and `TestServer` fixtures called `server.kill()` on
teardown but never unlinked the socket file under `/tmp/tmux-<uid>/`.
tmux does not reliably `unlink(2)` its own socket on non-graceful
exit, so every test run using these fixtures left behind stale
`libtmux_test*` entries that accumulated indefinitely — 10k+ were
observed on a long-lived dev machine, and downstream consumers
(libtmux-mcp, tmuxp) inherited the leak.

what:
- **Add `_reap_test_server(socket_name)`** helper in
  `src/libtmux/pytest_plugin.py`. Kills the daemon when alive, then
  unconditionally unlinks the computed socket path
  (`$TMUX_TMPDIR/tmux-<uid>/<name>`). Needed because
  `Server(socket_name=...)` leaves `socket_path` unset, so the helper
  recomputes the resolution the same way tmux does.
- **Delegate both fixture finalizers to the helper.** `server`
  passes `server.socket_name`; `TestServer` iterates the sockets it
  tracked in `on_init`. Kill-then-unlink applies uniformly.
- **Suppress cleanup-time errors** via `contextlib.suppress` on both
  the kill and the unlink. A finalizer that raised would replace the
  real test failure with a cleanup error; and any failure (socket
  already gone, permissions changed, race with a pytest-xdist
  worker) is not actionable.
- **Regression coverage** in `tests/test_pytest_plugin.py`: boot a
  real daemon, confirm the socket file exists, reap, assert it is
  gone; plus no-op paths for a missing socket and for a `None`
  socket name.

Fixes #660

Downstream mitigation tmux-python/libtmux-mcp#23.
tony added a commit that referenced this pull request Apr 19, 2026
libtmux 0.55.1 ships the pytest-plugin socket-reaper fix
(tmux-python/libtmux#661) so the `server` / `TestServer` fixtures now
kill the tmux daemon AND unlink the socket file under
`/tmp/tmux-<uid>/` on teardown. This bumps the runtime floor and
refreshes uv.lock so CI and fresh installs pick up the fix.

Verified locally: after the bump, `uv run pytest` leaves a single
residual socket per run (down from thousands accumulating across
runs pre-fix). The remaining straggler originates outside the
standard fixture teardown path and is a separate, minor issue —
not a regression.

Supersedes the local conftest reaper proposed in
#23; that PR can now be closed without merge.
@tony tony closed this in 8e2e0c3 Apr 19, 2026
@tony tony deleted the fix/pytest-reap-leaked-sockets branch April 19, 2026 19:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants