Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit cb737f9

Browse filesBrowse files
committed
!squash docs and doctest
1 parent 48327d3 commit cb737f9
Copy full SHA for cb737f9

File tree

Expand file treeCollapse file tree

1 file changed

+287
-8
lines changed
Filter options
Expand file treeCollapse file tree

1 file changed

+287
-8
lines changed

‎src/libtmux/test/waiter.py

Copy file name to clipboardExpand all lines: src/libtmux/test/waiter.py
+287-8Lines changed: 287 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,39 @@
22
33
This module provides utilities for waiting on tmux pane content in tests.
44
Inspired by Playwright's sync API for waiting on page content.
5+
6+
The main class is :class:`PaneWaiter` which provides methods to wait for specific
7+
content to appear in a tmux pane. This is particularly useful for testing shell
8+
commands and their output.
9+
10+
Examples
11+
--------
12+
>>> from libtmux.test.waiter import PaneWaiter
13+
>>> # Create a new window and get its pane
14+
>>> window = session.new_window(window_name="test_waiter")
15+
>>> pane = window.active_pane
16+
>>> # Create a waiter for the pane
17+
>>> waiter = PaneWaiter(pane)
18+
>>> # Wait for a specific prompt
19+
>>> result = waiter.wait_for_prompt("$ ")
20+
>>> result.success
21+
True
22+
>>> # Send a command and wait for its output
23+
>>> pane.send_keys("echo 'Hello World'")
24+
>>> result = waiter.wait_for_text("Hello World")
25+
>>> result.success
26+
True
27+
>>> "Hello World" in result.value
28+
True
29+
30+
The waiter also handles timeouts and errors gracefully:
31+
32+
>>> # Wait for text that won't appear (times out)
33+
>>> result = waiter.wait_for_text("this won't appear", timeout_seconds=0.1)
34+
>>> result.success
35+
False
36+
>>> isinstance(result.error, WaiterTimeoutError)
37+
True
538
"""
639

740
from __future__ import annotations
@@ -23,28 +56,140 @@
2356

2457

2558
class WaiterError(LibTmuxException):
26-
"""Base exception for waiter errors."""
59+
"""Base exception for waiter errors.
60+
61+
This is the parent class for all waiter-specific exceptions.
62+
"""
2763

2864

2965
class WaiterTimeoutError(WaiterError):
30-
"""Exception raised when waiting for content times out."""
66+
"""Exception raised when waiting for content times out.
67+
68+
This exception is raised when the content being waited for does not appear
69+
within the specified timeout period.
70+
71+
Examples
72+
--------
73+
>>> waiter = PaneWaiter(pane, timeout=0.1) # Short timeout
74+
>>> result = waiter.wait_for_text("won't appear")
75+
>>> isinstance(result.error, WaiterTimeoutError)
76+
True
77+
>>> str(result.error)
78+
"Text 'won't appear' not found in pane"
79+
"""
3180

3281

3382
class WaiterContentError(WaiterError):
34-
"""Exception raised when there's an error getting or checking content."""
83+
r"""Exception raised when there's an error getting or checking content.
84+
85+
This exception is raised when there's an error accessing or reading the
86+
pane content, for example if the pane is no longer available.
87+
88+
Examples
89+
--------
90+
>>> # Example of handling content errors
91+
>>> try:
92+
... content = "\\n".join(pane.capture_pane())
93+
... except Exception as e:
94+
... error = WaiterContentError("Error capturing pane content")
95+
... error.__cause__ = e
96+
... raise error from e
97+
"""
3598

3699

37100
@dataclass
38101
class WaitResult(t.Generic[T]):
39-
"""Result of a wait operation."""
102+
"""Result of a wait operation.
103+
104+
This class encapsulates the result of a wait operation, including whether it
105+
succeeded, the value found (if any), and any error that occurred.
106+
107+
Parameters
108+
----------
109+
success : bool
110+
Whether the wait operation succeeded
111+
value : T | None
112+
The value found, if any
113+
error : Exception | None
114+
The error that occurred, if any
115+
116+
Examples
117+
--------
118+
>>> # Successful wait result
119+
>>> result = WaitResult[str](success=True, value="found content")
120+
>>> result.success
121+
True
122+
>>> result.value
123+
'found content'
124+
>>> result.error is None
125+
True
126+
127+
>>> # Failed wait result with error
128+
>>> error = WaiterTimeoutError("Timed out")
129+
>>> result = WaitResult[str](success=False, error=error)
130+
>>> result.success
131+
False
132+
>>> result.value is None
133+
True
134+
>>> isinstance(result.error, WaiterTimeoutError)
135+
True
136+
"""
40137

41138
success: bool
42139
value: T | None = None
43140
error: Exception | None = None
44141

45142

46143
class PaneWaiter:
47-
"""Utility class for waiting on tmux pane content."""
144+
"""Utility class for waiting on tmux pane content.
145+
146+
This class provides methods to wait for specific content to appear in a tmux pane.
147+
It supports waiting for exact text matches, prompts, and custom predicates.
148+
149+
Parameters
150+
----------
151+
pane : Pane
152+
The tmux pane to wait on
153+
timeout : float, optional
154+
Default timeout in seconds, by default 2.0
155+
156+
Examples
157+
--------
158+
Basic usage with text:
159+
160+
>>> waiter = PaneWaiter(pane)
161+
>>> pane.send_keys("echo 'test'")
162+
>>> result = waiter.wait_for_text("test")
163+
>>> result.success
164+
True
165+
>>> "test" in result.value
166+
True
167+
168+
Waiting for a prompt:
169+
170+
>>> waiter = PaneWaiter(pane)
171+
>>> result = waiter.wait_for_prompt("$ ")
172+
>>> result.success
173+
True
174+
>>> "$ " in result.value
175+
True
176+
177+
Custom predicate:
178+
179+
>>> waiter = PaneWaiter(pane)
180+
>>> result = waiter.wait_for_content(lambda content: "error" not in content.lower())
181+
>>> result.success
182+
True
183+
184+
Handling timeouts:
185+
186+
>>> waiter = PaneWaiter(pane, timeout=0.1) # Short timeout
187+
>>> result = waiter.wait_for_text("won't appear")
188+
>>> result.success
189+
False
190+
>>> isinstance(result.error, WaiterTimeoutError)
191+
True
192+
"""
48193

49194
def __init__(self, pane: Pane, timeout: float = 2.0) -> None:
50195
"""Initialize PaneWaiter.
@@ -66,6 +211,9 @@ def _check_content(
66211
) -> bool:
67212
"""Check pane content against predicate.
68213
214+
This internal method captures the pane content and checks it against
215+
the provided predicate function.
216+
69217
Parameters
70218
----------
71219
predicate : Callable[[str], bool]
@@ -82,6 +230,16 @@ def _check_content(
82230
------
83231
WaiterContentError
84232
If there's an error capturing pane content
233+
234+
Examples
235+
--------
236+
>>> waiter = PaneWaiter(pane)
237+
>>> result = WaitResult[str](success=False)
238+
>>> success = waiter._check_content(lambda c: "test" in c, result)
239+
>>> success # True if "test" is found in pane content
240+
True
241+
>>> result.value is not None
242+
True
85243
"""
86244
try:
87245
content = "\n".join(self.pane.capture_pane())
@@ -101,7 +259,56 @@ def wait_for_content(
101259
interval_seconds: float | None = None,
102260
error_message: str | None = None,
103261
) -> WaitResult[str]:
104-
"""Wait for content in the pane to match a predicate."""
262+
"""Wait for content in the pane to match a predicate.
263+
264+
This is the core waiting method that other methods build upon. It repeatedly
265+
checks the pane content against a predicate function until it returns True
266+
or times out.
267+
268+
Parameters
269+
----------
270+
predicate : Callable[[str], bool]
271+
Function that takes pane content as string and returns bool
272+
timeout_seconds : float | None, optional
273+
Maximum time to wait in seconds, by default None (uses instance timeout)
274+
interval_seconds : float | None, optional
275+
Time between checks in seconds, by default None (uses 0.05)
276+
error_message : str | None, optional
277+
Custom error message for timeout, by default None
278+
279+
Returns
280+
-------
281+
WaitResult[str]
282+
Result of the wait operation
283+
284+
Examples
285+
--------
286+
>>> waiter = PaneWaiter(pane)
287+
>>> # Wait for content containing "success" but not "error"
288+
>>> result = waiter.wait_for_content(
289+
... lambda content: "success" in content and "error" not in content
290+
... )
291+
>>> result.success
292+
True
293+
294+
>>> # Wait with custom timeout and interval
295+
>>> result = waiter.wait_for_content(
296+
... lambda content: "test" in content,
297+
... timeout_seconds=5.0,
298+
... interval_seconds=0.1,
299+
... )
300+
>>> result.success
301+
True
302+
303+
>>> # Wait with custom error message
304+
>>> result = waiter.wait_for_content(
305+
... lambda content: False, # Never succeeds
306+
... timeout_seconds=0.1,
307+
... error_message="Custom timeout message",
308+
... )
309+
>>> str(result.error)
310+
'Custom timeout message'
311+
"""
105312
result = WaitResult[str](success=False, value=None, error=None)
106313
try:
107314
# Give the shell a moment to be ready
@@ -134,7 +341,40 @@ def wait_for_prompt(
134341
timeout_seconds: float | None = None,
135342
error_message: str | None = None,
136343
) -> WaitResult[str]:
137-
"""Wait for a specific prompt to appear in the pane."""
344+
"""Wait for a specific prompt to appear in the pane.
345+
346+
This method waits for a specific shell prompt to appear in the pane.
347+
It ensures the prompt is at the end of non-empty content.
348+
349+
Parameters
350+
----------
351+
prompt : str
352+
The prompt text to wait for
353+
timeout_seconds : float | None, optional
354+
Maximum time to wait in seconds, by default None (uses instance timeout)
355+
error_message : str | None, optional
356+
Custom error message for timeout, by default None
357+
358+
Returns
359+
-------
360+
WaitResult[str]
361+
Result of the wait operation
362+
363+
Examples
364+
--------
365+
>>> waiter = PaneWaiter(pane)
366+
>>> # Wait for bash prompt
367+
>>> result = waiter.wait_for_prompt("$ ")
368+
>>> result.success
369+
True
370+
>>> "$ " in result.value
371+
True
372+
373+
>>> # Wait for custom prompt
374+
>>> result = waiter.wait_for_prompt("my_prompt> ")
375+
>>> result.success
376+
True
377+
"""
138378
return self.wait_for_content(
139379
lambda content: prompt in content and len(content.strip()) > 0,
140380
timeout_seconds=timeout_seconds,
@@ -148,7 +388,46 @@ def wait_for_text(
148388
interval_seconds: float | None = None,
149389
error_message: str | None = None,
150390
) -> WaitResult[str]:
151-
"""Wait for text to appear in the pane."""
391+
"""Wait for text to appear in the pane.
392+
393+
This method waits for specific text to appear anywhere in the pane content.
394+
395+
Parameters
396+
----------
397+
text : str
398+
The text to wait for
399+
timeout_seconds : float | None, optional
400+
Maximum time to wait in seconds, by default None (uses instance timeout)
401+
interval_seconds : float | None, optional
402+
Time between checks in seconds, by default None (uses 0.05)
403+
error_message : str | None, optional
404+
Custom error message for timeout, by default None
405+
406+
Returns
407+
-------
408+
WaitResult[str]
409+
Result of the wait operation
410+
411+
Examples
412+
--------
413+
>>> waiter = PaneWaiter(pane)
414+
>>> # Send a command and wait for its output
415+
>>> pane.send_keys("echo 'Hello World'")
416+
>>> result = waiter.wait_for_text("Hello World")
417+
>>> result.success
418+
True
419+
>>> "Hello World" in result.value
420+
True
421+
422+
>>> # Wait with custom timeout
423+
>>> result = waiter.wait_for_text(
424+
... "test output",
425+
... timeout_seconds=5.0,
426+
... error_message="Failed to find test output",
427+
... )
428+
>>> result.success
429+
True
430+
"""
152431
if error_message is None:
153432
error_message = f"Text '{text}' not found in pane"
154433
return self.wait_for_content(

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.