1+ # Copyright 2025 Google LLC
2+ #
3+ # Licensed under the Apache License, Version 2.0 (the "License");
4+ # you may not use this file except in compliance with the License.
5+ # You may obtain a copy of the License at
6+ #
7+ # http://www.apache.org/licenses/LICENSE-2.0
8+ #
9+ # Unless required by applicable law or agreed to in writing, software
10+ # distributed under the License is distributed on an "AS IS" BASIS,
11+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ # See the License for the specific language governing permissions and
13+ # limitations under the License.
14+
15+ import asyncio
116import unittest
217from unittest import mock
318
419import pytest
520from google .api_core import exceptions
6- from google .api_core .retry . retry_streaming_async import AsyncStreamingRetry
21+ from google .api_core .retry_async import AsyncRetry
722
8- from google .cloud .storage ._experimental .asyncio .retry import manager
9- from google .cloud .storage ._experimental .asyncio .retry import strategy
23+ from google .cloud .storage ._experimental .asyncio .retry import bidi_stream_retry_manager as manager
24+ from google .cloud .storage ._experimental .asyncio .retry import base_strategy
1025
1126
1227def _is_retriable (exc ):
1328 return isinstance (exc , exceptions .ServiceUnavailable )
1429
15-
16- DEFAULT_TEST_RETRY = AsyncStreamingRetry (predicate = _is_retriable , deadline = 1 )
30+ DEFAULT_TEST_RETRY = AsyncRetry (predicate = _is_retriable , deadline = 1 )
1731
1832
1933class TestBidiStreamRetryManager (unittest .IsolatedAsyncioTestCase ):
2034 async def test_execute_success_on_first_try (self ):
21- """Verify the manager correctly handles a stream that succeeds immediately."""
22- mock_strategy = mock .AsyncMock (spec = strategy ._BaseResumptionStrategy )
23-
35+ mock_strategy = mock .AsyncMock (spec = base_strategy ._BaseResumptionStrategy )
2436 async def mock_stream_opener (* args , ** kwargs ):
2537 yield "response_1"
2638
2739 retry_manager = manager ._BidiStreamRetryManager (
28- strategy = mock_strategy ,
29- stream_opener = mock_stream_opener ,
30- retry_policy = DEFAULT_TEST_RETRY ,
40+ strategy = mock_strategy , stream_opener = mock_stream_opener
3141 )
32-
33- await retry_manager .execute (initial_state = {})
34-
42+ await retry_manager .execute (initial_state = {}, retry_policy = DEFAULT_TEST_RETRY )
3543 mock_strategy .generate_requests .assert_called_once ()
36- mock_strategy .update_state_from_response .assert_called_once_with (
37- "response_1" , {}
38- )
44+ mock_strategy .update_state_from_response .assert_called_once_with ("response_1" , {})
3945 mock_strategy .recover_state_on_failure .assert_not_called ()
4046
4147 async def test_execute_retries_and_succeeds (self ):
42- """Verify the manager retries on a transient error and then succeeds."""
43- mock_strategy = mock .AsyncMock (spec = strategy ._BaseResumptionStrategy )
44-
48+ mock_strategy = mock .AsyncMock (spec = base_strategy ._BaseResumptionStrategy )
4549 attempt_count = 0
46-
4750 async def mock_stream_opener (* args , ** kwargs ):
4851 nonlocal attempt_count
4952 attempt_count += 1
@@ -53,59 +56,43 @@ async def mock_stream_opener(*args, **kwargs):
5356 yield "response_2"
5457
5558 retry_manager = manager ._BidiStreamRetryManager (
56- strategy = mock_strategy ,
57- stream_opener = mock_stream_opener ,
58- retry_policy = AsyncStreamingRetry (predicate = _is_retriable , initial = 0.01 ),
59+ strategy = mock_strategy , stream_opener = mock_stream_opener
5960 )
60-
61- await retry_manager .execute (initial_state = {})
61+ retry_policy = AsyncRetry ( predicate = _is_retriable , initial = 0.01 )
62+ await retry_manager .execute (initial_state = {}, retry_policy = retry_policy )
6263
6364 self .assertEqual (attempt_count , 2 )
6465 self .assertEqual (mock_strategy .generate_requests .call_count , 2 )
6566 mock_strategy .recover_state_on_failure .assert_called_once ()
66- mock_strategy .update_state_from_response .assert_called_once_with (
67- "response_2" , {}
68- )
67+ mock_strategy .update_state_from_response .assert_called_once_with ("response_2" , {})
6968
7069 async def test_execute_fails_after_deadline_exceeded (self ):
71- """Verify the manager raises RetryError if the deadline is exceeded."""
72- mock_strategy = mock .AsyncMock (spec = strategy ._BaseResumptionStrategy )
73-
70+ mock_strategy = mock .AsyncMock (spec = base_strategy ._BaseResumptionStrategy )
7471 async def mock_stream_opener (* args , ** kwargs ):
72+ if False :
73+ yield
7574 raise exceptions .ServiceUnavailable ("Service is always down" )
7675
77- # Use a very short deadline to make the test fast.
78- fast_retry = AsyncStreamingRetry (
79- predicate = _is_retriable , deadline = 0.1 , initial = 0.05
80- )
76+ fast_retry = AsyncRetry (predicate = _is_retriable , deadline = 0.01 , initial = 0.02 )
8177 retry_manager = manager ._BidiStreamRetryManager (
82- strategy = mock_strategy ,
83- stream_opener = mock_stream_opener ,
84- retry_policy = fast_retry ,
78+ strategy = mock_strategy , stream_opener = mock_stream_opener
8579 )
80+ with pytest .raises (exceptions .RetryError , match = "Deadline of 0.01s exceeded" ):
81+ await retry_manager .execute (initial_state = {}, retry_policy = fast_retry )
8682
87- with pytest .raises (exceptions .RetryError , match = "Deadline of 0.1s exceeded" ):
88- await retry_manager .execute (initial_state = {})
89-
90- # Verify it attempted to recover state after each failure.
91- self .assertGreater (mock_strategy .recover_state_on_failure .call_count , 1 )
83+ self .assertGreater (mock_strategy .recover_state_on_failure .call_count , 0 )
9284
9385 async def test_execute_fails_immediately_on_non_retriable_error (self ):
94- """Verify the manager aborts immediately on a non-retriable error."""
95- mock_strategy = mock .AsyncMock (spec = strategy ._BaseResumptionStrategy )
96-
86+ mock_strategy = mock .AsyncMock (spec = base_strategy ._BaseResumptionStrategy )
9787 async def mock_stream_opener (* args , ** kwargs ):
88+ if False :
89+ yield
9890 raise exceptions .PermissionDenied ("Auth error" )
9991
10092 retry_manager = manager ._BidiStreamRetryManager (
101- strategy = mock_strategy ,
102- stream_opener = mock_stream_opener ,
103- retry_policy = DEFAULT_TEST_RETRY ,
93+ strategy = mock_strategy , stream_opener = mock_stream_opener
10494 )
105-
10695 with pytest .raises (exceptions .PermissionDenied ):
107- await retry_manager .execute (initial_state = {})
96+ await retry_manager .execute (initial_state = {}, retry_policy = DEFAULT_TEST_RETRY )
10897
109- # Verify that it did not try to recover or update state.
11098 mock_strategy .recover_state_on_failure .assert_not_called ()
111- mock_strategy .update_state_from_response .assert_not_called ()
0 commit comments