Skip to content

RUST-2154 Update tests that use WriteConcernFailed code name #1331

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,7 @@
]
},
{
"description": "InsertOne fails after WriteConcernError WriteConcernFailed",
"description": "InsertOne fails after WriteConcernError WriteConcernTimeout",
"operations": [
{
"name": "failPoint",
Expand All @@ -757,7 +757,6 @@
],
"writeConcernError": {
"code": 64,
"codeName": "WriteConcernFailed",
"errmsg": "waiting for replication timed out",
"errInfo": {
"wtimeout": true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -353,7 +353,6 @@ tests:
failCommands: [ insert ]
writeConcernError:
code: 64
codeName: WriteConcernFailed
errmsg: 'waiting for replication timed out'
errInfo:
wtimeout: true
Expand Down
48 changes: 48 additions & 0 deletions src/test/spec/json/transactions-convenient-api/README.md
Original file line number Diff line number Diff line change
@@ -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.
68 changes: 0 additions & 68 deletions src/test/spec/json/transactions-convenient-api/README.rst

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
],
"tests": [
{
"description": "commitTransaction is retried after WriteConcernFailed timeout error",
"description": "commitTransaction is retried after WriteConcernTimeout timeout error",
"operations": [
{
"name": "failPoint",
Expand All @@ -74,7 +74,6 @@
],
"writeConcernError": {
"code": 64,
"codeName": "WriteConcernFailed",
"errmsg": "waiting for replication timed out",
"errInfo": {
"wtimeout": true
Expand Down Expand Up @@ -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",
Expand All @@ -254,7 +253,6 @@
],
"writeConcernError": {
"code": 64,
"codeName": "WriteConcernFailed",
"errmsg": "multiple errors reported"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -144,7 +143,6 @@ tests:
# with writeConcernError (see: SERVER-39292)
writeConcernError:
code: 64
codeName: WriteConcernFailed
errmsg: "multiple errors reported"
- *operation
expectEvents: *expectEvents_with_retries
Expand Down
127 changes: 73 additions & 54 deletions src/test/spec/json/transactions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Loading