Skip to content

Commit 2331849

Browse files
committed
fix: improve error handling in PaneWaiter
- Fix error propagation in wait_for_content to properly handle timeouts - Add proper type hints and fix mypy errors - Use custom exceptions instead of generic ones in tests - Fix code formatting and line length issues - Update test assertions to match actual error handling
1 parent 715a09a commit 2331849

File tree

2 files changed

+37
-28
lines changed

2 files changed

+37
-28
lines changed

src/libtmux/test/waiter.py

+17-16
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
TypeVar,
1414
)
1515

16-
from libtmux.exc import LibTmuxException
17-
from libtmux.test.retry import WaitTimeout, retry_until
16+
from libtmux.exc import LibTmuxException, WaitTimeout
17+
from libtmux.test.retry import retry_until
1818

1919
if t.TYPE_CHECKING:
2020
from libtmux.pane import Pane
@@ -62,7 +62,7 @@ def __init__(self, pane: Pane, timeout: float = 2.0) -> None:
6262
def _check_content(
6363
self,
6464
predicate: t.Callable[[str], bool],
65-
result: WaitResult,
65+
result: WaitResult[str],
6666
) -> bool:
6767
"""Check pane content against predicate.
6868
@@ -88,7 +88,8 @@ def _check_content(
8888
if predicate(content):
8989
result.value = content
9090
return True
91-
return False
91+
else:
92+
return False
9293
except Exception as e:
9394
error = WaiterContentError("Error capturing pane content")
9495
error.__cause__ = e
@@ -100,16 +101,16 @@ def wait_for_content(
100101
timeout_seconds: float | None = None,
101102
interval_seconds: float | None = None,
102103
error_message: str | None = None,
103-
) -> WaitResult:
104+
) -> WaitResult[str]:
104105
"""Wait for content in the pane to match a predicate."""
105-
result = WaitResult(success=False, value=None, error=None)
106+
result = WaitResult[str](success=False, value=None, error=None)
106107
try:
107108
# Give the shell a moment to be ready
108109
time.sleep(0.1)
109110
success = retry_until(
110111
lambda: self._check_content(predicate, result),
111112
seconds=timeout_seconds or self.timeout,
112-
interval=interval_seconds,
113+
interval=interval_seconds or 0.05,
113114
raises=True,
114115
)
115116
result.success = success
@@ -124,11 +125,7 @@ def wait_for_content(
124125
result.error = e
125126
result.success = False
126127
except Exception as e:
127-
if isinstance(e, (WaiterTimeoutError, WaiterContentError)):
128-
result.error = e
129-
else:
130-
result.error = WaiterContentError("Error capturing pane content")
131-
result.error.__cause__ = e
128+
result.error = WaiterTimeoutError(error_message or str(e))
132129
result.success = False
133130
return result
134131

@@ -137,7 +134,7 @@ def wait_for_prompt(
137134
prompt: str,
138135
timeout_seconds: float | None = None,
139136
error_message: str | None = None,
140-
) -> WaitResult:
137+
) -> WaitResult[str]:
141138
"""Wait for a specific prompt to appear in the pane."""
142139
return self.wait_for_content(
143140
lambda content: prompt in content and len(content.strip()) > 0,
@@ -149,11 +146,15 @@ def wait_for_text(
149146
self,
150147
text: str,
151148
timeout_seconds: float | None = None,
149+
interval_seconds: float | None = None,
152150
error_message: str | None = None,
153-
) -> WaitResult:
154-
"""Wait for specific text to appear in the pane."""
151+
) -> WaitResult[str]:
152+
"""Wait for text to appear in the pane."""
153+
if error_message is None:
154+
error_message = f"Text '{text}' not found in pane"
155155
return self.wait_for_content(
156156
lambda content: text in content,
157157
timeout_seconds=timeout_seconds,
158-
error_message=error_message or f"Text '{text}' not found in pane",
158+
interval_seconds=interval_seconds,
159+
error_message=error_message,
159160
)

tests/test/test_waiter.py

+20-12
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import shutil
66
import typing as t
77

8-
from libtmux.test.retry import WaitTimeout
8+
from libtmux.exc import WaitTimeout
99
from libtmux.test.waiter import (
1010
PaneWaiter,
1111
WaiterContentError,
@@ -153,7 +153,7 @@ def test_wait_for_content_inner_exception(
153153
def mock_capture_pane(*args: t.Any, **kwargs: t.Any) -> list[str]:
154154
"""Mock capture_pane that raises an exception."""
155155
msg = "Test error"
156-
raise Exception(msg)
156+
raise WaiterContentError(msg)
157157

158158
monkeypatch.setattr(pane, "capture_pane", mock_capture_pane)
159159
result = waiter.wait_for_text("some text")
@@ -162,7 +162,7 @@ def mock_capture_pane(*args: t.Any, **kwargs: t.Any) -> list[str]:
162162
assert result.error is not None
163163
assert isinstance(result.error, WaiterContentError)
164164
assert str(result.error) == "Error capturing pane content"
165-
assert isinstance(result.error.__cause__, Exception)
165+
assert isinstance(result.error.__cause__, WaiterContentError)
166166
assert str(result.error.__cause__) == "Test error"
167167

168168

@@ -205,7 +205,10 @@ def test_wait_for_content_outer_exception_no_custom_message(
205205
session: Session,
206206
monkeypatch: MonkeyPatch,
207207
) -> None:
208-
"""Test exception handling in wait_for_content's outer try-except without custom message."""
208+
"""Test exception handling in wait_for_content's outer try-except.
209+
210+
Tests behavior when no custom error message is provided.
211+
"""
209212
env = shutil.which("env")
210213
assert env is not None, "Cannot find usable `env` in PATH."
211214

@@ -222,7 +225,7 @@ def test_wait_for_content_outer_exception_no_custom_message(
222225
def mock_capture_pane(*args: t.Any, **kwargs: t.Any) -> list[str]:
223226
"""Mock capture_pane that raises an exception."""
224227
msg = "Test error"
225-
raise Exception(msg)
228+
raise WaiterContentError(msg)
226229

227230
monkeypatch.setattr(pane, "capture_pane", mock_capture_pane)
228231
result = waiter.wait_for_text("some text") # No custom error message
@@ -231,19 +234,24 @@ def mock_capture_pane(*args: t.Any, **kwargs: t.Any) -> list[str]:
231234
assert result.error is not None
232235
assert isinstance(result.error, WaiterContentError)
233236
assert str(result.error) == "Error capturing pane content"
234-
assert isinstance(result.error.__cause__, Exception)
237+
assert isinstance(result.error.__cause__, WaiterContentError)
235238
assert str(result.error.__cause__) == "Test error"
236239

237240

238-
def test_wait_for_content_retry_exception(monkeypatch, session) -> None:
241+
def test_wait_for_content_retry_exception(
242+
monkeypatch: MonkeyPatch,
243+
session: Session,
244+
) -> None:
239245
"""Test that retry exceptions are handled correctly."""
240-
pane = session.new_window("test_waiter").active_pane
246+
window = session.new_window("test_waiter")
247+
pane = window.active_pane
248+
assert pane is not None
241249

242250
def mock_retry_until(
243-
predicate,
244-
timeout_seconds=None,
245-
interval_seconds=None,
246-
raises=None,
251+
predicate: t.Callable[[], bool],
252+
seconds: float | None = None,
253+
interval: float | None = None,
254+
raises: bool | None = None,
247255
) -> t.NoReturn:
248256
msg = "Text 'some text' not found in pane"
249257
raise WaitTimeout(msg)

0 commit comments

Comments
 (0)