Skip to content

Commit b448af5

Browse files
committed
Updated prose test
1 parent 409c626 commit b448af5

4 files changed

Lines changed: 124 additions & 72 deletions

File tree

test/asynchronous/test_client_backpressure.py

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from unittest.mock import patch
2424

2525
from pymongo.common import MAX_ADAPTIVE_RETRIES
26+
from pymongo.monitoring import CommandFailedEvent
2627

2728
sys.path[0:0] = [""]
2829

@@ -229,7 +230,7 @@ async def test_01_operation_retry_uses_exponential_backoff(self, random_func):
229230
self.assertTrue(abs((end1 - start1) - (end0 - start0 + 0.3)) < 0.3)
230231

231232
@async_client_context.require_failCommand_appName
232-
async def test_02_overload_retries_limited(self):
233+
async def test_03_overload_retries_limited(self):
233234
# Drivers should test that overload errors are retried a maximum of two times.
234235

235236
# 1. Let `client` be a `MongoClient`.
@@ -261,7 +262,7 @@ async def test_02_overload_retries_limited(self):
261262
self.assertEqual(len(self.listener.started_events), MAX_ADAPTIVE_RETRIES + 1)
262263

263264
@async_client_context.require_failCommand_appName
264-
async def test_03_overload_retries_limited_configured(self):
265+
async def test_04_overload_retries_limited_configured(self):
265266
# Drivers should test that overload errors are retried a maximum of maxAdaptiveRetries times.
266267
max_retries = 1
267268

@@ -295,40 +296,6 @@ async def test_03_overload_retries_limited_configured(self):
295296
# 6. Assert that the total number of started commands is max_retries + 1.
296297
self.assertEqual(len(self.listener.started_events), max_retries + 1)
297298

298-
@async_client_context.require_failCommand_fail_point
299-
async def test_04_backoff_is_not_applied_for_non_overload_errors(self):
300-
# Drivers should test that backoff is not applied for non-overload retryable errors.
301-
if _IS_SYNC:
302-
mock_target = "pymongo.synchronous.helpers._RetryPolicy.backoff"
303-
else:
304-
mock_target = "pymongo.asynchronous.helpers._RetryPolicy.backoff"
305-
306-
# 1. Let `client` be a `MongoClient`.
307-
client = self.client
308-
309-
# 2. Let `coll` be a collection.
310-
coll = client.test.test
311-
await coll.insert_one({})
312-
313-
# 3. Configure a failpoint with a retryable error that is NOT an overload error.
314-
failpoint = {
315-
"configureFailPoint": "failCommand",
316-
"mode": {"times": 1},
317-
"data": {
318-
"failCommands": ["find"],
319-
"errorCode": 91, # ShutdownInProgress
320-
"errorLabels": ["RetryableError"],
321-
},
322-
}
323-
324-
# 4. Perform a find operation with `coll` that succeeds on its first retry attempt.
325-
with mock.patch(mock_target, return_value=1) as mock_backoff:
326-
async with self.fail_point(failpoint):
327-
await coll.find_one({})
328-
329-
# 5. Assert that no backoff was used for the retry attempt.
330-
mock_backoff.assert_not_called()
331-
332299

333300
# Location of JSON test specifications.
334301
if _IS_SYNC:

test/asynchronous/test_retryable_reads.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import sys
2121
import threading
2222
from test.asynchronous.utils import async_set_fail_point
23+
from unittest import mock
2324

2425
from pymongo import MongoClient
2526
from pymongo.common import MAX_ADAPTIVE_RETRIES
@@ -452,6 +453,64 @@ def failed(event: CommandFailedEvent) -> None:
452453
started_finds = [e for e in listener.started_events if e.command_name == "find"]
453454
self.assertEqual(len(started_finds), MAX_ADAPTIVE_RETRIES + 1)
454455

456+
@async_client_context.require_failCommand_fail_point
457+
async def test_backoff_is_not_applied_for_non_overload_errors(self):
458+
if _IS_SYNC:
459+
mock_target = "pymongo.synchronous.helpers._RetryPolicy.backoff"
460+
else:
461+
mock_target = "pymongo.asynchronous.helpers._RetryPolicy.backoff"
462+
463+
# Create a client.
464+
listener = OvertCommandListener()
465+
466+
# Configure the client to listen to CommandFailedEvents. In the attached listener, configure a fail point with error
467+
# code `91` (ShutdownInProgress) and `RetryableError` and `SystemOverloadedError` labels.
468+
overload_fail_point = {
469+
"configureFailPoint": "failCommand",
470+
"mode": {"times": 1},
471+
"data": {
472+
"failCommands": ["find"],
473+
"errorLabels": ["RetryableError", "SystemOverloadedError"],
474+
"errorCode": 91,
475+
},
476+
}
477+
478+
# Configure a fail point with error code `91` (ShutdownInProgress) with only the `RetryableError` error label.
479+
non_overload_fail_point = {
480+
"configureFailPoint": "failCommand",
481+
"mode": "alwaysOn",
482+
"data": {
483+
"failCommands": ["find"],
484+
"errorCode": 91,
485+
"errorLabels": ["RetryableError"],
486+
},
487+
}
488+
489+
def failed(event: CommandFailedEvent) -> None:
490+
# Configure the fail point command only if the failed event is for the 91 error configured in step 2.
491+
if listener.failed_events:
492+
return
493+
assert event.failure["code"] == 91
494+
self.configure_fail_point_sync(non_overload_fail_point)
495+
self.addCleanup(self.configure_fail_point_sync, {}, off=True)
496+
listener.failed_events.append(event)
497+
498+
listener.failed = failed
499+
500+
client = await self.async_rs_client(event_listeners=[listener])
501+
await client.test.test.insert_one({})
502+
503+
self.configure_fail_point_sync(overload_fail_point)
504+
self.addCleanup(self.configure_fail_point_sync, {}, off=True)
505+
506+
# Perform a findOne operation with coll. Expect the operation to fail.
507+
with mock.patch(mock_target, return_value=0) as mock_backoff:
508+
with self.assertRaises(PyMongoError):
509+
await client.test.test.find_one()
510+
511+
# Assert that backoff was applied only once for the initial overload error and not for the subsequent non-overload retryable errors.
512+
self.assertEqual(mock_backoff.call_count, 1)
513+
455514

456515
if __name__ == "__main__":
457516
unittest.main()

test/test_client_backpressure.py

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from unittest.mock import patch
2424

2525
from pymongo.common import MAX_ADAPTIVE_RETRIES
26+
from pymongo.monitoring import CommandFailedEvent
2627

2728
sys.path[0:0] = [""]
2829

@@ -229,7 +230,7 @@ def test_01_operation_retry_uses_exponential_backoff(self, random_func):
229230
self.assertTrue(abs((end1 - start1) - (end0 - start0 + 0.3)) < 0.3)
230231

231232
@client_context.require_failCommand_appName
232-
def test_02_overload_retries_limited(self):
233+
def test_03_overload_retries_limited(self):
233234
# Drivers should test that overload errors are retried a maximum of two times.
234235

235236
# 1. Let `client` be a `MongoClient`.
@@ -261,7 +262,7 @@ def test_02_overload_retries_limited(self):
261262
self.assertEqual(len(self.listener.started_events), MAX_ADAPTIVE_RETRIES + 1)
262263

263264
@client_context.require_failCommand_appName
264-
def test_03_overload_retries_limited_configured(self):
265+
def test_04_overload_retries_limited_configured(self):
265266
# Drivers should test that overload errors are retried a maximum of maxAdaptiveRetries times.
266267
max_retries = 1
267268

@@ -293,40 +294,6 @@ def test_03_overload_retries_limited_configured(self):
293294
# 6. Assert that the total number of started commands is max_retries + 1.
294295
self.assertEqual(len(self.listener.started_events), max_retries + 1)
295296

296-
@client_context.require_failCommand_fail_point
297-
def test_04_backoff_is_not_applied_for_non_overload_errors(self):
298-
# Drivers should test that backoff is not applied for non-overload retryable errors.
299-
if _IS_SYNC:
300-
mock_target = "pymongo.synchronous.helpers._RetryPolicy.backoff"
301-
else:
302-
mock_target = "pymongo.helpers._RetryPolicy.backoff"
303-
304-
# 1. Let `client` be a `MongoClient`.
305-
client = self.client
306-
307-
# 2. Let `coll` be a collection.
308-
coll = client.test.test
309-
coll.insert_one({})
310-
311-
# 3. Configure a failpoint with a retryable error that is NOT an overload error.
312-
failpoint = {
313-
"configureFailPoint": "failCommand",
314-
"mode": {"times": 1},
315-
"data": {
316-
"failCommands": ["find"],
317-
"errorCode": 91, # ShutdownInProgress
318-
"errorLabels": ["RetryableError"],
319-
},
320-
}
321-
322-
# 4. Perform a find operation with `coll` that succeeds on its first retry attempt.
323-
with mock.patch(mock_target, return_value=1) as mock_backoff:
324-
with self.fail_point(failpoint):
325-
coll.find_one({})
326-
327-
# 5. Assert that no backoff was used for the retry attempt.
328-
mock_backoff.assert_not_called()
329-
330297

331298
# Location of JSON test specifications.
332299
if _IS_SYNC:

test/test_retryable_reads.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import sys
2121
import threading
2222
from test.utils import set_fail_point
23+
from unittest import mock
2324

2425
from pymongo import MongoClient
2526
from pymongo.common import MAX_ADAPTIVE_RETRIES
@@ -450,6 +451,64 @@ def failed(event: CommandFailedEvent) -> None:
450451
started_finds = [e for e in listener.started_events if e.command_name == "find"]
451452
self.assertEqual(len(started_finds), MAX_ADAPTIVE_RETRIES + 1)
452453

454+
@client_context.require_failCommand_fail_point
455+
def test_backoff_is_not_applied_for_non_overload_errors(self):
456+
if _IS_SYNC:
457+
mock_target = "pymongo.synchronous.helpers._RetryPolicy.backoff"
458+
else:
459+
mock_target = "pymongo.helpers._RetryPolicy.backoff"
460+
461+
# Create a client.
462+
listener = OvertCommandListener()
463+
464+
# Configure the client to listen to CommandFailedEvents. In the attached listener, configure a fail point with error
465+
# code `91` (ShutdownInProgress) and `RetryableError` and `SystemOverloadedError` labels.
466+
overload_fail_point = {
467+
"configureFailPoint": "failCommand",
468+
"mode": {"times": 1},
469+
"data": {
470+
"failCommands": ["find"],
471+
"errorLabels": ["RetryableError", "SystemOverloadedError"],
472+
"errorCode": 91,
473+
},
474+
}
475+
476+
# Configure a fail point with error code `91` (ShutdownInProgress) with only the `RetryableError` error label.
477+
non_overload_fail_point = {
478+
"configureFailPoint": "failCommand",
479+
"mode": "alwaysOn",
480+
"data": {
481+
"failCommands": ["find"],
482+
"errorCode": 91,
483+
"errorLabels": ["RetryableError"],
484+
},
485+
}
486+
487+
def failed(event: CommandFailedEvent) -> None:
488+
# Configure the fail point command only if the failed event is for the 91 error configured in step 2.
489+
if listener.failed_events:
490+
return
491+
assert event.failure["code"] == 91
492+
self.configure_fail_point_sync(non_overload_fail_point)
493+
self.addCleanup(self.configure_fail_point_sync, {}, off=True)
494+
listener.failed_events.append(event)
495+
496+
listener.failed = failed
497+
498+
client = self.rs_client(event_listeners=[listener])
499+
client.test.test.insert_one({})
500+
501+
self.configure_fail_point_sync(overload_fail_point)
502+
self.addCleanup(self.configure_fail_point_sync, {}, off=True)
503+
504+
# Perform a findOne operation with coll. Expect the operation to fail.
505+
with mock.patch(mock_target, return_value=0) as mock_backoff:
506+
with self.assertRaises(PyMongoError):
507+
client.test.test.find_one()
508+
509+
# Assert that backoff was applied only once for the initial overload error and not for the subsequent non-overload retryable errors.
510+
self.assertEqual(mock_backoff.call_count, 1)
511+
453512

454513
if __name__ == "__main__":
455514
unittest.main()

0 commit comments

Comments
 (0)