-
Notifications
You must be signed in to change notification settings - Fork 112
Waiter 0.1 #594
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
Open
tony
wants to merge
16
commits into
master
Choose a base branch
from
waiter-0.1
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Waiter 0.1 #594
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
c4b04c2
chore: Add `__init__.py` for tests/examples/_internal/waiter
tony 176aa61
fix(retry): Improve retry_until_extended function with better error m…
tony 3ef9ea7
feat(waiter): Enhance terminal content waiting utility with fluent AP…
tony 42cdb5d
test(waiter): Fix test cases and improve type safety
tony 9717152
docs(waiter): Add comprehensive documentation for terminal content wa…
tony e6c57da
test: add conftest.py to register example marker
tony c8b9983
pyproject(mypy[exceptions]): examples to ignore `no-untyped-def`
tony 8db0ac9
refactor(tests[waiter]): Add waiter test examples into individual files
tony 7d727b6
docs(CHANGES) Note `Waiter`
tony 271937f
fix(tests): add delay after send_keys in waiter test
tony dc1341c
fix(tests): use wait_until_pane_ready in waiter test
tony e29d4b6
fix(tests): use echo marker instead of prompt detection in waiter test
tony 812c164
tests(internal): add direct unit tests for retry_extended
tony 652cf3a
fix(tests): skip flaky waiter test on tmux 3.4
tony 6073bea
fix(docs): correct retry_extended module path in waiter docs
tony 3b57fc9
chore: Add `__init__.py` for tests/examples
tony File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| (waiter)= | ||
|
|
||
| # Waiters - `libtmux._internal.waiter` | ||
|
|
||
| The waiter module provides utilities for waiting on specific content to appear in tmux panes, making it easier to write reliable tests that interact with terminal output. | ||
|
|
||
| ## Key Features | ||
|
|
||
| - **Fluent API**: Playwright-inspired chainable API for expressive, readable test code | ||
| - **Multiple Match Types**: Wait for exact matches, substring matches, regex patterns, or custom predicate functions | ||
| - **Composable Waiting**: Wait for any of multiple conditions or all conditions to be met | ||
| - **Flexible Timeout Handling**: Configure timeout behavior and error handling to suit your needs | ||
| - **Shell Prompt Detection**: Easily wait for shell readiness with built-in prompt detection | ||
| - **Robust Error Handling**: Improved exception handling and result reporting | ||
| - **Clean Code**: Well-formatted, linted code with proper type annotations | ||
|
|
||
| ## Basic Concepts | ||
|
|
||
| When writing tests that interact with tmux sessions and panes, it's often necessary to wait for specific content to appear before proceeding with the next step. The waiter module provides a set of functions to help with this. | ||
|
|
||
| There are multiple ways to match content: | ||
| - **Exact match**: The content exactly matches the specified string | ||
| - **Contains**: The content contains the specified string | ||
| - **Regex**: The content matches the specified regular expression | ||
| - **Predicate**: A custom function that takes the pane content and returns a boolean | ||
|
|
||
| ## Quick Start Examples | ||
|
|
||
| ### Simple Waiting | ||
|
|
||
| Wait for specific text to appear in a pane: | ||
|
|
||
| ```{literalinclude} ../../tests/examples/_internal/waiter/test_wait_for_text.py | ||
| :language: python | ||
| ``` | ||
|
|
||
| ### Advanced Matching | ||
|
|
||
| Use regex patterns or custom predicates for more complex matching: | ||
|
|
||
| ```{literalinclude} ../../tests/examples/_internal/waiter/test_wait_for_regex.py | ||
| :language: python | ||
| ``` | ||
|
|
||
| ```{literalinclude} ../../tests/examples/_internal/waiter/test_custom_predicate.py | ||
| :language: python | ||
| ``` | ||
|
|
||
| ### Timeout Handling | ||
|
|
||
| Control how long to wait and what happens when a timeout occurs: | ||
|
|
||
| ```{literalinclude} ../../tests/examples/_internal/waiter/test_timeout_handling.py | ||
| :language: python | ||
| ``` | ||
|
|
||
| ### Waiting for Shell Readiness | ||
|
|
||
| A common use case is waiting for a shell prompt to appear, indicating the command has completed. The example below uses a regular expression to match common shell prompt characters (`$`, `%`, `>`, `#`): | ||
|
|
||
| ```{literalinclude} ../../tests/examples/_internal/waiter/test_wait_until_ready.py | ||
| :language: python | ||
| ``` | ||
|
|
||
| > Note: This test is skipped in CI environments due to timing issues but works well for local development. | ||
|
|
||
| ## Fluent API (Playwright-inspired) | ||
|
|
||
| For a more expressive and chainable API, you can use the fluent interface provided by the `PaneContentWaiter` class: | ||
|
|
||
| ```{literalinclude} ../../tests/examples/_internal/waiter/test_fluent_basic.py | ||
| :language: python | ||
| ``` | ||
|
|
||
| ```{literalinclude} ../../tests/examples/_internal/waiter/test_fluent_chaining.py | ||
| :language: python | ||
| ``` | ||
|
|
||
| ## Multiple Conditions | ||
|
|
||
| The waiter module also supports waiting for multiple conditions at once: | ||
|
|
||
| ```{literalinclude} ../../tests/examples/_internal/waiter/test_wait_for_any_content.py | ||
| :language: python | ||
| ``` | ||
|
|
||
| ```{literalinclude} ../../tests/examples/_internal/waiter/test_wait_for_all_content.py | ||
| :language: python | ||
| ``` | ||
|
|
||
| ```{literalinclude} ../../tests/examples/_internal/waiter/test_mixed_pattern_types.py | ||
| :language: python | ||
| ``` | ||
|
|
||
| ## Implementation Notes | ||
|
|
||
| ### Error Handling | ||
|
|
||
| The waiting functions are designed to be robust and handle timing and error conditions gracefully: | ||
|
|
||
| - All wait functions properly calculate elapsed time for performance tracking | ||
| - Functions handle exceptions consistently and provide clear error messages | ||
| - Proper handling of return values ensures consistent behavior whether or not raises=True | ||
|
|
||
| ### Type Safety | ||
|
|
||
| The waiter module is fully type-annotated to ensure compatibility with static type checkers: | ||
|
|
||
| - All functions include proper type hints for parameters and return values | ||
| - The ContentMatchType enum ensures that only valid match types are used | ||
| - Combined with runtime checks, this prevents type-related errors during testing | ||
|
|
||
| ### Example Usage in Documentation | ||
|
|
||
| All examples in this documentation are actual test files from the libtmux test suite. The examples are included using `literalinclude` directives, ensuring that the documentation remains synchronized with the actual code. | ||
|
|
||
| ## API Reference | ||
|
|
||
| ```{eval-rst} | ||
| .. automodule:: libtmux._internal.waiter | ||
| :members: | ||
| :undoc-members: | ||
| :show-inheritance: | ||
| :member-order: bysource | ||
| ``` | ||
|
|
||
| ## Extended Retry Functionality | ||
|
|
||
| ```{eval-rst} | ||
| .. automodule:: libtmux._internal.retry_extended | ||
| :members: | ||
| :undoc-members: | ||
| :show-inheritance: | ||
| :member-order: bysource | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| """Extended retry functionality for libtmux.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import logging | ||
| import time | ||
| import typing as t | ||
|
|
||
| from libtmux.exc import WaitTimeout | ||
| from libtmux.test.constants import ( | ||
| RETRY_INTERVAL_SECONDS, | ||
| RETRY_TIMEOUT_SECONDS, | ||
| ) | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| if t.TYPE_CHECKING: | ||
| from collections.abc import Callable | ||
|
|
||
|
|
||
| def retry_until_extended( | ||
| fun: Callable[[], bool], | ||
| seconds: float = RETRY_TIMEOUT_SECONDS, | ||
| *, | ||
| interval: float = RETRY_INTERVAL_SECONDS, | ||
| raises: bool | None = True, | ||
| ) -> tuple[bool, Exception | None]: | ||
| """ | ||
| Retry a function until a condition meets or the specified time passes. | ||
|
|
||
| Extended version that returns both success state and exception. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| fun : callable | ||
| A function that will be called repeatedly until it returns ``True`` or | ||
| the specified time passes. | ||
| seconds : float | ||
| Seconds to retry. Defaults to ``8``, which is configurable via | ||
| ``RETRY_TIMEOUT_SECONDS`` environment variables. | ||
| interval : float | ||
| Time in seconds to wait between calls. Defaults to ``0.05`` and is | ||
| configurable via ``RETRY_INTERVAL_SECONDS`` environment variable. | ||
| raises : bool | ||
| Whether or not to raise an exception on timeout. Defaults to ``True``. | ||
|
|
||
| Returns | ||
| ------- | ||
| tuple[bool, Exception | None] | ||
| Tuple containing (success, exception). If successful, the exception will | ||
| be None. | ||
| """ | ||
| ini = time.time() | ||
| exception = None | ||
|
|
||
| while not fun(): | ||
| end = time.time() | ||
| if end - ini >= seconds: | ||
| timeout_msg = f"Timed out after {seconds} seconds" | ||
| exception = WaitTimeout(timeout_msg) | ||
| if raises: | ||
| raise exception | ||
| return False, exception | ||
| time.sleep(interval) | ||
| return True, None | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Consider logging timeout exceptions for better diagnostics.
Inside the loop, when the timeout is reached, a WaitTimeout exception is created and possibly raised. Adding a debug log statement with the timeout details before raising could help trace issues during retries.
Suggested implementation:
Ensure that the above change is placed within the retry loop immediately before the WaitTimeout exception is raised. If your code constructs the exception differently or uses a different variable for the timeout details, make sure to adapt the logging message to include the relevant information.