Skip to content

Modify retry() to take a callable and enforce the timeout #372

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

Merged
merged 9 commits into from
May 20, 2022
Merged
Prev Previous commit
Next Next commit
Add retry_until() to take a callable and enforce the timeout
  • Loading branch information
categulario authored and tony committed May 20, 2022
commit 036642ebb4aa83c560fb6ca6acc2bc870d165b53
49 changes: 49 additions & 0 deletions libtmux/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
import tempfile
import time

from .exc import WaitTimeout

logger = logging.getLogger(__name__)

TEST_SESSION_PREFIX = "libtmux_"
RETRY_TIMEOUT_SECONDS = int(os.getenv("RETRY_TIMEOUT_SECONDS", 8))
RETRY_INTERVAL_SECONDS = float(os.getenv("RETRY_INTERVAL_SECONDS", 0.05))

namer = tempfile._RandomNameSequence()
current_dir = os.path.abspath(os.path.dirname(__file__))
Expand Down Expand Up @@ -43,6 +46,52 @@ def retry(seconds=RETRY_TIMEOUT_SECONDS):
return (lambda: time.time() < time.time() + seconds)()


def retry_until(
fun,
seconds=RETRY_TIMEOUT_SECONDS,
*,
interval=RETRY_INTERVAL_SECONDS,
raises=True,
):
"""
Retry a function until a condition meets or the specified time passes.

Parameters
----------
fun : callable
A function that will be called repeatedly until it returns ``True`` or
the specified time passes.
seconds : int
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
Wether or not to raise an exception on timeout. Defaults to ``True``.

Examples
--------

>>> def f():
... p = w.attached_pane
... p.server._update_panes()
... return p.current_path == pane_path
...
... retry(f)
"""
ini = time.time()

while not fun():
end = time.time()
if end - ini >= seconds:
if raises:
raise WaitTimeout()
else:
break
time.sleep(interval)


def get_test_session_name(server, prefix=TEST_SESSION_PREFIX):
"""
Faker to create a session name that doesn't exist.
Expand Down
53 changes: 53 additions & 0 deletions tests/test_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from time import time

import pytest

from libtmux.test import WaitTimeout, retry_until


def test_retry_three_times():
ini = time()
value = 0

def call_me_three_times():
nonlocal value

if value == 2:
return True

value += 1

return False

retry_until(call_me_three_times, 1)

end = time()

assert abs((end - ini) - 0.1) < 0.01


def test_function_times_out():
ini = time()

def never_true():
return False

with pytest.raises(WaitTimeout):
retry_until(never_true, 1)

end = time()

assert abs((end - ini) - 1.0) < 0.01


def test_function_times_out_no_rise():
ini = time()

def never_true():
return False

retry_until(never_true, 1, raises=False)

end = time()

assert abs((end - ini) - 1.0) < 0.01