Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tests: Improve shell interoperability #577

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions src/libtmux/pane.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,19 +371,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 ""

Expand Down
95 changes: 67 additions & 28 deletions tests/legacy_api/test_pane.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,61 @@
import shutil
import typing as t

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 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_resize_pane(session: Session) -> None:
"""Test Pane.resize_pane()."""
window = session.attached_window
window.rename_window("test_resize_pane")
"""Verify Pane.resize_pane()."""
window = setup_shell_window(session, "test_resize_pane")
pane = window.active_pane
assert pane is not None

pane1 = window.attached_pane
assert pane1 is not None
Expand All @@ -32,15 +77,24 @@ def test_resize_pane(session: Session) -> None:

def test_send_keys(session: Session) -> None:
"""Verify Pane.send_keys()."""
pane = session.attached_window.attached_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
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

pane.send_keys("c-a", literal=False)
assert "c-a" not in pane_contents, "should not print to pane"
retry_until(wait_for_echo, 2, raises=True)


def test_set_height(session: Session) -> None:
Expand Down Expand Up @@ -75,24 +129,9 @@ 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."

session.new_window(
attach=True,
window_name="capture_pane",
window_shell=f"{env} PS1='$ ' sh",
)
pane = session.attached_window.attached_pane
window = setup_shell_window(session, "test_capture_pane")
pane = window.active_pane
assert pane is not None

pane_contents = "\n".join(pane.capture_pane())
assert pane_contents == "$"
pane.send_keys(
r'printf "\n%s\n" "Hello World !"',
literal=True,
suppress_history=False,
)
pane_contents = "\n".join(pane.capture_pane())
assert pane_contents == r'$ printf "\n%s\n" "Hello World !"{}'.format(
"\n\nHello World !\n$",
)
assert "READY>" in pane_contents
67 changes: 57 additions & 10 deletions tests/legacy_api/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
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:
Expand Down Expand Up @@ -264,6 +265,47 @@ def test_cmd_inserts_session_id(session: Session) -> None:
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",
Expand All @@ -280,20 +322,25 @@ 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.attached_pane
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
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(
Expand Down
76 changes: 67 additions & 9 deletions tests/legacy_api/test_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import logging
import shutil
import time
import typing as t

import pytest
Expand All @@ -13,6 +12,7 @@
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:
Expand Down Expand Up @@ -389,6 +389,47 @@ def test_empty_window_name(session: Session) -> None:
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",
Expand All @@ -406,19 +447,36 @@ def test_split_window_with_environment(
) -> None:
"""Verify splitting window with environment variables."""
env = shutil.which("env")
assert env is not None, "Cannot find usable `env` in Path."
assert env is not None, "Cannot find usable `env` in PATH."

window = session.new_window(window_name="split_window_with_environment")
pane = window.split_window(
shell=f"{env} PS1='$ ' sh",
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 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)


@pytest.mark.skipif(
Expand Down
Loading