mirror of
https://github.com/NixOS/nixpkgs.git
synced 2026-10-30 16:10:36 +00:00
Changelog: https://github.com/tmux-python/libtmux/blob/v0.62.0/CHANGES Backport the tmux 3.8 fixes from upstream PR #758 so the full test suite passes with tmux 3.8: - decode the new hooks tmux 3.8 reports from show-hooks, which otherwise break Hooks - run the display-popup tests through a terminal client, as control-mode clients no longer execute popup commands - account for tmux 3.8 counting borders in floating pane geometry Upstream PR: https://github.com/tmux-python/libtmux/pull/758 Assisted-by: Claude Code (Claude Opus 5.5)
192 lines
6.3 KiB
Diff
192 lines
6.3 KiB
Diff
From 2466d5ec8b8e185c71839196aaac8cf59133ff75 Mon Sep 17 00:00:00 2001
|
|
From: Tony Narlock <tony@git-pull.com>
|
|
Date: Sun, 13 Sep 2026 10:33:12 -0500
|
|
Subject: [PATCH] Popup(test): Attach a terminal client
|
|
|
|
why: Control clients do not execute popup commands on tmux master.
|
|
|
|
what:
|
|
- Exercise popup flags and completion through a real terminal client
|
|
- Bound process cleanup and close PTYs on setup or wait failure
|
|
- State the control-client limitation in its module documentation
|
|
|
|
Upstream: https://github.com/tmux-python/libtmux/pull/758/commits/2466d5ec8b8e185c71839196aaac8cf59133ff75
|
|
---
|
|
src/libtmux/_internal/control_mode.py | 4 +-
|
|
tests/test_pane.py | 97 +++++++++++++++++++++------
|
|
2 files changed, 80 insertions(+), 21 deletions(-)
|
|
|
|
diff --git a/src/libtmux/_internal/control_mode.py b/src/libtmux/_internal/control_mode.py
|
|
index 6c1d230..1d73271 100644
|
|
--- a/src/libtmux/_internal/control_mode.py
|
|
+++ b/src/libtmux/_internal/control_mode.py
|
|
@@ -1,8 +1,8 @@
|
|
"""Control-mode client context manager for tmux testing.
|
|
|
|
Provides a context manager that spawns a ``tmux -C attach-session``
|
|
-subprocess, creating a real tmux client that satisfies commands
|
|
-requiring an attached client (e.g. ``display-popup``, ``detach-client``).
|
|
+subprocess, creating a real tmux client for commands such as
|
|
+``detach-client``. Popups require a terminal client to run their commands.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
diff --git a/tests/test_pane.py b/tests/test_pane.py
|
|
index 416032c..4865203 100644
|
|
--- a/tests/test_pane.py
|
|
+++ b/tests/test_pane.py
|
|
@@ -2,9 +2,17 @@
|
|
|
|
from __future__ import annotations
|
|
|
|
+import contextlib
|
|
+import fcntl
|
|
import logging
|
|
+import os
|
|
import pathlib
|
|
+import select
|
|
import shutil
|
|
+import struct
|
|
+import subprocess
|
|
+import termios
|
|
+import threading
|
|
import typing as t
|
|
|
|
import pytest
|
|
@@ -1082,6 +1090,65 @@ DISPLAY_POPUP_CASES: list[DisplayPopupCase] = [
|
|
]
|
|
|
|
|
|
+@pytest.fixture
|
|
+def terminal_client(server: Server, session: Session) -> t.Iterator[str]:
|
|
+ """Attach a terminal client for popup execution."""
|
|
+ socket_path = server.cmd("display-message", "-p", "#{socket_path}").stdout[0]
|
|
+ with contextlib.ExitStack() as cleanup:
|
|
+ master, slave = os.openpty()
|
|
+ cleanup.callback(os.close, master)
|
|
+ cleanup.callback(os.close, slave)
|
|
+ stop = threading.Event()
|
|
+ client: subprocess.Popen[bytes] | None = None
|
|
+
|
|
+ def drain() -> None:
|
|
+ while not stop.is_set():
|
|
+ if select.select([master], [], [], 0.1)[0]:
|
|
+ try:
|
|
+ if not os.read(master, 65536):
|
|
+ return
|
|
+ except OSError:
|
|
+ return
|
|
+
|
|
+ try:
|
|
+ fcntl.ioctl(slave, termios.TIOCSWINSZ, struct.pack("HHHH", 30, 100, 0, 0))
|
|
+ client = subprocess.Popen(
|
|
+ [
|
|
+ server.tmux_bin or "tmux",
|
|
+ "-S",
|
|
+ socket_path,
|
|
+ "attach-session",
|
|
+ "-t",
|
|
+ str(session.session_id),
|
|
+ ],
|
|
+ stdin=slave,
|
|
+ stdout=slave,
|
|
+ stderr=slave,
|
|
+ env={**os.environ, "TERM": "xterm-256color"},
|
|
+ start_new_session=True,
|
|
+ )
|
|
+ reader = threading.Thread(target=drain, daemon=True)
|
|
+ reader.start()
|
|
+ cleanup.callback(reader.join, timeout=1)
|
|
+ cleanup.callback(stop.set)
|
|
+ client_name = os.ttyname(slave)
|
|
+
|
|
+ def attached() -> bool:
|
|
+ clients = server.cmd("list-clients", "-F", "#{client_name}").stdout
|
|
+ return client_name in clients
|
|
+
|
|
+ retry_until(attached, 3, raises=True)
|
|
+ yield client_name
|
|
+ finally:
|
|
+ if client is not None:
|
|
+ client.terminate()
|
|
+ try:
|
|
+ client.wait(timeout=3)
|
|
+ except subprocess.TimeoutExpired:
|
|
+ client.kill()
|
|
+ client.wait(timeout=3)
|
|
+
|
|
+
|
|
@pytest.mark.parametrize(
|
|
list(DisplayPopupCase._fields),
|
|
DISPLAY_POPUP_CASES,
|
|
@@ -1091,7 +1158,7 @@ def test_display_popup_flags(
|
|
test_id: str,
|
|
kwargs: dict[str, t.Any],
|
|
min_tmux_version: str | None,
|
|
- control_mode: t.Callable[..., t.Any],
|
|
+ terminal_client: str,
|
|
session: Session,
|
|
tmp_path: pathlib.Path,
|
|
) -> None:
|
|
@@ -1110,14 +1177,13 @@ def test_display_popup_flags(
|
|
|
|
call_kwargs = {"command": f"touch {marker}", "close_on_exit": True, **kwargs}
|
|
|
|
- with control_mode():
|
|
- pane.display_popup(**call_kwargs)
|
|
+ pane.display_popup(**call_kwargs)
|
|
|
|
retry_until(lambda: marker.exists(), 3, raises=True)
|
|
|
|
|
|
def test_display_popup_close_on_success(
|
|
- control_mode: t.Callable[..., t.Any],
|
|
+ terminal_client: str,
|
|
session: Session,
|
|
tmp_path: pathlib.Path,
|
|
) -> None:
|
|
@@ -1126,8 +1192,7 @@ def test_display_popup_close_on_success(
|
|
pane = session.active_window.active_pane
|
|
assert pane is not None
|
|
|
|
- with control_mode():
|
|
- pane.display_popup(command=f"touch {marker}", close_on_success=True)
|
|
+ pane.display_popup(command=f"touch {marker}", close_on_success=True)
|
|
|
|
retry_until(lambda: marker.exists(), 3, raises=True)
|
|
|
|
@@ -1157,26 +1222,20 @@ def test_display_popup_close_existing(
|
|
|
|
|
|
def test_display_popup_target_client(
|
|
- control_mode: t.Callable[..., t.Any],
|
|
+ terminal_client: str,
|
|
session: Session,
|
|
tmp_path: pathlib.Path,
|
|
) -> None:
|
|
- """Test Pane.display_popup(target_client=...) emits ``-c <client>``.
|
|
-
|
|
- ``-c`` has been on ``display-popup`` since tmux 3.2a, so no version
|
|
- guard is needed. The popup itself is invisible without a TTY-backed
|
|
- client; this is a smoke test for the flag-passing path.
|
|
- """
|
|
+ """Run the popup command on the specified terminal client."""
|
|
pane = session.active_window.active_pane
|
|
assert pane is not None
|
|
marker = tmp_path / "popup_target_client.marker"
|
|
|
|
- with control_mode() as ctl:
|
|
- pane.display_popup(
|
|
- command=f"touch {marker}",
|
|
- close_on_exit=True,
|
|
- target_client=ctl.client_name,
|
|
- )
|
|
+ pane.display_popup(
|
|
+ command=f"touch {marker}",
|
|
+ close_on_exit=True,
|
|
+ target_client=terminal_client,
|
|
+ )
|
|
|
|
retry_until(lambda: marker.exists(), 3, raises=True)
|
|
|