@@ -162,6 +162,7 @@ def test_constructor_defaults(self):
162
162
assert retry_ ._multiplier == 2
163
163
assert retry_ ._deadline == 120
164
164
assert retry_ ._on_error is None
165
+ assert retry_ ._strict_deadline is False
165
166
166
167
def test_constructor_options (self ):
167
168
_some_function = mock .Mock ()
@@ -173,49 +174,118 @@ def test_constructor_options(self):
173
174
multiplier = 3 ,
174
175
deadline = 4 ,
175
176
on_error = _some_function ,
177
+ strict_deadline = True ,
176
178
)
177
179
assert retry_ ._predicate == mock .sentinel .predicate
178
180
assert retry_ ._initial == 1
179
181
assert retry_ ._maximum == 2
180
182
assert retry_ ._multiplier == 3
181
183
assert retry_ ._deadline == 4
182
184
assert retry_ ._on_error is _some_function
185
+ assert retry_ ._strict_deadline is True
183
186
184
187
def test_with_deadline (self ):
185
- retry_ = retry .Retry ()
186
- new_retry = retry_ .with_deadline (42 )
188
+ retry_ = retry .Retry (
189
+ predicate = mock .sentinel .predicate ,
190
+ initial = 1 ,
191
+ maximum = 2 ,
192
+ multiplier = 3 ,
193
+ deadline = 4 ,
194
+ on_error = mock .sentinel .on_error ,
195
+ strict_deadline = True ,
196
+ )
197
+ new_retry = retry_ .with_deadline (42 , strict_deadline = True )
187
198
assert retry_ is not new_retry
188
199
assert new_retry ._deadline == 42
200
+ assert new_retry ._strict_deadline is True
201
+
202
+ # the rest of the attributes should remain the same
203
+ assert new_retry ._predicate is retry_ ._predicate
204
+ assert new_retry ._initial == retry_ ._initial
205
+ assert new_retry ._maximum == retry_ ._maximum
206
+ assert new_retry ._multiplier == retry_ ._multiplier
207
+ assert new_retry ._on_error is retry_ ._on_error
189
208
190
209
def test_with_predicate (self ):
191
- retry_ = retry .Retry ()
210
+ retry_ = retry .Retry (
211
+ predicate = mock .sentinel .predicate ,
212
+ initial = 1 ,
213
+ maximum = 2 ,
214
+ multiplier = 3 ,
215
+ deadline = 4 ,
216
+ on_error = mock .sentinel .on_error ,
217
+ strict_deadline = True ,
218
+ )
192
219
new_retry = retry_ .with_predicate (mock .sentinel .predicate )
193
220
assert retry_ is not new_retry
194
221
assert new_retry ._predicate == mock .sentinel .predicate
195
222
223
+ # the rest of the attributes should remain the same
224
+ assert new_retry ._deadline == retry_ ._deadline
225
+ assert new_retry ._strict_deadline == retry_ ._strict_deadline
226
+ assert new_retry ._initial == retry_ ._initial
227
+ assert new_retry ._maximum == retry_ ._maximum
228
+ assert new_retry ._multiplier == retry_ ._multiplier
229
+ assert new_retry ._on_error is retry_ ._on_error
230
+
196
231
def test_with_delay_noop (self ):
197
- retry_ = retry .Retry ()
232
+ retry_ = retry .Retry (
233
+ predicate = mock .sentinel .predicate ,
234
+ initial = 1 ,
235
+ maximum = 2 ,
236
+ multiplier = 3 ,
237
+ deadline = 4 ,
238
+ on_error = mock .sentinel .on_error ,
239
+ strict_deadline = True ,
240
+ )
198
241
new_retry = retry_ .with_delay ()
199
242
assert retry_ is not new_retry
200
243
assert new_retry ._initial == retry_ ._initial
201
244
assert new_retry ._maximum == retry_ ._maximum
202
245
assert new_retry ._multiplier == retry_ ._multiplier
203
246
204
247
def test_with_delay (self ):
205
- retry_ = retry .Retry ()
248
+ retry_ = retry.Retry (
249
+ predicate = mock .sentinel .predicate ,
250
+ initial = 1 ,
251
+ maximum = 2 ,
252
+ multiplier = 3 ,
253
+ deadline = 4 ,
254
+ on_error = mock .sentinel .on_error ,
255
+ strict_deadline = True ,
256
+ )
206
257
new_retry = retry_ .with_delay (initial = 1 , maximum = 2 , multiplier = 3 )
207
258
assert retry_ is not new_retry
208
259
assert new_retry ._initial == 1
209
260
assert new_retry ._maximum == 2
210
261
assert new_retry ._multiplier == 3
211
262
263
+ # the rest of the attributes should remain the same
264
+ assert new_retry ._deadline == retry_ ._deadline
265
+ assert new_retry ._strict_deadline == retry_ ._strict_deadline
266
+ assert new_retry ._predicate is retry_ ._predicate
267
+ assert new_retry ._on_error is retry_ ._on_error
268
+
212
269
def test___str__ (self ):
213
- retry_ = retry .Retry ()
270
+ def if_exception_type (exc ):
271
+ return bool (exc ) # pragma: NO COVER
272
+
273
+ # Explicitly set all attributes as changed Retry defaults should not
274
+ # cause this test to start failing.
275
+ retry_ = retry .Retry (
276
+ predicate = if_exception_type ,
277
+ initial = 1.0 ,
278
+ maximum = 60.0 ,
279
+ multiplier = 2.0 ,
280
+ deadline = 120.0 ,
281
+ on_error = None ,
282
+ strict_deadline = False ,
283
+ )
214
284
assert re .match (
215
285
(
216
286
r"<Retry predicate=<function.*?if_exception_type.*?>, "
217
287
r"initial=1.0, maximum=60.0, multiplier=2.0, deadline=120.0, "
218
- r"on_error=None>"
288
+ r"on_error=None, strict_deadline=False >"
219
289
),
220
290
str (retry_ ),
221
291
)
@@ -259,6 +329,55 @@ def test___call___and_execute_retry(self, sleep, uniform):
259
329
sleep .assert_called_once_with (retry_ ._initial )
260
330
assert on_error .call_count == 1
261
331
332
+ # Make uniform return half of its maximum, which is the calculated sleep time.
333
+ @mock .patch ("random.uniform" , autospec = True , side_effect = lambda m , n : n / 2.0 )
334
+ @mock .patch ("time.sleep" , autospec = True )
335
+ def test___call___and_execute_retry_strict_deadline (self , sleep , uniform ):
336
+
337
+ on_error = mock .Mock (spec = ["__call__" ], side_effect = [None ] * 10 )
338
+ retry_ = retry .Retry (
339
+ predicate = retry .if_exception_type (ValueError ),
340
+ initial = 1.0 ,
341
+ maximum = 1024.0 ,
342
+ multiplier = 2.0 ,
343
+ deadline = 9.9 ,
344
+ strict_deadline = True ,
345
+ )
346
+
347
+ utcnow = datetime .datetime .utcnow ()
348
+ utcnow_patcher = mock .patch (
349
+ "google.api_core.datetime_helpers.utcnow" , return_value = utcnow
350
+ )
351
+
352
+ target = mock .Mock (spec = ["__call__" ], side_effect = [ValueError ()] * 10 )
353
+ # __name__ is needed by functools.partial.
354
+ target .__name__ = "target"
355
+
356
+ decorated = retry_ (target , on_error = on_error )
357
+ target .assert_not_called ()
358
+
359
+ with utcnow_patcher as patched_utcnow :
360
+ # Make sure that calls to fake time.sleep() also advance the mocked
361
+ # time clock.
362
+ def increase_time (sleep_delay ):
363
+ patched_utcnow .return_value += datetime .timedelta (seconds = sleep_delay )
364
+ sleep .side_effect = increase_time
365
+
366
+ with pytest .raises (exceptions .RetryError ):
367
+ decorated ("meep" )
368
+
369
+ assert target .call_count == 5
370
+ target .assert_has_calls ([mock .call ("meep" )] * 5 )
371
+ assert on_error .call_count == 5
372
+
373
+ # check the delays
374
+ assert sleep .call_count == 4 # once between each successive target calls
375
+ last_wait = sleep .call_args .args [0 ]
376
+ total_wait = sum (call_args .args [0 ] for call_args in sleep .call_args_list )
377
+
378
+ assert last_wait == 2.9 # and not 8.0, because the last delay was shortened
379
+ assert total_wait == 9.9 # the same as the (strict) deadline
380
+
262
381
@mock .patch ("time.sleep" , autospec = True )
263
382
def test___init___without_retry_executed (self , sleep ):
264
383
_some_function = mock .Mock ()
0 commit comments