From 321e893fe9fb6c96b1ab221f86a46f413949f064 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Tue, 27 Oct 2026 15:42:47 -0400 Subject: [PATCH 1/4] PYTHON-6181 Sync write concern error spec tests --- .evergreen/resync-specs.sh | 1 + .../test_read_write_concern_spec.py | 17 +- .../write-concern-errors/aggregate.json | 133 ++++++++ .../write-concern-errors/bulkWrite.json | 73 ++++ .../client-bulkWrite.json | 69 ++++ .../client-side-encryption.json | 279 ++++++++++++++++ .../write-concern-errors/crud.json | 315 ++++++++++++++++++ .../db-coll-management.json | 197 +++++++++++ .../write-concern-errors/findAndModify.json | 153 +++++++++ .../write-concern-errors/gridfs.json | 285 ++++++++++++++++ .../index-management.json | 158 +++++++++ .../write-concern-errors/mapReduce.json | 89 +++++ test/test_read_write_concern_spec.py | 17 +- 13 files changed, 1776 insertions(+), 10 deletions(-) create mode 100644 test/read_write_concern/write-concern-errors/aggregate.json create mode 100644 test/read_write_concern/write-concern-errors/bulkWrite.json create mode 100644 test/read_write_concern/write-concern-errors/client-bulkWrite.json create mode 100644 test/read_write_concern/write-concern-errors/client-side-encryption.json create mode 100644 test/read_write_concern/write-concern-errors/crud.json create mode 100644 test/read_write_concern/write-concern-errors/db-coll-management.json create mode 100644 test/read_write_concern/write-concern-errors/findAndModify.json create mode 100644 test/read_write_concern/write-concern-errors/gridfs.json create mode 100644 test/read_write_concern/write-concern-errors/index-management.json create mode 100644 test/read_write_concern/write-concern-errors/mapReduce.json diff --git a/.evergreen/resync-specs.sh b/.evergreen/resync-specs.sh index 840d05bf1e..c2eb795ca1 100755 --- a/.evergreen/resync-specs.sh +++ b/.evergreen/resync-specs.sh @@ -227,6 +227,7 @@ do ;; read-write-concern|read_write_concern) cpjson read-write-concern/tests/operation read_write_concern/operation + cpjson read-write-concern/tests/write-concern-errors read_write_concern/write-concern-errors ;; retryable-reads|retryable_reads) cpjson retryable-reads/tests/ retryable_reads diff --git a/test/asynchronous/test_read_write_concern_spec.py b/test/asynchronous/test_read_write_concern_spec.py index c14fc14d1d..776c843552 100644 --- a/test/asynchronous/test_read_write_concern_spec.py +++ b/test/asynchronous/test_read_write_concern_spec.py @@ -42,7 +42,7 @@ _IS_SYNC = False -TEST_PATH = get_test_path("read_write_concern") +_TEST_PATH = get_test_path("read_write_concern") class TestReadWriteConcernSpec(AsyncIntegrationTest): @@ -298,11 +298,11 @@ def run_test(self): def create_tests(): - for dirpath, _, filenames in os.walk(TEST_PATH): + for dirpath, _, filenames in os.walk(_TEST_PATH): dirname = os.path.split(dirpath)[-1] - if dirname == "operation": - # This directory is tested by TestOperations. + if dirname == "operation" or dirname == "write-concern-errors": + # These directories have their tests generated below by generate_test_classes(). continue elif dirname == "connection-string": create_test = create_connection_string_test @@ -333,11 +333,18 @@ def create_tests(): # PyMongo does not support MapReduce. globals().update( generate_test_classes( - os.path.join(TEST_PATH, "operation"), + os.path.join(_TEST_PATH, "operation"), module=__name__, expected_failures=["MapReduce .*"], ) ) +globals().update( + generate_test_classes( + os.path.join(_TEST_PATH, "write-concern-errors"), + module=__name__, + expected_failures=["(?i)MapReduce .*"], + ) +) if __name__ == "__main__": diff --git a/test/read_write_concern/write-concern-errors/aggregate.json b/test/read_write_concern/write-concern-errors/aggregate.json new file mode 100644 index 0000000000..e4b33ae33c --- /dev/null +++ b/test/read_write_concern/write-concern-errors/aggregate.json @@ -0,0 +1,133 @@ +{ + "description": "write-concern-errors-aggregate", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "write-concern-errors-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "write-concern-errors-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "aggregate with $out reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "aggregate", + "object": "collection0", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "aggregate_out" + } + ] + }, + "expectError": { + "errorCode": 79 + } + } + ] + }, + { + "description": "aggregate with $merge reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "aggregate", + "object": "collection0", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$merge": { + "into": "aggregate_merge" + } + } + ] + }, + "expectError": { + "errorCode": 79 + } + } + ] + } + ] +} diff --git a/test/read_write_concern/write-concern-errors/bulkWrite.json b/test/read_write_concern/write-concern-errors/bulkWrite.json new file mode 100644 index 0000000000..a357c77c55 --- /dev/null +++ b/test/read_write_concern/write-concern-errors/bulkWrite.json @@ -0,0 +1,73 @@ +{ + "description": "write-concern-errors-bulkWrite", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "write-concern-errors-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "tests": [ + { + "description": "bulkWrite reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection0", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "x": 33 + } + } + } + ] + }, + "expectError": { + "isError": true + } + } + ] + } + ] +} diff --git a/test/read_write_concern/write-concern-errors/client-bulkWrite.json b/test/read_write_concern/write-concern-errors/client-bulkWrite.json new file mode 100644 index 0000000000..bedd9f2b2e --- /dev/null +++ b/test/read_write_concern/write-concern-errors/client-bulkWrite.json @@ -0,0 +1,69 @@ +{ + "description": "write-concern-errors-client-bulkWrite", + "schemaVersion": "1.21", + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + } + ], + "tests": [ + { + "description": "clientBulkWrite reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "clientBulkWrite", + "object": "client0", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "write-concern-errors-tests.coll", + "document": { + "x": 33 + } + } + } + ] + }, + "expectError": { + "writeConcernErrors": [ + { + "code": 79 + } + ] + } + } + ] + } + ] +} diff --git a/test/read_write_concern/write-concern-errors/client-side-encryption.json b/test/read_write_concern/write-concern-errors/client-side-encryption.json new file mode 100644 index 0000000000..efd5766fb3 --- /dev/null +++ b/test/read_write_concern/write-concern-errors/client-side-encryption.json @@ -0,0 +1,279 @@ +{ + "description": "write-concern-errors-client-side-encryption", + "schemaVersion": "1.8", + "runOnRequirements": [ + { + "csfle": true + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "clientEncryption": { + "id": "clientEncryption0", + "clientEncryptionOpts": { + "keyVaultClient": "client0", + "keyVaultNamespace": "keyvault.datakeys", + "kmsProviders": { + "local": { + "key": { + "$$placeholder": 1 + } + } + } + } + } + } + ], + "initialData": [ + { + "databaseName": "keyvault", + "collectionName": "datakeys", + "documents": [ + { + "_id": { + "$binary": { + "base64": "bG9jYWxrZXlsb2NhbGtleQ==", + "subType": "04" + } + }, + "keyAltNames": [ + "local_key" + ], + "keyMaterial": { + "$binary": { + "base64": "ABKBldDEoDW323yejOnIRk6YQmlD9d3eQthd16scKL75nz2LjNL9fgPDZWrFFOlqlhMCFaSrNJfGrFUjYk5JFDO7soG5Syb50k1niJoKg4ilsj0L4mpimFUtTpOr2nzZOeQtvAksEXc7gsFgq8gV7t/U3lsaXPY7I0t42DfSE8EGlPdxRjFdHnxh+OR8h7U9b8Qs5K5UuhgyeyxaBZ1Hgw==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1641024000000" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1641024000000" + } + }, + "status": 1, + "masterKey": { + "provider": "local" + } + } + ] + } + ], + "tests": [ + { + "description": "rewrapManyDataKey reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "rewrapManyDataKey", + "object": "clientEncryption0", + "arguments": { + "filter": {}, + "opts": { + "provider": "local" + } + }, + "expectError": { + "isError": true + } + } + ] + }, + { + "description": "createDataKey reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "createDataKey", + "object": "clientEncryption0", + "arguments": { + "kmsProvider": "local" + }, + "expectError": { + "errorCode": 79 + } + } + ] + }, + { + "description": "deleteKey reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "deleteKey", + "object": "clientEncryption0", + "arguments": { + "id": { + "$binary": { + "base64": "bG9jYWxrZXlsb2NhbGtleQ==", + "subType": "04" + } + } + }, + "expectError": { + "errorCode": 79 + } + } + ] + }, + { + "description": "addKeyAltName reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "addKeyAltName", + "object": "clientEncryption0", + "arguments": { + "id": { + "$binary": { + "base64": "bG9jYWxrZXlsb2NhbGtleQ==", + "subType": "04" + } + }, + "keyAltName": "new_key_alt_name" + }, + "expectError": { + "errorCode": 79 + } + } + ] + }, + { + "description": "removeKeyAltName reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "removeKeyAltName", + "object": "clientEncryption0", + "arguments": { + "id": { + "$binary": { + "base64": "bG9jYWxrZXlsb2NhbGtleQ==", + "subType": "04" + } + }, + "keyAltName": "local_key" + }, + "expectError": { + "errorCode": 79 + } + } + ] + } + ] +} diff --git a/test/read_write_concern/write-concern-errors/crud.json b/test/read_write_concern/write-concern-errors/crud.json new file mode 100644 index 0000000000..7a92433869 --- /dev/null +++ b/test/read_write_concern/write-concern-errors/crud.json @@ -0,0 +1,315 @@ +{ + "description": "write-concern-errors-crud", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "write-concern-errors-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "tests": [ + { + "description": "insertOne reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 33 + } + }, + "expectError": { + "errorCode": 79 + } + } + ] + }, + { + "description": "insertMany reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "insertMany", + "object": "collection0", + "arguments": { + "documents": [ + { + "x": 44 + }, + { + "x": 55 + } + ] + }, + "expectError": { + "isError": true + } + } + ] + }, + { + "description": "updateOne reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "updateOne", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "x": 111 + } + } + }, + "expectError": { + "errorCode": 79 + } + } + ] + }, + { + "description": "updateMany reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "updateMany", + "object": "collection0", + "arguments": { + "filter": {}, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectError": { + "errorCode": 79 + } + } + ] + }, + { + "description": "replaceOne reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "replaceOne", + "object": "collection0", + "arguments": { + "filter": { + "_id": 2 + }, + "replacement": { + "x": 222 + } + }, + "expectError": { + "errorCode": 79 + } + } + ] + }, + { + "description": "deleteOne reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "deleteOne", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorCode": 79 + } + } + ] + }, + { + "description": "deleteMany reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "deleteMany", + "object": "collection0", + "arguments": { + "filter": {} + }, + "expectError": { + "errorCode": 79 + } + } + ] + } + ] +} diff --git a/test/read_write_concern/write-concern-errors/db-coll-management.json b/test/read_write_concern/write-concern-errors/db-coll-management.json new file mode 100644 index 0000000000..11154849ce --- /dev/null +++ b/test/read_write_concern/write-concern-errors/db-coll-management.json @@ -0,0 +1,197 @@ +{ + "description": "write-concern-errors-db-coll-management", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "write-concern-errors-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "write-concern-errors-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "createCollection reports a writeConcernError", + "operations": [ + { + "name": "dropCollection", + "object": "database0", + "arguments": { + "collection": "coll_create" + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "create" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "createCollection", + "object": "database0", + "arguments": { + "collection": "coll_create" + }, + "expectError": { + "errorCode": 79 + } + } + ] + }, + { + "description": "dropCollection reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "drop" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "dropCollection", + "object": "database0", + "arguments": { + "collection": "coll" + }, + "expectError": { + "errorCode": 79 + } + } + ] + }, + { + "description": "rename reports a writeConcernError", + "operations": [ + { + "name": "dropCollection", + "object": "database0", + "arguments": { + "collection": "coll_renamed" + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "renameCollection" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "rename", + "object": "collection0", + "arguments": { + "to": "coll_renamed" + }, + "expectError": { + "errorCode": 79 + } + } + ] + }, + { + "description": "dropDatabase reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "dropDatabase" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "dropDatabase", + "object": "client0", + "arguments": { + "database": "write-concern-errors-dropdb" + }, + "expectError": { + "errorCode": 79 + } + } + ] + } + ] +} diff --git a/test/read_write_concern/write-concern-errors/findAndModify.json b/test/read_write_concern/write-concern-errors/findAndModify.json new file mode 100644 index 0000000000..91dbf833aa --- /dev/null +++ b/test/read_write_concern/write-concern-errors/findAndModify.json @@ -0,0 +1,153 @@ +{ + "description": "write-concern-errors-findAndModify", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "write-concern-errors-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "tests": [ + { + "description": "findOneAndUpdate reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "findOneAndUpdate", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "x": 111 + } + } + }, + "expectError": { + "errorCode": 79 + } + } + ] + }, + { + "description": "findOneAndReplace reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "findOneAndReplace", + "object": "collection0", + "arguments": { + "filter": { + "_id": 2 + }, + "replacement": { + "x": 222 + } + }, + "expectError": { + "errorCode": 79 + } + } + ] + }, + { + "description": "findOneAndDelete reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "findOneAndDelete", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorCode": 79 + } + } + ] + } + ] +} diff --git a/test/read_write_concern/write-concern-errors/gridfs.json b/test/read_write_concern/write-concern-errors/gridfs.json new file mode 100644 index 0000000000..d433c68e08 --- /dev/null +++ b/test/read_write_concern/write-concern-errors/gridfs.json @@ -0,0 +1,285 @@ +{ + "description": "write-concern-errors-gridfs", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "write-concern-errors-tests" + } + }, + { + "bucket": { + "id": "bucket0", + "database": "database0" + } + } + ], + "initialData": [ + { + "databaseName": "write-concern-errors-tests", + "collectionName": "fs.files", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000001" + }, + "length": 0, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "filename" + } + ] + }, + { + "databaseName": "write-concern-errors-tests", + "collectionName": "fs.chunks", + "documents": [] + } + ], + "tests": [ + { + "description": "upload reports a writeConcernError from a chunk insert", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "upload", + "object": "bucket0", + "arguments": { + "filename": "filename", + "source": { + "$$hexBytes": "1122334455" + } + }, + "expectError": { + "errorCode": 79 + } + } + ] + }, + { + "description": "upload reports a writeConcernError from the files document insert", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "skip": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "upload", + "object": "bucket0", + "arguments": { + "filename": "filename", + "source": { + "$$hexBytes": "1122334455" + } + }, + "expectError": { + "errorCode": 79 + } + } + ] + }, + { + "description": "delete reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "delete", + "object": "bucket0", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + } + }, + "expectError": { + "errorCode": 79 + } + } + ] + }, + { + "description": "deleteByName reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "deleteByName", + "object": "bucket0", + "arguments": { + "filename": "filename" + }, + "expectError": { + "errorCode": 79 + } + } + ] + }, + { + "description": "file rename reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "rename", + "object": "bucket0", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + }, + "newFilename": "newfilename" + }, + "expectError": { + "errorCode": 79 + } + } + ] + }, + { + "description": "renameByName reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "renameByName", + "object": "bucket0", + "arguments": { + "filename": "filename", + "newFilename": "newfilename" + }, + "expectError": { + "errorCode": 79 + } + } + ] + } + ] +} diff --git a/test/read_write_concern/write-concern-errors/index-management.json b/test/read_write_concern/write-concern-errors/index-management.json new file mode 100644 index 0000000000..56fee80e7a --- /dev/null +++ b/test/read_write_concern/write-concern-errors/index-management.json @@ -0,0 +1,158 @@ +{ + "description": "write-concern-errors-index-management", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "write-concern-errors-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "tests": [ + { + "description": "createIndex reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "createIndexes" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "createIndex", + "object": "collection0", + "arguments": { + "keys": { + "x": 1 + } + }, + "expectError": { + "errorCode": 79 + } + } + ] + }, + { + "description": "dropIndex reports a writeConcernError", + "operations": [ + { + "name": "createIndex", + "object": "collection0", + "arguments": { + "keys": { + "x": 1 + }, + "name": "x_1" + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "dropIndexes" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "dropIndex", + "object": "collection0", + "arguments": { + "name": "x_1" + }, + "expectError": { + "errorCode": 79 + } + } + ] + }, + { + "description": "dropIndexes reports a writeConcernError", + "operations": [ + { + "name": "createIndex", + "object": "collection0", + "arguments": { + "keys": { + "x": 1 + }, + "name": "x_1" + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "dropIndexes" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "dropIndexes", + "object": "collection0", + "expectError": { + "errorCode": 79 + } + } + ] + } + ] +} diff --git a/test/read_write_concern/write-concern-errors/mapReduce.json b/test/read_write_concern/write-concern-errors/mapReduce.json new file mode 100644 index 0000000000..7701a43934 --- /dev/null +++ b/test/read_write_concern/write-concern-errors/mapReduce.json @@ -0,0 +1,89 @@ +{ + "description": "write-concern-errors-mapReduce", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "write-concern-errors-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "write-concern-errors-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "mapReduce with an output collection reports a writeConcernError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "mapReduce" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern" + } + } + } + } + }, + { + "name": "mapReduce", + "object": "collection0", + "arguments": { + "map": { + "$code": "function inc() { return emit(0, this.x + 1) }" + }, + "reduce": { + "$code": "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" + }, + "out": { + "replace": "mapreduce_out" + } + }, + "expectError": { + "errorCode": 79 + } + } + ] + } + ] +} diff --git a/test/test_read_write_concern_spec.py b/test/test_read_write_concern_spec.py index fd6f161346..30b1325e13 100644 --- a/test/test_read_write_concern_spec.py +++ b/test/test_read_write_concern_spec.py @@ -42,7 +42,7 @@ _IS_SYNC = True -TEST_PATH = get_test_path("read_write_concern") +_TEST_PATH = get_test_path("read_write_concern") class TestReadWriteConcernSpec(IntegrationTest): @@ -294,11 +294,11 @@ def run_test(self): def create_tests(): - for dirpath, _, filenames in os.walk(TEST_PATH): + for dirpath, _, filenames in os.walk(_TEST_PATH): dirname = os.path.split(dirpath)[-1] - if dirname == "operation": - # This directory is tested by TestOperations. + if dirname == "operation" or dirname == "write-concern-errors": + # These directories have their tests generated below by generate_test_classes(). continue elif dirname == "connection-string": create_test = create_connection_string_test @@ -329,11 +329,18 @@ def create_tests(): # PyMongo does not support MapReduce. globals().update( generate_test_classes( - os.path.join(TEST_PATH, "operation"), + os.path.join(_TEST_PATH, "operation"), module=__name__, expected_failures=["MapReduce .*"], ) ) +globals().update( + generate_test_classes( + os.path.join(_TEST_PATH, "write-concern-errors"), + module=__name__, + expected_failures=["(?i)MapReduce .*"], + ) +) if __name__ == "__main__": From 900c32e0124e4d36db1cf7988c1f1903e2ff3c50 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Tue, 27 Oct 2026 16:45:37 -0400 Subject: [PATCH 2/4] Fix write concern error handling in spec test --- test/asynchronous/unified_format.py | 47 ++++++++++++++++++----------- test/unified_format.py | 47 ++++++++++++++++++----------- 2 files changed, 58 insertions(+), 36 deletions(-) diff --git a/test/asynchronous/unified_format.py b/test/asynchronous/unified_format.py index 06062ee8fc..dfb171780c 100644 --- a/test/asynchronous/unified_format.py +++ b/test/asynchronous/unified_format.py @@ -186,6 +186,16 @@ async def is_run_on_requirement_satisfied(requirement): ) +def _unwrap_server_error(exception): + """Return the error that carries the server's response details and labels.""" + if isinstance(exception, ClientBulkWriteException) and exception.error is not None: + return exception.error + # EncryptionError wraps the original exception. + if isinstance(exception, EncryptionError) and isinstance(exception.cause, PyMongoError): + return exception.cause + return exception + + class NonLazyCursor: """A find cursor proxy that creates the remote cursor when initialized.""" @@ -673,11 +683,10 @@ def process_error(self, exception, spec): error_labels_omit = spec.get("errorLabelsOmit") expect_result = spec.get("expectResult") error_response = spec.get("errorResponse") + write_concern_errors = spec.get("writeConcernErrors") + server_error = _unwrap_server_error(exception) if error_response: - if isinstance(exception, ClientBulkWriteException): - self.match_evaluator.match_result(error_response, exception.error.details) - else: - self.match_evaluator.match_result(error_response, exception.details) + self.match_evaluator.match_result(error_response, server_error.details) if is_error: # already satisfied because exception was raised @@ -714,32 +723,34 @@ def process_error(self, exception, spec): self.assertIn(error_contains.lower(), errmsg) if error_code: - if isinstance(exception, ClientBulkWriteException): - self.assertEqual(error_code, exception.error.details.get("code")) - else: - self.assertEqual(error_code, exception.details.get("code")) + self.assertEqual(error_code, server_error.details.get("code")) if error_code_name: - if isinstance(exception, ClientBulkWriteException): - self.assertEqual(error_code, exception.error.details.get("codeName")) - else: - self.assertEqual(error_code_name, exception.details.get("codeName")) + self.assertEqual(error_code_name, server_error.details.get("codeName")) if error_labels_contain: - if isinstance(exception, ClientBulkWriteException): - error = exception.error - else: - error = exception labels = [ - err_label for err_label in error_labels_contain if error.has_error_label(err_label) + err_label + for err_label in error_labels_contain + if server_error.has_error_label(err_label) ] self.assertEqual(labels, error_labels_contain) if error_labels_omit: for err_label in error_labels_omit: - if exception.has_error_label(err_label): + if server_error.has_error_label(err_label): self.fail(f"Exception '{exception}' unexpectedly had label '{err_label}'") + if write_concern_errors is not None: + if not isinstance(exception, ClientBulkWriteException): + self.fail( + f"writeConcernErrors can only be specified with {ClientBulkWriteException} exceptions, got {exception}" + ) + actual_write_concern_errors = exception.details.get("writeConcernErrors", []) + self.assertEqual(len(write_concern_errors), len(actual_write_concern_errors)) + for expected_wce, actual_wce in zip(write_concern_errors, actual_write_concern_errors): + self.match_evaluator.match_result(expected_wce, actual_wce) + if expect_result: if isinstance(exception, BulkWriteError): result = parse_bulk_write_error_result(exception) diff --git a/test/unified_format.py b/test/unified_format.py index 4dcada2046..587475dcb9 100644 --- a/test/unified_format.py +++ b/test/unified_format.py @@ -185,6 +185,16 @@ def is_run_on_requirement_satisfied(requirement): ) +def _unwrap_server_error(exception): + """Return the error that carries the server's response details and labels.""" + if isinstance(exception, ClientBulkWriteException) and exception.error is not None: + return exception.error + # EncryptionError wraps the original exception. + if isinstance(exception, EncryptionError) and isinstance(exception.cause, PyMongoError): + return exception.cause + return exception + + class NonLazyCursor: """A find cursor proxy that creates the remote cursor when initialized.""" @@ -672,11 +682,10 @@ def process_error(self, exception, spec): error_labels_omit = spec.get("errorLabelsOmit") expect_result = spec.get("expectResult") error_response = spec.get("errorResponse") + write_concern_errors = spec.get("writeConcernErrors") + server_error = _unwrap_server_error(exception) if error_response: - if isinstance(exception, ClientBulkWriteException): - self.match_evaluator.match_result(error_response, exception.error.details) - else: - self.match_evaluator.match_result(error_response, exception.details) + self.match_evaluator.match_result(error_response, server_error.details) if is_error: # already satisfied because exception was raised @@ -713,32 +722,34 @@ def process_error(self, exception, spec): self.assertIn(error_contains.lower(), errmsg) if error_code: - if isinstance(exception, ClientBulkWriteException): - self.assertEqual(error_code, exception.error.details.get("code")) - else: - self.assertEqual(error_code, exception.details.get("code")) + self.assertEqual(error_code, server_error.details.get("code")) if error_code_name: - if isinstance(exception, ClientBulkWriteException): - self.assertEqual(error_code, exception.error.details.get("codeName")) - else: - self.assertEqual(error_code_name, exception.details.get("codeName")) + self.assertEqual(error_code_name, server_error.details.get("codeName")) if error_labels_contain: - if isinstance(exception, ClientBulkWriteException): - error = exception.error - else: - error = exception labels = [ - err_label for err_label in error_labels_contain if error.has_error_label(err_label) + err_label + for err_label in error_labels_contain + if server_error.has_error_label(err_label) ] self.assertEqual(labels, error_labels_contain) if error_labels_omit: for err_label in error_labels_omit: - if exception.has_error_label(err_label): + if server_error.has_error_label(err_label): self.fail(f"Exception '{exception}' unexpectedly had label '{err_label}'") + if write_concern_errors is not None: + if not isinstance(exception, ClientBulkWriteException): + self.fail( + f"writeConcernErrors can only be specified with {ClientBulkWriteException} exceptions, got {exception}" + ) + actual_write_concern_errors = exception.details.get("writeConcernErrors", []) + self.assertEqual(len(write_concern_errors), len(actual_write_concern_errors)) + for expected_wce, actual_wce in zip(write_concern_errors, actual_write_concern_errors): + self.match_evaluator.match_result(expected_wce, actual_wce) + if expect_result: if isinstance(exception, BulkWriteError): result = parse_bulk_write_error_result(exception) From 69c80272ca0237cd85d42808db71c634eb745845 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Wed, 28 Oct 2026 10:07:51 -0400 Subject: [PATCH 3/4] Format actual write concern errors to match spec tests --- test/asynchronous/unified_format.py | 4 +++- test/unified_format.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/test/asynchronous/unified_format.py b/test/asynchronous/unified_format.py index dfb171780c..55332b7829 100644 --- a/test/asynchronous/unified_format.py +++ b/test/asynchronous/unified_format.py @@ -749,7 +749,9 @@ def process_error(self, exception, spec): actual_write_concern_errors = exception.details.get("writeConcernErrors", []) self.assertEqual(len(write_concern_errors), len(actual_write_concern_errors)) for expected_wce, actual_wce in zip(write_concern_errors, actual_write_concern_errors): - self.match_evaluator.match_result(expected_wce, actual_wce) + # Use the spec's WriteConcernError field names. + formatted_actual_wce = {"code": actual_wce["code"], "message": actual_wce["errmsg"]} + self.match_evaluator.match_result(expected_wce, formatted_actual_wce) if expect_result: if isinstance(exception, BulkWriteError): diff --git a/test/unified_format.py b/test/unified_format.py index 587475dcb9..cd51e1de9a 100644 --- a/test/unified_format.py +++ b/test/unified_format.py @@ -748,7 +748,9 @@ def process_error(self, exception, spec): actual_write_concern_errors = exception.details.get("writeConcernErrors", []) self.assertEqual(len(write_concern_errors), len(actual_write_concern_errors)) for expected_wce, actual_wce in zip(write_concern_errors, actual_write_concern_errors): - self.match_evaluator.match_result(expected_wce, actual_wce) + # Use the spec's WriteConcernError field names. + formatted_actual_wce = {"code": actual_wce["code"], "message": actual_wce["errmsg"]} + self.match_evaluator.match_result(expected_wce, formatted_actual_wce) if expect_result: if isinstance(exception, BulkWriteError): From fd483132aaff91edd152537deb10f7b3e1514afc Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Wed, 28 Oct 2026 11:48:56 -0400 Subject: [PATCH 4/4] Make write concern error message optional in test runner --- test/asynchronous/unified_format.py | 5 ++++- test/unified_format.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/test/asynchronous/unified_format.py b/test/asynchronous/unified_format.py index 55332b7829..31aca76cab 100644 --- a/test/asynchronous/unified_format.py +++ b/test/asynchronous/unified_format.py @@ -750,7 +750,10 @@ def process_error(self, exception, spec): self.assertEqual(len(write_concern_errors), len(actual_write_concern_errors)) for expected_wce, actual_wce in zip(write_concern_errors, actual_write_concern_errors): # Use the spec's WriteConcernError field names. - formatted_actual_wce = {"code": actual_wce["code"], "message": actual_wce["errmsg"]} + formatted_actual_wce = { + "code": actual_wce.get("code", ""), + "message": actual_wce.get("errmsg", ""), + } self.match_evaluator.match_result(expected_wce, formatted_actual_wce) if expect_result: diff --git a/test/unified_format.py b/test/unified_format.py index cd51e1de9a..2ebf7c201b 100644 --- a/test/unified_format.py +++ b/test/unified_format.py @@ -749,7 +749,10 @@ def process_error(self, exception, spec): self.assertEqual(len(write_concern_errors), len(actual_write_concern_errors)) for expected_wce, actual_wce in zip(write_concern_errors, actual_write_concern_errors): # Use the spec's WriteConcernError field names. - formatted_actual_wce = {"code": actual_wce["code"], "message": actual_wce["errmsg"]} + formatted_actual_wce = { + "code": actual_wce.get("code", ""), + "message": actual_wce.get("errmsg", ""), + } self.match_evaluator.match_result(expected_wce, formatted_actual_wce) if expect_result: