test_retry.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. import contextlib
  2. import datetime
  3. import pytest
  4. import library.python.retry as retry
  5. def test_default():
  6. @retry.retry()
  7. def foo():
  8. pass
  9. foo()
  10. def test_exec():
  11. ctx = {"run": False}
  12. @retry.retry()
  13. def foo():
  14. ctx["run"] = True
  15. foo()
  16. assert ctx["run"]
  17. class RetriableError(Exception):
  18. pass
  19. def test_conf():
  20. conf = retry.RetryConf()
  21. conf2 = conf.clone()
  22. assert conf2 is not conf
  23. conf_on = conf.on(RetriableError)
  24. assert conf_on.retriable is not conf.retriable
  25. assert conf_on.retriable(RetriableError("error"))
  26. t = datetime.timedelta(seconds=3)
  27. conf_waiting = conf.waiting(42, backoff=1.5)
  28. assert conf_waiting.get_delay is not conf.get_delay
  29. assert conf_waiting.get_delay(3, t, 63) == 94.5
  30. class Counter(object):
  31. def __init__(self):
  32. self.value = 0
  33. def checkin(self):
  34. self.value += 1
  35. def DUMMY_RUN(*args, **kwargs):
  36. return None
  37. @contextlib.contextmanager
  38. def erroneous_runner(run, n=1, error=Exception):
  39. counter = Counter()
  40. def wrapped_run(*args, **kwargs):
  41. counter.checkin()
  42. if counter.value <= n:
  43. raise error("Error")
  44. return run(*args, **kwargs)
  45. yield wrapped_run
  46. @contextlib.contextmanager
  47. def counting_runner(run, counter):
  48. def wrapped_run(*args, **kwargs):
  49. counter.checkin()
  50. return run(*args, **kwargs)
  51. yield wrapped_run
  52. param_runs = pytest.mark.parametrize("runs", (1, 2, 3))
  53. @param_runs
  54. def test_retries_call(runs):
  55. counter = Counter()
  56. with erroneous_runner(DUMMY_RUN, runs) as run:
  57. with counting_runner(run, counter) as run:
  58. def foo():
  59. run()
  60. retry.retry_call(foo)
  61. assert counter.value == runs + 1
  62. @param_runs
  63. def test_retries_call_args(runs):
  64. counter = Counter()
  65. with erroneous_runner(DUMMY_RUN, runs) as run:
  66. with counting_runner(run, counter) as run:
  67. def foo(arg, kwarg=None):
  68. import logging
  69. logging.info("!!! %s %s", arg, kwarg)
  70. run()
  71. retry.retry_call(foo, (1,), {"kwarg": 2})
  72. assert counter.value == runs + 1
  73. @param_runs
  74. def test_retries_decorator(runs):
  75. counter = Counter()
  76. with erroneous_runner(DUMMY_RUN, runs) as run:
  77. with counting_runner(run, counter) as run:
  78. @retry.retry(retry.RetryConf())
  79. def foo():
  80. run()
  81. foo()
  82. assert counter.value == runs + 1
  83. @param_runs
  84. def test_retries_decorator_args(runs):
  85. counter = Counter()
  86. with erroneous_runner(DUMMY_RUN, runs) as run:
  87. with counting_runner(run, counter) as run:
  88. @retry.retry(retry.RetryConf())
  89. def foo(arg, kwarg=None):
  90. run()
  91. foo(1, kwarg=2)
  92. assert counter.value == runs + 1
  93. @param_runs
  94. def test_retries_decorator_method(runs):
  95. counter = Counter()
  96. with erroneous_runner(DUMMY_RUN, runs) as run:
  97. with counting_runner(run, counter) as run:
  98. class Bar(object):
  99. @retry.retry(retry.RetryConf())
  100. def foo(self):
  101. run()
  102. Bar().foo()
  103. assert counter.value == runs + 1
  104. @param_runs
  105. def test_retries_decorator_method_args(runs):
  106. counter = Counter()
  107. with erroneous_runner(DUMMY_RUN, runs) as run:
  108. with counting_runner(run, counter) as run:
  109. class Bar(object):
  110. @retry.retry(retry.RetryConf())
  111. def foo(self, arg, kwarg=None):
  112. run()
  113. Bar().foo(1, kwarg=2)
  114. assert counter.value == runs + 1
  115. @param_runs
  116. def test_retries_decorator_intrusive(runs):
  117. counter = Counter()
  118. with erroneous_runner(DUMMY_RUN, runs) as run:
  119. with counting_runner(run, counter) as run:
  120. class Bar(object):
  121. def __init__(self):
  122. self.retry_conf = retry.RetryConf()
  123. @retry.retry_intrusive
  124. def foo(self, arg, kwarg=None):
  125. run()
  126. Bar().foo(1, kwarg=2)
  127. assert counter.value == runs + 1
  128. def test_retries_decorator_intrusive_fail():
  129. class Bar(object):
  130. @retry.retry_intrusive
  131. def foo(self, arg, kwarg=None):
  132. pass
  133. with pytest.raises(AssertionError):
  134. Bar().foo(1, kwarg=2)
  135. @pytest.mark.parametrize(
  136. "conf",
  137. (
  138. retry.RetryConf(),
  139. retry.DEFAULT_CONF,
  140. ),
  141. )
  142. def test_confs(conf):
  143. counter = Counter()
  144. with erroneous_runner(DUMMY_RUN) as run:
  145. with counting_runner(run, counter) as run:
  146. def foo():
  147. run()
  148. retry.retry_call(foo, conf=conf)
  149. assert counter.value == 2
  150. counter = Counter()
  151. with erroneous_runner(DUMMY_RUN) as run:
  152. with counting_runner(run, counter) as run:
  153. @retry.retry(conf)
  154. def foo_retried():
  155. run()
  156. foo_retried()
  157. assert counter.value == 2
  158. @pytest.mark.parametrize(
  159. "conf",
  160. (
  161. retry.RetryConf().on(RetriableError),
  162. retry.RetryConf(retriable=lambda e: isinstance(e, RetriableError)),
  163. ),
  164. )
  165. def test_retriable(conf):
  166. counter = Counter()
  167. with erroneous_runner(DUMMY_RUN, error=RetriableError) as run:
  168. with counting_runner(run, counter) as run:
  169. @retry.retry(conf)
  170. def foo():
  171. run()
  172. foo()
  173. assert counter.value == 2
  174. counter = Counter()
  175. with erroneous_runner(DUMMY_RUN) as run:
  176. with counting_runner(run, counter) as run:
  177. @retry.retry(conf)
  178. def foo():
  179. run()
  180. with pytest.raises(Exception):
  181. foo()
  182. assert counter.value == 1
  183. def test_waiting():
  184. conf = retry.RetryConf().waiting(1)
  185. with erroneous_runner(DUMMY_RUN) as run:
  186. @retry.retry(conf)
  187. def foo():
  188. run()
  189. foo()
  190. def test_waiting_backoff():
  191. conf = retry.RetryConf().waiting(1, backoff=2)
  192. with erroneous_runner(DUMMY_RUN) as run:
  193. @retry.retry(conf)
  194. def foo():
  195. run()
  196. foo()
  197. def test_waiting_jitter():
  198. conf = retry.RetryConf().waiting(0, jitter=1)
  199. with erroneous_runner(DUMMY_RUN) as run:
  200. @retry.retry(conf)
  201. def foo():
  202. run()
  203. foo()
  204. def test_upto():
  205. conf = retry.RetryConf().upto(0)
  206. counter = Counter()
  207. with erroneous_runner(DUMMY_RUN) as run:
  208. with counting_runner(run, counter) as run:
  209. @retry.retry(conf)
  210. def foo():
  211. run()
  212. with pytest.raises(Exception):
  213. foo()
  214. assert counter.value == 1
  215. def test_upto_retries():
  216. conf = retry.RetryConf().upto_retries(0)
  217. counter = Counter()
  218. with erroneous_runner(DUMMY_RUN, 2) as run:
  219. with counting_runner(run, counter) as run:
  220. @retry.retry(conf)
  221. def foo():
  222. run()
  223. with pytest.raises(Exception):
  224. foo()
  225. assert counter.value == 1
  226. conf = retry.RetryConf().upto_retries(1)
  227. counter = Counter()
  228. with erroneous_runner(DUMMY_RUN, 2) as run:
  229. with counting_runner(run, counter) as run:
  230. @retry.retry(conf)
  231. def foo():
  232. run()
  233. with pytest.raises(Exception):
  234. foo()
  235. assert counter.value == 2
  236. conf = retry.RetryConf().upto_retries(2)
  237. counter = Counter()
  238. with erroneous_runner(DUMMY_RUN, 2) as run:
  239. with counting_runner(run, counter) as run:
  240. @retry.retry(conf)
  241. def foo():
  242. run()
  243. foo()
  244. assert counter.value == 3
  245. conf = retry.RetryConf().upto_retries(4)
  246. counter = Counter()
  247. with erroneous_runner(DUMMY_RUN, 2) as run:
  248. with counting_runner(run, counter) as run:
  249. @retry.retry(conf)
  250. def foo():
  251. run()
  252. foo()
  253. assert counter.value == 3