Skip to content

Commit b106ef6

Browse files
committed
refactor(tests): Split waiter test examples into individual files
- Reorganized test_waiter.py into separate test files for better clarity and organization - Each test file now focuses on a single feature or concept of the waiter module - Added descriptive docstrings to all test functions for better documentation - Created conftest.py with session fixture for waiter examples - Added helpers.py with utility functions for the test examples - Test files now follow a consistent naming convention for easier reference - Each test file is self-contained and demonstrates a single concept - All tests are marked with @pytest.mark.example for filtering This restructuring supports the documentation update to use literalinclude directives, making the documentation more maintainable and ensuring it stays in sync with actual code.
1 parent b9bcd96 commit b106ef6

13 files changed

+482
-182
lines changed
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""Pytest configuration for waiter examples."""
2+
3+
from __future__ import annotations
4+
5+
import contextlib
6+
from typing import TYPE_CHECKING
7+
8+
import pytest
9+
10+
from libtmux import Server
11+
12+
if TYPE_CHECKING:
13+
from collections.abc import Generator
14+
15+
from libtmux.session import Session
16+
17+
18+
@pytest.fixture
19+
def session() -> Generator[Session, None, None]:
20+
"""Provide a tmux session for tests.
21+
22+
This fixture creates a new session specifically for the waiter examples,
23+
and ensures it's properly cleaned up after the test.
24+
"""
25+
server = Server()
26+
session_name = "waiter_example_tests"
27+
28+
# Clean up any existing session with this name
29+
with contextlib.suppress(Exception):
30+
# Instead of using deprecated methods, use more direct approach
31+
server.cmd("kill-session", "-t", session_name)
32+
33+
# Create a new session
34+
session = server.new_session(session_name=session_name)
35+
36+
yield session
37+
38+
# Clean up
39+
with contextlib.suppress(Exception):
40+
session.kill()

tests/examples/test/waiter/helpers.py

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""Helper utilities for waiter tests."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING
6+
7+
if TYPE_CHECKING:
8+
from libtmux.pane import Pane
9+
from libtmux.window import Window
10+
11+
12+
def ensure_pane(pane: Pane | None) -> Pane:
13+
"""Ensure that a pane is not None.
14+
15+
This helper is needed for type safety in the examples.
16+
17+
Args:
18+
pane: The pane to check
19+
20+
Returns
21+
-------
22+
The pane if it's not None
23+
24+
Raises
25+
------
26+
ValueError: If the pane is None
27+
"""
28+
if pane is None:
29+
msg = "Pane cannot be None"
30+
raise ValueError(msg)
31+
return pane
32+
33+
34+
def send_keys(pane: Pane | None, keys: str) -> None:
35+
"""Send keys to a pane after ensuring it's not None.
36+
37+
Args:
38+
pane: The pane to send keys to
39+
keys: The keys to send
40+
41+
Raises
42+
------
43+
ValueError: If the pane is None
44+
"""
45+
ensure_pane(pane).send_keys(keys)
46+
47+
48+
def kill_window_safely(window: Window | None) -> None:
49+
"""Kill a window if it's not None.
50+
51+
Args:
52+
window: The window to kill
53+
"""
54+
if window is not None:
55+
window.kill()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""Example of using a custom predicate function for matching."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING
6+
7+
import pytest
8+
9+
from libtmux._internal.waiter import ContentMatchType, wait_for_pane_content
10+
11+
if TYPE_CHECKING:
12+
from libtmux.session import Session
13+
14+
15+
@pytest.mark.example
16+
def test_custom_predicate(session: Session) -> None:
17+
"""Demonstrate using a custom predicate function for matching."""
18+
window = session.new_window(window_name="test_custom_predicate")
19+
pane = window.active_pane
20+
assert pane is not None
21+
22+
# Send multiple lines of output
23+
pane.send_keys("echo 'line 1'")
24+
pane.send_keys("echo 'line 2'")
25+
pane.send_keys("echo 'line 3'")
26+
27+
# Define a custom predicate function
28+
def check_content(lines):
29+
return len(lines) >= 3 and "error" not in "".join(lines).lower()
30+
31+
# Use the custom predicate
32+
result = wait_for_pane_content(
33+
pane,
34+
check_content,
35+
match_type=ContentMatchType.PREDICATE,
36+
)
37+
assert result.success
38+
39+
# Cleanup
40+
window.kill()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Example of using the fluent API in libtmux waiters."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING
6+
7+
import pytest
8+
9+
from libtmux._internal.waiter import expect
10+
11+
if TYPE_CHECKING:
12+
from libtmux.session import Session
13+
14+
15+
@pytest.mark.example
16+
def test_fluent_basic(session: Session) -> None:
17+
"""Demonstrate basic usage of the fluent API."""
18+
window = session.new_window(window_name="test_fluent_basic")
19+
pane = window.active_pane
20+
assert pane is not None
21+
22+
# Send a command
23+
pane.send_keys("echo 'hello world'")
24+
25+
# Basic usage of the fluent API
26+
result = expect(pane).wait_for_text("hello world")
27+
assert result.success
28+
29+
# Cleanup
30+
window.kill()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Example of method chaining with the fluent API in libtmux waiters."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING
6+
7+
import pytest
8+
9+
from libtmux._internal.waiter import expect
10+
11+
if TYPE_CHECKING:
12+
from libtmux.session import Session
13+
14+
15+
@pytest.mark.example
16+
def test_fluent_chaining(session: Session) -> None:
17+
"""Demonstrate method chaining with the fluent API."""
18+
window = session.new_window(window_name="test_fluent_chaining")
19+
pane = window.active_pane
20+
assert pane is not None
21+
22+
# Send a command
23+
pane.send_keys("echo 'completed successfully'")
24+
25+
# With method chaining
26+
result = (
27+
expect(pane)
28+
.with_timeout(5.0)
29+
.with_interval(0.1)
30+
.without_raising()
31+
.wait_for_text("completed successfully")
32+
)
33+
assert result.success
34+
35+
# Cleanup
36+
window.kill()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""Example of using different pattern types and match types."""
2+
3+
from __future__ import annotations
4+
5+
import re
6+
from typing import TYPE_CHECKING
7+
8+
import pytest
9+
10+
from libtmux._internal.waiter import ContentMatchType, wait_for_any_content
11+
12+
if TYPE_CHECKING:
13+
from libtmux.session import Session
14+
15+
16+
@pytest.mark.example
17+
def test_mixed_pattern_types(session: Session) -> None:
18+
"""Demonstrate using different pattern types and match types."""
19+
window = session.new_window(window_name="test_mixed_patterns")
20+
pane = window.active_pane
21+
assert pane is not None
22+
23+
# Send commands that will match different patterns
24+
pane.send_keys("echo 'exact match'")
25+
pane.send_keys("echo '10 items found'")
26+
27+
# Create a predicate function
28+
def has_enough_lines(lines):
29+
return len(lines) >= 2
30+
31+
# Wait for any of these patterns with different match types
32+
result = wait_for_any_content(
33+
pane,
34+
[
35+
"exact match", # String for exact match
36+
re.compile(r"\d+ items found"), # Regex pattern
37+
has_enough_lines, # Predicate function
38+
],
39+
[ContentMatchType.EXACT, ContentMatchType.REGEX, ContentMatchType.PREDICATE],
40+
)
41+
assert result.success
42+
43+
# Cleanup
44+
window.kill()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""Example of timeout handling with libtmux waiters."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING
6+
7+
import pytest
8+
9+
from libtmux._internal.waiter import wait_for_pane_content
10+
11+
if TYPE_CHECKING:
12+
from libtmux.session import Session
13+
14+
15+
@pytest.mark.example
16+
def test_timeout_handling(session: Session) -> None:
17+
"""Demonstrate handling timeouts gracefully without exceptions."""
18+
window = session.new_window(window_name="test_timeout")
19+
pane = window.active_pane
20+
assert pane is not None
21+
22+
# Clear the pane
23+
pane.send_keys("clear")
24+
25+
# Handle timeouts gracefully without exceptions
26+
# Looking for content that won't appear (with a short timeout)
27+
result = wait_for_pane_content(
28+
pane,
29+
"this text will not appear",
30+
timeout=0.5,
31+
raises=False,
32+
)
33+
34+
# Should not raise an exception
35+
assert not result.success
36+
assert result.error is not None
37+
assert "Timed out" in result.error
38+
39+
# Cleanup
40+
window.kill()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""Example of waiting for all conditions to be met."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING, cast
6+
7+
import pytest
8+
9+
from libtmux._internal.waiter import ContentMatchType, wait_for_all_content
10+
11+
if TYPE_CHECKING:
12+
from libtmux.session import Session
13+
14+
15+
@pytest.mark.example
16+
def test_wait_for_all_content(session: Session) -> None:
17+
"""Demonstrate waiting for all conditions to be met."""
18+
window = session.new_window(window_name="test_all_content")
19+
pane = window.active_pane
20+
assert pane is not None
21+
22+
# Send commands with both required phrases
23+
pane.send_keys("echo 'Database connected'")
24+
pane.send_keys("echo 'Server started'")
25+
26+
# Wait for all conditions to be true
27+
result = wait_for_all_content(
28+
pane,
29+
["Database connected", "Server started"],
30+
ContentMatchType.CONTAINS,
31+
)
32+
assert result.success
33+
# For wait_for_all_content, the matched_content will be a list of matched patterns
34+
assert result.matched_content is not None
35+
matched_content = cast("list[str]", result.matched_content)
36+
assert len(matched_content) == 2
37+
assert "Database connected" in matched_content
38+
assert "Server started" in matched_content
39+
40+
# Cleanup
41+
window.kill()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Example of waiting for any of multiple conditions."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING
6+
7+
import pytest
8+
9+
from libtmux._internal.waiter import ContentMatchType, wait_for_any_content
10+
11+
if TYPE_CHECKING:
12+
from libtmux.session import Session
13+
14+
15+
@pytest.mark.example
16+
def test_wait_for_any_content(session: Session) -> None:
17+
"""Demonstrate waiting for any of multiple conditions."""
18+
window = session.new_window(window_name="test_any_content")
19+
pane = window.active_pane
20+
assert pane is not None
21+
22+
# Send a command
23+
pane.send_keys("echo 'Success'")
24+
25+
# Wait for any of these patterns
26+
result = wait_for_any_content(
27+
pane,
28+
["Success", "Error:", "timeout"],
29+
ContentMatchType.CONTAINS,
30+
)
31+
assert result.success
32+
assert result.matched_content == "Success"
33+
assert result.matched_pattern_index == 0
34+
35+
# Cleanup
36+
window.kill()

0 commit comments

Comments
 (0)