@@ -73,19 +73,17 @@ def test_should_not_export_again_if_not_retryable(self, mock_request):
73
73
74
74
self .assertEqual (result , LogExportResult .FAILURE )
75
75
76
- @patch (
77
- "amazon.opentelemetry.distro.exporter.otlp.aws.logs.otlp_aws_logs_exporter.sleep" , side_effect = lambda x : None
78
- )
76
+ @patch ("threading.Event.wait" , side_effect = lambda x : False )
79
77
@patch ("requests.Session.post" , return_value = retryable_response_no_header )
80
- def test_should_export_again_with_backoff_if_retryable_and_no_retry_after_header (self , mock_request , mock_sleep ):
78
+ def test_should_export_again_with_backoff_if_retryable_and_no_retry_after_header (self , mock_request , mock_wait ):
81
79
"""Tests that multiple export requests are made with exponential delay if the response status code is retryable.
82
80
But there is no Retry-After header."""
83
81
self .exporter ._timeout = 10000 # Large timeout to avoid early exit
84
82
result = self .exporter .export (self .logs )
85
83
86
- self .assertEqual (mock_sleep .call_count , _MAX_RETRYS - 1 )
84
+ self .assertEqual (mock_wait .call_count , _MAX_RETRYS - 1 )
87
85
88
- delays = mock_sleep .call_args_list
86
+ delays = mock_wait .call_args_list
89
87
90
88
for index , delay in enumerate (delays ):
91
89
expected_base = 2 ** index
@@ -97,30 +95,26 @@ def test_should_export_again_with_backoff_if_retryable_and_no_retry_after_header
97
95
self .assertEqual (mock_request .call_count , _MAX_RETRYS )
98
96
self .assertEqual (result , LogExportResult .FAILURE )
99
97
100
- @patch (
101
- "amazon.opentelemetry.distro.exporter.otlp.aws.logs.otlp_aws_logs_exporter.sleep" , side_effect = lambda x : None
102
- )
98
+ @patch ("threading.Event.wait" , side_effect = lambda x : False )
103
99
@patch (
104
100
"requests.Session.post" ,
105
101
side_effect = [retryable_response_header , retryable_response_header , retryable_response_header , good_response ],
106
102
)
107
- def test_should_export_again_with_server_delay_if_retryable_and_retry_after_header (self , mock_request , mock_sleep ):
103
+ def test_should_export_again_with_server_delay_if_retryable_and_retry_after_header (self , mock_request , mock_wait ):
108
104
"""Tests that multiple export requests are made with the server's suggested
109
105
delay if the response status code is retryable and there is a Retry-After header."""
110
106
self .exporter ._timeout = 10000 # Large timeout to avoid early exit
111
107
result = self .exporter .export (self .logs )
112
- delays = mock_sleep .call_args_list
108
+ delays = mock_wait .call_args_list
113
109
114
110
for delay in delays :
115
111
self .assertEqual (delay [0 ][0 ], 10 )
116
112
117
- self .assertEqual (mock_sleep .call_count , 3 )
113
+ self .assertEqual (mock_wait .call_count , 3 )
118
114
self .assertEqual (mock_request .call_count , 4 )
119
115
self .assertEqual (result , LogExportResult .SUCCESS )
120
116
121
- @patch (
122
- "amazon.opentelemetry.distro.exporter.otlp.aws.logs.otlp_aws_logs_exporter.sleep" , side_effect = lambda x : None
123
- )
117
+ @patch ("threading.Event.wait" , side_effect = lambda x : False )
124
118
@patch (
125
119
"requests.Session.post" ,
126
120
side_effect = [
@@ -131,13 +125,13 @@ def test_should_export_again_with_server_delay_if_retryable_and_retry_after_head
131
125
],
132
126
)
133
127
def test_should_export_again_with_backoff_delay_if_retryable_and_bad_retry_after_header (
134
- self , mock_request , mock_sleep
128
+ self , mock_request , mock_wait
135
129
):
136
130
"""Tests that multiple export requests are made with exponential delay if the response status code is retryable.
137
131
but the Retry-After header is invalid or malformed."""
138
132
self .exporter ._timeout = 10000 # Large timeout to avoid early exit
139
133
result = self .exporter .export (self .logs )
140
- delays = mock_sleep .call_args_list
134
+ delays = mock_wait .call_args_list
141
135
142
136
for index , delay in enumerate (delays ):
143
137
expected_base = 2 ** index
@@ -146,7 +140,7 @@ def test_should_export_again_with_backoff_delay_if_retryable_and_bad_retry_after
146
140
self .assertGreaterEqual (actual_delay , expected_base * 0.8 )
147
141
self .assertLessEqual (actual_delay , expected_base * 1.2 )
148
142
149
- self .assertEqual (mock_sleep .call_count , 3 )
143
+ self .assertEqual (mock_wait .call_count , 3 )
150
144
self .assertEqual (mock_request .call_count , 4 )
151
145
self .assertEqual (result , LogExportResult .SUCCESS )
152
146
@@ -158,29 +152,44 @@ def test_export_connection_error_retry(self, mock_request):
158
152
self .assertEqual (mock_request .call_count , 2 )
159
153
self .assertEqual (result , LogExportResult .SUCCESS )
160
154
161
- @patch (
162
- "amazon.opentelemetry.distro.exporter.otlp.aws.logs.otlp_aws_logs_exporter.sleep" , side_effect = lambda x : None
163
- )
155
+ @patch ("threading.Event.wait" , side_effect = lambda x : False )
164
156
@patch ("requests.Session.post" , return_value = retryable_response_no_header )
165
- def test_should_stop_retrying_when_deadline_exceeded (self , mock_request , mock_sleep ):
157
+ def test_should_stop_retrying_when_deadline_exceeded (self , mock_request , mock_wait ):
166
158
"""Tests that the exporter stops retrying when the deadline is exceeded."""
167
159
self .exporter ._timeout = 5 # Short timeout to trigger deadline check
168
160
169
- # Mock time to simulate time passing
170
161
with patch ("amazon.opentelemetry.distro.exporter.otlp.aws.logs.otlp_aws_logs_exporter.time" ) as mock_time :
171
162
# First call returns start time, subsequent calls simulate time passing
172
163
mock_time .side_effect = [0 , 0 , 1 , 2 , 4 , 8 ] # Exponential backoff would be 1, 2, 4 seconds
173
164
174
165
result = self .exporter .export (self .logs )
175
166
176
167
# Should stop before max retries due to deadline
177
- self .assertLess (mock_sleep .call_count , _MAX_RETRYS )
168
+ self .assertLess (mock_wait .call_count , _MAX_RETRYS )
178
169
self .assertLess (mock_request .call_count , _MAX_RETRYS + 1 )
179
170
self .assertEqual (result , LogExportResult .FAILURE )
180
171
181
172
# Verify total time passed is at the timeout limit
182
173
self .assertGreaterEqual (5 , self .exporter ._timeout )
183
174
175
+ @patch ("requests.Session.post" , return_value = retryable_response_no_header )
176
+ def test_export_interrupted_by_shutdown (self , mock_request ):
177
+ """Tests that export can be interrupted by shutdown during retry wait."""
178
+ self .exporter ._timeout = 10000
179
+
180
+ # Mock Event.wait to call shutdown on first call, then return True (interrupted)
181
+ # We cannot call shutdown() at the beginning since the exporter would just automatically return a FAILURE result without even attempting the export.
182
+ def mock_wait_with_shutdown (timeout ):
183
+ self .exporter .shutdown ()
184
+ return True
185
+
186
+ with patch .object (self .exporter ._shutdown_event , 'wait' , side_effect = mock_wait_with_shutdown ):
187
+ result = self .exporter .export (self .logs )
188
+
189
+ # Should make one request, then get interrupted during retry wait
190
+ self .assertEqual (mock_request .call_count , 1 )
191
+ self .assertEqual (result , LogExportResult .FAILURE )
192
+
184
193
@staticmethod
185
194
def generate_test_log_data (count = 5 ):
186
195
logs = []
0 commit comments