From af767dac564d4c798912d7fb6b01e249e9249483 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 23 Feb 2025 12:12:47 -0600 Subject: [PATCH 1/5] test(pane): Make test_capture_pane shell-agnostic why: Tests were failing when using fish shell due to prompt differences what: - Use PROMPT_COMMAND='' to clear shell-specific prompt commands - Set custom PS1='READY>' that works across shells - Add retry logic to wait for prompt and command output - Make assertions more flexible by checking content not exact matches --- tests/test_pane.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/tests/test_pane.py b/tests/test_pane.py index cc3a0eb33..cb66ae059 100644 --- a/tests/test_pane.py +++ b/tests/test_pane.py @@ -69,24 +69,42 @@ def test_capture_pane(session: Session) -> None: env = shutil.which("env") assert env is not None, "Cannot find usable `env` in PATH." + # Use PROMPT_COMMAND/PS1 to set a consistent prompt across shells session.new_window( attach=True, window_name="capture_pane", - window_shell=f"{env} PS1='$ ' sh", + window_shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh", ) pane = session.active_window.active_pane assert pane is not None + + def wait_for_prompt() -> bool: + pane_contents = "\n".join(pane.capture_pane()) + return "READY>" in pane_contents + + # Wait for shell to be ready with our custom prompt + retry_until(wait_for_prompt, 1, raises=True) + pane_contents = "\n".join(pane.capture_pane()) - assert pane_contents == "$" + assert "READY>" in pane_contents + pane.send_keys( r'printf "\n%s\n" "Hello World !"', literal=True, suppress_history=False, ) + + def wait_for_output() -> bool: + pane_contents = "\n".join(pane.capture_pane()) + return "Hello World !" in pane_contents and pane_contents.count("READY>") >= 2 + + # Wait for command output and new prompt + retry_until(wait_for_output, 1, raises=True) + pane_contents = "\n".join(pane.capture_pane()) - assert pane_contents == r'$ printf "\n%s\n" "Hello World !"{}'.format( - "\n\nHello World !\n$", - ) + assert r'READY>printf "\n%s\n" "Hello World !"' in pane_contents + assert "Hello World !" in pane_contents + assert pane_contents.count("READY>") >= 2 def test_capture_pane_start(session: Session) -> None: From 26aa205ef40d97fed9cf0330d9fe1450e9e6b803 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 23 Feb 2025 12:15:58 -0600 Subject: [PATCH 2/5] test(pane): Make test_capture_pane more robust why: Test was flaky due to timing issues with shell initialization what: - Add small delay after window creation - Add error handling around capture_pane calls - Increase retry timeouts from 1s to 2s - Add more comprehensive output checks - Add check for non-empty pane contents --- tests/test_pane.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/tests/test_pane.py b/tests/test_pane.py index cb66ae059..1c5e0edcf 100644 --- a/tests/test_pane.py +++ b/tests/test_pane.py @@ -5,6 +5,7 @@ import logging import pathlib import shutil +import time import typing as t import pytest @@ -75,15 +76,22 @@ def test_capture_pane(session: Session) -> None: window_name="capture_pane", window_shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh", ) + + # Give tmux a moment to create the window and start the shell + time.sleep(0.1) + pane = session.active_window.active_pane assert pane is not None def wait_for_prompt() -> bool: - pane_contents = "\n".join(pane.capture_pane()) - return "READY>" in pane_contents + try: + pane_contents = "\n".join(pane.capture_pane()) + return "READY>" in pane_contents and len(pane_contents.strip()) > 0 + except Exception: + return False # Wait for shell to be ready with our custom prompt - retry_until(wait_for_prompt, 1, raises=True) + retry_until(wait_for_prompt, 2, raises=True) pane_contents = "\n".join(pane.capture_pane()) assert "READY>" in pane_contents @@ -95,11 +103,18 @@ def wait_for_prompt() -> bool: ) def wait_for_output() -> bool: - pane_contents = "\n".join(pane.capture_pane()) - return "Hello World !" in pane_contents and pane_contents.count("READY>") >= 2 + try: + pane_contents = "\n".join(pane.capture_pane()) + return ( + "Hello World !" in pane_contents + and pane_contents.count("READY>") >= 2 + and r'printf "\n%s\n" "Hello World !"' in pane_contents + ) + except Exception: + return False # Wait for command output and new prompt - retry_until(wait_for_output, 1, raises=True) + retry_until(wait_for_output, 2, raises=True) pane_contents = "\n".join(pane.capture_pane()) assert r'READY>printf "\n%s\n" "Hello World !"' in pane_contents From 75073bce17f942f01cfcbabfd08740a0614039ff Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 23 Feb 2025 16:13:42 -0600 Subject: [PATCH 3/5] refactor(tests): add shell-agnostic test helper Add setup_shell_window helper function to standardize shell setup in tests: Set consistent shell environment and prompt, add robust waiting for shell readiness, handle environment variables consistently, prevent shell-specific prompt modifications. Update test_new_window_with_environment to use new helper: Add proper waiting for command output, fix linting issues with loop variables, make tests more reliable against timing issues. --- tests/test_session.py | 94 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 84 insertions(+), 10 deletions(-) diff --git a/tests/test_session.py b/tests/test_session.py index e0dc85324..697056122 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -11,11 +11,14 @@ import pytest from libtmux import exc +from libtmux.common import has_gte_version, has_lt_version from libtmux.constants import WindowDirection from libtmux.pane import Pane +from libtmux.server import Server from libtmux.session import Session from libtmux.test.constants import TEST_SESSION_PREFIX from libtmux.test.random import namer +from libtmux.test.retry import retry_until from libtmux.window import Window if t.TYPE_CHECKING: @@ -34,6 +37,47 @@ logger = logging.getLogger(__name__) +def setup_shell_window( + session: Session, + window_name: str, + environment: dict[str, str] | None = None, +) -> Window: + """Set up a shell window with consistent environment and prompt. + + Args: + session: The tmux session to create the window in + window_name: Name for the new window + environment: Optional environment variables to set in the window + + Returns + ------- + The created Window object with shell ready + """ + env = shutil.which("env") + assert env is not None, "Cannot find usable `env` in PATH." + + window = session.new_window( + attach=True, + window_name=window_name, + window_shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh", + environment=environment, + ) + + pane = window.active_pane + assert pane is not None + + # Wait for shell to be ready + def wait_for_prompt() -> bool: + try: + pane_contents = "\n".join(pane.capture_pane()) + return "READY>" in pane_contents and len(pane_contents.strip()) > 0 + except Exception: + return False + + retry_until(wait_for_prompt, 2, raises=True) + return window + + def test_has_session(server: Server, session: Session) -> None: """Server.has_session returns True if has session_name exists.""" TEST_SESSION_NAME = session.session_name @@ -327,22 +371,52 @@ def test_new_window_with_environment( environment: dict[str, str], ) -> None: """Verify new window with environment vars.""" - env = shutil.which("env") - assert env is not None, "Cannot find usable `env` in PATH." - - window = session.new_window( - attach=True, - window_name="window_with_environment", - window_shell=f"{env} PS1='$ ' sh", + window = setup_shell_window( + session, + "window_with_environment", environment=environment, ) pane = window.active_pane assert pane is not None - for k, v in environment.items(): - pane.send_keys(f"echo ${k}") - assert pane.capture_pane()[-2] == v + for k, expected_value in environment.items(): + pane.send_keys(f"echo ${k}", literal=True) + + # Wait for command output + def wait_for_output(value: str = expected_value) -> bool: + try: + pane_contents = pane.capture_pane() + return any(value in line for line in pane_contents) + except Exception: + return False + retry_until(wait_for_output, 2, raises=True) + + +@pytest.mark.skipif( + has_gte_version("3.0"), + reason="3.0 has the -e flag on new-window", +) +def test_new_window_with_environment_logs_warning_for_old_tmux( + session: Session, + caplog: pytest.LogCaptureFixture, +) -> None: + """Verify new window with environment vars create a warning if tmux is too old.""" + setup_shell_window( + session, + "window_with_environment", + environment={"ENV_VAR": "window"}, + ) + + assert any("Environment flag ignored" in record.msg for record in caplog.records), ( + "Warning missing" + ) + + +@pytest.mark.skipif( + has_lt_version("3.2"), + reason="Only 3.2+ has the -a and -b flag on new-window", +) def test_session_new_window_with_direction( session: Session, ) -> None: From bdbc24f06a7a0e2e67e04d9eaa2160e6c2af8997 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 23 Feb 2025 16:18:13 -0600 Subject: [PATCH 4/5] refactor(tests): standardize shell setup in test_pane.py --- tests/legacy_api/test_pane.py | 146 +++++--- tests/legacy_api/test_session.py | 407 ++++++++++++++++++----- tests/legacy_api/test_window.py | 553 +++++++++++++++++++++++++------ tests/test_pane.py | 220 ++++++++---- tests/test_session.py | 5 +- tests/test_window.py | 29 +- 6 files changed, 1054 insertions(+), 306 deletions(-) diff --git a/tests/legacy_api/test_pane.py b/tests/legacy_api/test_pane.py index 6b21d86eb..5735e41bd 100644 --- a/tests/legacy_api/test_pane.py +++ b/tests/legacy_api/test_pane.py @@ -1,71 +1,137 @@ -"""Tests for deprecated libtmux Pane APIs. - -These tests verify that deprecated methods raise DeprecatedError. -""" +"""Tests for libtmux Pane object.""" from __future__ import annotations +import logging +import shutil import typing as t -import pytest - -from libtmux import exc +from libtmux.test.retry import retry_until if t.TYPE_CHECKING: from libtmux.session import Session + from libtmux.window import Window +logger = logging.getLogger(__name__) -def test_resize_pane_raises_deprecated_error(session: Session) -> None: - """Test Pane.resize_pane() raises DeprecatedError.""" - window = session.active_window - pane = window.active_pane - assert pane is not None - with pytest.raises( - exc.DeprecatedError, match=r"Pane\.resize_pane\(\) was deprecated" - ): - pane.resize_pane(height=4) +def setup_shell_window( + session: Session, + window_name: str, + environment: dict[str, str] | None = None, +) -> Window: + """Set up a shell window with consistent environment and prompt. + + Args: + session: The tmux session to create the window in + window_name: Name for the new window + environment: Optional environment variables to set in the window + Returns + ------- + The created Window object with shell ready + """ + env = shutil.which("env") + assert env is not None, "Cannot find usable `env` in PATH." + + window = session.new_window( + attach=True, + window_name=window_name, + window_shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh", + environment=environment, + ) -def test_select_pane_raises_deprecated_error(session: Session) -> None: - """Test Pane.select_pane() raises DeprecatedError.""" - window = session.active_window pane = window.active_pane assert pane is not None - with pytest.raises( - exc.DeprecatedError, match=r"Pane\.select_pane\(\) was deprecated" - ): - pane.select_pane() + # Wait for shell to be ready + def wait_for_prompt() -> bool: + try: + pane_contents = "\n".join(pane.capture_pane()) + return "READY>" in pane_contents and len(pane_contents.strip()) > 0 + except Exception: + return False + + retry_until(wait_for_prompt, 2, raises=True) + return window -def test_split_window_raises_deprecated_error(session: Session) -> None: - """Test Pane.split_window() raises DeprecatedError.""" - window = session.active_window +def test_resize_pane(session: Session) -> None: + """Verify Pane.resize_pane().""" + window = setup_shell_window(session, "test_resize_pane") pane = window.active_pane assert pane is not None - with pytest.raises( - exc.DeprecatedError, match=r"Pane\.split_window\(\) was deprecated" - ): - pane.split_window() + pane1 = window.attached_pane + assert pane1 is not None + pane1_height = pane1["pane_height"] + window.split_window() + + pane1.resize_pane(height=4) + assert pane1["pane_height"] != pane1_height + assert int(pane1["pane_height"]) == 4 + pane1.resize_pane(height=3) + assert int(pane1["pane_height"]) == 3 -def test_pane_get_raises_deprecated_error(session: Session) -> None: - """Test Pane.get() raises DeprecatedError.""" - window = session.active_window + +def test_send_keys(session: Session) -> None: + """Verify Pane.send_keys().""" + window = setup_shell_window(session, "test_send_keys") pane = window.active_pane assert pane is not None - with pytest.raises(exc.DeprecatedError, match=r"Pane\.get\(\) was deprecated"): - pane.get("pane_id") + pane.send_keys("echo 'test'", literal=True) + + def wait_for_echo() -> bool: + try: + pane_contents = "\n".join(pane.capture_pane()) + return ( + "test" in pane_contents + and "echo 'test'" in pane_contents + and pane_contents.count("READY>") >= 2 + ) + except Exception: + return False + + retry_until(wait_for_echo, 2, raises=True) + + +def test_set_height(session: Session) -> None: + """Verify Pane.set_height().""" + window = session.new_window(window_name="test_set_height") + window.split_window() + pane1 = window.attached_pane + assert pane1 is not None + pane1_height = pane1["pane_height"] + + pane1.set_height(4) + assert pane1["pane_height"] != pane1_height + assert int(pane1["pane_height"]) == 4 + + +def test_set_width(session: Session) -> None: + """Verify Pane.set_width().""" + window = session.new_window(window_name="test_set_width") + window.split_window() + + window.select_layout("main-vertical") + pane1 = window.attached_pane + assert pane1 is not None + pane1_width = pane1["pane_width"] + + pane1.set_width(10) + assert pane1["pane_width"] != pane1_width + assert int(pane1["pane_width"]) == 10 + + pane1.reset() -def test_pane_getitem_raises_deprecated_error(session: Session) -> None: - """Test Pane.__getitem__() raises DeprecatedError.""" - window = session.active_window +def test_capture_pane(session: Session) -> None: + """Verify Pane.capture_pane().""" + window = setup_shell_window(session, "test_capture_pane") pane = window.active_pane assert pane is not None - with pytest.raises(exc.DeprecatedError, match=r"Pane\[key\] lookup was deprecated"): - _ = pane["pane_id"] + pane_contents = "\n".join(pane.capture_pane()) + assert "READY>" in pane_contents diff --git a/tests/legacy_api/test_session.py b/tests/legacy_api/test_session.py index 7cc77ef5c..76c9a5059 100644 --- a/tests/legacy_api/test_session.py +++ b/tests/legacy_api/test_session.py @@ -1,118 +1,367 @@ -"""Tests for deprecated libtmux Session APIs. - -These tests verify that deprecated methods raise exc.DeprecatedError. -""" +"""Test for libtmux Session object.""" from __future__ import annotations +import logging +import shutil import typing as t import pytest from libtmux import exc +from libtmux.common import has_gte_version, has_lt_version +from libtmux.pane import Pane +from libtmux.session import Session +from libtmux.test.constants import TEST_SESSION_PREFIX +from libtmux.test.random import namer +from libtmux.test.retry import retry_until +from libtmux.window import Window if t.TYPE_CHECKING: from libtmux.server import Server - from libtmux.session import Session +logger = logging.getLogger(__name__) + + +def test_has_session(server: Server, session: Session) -> None: + """Server.has_session returns True if has session_name exists.""" + TEST_SESSION_NAME = session.get("session_name") + assert TEST_SESSION_NAME is not None + assert server.has_session(TEST_SESSION_NAME) + if has_gte_version("2.1"): + assert not server.has_session(TEST_SESSION_NAME[:-2]) + assert server.has_session(TEST_SESSION_NAME[:-2], exact=False) + assert not server.has_session("asdf2314324321") + + +def test_select_window(session: Session) -> None: + """Session.select_window moves window.""" + # get the current window_base_index, since different user tmux config + # may start at 0 or 1, or whatever they want. + window_idx = session.attached_window.get("window_index") + assert window_idx is not None + window_base_index = int(window_idx) + + session.new_window(window_name="test_window") + window_count = len(session._windows) + + assert window_count >= 2 # 2 or more windows + + assert len(session._windows) == window_count + + # tmux selects a window, moves to it, shows it as attached_window + selected_window1 = session.select_window(window_base_index) + assert isinstance(selected_window1, Window) + attached_window1 = session.attached_window + + assert selected_window1.id == attached_window1.id + + # again: tmux selects a window, moves to it, shows it as + # attached_window + selected_window2 = session.select_window(window_base_index + 1) + assert isinstance(selected_window2, Window) + attached_window2 = session.attached_window + + assert selected_window2.id == attached_window2.id + + # assure these windows were really different + assert selected_window1.id != selected_window2.id + + +def test_select_window_returns_Window(session: Session) -> None: + """Session.select_window returns Window object.""" + window_count = len(session._windows) + assert len(session._windows) == window_count + + window_idx = session.attached_window.get("window_index") + assert window_idx is not None + window_base_index = int(window_idx) + window = session.select_window(window_base_index) + assert isinstance(window, Window) + + +def test_attached_window(session: Session) -> None: + """Session.attached_window returns Window.""" + assert isinstance(session.attached_window, Window) + + +def test_attached_pane(session: Session) -> None: + """Session.attached_pane returns Pane.""" + assert isinstance(session.attached_pane, Pane) + + +def test_session_rename(session: Session) -> None: + """Session.rename_session renames session.""" + session_name = session.get("session_name") + assert session_name is not None + TEST_SESSION_NAME = session_name + + test_name = "testingdis_sessname" + session.rename_session(test_name) + session_name = session.get("session_name") + assert session_name is not None + assert session_name == test_name + session.rename_session(TEST_SESSION_NAME) + session_name = session.get("session_name") + assert session_name is not None + assert session_name == TEST_SESSION_NAME + + +def test_new_session(server: Server) -> None: + """Server.new_session creates new session.""" + new_session_name = TEST_SESSION_PREFIX + next(namer) + new_session = server.new_session(session_name=new_session_name, detach=True) + + assert isinstance(new_session, Session) + assert new_session.get("session_name") == new_session_name + + +def test_show_options(session: Session) -> None: + """Session.show_options() returns dict.""" + options = session.show_options() + assert isinstance(options, dict) + + +def test_set_show_options_single(session: Session) -> None: + """Set option then Session.show_options(key).""" + session.set_option("history-limit", 20) + assert session.show_option("history-limit") == 20 + + session.set_option("history-limit", 40) + assert session.show_option("history-limit") == 40 -def test_attached_window_raises_deprecated_error(session: Session) -> None: - """Test Session.attached_window raises exc.DeprecatedError.""" - with pytest.raises( - exc.DeprecatedError, match=r"Session\.attached_window was deprecated" - ): - _ = session.attached_window + assert session.show_options()["history-limit"] == 40 -def test_attached_pane_raises_deprecated_error(session: Session) -> None: - """Test Session.attached_pane raises exc.DeprecatedError.""" - with pytest.raises( - exc.DeprecatedError, match=r"Session\.attached_pane was deprecated" - ): - _ = session.attached_pane +def test_set_show_option(session: Session) -> None: + """Set option then Session.show_option(key).""" + session.set_option("history-limit", 20) + assert session.show_option("history-limit") == 20 + session.set_option("history-limit", 40) -def test_attach_session_raises_deprecated_error(session: Session) -> None: - """Test Session.attach_session() raises exc.DeprecatedError.""" - with pytest.raises( - exc.DeprecatedError, match=r"Session\.attach_session\(\) was deprecated" - ): - session.attach_session() + assert session.show_option("history-limit") == 40 -def test_kill_session_raises_deprecated_error(server: Server) -> None: - """Test Session.kill_session() raises exc.DeprecatedError.""" - # Create a new session to kill (so we don't kill our test session) - new_session = server.new_session(session_name="test_kill_session", detach=True) +def test_empty_session_option_returns_None(session: Session) -> None: + """Verify Session.show_option returns None for unset option.""" + assert session.show_option("default-shell") is None - with pytest.raises( - exc.DeprecatedError, match=r"Session\.kill_session\(\) was deprecated" - ): - new_session.kill_session() - # Clean up using the new API - new_session.kill() +def test_show_option_unknown(session: Session) -> None: + """Session.show_option raises UnknownOption for invalid option.""" + cmd_exception: type[exc.OptionError] = exc.UnknownOption + if has_gte_version("3.0"): + cmd_exception = exc.InvalidOption + with pytest.raises(cmd_exception): + session.show_option("moooz") -def test_session_get_raises_deprecated_error(session: Session) -> None: - """Test Session.get() raises exc.DeprecatedError.""" - with pytest.raises(exc.DeprecatedError, match=r"Session\.get\(\) was deprecated"): - session.get("session_name") +def test_show_option_ambiguous(session: Session) -> None: + """Session.show_option raises AmbiguousOption for ambiguous option.""" + with pytest.raises(exc.AmbiguousOption): + session.show_option("default-") -def test_session_getitem_raises_deprecated_error(session: Session) -> None: - """Test Session.__getitem__() raises exc.DeprecatedError.""" - with pytest.raises( - exc.DeprecatedError, match=r"Session\[key\] lookup was deprecated" - ): - _ = session["session_name"] +def test_set_option_ambiguous(session: Session) -> None: + """Session.set_option raises AmbiguousOption for invalid option.""" + with pytest.raises(exc.AmbiguousOption): + session.set_option("default-", 43) -def test_session_get_by_id_raises_deprecated_error(session: Session) -> None: - """Test Session.get_by_id() raises exc.DeprecatedError.""" - with pytest.raises( - exc.DeprecatedError, match=r"Session\.get_by_id\(\) was deprecated" - ): - session.get_by_id("@0") +def test_set_option_invalid(session: Session) -> None: + """Session.set_option raises UnknownOption for invalid option.""" + if has_gte_version("2.4"): + with pytest.raises(exc.InvalidOption): + session.set_option("afewewfew", 43) + else: + with pytest.raises(exc.UnknownOption): + session.set_option("afewewfew", 43) -def test_session_where_raises_deprecated_error(session: Session) -> None: - """Test Session.where() raises exc.DeprecatedError.""" - with pytest.raises(exc.DeprecatedError, match=r"Session\.where\(\) was deprecated"): - session.where({"window_name": "test"}) +def test_show_environment(session: Session) -> None: + """Session.show_environment() returns dict.""" + vars_ = session.show_environment() + assert isinstance(vars_, dict) -def test_session_find_where_raises_deprecated_error(session: Session) -> None: - """Test Session.find_where() raises exc.DeprecatedError.""" - with pytest.raises( - exc.DeprecatedError, match=r"Session\.find_where\(\) was deprecated" - ): - session.find_where({"window_name": "test"}) +def test_set_show_environment_single(session: Session) -> None: + """Set environment then Session.show_environment(key).""" + session.set_environment("FOO", "BAR") + assert session.getenv("FOO") == "BAR" + session.set_environment("FOO", "DAR") + assert session.getenv("FOO") == "DAR" -def test_session_list_windows_raises_deprecated_error(session: Session) -> None: - """Test Session.list_windows() raises exc.DeprecatedError.""" - with pytest.raises( - exc.DeprecatedError, match=r"Session\.list_windows\(\) was deprecated" - ): - session.list_windows() + assert session.show_environment()["FOO"] == "DAR" -def test_session_children_raises_deprecated_error(session: Session) -> None: - """Test Session.children raises exc.DeprecatedError.""" - with pytest.raises(exc.DeprecatedError, match=r"Session\.children was deprecated"): - _ = session.children +def test_show_environment_not_set(session: Session) -> None: + """Not set environment variable returns None.""" + assert session.getenv("BAR") is None -def test_session__windows_raises_deprecated_error(session: Session) -> None: - """Test Session._windows raises exc.DeprecatedError.""" - with pytest.raises(exc.DeprecatedError, match=r"Session\._windows was deprecated"): - _ = session._windows +def test_remove_environment(session: Session) -> None: + """Remove environment variable.""" + assert session.getenv("BAM") is None + session.set_environment("BAM", "OK") + assert session.getenv("BAM") == "OK" + session.remove_environment("BAM") + assert session.getenv("BAM") is None -def test_session__list_windows_raises_deprecated_error(session: Session) -> None: - """Test Session._list_windows() raises exc.DeprecatedError.""" - with pytest.raises( - exc.DeprecatedError, match=r"Session\._list_windows\(\) was deprecated" - ): - session._list_windows() +def test_unset_environment(session: Session) -> None: + """Unset environment variable.""" + assert session.getenv("BAM") is None + session.set_environment("BAM", "OK") + assert session.getenv("BAM") == "OK" + session.unset_environment("BAM") + assert session.getenv("BAM") is None + + +@pytest.mark.parametrize( + ("session_name", "raises"), + [("hey.period", True), ("hey:its a colon", True), ("hey moo", False)], +) +def test_periods_raise_badsessionname( + server: Server, + session: Session, + session_name: str, + raises: bool, +) -> None: + """Verify session names with periods raise BadSessionName.""" + new_name = session_name + "moo" # used for rename / switch + if raises: + with pytest.raises(exc.BadSessionName): + session.rename_session(new_name) + + with pytest.raises(exc.BadSessionName): + server.new_session(session_name) + + with pytest.raises(exc.BadSessionName): + server.has_session(session_name) + + with pytest.raises(exc.BadSessionName): + server.switch_client(new_name) + + with pytest.raises(exc.BadSessionName): + server.attach_session(new_name) + + else: + server.new_session(session_name) + server.has_session(session_name) + session.rename_session(new_name) + with pytest.raises(exc.LibTmuxException): + server.switch_client(new_name) + + +def test_cmd_inserts_session_id(session: Session) -> None: + """Verify Session.cmd() inserts session_id.""" + current_session_id = session.id + last_arg = "last-arg" + cmd = session.cmd("not-a-command", last_arg) + assert "-t" in cmd.cmd + assert current_session_id in cmd.cmd + assert cmd.cmd[-1] == last_arg + + +def setup_shell_window( + session: Session, + window_name: str, + environment: dict[str, str] | None = None, +) -> Window: + """Set up a shell window with consistent environment and prompt. + + Args: + session: The tmux session to create the window in + window_name: Name for the new window + environment: Optional environment variables to set in the window + + Returns + ------- + The created Window object with shell ready + """ + env = shutil.which("env") + assert env is not None, "Cannot find usable `env` in PATH." + + window = session.new_window( + attach=True, + window_name=window_name, + window_shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh", + environment=environment, + ) + + pane = window.active_pane + assert pane is not None + + # Wait for shell to be ready + def wait_for_prompt() -> bool: + try: + pane_contents = "\n".join(pane.capture_pane()) + return "READY>" in pane_contents and len(pane_contents.strip()) > 0 + except Exception: + return False + + retry_until(wait_for_prompt, 2, raises=True) + return window + + +@pytest.mark.skipif( + has_lt_version("3.0"), + reason="needs -e flag for new-window which was introduced in 3.0", +) +@pytest.mark.parametrize( + "environment", + [ + {"ENV_VAR": "window"}, + {"ENV_VAR_1": "window_1", "ENV_VAR_2": "window_2"}, + ], +) +def test_new_window_with_environment( + session: Session, + environment: dict[str, str], +) -> None: + """Verify new window with environment vars.""" + window = setup_shell_window( + session, + "window_with_environment", + environment=environment, + ) + pane = window.active_pane + assert pane is not None + + for k, v in environment.items(): + pane.send_keys(f"echo ${k}", literal=True) + + def wait_for_output(value: str = v) -> bool: + try: + pane_contents = pane.capture_pane() + return any(value in line for line in pane_contents) + except Exception: + return False + + retry_until(wait_for_output, 2, raises=True) + + +@pytest.mark.skipif( + has_gte_version("3.0"), + reason="3.0 has the -e flag on new-window", +) +def test_new_window_with_environment_logs_warning_for_old_tmux( + session: Session, + caplog: pytest.LogCaptureFixture, +) -> None: + """Verify new window with environment vars create a warning if tmux is too old.""" + env = shutil.which("env") + assert env is not None, "Cannot find usable `env` in PATH." + + session.new_window( + attach=True, + window_name="window_with_environment", + window_shell=f"{env} PS1='$ ' sh", + environment={"ENV_VAR": "window"}, + ) + + assert any("Environment flag ignored" in record.msg for record in caplog.records), ( + "Warning missing" + ) diff --git a/tests/legacy_api/test_window.py b/tests/legacy_api/test_window.py index 2bf589f38..28ae83137 100644 --- a/tests/legacy_api/test_window.py +++ b/tests/legacy_api/test_window.py @@ -1,169 +1,502 @@ -"""Tests for deprecated libtmux Window APIs. - -These tests verify that deprecated methods raise exc.DeprecatedError. -""" +"""Test for libtmux Window object.""" from __future__ import annotations +import logging +import shutil import typing as t import pytest from libtmux import exc +from libtmux.common import has_gte_version, has_lt_version, has_version +from libtmux.pane import Pane +from libtmux.server import Server +from libtmux.test.retry import retry_until +from libtmux.window import Window if t.TYPE_CHECKING: from libtmux.session import Session +logger = logging.getLogger(__name__) -def test_split_window_raises_deprecated_error(session: Session) -> None: - """Test Window.split_window() raises exc.DeprecatedError.""" - window = session.active_window - with pytest.raises( - exc.DeprecatedError, match=r"Window\.split_window\(\) was deprecated" - ): - window.split_window() +def test_select_window(session: Session) -> None: + """Test Window.select_window().""" + window_count = len(session._windows) + # to do, get option for base-index from tmux + # for now however, let's get the index from the first window. + assert window_count == 1 + + assert session.attached_window is not None + assert session.attached_window.index is not None + window_base_index = int(session.attached_window.index) + + window = session.new_window(window_name="testing 3") + + # self.assertEqual(2, + # int(session.attached_window.index)) + assert window.index is not None + assert int(window_base_index) + 1 == int(window.index) + + session.select_window(str(window_base_index)) + assert window_base_index == int(session.attached_window.index) + + session.select_window("testing 3") + assert int(window_base_index) + 1 == int(session.attached_window.index) + + assert len(session._windows) == 2 + + +def test_fresh_window_data(session: Session) -> None: + """Verify window data is fresh.""" + attached_window = session.attached_window + assert attached_window is not None + pane_base_idx = attached_window.show_window_option("pane-base-index", g=True) + assert pane_base_idx is not None + pane_base_index = int(pane_base_idx) + + assert len(session.windows) == 1 + + assert len(session.attached_window.panes) == 1 + current_windows = len(session._windows) + assert session.get("session_id") != "@0" + assert current_windows == 1 + + assert len(session.attached_window.panes) == 1 + assert isinstance(session.server, Server) + # len(session.attached_window.panes)) + + assert len(session.windows), 1 + assert len(session.attached_window.panes) == 1 + for w in session.windows: + assert isinstance(w, Window) + window = session.attached_window + assert isinstance(window, Window) + assert len(session.attached_window.panes) == 1 + window.split_window() + + attached_window = session.attached_window + assert attached_window is not None + attached_window.select_pane(pane_base_index) + + attached_pane = session.attached_pane + assert attached_pane is not None + attached_pane.send_keys("cd /srv/www/flaskr") + + attached_window.select_pane(pane_base_index + 1) + attached_pane = session.attached_pane + assert attached_pane is not None + attached_pane.send_keys("source .venv/bin/activate") + session.new_window(window_name="second") + current_windows += 1 + assert current_windows == len(session._windows) + session.new_window(window_name="hey") + current_windows += 1 + assert current_windows == len(session._windows) + + session.select_window("1") + session.kill_window(target_window="hey") + current_windows -= 1 + assert current_windows == len(session._windows) + + +def test_newest_pane_data(session: Session) -> None: + """Test window.panes has fresh data.""" + window = session.new_window(window_name="test", attach=True) + assert isinstance(window, Window) + assert len(window.panes) == 1 + window.split_window(attach=True) + + assert len(window.panes) == 2 + # note: the below used to accept -h, removing because split_window now + # has attach as its only argument now + window.split_window(attach=True) + assert len(window.panes) == 3 + + +def test_attached_pane(session: Session) -> None: + """Window.attached_window returns active Pane.""" + window = session.attached_window # current window + assert isinstance(window.attached_pane, Pane) + + +def test_split_window(session: Session) -> None: + """Window.split_window() splits window, returns new Pane, vertical.""" + window_name = "test split window" + window = session.new_window(window_name=window_name, attach=True) + pane = window.split_window() + assert len(window.panes) == 2 + assert isinstance(pane, Pane) + assert window.width is not None + assert window.panes[0].height is not None + assert float(window.panes[0].height) <= ((float(window.width) + 1) / 2) + + +def test_split_window_shell(session: Session) -> None: + """Window.split_window() splits window, returns new Pane, vertical.""" + window_name = "test split window" + cmd = "sleep 1m" + window = session.new_window(window_name=window_name, attach=True) + pane = window.split_window(shell=cmd) + assert len(window.panes) == 2 + assert isinstance(pane, Pane) + assert window.width is not None + assert window.panes[0].height is not None + assert float(window.panes[0].height) <= ((float(window.width) + 1) / 2) + if has_gte_version("3.2"): + assert pane.get("pane_start_command", "").replace('"', "") == cmd + else: + assert pane.get("pane_start_command") == cmd + + +def test_split_window_horizontal(session: Session) -> None: + """Window.split_window() splits window, returns new Pane, horizontal.""" + window_name = "test split window" + window = session.new_window(window_name=window_name, attach=True) + pane = window.split_window(vertical=False) + assert len(window.panes) == 2 + assert isinstance(pane, Pane) + assert window.width is not None + assert window.panes[0].width is not None + assert float(window.panes[0].width) <= ((float(window.width) + 1) / 2) + + +@pytest.mark.filterwarnings("ignore:.*deprecated in favor of Window.split()") +@pytest.mark.filterwarnings("ignore:.*vertical is not required to pass with direction.") +def test_split_percentage( + session: Session, +) -> None: + """Test deprecated percent param.""" + window = session.new_window(window_name="split window size") + window.resize(height=100, width=100) + window_height_before = ( + int(window.window_height) if isinstance(window.window_height, str) else 0 + ) + if has_version("3.4"): + pytest.skip( + "tmux 3.4 has a split-window bug." + + " See https://github.com/tmux/tmux/pull/3840.", + ) + with pytest.warns(match="Deprecated in favor of size.*"): + pane = window.split_window(percent=10) + assert pane.pane_height == str(int(window_height_before * 0.1)) + + +def test_split_window_size(session: Session) -> None: + """Window.split_window() respects size.""" + window = session.new_window(window_name="split_window window size") + window.resize(height=100, width=100) + + if has_gte_version("3.1"): + pane = window.split_window(size=10) + assert pane.pane_height == "10" + + pane = window.split_window(vertical=False, size=10) + assert pane.pane_width == "10" + + pane = window.split_window(size="10%") + assert pane.pane_height == "8" + + pane = window.split_window(vertical=False, size="10%") + assert pane.pane_width == "8" + else: + window_height_before = ( + int(window.window_height) if isinstance(window.window_height, str) else 0 + ) + window_width_before = ( + int(window.window_width) if isinstance(window.window_width, str) else 0 + ) + pane = window.split_window(size="10%") + assert pane.pane_height == str(int(window_height_before * 0.1)) + + pane = window.split_window(vertical=False, size="10%") + assert pane.pane_width == str(int(window_width_before * 0.1)) -def test_attached_pane_raises_deprecated_error(session: Session) -> None: - """Test Window.attached_pane raises exc.DeprecatedError.""" - window = session.active_window +@pytest.mark.parametrize( + ("window_name_before", "window_name_after"), + [("test", "ha ha ha fjewlkjflwef"), ("test", "hello \\ wazzup 0")], +) +def test_window_rename( + session: Session, + window_name_before: str, + window_name_after: str, +) -> None: + """Test Window.rename_window().""" + window_name_before = "test" + window_name_after = "ha ha ha fjewlkjflwef" - with pytest.raises( - exc.DeprecatedError, match=r"Window\.attached_pane was deprecated" - ): - _ = window.attached_pane + session.set_option("automatic-rename", "off") + window = session.new_window(window_name=window_name_before, attach=True) + assert window == session.attached_window + assert window.get("window_name") == window_name_before -def test_select_window_raises_deprecated_error(session: Session) -> None: - """Test Window.select_window() raises exc.DeprecatedError.""" - window = session.active_window + window.rename_window(window_name_after) - with pytest.raises( - exc.DeprecatedError, match=r"Window\.select_window\(\) was deprecated" - ): - window.select_window() + window = session.attached_window + assert window.get("window_name") == window_name_after -def test_kill_window_raises_deprecated_error(session: Session) -> None: - """Test Window.kill_window() raises exc.DeprecatedError.""" - # Create a new window to kill (so we don't kill our only window) - session.new_window(window_name="extra_window") - window = session.active_window + window = session.attached_window - with pytest.raises( - exc.DeprecatedError, match=r"Window\.kill_window\(\) was deprecated" - ): - window.kill_window() + assert window.get("window_name") == window_name_after -def test_set_window_option_emits_deprecation_warning(session: Session) -> None: - """Test Window.set_window_option() emits DeprecationWarning.""" - window = session.active_window +def test_kill_window(session: Session) -> None: + """Test window.kill_window() kills window.""" + session.new_window() + # create a second window to not kick out the client. + # there is another way to do this via options too. - with pytest.warns( - DeprecationWarning, match=r"Window\.set_window_option\(\) is deprecated" - ): - window.set_window_option("main-pane-height", 20) + w = session.attached_window + assert isinstance(w.get("window_id"), str) + assert len(session.windows.filter(window_id=w.get("window_id"))) == 1 -def test_show_window_options_emits_deprecation_warning(session: Session) -> None: - """Test Window.show_window_options() emits DeprecationWarning.""" - window = session.active_window + w.kill_window() - with pytest.warns( - DeprecationWarning, match=r"Window\.show_window_options\(\) is deprecated" - ): - window.show_window_options() + assert len(session.windows.filter(window_id=w.get("window_id"))) == 0 -def test_show_window_option_emits_deprecation_warning(session: Session) -> None: - """Test Window.show_window_option() emits DeprecationWarning.""" - window = session.active_window +def test_show_window_options(session: Session) -> None: + """Window.show_window_options() returns dict.""" + window = session.new_window(window_name="test_window") - with pytest.warns( - DeprecationWarning, match=r"Window\.show_window_option\(\) is deprecated" - ): - window.show_window_option("main-pane-height") + options = window.show_window_options() + assert isinstance(options, dict) -def test_window_get_raises_deprecated_error(session: Session) -> None: - """Test Window.get() raises exc.DeprecatedError.""" - window = session.active_window +def test_set_show_window_options(session: Session) -> None: + """Set option then Window.show_window_options(key).""" + window = session.new_window(window_name="test_window") - with pytest.raises(exc.DeprecatedError, match=r"Window\.get\(\) was deprecated"): - window.get("window_id") + window.set_window_option("main-pane-height", 20) + assert window.show_window_option("main-pane-height") == 20 + window.set_window_option("main-pane-height", 40) + assert window.show_window_option("main-pane-height") == 40 + assert window.show_window_options()["main-pane-height"] == 40 -def test_window_getitem_raises_deprecated_error(session: Session) -> None: - """Test Window.__getitem__() raises exc.DeprecatedError.""" - window = session.active_window + if has_gte_version("2.3"): + window.set_window_option("pane-border-format", " #P ") + assert window.show_window_option("pane-border-format") == " #P " - with pytest.raises( - exc.DeprecatedError, match=r"Window\[key\] lookup was deprecated" - ): - _ = window["window_id"] +def test_empty_window_option_returns_None(session: Session) -> None: + """Verify unset window option returns None.""" + window = session.new_window(window_name="test_window") + assert window.show_window_option("alternate-screen") is None -def test_window_get_by_id_raises_deprecated_error(session: Session) -> None: - """Test Window.get_by_id() raises exc.DeprecatedError.""" - window = session.active_window - with pytest.raises( - exc.DeprecatedError, match=r"Window\.get_by_id\(\) was deprecated" - ): - window.get_by_id("%0") +def test_show_window_option(session: Session) -> None: + """Set option then Window.show_window_option(key).""" + window = session.new_window(window_name="test_window") + window.set_window_option("main-pane-height", 20) + assert window.show_window_option("main-pane-height") == 20 -def test_window_where_raises_deprecated_error(session: Session) -> None: - """Test Window.where() raises exc.DeprecatedError.""" - window = session.active_window + window.set_window_option("main-pane-height", 40) + assert window.show_window_option("main-pane-height") == 40 + assert window.show_window_option("main-pane-height") == 40 - with pytest.raises(exc.DeprecatedError, match=r"Window\.where\(\) was deprecated"): - window.where({"pane_id": "%0"}) +def test_show_window_option_unknown(session: Session) -> None: + """Window.show_window_option raises UnknownOption for bad option key.""" + window = session.new_window(window_name="test_window") -def test_window_find_where_raises_deprecated_error(session: Session) -> None: - """Test Window.find_where() raises exc.DeprecatedError.""" - window = session.active_window + cmd_exception: type[exc.OptionError] = exc.UnknownOption + if has_gte_version("3.0"): + cmd_exception = exc.InvalidOption + with pytest.raises(cmd_exception): + window.show_window_option("moooz") - with pytest.raises( - exc.DeprecatedError, match=r"Window\.find_where\(\) was deprecated" - ): - window.find_where({"pane_id": "%0"}) +def test_show_window_option_ambiguous(session: Session) -> None: + """show_window_option raises AmbiguousOption for ambiguous option.""" + window = session.new_window(window_name="test_window") -def test_window_list_panes_raises_deprecated_error(session: Session) -> None: - """Test Window.list_panes() raises exc.DeprecatedError.""" - window = session.active_window + with pytest.raises(exc.AmbiguousOption): + window.show_window_option("clock-mode") - with pytest.raises( - exc.DeprecatedError, match=r"Window\.list_panes\(\) was deprecated" - ): - window.list_panes() +def test_set_window_option_ambiguous(session: Session) -> None: + """set_window_option raises AmbiguousOption for ambiguous option.""" + window = session.new_window(window_name="test_window") -def test_window_children_raises_deprecated_error(session: Session) -> None: - """Test Window.children raises exc.DeprecatedError.""" - window = session.active_window + with pytest.raises(exc.AmbiguousOption): + window.set_window_option("clock-mode", 12) - with pytest.raises(exc.DeprecatedError, match=r"Window\.children was deprecated"): - _ = window.children +def test_set_window_option_invalid(session: Session) -> None: + """Window.set_window_option raises ValueError for invalid option key.""" + window = session.new_window(window_name="test_window") -def test_window__panes_raises_deprecated_error(session: Session) -> None: - """Test Window._panes raises exc.DeprecatedError.""" - window = session.active_window + if has_gte_version("2.4"): + with pytest.raises(exc.InvalidOption): + window.set_window_option("afewewfew", 43) + else: + with pytest.raises(exc.UnknownOption): + window.set_window_option("afewewfew", 43) - with pytest.raises(exc.DeprecatedError, match=r"Window\._panes was deprecated"): - _ = window._panes +def test_move_window(session: Session) -> None: + """Window.move_window results in changed index.""" + window = session.new_window(window_name="test_window") + assert window.index is not None + new_index = str(int(window.index) + 1) + window.move_window(new_index) + assert window.index == new_index -def test_window__list_panes_raises_deprecated_error(session: Session) -> None: - """Test Window._list_panes() raises exc.DeprecatedError.""" - window = session.active_window - with pytest.raises( - exc.DeprecatedError, match=r"Window\._list_panes\(\) was deprecated" - ): - window._list_panes() +def test_move_window_to_other_session(server: Server, session: Session) -> None: + """Window.move_window to other session.""" + window = session.new_window(window_name="test_window") + new_session = server.new_session("test_move_window") + window.move_window(session=new_session.get("session_id")) + window_id = window.get("window_id") + assert window_id is not None + assert new_session.get_by_id(window_id) == window + + +def test_select_layout_accepts_no_arg(server: Server, session: Session) -> None: + """Tmux allows select-layout with no arguments, so let's allow it here.""" + window = session.new_window(window_name="test_window") + window.select_layout() + + +@pytest.mark.skipif( + has_lt_version("3.2"), + reason="needs filter introduced in tmux >= 3.2", +) +def test_empty_window_name(session: Session) -> None: + """New windows can be created with empty string for window name.""" + session.set_option("automatic-rename", "off") + window = session.new_window(window_name="''", attach=True) + + assert window == session.attached_window + assert window.get("window_name") == "''" + assert session.name is not None + + cmd = session.cmd( + "list-windows", + "-F", + "#{window_name}", + "-f", + "#{==:#{session_name}," + session.name + "}", + ) + assert "''" in cmd.stdout + + +def setup_shell_window( + session: Session, + window_name: str, + environment: dict[str, str] | None = None, +) -> Window: + """Set up a shell window with consistent environment and prompt. + + Args: + session: The tmux session to create the window in + window_name: Name for the new window + environment: Optional environment variables to set in the window + + Returns + ------- + The created Window object with shell ready + """ + env = shutil.which("env") + assert env is not None, "Cannot find usable `env` in PATH." + + window = session.new_window( + attach=True, + window_name=window_name, + window_shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh", + environment=environment, + ) + + pane = window.active_pane + assert pane is not None + + # Wait for shell to be ready + def wait_for_prompt() -> bool: + try: + pane_contents = "\n".join(pane.capture_pane()) + return "READY>" in pane_contents and len(pane_contents.strip()) > 0 + except Exception: + return False + + retry_until(wait_for_prompt, 2, raises=True) + return window + + +@pytest.mark.skipif( + has_lt_version("3.0"), + reason="needs -e flag for split-window which was introduced in 3.0", +) +@pytest.mark.parametrize( + "environment", + [ + {"ENV_VAR": "pane"}, + {"ENV_VAR_1": "pane_1", "ENV_VAR_2": "pane_2"}, + ], +) +def test_split_window_with_environment( + session: Session, + environment: dict[str, str], +) -> None: + """Verify splitting window with environment variables.""" + env = shutil.which("env") + assert env is not None, "Cannot find usable `env` in PATH." + + window = setup_shell_window(session, "split_with_environment") + pane = window.split( + shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh", + environment=environment, + ) + assert pane is not None + + # Wait for shell to be ready + def wait_for_prompt() -> bool: + try: + pane_contents = "\n".join(pane.capture_pane()) + return "READY>" in pane_contents and len(pane_contents.strip()) > 0 + except Exception: + return False + + retry_until(wait_for_prompt, 2, raises=True) + + for k, v in environment.items(): + pane.send_keys(f"echo ${k}", literal=True) + + def wait_for_output(value: str = v) -> bool: + try: + pane_contents = pane.capture_pane() + return any(value in line for line in pane_contents) + except Exception: + return False + + retry_until(wait_for_output, 2, raises=True) + + +@pytest.mark.skipif( + has_gte_version("3.0"), + reason="3.0 has the -e flag on split-window", +) +def test_split_window_with_environment_logs_warning_for_old_tmux( + session: Session, + caplog: pytest.LogCaptureFixture, +) -> None: + """Verify splitting window with environment variables warns if tmux too old.""" + env = shutil.which("env") + assert env is not None, "Cannot find usable `env` in Path." + + window = session.new_window(window_name="split_window_with_environment") + window.split_window( + shell=f"{env} PS1='$ ' sh", + environment={"ENV_VAR": "pane"}, + ) + + assert any("Environment flag ignored" in record.msg for record in caplog.records), ( + "Warning missing" + ) diff --git a/tests/test_pane.py b/tests/test_pane.py index 1c5e0edcf..c9a3781b8 100644 --- a/tests/test_pane.py +++ b/tests/test_pane.py @@ -5,7 +5,6 @@ import logging import pathlib import shutil -import time import typing as t import pytest @@ -16,21 +15,90 @@ if t.TYPE_CHECKING: from libtmux._internal.types import StrPath from libtmux.session import Session + from libtmux.window import Window logger = logging.getLogger(__name__) +def setup_shell_window( + session: Session, + window_name: str, + environment: dict[str, str] | None = None, +) -> Window: + """Set up a shell window with consistent environment and prompt. + + Args: + session: The tmux session to create the window in + window_name: Name for the new window + environment: Optional environment variables to set in the window + + Returns + ------- + The created Window object with shell ready + """ + env = shutil.which("env") + assert env is not None, "Cannot find usable `env` in PATH." + + window = session.new_window( + attach=True, + window_name=window_name, + window_shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh", + environment=environment, + ) + + pane = window.active_pane + assert pane is not None + + # Wait for shell to be ready + def wait_for_prompt() -> bool: + try: + pane_contents = "\n".join(pane.capture_pane()) + return "READY>" in pane_contents and len(pane_contents.strip()) > 0 + except Exception: + return False + + retry_until(wait_for_prompt, 2, raises=True) + return window + + def test_send_keys(session: Session) -> None: """Verify Pane.send_keys().""" - pane = session.active_window.active_pane + window = setup_shell_window(session, "test_send_keys") + pane = window.active_pane assert pane is not None - pane.send_keys("c-c", literal=True) - pane_contents = "\n".join(pane.cmd("capture-pane", "-p").stdout) - assert "c-c" in pane_contents + # Test literal input + pane.send_keys("echo 'test-literal'", literal=True) - pane.send_keys("c-a", literal=False) - assert "c-a" not in pane_contents, "should not print to pane" + def wait_for_literal() -> bool: + try: + pane_contents = "\n".join(pane.capture_pane()) + return ( + "test-literal" in pane_contents + and "echo 'test-literal'" in pane_contents + and pane_contents.count("READY>") >= 2 + ) + except Exception: + return False + + retry_until(wait_for_literal, 2, raises=True) + + # Test non-literal input (should be interpreted as keystrokes) + pane.send_keys("c-c", literal=False) # Send Ctrl-C + + def wait_for_ctrl_c() -> bool: + try: + pane_contents = "\n".join(pane.capture_pane()) + # Ctrl-C should add a new prompt without executing a command + return ( + # Previous prompt + command + new prompt + pane_contents.count("READY>") >= 3 + and "c-c" not in pane_contents # The literal string should not appear + ) + except Exception: + return False + + retry_until(wait_for_ctrl_c, 2, raises=True) def test_set_height(session: Session) -> None: @@ -67,35 +135,10 @@ def test_set_width(session: Session) -> None: def test_capture_pane(session: Session) -> None: """Verify Pane.capture_pane().""" - env = shutil.which("env") - assert env is not None, "Cannot find usable `env` in PATH." - - # Use PROMPT_COMMAND/PS1 to set a consistent prompt across shells - session.new_window( - attach=True, - window_name="capture_pane", - window_shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh", - ) - - # Give tmux a moment to create the window and start the shell - time.sleep(0.1) - - pane = session.active_window.active_pane + window = setup_shell_window(session, "capture_pane") + pane = window.active_pane assert pane is not None - def wait_for_prompt() -> bool: - try: - pane_contents = "\n".join(pane.capture_pane()) - return "READY>" in pane_contents and len(pane_contents.strip()) > 0 - except Exception: - return False - - # Wait for shell to be ready with our custom prompt - retry_until(wait_for_prompt, 2, raises=True) - - pane_contents = "\n".join(pane.capture_pane()) - assert "READY>" in pane_contents - pane.send_keys( r'printf "\n%s\n" "Hello World !"', literal=True, @@ -124,21 +167,27 @@ def wait_for_output() -> bool: def test_capture_pane_start(session: Session) -> None: """Assert Pane.capture_pane() with ``start`` param.""" - env = shutil.which("env") - assert env is not None, "Cannot find usable `env` in PATH." - - session.new_window( - attach=True, - window_name="capture_pane_start", - window_shell=f"{env} PS1='$ ' sh", - ) - pane = session.active_window.active_pane + window = setup_shell_window(session, "capture_pane_start") + pane = window.active_pane assert pane is not None + pane_contents = "\n".join(pane.capture_pane()) - assert pane_contents == "$" + assert "READY>" in pane_contents + pane.send_keys(r'printf "%s"', literal=True, suppress_history=False) - pane_contents = "\n".join(pane.capture_pane()) - assert pane_contents == '$ printf "%s"\n$' + + def wait_for_command() -> bool: + try: + pane_contents = "\n".join(pane.capture_pane()) + except Exception: + return False + else: + has_command = r'printf "%s"' in pane_contents + has_prompts = pane_contents.count("READY>") >= 2 + return has_command and has_prompts + + retry_until(wait_for_command, 2, raises=True) + pane.send_keys("clear -x", literal=True, suppress_history=False) def wait_until_pane_cleared() -> bool: @@ -149,45 +198,53 @@ def wait_until_pane_cleared() -> bool: def pane_contents_shell_prompt() -> bool: pane_contents = "\n".join(pane.capture_pane()) - return pane_contents == "$" + return "READY>" in pane_contents and len(pane_contents.strip()) > 0 retry_until(pane_contents_shell_prompt, 1, raises=True) pane_contents_history_start = pane.capture_pane(start=-2) - assert pane_contents_history_start[0] == '$ printf "%s"' - assert pane_contents_history_start[1] == "$ clear -x" - assert pane_contents_history_start[-1] == "$" + assert r'READY>printf "%s"' in pane_contents_history_start[0] + assert "READY>clear -x" in pane_contents_history_start[1] + assert "READY>" in pane_contents_history_start[-1] pane.send_keys("") def pane_contents_capture_visible_only_shows_prompt() -> bool: pane_contents = "\n".join(pane.capture_pane(start=1)) - return pane_contents == "$" + return "READY>" in pane_contents and len(pane_contents.strip()) > 0 assert retry_until(pane_contents_capture_visible_only_shows_prompt, 1, raises=True) def test_capture_pane_end(session: Session) -> None: """Assert Pane.capture_pane() with ``end`` param.""" - env = shutil.which("env") - assert env is not None, "Cannot find usable `env` in PATH." - - session.new_window( - attach=True, - window_name="capture_pane_end", - window_shell=f"{env} PS1='$ ' sh", - ) - pane = session.active_window.active_pane + window = setup_shell_window(session, "capture_pane_end") + pane = window.active_pane assert pane is not None + pane_contents = "\n".join(pane.capture_pane()) - assert pane_contents == "$" + assert "READY>" in pane_contents + pane.send_keys(r'printf "%s"', literal=True, suppress_history=False) - pane_contents = "\n".join(pane.capture_pane()) - assert pane_contents == '$ printf "%s"\n$' + + def wait_for_command() -> bool: + try: + pane_contents = "\n".join(pane.capture_pane()) + except Exception: + return False + else: + has_command = r'printf "%s"' in pane_contents + has_prompts = pane_contents.count("READY>") >= 2 + return has_command and has_prompts + + retry_until(wait_for_command, 2, raises=True) + pane_contents = "\n".join(pane.capture_pane(end=0)) - assert pane_contents == '$ printf "%s"' + assert r'READY>printf "%s"' in pane_contents + pane_contents = "\n".join(pane.capture_pane(end="-")) - assert pane_contents == '$ printf "%s"\n$' + assert r'READY>printf "%s"' in pane_contents + assert pane_contents.count("READY>") >= 2 def test_pane_split_window_zoom( @@ -360,11 +417,38 @@ def test_set_title_special_characters(session: Session) -> None: def test_pane_context_manager(session: Session) -> None: """Test Pane context manager functionality.""" - window = session.new_window() - initial_pane_count = len(window.panes) + env = shutil.which("env") + assert env is not None, "Cannot find usable `env` in PATH." - with window.split() as pane: + window = setup_shell_window(session, "test_context_manager") + initial_pane_count = len(window.panes) + with window.split(shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh") as pane: assert len(window.panes) == initial_pane_count + 1 + + # Wait for shell to be ready in the split pane + def wait_for_shell() -> bool: + try: + pane_contents = "\n".join(pane.capture_pane()) + return "READY>" in pane_contents and len(pane_contents.strip()) > 0 + except Exception: + return False + + retry_until(wait_for_shell, 2, raises=True) + + pane.send_keys('echo "Hello"', literal=True) + + def wait_for_output() -> bool: + try: + pane_contents = "\n".join(pane.capture_pane()) + return ( + 'echo "Hello"' in pane_contents + and "Hello" in pane_contents + and pane_contents.count("READY>") >= 2 + ) + except Exception: + return False + + retry_until(wait_for_output, 2, raises=True) assert pane in window.panes # Pane should be killed after exiting context diff --git a/tests/test_session.py b/tests/test_session.py index 697056122..5ef00652f 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -379,11 +379,10 @@ def test_new_window_with_environment( pane = window.active_pane assert pane is not None - for k, expected_value in environment.items(): + for k, v in environment.items(): pane.send_keys(f"echo ${k}", literal=True) - # Wait for command output - def wait_for_output(value: str = expected_value) -> bool: + def wait_for_output(value: str = v) -> bool: try: pane_contents = pane.capture_pane() return any(value in line for line in pane_contents) diff --git a/tests/test_window.py b/tests/test_window.py index 5ea57f3c5..cd4ea1a99 100644 --- a/tests/test_window.py +++ b/tests/test_window.py @@ -5,7 +5,6 @@ import logging import pathlib import shutil -import time import typing as t import pytest @@ -20,6 +19,7 @@ ) from libtmux.pane import Pane from libtmux.server import Server +from libtmux.test.retry import retry_until from libtmux.window import Window if t.TYPE_CHECKING: @@ -460,15 +460,32 @@ def test_split_with_environment( window = session.new_window(window_name="split_with_environment") pane = window.split( - shell=f"{env} PS1='$ ' sh", + shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh", environment=environment, ) assert pane is not None - # wait a bit for the prompt to be ready as the test gets flaky otherwise - time.sleep(0.05) + + # Wait for shell to be ready + def wait_for_prompt() -> bool: + try: + pane_contents = "\n".join(pane.capture_pane()) + return "READY>" in pane_contents and len(pane_contents.strip()) > 0 + except Exception: + return False + + retry_until(wait_for_prompt, 2, raises=True) + for k, v in environment.items(): - pane.send_keys(f"echo ${k}") - assert pane.capture_pane()[-2] == v + pane.send_keys(f"echo ${k}", literal=True) + + def wait_for_output(value: str = v) -> bool: + try: + pane_contents = pane.capture_pane() + return any(value in line for line in pane_contents) + except Exception: + return False + + retry_until(wait_for_output, 2, raises=True) def test_split_window_zoom( From 78c656ba29c996586d1376aaaa219d4d0c26051f Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 23 Feb 2025 16:45:53 -0600 Subject: [PATCH 5/5] doctest(Pane) send_keys --- src/libtmux/pane.py | 21 +- tests/legacy_api/test_pane.py | 146 +++----- tests/legacy_api/test_session.py | 407 +++++------------------ tests/legacy_api/test_window.py | 553 ++++++------------------------- 4 files changed, 245 insertions(+), 882 deletions(-) diff --git a/src/libtmux/pane.py b/src/libtmux/pane.py index 610ba3d2b..8adb4138b 100644 --- a/src/libtmux/pane.py +++ b/src/libtmux/pane.py @@ -449,19 +449,30 @@ def send_keys( Examples -------- - >>> pane = window.split(shell='sh') + >>> import shutil + >>> pane = window.split( + ... shell=f"{shutil.which('env')} PROMPT_COMMAND='' PS1='READY>' sh") + >>> from libtmux.test.retry import retry_until + >>> def wait_for_prompt() -> bool: + ... try: + ... pane_contents = "\n".join(pane.capture_pane()) + ... return "READY>" in pane_contents and len(pane_contents.strip()) > 0 + ... except Exception: + ... return False + >>> retry_until(wait_for_prompt, 2, raises=True) + True >>> pane.capture_pane() - ['$'] + ['READY>'] >>> pane.send_keys('echo "Hello world"', enter=True) >>> pane.capture_pane() - ['$ echo "Hello world"', 'Hello world', '$'] + ['READY>echo "Hello world"', 'Hello world', 'READY>'] >>> print('\n'.join(pane.capture_pane())) # doctest: +NORMALIZE_WHITESPACE - $ echo "Hello world" + READY>echo "Hello world" Hello world - $ + READY> """ prefix = " " if suppress_history else "" diff --git a/tests/legacy_api/test_pane.py b/tests/legacy_api/test_pane.py index 5735e41bd..6b21d86eb 100644 --- a/tests/legacy_api/test_pane.py +++ b/tests/legacy_api/test_pane.py @@ -1,137 +1,71 @@ -"""Tests for libtmux Pane object.""" +"""Tests for deprecated libtmux Pane APIs. + +These tests verify that deprecated methods raise DeprecatedError. +""" from __future__ import annotations -import logging -import shutil import typing as t -from libtmux.test.retry import retry_until +import pytest + +from libtmux import exc if t.TYPE_CHECKING: from libtmux.session import Session - from libtmux.window import Window - -logger = logging.getLogger(__name__) - - -def setup_shell_window( - session: Session, - window_name: str, - environment: dict[str, str] | None = None, -) -> Window: - """Set up a shell window with consistent environment and prompt. - Args: - session: The tmux session to create the window in - window_name: Name for the new window - environment: Optional environment variables to set in the window - - Returns - ------- - The created Window object with shell ready - """ - env = shutil.which("env") - assert env is not None, "Cannot find usable `env` in PATH." - - window = session.new_window( - attach=True, - window_name=window_name, - window_shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh", - environment=environment, - ) +def test_resize_pane_raises_deprecated_error(session: Session) -> None: + """Test Pane.resize_pane() raises DeprecatedError.""" + window = session.active_window pane = window.active_pane assert pane is not None - # Wait for shell to be ready - def wait_for_prompt() -> bool: - try: - pane_contents = "\n".join(pane.capture_pane()) - return "READY>" in pane_contents and len(pane_contents.strip()) > 0 - except Exception: - return False - - retry_until(wait_for_prompt, 2, raises=True) - return window + with pytest.raises( + exc.DeprecatedError, match=r"Pane\.resize_pane\(\) was deprecated" + ): + pane.resize_pane(height=4) -def test_resize_pane(session: Session) -> None: - """Verify Pane.resize_pane().""" - window = setup_shell_window(session, "test_resize_pane") +def test_select_pane_raises_deprecated_error(session: Session) -> None: + """Test Pane.select_pane() raises DeprecatedError.""" + window = session.active_window pane = window.active_pane assert pane is not None - pane1 = window.attached_pane - assert pane1 is not None - pane1_height = pane1["pane_height"] - window.split_window() - - pane1.resize_pane(height=4) - assert pane1["pane_height"] != pane1_height - assert int(pane1["pane_height"]) == 4 - - pane1.resize_pane(height=3) - assert int(pane1["pane_height"]) == 3 + with pytest.raises( + exc.DeprecatedError, match=r"Pane\.select_pane\(\) was deprecated" + ): + pane.select_pane() -def test_send_keys(session: Session) -> None: - """Verify Pane.send_keys().""" - window = setup_shell_window(session, "test_send_keys") +def test_split_window_raises_deprecated_error(session: Session) -> None: + """Test Pane.split_window() raises DeprecatedError.""" + window = session.active_window pane = window.active_pane assert pane is not None - pane.send_keys("echo 'test'", literal=True) + with pytest.raises( + exc.DeprecatedError, match=r"Pane\.split_window\(\) was deprecated" + ): + pane.split_window() - def wait_for_echo() -> bool: - try: - pane_contents = "\n".join(pane.capture_pane()) - return ( - "test" in pane_contents - and "echo 'test'" in pane_contents - and pane_contents.count("READY>") >= 2 - ) - except Exception: - return False - retry_until(wait_for_echo, 2, raises=True) - - -def test_set_height(session: Session) -> None: - """Verify Pane.set_height().""" - window = session.new_window(window_name="test_set_height") - window.split_window() - pane1 = window.attached_pane - assert pane1 is not None - pane1_height = pane1["pane_height"] - - pane1.set_height(4) - assert pane1["pane_height"] != pane1_height - assert int(pane1["pane_height"]) == 4 - - -def test_set_width(session: Session) -> None: - """Verify Pane.set_width().""" - window = session.new_window(window_name="test_set_width") - window.split_window() - - window.select_layout("main-vertical") - pane1 = window.attached_pane - assert pane1 is not None - pane1_width = pane1["pane_width"] - - pane1.set_width(10) - assert pane1["pane_width"] != pane1_width - assert int(pane1["pane_width"]) == 10 +def test_pane_get_raises_deprecated_error(session: Session) -> None: + """Test Pane.get() raises DeprecatedError.""" + window = session.active_window + pane = window.active_pane + assert pane is not None - pane1.reset() + with pytest.raises(exc.DeprecatedError, match=r"Pane\.get\(\) was deprecated"): + pane.get("pane_id") -def test_capture_pane(session: Session) -> None: - """Verify Pane.capture_pane().""" - window = setup_shell_window(session, "test_capture_pane") +def test_pane_getitem_raises_deprecated_error(session: Session) -> None: + """Test Pane.__getitem__() raises DeprecatedError.""" + window = session.active_window pane = window.active_pane assert pane is not None - pane_contents = "\n".join(pane.capture_pane()) - assert "READY>" in pane_contents + with pytest.raises(exc.DeprecatedError, match=r"Pane\[key\] lookup was deprecated"): + _ = pane["pane_id"] diff --git a/tests/legacy_api/test_session.py b/tests/legacy_api/test_session.py index 76c9a5059..7cc77ef5c 100644 --- a/tests/legacy_api/test_session.py +++ b/tests/legacy_api/test_session.py @@ -1,367 +1,118 @@ -"""Test for libtmux Session object.""" +"""Tests for deprecated libtmux Session APIs. + +These tests verify that deprecated methods raise exc.DeprecatedError. +""" from __future__ import annotations -import logging -import shutil import typing as t import pytest from libtmux import exc -from libtmux.common import has_gte_version, has_lt_version -from libtmux.pane import Pane -from libtmux.session import Session -from libtmux.test.constants import TEST_SESSION_PREFIX -from libtmux.test.random import namer -from libtmux.test.retry import retry_until -from libtmux.window import Window if t.TYPE_CHECKING: from libtmux.server import Server + from libtmux.session import Session -logger = logging.getLogger(__name__) - - -def test_has_session(server: Server, session: Session) -> None: - """Server.has_session returns True if has session_name exists.""" - TEST_SESSION_NAME = session.get("session_name") - assert TEST_SESSION_NAME is not None - assert server.has_session(TEST_SESSION_NAME) - if has_gte_version("2.1"): - assert not server.has_session(TEST_SESSION_NAME[:-2]) - assert server.has_session(TEST_SESSION_NAME[:-2], exact=False) - assert not server.has_session("asdf2314324321") - - -def test_select_window(session: Session) -> None: - """Session.select_window moves window.""" - # get the current window_base_index, since different user tmux config - # may start at 0 or 1, or whatever they want. - window_idx = session.attached_window.get("window_index") - assert window_idx is not None - window_base_index = int(window_idx) - - session.new_window(window_name="test_window") - window_count = len(session._windows) - - assert window_count >= 2 # 2 or more windows - - assert len(session._windows) == window_count - - # tmux selects a window, moves to it, shows it as attached_window - selected_window1 = session.select_window(window_base_index) - assert isinstance(selected_window1, Window) - attached_window1 = session.attached_window - - assert selected_window1.id == attached_window1.id - - # again: tmux selects a window, moves to it, shows it as - # attached_window - selected_window2 = session.select_window(window_base_index + 1) - assert isinstance(selected_window2, Window) - attached_window2 = session.attached_window - - assert selected_window2.id == attached_window2.id - - # assure these windows were really different - assert selected_window1.id != selected_window2.id - - -def test_select_window_returns_Window(session: Session) -> None: - """Session.select_window returns Window object.""" - window_count = len(session._windows) - assert len(session._windows) == window_count - - window_idx = session.attached_window.get("window_index") - assert window_idx is not None - window_base_index = int(window_idx) - window = session.select_window(window_base_index) - assert isinstance(window, Window) - - -def test_attached_window(session: Session) -> None: - """Session.attached_window returns Window.""" - assert isinstance(session.attached_window, Window) - - -def test_attached_pane(session: Session) -> None: - """Session.attached_pane returns Pane.""" - assert isinstance(session.attached_pane, Pane) - - -def test_session_rename(session: Session) -> None: - """Session.rename_session renames session.""" - session_name = session.get("session_name") - assert session_name is not None - TEST_SESSION_NAME = session_name - - test_name = "testingdis_sessname" - session.rename_session(test_name) - session_name = session.get("session_name") - assert session_name is not None - assert session_name == test_name - session.rename_session(TEST_SESSION_NAME) - session_name = session.get("session_name") - assert session_name is not None - assert session_name == TEST_SESSION_NAME - - -def test_new_session(server: Server) -> None: - """Server.new_session creates new session.""" - new_session_name = TEST_SESSION_PREFIX + next(namer) - new_session = server.new_session(session_name=new_session_name, detach=True) - - assert isinstance(new_session, Session) - assert new_session.get("session_name") == new_session_name - - -def test_show_options(session: Session) -> None: - """Session.show_options() returns dict.""" - options = session.show_options() - assert isinstance(options, dict) - - -def test_set_show_options_single(session: Session) -> None: - """Set option then Session.show_options(key).""" - session.set_option("history-limit", 20) - assert session.show_option("history-limit") == 20 - - session.set_option("history-limit", 40) - assert session.show_option("history-limit") == 40 - assert session.show_options()["history-limit"] == 40 +def test_attached_window_raises_deprecated_error(session: Session) -> None: + """Test Session.attached_window raises exc.DeprecatedError.""" + with pytest.raises( + exc.DeprecatedError, match=r"Session\.attached_window was deprecated" + ): + _ = session.attached_window -def test_set_show_option(session: Session) -> None: - """Set option then Session.show_option(key).""" - session.set_option("history-limit", 20) - assert session.show_option("history-limit") == 20 +def test_attached_pane_raises_deprecated_error(session: Session) -> None: + """Test Session.attached_pane raises exc.DeprecatedError.""" + with pytest.raises( + exc.DeprecatedError, match=r"Session\.attached_pane was deprecated" + ): + _ = session.attached_pane - session.set_option("history-limit", 40) - assert session.show_option("history-limit") == 40 +def test_attach_session_raises_deprecated_error(session: Session) -> None: + """Test Session.attach_session() raises exc.DeprecatedError.""" + with pytest.raises( + exc.DeprecatedError, match=r"Session\.attach_session\(\) was deprecated" + ): + session.attach_session() -def test_empty_session_option_returns_None(session: Session) -> None: - """Verify Session.show_option returns None for unset option.""" - assert session.show_option("default-shell") is None +def test_kill_session_raises_deprecated_error(server: Server) -> None: + """Test Session.kill_session() raises exc.DeprecatedError.""" + # Create a new session to kill (so we don't kill our test session) + new_session = server.new_session(session_name="test_kill_session", detach=True) + with pytest.raises( + exc.DeprecatedError, match=r"Session\.kill_session\(\) was deprecated" + ): + new_session.kill_session() -def test_show_option_unknown(session: Session) -> None: - """Session.show_option raises UnknownOption for invalid option.""" - cmd_exception: type[exc.OptionError] = exc.UnknownOption - if has_gte_version("3.0"): - cmd_exception = exc.InvalidOption - with pytest.raises(cmd_exception): - session.show_option("moooz") + # Clean up using the new API + new_session.kill() -def test_show_option_ambiguous(session: Session) -> None: - """Session.show_option raises AmbiguousOption for ambiguous option.""" - with pytest.raises(exc.AmbiguousOption): - session.show_option("default-") +def test_session_get_raises_deprecated_error(session: Session) -> None: + """Test Session.get() raises exc.DeprecatedError.""" + with pytest.raises(exc.DeprecatedError, match=r"Session\.get\(\) was deprecated"): + session.get("session_name") -def test_set_option_ambiguous(session: Session) -> None: - """Session.set_option raises AmbiguousOption for invalid option.""" - with pytest.raises(exc.AmbiguousOption): - session.set_option("default-", 43) +def test_session_getitem_raises_deprecated_error(session: Session) -> None: + """Test Session.__getitem__() raises exc.DeprecatedError.""" + with pytest.raises( + exc.DeprecatedError, match=r"Session\[key\] lookup was deprecated" + ): + _ = session["session_name"] -def test_set_option_invalid(session: Session) -> None: - """Session.set_option raises UnknownOption for invalid option.""" - if has_gte_version("2.4"): - with pytest.raises(exc.InvalidOption): - session.set_option("afewewfew", 43) - else: - with pytest.raises(exc.UnknownOption): - session.set_option("afewewfew", 43) +def test_session_get_by_id_raises_deprecated_error(session: Session) -> None: + """Test Session.get_by_id() raises exc.DeprecatedError.""" + with pytest.raises( + exc.DeprecatedError, match=r"Session\.get_by_id\(\) was deprecated" + ): + session.get_by_id("@0") -def test_show_environment(session: Session) -> None: - """Session.show_environment() returns dict.""" - vars_ = session.show_environment() - assert isinstance(vars_, dict) +def test_session_where_raises_deprecated_error(session: Session) -> None: + """Test Session.where() raises exc.DeprecatedError.""" + with pytest.raises(exc.DeprecatedError, match=r"Session\.where\(\) was deprecated"): + session.where({"window_name": "test"}) -def test_set_show_environment_single(session: Session) -> None: - """Set environment then Session.show_environment(key).""" - session.set_environment("FOO", "BAR") - assert session.getenv("FOO") == "BAR" +def test_session_find_where_raises_deprecated_error(session: Session) -> None: + """Test Session.find_where() raises exc.DeprecatedError.""" + with pytest.raises( + exc.DeprecatedError, match=r"Session\.find_where\(\) was deprecated" + ): + session.find_where({"window_name": "test"}) - session.set_environment("FOO", "DAR") - assert session.getenv("FOO") == "DAR" - assert session.show_environment()["FOO"] == "DAR" +def test_session_list_windows_raises_deprecated_error(session: Session) -> None: + """Test Session.list_windows() raises exc.DeprecatedError.""" + with pytest.raises( + exc.DeprecatedError, match=r"Session\.list_windows\(\) was deprecated" + ): + session.list_windows() -def test_show_environment_not_set(session: Session) -> None: - """Not set environment variable returns None.""" - assert session.getenv("BAR") is None +def test_session_children_raises_deprecated_error(session: Session) -> None: + """Test Session.children raises exc.DeprecatedError.""" + with pytest.raises(exc.DeprecatedError, match=r"Session\.children was deprecated"): + _ = session.children -def test_remove_environment(session: Session) -> None: - """Remove environment variable.""" - assert session.getenv("BAM") is None - session.set_environment("BAM", "OK") - assert session.getenv("BAM") == "OK" - session.remove_environment("BAM") - assert session.getenv("BAM") is None +def test_session__windows_raises_deprecated_error(session: Session) -> None: + """Test Session._windows raises exc.DeprecatedError.""" + with pytest.raises(exc.DeprecatedError, match=r"Session\._windows was deprecated"): + _ = session._windows -def test_unset_environment(session: Session) -> None: - """Unset environment variable.""" - assert session.getenv("BAM") is None - session.set_environment("BAM", "OK") - assert session.getenv("BAM") == "OK" - session.unset_environment("BAM") - assert session.getenv("BAM") is None - - -@pytest.mark.parametrize( - ("session_name", "raises"), - [("hey.period", True), ("hey:its a colon", True), ("hey moo", False)], -) -def test_periods_raise_badsessionname( - server: Server, - session: Session, - session_name: str, - raises: bool, -) -> None: - """Verify session names with periods raise BadSessionName.""" - new_name = session_name + "moo" # used for rename / switch - if raises: - with pytest.raises(exc.BadSessionName): - session.rename_session(new_name) - - with pytest.raises(exc.BadSessionName): - server.new_session(session_name) - - with pytest.raises(exc.BadSessionName): - server.has_session(session_name) - - with pytest.raises(exc.BadSessionName): - server.switch_client(new_name) - - with pytest.raises(exc.BadSessionName): - server.attach_session(new_name) - - else: - server.new_session(session_name) - server.has_session(session_name) - session.rename_session(new_name) - with pytest.raises(exc.LibTmuxException): - server.switch_client(new_name) - - -def test_cmd_inserts_session_id(session: Session) -> None: - """Verify Session.cmd() inserts session_id.""" - current_session_id = session.id - last_arg = "last-arg" - cmd = session.cmd("not-a-command", last_arg) - assert "-t" in cmd.cmd - assert current_session_id in cmd.cmd - assert cmd.cmd[-1] == last_arg - - -def setup_shell_window( - session: Session, - window_name: str, - environment: dict[str, str] | None = None, -) -> Window: - """Set up a shell window with consistent environment and prompt. - - Args: - session: The tmux session to create the window in - window_name: Name for the new window - environment: Optional environment variables to set in the window - - Returns - ------- - The created Window object with shell ready - """ - env = shutil.which("env") - assert env is not None, "Cannot find usable `env` in PATH." - - window = session.new_window( - attach=True, - window_name=window_name, - window_shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh", - environment=environment, - ) - - pane = window.active_pane - assert pane is not None - - # Wait for shell to be ready - def wait_for_prompt() -> bool: - try: - pane_contents = "\n".join(pane.capture_pane()) - return "READY>" in pane_contents and len(pane_contents.strip()) > 0 - except Exception: - return False - - retry_until(wait_for_prompt, 2, raises=True) - return window - - -@pytest.mark.skipif( - has_lt_version("3.0"), - reason="needs -e flag for new-window which was introduced in 3.0", -) -@pytest.mark.parametrize( - "environment", - [ - {"ENV_VAR": "window"}, - {"ENV_VAR_1": "window_1", "ENV_VAR_2": "window_2"}, - ], -) -def test_new_window_with_environment( - session: Session, - environment: dict[str, str], -) -> None: - """Verify new window with environment vars.""" - window = setup_shell_window( - session, - "window_with_environment", - environment=environment, - ) - pane = window.active_pane - assert pane is not None - - for k, v in environment.items(): - pane.send_keys(f"echo ${k}", literal=True) - - def wait_for_output(value: str = v) -> bool: - try: - pane_contents = pane.capture_pane() - return any(value in line for line in pane_contents) - except Exception: - return False - - retry_until(wait_for_output, 2, raises=True) - - -@pytest.mark.skipif( - has_gte_version("3.0"), - reason="3.0 has the -e flag on new-window", -) -def test_new_window_with_environment_logs_warning_for_old_tmux( - session: Session, - caplog: pytest.LogCaptureFixture, -) -> None: - """Verify new window with environment vars create a warning if tmux is too old.""" - env = shutil.which("env") - assert env is not None, "Cannot find usable `env` in PATH." - - session.new_window( - attach=True, - window_name="window_with_environment", - window_shell=f"{env} PS1='$ ' sh", - environment={"ENV_VAR": "window"}, - ) - - assert any("Environment flag ignored" in record.msg for record in caplog.records), ( - "Warning missing" - ) +def test_session__list_windows_raises_deprecated_error(session: Session) -> None: + """Test Session._list_windows() raises exc.DeprecatedError.""" + with pytest.raises( + exc.DeprecatedError, match=r"Session\._list_windows\(\) was deprecated" + ): + session._list_windows() diff --git a/tests/legacy_api/test_window.py b/tests/legacy_api/test_window.py index 28ae83137..2bf589f38 100644 --- a/tests/legacy_api/test_window.py +++ b/tests/legacy_api/test_window.py @@ -1,502 +1,169 @@ -"""Test for libtmux Window object.""" +"""Tests for deprecated libtmux Window APIs. + +These tests verify that deprecated methods raise exc.DeprecatedError. +""" from __future__ import annotations -import logging -import shutil import typing as t import pytest from libtmux import exc -from libtmux.common import has_gte_version, has_lt_version, has_version -from libtmux.pane import Pane -from libtmux.server import Server -from libtmux.test.retry import retry_until -from libtmux.window import Window if t.TYPE_CHECKING: from libtmux.session import Session -logger = logging.getLogger(__name__) +def test_split_window_raises_deprecated_error(session: Session) -> None: + """Test Window.split_window() raises exc.DeprecatedError.""" + window = session.active_window -def test_select_window(session: Session) -> None: - """Test Window.select_window().""" - window_count = len(session._windows) - # to do, get option for base-index from tmux - # for now however, let's get the index from the first window. - assert window_count == 1 - - assert session.attached_window is not None - assert session.attached_window.index is not None - window_base_index = int(session.attached_window.index) - - window = session.new_window(window_name="testing 3") - - # self.assertEqual(2, - # int(session.attached_window.index)) - assert window.index is not None - assert int(window_base_index) + 1 == int(window.index) - - session.select_window(str(window_base_index)) - assert window_base_index == int(session.attached_window.index) - - session.select_window("testing 3") - assert int(window_base_index) + 1 == int(session.attached_window.index) - - assert len(session._windows) == 2 - - -def test_fresh_window_data(session: Session) -> None: - """Verify window data is fresh.""" - attached_window = session.attached_window - assert attached_window is not None - pane_base_idx = attached_window.show_window_option("pane-base-index", g=True) - assert pane_base_idx is not None - pane_base_index = int(pane_base_idx) - - assert len(session.windows) == 1 - - assert len(session.attached_window.panes) == 1 - current_windows = len(session._windows) - assert session.get("session_id") != "@0" - assert current_windows == 1 - - assert len(session.attached_window.panes) == 1 - assert isinstance(session.server, Server) - # len(session.attached_window.panes)) - - assert len(session.windows), 1 - assert len(session.attached_window.panes) == 1 - for w in session.windows: - assert isinstance(w, Window) - window = session.attached_window - assert isinstance(window, Window) - assert len(session.attached_window.panes) == 1 - window.split_window() - - attached_window = session.attached_window - assert attached_window is not None - attached_window.select_pane(pane_base_index) - - attached_pane = session.attached_pane - assert attached_pane is not None - attached_pane.send_keys("cd /srv/www/flaskr") - - attached_window.select_pane(pane_base_index + 1) - attached_pane = session.attached_pane - assert attached_pane is not None - attached_pane.send_keys("source .venv/bin/activate") - session.new_window(window_name="second") - current_windows += 1 - assert current_windows == len(session._windows) - session.new_window(window_name="hey") - current_windows += 1 - assert current_windows == len(session._windows) - - session.select_window("1") - session.kill_window(target_window="hey") - current_windows -= 1 - assert current_windows == len(session._windows) - - -def test_newest_pane_data(session: Session) -> None: - """Test window.panes has fresh data.""" - window = session.new_window(window_name="test", attach=True) - assert isinstance(window, Window) - assert len(window.panes) == 1 - window.split_window(attach=True) - - assert len(window.panes) == 2 - # note: the below used to accept -h, removing because split_window now - # has attach as its only argument now - window.split_window(attach=True) - assert len(window.panes) == 3 - - -def test_attached_pane(session: Session) -> None: - """Window.attached_window returns active Pane.""" - window = session.attached_window # current window - assert isinstance(window.attached_pane, Pane) - - -def test_split_window(session: Session) -> None: - """Window.split_window() splits window, returns new Pane, vertical.""" - window_name = "test split window" - window = session.new_window(window_name=window_name, attach=True) - pane = window.split_window() - assert len(window.panes) == 2 - assert isinstance(pane, Pane) - assert window.width is not None - assert window.panes[0].height is not None - assert float(window.panes[0].height) <= ((float(window.width) + 1) / 2) - - -def test_split_window_shell(session: Session) -> None: - """Window.split_window() splits window, returns new Pane, vertical.""" - window_name = "test split window" - cmd = "sleep 1m" - window = session.new_window(window_name=window_name, attach=True) - pane = window.split_window(shell=cmd) - assert len(window.panes) == 2 - assert isinstance(pane, Pane) - assert window.width is not None - assert window.panes[0].height is not None - assert float(window.panes[0].height) <= ((float(window.width) + 1) / 2) - if has_gte_version("3.2"): - assert pane.get("pane_start_command", "").replace('"', "") == cmd - else: - assert pane.get("pane_start_command") == cmd - - -def test_split_window_horizontal(session: Session) -> None: - """Window.split_window() splits window, returns new Pane, horizontal.""" - window_name = "test split window" - window = session.new_window(window_name=window_name, attach=True) - pane = window.split_window(vertical=False) - assert len(window.panes) == 2 - assert isinstance(pane, Pane) - assert window.width is not None - assert window.panes[0].width is not None - assert float(window.panes[0].width) <= ((float(window.width) + 1) / 2) - - -@pytest.mark.filterwarnings("ignore:.*deprecated in favor of Window.split()") -@pytest.mark.filterwarnings("ignore:.*vertical is not required to pass with direction.") -def test_split_percentage( - session: Session, -) -> None: - """Test deprecated percent param.""" - window = session.new_window(window_name="split window size") - window.resize(height=100, width=100) - window_height_before = ( - int(window.window_height) if isinstance(window.window_height, str) else 0 - ) - if has_version("3.4"): - pytest.skip( - "tmux 3.4 has a split-window bug." - + " See https://github.com/tmux/tmux/pull/3840.", - ) - with pytest.warns(match="Deprecated in favor of size.*"): - pane = window.split_window(percent=10) - assert pane.pane_height == str(int(window_height_before * 0.1)) - - -def test_split_window_size(session: Session) -> None: - """Window.split_window() respects size.""" - window = session.new_window(window_name="split_window window size") - window.resize(height=100, width=100) - - if has_gte_version("3.1"): - pane = window.split_window(size=10) - assert pane.pane_height == "10" - - pane = window.split_window(vertical=False, size=10) - assert pane.pane_width == "10" - - pane = window.split_window(size="10%") - assert pane.pane_height == "8" - - pane = window.split_window(vertical=False, size="10%") - assert pane.pane_width == "8" - else: - window_height_before = ( - int(window.window_height) if isinstance(window.window_height, str) else 0 - ) - window_width_before = ( - int(window.window_width) if isinstance(window.window_width, str) else 0 - ) - pane = window.split_window(size="10%") - assert pane.pane_height == str(int(window_height_before * 0.1)) - - pane = window.split_window(vertical=False, size="10%") - assert pane.pane_width == str(int(window_width_before * 0.1)) + with pytest.raises( + exc.DeprecatedError, match=r"Window\.split_window\(\) was deprecated" + ): + window.split_window() -@pytest.mark.parametrize( - ("window_name_before", "window_name_after"), - [("test", "ha ha ha fjewlkjflwef"), ("test", "hello \\ wazzup 0")], -) -def test_window_rename( - session: Session, - window_name_before: str, - window_name_after: str, -) -> None: - """Test Window.rename_window().""" - window_name_before = "test" - window_name_after = "ha ha ha fjewlkjflwef" +def test_attached_pane_raises_deprecated_error(session: Session) -> None: + """Test Window.attached_pane raises exc.DeprecatedError.""" + window = session.active_window - session.set_option("automatic-rename", "off") - window = session.new_window(window_name=window_name_before, attach=True) + with pytest.raises( + exc.DeprecatedError, match=r"Window\.attached_pane was deprecated" + ): + _ = window.attached_pane - assert window == session.attached_window - assert window.get("window_name") == window_name_before - window.rename_window(window_name_after) +def test_select_window_raises_deprecated_error(session: Session) -> None: + """Test Window.select_window() raises exc.DeprecatedError.""" + window = session.active_window - window = session.attached_window + with pytest.raises( + exc.DeprecatedError, match=r"Window\.select_window\(\) was deprecated" + ): + window.select_window() - assert window.get("window_name") == window_name_after - window = session.attached_window +def test_kill_window_raises_deprecated_error(session: Session) -> None: + """Test Window.kill_window() raises exc.DeprecatedError.""" + # Create a new window to kill (so we don't kill our only window) + session.new_window(window_name="extra_window") + window = session.active_window - assert window.get("window_name") == window_name_after + with pytest.raises( + exc.DeprecatedError, match=r"Window\.kill_window\(\) was deprecated" + ): + window.kill_window() -def test_kill_window(session: Session) -> None: - """Test window.kill_window() kills window.""" - session.new_window() - # create a second window to not kick out the client. - # there is another way to do this via options too. +def test_set_window_option_emits_deprecation_warning(session: Session) -> None: + """Test Window.set_window_option() emits DeprecationWarning.""" + window = session.active_window - w = session.attached_window + with pytest.warns( + DeprecationWarning, match=r"Window\.set_window_option\(\) is deprecated" + ): + window.set_window_option("main-pane-height", 20) - assert isinstance(w.get("window_id"), str) - assert len(session.windows.filter(window_id=w.get("window_id"))) == 1 - w.kill_window() +def test_show_window_options_emits_deprecation_warning(session: Session) -> None: + """Test Window.show_window_options() emits DeprecationWarning.""" + window = session.active_window - assert len(session.windows.filter(window_id=w.get("window_id"))) == 0 + with pytest.warns( + DeprecationWarning, match=r"Window\.show_window_options\(\) is deprecated" + ): + window.show_window_options() -def test_show_window_options(session: Session) -> None: - """Window.show_window_options() returns dict.""" - window = session.new_window(window_name="test_window") +def test_show_window_option_emits_deprecation_warning(session: Session) -> None: + """Test Window.show_window_option() emits DeprecationWarning.""" + window = session.active_window - options = window.show_window_options() - assert isinstance(options, dict) + with pytest.warns( + DeprecationWarning, match=r"Window\.show_window_option\(\) is deprecated" + ): + window.show_window_option("main-pane-height") -def test_set_show_window_options(session: Session) -> None: - """Set option then Window.show_window_options(key).""" - window = session.new_window(window_name="test_window") +def test_window_get_raises_deprecated_error(session: Session) -> None: + """Test Window.get() raises exc.DeprecatedError.""" + window = session.active_window - window.set_window_option("main-pane-height", 20) - assert window.show_window_option("main-pane-height") == 20 + with pytest.raises(exc.DeprecatedError, match=r"Window\.get\(\) was deprecated"): + window.get("window_id") - window.set_window_option("main-pane-height", 40) - assert window.show_window_option("main-pane-height") == 40 - assert window.show_window_options()["main-pane-height"] == 40 - if has_gte_version("2.3"): - window.set_window_option("pane-border-format", " #P ") - assert window.show_window_option("pane-border-format") == " #P " +def test_window_getitem_raises_deprecated_error(session: Session) -> None: + """Test Window.__getitem__() raises exc.DeprecatedError.""" + window = session.active_window + with pytest.raises( + exc.DeprecatedError, match=r"Window\[key\] lookup was deprecated" + ): + _ = window["window_id"] -def test_empty_window_option_returns_None(session: Session) -> None: - """Verify unset window option returns None.""" - window = session.new_window(window_name="test_window") - assert window.show_window_option("alternate-screen") is None +def test_window_get_by_id_raises_deprecated_error(session: Session) -> None: + """Test Window.get_by_id() raises exc.DeprecatedError.""" + window = session.active_window -def test_show_window_option(session: Session) -> None: - """Set option then Window.show_window_option(key).""" - window = session.new_window(window_name="test_window") + with pytest.raises( + exc.DeprecatedError, match=r"Window\.get_by_id\(\) was deprecated" + ): + window.get_by_id("%0") - window.set_window_option("main-pane-height", 20) - assert window.show_window_option("main-pane-height") == 20 - window.set_window_option("main-pane-height", 40) - assert window.show_window_option("main-pane-height") == 40 - assert window.show_window_option("main-pane-height") == 40 +def test_window_where_raises_deprecated_error(session: Session) -> None: + """Test Window.where() raises exc.DeprecatedError.""" + window = session.active_window + with pytest.raises(exc.DeprecatedError, match=r"Window\.where\(\) was deprecated"): + window.where({"pane_id": "%0"}) -def test_show_window_option_unknown(session: Session) -> None: - """Window.show_window_option raises UnknownOption for bad option key.""" - window = session.new_window(window_name="test_window") - cmd_exception: type[exc.OptionError] = exc.UnknownOption - if has_gte_version("3.0"): - cmd_exception = exc.InvalidOption - with pytest.raises(cmd_exception): - window.show_window_option("moooz") +def test_window_find_where_raises_deprecated_error(session: Session) -> None: + """Test Window.find_where() raises exc.DeprecatedError.""" + window = session.active_window + with pytest.raises( + exc.DeprecatedError, match=r"Window\.find_where\(\) was deprecated" + ): + window.find_where({"pane_id": "%0"}) -def test_show_window_option_ambiguous(session: Session) -> None: - """show_window_option raises AmbiguousOption for ambiguous option.""" - window = session.new_window(window_name="test_window") - with pytest.raises(exc.AmbiguousOption): - window.show_window_option("clock-mode") +def test_window_list_panes_raises_deprecated_error(session: Session) -> None: + """Test Window.list_panes() raises exc.DeprecatedError.""" + window = session.active_window + with pytest.raises( + exc.DeprecatedError, match=r"Window\.list_panes\(\) was deprecated" + ): + window.list_panes() -def test_set_window_option_ambiguous(session: Session) -> None: - """set_window_option raises AmbiguousOption for ambiguous option.""" - window = session.new_window(window_name="test_window") - with pytest.raises(exc.AmbiguousOption): - window.set_window_option("clock-mode", 12) +def test_window_children_raises_deprecated_error(session: Session) -> None: + """Test Window.children raises exc.DeprecatedError.""" + window = session.active_window + with pytest.raises(exc.DeprecatedError, match=r"Window\.children was deprecated"): + _ = window.children -def test_set_window_option_invalid(session: Session) -> None: - """Window.set_window_option raises ValueError for invalid option key.""" - window = session.new_window(window_name="test_window") - if has_gte_version("2.4"): - with pytest.raises(exc.InvalidOption): - window.set_window_option("afewewfew", 43) - else: - with pytest.raises(exc.UnknownOption): - window.set_window_option("afewewfew", 43) +def test_window__panes_raises_deprecated_error(session: Session) -> None: + """Test Window._panes raises exc.DeprecatedError.""" + window = session.active_window + with pytest.raises(exc.DeprecatedError, match=r"Window\._panes was deprecated"): + _ = window._panes -def test_move_window(session: Session) -> None: - """Window.move_window results in changed index.""" - window = session.new_window(window_name="test_window") - assert window.index is not None - new_index = str(int(window.index) + 1) - window.move_window(new_index) - assert window.index == new_index +def test_window__list_panes_raises_deprecated_error(session: Session) -> None: + """Test Window._list_panes() raises exc.DeprecatedError.""" + window = session.active_window -def test_move_window_to_other_session(server: Server, session: Session) -> None: - """Window.move_window to other session.""" - window = session.new_window(window_name="test_window") - new_session = server.new_session("test_move_window") - window.move_window(session=new_session.get("session_id")) - window_id = window.get("window_id") - assert window_id is not None - assert new_session.get_by_id(window_id) == window - - -def test_select_layout_accepts_no_arg(server: Server, session: Session) -> None: - """Tmux allows select-layout with no arguments, so let's allow it here.""" - window = session.new_window(window_name="test_window") - window.select_layout() - - -@pytest.mark.skipif( - has_lt_version("3.2"), - reason="needs filter introduced in tmux >= 3.2", -) -def test_empty_window_name(session: Session) -> None: - """New windows can be created with empty string for window name.""" - session.set_option("automatic-rename", "off") - window = session.new_window(window_name="''", attach=True) - - assert window == session.attached_window - assert window.get("window_name") == "''" - assert session.name is not None - - cmd = session.cmd( - "list-windows", - "-F", - "#{window_name}", - "-f", - "#{==:#{session_name}," + session.name + "}", - ) - assert "''" in cmd.stdout - - -def setup_shell_window( - session: Session, - window_name: str, - environment: dict[str, str] | None = None, -) -> Window: - """Set up a shell window with consistent environment and prompt. - - Args: - session: The tmux session to create the window in - window_name: Name for the new window - environment: Optional environment variables to set in the window - - Returns - ------- - The created Window object with shell ready - """ - env = shutil.which("env") - assert env is not None, "Cannot find usable `env` in PATH." - - window = session.new_window( - attach=True, - window_name=window_name, - window_shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh", - environment=environment, - ) - - pane = window.active_pane - assert pane is not None - - # Wait for shell to be ready - def wait_for_prompt() -> bool: - try: - pane_contents = "\n".join(pane.capture_pane()) - return "READY>" in pane_contents and len(pane_contents.strip()) > 0 - except Exception: - return False - - retry_until(wait_for_prompt, 2, raises=True) - return window - - -@pytest.mark.skipif( - has_lt_version("3.0"), - reason="needs -e flag for split-window which was introduced in 3.0", -) -@pytest.mark.parametrize( - "environment", - [ - {"ENV_VAR": "pane"}, - {"ENV_VAR_1": "pane_1", "ENV_VAR_2": "pane_2"}, - ], -) -def test_split_window_with_environment( - session: Session, - environment: dict[str, str], -) -> None: - """Verify splitting window with environment variables.""" - env = shutil.which("env") - assert env is not None, "Cannot find usable `env` in PATH." - - window = setup_shell_window(session, "split_with_environment") - pane = window.split( - shell=f"{env} PROMPT_COMMAND='' PS1='READY>' sh", - environment=environment, - ) - assert pane is not None - - # Wait for shell to be ready - def wait_for_prompt() -> bool: - try: - pane_contents = "\n".join(pane.capture_pane()) - return "READY>" in pane_contents and len(pane_contents.strip()) > 0 - except Exception: - return False - - retry_until(wait_for_prompt, 2, raises=True) - - for k, v in environment.items(): - pane.send_keys(f"echo ${k}", literal=True) - - def wait_for_output(value: str = v) -> bool: - try: - pane_contents = pane.capture_pane() - return any(value in line for line in pane_contents) - except Exception: - return False - - retry_until(wait_for_output, 2, raises=True) - - -@pytest.mark.skipif( - has_gte_version("3.0"), - reason="3.0 has the -e flag on split-window", -) -def test_split_window_with_environment_logs_warning_for_old_tmux( - session: Session, - caplog: pytest.LogCaptureFixture, -) -> None: - """Verify splitting window with environment variables warns if tmux too old.""" - env = shutil.which("env") - assert env is not None, "Cannot find usable `env` in Path." - - window = session.new_window(window_name="split_window_with_environment") - window.split_window( - shell=f"{env} PS1='$ ' sh", - environment={"ENV_VAR": "pane"}, - ) - - assert any("Environment flag ignored" in record.msg for record in caplog.records), ( - "Warning missing" - ) + with pytest.raises( + exc.DeprecatedError, match=r"Window\._list_panes\(\) was deprecated" + ): + window._list_panes()