diff --git a/src/test/spec/json/retryable-writes/unified/insertOne-serverErrors.json b/src/test/spec/json/retryable-writes/unified/insertOne-serverErrors.json index f404adcaf..8edafb702 100644 --- a/src/test/spec/json/retryable-writes/unified/insertOne-serverErrors.json +++ b/src/test/spec/json/retryable-writes/unified/insertOne-serverErrors.json @@ -739,7 +739,7 @@ ] }, { - "description": "InsertOne fails after WriteConcernError WriteConcernFailed", + "description": "InsertOne fails after WriteConcernError WriteConcernTimeout", "operations": [ { "name": "failPoint", @@ -757,7 +757,6 @@ ], "writeConcernError": { "code": 64, - "codeName": "WriteConcernFailed", "errmsg": "waiting for replication timed out", "errInfo": { "wtimeout": true diff --git a/src/test/spec/json/retryable-writes/unified/insertOne-serverErrors.yml b/src/test/spec/json/retryable-writes/unified/insertOne-serverErrors.yml index 95fa71ec7..6fd43365d 100644 --- a/src/test/spec/json/retryable-writes/unified/insertOne-serverErrors.yml +++ b/src/test/spec/json/retryable-writes/unified/insertOne-serverErrors.yml @@ -339,7 +339,7 @@ tests: - { _id: 2, x: 22 } - { _id: 3, x: 33 } # The write was still applied. - - description: 'InsertOne fails after WriteConcernError WriteConcernFailed' + description: 'InsertOne fails after WriteConcernError WriteConcernTimeout' operations: - name: failPoint @@ -353,7 +353,6 @@ tests: failCommands: [ insert ] writeConcernError: code: 64 - codeName: WriteConcernFailed errmsg: 'waiting for replication timed out' errInfo: wtimeout: true diff --git a/src/test/spec/json/transactions-convenient-api/README.md b/src/test/spec/json/transactions-convenient-api/README.md new file mode 100644 index 000000000..a797a3182 --- /dev/null +++ b/src/test/spec/json/transactions-convenient-api/README.md @@ -0,0 +1,48 @@ +# Convenient API for Transactions Tests + +______________________________________________________________________ + +## Introduction + +The YAML and JSON files in this directory are platform-independent tests meant to exercise a driver's implementation of +the Convenient API for Transactions spec. These tests utilize the +[Unified Test Format](../../unified-test-format/unified-test-format.md). + +Several prose tests, which are not easily expressed in YAML, are also presented in this file. Those tests will need to +be manually implemented by each driver. + +## Prose Tests + +### Callback Raises a Custom Error + +Write a callback that raises a custom exception or error that does not include either UnknownTransactionCommitResult or +TransientTransactionError error labels. Execute this callback using `withTransaction` and assert that the callback's +error bypasses any retry logic within `withTransaction` and is propagated to the caller of `withTransaction`. + +### Callback Returns a Value + +Write a callback that returns a custom value (e.g. boolean, string, object). Execute this callback using +`withTransaction` and assert that the callback's return value is propagated to the caller of `withTransaction`. + +### Retry Timeout is Enforced + +Drivers should test that `withTransaction` enforces a non-configurable timeout before retrying both commits and entire +transactions. Specifically, three cases should be checked: + +- If the callback raises an error with the TransientTransactionError label and the retry timeout has been exceeded, + `withTransaction` should propagate the error to its caller. +- If committing raises an error with the UnknownTransactionCommitResult label, and the retry timeout has been exceeded, + `withTransaction` should propagate the error to its caller. +- If committing raises an error with the TransientTransactionError label and the retry timeout has been exceeded, + `withTransaction` should propagate the error to its caller. This case may occur if the commit was internally retried + against a new primary after a failover and the second primary returned a NoSuchTransaction error response. + +If possible, drivers should implement these tests without requiring the test runner to block for the full duration of +the retry timeout. This might be done by internally modifying the timeout value used by `withTransaction` with some +private API or using a mock timer. + +## Changelog + +- 2024-09-06: Migrated from reStructuredText to Markdown. +- 2024-02-08: Converted legacy tests to unified format. +- 2021-04-29: Remove text about write concern timeouts from prose test. diff --git a/src/test/spec/json/transactions-convenient-api/README.rst b/src/test/spec/json/transactions-convenient-api/README.rst deleted file mode 100644 index 1a16560a5..000000000 --- a/src/test/spec/json/transactions-convenient-api/README.rst +++ /dev/null @@ -1,68 +0,0 @@ -===================================== -Convenient API for Transactions Tests -===================================== - -.. contents:: - ----- - -Introduction -============ - -The YAML and JSON files in this directory are platform-independent tests -meant to exercise a driver's implementation of the Convenient API for -Transactions spec. These tests utilize the -`Unified Test Format <../../unified-test-format/unified-test-format.rst>`__. - -Several prose tests, which are not easily expressed in YAML, are also presented -in this file. Those tests will need to be manually implemented by each driver. - -Prose Tests -=========== - -Callback Raises a Custom Error -`````````````````````````````` - -Write a callback that raises a custom exception or error that does not include -either UnknownTransactionCommitResult or TransientTransactionError error labels. -Execute this callback using ``withTransaction`` and assert that the callback's -error bypasses any retry logic within ``withTransaction`` and is propagated to -the caller of ``withTransaction``. - -Callback Returns a Value -```````````````````````` - -Write a callback that returns a custom value (e.g. boolean, string, object). -Execute this callback using ``withTransaction`` and assert that the callback's -return value is propagated to the caller of ``withTransaction``. - -Retry Timeout is Enforced -````````````````````````` - -Drivers should test that ``withTransaction`` enforces a non-configurable timeout -before retrying both commits and entire transactions. Specifically, three cases -should be checked: - - * If the callback raises an error with the TransientTransactionError label and - the retry timeout has been exceeded, ``withTransaction`` should propagate the - error to its caller. - * If committing raises an error with the UnknownTransactionCommitResult label, - and the retry timeout has been exceeded, ``withTransaction`` should - propagate the error to its caller. - * If committing raises an error with the TransientTransactionError label and - the retry timeout has been exceeded, ``withTransaction`` should propagate the - error to its caller. This case may occur if the commit was internally retried - against a new primary after a failover and the second primary returned a - NoSuchTransaction error response. - - If possible, drivers should implement these tests without requiring the test - runner to block for the full duration of the retry timeout. This might be done - by internally modifying the timeout value used by ``withTransaction`` with some - private API or using a mock timer. - -Changelog -========= - -:2024-02-08: Converted legacy tests to unified format. - -:2021-04-29: Remove text about write concern timeouts from prose test. diff --git a/src/test/spec/json/transactions-convenient-api/unified/commit-writeconcernerror.json b/src/test/spec/json/transactions-convenient-api/unified/commit-writeconcernerror.json index a6f6e6bd7..568f7ede4 100644 --- a/src/test/spec/json/transactions-convenient-api/unified/commit-writeconcernerror.json +++ b/src/test/spec/json/transactions-convenient-api/unified/commit-writeconcernerror.json @@ -56,7 +56,7 @@ ], "tests": [ { - "description": "commitTransaction is retried after WriteConcernFailed timeout error", + "description": "commitTransaction is retried after WriteConcernTimeout timeout error", "operations": [ { "name": "failPoint", @@ -74,7 +74,6 @@ ], "writeConcernError": { "code": 64, - "codeName": "WriteConcernFailed", "errmsg": "waiting for replication timed out", "errInfo": { "wtimeout": true @@ -236,7 +235,7 @@ ] }, { - "description": "commitTransaction is retried after WriteConcernFailed non-timeout error", + "description": "commitTransaction is retried after WriteConcernTimeout non-timeout error", "operations": [ { "name": "failPoint", @@ -254,7 +253,6 @@ ], "writeConcernError": { "code": 64, - "codeName": "WriteConcernFailed", "errmsg": "multiple errors reported" } } diff --git a/src/test/spec/json/transactions-convenient-api/unified/commit-writeconcernerror.yml b/src/test/spec/json/transactions-convenient-api/unified/commit-writeconcernerror.yml index 44877fa00..408f57fde 100644 --- a/src/test/spec/json/transactions-convenient-api/unified/commit-writeconcernerror.yml +++ b/src/test/spec/json/transactions-convenient-api/unified/commit-writeconcernerror.yml @@ -32,7 +32,7 @@ initialData: tests: - - description: commitTransaction is retried after WriteConcernFailed timeout error + description: commitTransaction is retried after WriteConcernTimeout timeout error operations: - name: failPoint object: testRunner @@ -47,7 +47,6 @@ tests: # with writeConcernError (see: SERVER-39292) writeConcernError: code: 64 - codeName: WriteConcernFailed errmsg: "waiting for replication timed out" errInfo: { wtimeout: true } - &operation @@ -126,10 +125,10 @@ tests: - { _id: 1 } - # This test configures the fail point to return an error with the - # WriteConcernFailed code but without errInfo that would identify it as a + # WriteConcernTimeout code but without errInfo that would identify it as a # wtimeout error. This tests that drivers do not assume that all - # WriteConcernFailed errors are due to a replication timeout. - description: commitTransaction is retried after WriteConcernFailed non-timeout error + # WriteConcernTimeout errors are due to a replication timeout. + description: commitTransaction is retried after WriteConcernTimeout non-timeout error operations: - name: failPoint object: testRunner @@ -144,7 +143,6 @@ tests: # with writeConcernError (see: SERVER-39292) writeConcernError: code: 64 - codeName: WriteConcernFailed errmsg: "multiple errors reported" - *operation expectEvents: *expectEvents_with_retries diff --git a/src/test/spec/json/transactions/README.md b/src/test/spec/json/transactions/README.md index 82b9ced51..212d707ca 100644 --- a/src/test/spec/json/transactions/README.md +++ b/src/test/spec/json/transactions/README.md @@ -19,63 +19,82 @@ These tests use a cursor's address field to track which server an operation was driver, use command monitoring instead. 1. Test that starting a new transaction on a pinned ClientSession unpins the session and normal server selection is - performed for the next operation. - - ```python - @require_server_version(4, 1, 6) - @require_mongos_count_at_least(2) - def test_unpin_for_next_transaction(self): - # Increase localThresholdMS and wait until both nodes are discovered - # to avoid false positives. - client = MongoClient(mongos_hosts, localThresholdMS=1000) - wait_until(lambda: len(client.nodes) > 1) - # Create the collection. - client.test.test.insert_one({}) - with client.start_session() as s: - # Session is pinned to Mongos. - with s.start_transaction(): - client.test.test.insert_one({}, session=s) - - addresses = set() - for _ in range(50): - with s.start_transaction(): - cursor = client.test.test.find({}, session=s) - assert next(cursor) - addresses.add(cursor.address) - - assert len(addresses) > 1 - ``` + performed for the next operation. + + ```python + @require_server_version(4, 1, 6) + @require_mongos_count_at_least(2) + def test_unpin_for_next_transaction(self): + # Increase localThresholdMS and wait until both nodes are discovered + # to avoid false positives. + client = MongoClient(mongos_hosts, localThresholdMS=1000) + wait_until(lambda: len(client.nodes) > 1) + # Create the collection. + client.test.test.insert_one({}) + with client.start_session() as s: + # Session is pinned to Mongos. + with s.start_transaction(): + client.test.test.insert_one({}, session=s) + + addresses = set() + for _ in range(50): + with s.start_transaction(): + cursor = client.test.test.find({}, session=s) + assert next(cursor) + addresses.add(cursor.address) + + assert len(addresses) > 1 + ``` 2. Test non-transaction operations using a pinned ClientSession unpins the session and normal server selection is - performed. - - ```python - @require_server_version(4, 1, 6) - @require_mongos_count_at_least(2) - def test_unpin_for_non_transaction_operation(self): - # Increase localThresholdMS and wait until both nodes are discovered - # to avoid false positives. - client = MongoClient(mongos_hosts, localThresholdMS=1000) - wait_until(lambda: len(client.nodes) > 1) - # Create the collection. - client.test.test.insert_one({}) - with client.start_session() as s: - # Session is pinned to Mongos. - with s.start_transaction(): - client.test.test.insert_one({}, session=s) - - addresses = set() - for _ in range(50): - cursor = client.test.test.find({}, session=s) - assert next(cursor) - addresses.add(cursor.address) - - assert len(addresses) > 1 - ``` + performed. + + ```python + @require_server_version(4, 1, 6) + @require_mongos_count_at_least(2) + def test_unpin_for_non_transaction_operation(self): + # Increase localThresholdMS and wait until both nodes are discovered + # to avoid false positives. + client = MongoClient(mongos_hosts, localThresholdMS=1000) + wait_until(lambda: len(client.nodes) > 1) + # Create the collection. + client.test.test.insert_one({}) + with client.start_session() as s: + # Session is pinned to Mongos. + with s.start_transaction(): + client.test.test.insert_one({}, session=s) + + addresses = set() + for _ in range(50): + cursor = client.test.test.find({}, session=s) + assert next(cursor) + addresses.add(cursor.address) + + assert len(addresses) > 1 + ``` + +3. Test that `PoolClearedError` has `TransientTransactionError` label. Since there is no simple way to trigger + `PoolClearedError`, this test should be implemented in a way that suites each driver the best. + +## Options Inside Transaction Prose Tests. + +These prose tests ensure drivers handle options inside a transaction where the unified tests do not suffice. Ensure +these tests do not run against a standalone server. + +### 1.0 Write concern not inherited from collection object inside transaction. + +- Create a MongoClient running against a configured sharded/replica set/load balanced cluster. +- Start a new session on the client. +- Start a transaction on the session. +- Instantiate a collection object in the driver with a default write concern of `{ w: 0 }`. +- Insert the document `{ n: 1 }` on the instantiated collection. +- Commit the transaction. +- End the session. +- Ensure the document was inserted and no error was thrown from the transaction. ## Changelog +- 2024-10-31: Add test for PoolClearedError. - 2024-02-15: Migrated from reStructuredText to Markdown. -- 2024-02-07: Converted legacy transaction tests to unified format and moved the\ - legacy test format docs to a separate - file. +- 2024-02-07: Converted legacy transaction tests to unified format and moved the legacy test format docs to a separate + file. diff --git a/src/test/spec/json/transactions/legacy-test-format.md b/src/test/spec/json/transactions/legacy-test-format.md index db2fe9fe0..59f1e6a06 100644 --- a/src/test/spec/json/transactions/legacy-test-format.md +++ b/src/test/spec/json/transactions/legacy-test-format.md @@ -39,112 +39,109 @@ The `failCommand` fail point may be configured like so: or `skip`, which are mutually exclusive: - `{ times: }` may be used to limit the number of times the fail point may trigger before transitioning to - `"off"`. + `"off"`. - `{ skip: }` may be used to defer the first trigger of a fail point, after which it will transition to - `"alwaysOn"`. + `"alwaysOn"`. The `data` option is a document that may be used to specify options that control the fail point's behavior. `failCommand` supports the following `data` options, which may be combined if desired: - `failCommands`: Required, the list of command names to fail. - `closeConnection`: Boolean option, which defaults to `false`. If `true`, the command will not be executed, the - connection will be closed, and the client will see a network error. + connection will be closed, and the client will see a network error. - `errorCode`: Integer option, which is unset by default. If set, the command will not be executed and the specified - command error code will be returned as a command error. + command error code will be returned as a command error. - `appName`: A string to filter which MongoClient should be affected by the failpoint. - [New in mongod 4.4.0-rc2](https://jira.mongodb.org/browse/SERVER-47195). + [New in mongod 4.4.0-rc2](https://jira.mongodb.org/browse/SERVER-47195). - `blockConnection`: Whether the server should block the affected commands. Default false. - `blockTimeMS`: The number of milliseconds the affect commands should be blocked for. Required when blockConnection is - true. [New in mongod 4.3.4](https://jira.mongodb.org/browse/SERVER-41070). - -## Speeding Up Tests - -See [Speeding Up Tests](../../retryable-reads/tests/README.md#speeding-up-tests) in the retryable reads spec tests. + true. [New in mongod 4.3.4](https://jira.mongodb.org/browse/SERVER-41070). ## Test Format Each YAML file has the following keys: - `runOn` (optional): An array of server version and/or topology requirements for which the tests can be run. If the - test environment satisfies one or more of these requirements, the tests may be executed; otherwise, this file should - be skipped. If this field is omitted, the tests can be assumed to have no particular requirements and should be - executed. Each element will have some or all of the following fields: - - `minServerVersion` (optional): The minimum server version (inclusive) required to successfully run the tests. If - this field is omitted, it should be assumed that there is no lower bound on the required server version. - - - `maxServerVersion` (optional): The maximum server version (inclusive) against which the tests can be run - successfully. If this field is omitted, it should be assumed that there is no upper bound on the required server - version. - - - `topology` (optional): An array of server topologies against which the tests can be run successfully. Valid - topologies are "single", "replicaset", "sharded", and "load-balanced". If this field is omitted, the default is all - topologies (i.e. `["single", "replicaset", "sharded", "load-balanced"]`). - - - `serverless`: (optional): Whether or not the test should be run on Atlas Serverless instances. Valid values are - "require", "forbid", and "allow". If "require", the test MUST only be run on Atlas Serverless instances. If - "forbid", the test MUST NOT be run on Atlas Serverless instances. If omitted or "allow", this option has no effect. - - The test runner MUST be informed whether or not Atlas Serverless is being used in order to determine if this - requirement is met (e.g. through an environment variable or configuration option). - - Note: the Atlas Serverless proxy imitates mongos, so the test runner is not capable of determining if Atlas - Serverless is in use by issuing commands such as `buildInfo` or `hello`. Furthermore, connections to Atlas - Serverless use a load balancer, so the topology will appear as "load-balanced". + test environment satisfies one or more of these requirements, the tests may be executed; otherwise, this file should + be skipped. If this field is omitted, the tests can be assumed to have no particular requirements and should be + executed. Each element will have some or all of the following fields: + - `minServerVersion` (optional): The minimum server version (inclusive) required to successfully run the tests. If + this field is omitted, it should be assumed that there is no lower bound on the required server version. + + - `maxServerVersion` (optional): The maximum server version (inclusive) against which the tests can be run + successfully. If this field is omitted, it should be assumed that there is no upper bound on the required server + version. + + - `topology` (optional): An array of server topologies against which the tests can be run successfully. Valid + topologies are "single", "replicaset", "sharded", and "load-balanced". If this field is omitted, the default is + all topologies (i.e. `["single", "replicaset", "sharded", "load-balanced"]`). + + - `serverless`: (optional): Whether or not the test should be run on Atlas Serverless instances. Valid values are + "require", "forbid", and "allow". If "require", the test MUST only be run on Atlas Serverless instances. If + "forbid", the test MUST NOT be run on Atlas Serverless instances. If omitted or "allow", this option has no + effect. + + The test runner MUST be informed whether or not Atlas Serverless is being used in order to determine if this + requirement is met (e.g. through an environment variable or configuration option). + + Note: the Atlas Serverless proxy imitates mongos, so the test runner is not capable of determining if Atlas + Serverless is in use by issuing commands such as `buildInfo` or `hello`. Furthermore, connections to Atlas + Serverless use a load balancer, so the topology will appear as "load-balanced". - `database_name` and `collection_name`: The database and collection to use for testing. - `data`: The data that should exist in the collection under test before each test run. - `tests`: An array of tests that are to be run independently of each other. Each test will have some or all of the - following fields: - - `description`: The name of the test. + following fields: + - `description`: The name of the test. - - `skipReason`: Optional, string describing why this test should be skipped. + - `skipReason`: Optional, string describing why this test should be skipped. - - `useMultipleMongoses` (optional): If `true`, and the topology type is `Sharded`, the MongoClient for this test - should be initialized with multiple mongos seed addresses. If `false` or omitted, only a single mongos address - should be specified. + - `useMultipleMongoses` (optional): If `true`, and the topology type is `Sharded`, the MongoClient for this test + should be initialized with multiple mongos seed addresses. If `false` or omitted, only a single mongos address + should be specified. - If `true`, the topology type is `LoadBalanced`, and Atlas Serverless is not being used, the MongoClient for this - test should be initialized with the URI of the load balancer fronting multiple servers. If `false` or omitted, the - MongoClient for this test should be initialized with the URI of the load balancer fronting a single server. + If `true`, the topology type is `LoadBalanced`, and Atlas Serverless is not being used, the MongoClient for this + test should be initialized with the URI of the load balancer fronting multiple servers. If `false` or omitted, the + MongoClient for this test should be initialized with the URI of the load balancer fronting a single server. - `useMultipleMongoses` only affects `Sharded` and `LoadBalanced` topologies (excluding Atlas Serverless). + `useMultipleMongoses` only affects `Sharded` and `LoadBalanced` topologies (excluding Atlas Serverless). - - `clientOptions`: Optional, parameters to pass to MongoClient(). + - `clientOptions`: Optional, parameters to pass to MongoClient(). - - `failPoint`: Optional, a server failpoint to enable expressed as the configureFailPoint command to run on the admin - database. This option and `useMultipleMongoses: true` are mutually exclusive. + - `failPoint`: Optional, a server failpoint to enable expressed as the configureFailPoint command to run on the admin + database. This option and `useMultipleMongoses: true` are mutually exclusive. - - `sessionOptions`: Optional, map of session names (e.g. "session0") to parameters to pass to - MongoClient.startSession() when creating that session. + - `sessionOptions`: Optional, map of session names (e.g. "session0") to parameters to pass to + MongoClient.startSession() when creating that session. - - `operations`: Array of documents, each describing an operation to be executed. Each document has the following - fields: + - `operations`: Array of documents, each describing an operation to be executed. Each document has the following + fields: - - `name`: The name of the operation on `object`. - - `object`: The name of the object to perform the operation on. Can be "database", "collection", "session0", - "session1", or "testRunner". See the "targetedFailPoint" operation in - [Special Test Operations](#special-test-operations). - - `collectionOptions`: Optional, parameters to pass to the Collection() used for this operation. - - `databaseOptions`: Optional, parameters to pass to the Database() used for this operation. - - `command_name`: Present only when `name` is "runCommand". The name of the command to run. Required for languages - that are unable preserve the order keys in the "command" argument when parsing JSON/YAML. - - `arguments`: Optional, the names and values of arguments. - - `error`: Optional. If true, the test should expect an error or exception. This could be a server-generated or a - driver-generated error. - - `result`: The return value from the operation, if any. This field may be a single document or an array of - documents in the case of a multi-document read. If the operation is expected to return an error, the `result` is a - single document that has one or more of the following fields: - - `errorContains`: A substring of the expected error message. - - `errorCodeName`: The expected "codeName" field in the server error response. - - `errorLabelsContain`: A list of error label strings that the error is expected to have. - - `errorLabelsOmit`: A list of error label strings that the error is expected not to have. + - `name`: The name of the operation on `object`. + - `object`: The name of the object to perform the operation on. Can be "database", "collection", "session0", + "session1", or "testRunner". See the "targetedFailPoint" operation in + [Special Test Operations](#special-test-operations). + - `collectionOptions`: Optional, parameters to pass to the Collection() used for this operation. + - `databaseOptions`: Optional, parameters to pass to the Database() used for this operation. + - `command_name`: Present only when `name` is "runCommand". The name of the command to run. Required for languages + that are unable preserve the order keys in the "command" argument when parsing JSON/YAML. + - `arguments`: Optional, the names and values of arguments. + - `error`: Optional. If true, the test should expect an error or exception. This could be a server-generated or a + driver-generated error. + - `result`: The return value from the operation, if any. This field may be a single document or an array of + documents in the case of a multi-document read. If the operation is expected to return an error, the `result` is + a single document that has one or more of the following fields: + - `errorContains`: A substring of the expected error message. + - `errorCodeName`: The expected "codeName" field in the server error response. + - `errorLabelsContain`: A list of error label strings that the error is expected to have. + - `errorLabelsOmit`: A list of error label strings that the error is expected not to have. - - `expectations`: Optional list of command-started events. + - `expectations`: Optional list of command-started events. - - `outcome`: Document describing the return value and/or expected state of the collection after the operation is - executed. Contains the following fields: + - `outcome`: Document describing the return value and/or expected state of the collection after the operation is + executed. Contains the following fields: - - `collection`: - - `data`: The data that should exist in the collection after the operations have run, sorted by "\_id". + - `collection`: + - `data`: The data that should exist in the collection after the operations have run, sorted by "\_id". ## Use as Integration Tests @@ -160,41 +157,42 @@ Load each YAML (or JSON) file using a Canonical Extended JSON parser. Then for each element in `tests`: -01. If the `skipReason` field is present, skip this test completely. +1. If the `skipReason` field is present, skip this test completely. -02. Create a MongoClient and call `client.admin.runCommand({killAllSessions: []})` to clean up any open transactions +2. Create a MongoClient and call `client.admin.runCommand({killAllSessions: []})` to clean up any open transactions from previous test failures. Ignore a command failure with error code 11601 ("Interrupted") to work around [SERVER-38335](https://jira.mongodb.org/browse/SERVER-38335). - Running `killAllSessions` cleans up any open transactions from a previously failed test to prevent the current - test from blocking. It is sufficient to run this command once before starting the test suite and once after each - failed test. + test from blocking. It is sufficient to run this command once before starting the test suite and once after each + failed test. - When testing against a sharded cluster run this command on ALL mongoses. -03. Create a collection object from the MongoClient, using the `database_name` and `collection_name` fields of the YAML +3. Create a collection object from the MongoClient, using the `database_name` and `collection_name` fields of the YAML file. -04. Drop the test collection, using writeConcern "majority". +4. Drop the test collection, using writeConcern "majority". -05. Execute the "create" command to recreate the collection, using writeConcern "majority". (Creating the collection +5. Execute the "create" command to recreate the collection, using writeConcern "majority". (Creating the collection inside a transaction is prohibited, so create it explicitly.) -06. If the YAML file contains a `data` array, insert the documents in `data` into the test collection, using +6. If the YAML file contains a `data` array, insert the documents in `data` into the test collection, using writeConcern "majority". -07. When testing against a sharded cluster run a `distinct` command on the newly created collection on all mongoses. For +7. When testing against a sharded cluster run a `distinct` command on the newly created collection on all mongoses. For an explanation see, [Why do tests that run distinct sometimes fail with StaleDbVersion?](#why-do-tests-that-run-distinct-sometimes-fail-with-staledbversion) + -08. If `failPoint` is specified, its value is a configureFailPoint command. Run the command on the admin database to +8. If `failPoint` is specified, its value is a configureFailPoint command. Run the command on the admin database to enable the fail point. -09. Create a **new** MongoClient `client`, with Command Monitoring listeners enabled. (Using a new MongoClient for each +9. Create a **new** MongoClient `client`, with Command Monitoring listeners enabled. (Using a new MongoClient for each test ensures a fresh session pool that hasn't executed any transactions previously, so the tests can assert actual txnNumbers, starting from 1.) Pass this test's `clientOptions` if present. - When testing against a sharded cluster and `useMultipleMongoses` is `true` the client MUST be created with - multiple (valid) mongos seed addresses. + multiple (valid) mongos seed addresses. 10. Call `client.startSession` twice to create ClientSession objects `session0` and `session1`, using the test's "sessionOptions" if they are present. Save their lsids so they are available after calling `endSession`, see @@ -203,51 +201,51 @@ Then for each element in `tests`: 11. For each element in `operations`: - If the operation `name` is a special test operation type, execute it and go to the next operation, otherwise - proceed to the next step. + proceed to the next step. - Enter a "try" block or your programming language's closest equivalent. - Create a Database object from the MongoClient, using the `database_name` field at the top level of the test file. - Create a Collection object from the Database, using the `collection_name` field at the top level of the test file. - If `collectionOptions` or `databaseOptions` is present, create the Collection or Database object with the provided - options, respectively. Otherwise create the object with the default options. + If `collectionOptions` or `databaseOptions` is present, create the Collection or Database object with the + provided options, respectively. Otherwise create the object with the default options. - Execute the named method on the provided `object`, passing the arguments listed. Pass `session0` or `session1` to - the method, depending on which session's name is in the arguments list. If `arguments` contains no "session", pass - no explicit session to the method. + the method, depending on which session's name is in the arguments list. If `arguments` contains no "session", + pass no explicit session to the method. - If the driver throws an exception / returns an error while executing this series of operations, store the error - message and server error code. + message and server error code. - If the operation's `error` field is `true`, verify that the method threw an exception or returned an error. - If the result document has an "errorContains" field, verify that the method threw an exception or returned an - error, and that the value of the "errorContains" field matches the error string. "errorContains" is a substring - (case-insensitive) of the actual error message. + error, and that the value of the "errorContains" field matches the error string. "errorContains" is a substring + (case-insensitive) of the actual error message. - If the result document has an "errorCodeName" field, verify that the method threw a command failed exception or - returned an error, and that the value of the "errorCodeName" field matches the "codeName" in the server error - response. + If the result document has an "errorCodeName" field, verify that the method threw a command failed exception or + returned an error, and that the value of the "errorCodeName" field matches the "codeName" in the server error + response. - If the result document has an "errorLabelsContain" field, verify that the method threw an exception or returned an - error. Verify that all of the error labels in "errorLabelsContain" are present in the error or exception using the - `hasErrorLabel` method. + If the result document has an "errorLabelsContain" field, verify that the method threw an exception or returned an + error. Verify that all of the error labels in "errorLabelsContain" are present in the error or exception using + the `hasErrorLabel` method. - If the result document has an "errorLabelsOmit" field, verify that the method threw an exception or returned an - error. Verify that none of the error labels in "errorLabelsOmit" are present in the error or exception using the - `hasErrorLabel` method. + If the result document has an "errorLabelsOmit" field, verify that the method threw an exception or returned an + error. Verify that none of the error labels in "errorLabelsOmit" are present in the error or exception using the + `hasErrorLabel` method. - If the operation returns a raw command response, eg from `runCommand`, then compare only the fields present in the - expected result document. Otherwise, compare the method's return value to `result` using the same logic as the - CRUD Spec Tests runner. + expected result document. Otherwise, compare the method's return value to `result` using the same logic as the + CRUD Spec Tests runner. 12. Call `session0.endSession()` and `session1.endSession`. 13. If the test includes a list of command-started events in `expectations`, compare them to the actual command-started events using the same logic as the - [legacy Command Monitoring Spec Tests runner](https://github.com/mongodb/specifications/blob/09ee1ebc481f1502e3246971a9419e484d736207/source/command-monitoring/tests/README.rst#expectations), - plus the rules in the Command-Started Events instructions below. + [legacy Command Monitoring Spec Tests runner](../../command-logging-and-monitoring/tests/README.md), plus the + rules in the Command-Started Events instructions below. 14. If `failPoint` is specified, disable the fail point to avoid spurious failures in subsequent tests. The fail point may be disabled like so: @@ -262,9 +260,9 @@ Then for each element in `tests`: 15. For each element in `outcome`: - If `name` is "collection", verify that the test collection contains exactly the documents in the `data` array. - Ensure this find reads the latest data by using **primary read preference** with **local read concern** even when - the MongoClient is configured with another read preference or read concern. Note the server does not guarantee - that documents returned by a find command will be in inserted order. This find MUST sort by `{_id:1}`. + Ensure this find reads the latest data by using **primary read preference** with **local read concern** even + when the MongoClient is configured with another read preference or read concern. Note the server does not + guarantee that documents returned by a find command will be in inserted order. This find MUST sort by `{_id:1}`. ### Special Test Operations @@ -290,7 +288,7 @@ subsequent tests. The fail point may be disabled like so: Here is an example which instructs the test runner to enable the failCommand fail point on the mongos server which "session0" is pinned to: -``` +```yaml # Enable the fail point only on the Mongos that session0 is pinned to. - name: targetedFailPoint object: testRunner @@ -315,7 +313,7 @@ The "assertSessionTransactionState" operation instructs the test runner to asser given session is equal to the specified value. The possible values are as follows: `none`, `starting`, `in_progress`, `committed`, `aborted`: -``` +```yaml - name: assertSessionTransactionState object: testRunner arguments: @@ -327,7 +325,7 @@ given session is equal to the specified value. The possible values are as follow The "assertSessionPinned" operation instructs the test runner to assert that the given session is pinned to a mongos: -``` +```yaml - name: assertSessionPinned object: testRunner arguments: @@ -339,7 +337,7 @@ The "assertSessionPinned" operation instructs the test runner to assert that the The "assertSessionUnpinned" operation instructs the test runner to assert that the given session is not pinned to a mongos: -``` +```yaml - name: assertSessionPinned object: testRunner arguments: @@ -351,7 +349,7 @@ mongos: The "assertCollectionExists" operation instructs the test runner to assert that the given collection exists in the database: -``` +```yaml - name: assertCollectionExists object: testRunner arguments: @@ -367,7 +365,7 @@ Use a `listCollections` command to check whether the collection exists. Note tha The "assertCollectionNotExists" operation instructs the test runner to assert that the given collection does not exist in the database: -``` +```yaml - name: assertCollectionNotExists object: testRunner arguments: @@ -383,7 +381,7 @@ Use a `listCollections` command to check whether the collection exists. Note tha The "assertIndexExists" operation instructs the test runner to assert that the index with the given name exists on the collection: -``` +```yaml - name: assertIndexExists object: testRunner arguments: @@ -400,7 +398,7 @@ Use a `listIndexes` command to check whether the index exists. Note that it is c The "assertIndexNotExists" operation instructs the test runner to assert that the index with the given name does not exist on the collection: -``` +```yaml - name: assertIndexNotExists object: testRunner arguments: @@ -451,7 +449,7 @@ When a shard receives its first command that contains a dbVersion, the shard ret Mongos retries the operation. In a sharded transaction, Mongos does not retry these operations and instead returns the error to the client. For example: -``` +```text Command distinct failed: Transaction aa09e296-472a-494f-8334-48d57ab530b6:1 was aborted on statement 0 due to: an error from cluster data placement change :: caused by :: got stale databaseVersion response from shard sh01 at host localhost:27217 :: caused by :: don't know dbVersion. ``` @@ -467,7 +465,7 @@ sharded transaction that uses the `dbVersion` concept so it is the only command - 2024-02-15: Migrated from reStructuredText to Markdown. -- 2024-02-07: Moved legacy test format docs to this file from README.rst. +- 2024-02-07: Moved legacy test format docs to this file from README.md. - 2023-09-28: Add `load-balanced` to test topology requirements. @@ -477,13 +475,10 @@ sharded transaction that uses the `dbVersion` concept so it is the only command - 2019-03-25: Add workaround for StaleDbVersion on distinct. -- 2019-03-01: Add top-level `runOn` field to denote server version and/or\ - topology requirements requirements for the - test file. Removes the `topology` top-level field, which is now expressed within `runOn` elements. +- 2019-03-01: Add top-level `runOn` field to denote server version and/or topology requirements requirements for the + test file. Removes the `topology` top-level field, which is now expressed within `runOn` elements. -- 2019-02-28: `useMultipleMongoses: true` and non-targeted fail points are\ - mutually exclusive. +- 2019-02-28: `useMultipleMongoses: true` and non-targeted fail points are mutually exclusive. -- 2019-02-13: Modify test format for 4.2 sharded transactions, including\ - "useMultipleMongoses", `object: testRunner`, - the `targetedFailPoint` operation, and recoveryToken assertions. +- 2019-02-13: Modify test format for 4.2 sharded transactions, including "useMultipleMongoses", `object: testRunner`, + the `targetedFailPoint` operation, and recoveryToken assertions. diff --git a/src/test/spec/json/transactions/unified/error-labels.json b/src/test/spec/json/transactions/unified/error-labels.json index be8df10ed..74ed750b0 100644 --- a/src/test/spec/json/transactions/unified/error-labels.json +++ b/src/test/spec/json/transactions/unified/error-labels.json @@ -1176,7 +1176,7 @@ ] }, { - "description": "add UnknownTransactionCommitResult label to writeConcernError WriteConcernFailed", + "description": "add UnknownTransactionCommitResult label to writeConcernError WriteConcernTimeout", "operations": [ { "object": "testRunner", @@ -1338,7 +1338,7 @@ ] }, { - "description": "add UnknownTransactionCommitResult label to writeConcernError WriteConcernFailed with wtimeout", + "description": "add UnknownTransactionCommitResult label to writeConcernError WriteConcernTimeout with wtimeout", "operations": [ { "object": "testRunner", @@ -1356,7 +1356,6 @@ ], "writeConcernError": { "code": 64, - "codeName": "WriteConcernFailed", "errmsg": "waiting for replication timed out", "errInfo": { "wtimeout": true diff --git a/src/test/spec/json/transactions/unified/error-labels.yml b/src/test/spec/json/transactions/unified/error-labels.yml index 6eb8d7d75..64e241d55 100644 --- a/src/test/spec/json/transactions/unified/error-labels.yml +++ b/src/test/spec/json/transactions/unified/error-labels.yml @@ -688,7 +688,7 @@ tests: databaseName: *database_name documents: [] - - description: 'add UnknownTransactionCommitResult label to writeConcernError WriteConcernFailed' + description: 'add UnknownTransactionCommitResult label to writeConcernError WriteConcernTimeout' operations: - object: testRunner @@ -702,7 +702,7 @@ tests: failCommands: - commitTransaction writeConcernError: - code: 64 # WriteConcernFailed without wtimeout + code: 64 # WriteConcernTimeout without wtimeout errmsg: 'multiple errors reported' - object: *session0 @@ -782,7 +782,7 @@ tests: documents: - { _id: 1 } - - description: 'add UnknownTransactionCommitResult label to writeConcernError WriteConcernFailed with wtimeout' + description: 'add UnknownTransactionCommitResult label to writeConcernError WriteConcernTimeout with wtimeout' operations: - object: testRunner @@ -797,7 +797,6 @@ tests: - commitTransaction writeConcernError: code: 64 - codeName: WriteConcernFailed errmsg: 'waiting for replication timed out' errInfo: wtimeout: true diff --git a/src/test/spec/json/transactions/unified/findOneAndReplace.json b/src/test/spec/json/transactions/unified/findOneAndReplace.json index d9248244b..f0742f0c6 100644 --- a/src/test/spec/json/transactions/unified/findOneAndReplace.json +++ b/src/test/spec/json/transactions/unified/findOneAndReplace.json @@ -127,7 +127,9 @@ "update": { "x": 1 }, - "new": false, + "new": { + "$$unsetOrMatches": false + }, "lsid": { "$$sessionLsid": "session0" }, @@ -299,7 +301,9 @@ "update": { "x": 1 }, - "new": false, + "new": { + "$$unsetOrMatches": false + }, "lsid": { "$$sessionLsid": "session0" }, diff --git a/src/test/spec/json/transactions/unified/findOneAndReplace.yml b/src/test/spec/json/transactions/unified/findOneAndReplace.yml index 2b4a9920d..f1b79c958 100644 --- a/src/test/spec/json/transactions/unified/findOneAndReplace.yml +++ b/src/test/spec/json/transactions/unified/findOneAndReplace.yml @@ -88,7 +88,7 @@ tests: findAndModify: *collection_name query: { _id: 3 } update: { x: 1 } - new: false + new: { $$unsetOrMatches: false } lsid: { $$sessionLsid: *session0 } txnNumber: { $numberLong: '1' } startTransaction: true @@ -178,7 +178,7 @@ tests: findAndModify: *collection_name query: { _id: 3 } update: { x: 1 } - new: false + new: { $$unsetOrMatches: false } lsid: { $$sessionLsid: *session0 } txnNumber: { $numberLong: '1' } startTransaction: true diff --git a/src/test/spec/json/transactions/unified/findOneAndUpdate.json b/src/test/spec/json/transactions/unified/findOneAndUpdate.json index 34a40bb57..f5308efef 100644 --- a/src/test/spec/json/transactions/unified/findOneAndUpdate.json +++ b/src/test/spec/json/transactions/unified/findOneAndUpdate.json @@ -189,7 +189,9 @@ "x": 1 } }, - "new": false, + "new": { + "$$unsetOrMatches": false + }, "lsid": { "$$sessionLsid": "session0" }, @@ -281,7 +283,9 @@ "x": 1 } }, - "new": false, + "new": { + "$$unsetOrMatches": false + }, "lsid": { "$$sessionLsid": "session0" }, @@ -340,7 +344,9 @@ "x": 1 } }, - "new": false, + "new": { + "$$unsetOrMatches": false + }, "lsid": { "$$sessionLsid": "session0" }, @@ -485,7 +491,9 @@ "x": 1 } }, - "new": false, + "new": { + "$$unsetOrMatches": false + }, "lsid": { "$$sessionLsid": "session0" }, diff --git a/src/test/spec/json/transactions/unified/findOneAndUpdate.yml b/src/test/spec/json/transactions/unified/findOneAndUpdate.yml index 1865cabb0..49d68a209 100644 --- a/src/test/spec/json/transactions/unified/findOneAndUpdate.yml +++ b/src/test/spec/json/transactions/unified/findOneAndUpdate.yml @@ -130,7 +130,7 @@ tests: findAndModify: *collection_name query: { _id: 3 } update: { $inc: { x: 1 } } - new: false + new: { $$unsetOrMatches: false } lsid: { $$sessionLsid: *session0 } txnNumber: { $numberLong: '1' } startTransaction: true @@ -173,7 +173,7 @@ tests: findAndModify: *collection_name query: { _id: 3 } update: { $inc: { x: 1 } } - new: false + new: { $$unsetOrMatches: false } lsid: { $$sessionLsid: *session0 } txnNumber: { $numberLong: '2' } startTransaction: true @@ -201,7 +201,7 @@ tests: findAndModify: *collection_name query: { _id: 3 } update: { $inc: { x: 1 } } - new: false + new: { $$unsetOrMatches: false } lsid: { $$sessionLsid: *session0 } txnNumber: { $numberLong: '3' } startTransaction: true @@ -277,7 +277,7 @@ tests: findAndModify: *collection_name query: { _id: 3 } update: { $inc: { x: 1 } } - new: false + new: { $$unsetOrMatches: false } lsid: { $$sessionLsid: *session0 } txnNumber: { $numberLong: '1' } startTransaction: true diff --git a/src/test/spec/json/transactions/unified/mongos-recovery-token.json b/src/test/spec/json/transactions/unified/mongos-recovery-token.json index 00909c421..bb88aa16b 100644 --- a/src/test/spec/json/transactions/unified/mongos-recovery-token.json +++ b/src/test/spec/json/transactions/unified/mongos-recovery-token.json @@ -232,7 +232,8 @@ "id": "client1", "useMultipleMongoses": true, "uriOptions": { - "heartbeatFrequencyMS": 30000 + "heartbeatFrequencyMS": 30000, + "appName": "transactionsClient" }, "observeEvents": [ "commandStartedEvent" @@ -299,7 +300,8 @@ "isMaster", "hello" ], - "closeConnection": true + "closeConnection": true, + "appName": "transactionsClient" } } } diff --git a/src/test/spec/json/transactions/unified/mongos-recovery-token.yml b/src/test/spec/json/transactions/unified/mongos-recovery-token.yml index f8606f078..9eed826ec 100644 --- a/src/test/spec/json/transactions/unified/mongos-recovery-token.yml +++ b/src/test/spec/json/transactions/unified/mongos-recovery-token.yml @@ -150,6 +150,7 @@ tests: # flight heartbeat refreshes the first mongoes' SDAM state in between # the initial commitTransaction and the retry attempt. heartbeatFrequencyMS: 30000 + appName: &appName transactionsClient observeEvents: - commandStartedEvent - database: @@ -195,6 +196,7 @@ tests: - isMaster - hello closeConnection: true + appName: *appName # The first commitTransaction sees a retryable connection error due to # the fail point and also fails on the server. The retry attempt on a # new mongos will wait for the transaction to timeout and will fail diff --git a/src/test/spec/json/transactions/unified/pin-mongos.json b/src/test/spec/json/transactions/unified/pin-mongos.json index 5f2ecca5c..c96f3f341 100644 --- a/src/test/spec/json/transactions/unified/pin-mongos.json +++ b/src/test/spec/json/transactions/unified/pin-mongos.json @@ -1249,7 +1249,8 @@ "id": "client1", "useMultipleMongoses": true, "uriOptions": { - "heartbeatFrequencyMS": 30000 + "heartbeatFrequencyMS": 30000, + "appName": "transactionsClient" }, "observeEvents": [ "commandStartedEvent" @@ -1316,7 +1317,8 @@ "isMaster", "hello" ], - "closeConnection": true + "closeConnection": true, + "appName": "transactionsClient" } } } diff --git a/src/test/spec/json/transactions/unified/pin-mongos.yml b/src/test/spec/json/transactions/unified/pin-mongos.yml index 596dc2d00..4869ac3c1 100644 --- a/src/test/spec/json/transactions/unified/pin-mongos.yml +++ b/src/test/spec/json/transactions/unified/pin-mongos.yml @@ -527,6 +527,7 @@ tests: # flight heartbeat refreshes the first mongoes' SDAM state in between # the insert connection error and the single commit attempt. heartbeatFrequencyMS: 30000 + appName: &appName transactionsClient observeEvents: - commandStartedEvent - database: @@ -572,6 +573,7 @@ tests: - isMaster - hello closeConnection: true + appName: *appName - object: *collection1 name: insertOne diff --git a/src/test/spec/json/transactions/unified/write-concern.json b/src/test/spec/json/transactions/unified/write-concern.json index 7acdd5406..29d1977a8 100644 --- a/src/test/spec/json/transactions/unified/write-concern.json +++ b/src/test/spec/json/transactions/unified/write-concern.json @@ -1417,7 +1417,9 @@ "update": { "x": 1 }, - "new": false, + "new": { + "$$unsetOrMatches": false + }, "lsid": { "$$sessionLsid": "session0" }, @@ -1522,7 +1524,9 @@ "x": 1 } }, - "new": false, + "new": { + "$$unsetOrMatches": false + }, "lsid": { "$$sessionLsid": "session0" }, diff --git a/src/test/spec/json/transactions/unified/write-concern.yml b/src/test/spec/json/transactions/unified/write-concern.yml index ecb9fac0e..1eb27cf1d 100644 --- a/src/test/spec/json/transactions/unified/write-concern.yml +++ b/src/test/spec/json/transactions/unified/write-concern.yml @@ -606,7 +606,7 @@ tests: findAndModify: *collection_name query: { _id: 0 } update: { x: 1 } - new: false + new: { $$unsetOrMatches: false } <<: *transactionCommandArgs commandName: findAndModify databaseName: *database_name @@ -642,7 +642,7 @@ tests: findAndModify: *collection_name query: { _id: 0 } update: { $inc: { x: 1 } } - new: false + new: { $$unsetOrMatches: false } <<: *transactionCommandArgs commandName: findAndModify databaseName: *database_name