Skip to content

Commit df17aaf

Browse files
authored
tests!: retry_until(), deprecate retry() (#372)
Fixes #368, as retry() in its current form is broke and won't work within a `with` block. The function is deprecated and will be removed in 0.13.x. 0.12.x will warn when using it. retry_until() is now available and will either raise or return False if raise=False is passed. See also: - tmux-python/tmuxp#620 - tmux-python/tmuxp#704 (comment)
2 parents 897bd24 + 8f555e5 commit df17aaf

File tree

5 files changed

+168
-3
lines changed

5 files changed

+168
-3
lines changed

CHANGES

+6-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ To install the unreleased libtmux version, see [developmental releases](https://
88
$ pip install --user --upgrade --pre libtmux
99
```
1010

11-
## libtmux current (unreleased)
11+
## libtmux 0.12.x (unreleased)
1212

1313
- _Insert changes/features/fixes for next release here_
1414

@@ -22,6 +22,11 @@ $ pip install --user --upgrade --pre libtmux
2222

2323
- Try out sphinx-autoapi for its table of contents generation ({issue}`367`)
2424

25+
### Testing
26+
27+
- `retry()`: Add deprecation warning. This will be removed in 0.13.x ({issue}`368`, {issue}`372`)
28+
- New function `retry_until()`: Polls a callback function for a set period of time until it returns `True` or times out. By default it will raise {exc}`libtmux.exc.WaitTimeout`, with `raises=False` it will return `False`. Thank you @categulario! ({issue}`368`, {issue}`372`)
29+
2530
## libtmux 0.11.0 (2022-03-10)
2631

2732
### Compatibility

docs/api.md

+4
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,10 @@ versions.
170170
.. automethod:: libtmux.test.retry
171171
```
172172

173+
```{eval-rst}
174+
.. automethod:: libtmux.test.retry_until
175+
```
176+
173177
```{eval-rst}
174178
.. automethod:: libtmux.test.get_test_session_name
175179
```

libtmux/exc.py

+5
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,8 @@ class InvalidOption(OptionError):
4949
class AmbiguousOption(OptionError):
5050

5151
"""Option that could potentially match more than one."""
52+
53+
54+
class WaitTimeout(LibTmuxException):
55+
56+
"""Function timed out without meeting condition"""

libtmux/test.py

+66-2
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,34 @@
44
import os
55
import tempfile
66
import time
7+
import warnings
8+
from typing import Callable, Optional
9+
10+
from .exc import WaitTimeout
711

812
logger = logging.getLogger(__name__)
913

1014
TEST_SESSION_PREFIX = "libtmux_"
1115
RETRY_TIMEOUT_SECONDS = int(os.getenv("RETRY_TIMEOUT_SECONDS", 8))
16+
RETRY_INTERVAL_SECONDS = float(os.getenv("RETRY_INTERVAL_SECONDS", 0.05))
1217

1318
namer = tempfile._RandomNameSequence()
1419
current_dir = os.path.abspath(os.path.dirname(__file__))
1520
example_dir = os.path.abspath(os.path.join(current_dir, "..", "examples"))
1621
fixtures_dir = os.path.realpath(os.path.join(current_dir, "fixtures"))
1722

1823

19-
def retry(seconds=RETRY_TIMEOUT_SECONDS):
24+
def retry(seconds: Optional[float] = RETRY_TIMEOUT_SECONDS) -> bool:
2025
"""
2126
Retry a block of code until a time limit or ``break``.
2227
28+
.. deprecated:: 0.12.0
29+
`retry` doesn't work, it will be removed in libtmux 0.13.0, it is replaced by
30+
`retry_until`, more info: https://github.com/tmux-python/libtmux/issues/368.
31+
2332
Parameters
2433
----------
25-
seconds : int
34+
seconds : float
2635
Seconds to retry, defaults to ``RETRY_TIMEOUT_SECONDS``, which is
2736
configurable via environmental variables.
2837
@@ -40,9 +49,64 @@ def retry(seconds=RETRY_TIMEOUT_SECONDS):
4049
... if p.current_path == pane_path:
4150
... break
4251
"""
52+
warnings.warn(
53+
"retry() is being deprecated and will soon be replaced by retry_until()",
54+
DeprecationWarning,
55+
)
4356
return (lambda: time.time() < time.time() + seconds)()
4457

4558

59+
def retry_until(
60+
fun: Callable,
61+
seconds: float = RETRY_TIMEOUT_SECONDS,
62+
*,
63+
interval: Optional[float] = RETRY_INTERVAL_SECONDS,
64+
raises: Optional[bool] = True,
65+
) -> bool:
66+
"""
67+
Retry a function until a condition meets or the specified time passes.
68+
69+
Parameters
70+
----------
71+
fun : callable
72+
A function that will be called repeatedly until it returns ``True`` or
73+
the specified time passes.
74+
seconds : float
75+
Seconds to retry. Defaults to ``8``, which is configurable via
76+
``RETRY_TIMEOUT_SECONDS`` environment variables.
77+
interval : float
78+
Time in seconds to wait between calls. Defaults to ``0.05`` and is
79+
configurable via ``RETRY_INTERVAL_SECONDS`` environment variable.
80+
raises : bool
81+
Wether or not to raise an exception on timeout. Defaults to ``True``.
82+
83+
Examples
84+
--------
85+
86+
>>> def f():
87+
... p = w.attached_pane
88+
... p.server._update_panes()
89+
... return p.current_path == pane_path
90+
...
91+
... retry(f)
92+
93+
In pytest:
94+
95+
>>> assert retry(f, raises=False)
96+
"""
97+
ini = time.time()
98+
99+
while not fun():
100+
end = time.time()
101+
if end - ini >= seconds:
102+
if raises:
103+
raise WaitTimeout()
104+
else:
105+
return False
106+
time.sleep(interval)
107+
return True
108+
109+
46110
def get_test_session_name(server, prefix=TEST_SESSION_PREFIX):
47111
"""
48112
Faker to create a session name that doesn't exist.

tests/test_test.py

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from time import time
2+
3+
import pytest
4+
5+
from libtmux.test import WaitTimeout, retry_until
6+
7+
8+
def test_retry_three_times():
9+
ini = time()
10+
value = 0
11+
12+
def call_me_three_times():
13+
nonlocal value
14+
15+
if value == 2:
16+
return True
17+
18+
value += 1
19+
20+
return False
21+
22+
retry_until(call_me_three_times, 1)
23+
24+
end = time()
25+
26+
assert abs((end - ini) - 0.1) < 0.01
27+
28+
29+
def test_function_times_out():
30+
ini = time()
31+
32+
def never_true():
33+
return False
34+
35+
with pytest.raises(WaitTimeout):
36+
retry_until(never_true, 1)
37+
38+
end = time()
39+
40+
assert abs((end - ini) - 1.0) < 0.01
41+
42+
43+
def test_function_times_out_no_rise():
44+
ini = time()
45+
46+
def never_true():
47+
return False
48+
49+
retry_until(never_true, 1, raises=False)
50+
51+
end = time()
52+
53+
assert abs((end - ini) - 1.0) < 0.01
54+
55+
56+
def test_function_times_out_no_raise_assert():
57+
ini = time()
58+
59+
def never_true():
60+
return False
61+
62+
assert not retry_until(never_true, 1, raises=False)
63+
64+
end = time()
65+
66+
assert abs((end - ini) - 1.0) < 0.01
67+
68+
69+
def test_retry_three_times_no_raise_assert():
70+
ini = time()
71+
value = 0
72+
73+
def call_me_three_times():
74+
nonlocal value
75+
76+
if value == 2:
77+
return True
78+
79+
value += 1
80+
81+
return False
82+
83+
assert retry_until(call_me_three_times, 1, raises=False)
84+
85+
end = time()
86+
87+
assert abs((end - ini) - 0.1) < 0.01

0 commit comments

Comments
 (0)