@@ -182,35 +182,94 @@ def test_constructor_options(self):
182182assert retry_ ._on_error is _some_function
183183
184184def test_with_deadline (self ):
185- retry_ = retry .Retry ()
185+ retry_ = retry .Retry (
186+ predicate = mock .sentinel .predicate ,
187+ initial = 1 ,
188+ maximum = 2 ,
189+ multiplier = 3 ,
190+ deadline = 4 ,
191+ on_error = mock .sentinel .on_error ,
192+ )
186193new_retry = retry_ .with_deadline (42 )
187194assert retry_ is not new_retry
188195assert new_retry ._deadline == 42
189196
197+ # the rest of the attributes should remain the same
198+ assert new_retry ._predicate is retry_ ._predicate
199+ assert new_retry ._initial == retry_ ._initial
200+ assert new_retry ._maximum == retry_ ._maximum
201+ assert new_retry ._multiplier == retry_ ._multiplier
202+ assert new_retry ._on_error is retry_ ._on_error
203+
190204def test_with_predicate (self ):
191- retry_ = retry .Retry ()
205+ retry_ = retry .Retry (
206+ predicate = mock .sentinel .predicate ,
207+ initial = 1 ,
208+ maximum = 2 ,
209+ multiplier = 3 ,
210+ deadline = 4 ,
211+ on_error = mock .sentinel .on_error ,
212+ )
192213new_retry = retry_ .with_predicate (mock .sentinel .predicate )
193214assert retry_ is not new_retry
194215assert new_retry ._predicate == mock .sentinel .predicate
195216
217+ # the rest of the attributes should remain the same
218+ assert new_retry ._deadline == retry_ ._deadline
219+ assert new_retry ._initial == retry_ ._initial
220+ assert new_retry ._maximum == retry_ ._maximum
221+ assert new_retry ._multiplier == retry_ ._multiplier
222+ assert new_retry ._on_error is retry_ ._on_error
223+
196224def test_with_delay_noop (self ):
197- retry_ = retry .Retry ()
225+ retry_ = retry .Retry (
226+ predicate = mock .sentinel .predicate ,
227+ initial = 1 ,
228+ maximum = 2 ,
229+ multiplier = 3 ,
230+ deadline = 4 ,
231+ on_error = mock .sentinel .on_error ,
232+ )
198233new_retry = retry_ .with_delay ()
199234assert retry_ is not new_retry
200235assert new_retry ._initial == retry_ ._initial
201236assert new_retry ._maximum == retry_ ._maximum
202237assert new_retry ._multiplier == retry_ ._multiplier
203238
204239def test_with_delay (self ):
205- retry_ = retry .Retry ()
240+ retry_ = retry .Retry (
241+ predicate = mock .sentinel .predicate ,
242+ initial = 1 ,
243+ maximum = 2 ,
244+ multiplier = 3 ,
245+ deadline = 4 ,
246+ on_error = mock .sentinel .on_error ,
247+ )
206248new_retry = retry_ .with_delay (initial = 1 ,maximum = 2 ,multiplier = 3 )
207249assert retry_ is not new_retry
208250assert new_retry ._initial == 1
209251assert new_retry ._maximum == 2
210252assert new_retry ._multiplier == 3
211253
254+ # the rest of the attributes should remain the same
255+ assert new_retry ._deadline == retry_ ._deadline
256+ assert new_retry ._predicate is retry_ ._predicate
257+ assert new_retry ._on_error is retry_ ._on_error
258+
212259def test___str__ (self ):
213- retry_ = retry .Retry ()
260+ def if_exception_type (exc ):
261+ return bool (exc )# pragma: NO COVER
262+
263+ # Explicitly set all attributes as changed Retry defaults should not
264+ # cause this test to start failing.
265+ retry_ = retry .Retry (
266+ predicate = if_exception_type ,
267+ initial = 1.0 ,
268+ maximum = 60.0 ,
269+ multiplier = 2.0 ,
270+ deadline = 120.0 ,
271+ on_error = None ,
272+ )
214273assert re .match (
215274 (
216275r"<Retry predicate=<function.*?if_exception_type.*?>, "
@@ -259,6 +318,54 @@ def test___call___and_execute_retry(self, sleep, uniform):
259318sleep .assert_called_once_with (retry_ ._initial )
260319assert on_error .call_count == 1
261320
321+ # Make uniform return half of its maximum, which is the calculated sleep time.
322+ @mock .patch ("random.uniform" ,autospec = True ,side_effect = lambda m ,n :n / 2.0 )
323+ @mock .patch ("time.sleep" ,autospec = True )
324+ def test___call___and_execute_retry_hitting_deadline (self ,sleep ,uniform ):
325+
326+ on_error = mock .Mock (spec = ["__call__" ],side_effect = [None ]* 10 )
327+ retry_ = retry .Retry (
328+ predicate = retry .if_exception_type (ValueError ),
329+ initial = 1.0 ,
330+ maximum = 1024.0 ,
331+ multiplier = 2.0 ,
332+ deadline = 9.9 ,
333+ )
334+
335+ utcnow = datetime .datetime .utcnow ()
336+ utcnow_patcher = mock .patch (
337+ "google.api_core.datetime_helpers.utcnow" ,return_value = utcnow
338+ )
339+
340+ target = mock .Mock (spec = ["__call__" ],side_effect = [ValueError ()]* 10 )
341+ # __name__ is needed by functools.partial.
342+ target .__name__ = "target"
343+
344+ decorated = retry_ (target ,on_error = on_error )
345+ target .assert_not_called ()
346+
347+ with utcnow_patcher as patched_utcnow :
348+ # Make sure that calls to fake time.sleep() also advance the mocked
349+ # time clock.
350+ def increase_time (sleep_delay ):
351+ patched_utcnow .return_value += datetime .timedelta (seconds = sleep_delay )
352+ sleep .side_effect = increase_time
353+
354+ with pytest .raises (exceptions .RetryError ):
355+ decorated ("meep" )
356+
357+ assert target .call_count == 5
358+ target .assert_has_calls ([mock .call ("meep" )]* 5 )
359+ assert on_error .call_count == 5
360+
361+ # check the delays
362+ assert sleep .call_count == 4 # once between each successive target calls
363+ last_wait = sleep .call_args .args [0 ]
364+ total_wait = sum (call_args .args [0 ]for call_args in sleep .call_args_list )
365+
366+ assert last_wait == 2.9 # and not 8.0, because the last delay was shortened
367+ assert total_wait == 9.9 # the same as the deadline
368+
262369@mock .patch ("time.sleep" ,autospec = True )
263370def test___init___without_retry_executed (self ,sleep ):
264371_some_function = mock .Mock ()