|
12 | 12 | # See the License for the specific language governing permissions and
|
13 | 13 | # limitations under the License.
|
14 | 14 |
|
| 15 | +import datetime |
15 | 16 | import logging
|
16 | 17 | import threading
|
17 | 18 |
|
@@ -116,6 +117,87 @@ def test_exit_with_stop(self):
|
116 | 117 | assert items == []
|
117 | 118 |
|
118 | 119 |
|
| 120 | +class Test_Throttle(object): |
| 121 | + def test_repr(self): |
| 122 | + delta = datetime.timedelta(seconds=4.5) |
| 123 | + instance = bidi._Throttle(access_limit=42, time_window=delta) |
| 124 | + assert repr(instance) == \ |
| 125 | + "_Throttle(access_limit=42, time_window={})".format(repr(delta)) |
| 126 | + |
| 127 | + def test_raises_error_on_invalid_init_arguments(self): |
| 128 | + with pytest.raises(ValueError) as exc_info: |
| 129 | + bidi._Throttle( |
| 130 | + access_limit=10, time_window=datetime.timedelta(seconds=0.0) |
| 131 | + ) |
| 132 | + assert "time_window" in str(exc_info.value) |
| 133 | + assert "must be a positive timedelta" in str(exc_info.value) |
| 134 | + |
| 135 | + with pytest.raises(ValueError) as exc_info: |
| 136 | + bidi._Throttle( |
| 137 | + access_limit=0, time_window=datetime.timedelta(seconds=10) |
| 138 | + ) |
| 139 | + assert "access_limit" in str(exc_info.value) |
| 140 | + assert "must be positive" in str(exc_info.value) |
| 141 | + |
| 142 | + def test_does_not_delay_entry_attempts_under_threshold(self): |
| 143 | + throttle = bidi._Throttle( |
| 144 | + access_limit=3, time_window=datetime.timedelta(seconds=1) |
| 145 | + ) |
| 146 | + entries = [] |
| 147 | + |
| 148 | + for _ in range(3): |
| 149 | + with throttle as time_waited: |
| 150 | + entry_info = { |
| 151 | + "entered_at": datetime.datetime.now(), |
| 152 | + "reported_wait": time_waited, |
| 153 | + } |
| 154 | + entries.append(entry_info) |
| 155 | + |
| 156 | + # check the reported wait times ... |
| 157 | + assert all(entry["reported_wait"] == 0.0 for entry in entries) |
| 158 | + |
| 159 | + # .. and the actual wait times |
| 160 | + delta = entries[1]["entered_at"] - entries[0]["entered_at"] |
| 161 | + assert delta.total_seconds() < 0.1 |
| 162 | + delta = entries[2]["entered_at"] - entries[1]["entered_at"] |
| 163 | + assert delta.total_seconds() < 0.1 |
| 164 | + |
| 165 | + def test_delays_entry_attempts_above_threshold(self): |
| 166 | + throttle = bidi._Throttle( |
| 167 | + access_limit=3, time_window=datetime.timedelta(seconds=1) |
| 168 | + ) |
| 169 | + entries = [] |
| 170 | + |
| 171 | + for _ in range(6): |
| 172 | + with throttle as time_waited: |
| 173 | + entry_info = { |
| 174 | + "entered_at": datetime.datetime.now(), |
| 175 | + "reported_wait": time_waited, |
| 176 | + } |
| 177 | + entries.append(entry_info) |
| 178 | + |
| 179 | + # For each group of 4 consecutive entries the time difference between |
| 180 | + # the first and the last entry must have been greater than time_window, |
| 181 | + # because a maximum of 3 are allowed in each time_window. |
| 182 | + for i, entry in enumerate(entries[3:], start=3): |
| 183 | + first_entry = entries[i - 3] |
| 184 | + delta = entry["entered_at"] - first_entry["entered_at"] |
| 185 | + assert delta.total_seconds() > 1.0 |
| 186 | + |
| 187 | + # check the reported wait times |
| 188 | + # (NOTE: not using assert all(...), b/c the coverage check would complain) |
| 189 | + for i, entry in enumerate(entries): |
| 190 | + if i != 3: |
| 191 | + assert entry["reported_wait"] == 0.0 |
| 192 | + |
| 193 | + # The delayed entry is expected to have been delayed for a significant |
| 194 | + # chunk of the full second, and the actual and reported delay times |
| 195 | + # should reflect that. |
| 196 | + assert entries[3]["reported_wait"] > 0.7 |
| 197 | + delta = entries[3]["entered_at"] - entries[2]["entered_at"] |
| 198 | + assert delta.total_seconds() > 0.7 |
| 199 | + |
| 200 | + |
119 | 201 | class _CallAndFuture(grpc.Call, grpc.Future):
|
120 | 202 | pass
|
121 | 203 |
|
@@ -442,6 +524,22 @@ def test_reopen_failure_on_rpc_restart(self):
|
442 | 524 | assert bidi_rpc.is_active is False
|
443 | 525 | callback.assert_called_once_with(error2)
|
444 | 526 |
|
| 527 | + def test_using_throttle_on_reopen_requests(self): |
| 528 | + call = CallStub([]) |
| 529 | + start_rpc = mock.create_autospec( |
| 530 | + grpc.StreamStreamMultiCallable, instance=True, return_value=call |
| 531 | + ) |
| 532 | + should_recover = mock.Mock(spec=["__call__"], return_value=True) |
| 533 | + bidi_rpc = bidi.ResumableBidiRpc( |
| 534 | + start_rpc, should_recover, throttle_reopen=True |
| 535 | + ) |
| 536 | + |
| 537 | + patcher = mock.patch.object(bidi_rpc._reopen_throttle.__class__, "__enter__") |
| 538 | + with patcher as mock_enter: |
| 539 | + bidi_rpc._reopen() |
| 540 | + |
| 541 | + mock_enter.assert_called_once() |
| 542 | + |
445 | 543 | def test_send_not_open(self):
|
446 | 544 | bidi_rpc = bidi.ResumableBidiRpc(None, lambda _: False)
|
447 | 545 |
|
|
0 commit comments