Skip to content
New issue

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

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

Already on GitHub? # to your account

Tests: Improve shell interoperability #577

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
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
@@ -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 ""

95 changes: 67 additions & 28 deletions tests/legacy_api/test_pane.py
Original file line number Diff line number Diff line change
@@ -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
@@ -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:
@@ -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
@@ -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:
@@ -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",
@@ -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(
76 changes: 67 additions & 9 deletions tests/legacy_api/test_window.py
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@

import logging
import shutil
import time
import typing as t

import pytest
@@ -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:
@@ -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",
@@ -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(
Loading