import contextlib import datetime import pytest import library.python.retry as retry def test_default(): @retry.retry() def foo(): pass foo() def test_exec(): ctx = {"run": False} @retry.retry() def foo(): ctx["run"] = True foo() assert ctx["run"] class RetriableError(Exception): pass def test_conf(): conf = retry.RetryConf() conf2 = conf.clone() assert conf2 is not conf conf_on = conf.on(RetriableError) assert conf_on.retriable is not conf.retriable assert conf_on.retriable(RetriableError("error")) t = datetime.timedelta(seconds=3) conf_waiting = conf.waiting(42, backoff=1.5) assert conf_waiting.get_delay is not conf.get_delay assert conf_waiting.get_delay(3, t, 63) == 94.5 class Counter(object): def __init__(self): self.value = 0 def checkin(self): self.value += 1 def DUMMY_RUN(*args, **kwargs): return None @contextlib.contextmanager def erroneous_runner(run, n=1, error=Exception): counter = Counter() def wrapped_run(*args, **kwargs): counter.checkin() if counter.value <= n: raise error("Error") return run(*args, **kwargs) yield wrapped_run @contextlib.contextmanager def counting_runner(run, counter): def wrapped_run(*args, **kwargs): counter.checkin() return run(*args, **kwargs) yield wrapped_run param_runs = pytest.mark.parametrize("runs", (1, 2, 3)) @param_runs def test_retries_call(runs): counter = Counter() with erroneous_runner(DUMMY_RUN, runs) as run: with counting_runner(run, counter) as run: def foo(): run() retry.retry_call(foo) assert counter.value == runs + 1 @param_runs def test_retries_call_args(runs): counter = Counter() with erroneous_runner(DUMMY_RUN, runs) as run: with counting_runner(run, counter) as run: def foo(arg, kwarg=None): import logging logging.info("!!! %s %s", arg, kwarg) run() retry.retry_call(foo, (1,), {"kwarg": 2}) assert counter.value == runs + 1 @param_runs def test_retries_decorator(runs): counter = Counter() with erroneous_runner(DUMMY_RUN, runs) as run: with counting_runner(run, counter) as run: @retry.retry(retry.RetryConf()) def foo(): run() foo() assert counter.value == runs + 1 @param_runs def test_retries_decorator_args(runs): counter = Counter() with erroneous_runner(DUMMY_RUN, runs) as run: with counting_runner(run, counter) as run: @retry.retry(retry.RetryConf()) def foo(arg, kwarg=None): run() foo(1, kwarg=2) assert counter.value == runs + 1 @param_runs def test_retries_decorator_method(runs): counter = Counter() with erroneous_runner(DUMMY_RUN, runs) as run: with counting_runner(run, counter) as run: class Bar(object): @retry.retry(retry.RetryConf()) def foo(self): run() Bar().foo() assert counter.value == runs + 1 @param_runs def test_retries_decorator_method_args(runs): counter = Counter() with erroneous_runner(DUMMY_RUN, runs) as run: with counting_runner(run, counter) as run: class Bar(object): @retry.retry(retry.RetryConf()) def foo(self, arg, kwarg=None): run() Bar().foo(1, kwarg=2) assert counter.value == runs + 1 @param_runs def test_retries_decorator_intrusive(runs): counter = Counter() with erroneous_runner(DUMMY_RUN, runs) as run: with counting_runner(run, counter) as run: class Bar(object): def __init__(self): self.retry_conf = retry.RetryConf() @retry.retry_intrusive def foo(self, arg, kwarg=None): run() Bar().foo(1, kwarg=2) assert counter.value == runs + 1 def test_retries_decorator_intrusive_fail(): class Bar(object): @retry.retry_intrusive def foo(self, arg, kwarg=None): pass with pytest.raises(AssertionError): Bar().foo(1, kwarg=2) @pytest.mark.parametrize( "conf", ( retry.RetryConf(), retry.DEFAULT_CONF, ), ) def test_confs(conf): counter = Counter() with erroneous_runner(DUMMY_RUN) as run: with counting_runner(run, counter) as run: def foo(): run() retry.retry_call(foo, conf=conf) assert counter.value == 2 counter = Counter() with erroneous_runner(DUMMY_RUN) as run: with counting_runner(run, counter) as run: @retry.retry(conf) def foo_retried(): run() foo_retried() assert counter.value == 2 @pytest.mark.parametrize( "conf", ( retry.RetryConf().on(RetriableError), retry.RetryConf(retriable=lambda e: isinstance(e, RetriableError)), ), ) def test_retriable(conf): counter = Counter() with erroneous_runner(DUMMY_RUN, error=RetriableError) as run: with counting_runner(run, counter) as run: @retry.retry(conf) def foo(): run() foo() assert counter.value == 2 counter = Counter() with erroneous_runner(DUMMY_RUN) as run: with counting_runner(run, counter) as run: @retry.retry(conf) def foo(): run() with pytest.raises(Exception): foo() assert counter.value == 1 def test_waiting(): conf = retry.RetryConf().waiting(1) with erroneous_runner(DUMMY_RUN) as run: @retry.retry(conf) def foo(): run() foo() def test_waiting_backoff(): conf = retry.RetryConf().waiting(1, backoff=2) with erroneous_runner(DUMMY_RUN) as run: @retry.retry(conf) def foo(): run() foo() def test_waiting_jitter(): conf = retry.RetryConf().waiting(0, jitter=1) with erroneous_runner(DUMMY_RUN) as run: @retry.retry(conf) def foo(): run() foo() def test_upto(): conf = retry.RetryConf().upto(0) counter = Counter() with erroneous_runner(DUMMY_RUN) as run: with counting_runner(run, counter) as run: @retry.retry(conf) def foo(): run() with pytest.raises(Exception): foo() assert counter.value == 1 def test_upto_retries(): conf = retry.RetryConf().upto_retries(0) counter = Counter() with erroneous_runner(DUMMY_RUN, 2) as run: with counting_runner(run, counter) as run: @retry.retry(conf) def foo(): run() with pytest.raises(Exception): foo() assert counter.value == 1 conf = retry.RetryConf().upto_retries(1) counter = Counter() with erroneous_runner(DUMMY_RUN, 2) as run: with counting_runner(run, counter) as run: @retry.retry(conf) def foo(): run() with pytest.raises(Exception): foo() assert counter.value == 2 conf = retry.RetryConf().upto_retries(2) counter = Counter() with erroneous_runner(DUMMY_RUN, 2) as run: with counting_runner(run, counter) as run: @retry.retry(conf) def foo(): run() foo() assert counter.value == 3 conf = retry.RetryConf().upto_retries(4) counter = Counter() with erroneous_runner(DUMMY_RUN, 2) as run: with counting_runner(run, counter) as run: @retry.retry(conf) def foo(): run() foo() assert counter.value == 3