|
1 |
| -import unittest |
| 1 | +from unittest import mock |
2 | 2 |
|
3 | 3 | import pytest
|
4 | 4 |
|
| 5 | +from kazoo import exceptions as ke |
| 6 | +from kazoo import retry as kr |
5 | 7 |
|
6 |
| -class TestRetrySleeper(unittest.TestCase): |
7 |
| - def _pass(self): |
8 |
| - pass |
9 | 8 |
|
10 |
| - def _fail(self, times=1): |
11 |
| - from kazoo.retry import ForceRetryError |
| 9 | +def _make_retry(*args, **kwargs): |
| 10 | + """Return a KazooRetry instance with a dummy sleep function.""" |
| 11 | + |
| 12 | + def _sleep_func(_time): |
| 13 | + pass |
12 | 14 |
|
13 |
| - scope = dict(times=0) |
| 15 | + return kr.KazooRetry(*args, sleep_func=_sleep_func, **kwargs) |
14 | 16 |
|
15 |
| - def inner(): |
16 |
| - if scope["times"] >= times: |
17 |
| - pass |
18 |
| - else: |
19 |
| - scope["times"] += 1 |
20 |
| - raise ForceRetryError("Failed!") |
21 | 17 |
|
22 |
| - return inner |
| 18 | +def _make_try_func(times=1): |
| 19 | + """Returns a function that raises ForceRetryError `times` time before |
| 20 | + returning None. |
| 21 | + """ |
| 22 | + callmock = mock.Mock( |
| 23 | + side_effect=[kr.ForceRetryError("Failed!")] * times + [None], |
| 24 | + ) |
| 25 | + return callmock |
23 | 26 |
|
24 |
| - def _makeOne(self, *args, **kwargs): |
25 |
| - from kazoo.retry import KazooRetry |
26 | 27 |
|
27 |
| - return KazooRetry(*args, **kwargs) |
| 28 | +def test_call(): |
| 29 | + retry = _make_retry(delay=0, max_tries=2) |
| 30 | + func = _make_try_func() |
| 31 | + retry(func, "foo", bar="baz") |
| 32 | + assert func.call_args_list == [ |
| 33 | + mock.call("foo", bar="baz"), |
| 34 | + mock.call("foo", bar="baz"), |
| 35 | + ] |
28 | 36 |
|
29 |
| - def test_reset(self): |
30 |
| - retry = self._makeOne(delay=0, max_tries=2) |
31 |
| - retry(self._fail()) |
32 |
| - assert retry._attempts == 1 |
33 |
| - retry.reset() |
34 |
| - assert retry._attempts == 0 |
35 | 37 |
|
36 |
| - def test_too_many_tries(self): |
37 |
| - from kazoo.retry import RetryFailedError |
| 38 | +def test_reset(): |
| 39 | + retry = _make_retry(delay=0, max_tries=2) |
| 40 | + func = _make_try_func() |
| 41 | + retry(func) |
| 42 | + assert ( |
| 43 | + func.call_count == retry._attempts + 1 == 2 |
| 44 | + ), "Called 2 times, failed _attempts 1, succeeded 1" |
| 45 | + retry.reset() |
| 46 | + assert retry._attempts == 0 |
38 | 47 |
|
39 |
| - retry = self._makeOne(delay=0) |
40 |
| - with pytest.raises(RetryFailedError): |
41 |
| - retry(self._fail(times=999)) |
42 |
| - assert retry._attempts == 1 |
43 | 48 |
|
44 |
| - def test_maximum_delay(self): |
45 |
| - def sleep_func(_time): |
46 |
| - pass |
| 49 | +def test_too_many_tries(): |
| 50 | + retry = _make_retry(delay=0, max_tries=10) |
| 51 | + func = _make_try_func(times=999) |
| 52 | + with pytest.raises(kr.RetryFailedError): |
| 53 | + retry(func) |
| 54 | + assert ( |
| 55 | + func.call_count == retry._attempts == 10 |
| 56 | + ), "Called 10 times, failed _attempts 10" |
47 | 57 |
|
48 |
| - retry = self._makeOne(delay=10, max_tries=100, sleep_func=sleep_func) |
49 |
| - retry(self._fail(times=10)) |
50 |
| - assert retry._cur_delay < 4000 |
51 |
| - # gevent's sleep function is picky about the type |
52 |
| - assert type(retry._cur_delay) == float |
53 | 58 |
|
54 |
| - def test_copy(self): |
55 |
| - def _sleep(t): |
56 |
| - return None |
| 59 | +def test_maximum_delay(): |
| 60 | + retry = _make_retry(delay=10, max_tries=100, max_jitter=0) |
| 61 | + func = _make_try_func(times=2) |
| 62 | + retry(func) |
| 63 | + assert func.call_count == 3, "Called 3 times, 2 failed _attemps" |
| 64 | + assert retry._cur_delay == 10 * 2**2, "Normal exponential backoff" |
| 65 | + retry.reset() |
| 66 | + func = _make_try_func(times=10) |
| 67 | + retry(func) |
| 68 | + assert func.call_count == 11, "Called 11 times, 10 failed _attemps" |
| 69 | + assert retry._cur_delay == 60, "Delay capped by maximun" |
| 70 | + # gevent's sleep function is picky about the type |
| 71 | + assert isinstance(retry._cur_delay, float) |
57 | 72 |
|
58 |
| - retry = self._makeOne(sleep_func=_sleep) |
59 |
| - rcopy = retry.copy() |
60 |
| - assert rcopy.sleep_func is _sleep |
61 | 73 |
|
| 74 | +def test_copy(): |
| 75 | + retry = _make_retry() |
| 76 | + rcopy = retry.copy() |
| 77 | + assert rcopy is not retry |
| 78 | + assert rcopy.sleep_func is retry.sleep_func |
62 | 79 |
|
63 |
| -class TestKazooRetry(unittest.TestCase): |
64 |
| - def _makeOne(self, **kw): |
65 |
| - from kazoo.retry import KazooRetry |
66 | 80 |
|
67 |
| - return KazooRetry(**kw) |
| 81 | +def test_connection_closed(): |
| 82 | + retry = _make_retry() |
68 | 83 |
|
69 |
| - def test_connection_closed(self): |
70 |
| - from kazoo.exceptions import ConnectionClosedError |
| 84 | + def testit(): |
| 85 | + raise ke.ConnectionClosedError |
71 | 86 |
|
72 |
| - retry = self._makeOne() |
| 87 | + with pytest.raises(ke.ConnectionClosedError): |
| 88 | + retry(testit) |
73 | 89 |
|
74 |
| - def testit(): |
75 |
| - raise ConnectionClosedError() |
76 | 90 |
|
77 |
| - with pytest.raises(ConnectionClosedError): |
78 |
| - retry(testit) |
| 91 | +def test_session_expired(): |
| 92 | + retry = _make_retry(max_tries=1) |
79 | 93 |
|
80 |
| - def test_session_expired(self): |
81 |
| - from kazoo.exceptions import SessionExpiredError |
| 94 | + def testit(): |
| 95 | + raise ke.SessionExpiredError |
82 | 96 |
|
83 |
| - retry = self._makeOne(max_tries=1) |
| 97 | + with pytest.raises(kr.RetryFailedError): |
| 98 | + retry(testit) |
84 | 99 |
|
85 |
| - def testit(): |
86 |
| - raise SessionExpiredError() |
| 100 | + retry = _make_retry(max_tries=1, ignore_expire=False) |
87 | 101 |
|
88 |
| - with pytest.raises(Exception): |
89 |
| - retry(testit) |
| 102 | + with pytest.raises(ke.SessionExpiredError): |
| 103 | + retry(testit) |
0 commit comments