|
20 | 20 |
|
21 | 21 | sys.path[0:0] = [""]
|
22 | 22 |
|
| 23 | +from bson.codec_options import DEFAULT_CODEC_OPTIONS |
23 | 24 | from bson.int64 import Int64
|
24 | 25 | from bson.objectid import ObjectId
|
| 26 | +from bson.raw_bson import RawBSONDocument |
25 | 27 | from bson.son import SON
|
26 | 28 |
|
27 | 29 |
|
28 | 30 | from pymongo.errors import (ConnectionFailure,
|
29 | 31 | OperationFailure,
|
30 |
| - ServerSelectionTimeoutError) |
| 32 | + ServerSelectionTimeoutError, |
| 33 | + WriteConcernError) |
31 | 34 | from pymongo.mongo_client import MongoClient
|
32 | 35 | from pymongo.operations import (InsertOne,
|
33 | 36 | DeleteMany,
|
|
43 | 46 | OvertCommandListener,
|
44 | 47 | TestCreator)
|
45 | 48 | from test.utils_spec_runner import SpecRunner
|
| 49 | +from test.version import Version |
46 | 50 |
|
47 | 51 | # Location of JSON test specifications.
|
48 | 52 | _TEST_PATH = os.path.join(
|
@@ -454,6 +458,60 @@ def test_batch_splitting_retry_fails(self):
|
454 | 458 | self.assertEqual(coll.find_one(projection={'_id': True}), {'_id': 1})
|
455 | 459 |
|
456 | 460 |
|
| 461 | +class TestWriteConcernError(IntegrationTest): |
| 462 | + @classmethod |
| 463 | + @client_context.require_replica_set |
| 464 | + @client_context.require_no_mmap |
| 465 | + @client_context.require_failCommand_fail_point |
| 466 | + def setUpClass(cls): |
| 467 | + super(TestWriteConcernError, cls).setUpClass() |
| 468 | + cls.fail_insert = { |
| 469 | + 'configureFailPoint': 'failCommand', |
| 470 | + 'mode': {'times': 2}, |
| 471 | + 'data': { |
| 472 | + 'failCommands': ['insert'], |
| 473 | + 'writeConcernError': { |
| 474 | + 'code': 91, |
| 475 | + 'errmsg': 'Replication is being shut down'}, |
| 476 | + }} |
| 477 | + |
| 478 | + @client_context.require_version_min(4, 0) |
| 479 | + def test_RetryableWriteError_error_label(self): |
| 480 | + listener = OvertCommandListener() |
| 481 | + client = rs_or_single_client( |
| 482 | + retryWrites=True, event_listeners=[listener]) |
| 483 | + |
| 484 | + # Ensure collection exists. |
| 485 | + client.pymongo_test.testcoll.insert_one({}) |
| 486 | + |
| 487 | + with self.fail_point(self.fail_insert): |
| 488 | + with self.assertRaises(WriteConcernError) as cm: |
| 489 | + client.pymongo_test.testcoll.insert_one({}) |
| 490 | + self.assertTrue(cm.exception.has_error_label( |
| 491 | + 'RetryableWriteError')) |
| 492 | + |
| 493 | + if client_context.version >= Version(4, 4): |
| 494 | + # In MongoDB 4.4+ we rely on the server returning the error label. |
| 495 | + self.assertIn( |
| 496 | + 'RetryableWriteError', |
| 497 | + listener.results['succeeded'][-1].reply['errorLabels']) |
| 498 | + |
| 499 | + @client_context.require_version_min(4, 4) |
| 500 | + def test_RetryableWriteError_error_label_RawBSONDocument(self): |
| 501 | + # using RawBSONDocument should not cause errorLabel parsing to fail |
| 502 | + with self.fail_point(self.fail_insert): |
| 503 | + with self.client.start_session() as s: |
| 504 | + s._start_retryable_write() |
| 505 | + result = self.client.pymongo_test.command( |
| 506 | + 'insert', 'testcoll', documents=[{'_id': 1}], |
| 507 | + txnNumber=s._server_session.transaction_id, session=s, |
| 508 | + codec_options=DEFAULT_CODEC_OPTIONS.with_options( |
| 509 | + document_class=RawBSONDocument)) |
| 510 | + |
| 511 | + self.assertIn('writeConcernError', result) |
| 512 | + self.assertIn('RetryableWriteError', result['errorLabels']) |
| 513 | + |
| 514 | + |
457 | 515 | # TODO: Make this a real integration test where we stepdown the primary.
|
458 | 516 | class TestRetryableWritesTxnNumber(IgnoreDeprecationsTest):
|
459 | 517 | @client_context.require_version_min(3, 6)
|
|
0 commit comments