PYTHON-1797 PYTHON-1659 Update retryWrites tests

This commit is contained in:
Bernie Hackett 2019-07-15 15:42:18 -07:00
parent c6e59832f1
commit 8f0ea1daec
22 changed files with 347 additions and 131 deletions

View File

@ -1,4 +1,18 @@
{
"runOn": [
{
"minServerVersion": "4.0",
"topology": [
"replicaset"
]
},
{
"minServerVersion": "4.1.7",
"topology": [
"sharded"
]
}
],
"data": [
{
"_id": 1,
@ -9,7 +23,6 @@
"x": 22
}
],
"minServerVersion": "3.99",
"tests": [
{
"description": "BulkWrite succeeds after PrimarySteppedDown",
@ -68,6 +81,7 @@
"outcome": {
"result": {
"deletedCount": 1,
"insertedCount": 1,
"insertedIds": {
"1": 3
},
@ -150,6 +164,7 @@
"outcome": {
"result": {
"deletedCount": 1,
"insertedCount": 1,
"insertedIds": {
"1": 3
},

View File

@ -1,11 +1,18 @@
{
"runOn": [
{
"minServerVersion": "3.6",
"topology": [
"replicaset"
]
}
],
"data": [
{
"_id": 1,
"x": 11
}
],
"minServerVersion": "3.6",
"tests": [
{
"description": "First command is retried",
@ -58,6 +65,7 @@
"outcome": {
"result": {
"deletedCount": 1,
"insertedCount": 1,
"insertedIds": {
"0": 2
},
@ -172,6 +180,7 @@
"outcome": {
"result": {
"deletedCount": 1,
"insertedCount": 3,
"insertedIds": {
"0": 2,
"2": 3,
@ -262,6 +271,7 @@
"outcome": {
"result": {
"deletedCount": 0,
"insertedCount": 1,
"insertedIds": {
"0": 2
},
@ -340,6 +350,7 @@
"outcome": {
"result": {
"deletedCount": 0,
"insertedCount": 1,
"insertedIds": {
"0": 2
},
@ -401,6 +412,7 @@
"outcome": {
"result": {
"deletedCount": 0,
"insertedCount": 2,
"insertedIds": {
"0": 2,
"1": 3
@ -483,6 +495,7 @@
"error": true,
"result": {
"deletedCount": 0,
"insertedCount": 0,
"insertedIds": {},
"matchedCount": 0,
"modifiedCount": 0,
@ -554,6 +567,7 @@
"error": true,
"result": {
"deletedCount": 0,
"insertedCount": 1,
"insertedIds": {
"0": 2
},
@ -636,6 +650,7 @@
"error": true,
"result": {
"deletedCount": 0,
"insertedCount": 1,
"insertedIds": {
"1": 2
},
@ -699,6 +714,7 @@
"outcome": {
"result": {
"deletedCount": 1,
"insertedCount": 1,
"insertedIds": {
"1": 2
},
@ -763,6 +779,7 @@
"outcome": {
"result": {
"deletedCount": 0,
"insertedCount": 1,
"insertedIds": {
"1": 2
},

View File

@ -0,0 +1,41 @@
{
"runOn": [
{
"minServerVersion": "3.6",
"topology": [
"replicaset",
"sharded"
]
}
],
"data": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 22
}
],
"tests": [
{
"description": "DeleteMany ignores retryWrites",
"useMultipleMongoses": true,
"operation": {
"name": "deleteMany",
"arguments": {
"filter": {}
}
},
"outcome": {
"result": {
"deletedCount": 2
},
"collection": {
"data": []
}
}
}
]
}

View File

@ -1,4 +1,18 @@
{
"runOn": [
{
"minServerVersion": "4.0",
"topology": [
"replicaset"
]
},
{
"minServerVersion": "4.1.7",
"topology": [
"sharded"
]
}
],
"data": [
{
"_id": 1,
@ -9,7 +23,6 @@
"x": 22
}
],
"minServerVersion": "3.99",
"tests": [
{
"description": "DeleteOne succeeds after PrimarySteppedDown",

View File

@ -1,4 +1,12 @@
{
"runOn": [
{
"minServerVersion": "3.6",
"topology": [
"replicaset"
]
}
],
"data": [
{
"_id": 1,
@ -9,7 +17,6 @@
"x": 22
}
],
"minServerVersion": "3.6",
"tests": [
{
"description": "DeleteOne is committed on first attempt",

View File

@ -1,4 +1,18 @@
{
"runOn": [
{
"minServerVersion": "4.0",
"topology": [
"replicaset"
]
},
{
"minServerVersion": "4.1.7",
"topology": [
"sharded"
]
}
],
"data": [
{
"_id": 1,
@ -9,7 +23,6 @@
"x": 22
}
],
"minServerVersion": "3.99",
"tests": [
{
"description": "FindOneAndDelete succeeds after PrimarySteppedDown",

View File

@ -1,4 +1,12 @@
{
"runOn": [
{
"minServerVersion": "3.6",
"topology": [
"replicaset"
]
}
],
"data": [
{
"_id": 1,
@ -9,7 +17,6 @@
"x": 22
}
],
"minServerVersion": "3.6",
"tests": [
{
"description": "FindOneAndDelete is committed on first attempt",

View File

@ -1,4 +1,18 @@
{
"runOn": [
{
"minServerVersion": "4.0",
"topology": [
"replicaset"
]
},
{
"minServerVersion": "4.1.7",
"topology": [
"sharded"
]
}
],
"data": [
{
"_id": 1,
@ -9,7 +23,6 @@
"x": 22
}
],
"minServerVersion": "3.99",
"tests": [
{
"description": "FindOneAndReplace succeeds after PrimarySteppedDown",

View File

@ -1,4 +1,12 @@
{
"runOn": [
{
"minServerVersion": "3.6",
"topology": [
"replicaset"
]
}
],
"data": [
{
"_id": 1,
@ -9,7 +17,6 @@
"x": 22
}
],
"minServerVersion": "3.6",
"tests": [
{
"description": "FindOneAndReplace is committed on first attempt",

View File

@ -1,4 +1,18 @@
{
"runOn": [
{
"minServerVersion": "4.0",
"topology": [
"replicaset"
]
},
{
"minServerVersion": "4.1.7",
"topology": [
"sharded"
]
}
],
"data": [
{
"_id": 1,
@ -9,7 +23,6 @@
"x": 22
}
],
"minServerVersion": "3.99",
"tests": [
{
"description": "FindOneAndUpdate succeeds after PrimarySteppedDown",

View File

@ -1,4 +1,12 @@
{
"runOn": [
{
"minServerVersion": "3.6",
"topology": [
"replicaset"
]
}
],
"data": [
{
"_id": 1,
@ -9,7 +17,6 @@
"x": 22
}
],
"minServerVersion": "3.6",
"tests": [
{
"description": "FindOneAndUpdate is committed on first attempt",

View File

@ -1,11 +1,24 @@
{
"runOn": [
{
"minServerVersion": "4.0",
"topology": [
"replicaset"
]
},
{
"minServerVersion": "4.1.7",
"topology": [
"sharded"
]
}
],
"data": [
{
"_id": 1,
"x": 11
}
],
"minServerVersion": "3.99",
"tests": [
{
"description": "InsertMany succeeds after PrimarySteppedDown",

View File

@ -1,11 +1,18 @@
{
"runOn": [
{
"minServerVersion": "3.6",
"topology": [
"replicaset"
]
}
],
"data": [
{
"_id": 1,
"x": 11
}
],
"minServerVersion": "3.6",
"tests": [
{
"description": "InsertMany succeeds after one network error",

View File

@ -1,4 +1,18 @@
{
"runOn": [
{
"minServerVersion": "4.0",
"topology": [
"replicaset"
]
},
{
"minServerVersion": "4.1.7",
"topology": [
"sharded"
]
}
],
"data": [
{
"_id": 1,
@ -9,7 +23,6 @@
"x": 22
}
],
"minServerVersion": "3.99",
"tests": [
{
"description": "InsertOne succeeds after connection failure",

View File

@ -1,4 +1,12 @@
{
"runOn": [
{
"minServerVersion": "3.6",
"topology": [
"replicaset"
]
}
],
"data": [
{
"_id": 1,
@ -9,7 +17,6 @@
"x": 22
}
],
"minServerVersion": "3.6",
"tests": [
{
"description": "InsertOne is committed on first attempt",

View File

@ -1,4 +1,18 @@
{
"runOn": [
{
"minServerVersion": "4.0",
"topology": [
"replicaset"
]
},
{
"minServerVersion": "4.1.7",
"topology": [
"sharded"
]
}
],
"data": [
{
"_id": 1,
@ -9,7 +23,6 @@
"x": 22
}
],
"minServerVersion": "3.99",
"tests": [
{
"description": "ReplaceOne succeeds after PrimarySteppedDown",

View File

@ -1,4 +1,12 @@
{
"runOn": [
{
"minServerVersion": "3.6",
"topology": [
"replicaset"
]
}
],
"data": [
{
"_id": 1,
@ -9,7 +17,6 @@
"x": 22
}
],
"minServerVersion": "3.6",
"tests": [
{
"description": "ReplaceOne is committed on first attempt",

View File

@ -0,0 +1,57 @@
{
"runOn": [
{
"minServerVersion": "3.6",
"topology": [
"replicaset",
"sharded"
]
}
],
"data": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 22
}
],
"tests": [
{
"description": "UpdateMany ignores retryWrites",
"useMultipleMongoses": true,
"operation": {
"name": "updateMany",
"arguments": {
"filter": {},
"update": {
"$inc": {
"x": 1
}
}
}
},
"outcome": {
"result": {
"matchedCount": 2,
"modifiedCount": 2,
"upsertedCount": 0
},
"collection": {
"data": [
{
"_id": 1,
"x": 12
},
{
"_id": 2,
"x": 23
}
]
}
}
}
]
}

View File

@ -1,4 +1,18 @@
{
"runOn": [
{
"minServerVersion": "4.0",
"topology": [
"replicaset"
]
},
{
"minServerVersion": "4.1.7",
"topology": [
"sharded"
]
}
],
"data": [
{
"_id": 1,
@ -9,7 +23,6 @@
"x": 22
}
],
"minServerVersion": "3.99",
"tests": [
{
"description": "UpdateOne succeeds after PrimarySteppedDown",

View File

@ -1,4 +1,12 @@
{
"runOn": [
{
"minServerVersion": "3.6",
"topology": [
"replicaset"
]
}
],
"data": [
{
"_id": 1,
@ -9,7 +17,6 @@
"x": 22
}
],
"minServerVersion": "3.6",
"tests": [
{
"description": "UpdateOne is committed on first attempt",

View File

@ -15,7 +15,6 @@
"""Test retryable writes."""
import copy
import json
import os
import sys
@ -36,137 +35,58 @@ from pymongo.operations import (InsertOne,
ReplaceOne,
UpdateMany,
UpdateOne)
from pymongo.results import BulkWriteResult
from pymongo.write_concern import WriteConcern
from test import unittest, client_context, IntegrationTest, SkipTest, client_knobs
from test.test_crud_v1 import check_result as crud_v1_check_result
from test.utils import (rs_or_single_client,
DeprecationFilter,
OvertCommandListener)
from test.test_crud_v1 import check_result, run_operation
OvertCommandListener,
TestCreator)
from test.utils_spec_runner import SpecRunner
# Location of JSON test specifications.
_TEST_PATH = os.path.join(
os.path.dirname(os.path.realpath(__file__)), 'retryable_writes')
class TestAllScenarios(IntegrationTest):
class TestAllScenarios(SpecRunner):
@classmethod
@client_context.require_version_min(3, 5)
@client_context.require_replica_set
@client_context.require_test_commands
def setUpClass(cls):
super(TestAllScenarios, cls).setUpClass()
# Speed up the tests by decreasing the heartbeat frequency.
cls.knobs = client_knobs(heartbeat_frequency=0.1,
min_heartbeat_interval=0.1)
cls.knobs.enable()
def get_object_name(self, op):
return op.get('object', 'collection')
@classmethod
def tearDownClass(cls):
cls.knobs.disable()
super(TestAllScenarios, cls).tearDownClass()
def get_scenario_db_name(self, scenario_def):
return scenario_def.get('database_name', 'pymongo_test')
def tearDown(self):
client_context.client.admin.command(SON([
('configureFailPoint', 'onPrimaryTransactionalWrite'),
('mode', 'off')]))
def get_scenario_coll_name(self, scenario_def):
return scenario_def.get('collection_name', 'test')
def set_fail_point(self, command_args):
cmd = SON([('configureFailPoint', 'onPrimaryTransactionalWrite')])
cmd.update(command_args)
client_context.client.admin.command(cmd)
def create_test(scenario_def, test):
def run_scenario(self):
# Load data.
assert scenario_def['data'], "tests must have non-empty data"
client_context.client.pymongo_test.test.drop()
client_context.client.pymongo_test.test.insert_many(scenario_def['data'])
# Set the failPoint
self.set_fail_point(test['failPoint'])
self.addCleanup(self.set_fail_point, {
'configureFailPoint': test['failPoint']['configureFailPoint'],
'mode': 'off'})
test_outcome = test['outcome']
should_fail = test_outcome.get('error')
def run_test_ops(self, sessions, collection, test):
outcome = test['outcome']
should_fail = outcome.get('error')
result = None
error = None
db = rs_or_single_client(**test.get('clientOptions', {})).pymongo_test
# Close the client explicitly to avoid having too many threads open.
self.addCleanup(db.client.close)
try:
result = run_operation(db.test, test)
result = self.run_operation(
sessions, collection, test['operation'])
except (ConnectionFailure, OperationFailure) as exc:
error = exc
if should_fail:
self.assertIsNotNone(error, 'should have raised an error')
else:
self.assertIsNone(error)
crud_v1_check_result(self, outcome['result'], result)
# Assert final state is expected.
expected_c = test_outcome.get('collection')
if expected_c is not None:
expected_name = expected_c.get('name')
if expected_name is not None:
db_coll = db[expected_name]
else:
db_coll = db.test
self.assertEqual(list(db_coll.find()), expected_c['data'])
expected_result = test_outcome.get('result')
# We can't test the expected result when the test should fail because
# the BulkWriteResult is not reported when raising a network error.
if not should_fail:
check_result(self, expected_result, result)
def create_test(scenario_def, test, name):
@client_context.require_test_commands
def run_scenario(self):
self.run_scenario(scenario_def, test)
return run_scenario
def create_tests():
for dirpath, _, filenames in os.walk(_TEST_PATH):
dirname = os.path.split(dirpath)[-1]
for filename in filenames:
with open(os.path.join(dirpath, filename)) as scenario_stream:
scenario_def = json.load(scenario_stream)
test_type = os.path.splitext(filename)[0]
min_ver, max_ver = None, None
if 'minServerVersion' in scenario_def:
min_ver = tuple(
int(elt) for
elt in scenario_def['minServerVersion'].split('.'))
if 'maxServerVersion' in scenario_def:
max_ver = tuple(
int(elt) for
elt in scenario_def['maxServerVersion'].split('.'))
# Construct test from scenario.
for test in scenario_def['tests']:
new_test = create_test(scenario_def, test)
if min_ver is not None:
new_test = client_context.require_version_min(*min_ver)(
new_test)
if max_ver is not None:
new_test = client_context.require_version_max(*max_ver)(
new_test)
test_name = 'test_%s_%s_%s' % (
dirname,
test_type,
str(test['description'].replace(" ", "_")))
new_test.__name__ = test_name
setattr(TestAllScenarios, new_test.__name__, new_test)
create_tests()
test_creator = TestCreator(create_test, TestAllScenarios, _TEST_PATH)
test_creator.create_tests()
def _retryable_single_statement_ops(coll):

View File

@ -37,7 +37,6 @@ from test import (client_context,
client_knobs,
IntegrationTest,
unittest)
from test.utils import (camel_to_snake,
camel_to_snake_args,
camel_to_upper_camel,
@ -175,7 +174,7 @@ class SpecRunner(IntegrationTest):
self.assertEqual(result, expected_result)
def get_object_name(self, op):
"""Allow CRUD spec to override handling of 'object'
"""Allow subclasses to override handling of 'object'
Transaction spec says 'object' is required.
"""
@ -417,17 +416,22 @@ class SpecRunner(IntegrationTest):
raise unittest.SkipTest(test.get('skipReason'))
def get_scenario_db_name(self, scenario_def):
"""Allow CRUD spec to override a test's database name."""
"""Allow subclasses to override a test's database name."""
return scenario_def['database_name']
def get_scenario_coll_name(self, scenario_def):
"""Allow CRUD spec to override a test's collection name."""
"""Allow subclasses to override a test's collection name."""
return scenario_def['collection_name']
def get_outcome_coll_name(self, outcome, collection):
"""Allow CRUD spec to override outcome collection."""
"""Allow subclasses to override outcome collection."""
return collection.name
def run_test_ops(self, sessions, collection, test):
"""Added to allow retryable writes spec to override a test's
operation."""
self.run_operations(sessions, collection, test['operations'])
def run_scenario(self, scenario_def, test):
self.maybe_skip_scenario(test)
listener = OvertCommandListener()
@ -497,14 +501,15 @@ class SpecRunner(IntegrationTest):
self.addCleanup(end_sessions, sessions)
if 'failPoint' in test:
self.set_fail_point(test['failPoint'])
fp = test['failPoint']
self.set_fail_point(fp)
self.addCleanup(self.set_fail_point, {
'configureFailPoint': 'failCommand', 'mode': 'off'})
'configureFailPoint': fp['configureFailPoint'], 'mode': 'off'})
listener.results.clear()
collection = client[database_name][collection_name]
self.run_operations(sessions, collection, test['operations'])
self.run_test_ops(sessions, collection, test)
end_sessions(sessions)
@ -512,8 +517,9 @@ class SpecRunner(IntegrationTest):
# Disable fail points.
if 'failPoint' in test:
fp = test['failPoint']
self.set_fail_point({
'configureFailPoint': 'failCommand', 'mode': 'off'})
'configureFailPoint': fp['configureFailPoint'], 'mode': 'off'})
# Assert final state is expected.
outcome = test['outcome']