|
20 | 20 | import multiprocessing |
21 | 21 | import time |
22 | 22 |
|
| 23 | +from unittest.mock import MagicMock, patch |
| 24 | + |
| 25 | +import redis |
| 26 | + |
23 | 27 | from grimoirelab.core.consumers.consumer import Consumer, Entry |
24 | 28 |
|
25 | 29 | from ..base import GrimoireLabTestCase |
@@ -231,3 +235,54 @@ def test_different_consumer_groups(self): |
231 | 235 | for entry, expected_entry in zip(consumer_2.entries, expected_entries): |
232 | 236 | self.assertEqual(entry.message_id.decode(), expected_entry.message_id) |
233 | 237 | self.assertDictEqual(entry.event, expected_entry.event) |
| 238 | + |
| 239 | + def test_consumer_exponential_backoff(self): |
| 240 | + """Test whether the consumer implements exponential backoff on Redis connection errors""" |
| 241 | + |
| 242 | + # Create a mock consumer instance |
| 243 | + consumer = SampleConsumer( |
| 244 | + connection=self.conn, |
| 245 | + stream_name="test_stream", |
| 246 | + consumer_group="test_group", |
| 247 | + consumer_name="test_consumer", |
| 248 | + stream_block_timeout=1000, |
| 249 | + logging_level="DEBUG", |
| 250 | + ) |
| 251 | + |
| 252 | + # Setup the mock for redis client to raise ConnectionError |
| 253 | + consumer.recover_stream_entries = MagicMock( |
| 254 | + side_effect=[ |
| 255 | + redis.exceptions.ConnectionError("fail 1"), |
| 256 | + redis.exceptions.ConnectionError("fail 2"), |
| 257 | + redis.exceptions.ConnectionError("fail 3"), |
| 258 | + [], |
| 259 | + ] |
| 260 | + ) # Succeeds on 4th call |
| 261 | + |
| 262 | + consumer.fetch_new_entries = MagicMock(return_value=[]) |
| 263 | + consumer.process_entries = MagicMock() |
| 264 | + consumer.logger = MagicMock() |
| 265 | + |
| 266 | + # Mock time.sleep to avoid real waiting |
| 267 | + with patch("time.sleep") as sleep_mock: |
| 268 | + try: |
| 269 | + consumer.start(burst=False) |
| 270 | + except StopIteration: |
| 271 | + # the recover_stream_entries mock will eventually fail |
| 272 | + pass |
| 273 | + |
| 274 | + # Check that sleep was called with exponential backoff times |
| 275 | + sleep_mock.assert_any_call(1) |
| 276 | + sleep_mock.assert_any_call(2) |
| 277 | + sleep_mock.assert_any_call(4) |
| 278 | + |
| 279 | + # Check that the logger recorded the connection errors |
| 280 | + consumer.logger.error.assert_any_call( |
| 281 | + "Could not connect to Redis instance: fail 1 Retrying in 1 seconds..." |
| 282 | + ) |
| 283 | + consumer.logger.error.assert_any_call( |
| 284 | + "Could not connect to Redis instance: fail 2 Retrying in 2 seconds..." |
| 285 | + ) |
| 286 | + consumer.logger.error.assert_any_call( |
| 287 | + "Could not connect to Redis instance: fail 3 Retrying in 4 seconds..." |
| 288 | + ) |
0 commit comments