10000 Docs: Tested doc examples by tony · Pull Request #581 · tmux-python/libtmux · GitHub
[go: up one dir, main page]

Skip to content

Docs: Tested doc examples #581

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

Draft
wants to merge 39 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
2d40e6c
docs(test-helpers) Update to existing modules
tony Feb 27, 2025
e8b5d7a
docs(test-helpers) Add retry page
tony Feb 27, 2025
76326f3
py(deps[dev]) Bump dev packages
tony Feb 27, 2025
1668b45
chore: Add `__init__.py` for tests/examples
tony Feb 27, 2025
fc3dec2
chore: Add `__init__.py` for tests/examples/test
tony Feb 26, 2025
a438f2d
chore: Add `__init__.py` for tests/examples/_internal/waiter
tony Feb 27, 2025
aa260f2
fix(retry): Improve retry_until_extended function with better error m…
tony Feb 26, 2025
10a37e5
feat(waiter): Enhance terminal content waiting utility with fluent AP…
tony Feb 26, 2025
0c74d33
test(waiter): Fix test cases and improve type safety
tony Feb 26, 2025
a10493a
docs(waiter): Add comprehensive documentation for terminal content wa…
tony Feb 26, 2025
6bdbb5c
pyproject(mypy[exceptions]): examples to ignore `no-untyped-def`
tony Feb 26, 2025
d436d75
test: add conftest.py to register example marker
tony Feb 26, 2025
17d2967
refactor(tests[waiter]): Add waiter test examples into individual files
tony Feb 26, 2025
50081b5
docs(CHANGES) Note `Waiter`
tony Feb 27, 2025
db47652
feat(waiter): Add terminal content waiting utility for testing (#582)
tony Feb 27, 2025
9661039
cursor(rules[git-commits]) Use component name first
tony Feb 28, 2025
677aa96
cursor(rules[git-commits]) Standardize further
tony Feb 28, 2025
60d1386
cursor(rules[dev-loop]) Use `--show-fixes` in `ruff check`
tony Feb 28, 2025
9695bdf
tests(test_waiter[capture_pane]): Add resiliency for CI test grid
tony Feb 28, 2025
cf08043
tests(test_waiter[exact_match]): Skip flaky exact match test on tmux …
tony Feb 28, 2025
7db6426
test(waiter): Replace assertions with warning-based checks in detaile…
tony Feb 28, 2025
463b9c3
pyproject(mypy[test.examples.pytest_plugin]): pytest examples to igno…
tony Feb 26, 2025
f768e53
docs: Improve window.py
tony Feb 1, 2025
9020e14
docs: Improve session.py
tony Feb 1, 2025
5a41553
docs: Improve server.py
tony Feb 1, 2025
ca20738
docs: Improve pane.py
tony Feb 1, 2025
1ccd708
docs: Improve neo.py
tony Feb 1, 2025
2c1182a
docs: Improve test.py
tony Feb 1, 2025
431985e
docs: Improve common.py
tony Feb 1, 2025
002df05
docs: Improve exc.py
tony Feb 1, 2025
355d0bf
docs: Improve pytest_plugin.py
tony Feb 1, 2025
238d6d2
docs: Improve conftest.py
tony Feb 1, 2025
626f024
docs/tests: Add `server` doctest examples
tony Feb 15, 2025
38182be
fix(Server): update Server.windows doctest to be environment-agnostic
tony Feb 26, 2025
73b4a2c
docs: Add new Topics
tony Feb 26, 2025
0add0be
docs(topics): Improve documentation with executable examples
tony Feb 26, 2025
2248328
docs(README): Enhance project README with comprehensive overview
tony Feb 26, 2025
2a2b6ff
docs,tests(pytest plugin) Examples
tony Feb 26, 2025
14c4b27
!squash docs topics
tony Feb 28, 2025
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
Prev Previous commit
Next Next commit
refactor(tests[waiter]): Add waiter test examples into individual files
- Each test file 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.
  • Loading branch information
tony committed Feb 27, 2025
commit 17d2967a15c792ec1bd4891f4d14a900ab0bfffd
40 changes: 40 additions & 0 deletions tests/examples/_internal/waiter/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Pytest configuration for waiter examples."""

from __future__ import annotations

import contextlib
from typing import TYPE_CHECKING

import pytest

from libtmux import Server

if TYPE_CHECKING:
from collections.abc import Generator

from libtmux.session import Session


@pytest.fixture
def session() -> Generator[Session, None, None]:
"""Provide a tmux session for tests.

This fixture creates a new session specifically for the waiter examples,
and ensures it's properly cleaned up after the test.
"""
server = Server()
session_name = "waiter_example_tests"

# Clean up any existing session with this name
with contextlib.suppress(Exception):
# Instead of using deprecated methods, use more direct approach
server.cmd("kill-session", "-t", session_name)

# Create a new session
session = server.new_session(session_name=session_name)

yield session

# Clean up
with contextlib.suppress(Exception):
session.kill()
55 changes: 55 additions & 0 deletions tests/examples/_internal/waiter/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""Helper utilities for waiter tests."""

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from libtmux.pane import Pane
from libtmux.window import Window


def ensure_pane(pane: Pane | None) -> Pane:
"""Ensure that a pane is not None.

This helper is needed for type safety in the examples.

Args:
pane: The pane to check

Returns
-------
The pane if it's not None

Raises
------
ValueError: If the pane is None
"""
if pane is None:
msg = "Pane cannot be None"
raise ValueError(msg)
return pane


def send_keys(pane: Pane | None, keys: str) -> None:
"""Send keys to a pane after ensuring it's not None.

Args:
pane: The pane to send keys to
keys: The keys to send

Raises
------
ValueError: If the pane is None
"""
ensure_pane(pane).send_keys(keys)


def kill_window_safely(window: Window | None) -> None:
"""Kill a window if it's not None.

Args:
window: The window to kill
"""
if window is not None:
window.kill()
40 changes: 40 additions & 0 deletions tests/examples/_internal/waiter/test_custom_predicate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Example of using a custom predicate function for matching."""

from __future__ import annotations

from typing import TYPE_CHECKING

import pytest

from libtmux._internal.waiter import ContentMatchType, wait_for_pane_content

if TYPE_CHECKING:
from libtmux.session import Session


@pytest.mark.example
def test_custom_predicate(session: Session) -> None:
"""Demonstrate using a custom predicate function for matching."""
window = session.new_window(window_name="test_custom_predicate")
pane = window.active_pane
assert pane is not None

# Send multiple lines of output
pane.send_keys("echo 'line 1'")
pane.send_keys("echo 'line 2'")
pane.send_keys("echo 'line 3'")

# Define a custom predicate function
def check_content(lines):
return len(lines) >= 3 and "error" not in "".join(lines).lower()

# Use the custom predicate
result = wait_for_pane_content(
pane,
check_content,
match_type=ContentMatchType.PREDICATE,
)
assert result.success

# Cleanup
window.kill()
30 changes: 30 additions & 0 deletions tests/examples/_internal/waiter/test_fluent_basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Example of using the fluent API in libtmux waiters."""

from __future__ import annotations

from typing import TYPE_CHECKING

import pytest

from libtmux._internal.waiter import expect

if TYPE_CHECKING:
from libtmux.session import Session


@pytest.mark.example
def test_fluent_basic(session: Session) -> None:
"""Demonstrate basic usage of the fluent API."""
window = session.new_window(window_name="test_fluent_basic")
pane = window.active_pane
assert pane is not None

# Send a command
pane.send_keys("echo 'hello world'")

# Basic usage of the fluent API
result = expect(pane).wait_for_text("hello world")
assert result.success

# Cleanup
window.kill()
36 changes: 36 additions & 0 deletions tests/examples/_internal/waiter/test_fluent_chaining.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Example of method chaining with the fluent API in libtmux waiters."""

from __future__ import annotations

from typing import TYPE_CHECKING

import pytest

from libtmux._internal.waiter import expect

if TYPE_CHECKING:
from libtmux.session import Session


@pytest.mark.example
def test_fluent_chaining(session: Session) -> None:
"""Demonstrate method chaining wi A3D4 th the fluent API."""
window = session.new_window(window_name="test_fluent_chaining")
pane = window.active_pane
assert pane is not None

# Send a command
pane.send_keys("echo 'completed successfully'")

# With method chaining
result = (
expect(pane)
.with_timeout(5.0)
.with_interval(0.1)
.without_raising()
.wait_for_text("completed successfully")
)
assert result.success

# Cleanup
window.kill()
44 changes: 44 additions & 0 deletions tests/examples/_internal/waiter/test_mixed_pattern_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Example of using different pattern types and match types."""

from __future__ import annotations

import re
from typing import TYPE_CHECKING

import pytest

from libtmux._internal.waiter import ContentMatchType, wait_for_any_content

if TYPE_CHECKING:
from libtmux.session import Session


@pytest.mark.example
def test_mixed_pattern_types(session: Session) -> None:
"""Demonstrate using different pattern types and match types."""
window = session.new_window(window_name="test_mixed_patterns")
pane = window.active_pane
assert pane is not None

# Send commands that will match different patterns
pane.send_keys("echo 'exact match'")
pane.send_keys("echo '10 items found'")

# Create a predicate function
def has_enough_lines(lines):
return len(lines) >= 2

# Wait for any of these patterns with different match types
result = wait_for_any_content(
pane,
[
"exact match", # String for exact match
re.compile(r"\d+ items found"), # Regex pattern
has_enough_lines, # Predicate function
],
[ContentMatchType.EXACT, ContentMatchType.REGEX, ContentMatchType.PREDICATE],
)
assert result.success

# Cleanup
window.kill()
40 changes: 40 additions & 0 deletions tests/examples/_internal/waiter/test_timeout_handling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Example of timeout handling with libtmux waiters."""

from __future__ import annotations

from typing import TYPE_CHECKING

import pytest

from libtmux._internal.waiter import wait_for_pane_content

if TYPE_CHECKING:
from libtmux.session import Session


@pytest.mark.example
def test_timeout_handling(session: Session) -> None:
"""Demonstrate handling timeouts gracefully without exceptions."""
window = session.new_window(window_name="test_timeout")
pane = window.active_pane
assert pane is not None

# Clear the pane
pane.send_keys("clear")

# Handle timeouts gracefully without exceptions
# Looking for content that won't appear (with a short timeout)
result = wait_for_pane_content(
pane,
"this text will not appear",
timeout=0.5,
raises=False,
)

# Should not raise an exception
assert not result.success
assert result.error is not None
assert "Timed out" in result.error

# Cleanup
window.kill()
41 changes: 41 additions & 0 deletions tests/examples/_internal/waiter/test_wait_for_all_content.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Example of waiting for all conditions to be met."""

from __future__ import annotations

from typing import TYPE_CHECKING, cast

import pytest

from libtmux._internal.waiter import ContentMatchType, wait_for_all_content

if TYPE_CHECKING:
from libtmux.session import Session


@pytest.mark.example
def test_wait_for_all_content(session: Session) -> None:
"""Demonstrate waiting for all conditions to be met."""
window = session.new_window(window_name="test_all_content")
pane = window.active_pane
assert pane is not None

# Send commands with both required phrases
pane.send_keys("echo 'Database connected'")
pane.send_keys("echo 'Server started'")

# Wait for all conditions to be true
result = wait_for_all_content(
pane,
["Database connected", "Server started"],
ContentMatchType.CONTAINS,
)
assert result.success
# For wait_for_all_content, the matched_content will be a list of matched patterns
assert result.matched_content is not None
matched_content = cast("list[str]", result.matched_content)
assert len(matched_content) == 2
assert "Database connected" in matched_content
assert "Server started" in matched_content

# Cleanup
window.kill()
36 changes: 36 additions & 0 deletions tests/examples/_internal/waiter/test_wait_for_any_content.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Example of waiting for any of multiple conditions."""

from __future__ import annotations

from typing import TYPE_CHECKING

import pytest

from libtmux._internal.waiter import ContentMatchType, wait_for_any_content

if TYPE_CHECKING:
from libtmux.session import Session


@pytest.mark.example
def test_wait_for_any_content(session: Session) -> None:
"""Demonstrate waiting for any of multiple conditions."""
window = session.new_window(window_name="test_any_content")
pane = window.active_pane
assert pane is not None

# Send a command
pane.send_keys("echo 'Success'")

# Wait for any of these patterns
result = wait_for_any_content(
pane,
["Success", "Error:", "timeout"],
ContentMatchType.CONTAINS,
)
assert result.success
assert result.matched_content == "Success"
assert result.matched_pattern_index == 0

# Cleanup
window.kill()
Loading
0