6 import arvados.errors as arv_error
7 import arvados.retry as arv_retry
10 from arvados_testutil import fake_httplib2_response
12 class RetryLoopTestMixin(object):
14 def loop_success(result):
15 # During the tests, we use integers that look like HTTP status
16 # codes as loop results. Then we define simplified HTTP
17 # heuristics here to decide whether the result is success (True),
18 # permanent failure (False), or temporary failure (None).
26 def run_loop(self, num_retries, *results, **kwargs):
27 responses = itertools.chain(results, itertools.repeat(None))
28 retrier = arv_retry.RetryLoop(num_retries, self.loop_success,
30 for tries_left, response in itertools.izip(retrier, responses):
31 retrier.save_result(response)
34 def check_result(self, retrier, expect_success, last_code):
35 self.assertIs(retrier.success(), expect_success,
36 "loop success flag is incorrect")
37 self.assertEqual(last_code, retrier.last_result())
40 class RetryLoopTestCase(unittest.TestCase, RetryLoopTestMixin):
41 def test_zero_retries_and_success(self):
42 retrier = self.run_loop(0, 200)
43 self.check_result(retrier, True, 200)
45 def test_zero_retries_and_tempfail(self):
46 retrier = self.run_loop(0, 500, 501)
47 self.check_result(retrier, None, 500)
49 def test_zero_retries_and_permfail(self):
50 retrier = self.run_loop(0, 400, 201)
51 self.check_result(retrier, False, 400)
53 def test_one_retry_with_immediate_success(self):
54 retrier = self.run_loop(1, 200, 201)
55 self.check_result(retrier, True, 200)
57 def test_one_retry_with_delayed_success(self):
58 retrier = self.run_loop(1, 500, 201)
59 self.check_result(retrier, True, 201)
61 def test_one_retry_with_no_success(self):
62 retrier = self.run_loop(1, 500, 501, 502)
63 self.check_result(retrier, None, 501)
65 def test_one_retry_but_permfail(self):
66 retrier = self.run_loop(1, 400, 201)
67 self.check_result(retrier, False, 400)
69 def test_two_retries_with_immediate_success(self):
70 retrier = self.run_loop(2, 200, 201, 202)
71 self.check_result(retrier, True, 200)
73 def test_two_retries_with_success_after_one(self):
74 retrier = self.run_loop(2, 500, 201, 502)
75 self.check_result(retrier, True, 201)
77 def test_two_retries_with_success_after_two(self):
78 retrier = self.run_loop(2, 500, 501, 202, 503)
79 self.check_result(retrier, True, 202)
81 def test_two_retries_with_no_success(self):
82 retrier = self.run_loop(2, 500, 501, 502, 503)
83 self.check_result(retrier, None, 502)
85 def test_two_retries_with_permfail(self):
86 retrier = self.run_loop(2, 500, 401, 202)
87 self.check_result(retrier, False, 401)
89 def test_save_result_before_start_is_error(self):
90 retrier = arv_retry.RetryLoop(0)
91 self.assertRaises(arv_error.AssertionError, retrier.save_result, 1)
93 def test_save_result_after_end_is_error(self):
94 retrier = arv_retry.RetryLoop(0)
97 self.assertRaises(arv_error.AssertionError, retrier.save_result, 1)
100 @mock.patch('time.time', side_effect=itertools.count())
101 @mock.patch('time.sleep')
102 class RetryLoopBackoffTestCase(unittest.TestCase, RetryLoopTestMixin):
103 def run_loop(self, num_retries, *results, **kwargs):
104 kwargs.setdefault('backoff_start', 8)
105 return super(RetryLoopBackoffTestCase, self).run_loop(
106 num_retries, *results, **kwargs)
108 def check_backoff(self, sleep_mock, sleep_count, multiplier=1):
109 # Figure out how much time we actually spent sleeping.
110 sleep_times = [arglist[0][0] for arglist in sleep_mock.call_args_list
111 if arglist[0][0] > 0]
112 self.assertEqual(sleep_count, len(sleep_times),
113 "loop did not back off correctly")
115 for this_wait in sleep_times:
116 self.assertGreater(this_wait, last_wait * multiplier,
117 "loop did not grow backoff times correctly")
118 last_wait = this_wait
120 def test_no_backoff_with_no_retries(self, sleep_mock, time_mock):
121 self.run_loop(0, 500, 201)
122 self.check_backoff(sleep_mock, 0)
124 def test_no_backoff_after_success(self, sleep_mock, time_mock):
125 self.run_loop(1, 200, 501)
126 self.check_backoff(sleep_mock, 0)
128 def test_no_backoff_after_permfail(self, sleep_mock, time_mock):
129 self.run_loop(1, 400, 201)
130 self.check_backoff(sleep_mock, 0)
132 def test_backoff_before_success(self, sleep_mock, time_mock):
133 self.run_loop(5, 500, 501, 502, 203, 504)
134 self.check_backoff(sleep_mock, 3)
136 def test_backoff_before_permfail(self, sleep_mock, time_mock):
137 self.run_loop(5, 500, 501, 502, 403, 504)
138 self.check_backoff(sleep_mock, 3)
140 def test_backoff_all_tempfail(self, sleep_mock, time_mock):
141 self.run_loop(3, 500, 501, 502, 503, 504)
142 self.check_backoff(sleep_mock, 3)
144 def test_backoff_multiplier(self, sleep_mock, time_mock):
145 self.run_loop(5, 500, 501, 502, 503, 504, 505,
146 backoff_start=5, backoff_growth=10)
147 self.check_backoff(sleep_mock, 5, 9)
150 class CheckHTTPResponseSuccessTestCase(unittest.TestCase):
151 def results_map(self, *codes):
153 response = (fake_httplib2_response(code), None)
154 yield code, arv_retry.check_http_response_success(response)
156 def check(assert_name):
157 def check_method(self, expected, *codes):
158 assert_func = getattr(self, assert_name)
159 for code, actual in self.results_map(*codes):
160 assert_func(expected, actual,
161 "{} status flagged {}".format(code, actual))
162 if assert_name != 'assertIs':
164 actual is True or actual is False or actual is None,
165 "{} status returned {}".format(code, actual))
168 check_is = check('assertIs')
169 check_is_not = check('assertIsNot')
171 def test_obvious_successes(self):
172 self.check_is(True, *range(200, 207))
174 def test_obvious_stops(self):
175 self.check_is(False, 424, 426, 428, 431,
176 *range(400, 408) + range(410, 420))
178 def test_obvious_retries(self):
179 self.check_is(None, 500, 502, 503, 504)
181 def test_4xx_retries(self):
182 self.check_is(None, 408, 409, 422, 423)
184 def test_5xx_failures(self):
185 self.check_is(False, 501, *range(505, 512))
187 def test_1xx_not_retried(self):
188 self.check_is_not(None, 100, 101)
190 def test_redirects_not_retried(self):
191 self.check_is_not(None, *range(300, 309))
193 def test_wacky_code_retries(self):
194 self.check_is(None, 0, 99, 600, -200)
197 if __name__ == '__main__':