1
1
from os import error
2
2
import time
3
- from unittest .mock import Mock , patch
3
+ from unittest .mock import Mock , patch , call
4
4
import pytest
5
5
from requests import Request
6
6
from urllib3 import HTTPResponse
7
- from databricks .sql .auth .retry import DatabricksRetryPolicy , RequestHistory
8
-
7
+ from databricks .sql .auth .retry import DatabricksRetryPolicy , RequestHistory , CommandType
8
+ from databricks .sql .exc import MaxRetryDurationError
9
+ from urllib3 .exceptions import MaxRetryError
9
10
10
11
class TestRetry :
11
12
@pytest .fixture ()
@@ -25,32 +26,55 @@ def error_history(self) -> RequestHistory:
25
26
method = "POST" , url = None , error = None , status = 503 , redirect_location = None
26
27
)
27
28
29
+ def calculate_backoff_time (self , attempt , delay_min , delay_max ):
30
+ exponential_backoff_time = (2 ** attempt ) * delay_min
31
+ return min (exponential_backoff_time , delay_max )
32
+
28
33
@patch ("time.sleep" )
29
34
def test_sleep__no_retry_after (self , t_mock , retry_policy , error_history ):
30
35
retry_policy ._retry_start_time = time .time ()
31
36
retry_policy .history = [error_history , error_history ]
32
37
retry_policy .sleep (HTTPResponse (status = 503 ))
33
- t_mock .assert_called_with (2 )
38
+
39
+ expected_backoff_time = self .calculate_backoff_time (0 , retry_policy .delay_min , retry_policy .delay_max )
40
+ t_mock .assert_called_with (expected_backoff_time )
34
41
35
42
@patch ("time.sleep" )
36
- def test_sleep__retry_after_is_binding (self , t_mock , retry_policy , error_history ):
43
+ def test_sleep__no_retry_after_header__multiple_retries (self , t_mock , retry_policy ):
44
+ num_attempts = retry_policy .stop_after_attempts_count
45
+
37
46
retry_policy ._retry_start_time = time .time ()
38
- retry_policy .history = [error_history , error_history ]
39
- retry_policy .sleep (HTTPResponse (status = 503 , headers = {"Retry-After" : "3" }))
40
- t_mock .assert_called_with (3 )
47
+ retry_policy .command_type = CommandType .OTHER
48
+
49
+ for attempt in range (num_attempts ):
50
+ retry_policy .sleep (HTTPResponse (status = 503 ))
51
+ # Internally urllib3 calls the increment function generating a new instance for every retry
52
+ retry_policy = retry_policy .increment ()
53
+
54
+ expected_backoff_times = []
55
+ for attempt in range (num_attempts ):
56
+ expected_backoff_times .append (self .calculate_backoff_time (attempt , retry_policy .delay_min , retry_policy .delay_max ))
57
+
58
+ # Asserts if the sleep value was called in the expected order
59
+ t_mock .assert_has_calls ([call (expected_time ) for expected_time in expected_backoff_times ])
41
60
42
61
@patch ("time.sleep" )
43
- def test_sleep__retry_after_present_but_not_binding (
44
- self , t_mock , retry_policy , error_history
45
- ):
62
+ def test_excessive_retry_attempts_error (self , t_mock , retry_policy ):
63
+ # Attempting more than stop_after_attempt_count
64
+ num_attempts = retry_policy .stop_after_attempts_count + 1
65
+
46
66
retry_policy ._retry_start_time = time .time ()
47
- retry_policy .history = [error_history , error_history ]
48
- retry_policy .sleep (HTTPResponse (status = 503 , headers = {"Retry-After" : "1" }))
49
- t_mock .assert_called_with (2 )
67
+ retry_policy .command_type = CommandType .OTHER
68
+
69
+ with pytest .raises (MaxRetryError ):
70
+ for attempt in range (num_attempts ):
71
+ retry_policy .sleep (HTTPResponse (status = 503 ))
72
+ # Internally urllib3 calls the increment function generating a new instance for every retry
73
+ retry_policy = retry_policy .increment ()
50
74
51
75
@patch ("time.sleep" )
52
- def test_sleep__retry_after_surpassed (self , t_mock , retry_policy , error_history ):
76
+ def test_sleep__retry_after_present (self , t_mock , retry_policy , error_history ):
53
77
retry_policy ._retry_start_time = time .time ()
54
78
retry_policy .history = [error_history , error_history , error_history ]
55
79
retry_policy .sleep (HTTPResponse (status = 503 , headers = {"Retry-After" : "3" }))
56
- t_mock .assert_called_with (4 )
80
+ t_mock .assert_called_with (3 )
0 commit comments