SPEC 1153: Adding readConcern support to aggregation with $out (#400)
This commit is contained in:
parent
3030a5a094
commit
2b1fdb091d
@ -2276,11 +2276,13 @@ class Collection(common.BaseObject):
|
||||
kwargs["cursor"]["batchSize"] = first_batch_size
|
||||
|
||||
cmd.update(kwargs)
|
||||
# Apply this Collection's read concern if $out is not in the
|
||||
# pipeline.
|
||||
if (sock_info.max_wire_version >= 4
|
||||
and 'readConcern' not in cmd
|
||||
and not dollar_out):
|
||||
# Apply this Collection's read concern if
|
||||
# readConcern has not been specified as a kwarg and either
|
||||
# - server version is >= 4.2 or
|
||||
# - server version is >= 3.2 and pipeline doesn't use $out
|
||||
if (('readConcern' not in cmd) and
|
||||
((sock_info.max_wire_version >= 4 and not dollar_out) or
|
||||
(sock_info.max_wire_version >= 8))):
|
||||
read_concern = self.read_concern
|
||||
else:
|
||||
read_concern = None
|
||||
|
||||
39
test/crud/v1/read/count-empty.json
Normal file
39
test/crud/v1/read/count-empty.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"data": [],
|
||||
"tests": [
|
||||
{
|
||||
"description": "Estimated document count with empty collection",
|
||||
"operation": {
|
||||
"name": "estimatedDocumentCount",
|
||||
"arguments": {}
|
||||
},
|
||||
"outcome": {
|
||||
"result": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Count documents with empty collection",
|
||||
"operation": {
|
||||
"name": "countDocuments",
|
||||
"arguments": {
|
||||
"filter": {}
|
||||
}
|
||||
},
|
||||
"outcome": {
|
||||
"result": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Deprecated count with empty collection",
|
||||
"operation": {
|
||||
"name": "count",
|
||||
"arguments": {
|
||||
"filter": {}
|
||||
}
|
||||
},
|
||||
"outcome": {
|
||||
"result": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -97,7 +97,7 @@
|
||||
{
|
||||
"description": "Deprecated count with skip and limit",
|
||||
"operation": {
|
||||
"name": "countDocuments",
|
||||
"name": "count",
|
||||
"arguments": {
|
||||
"filter": {},
|
||||
"skip": 1,
|
||||
@ -28,48 +28,57 @@
|
||||
{
|
||||
"description": "BulkWrite with arrayFilters",
|
||||
"operation": {
|
||||
"name": "bulkWrite",
|
||||
"arguments": {
|
||||
"options": {
|
||||
"ordered": true
|
||||
},
|
||||
"requests": [
|
||||
{
|
||||
"name": "updateOne",
|
||||
"arguments": {
|
||||
"filter": {},
|
||||
"update": {
|
||||
"$set": {
|
||||
"y.$[i].b": 2
|
||||
}
|
||||
},
|
||||
"arrayFilters": [
|
||||
{
|
||||
"i.b": 3
|
||||
}
|
||||
],
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "updateMany",
|
||||
"arguments": {
|
||||
"filter": {},
|
||||
"update": {
|
||||
"$set": {
|
||||
"y.$[i].b": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "updateOne"
|
||||
},
|
||||
{
|
||||
"arguments": {
|
||||
},
|
||||
"arrayFilters": [
|
||||
{
|
||||
"i.b": 1
|
||||
}
|
||||
],
|
||||
"filter": {},
|
||||
"update": {
|
||||
"$set": {
|
||||
"y.$[i].b": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "updateMany"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "bulkWrite"
|
||||
],
|
||||
"options": {
|
||||
"ordered": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"outcome": {
|
||||
"result": {
|
||||
"deletedCount": 0,
|
||||
"insertedCount": 0,
|
||||
"insertedIds": {},
|
||||
"matchedCount": 3,
|
||||
"modifiedCount": 3,
|
||||
"upsertedCount": 0,
|
||||
"upsertedIds": {}
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
{
|
||||
@ -95,16 +104,8 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"result": {
|
||||
"deletedCount": 0,
|
||||
"insertedIds": {},
|
||||
"matchedCount": 3,
|
||||
"modifiedCount": 3,
|
||||
"upsertedCount": 0,
|
||||
"upsertedIds": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
217
test/crud/v1/write/bulkWrite-collation.json
Normal file
217
test/crud/v1/write/bulkWrite-collation.json
Normal file
@ -0,0 +1,217 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"x": 11
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"x": "ping"
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"x": "pINg"
|
||||
},
|
||||
{
|
||||
"_id": 4,
|
||||
"x": "pong"
|
||||
},
|
||||
{
|
||||
"_id": 5,
|
||||
"x": "pONg"
|
||||
}
|
||||
],
|
||||
"minServerVersion": "3.4",
|
||||
"tests": [
|
||||
{
|
||||
"description": "BulkWrite with delete operations and collation",
|
||||
"operation": {
|
||||
"name": "bulkWrite",
|
||||
"arguments": {
|
||||
"requests": [
|
||||
{
|
||||
"name": "deleteOne",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"x": "PING"
|
||||
},
|
||||
"collation": {
|
||||
"locale": "en_US",
|
||||
"strength": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "deleteOne",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"x": "PING"
|
||||
},
|
||||
"collation": {
|
||||
"locale": "en_US",
|
||||
"strength": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "deleteMany",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"x": "PONG"
|
||||
},
|
||||
"collation": {
|
||||
"locale": "en_US",
|
||||
"strength": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"ordered": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"outcome": {
|
||||
"result": {
|
||||
"deletedCount": 4,
|
||||
"insertedCount": 0,
|
||||
"insertedIds": {},
|
||||
"matchedCount": 0,
|
||||
"modifiedCount": 0,
|
||||
"upsertedCount": 0,
|
||||
"upsertedIds": {}
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"x": 11
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "BulkWrite with update operations and collation",
|
||||
"operation": {
|
||||
"name": "bulkWrite",
|
||||
"arguments": {
|
||||
"requests": [
|
||||
{
|
||||
"name": "updateMany",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"x": "ping"
|
||||
},
|
||||
"update": {
|
||||
"$set": {
|
||||
"x": "PONG"
|
||||
}
|
||||
},
|
||||
"collation": {
|
||||
"locale": "en_US",
|
||||
"strength": 3
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "updateOne",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"x": "ping"
|
||||
},
|
||||
"update": {
|
||||
"$set": {
|
||||
"x": "PONG"
|
||||
}
|
||||
},
|
||||
"collation": {
|
||||
"locale": "en_US",
|
||||
"strength": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "replaceOne",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"x": "ping"
|
||||
},
|
||||
"replacement": {
|
||||
"_id": 6,
|
||||
"x": "ping"
|
||||
},
|
||||
"upsert": true,
|
||||
"collation": {
|
||||
"locale": "en_US",
|
||||
"strength": 3
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "updateMany",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"x": "pong"
|
||||
},
|
||||
"update": {
|
||||
"$set": {
|
||||
"x": "PONG"
|
||||
}
|
||||
},
|
||||
"collation": {
|
||||
"locale": "en_US",
|
||||
"strength": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"ordered": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"outcome": {
|
||||
"result": {
|
||||
"deletedCount": 0,
|
||||
"insertedCount": 0,
|
||||
"insertedIds": {},
|
||||
"matchedCount": 6,
|
||||
"modifiedCount": 4,
|
||||
"upsertedCount": 1,
|
||||
"upsertedIds": {
|
||||
"2": 6
|
||||
}
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"x": 11
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"x": "PONG"
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"x": "PONG"
|
||||
},
|
||||
{
|
||||
"_id": 4,
|
||||
"x": "PONG"
|
||||
},
|
||||
{
|
||||
"_id": 5,
|
||||
"x": "PONG"
|
||||
},
|
||||
{
|
||||
"_id": 6,
|
||||
"x": "ping"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
778
test/crud/v1/write/bulkWrite.json
Normal file
778
test/crud/v1/write/bulkWrite.json
Normal file
@ -0,0 +1,778 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"x": 11
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 22
|
||||
}
|
||||
],
|
||||
"minServerVersion": "2.6",
|
||||
"tests": [
|
||||
{
|
||||
"description": "BulkWrite with deleteOne operations",
|
||||
"operation": {
|
||||
"name": "bulkWrite",
|
||||
"arguments": {
|
||||
"requests": [
|
||||
{
|
||||
"name": "deleteOne",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"_id": 3
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "deleteOne",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"_id": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"ordered": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"outcome": {
|
||||
"result": {
|
||||
"deletedCount": 1,
|
||||
"insertedCount": 0,
|
||||
"insertedIds": {},
|
||||
"matchedCount": 0,
|
||||
"modifiedCount": 0,
|
||||
"upsertedCount": 0,
|
||||
"upsertedIds": {}
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"x": 11
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "BulkWrite with deleteMany operations",
|
||||
"operation": {
|
||||
"name": "bulkWrite",
|
||||
"arguments": {
|
||||
"requests": [
|
||||
{
|
||||
"name": "deleteMany",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"x": {
|
||||
"$lt": 11
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "deleteMany",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"x": {
|
||||
"$lte": 22
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"ordered": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"outcome": {
|
||||
"result": {
|
||||
"deletedCount": 2,
|
||||
"insertedCount": 0,
|
||||
"insertedIds": {},
|
||||
"matchedCount": 0,
|
||||
"modifiedCount": 0,
|
||||
"upsertedCount": 0,
|
||||
"upsertedIds": {}
|
||||
},
|
||||
"collection": {
|
||||
"data": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "BulkWrite with insertOne operations",
|
||||
"operation": {
|
||||
"name": "bulkWrite",
|
||||
"arguments": {
|
||||
"requests": [
|
||||
{
|
||||
"name": "insertOne",
|
||||
"arguments": {
|
||||
"document": {
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "insertOne",
|
||||
"arguments": {
|
||||
"document": {
|
||||
"_id": 4,
|
||||
"x": 44
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"ordered": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"outcome": {
|
||||
"result": {
|
||||
"deletedCount": 0,
|
||||
"insertedCount": 2,
|
||||
"insertedIds": {
|
||||
"0": 3,
|
||||
"1": 4
|
||||
},
|
||||
"matchedCount": 0,
|
||||
"modifiedCount": 0,
|
||||
"upsertedCount": 0,
|
||||
"upsertedIds": {}
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"x": 11
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 22
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
},
|
||||
{
|
||||
"_id": 4,
|
||||
"x": 44
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "BulkWrite with replaceOne operations",
|
||||
"operation": {
|
||||
"name": "bulkWrite",
|
||||
"arguments": {
|
||||
"requests": [
|
||||
{
|
||||
"name": "replaceOne",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"_id": 3
|
||||
},
|
||||
"replacement": {
|
||||
"x": 33
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "replaceOne",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"_id": 1
|
||||
},
|
||||
"replacement": {
|
||||
"x": 12
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "replaceOne",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"_id": 3
|
||||
},
|
||||
"replacement": {
|
||||
"x": 33
|
||||
},
|
||||
"upsert": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"ordered": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"outcome": {
|
||||
"result": {
|
||||
"deletedCount": 0,
|
||||
"insertedCount": 0,
|
||||
"insertedIds": {},
|
||||
"matchedCount": 1,
|
||||
"modifiedCount": 1,
|
||||
"upsertedCount": 1,
|
||||
"upsertedIds": {
|
||||
"2": 3
|
||||
}
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"x": 12
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 22
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "BulkWrite with updateOne operations",
|
||||
"operation": {
|
||||
"name": "bulkWrite",
|
||||
"arguments": {
|
||||
"requests": [
|
||||
{
|
||||
"name": "updateOne",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"_id": 0
|
||||
},
|
||||
"update": {
|
||||
"$set": {
|
||||
"x": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "updateOne",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"_id": 1
|
||||
},
|
||||
"update": {
|
||||
"$set": {
|
||||
"x": 11
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "updateOne",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"_id": 2
|
||||
},
|
||||
"update": {
|
||||
"$inc": {
|
||||
"x": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "updateOne",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"_id": 3
|
||||
},
|
||||
"update": {
|
||||
"$set": {
|
||||
"x": 33
|
||||
}
|
||||
},
|
||||
"upsert": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"ordered": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"outcome": {
|
||||
"result": {
|
||||
"deletedCount": 0,
|
||||
"insertedCount": 0,
|
||||
"insertedIds": {},
|
||||
"matchedCount": 2,
|
||||
"modifiedCount": 1,
|
||||
"upsertedCount": 1,
|
||||
"upsertedIds": {
|
||||
"3": 3
|
||||
}
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"x": 11
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 23
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "BulkWrite with updateMany operations",
|
||||
"operation": {
|
||||
"name": "bulkWrite",
|
||||
"arguments": {
|
||||
"requests": [
|
||||
{
|
||||
"name": "updateMany",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"x": {
|
||||
"$lt": 11
|
||||
}
|
||||
},
|
||||
"update": {
|
||||
"$set": {
|
||||
"x": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "updateMany",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"x": {
|
||||
"$lte": 22
|
||||
}
|
||||
},
|
||||
"update": {
|
||||
"$unset": {
|
||||
"y": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "updateMany",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"x": {
|
||||
"$lte": 22
|
||||
}
|
||||
},
|
||||
"update": {
|
||||
"$inc": {
|
||||
"x": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "updateMany",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"_id": 3
|
||||
},
|
||||
"update": {
|
||||
"$set": {
|
||||
"x": 33
|
||||
}
|
||||
},
|
||||
"upsert": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"ordered": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"outcome": {
|
||||
"result": {
|
||||
"deletedCount": 0,
|
||||
"insertedCount": 0,
|
||||
"insertedIds": {},
|
||||
"matchedCount": 4,
|
||||
"modifiedCount": 2,
|
||||
"upsertedCount": 1,
|
||||
"upsertedIds": {
|
||||
"3": 3
|
||||
}
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"x": 12
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 23
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "BulkWrite with mixed ordered operations",
|
||||
"operation": {
|
||||
"name": "bulkWrite",
|
||||
"arguments": {
|
||||
"requests": [
|
||||
{
|
||||
"name": "insertOne",
|
||||
"arguments": {
|
||||
"document": {
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "updateOne",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"_id": 2
|
||||
},
|
||||
"update": {
|
||||
"$inc": {
|
||||
"x": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "updateMany",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"_id": {
|
||||
"$gt": 1
|
||||
}
|
||||
},
|
||||
"update": {
|
||||
"$inc": {
|
||||
"x": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "insertOne",
|
||||
"arguments": {
|
||||
"document": {
|
||||
"_id": 4,
|
||||
"x": 44
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "deleteMany",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"x": {
|
||||
"$nin": [
|
||||
24,
|
||||
34
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "replaceOne",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"_id": 4
|
||||
},
|
||||
"replacement": {
|
||||
"_id": 4,
|
||||
"x": 44
|
||||
},
|
||||
"upsert": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"ordered": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"outcome": {
|
||||
"result": {
|
||||
"deletedCount": 2,
|
||||
"insertedCount": 2,
|
||||
"insertedIds": {
|
||||
"0": 3,
|
||||
"3": 4
|
||||
},
|
||||
"matchedCount": 3,
|
||||
"modifiedCount": 3,
|
||||
"upsertedCount": 1,
|
||||
"upsertedIds": {
|
||||
"5": 4
|
||||
}
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 24
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"x": 34
|
||||
},
|
||||
{
|
||||
"_id": 4,
|
||||
"x": 44
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "BulkWrite with mixed unordered operations",
|
||||
"operation": {
|
||||
"name": "bulkWrite",
|
||||
"arguments": {
|
||||
"requests": [
|
||||
{
|
||||
"name": "replaceOne",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"_id": 3
|
||||
},
|
||||
"replacement": {
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
},
|
||||
"upsert": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "deleteOne",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"_id": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "updateOne",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"_id": 2
|
||||
},
|
||||
"update": {
|
||||
"$inc": {
|
||||
"x": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"ordered": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"outcome": {
|
||||
"result": {
|
||||
"deletedCount": 1,
|
||||
"insertedCount": 0,
|
||||
"insertedIds": {},
|
||||
"matchedCount": 1,
|
||||
"modifiedCount": 1,
|
||||
"upsertedCount": 1,
|
||||
"upsertedIds": {
|
||||
"0": 3
|
||||
}
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 23
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "BulkWrite continue-on-error behavior with unordered (preexisting duplicate key)",
|
||||
"operation": {
|
||||
"name": "bulkWrite",
|
||||
"arguments": {
|
||||
"requests": [
|
||||
{
|
||||
"name": "insertOne",
|
||||
"arguments": {
|
||||
"document": {
|
||||
"_id": 2,
|
||||
"x": 22
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "insertOne",
|
||||
"arguments": {
|
||||
"document": {
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "insertOne",
|
||||
"arguments": {
|
||||
"document": {
|
||||
"_id": 4,
|
||||
"x": 44
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"ordered": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"outcome": {
|
||||
"error": true,
|
||||
"result": {
|
||||
"deletedCount": 0,
|
||||
"insertedCount": 2,
|
||||
"matchedCount": 0,
|
||||
"modifiedCount": 0,
|
||||
"upsertedCount": 0,
|
||||
"upsertedIds": {}
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"x": 11
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 22
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
},
|
||||
{
|
||||
"_id": 4,
|
||||
"x": 44
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "BulkWrite continue-on-error behavior with unordered (duplicate key in requests)",
|
||||
"operation": {
|
||||
"name": "bulkWrite",
|
||||
"arguments": {
|
||||
"requests": [
|
||||
{
|
||||
"name": "insertOne",
|
||||
"arguments": {
|
||||
"document": {
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "insertOne",
|
||||
"arguments": {
|
||||
"document": {
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "insertOne",
|
||||
"arguments": {
|
||||
"document": {
|
||||
"_id": 4,
|
||||
"x": 44
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"ordered": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"outcome": {
|
||||
"error": true,
|
||||
"result": {
|
||||
"deletedCount": 0,
|
||||
"insertedCount": 2,
|
||||
"matchedCount": 0,
|
||||
"modifiedCount": 0,
|
||||
"upsertedCount": 0,
|
||||
"upsertedIds": {}
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"x": 11
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 22
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
},
|
||||
{
|
||||
"_id": 4,
|
||||
"x": 44
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
159
test/crud/v1/write/insertMany.json
Normal file
159
test/crud/v1/write/insertMany.json
Normal file
@ -0,0 +1,159 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"x": 11
|
||||
}
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"description": "InsertMany with non-existing documents",
|
||||
"operation": {
|
||||
"name": "insertMany",
|
||||
"arguments": {
|
||||
"documents": [
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 22
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"ordered": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"outcome": {
|
||||
"result": {
|
||||
"insertedIds": {
|
||||
"0": 2,
|
||||
"1": 3
|
||||
}
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"x": 11
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 22
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "InsertMany continue-on-error behavior with unordered (preexisting duplicate key)",
|
||||
"operation": {
|
||||
"name": "insertMany",
|
||||
"arguments": {
|
||||
"documents": [
|
||||
{
|
||||
"_id": 1,
|
||||
"x": 11
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 22
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"ordered": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"outcome": {
|
||||
"error": true,
|
||||
"result": {
|
||||
"deletedCount": 0,
|
||||
"insertedCount": 2,
|
||||
"matchedCount": 0,
|
||||
"modifiedCount": 0,
|
||||
"upsertedCount": 0,
|
||||
"upsertedIds": {}
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"x": 11
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 22
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "InsertMany continue-on-error behavior with unordered (duplicate key in requests)",
|
||||
"operation": {
|
||||
"name": "insertMany",
|
||||
"arguments": {
|
||||
"documents": [
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 22
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 22
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"ordered": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"outcome": {
|
||||
"error": true,
|
||||
"result": {
|
||||
"deletedCount": 0,
|
||||
"insertedCount": 2,
|
||||
"matchedCount": 0,
|
||||
"modifiedCount": 0,
|
||||
"upsertedCount": 0,
|
||||
"upsertedIds": {}
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"x": 11
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 22
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -46,7 +46,8 @@
|
||||
"outcome": {
|
||||
"result": {
|
||||
"matchedCount": 2,
|
||||
"modifiedCount": 0
|
||||
"modifiedCount": 0,
|
||||
"upsertedCount": 0
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
@ -97,7 +98,8 @@
|
||||
"outcome": {
|
||||
"result": {
|
||||
"matchedCount": 2,
|
||||
"modifiedCount": 1
|
||||
"modifiedCount": 1,
|
||||
"upsertedCount": 0
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
@ -148,7 +150,8 @@
|
||||
"outcome": {
|
||||
"result": {
|
||||
"matchedCount": 2,
|
||||
"modifiedCount": 2
|
||||
"modifiedCount": 2,
|
||||
"upsertedCount": 0
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
@ -62,7 +62,8 @@
|
||||
"outcome": {
|
||||
"result": {
|
||||
"matchedCount": 1,
|
||||
"modifiedCount": 0
|
||||
"modifiedCount": 0,
|
||||
"upsertedCount": 0
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
@ -129,7 +130,8 @@
|
||||
"outcome": {
|
||||
"result": {
|
||||
"matchedCount": 1,
|
||||
"modifiedCount": 1
|
||||
"modifiedCount": 1,
|
||||
"upsertedCount": 0
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
@ -196,7 +198,8 @@
|
||||
"outcome": {
|
||||
"result": {
|
||||
"matchedCount": 1,
|
||||
"modifiedCount": 1
|
||||
"modifiedCount": 1,
|
||||
"upsertedCount": 0
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
@ -268,7 +271,8 @@
|
||||
"outcome": {
|
||||
"result": {
|
||||
"matchedCount": 1,
|
||||
"modifiedCount": 0
|
||||
"modifiedCount": 0,
|
||||
"upsertedCount": 0
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
@ -340,7 +344,8 @@
|
||||
"outcome": {
|
||||
"result": {
|
||||
"matchedCount": 1,
|
||||
"modifiedCount": 1
|
||||
"modifiedCount": 1,
|
||||
"upsertedCount": 0
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
377
test/crud/v2/aggregate-out-readConcern.json
Normal file
377
test/crud/v2/aggregate-out-readConcern.json
Normal file
@ -0,0 +1,377 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"x": 11
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 22
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
}
|
||||
],
|
||||
"minServerVersion": "4.1",
|
||||
"collection_name": "test_aggregate_out_readconcern",
|
||||
"tests": [
|
||||
{
|
||||
"description": "readConcern majority with out stage",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection",
|
||||
"name": "aggregate",
|
||||
"collectionOptions": {
|
||||
"readConcern": {
|
||||
"level": "majority"
|
||||
}
|
||||
},
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$sort": {
|
||||
"x": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"$match": {
|
||||
"_id": {
|
||||
"$gt": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$out": "other_test_collection"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"command_started_event": {
|
||||
"command": {
|
||||
"aggregate": "test_aggregate_out_readconcern",
|
||||
"pipeline": [
|
||||
{
|
||||
"$sort": {
|
||||
"x": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"$match": {
|
||||
"_id": {
|
||||
"$gt": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$out": "other_test_collection"
|
||||
}
|
||||
],
|
||||
"readConcern": {
|
||||
"level": "majority"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"outcome": {
|
||||
"collection": {
|
||||
"name": "other_test_collection",
|
||||
"data": [
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 22
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "readConcern local with out stage",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection",
|
||||
"name": "aggregate",
|
||||
"collectionOptions": {
|
||||
"readConcern": {
|
||||
"level": "local"
|
||||
}
|
||||
},
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$sort": {
|
||||
"x": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"$match": {
|
||||
"_id": {
|
||||
"$gt": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$out": "other_test_collection"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"command_started_event": {
|
||||
"command": {
|
||||
"aggregate": "test_aggregate_out_readconcern",
|
||||
"pipeline": [
|
||||
{
|
||||
"$sort": {
|
||||
"x": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"$match": {
|
||||
"_id": {
|
||||
"$gt": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$out": "other_test_collection"
|
||||
}
|
||||
],
|
||||
"readConcern": {
|
||||
"level": "local"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"outcome": {
|
||||
"collection": {
|
||||
"name": "other_test_collection",
|
||||
"data": [
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 22
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "readConcern available with out stage",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection",
|
||||
"name": "aggregate",
|
||||
"collectionOptions": {
|
||||
"readConcern": {
|
||||
"level": "available"
|
||||
}
|
||||
},
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$sort": {
|
||||
"x": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"$match": {
|
||||
"_id": {
|
||||
"$gt": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$out": "other_test_collection"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"command_started_event": {
|
||||
"command": {
|
||||
"aggregate": "test_aggregate_out_readconcern",
|
||||
"pipeline": [
|
||||
{
|
||||
"$sort": {
|
||||
"x": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"$match": {
|
||||
"_id": {
|
||||
"$gt": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$out": "other_test_collection"
|
||||
}
|
||||
],
|
||||
"readConcern": {
|
||||
"level": "available"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"outcome": {
|
||||
"collection": {
|
||||
"name": "other_test_collection",
|
||||
"data": [
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 22
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "readConcern linearizable with out stage",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection",
|
||||
"name": "aggregate",
|
||||
"collectionOptions": {
|
||||
"readConcern": {
|
||||
"level": "linearizable"
|
||||
}
|
||||
},
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$sort": {
|
||||
"x": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"$match": {
|
||||
"_id": {
|
||||
"$gt": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$out": "other_test_collection"
|
||||
}
|
||||
]
|
||||
},
|
||||
"error": true
|
||||
}
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"command_started_event": {
|
||||
"command": {
|
||||
"aggregate": "test_aggregate_out_readconcern",
|
||||
"pipeline": [
|
||||
{
|
||||
"$sort": {
|
||||
"x": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"$match": {
|
||||
"_id": {
|
||||
"$gt": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$out": "other_test_collection"
|
||||
}
|
||||
],
|
||||
"readConcern": {
|
||||
"level": "linearizable"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "invalid readConcern with out stage",
|
||||
"operations": [
|
||||
{
|
||||
"object": "collection",
|
||||
"name": "aggregate",
|
||||
"collectionOptions": {
|
||||
"readConcern": {
|
||||
"level": "!invalid123"
|
||||
}
|
||||
},
|
||||
"arguments": {
|
||||
"pipeline": [
|
||||
{
|
||||
"$sort": {
|
||||
"x": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"$match": {
|
||||
"_id": {
|
||||
"$gt": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$out": "other_test_collection"
|
||||
}
|
||||
]
|
||||
},
|
||||
"error": true
|
||||
}
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"command_started_event": {
|
||||
"command": {
|
||||
"aggregate": "test_aggregate_out_readconcern",
|
||||
"pipeline": [
|
||||
{
|
||||
"$sort": {
|
||||
"x": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"$match": {
|
||||
"_id": {
|
||||
"$gt": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$out": "other_test_collection"
|
||||
}
|
||||
],
|
||||
"readConcern": {
|
||||
"level": "!invalid123"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
156
test/crud/v2/bulkWrite-arrayFilters.json
Normal file
156
test/crud/v2/bulkWrite-arrayFilters.json
Normal file
@ -0,0 +1,156 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"y": [
|
||||
{
|
||||
"b": 3
|
||||
},
|
||||
{
|
||||
"b": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"y": [
|
||||
{
|
||||
"b": 0
|
||||
},
|
||||
{
|
||||
"b": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"minServerVersion": "3.5.6",
|
||||
"collection_name": "test",
|
||||
"database_name": "crud-tests",
|
||||
"tests": [
|
||||
{
|
||||
"description": "BulkWrite with arrayFilters",
|
||||
"operations": [
|
||||
{
|
||||
"name": "bulkWrite",
|
||||
"arguments": {
|
||||
"requests": [
|
||||
{
|
||||
"name": "updateOne",
|
||||
"arguments": {
|
||||
"filter": {},
|
||||
"update": {
|
||||
"$set": {
|
||||
"y.$[i].b": 2
|
||||
}
|
||||
},
|
||||
"arrayFilters": [
|
||||
{
|
||||
"i.b": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "updateMany",
|
||||
"arguments": {
|
||||
"filter": {},
|
||||
"update": {
|
||||
"$set": {
|
||||
"y.$[i].b": 2
|
||||
}
|
||||
},
|
||||
"arrayFilters": [
|
||||
{
|
||||
"i.b": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"ordered": true
|
||||
}
|
||||
},
|
||||
"result": {
|
||||
"deletedCount": 0,
|
||||
"insertedCount": 0,
|
||||
"insertedIds": {},
|
||||
"matchedCount": 3,
|
||||
"modifiedCount": 3,
|
||||
"upsertedCount": 0,
|
||||
"upsertedIds": {}
|
||||
}
|
||||
}
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"command_started_event": {
|
||||
"command": {
|
||||
"update": "test",
|
||||
"updates": [
|
||||
{
|
||||
"q": {},
|
||||
"u": {
|
||||
"$set": {
|
||||
"y.$[i].b": 2
|
||||
}
|
||||
},
|
||||
"arrayFilters": [
|
||||
{
|
||||
"i.b": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"q": {},
|
||||
"u": {
|
||||
"$set": {
|
||||
"y.$[i].b": 2
|
||||
}
|
||||
},
|
||||
"multi": true,
|
||||
"arrayFilters": [
|
||||
{
|
||||
"i.b": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ordered": true
|
||||
},
|
||||
"command_name": "update",
|
||||
"database_name": "crud-tests"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outcome": {
|
||||
"collection": {
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"y": [
|
||||
{
|
||||
"b": 2
|
||||
},
|
||||
{
|
||||
"b": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"y": [
|
||||
{
|
||||
"b": 0
|
||||
},
|
||||
{
|
||||
"b": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"x": 11
|
||||
}
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"description": "InsertMany with non-existing documents",
|
||||
"operation": {
|
||||
"name": "insertMany",
|
||||
"arguments": {
|
||||
"documents": [
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 22
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"outcome": {
|
||||
"result": {
|
||||
"insertedIds": {
|
||||
"0": 2,
|
||||
"1": 3
|
||||
}
|
||||
},
|
||||
"collection": {
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"x": 11
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 22
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"x": 33
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -22,9 +22,11 @@ import sys
|
||||
sys.path[0:0] = [""]
|
||||
|
||||
from bson.py3compat import iteritems
|
||||
from pymongo import operations
|
||||
from pymongo import operations, WriteConcern
|
||||
from pymongo.command_cursor import CommandCursor
|
||||
from pymongo.cursor import Cursor
|
||||
from pymongo.errors import PyMongoError
|
||||
from pymongo.read_concern import ReadConcern
|
||||
from pymongo.results import _WriteResult, BulkWriteResult
|
||||
from pymongo.operations import (InsertOne,
|
||||
DeleteOne,
|
||||
@ -34,27 +36,18 @@ from pymongo.operations import (InsertOne,
|
||||
UpdateMany)
|
||||
|
||||
from test import unittest, client_context, IntegrationTest
|
||||
from test.utils import drop_collections
|
||||
from test.utils import (camel_to_snake, camel_to_upper_camel,
|
||||
camel_to_snake_args, drop_collections, TestCreator)
|
||||
|
||||
# Location of JSON test specifications.
|
||||
_TEST_PATH = os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)), 'crud')
|
||||
os.path.dirname(os.path.realpath(__file__)), 'crud', 'v1')
|
||||
|
||||
|
||||
class TestAllScenarios(IntegrationTest):
|
||||
pass
|
||||
|
||||
|
||||
def camel_to_snake(camel):
|
||||
# Regex to convert CamelCase to snake_case.
|
||||
snake = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', camel)
|
||||
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', snake).lower()
|
||||
|
||||
|
||||
def camel_to_upper_camel(camel):
|
||||
return camel[0].upper() + camel[1:]
|
||||
|
||||
|
||||
def check_result(expected_result, result):
|
||||
if isinstance(result, Cursor) or isinstance(result, CommandCursor):
|
||||
return list(result) == expected_result
|
||||
@ -96,19 +89,12 @@ def check_result(expected_result, result):
|
||||
return False
|
||||
return True
|
||||
else:
|
||||
if not expected_result:
|
||||
if expected_result is None:
|
||||
return result is None
|
||||
else:
|
||||
return result == expected_result
|
||||
|
||||
|
||||
def camel_to_snake_args(arguments):
|
||||
for arg_name in list(arguments):
|
||||
c2s = camel_to_snake(arg_name)
|
||||
arguments[c2s] = arguments.pop(arg_name)
|
||||
return arguments
|
||||
|
||||
|
||||
def run_operation(collection, test):
|
||||
# Convert command from CamelCase to pymongo.collection method.
|
||||
operation = camel_to_snake(test['operation']['name'])
|
||||
@ -156,14 +142,25 @@ def run_operation(collection, test):
|
||||
return result
|
||||
|
||||
|
||||
def create_test(scenario_def, test):
|
||||
def create_test(scenario_def, test, name):
|
||||
def run_scenario(self):
|
||||
# Load data.
|
||||
assert scenario_def['data'], "tests must have non-empty data"
|
||||
# Cleanup state and load data (if provided).
|
||||
drop_collections(self.db)
|
||||
self.db.test.insert_many(scenario_def['data'])
|
||||
data = scenario_def.get('data')
|
||||
if data:
|
||||
self.db.test.with_options(
|
||||
write_concern=WriteConcern(w="majority")).insert_many(
|
||||
scenario_def['data'])
|
||||
|
||||
result = run_operation(self.db.test, test)
|
||||
# Run operations and check results or errors.
|
||||
expected_result = test.get('outcome', {}).get('result')
|
||||
expected_error = test.get('outcome', {}).get('error')
|
||||
if expected_error is True:
|
||||
with self.assertRaises(PyMongoError):
|
||||
run_operation(self.db.test, test)
|
||||
else:
|
||||
result = run_operation(self.db.test, test)
|
||||
self.assertTrue(check_result(expected_result, result))
|
||||
|
||||
# Assert final state is expected.
|
||||
expected_c = test['outcome'].get('collection')
|
||||
@ -173,53 +170,15 @@ def create_test(scenario_def, test):
|
||||
db_coll = self.db[expected_name]
|
||||
else:
|
||||
db_coll = self.db.test
|
||||
db_coll = db_coll.with_options(
|
||||
read_concern=ReadConcern(level="local"))
|
||||
self.assertEqual(list(db_coll.find()), expected_c['data'])
|
||||
expected_result = test['outcome'].get('result')
|
||||
self.assertTrue(check_result(expected_result, result))
|
||||
|
||||
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()
|
||||
|
||||
|
||||
class TestWriteOpsComparison(unittest.TestCase):
|
||||
258
test/test_crud_v2.py
Normal file
258
test/test_crud_v2.py
Normal file
@ -0,0 +1,258 @@
|
||||
# Copyright 2019-present MongoDB, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Test the collection module."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path[0:0] = [""]
|
||||
|
||||
from bson.py3compat import iteritems
|
||||
from pymongo import operations, WriteConcern
|
||||
from pymongo.command_cursor import CommandCursor
|
||||
from pymongo.cursor import Cursor
|
||||
from pymongo.errors import PyMongoError
|
||||
from pymongo.read_concern import ReadConcern
|
||||
from pymongo.results import _WriteResult, BulkWriteResult
|
||||
|
||||
from test import unittest, client_context, IntegrationTest
|
||||
from test.utils import (camel_to_snake, camel_to_upper_camel,
|
||||
camel_to_snake_args, drop_collections,
|
||||
parse_collection_options, rs_client,
|
||||
OvertCommandListener, TestCreator)
|
||||
|
||||
# Location of JSON test specifications.
|
||||
_TEST_PATH = os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)), 'crud', 'v2')
|
||||
|
||||
# Default test database and collection names.
|
||||
TEST_DB = 'testdb'
|
||||
TEST_COLLECTION = 'testcollection'
|
||||
|
||||
|
||||
class TestAllScenarios(IntegrationTest):
|
||||
def run_operation(self, collection, test):
|
||||
# Iterate over all operations.
|
||||
for opdef in test['operations']:
|
||||
# Convert command from CamelCase to pymongo.collection method.
|
||||
operation = camel_to_snake(opdef['name'])
|
||||
|
||||
# Get command handle on target entity (collection/database).
|
||||
target_object = opdef.get('object', 'collection')
|
||||
if target_object == 'database':
|
||||
cmd = getattr(collection.database, operation)
|
||||
elif target_object == 'collection':
|
||||
collection = collection.with_options(**dict(
|
||||
parse_collection_options(opdef.get(
|
||||
'collectionOptions', {}))))
|
||||
cmd = getattr(collection, operation)
|
||||
else:
|
||||
self.fail("Unknown object name %s" % (target_object,))
|
||||
|
||||
# Convert arguments to snake_case and handle special cases.
|
||||
arguments = opdef['arguments']
|
||||
options = arguments.pop("options", {})
|
||||
|
||||
for option_name in options:
|
||||
arguments[camel_to_snake(option_name)] = options[option_name]
|
||||
|
||||
if operation == "bulk_write":
|
||||
# Parse each request into a bulk write model.
|
||||
requests = []
|
||||
for request in arguments["requests"]:
|
||||
bulk_model = camel_to_upper_camel(request["name"])
|
||||
bulk_class = getattr(operations, bulk_model)
|
||||
bulk_arguments = camel_to_snake_args(request["arguments"])
|
||||
requests.append(bulk_class(**bulk_arguments))
|
||||
arguments["requests"] = requests
|
||||
else:
|
||||
for arg_name in list(arguments):
|
||||
c2s = camel_to_snake(arg_name)
|
||||
# PyMongo accepts sort as list of tuples.
|
||||
if arg_name == "sort":
|
||||
sort_dict = arguments[arg_name]
|
||||
arguments[arg_name] = list(iteritems(sort_dict))
|
||||
# Named "key" instead not fieldName.
|
||||
if arg_name == "fieldName":
|
||||
arguments["key"] = arguments.pop(arg_name)
|
||||
# Aggregate uses "batchSize", while find uses batch_size.
|
||||
elif arg_name == "batchSize" and operation == "aggregate":
|
||||
continue
|
||||
# Requires boolean returnDocument.
|
||||
elif arg_name == "returnDocument":
|
||||
arguments[c2s] = arguments[arg_name] == "After"
|
||||
else:
|
||||
arguments[c2s] = arguments.pop(arg_name)
|
||||
|
||||
if opdef.get('error') is True:
|
||||
with self.assertRaises(PyMongoError):
|
||||
cmd(**arguments)
|
||||
else:
|
||||
result = cmd(**arguments)
|
||||
self.check_result(opdef.get('result'), result)
|
||||
|
||||
def check_result(self, expected_result, result):
|
||||
if expected_result is None:
|
||||
return True
|
||||
|
||||
if isinstance(result, Cursor) or isinstance(result, CommandCursor):
|
||||
return list(result) == expected_result
|
||||
|
||||
elif isinstance(result, _WriteResult):
|
||||
for res in expected_result:
|
||||
prop = camel_to_snake(res)
|
||||
# SPEC-869: Only BulkWriteResult has upserted_count.
|
||||
if (prop == "upserted_count" and
|
||||
not isinstance(result, BulkWriteResult)):
|
||||
if result.upserted_id is not None:
|
||||
upserted_count = 1
|
||||
else:
|
||||
upserted_count = 0
|
||||
if upserted_count != expected_result[res]:
|
||||
return False
|
||||
elif prop == "inserted_ids":
|
||||
# BulkWriteResult does not have inserted_ids.
|
||||
if isinstance(result, BulkWriteResult):
|
||||
if len(expected_result[res]) != result.inserted_count:
|
||||
return False
|
||||
else:
|
||||
# InsertManyResult may be compared to [id1] from the
|
||||
# crud spec or {"0": id1} from the retryable write spec.
|
||||
ids = expected_result[res]
|
||||
if isinstance(ids, dict):
|
||||
ids = [ids[str(i)] for i in range(len(ids))]
|
||||
if ids != result.inserted_ids:
|
||||
return False
|
||||
elif prop == "upserted_ids":
|
||||
# Convert indexes from strings to integers.
|
||||
ids = expected_result[res]
|
||||
expected_ids = {}
|
||||
for str_index in ids:
|
||||
expected_ids[int(str_index)] = ids[str_index]
|
||||
if expected_ids != result.upserted_ids:
|
||||
return False
|
||||
elif getattr(result, prop) != expected_result[res]:
|
||||
return False
|
||||
return True
|
||||
else:
|
||||
if not expected_result:
|
||||
return result is None
|
||||
else:
|
||||
return result == expected_result
|
||||
|
||||
def check_events(self, expected_events, listener):
|
||||
res = listener.results
|
||||
if not len(expected_events):
|
||||
return
|
||||
|
||||
# Expectations only have CommandStartedEvents.
|
||||
self.assertEqual(len(res['started']), len(expected_events))
|
||||
for i, expectation in enumerate(expected_events):
|
||||
event_type = next(iter(expectation))
|
||||
event = res['started'][i]
|
||||
|
||||
# The tests substitute 42 for any number other than 0.
|
||||
if (event.command_name == 'getMore'
|
||||
and event.command['getMore']):
|
||||
event.command['getMore'] = 42
|
||||
elif event.command_name == 'killCursors':
|
||||
event.command['cursors'] = [42]
|
||||
# Add upsert and multi fields back into expectations.
|
||||
elif event.command_name == 'update':
|
||||
updates = expectation[event_type]['command']['updates']
|
||||
for update in updates:
|
||||
update.setdefault('upsert', False)
|
||||
update.setdefault('multi', False)
|
||||
|
||||
# Replace afterClusterTime: 42 with actual afterClusterTime.
|
||||
expected_cmd = expectation[event_type]['command']
|
||||
expected_read_concern = expected_cmd.get('readConcern')
|
||||
if expected_read_concern is not None:
|
||||
time = expected_read_concern.get('afterClusterTime')
|
||||
if time == 42:
|
||||
actual_time = event.command.get(
|
||||
'readConcern', {}).get('afterClusterTime')
|
||||
if actual_time is not None:
|
||||
expected_read_concern['afterClusterTime'] = actual_time
|
||||
|
||||
for attr, expected in expectation[event_type].items():
|
||||
actual = getattr(event, attr)
|
||||
if isinstance(expected, dict):
|
||||
for key, val in expected.items():
|
||||
if val is None:
|
||||
if key in actual:
|
||||
self.fail("Unexpected key [%s] in %r" % (
|
||||
key, actual))
|
||||
elif key not in actual:
|
||||
self.fail("Expected key [%s] in %r" % (
|
||||
key, actual))
|
||||
else:
|
||||
self.assertEqual(val, actual[key],
|
||||
"Key [%s] in %s" % (key, actual))
|
||||
else:
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
|
||||
def create_test(scenario_def, test, name):
|
||||
def run_scenario(self):
|
||||
listener = OvertCommandListener()
|
||||
# New client, to avoid interference from pooled sessions.
|
||||
# Convert test['clientOptions'] to dict to avoid a Jython bug using "**"
|
||||
# with ScenarioDict.
|
||||
client = rs_client(event_listeners=[listener],
|
||||
**dict(test.get('clientOptions', {})))
|
||||
# Close the client explicitly to avoid having too many threads open.
|
||||
self.addCleanup(client.close)
|
||||
|
||||
# Get database and collection objects.
|
||||
database = getattr(
|
||||
client, scenario_def.get('database_name', TEST_DB))
|
||||
drop_collections(database)
|
||||
collection = getattr(
|
||||
database, scenario_def.get('collection_name', TEST_COLLECTION))
|
||||
|
||||
# Populate collection with data and run test.
|
||||
collection.with_options(
|
||||
write_concern=WriteConcern(w="majority")).insert_many(
|
||||
scenario_def.get('data', []))
|
||||
listener.results.clear()
|
||||
self.run_operation(collection, test)
|
||||
|
||||
# Assert expected events.
|
||||
self.check_events(test.get('expectations', {}), listener)
|
||||
|
||||
# Assert final state is expected.
|
||||
expected_outcome = test.get('outcome', {}).get('collection')
|
||||
if expected_outcome is not None:
|
||||
collname = expected_outcome.get('name')
|
||||
if collname is not None:
|
||||
o_collection = getattr(database, collname)
|
||||
else:
|
||||
o_collection = collection
|
||||
o_collection = o_collection.with_options(
|
||||
read_concern=ReadConcern(level="local"))
|
||||
self.assertEqual(list(o_collection.find()),
|
||||
expected_outcome['data'])
|
||||
|
||||
return run_scenario
|
||||
|
||||
|
||||
test_creator = TestCreator(create_test, TestAllScenarios, _TEST_PATH)
|
||||
test_creator.create_tests()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@ -20,7 +20,7 @@ from pymongo.errors import ConfigurationError, OperationFailure
|
||||
from pymongo.read_concern import ReadConcern
|
||||
|
||||
from test import client_context, PyMongoTestCase
|
||||
from test.utils import single_client, rs_or_single_client, EventListener
|
||||
from test.utils import single_client, rs_or_single_client, OvertCommandListener
|
||||
|
||||
|
||||
class TestReadConcern(PyMongoTestCase):
|
||||
@ -28,7 +28,7 @@ class TestReadConcern(PyMongoTestCase):
|
||||
@classmethod
|
||||
@client_context.require_connection
|
||||
def setUpClass(cls):
|
||||
cls.listener = EventListener()
|
||||
cls.listener = OvertCommandListener()
|
||||
cls.saved_listeners = monitoring._LISTENERS
|
||||
# Don't use any global subscribers.
|
||||
monitoring._LISTENERS = monitoring._Listeners([], [], [], [])
|
||||
@ -118,8 +118,14 @@ class TestReadConcern(PyMongoTestCase):
|
||||
except OperationFailure:
|
||||
# "ns doesn't exist"
|
||||
pass
|
||||
self.assertNotIn('readConcern',
|
||||
self.listener.results['started'][0].command)
|
||||
|
||||
# Aggregate with $out supports readConcern MongoDB 4.2 onwards.
|
||||
if client_context.version >= (4, 1):
|
||||
self.assertIn('readConcern',
|
||||
self.listener.results['started'][0].command)
|
||||
else:
|
||||
self.assertNotIn('readConcern',
|
||||
self.listener.results['started'][0].command)
|
||||
|
||||
def test_map_reduce_out(self):
|
||||
coll = self.db.get_collection('coll', read_concern=ReadConcern('local'))
|
||||
|
||||
@ -43,7 +43,7 @@ from test import unittest, client_context, IntegrationTest, SkipTest, client_kno
|
||||
from test.utils import (rs_or_single_client,
|
||||
DeprecationFilter,
|
||||
OvertCommandListener)
|
||||
from test.test_crud import check_result, run_operation
|
||||
from test.test_crud_v1 import check_result, run_operation
|
||||
|
||||
# Location of JSON test specifications.
|
||||
_TEST_PATH = os.path.join(
|
||||
|
||||
@ -981,9 +981,6 @@ class TestCausalConsistency(unittest.TestCase):
|
||||
lambda coll, session: coll.drop_index("foo_1", session=session))
|
||||
self._test_no_read_concern(
|
||||
lambda coll, session: coll.drop_indexes(session=session))
|
||||
self._test_no_read_concern(
|
||||
lambda coll, session: list(
|
||||
coll.aggregate([{"$out": "aggout"}], session=session)))
|
||||
self._test_no_read_concern(
|
||||
lambda coll, session: coll.map_reduce(
|
||||
'function() {}', 'function() {}', 'mrout', session=session))
|
||||
@ -999,6 +996,13 @@ class TestCausalConsistency(unittest.TestCase):
|
||||
self._test_no_read_concern(
|
||||
lambda coll, session: coll.reindex(session=session))
|
||||
|
||||
@client_context.require_no_standalone
|
||||
@client_context.require_version_max(4, 1, 0)
|
||||
def test_aggregate_out_does_not_include_read_concern(self):
|
||||
self._test_no_read_concern(
|
||||
lambda coll, session: list(
|
||||
coll.aggregate([{"$out": "aggout"}], session=session)))
|
||||
|
||||
@client_context.require_no_standalone
|
||||
def test_get_more_does_not_include_read_concern(self):
|
||||
coll = self.client.pymongo_test.test
|
||||
|
||||
@ -14,15 +14,11 @@
|
||||
|
||||
"""Execute Transactions Spec tests."""
|
||||
|
||||
import collections
|
||||
from functools import partial
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
sys.path[0:0] = [""]
|
||||
|
||||
from bson import json_util, py3compat
|
||||
from bson.py3compat import iteritems
|
||||
from bson.son import SON
|
||||
from pymongo import client_session, operations, WriteConcern
|
||||
@ -38,10 +34,9 @@ from pymongo.read_preferences import ReadPreference
|
||||
from pymongo.results import _WriteResult, BulkWriteResult
|
||||
|
||||
from test import unittest, client_context, IntegrationTest
|
||||
from test.utils import (OvertCommandListener,
|
||||
rs_client,
|
||||
single_client,
|
||||
wait_until)
|
||||
from test.utils import (camel_to_snake, camel_to_upper_camel,
|
||||
camel_to_snake_args, rs_client, single_client,
|
||||
wait_until, OvertCommandListener, TestCreator)
|
||||
from test.utils_selection_tests import parse_read_preference
|
||||
|
||||
# Location of JSON test specifications.
|
||||
@ -57,26 +52,7 @@ _TXN_TESTS_DEBUG = os.environ.get('TRANSACTION_TESTS_DEBUG')
|
||||
UNPIN_TEST_MAX_ATTEMPTS = 50
|
||||
|
||||
|
||||
# TODO: factor the following functions with test_crud.py.
|
||||
def camel_to_snake(camel):
|
||||
# Regex to convert CamelCase to snake_case.
|
||||
snake = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', camel)
|
||||
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', snake).lower()
|
||||
|
||||
|
||||
def camel_to_upper_camel(camel):
|
||||
return camel[0].upper() + camel[1:]
|
||||
|
||||
|
||||
def camel_to_snake_args(arguments):
|
||||
for arg_name in list(arguments):
|
||||
c2s = camel_to_snake(arg_name)
|
||||
arguments[c2s] = arguments.pop(arg_name)
|
||||
return arguments
|
||||
|
||||
|
||||
class TestTransactions(IntegrationTest):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestTransactions, cls).setUpClass()
|
||||
@ -255,7 +231,7 @@ class TestTransactions(IntegrationTest):
|
||||
pass
|
||||
self.assertEqual(filtered_result, expected_result)
|
||||
|
||||
# TODO: factor the following function with test_crud.py.
|
||||
# TODO: factor the following function with CRUD v2 test runner.
|
||||
def check_result(self, expected_result, result):
|
||||
if isinstance(result, _WriteResult):
|
||||
for res in expected_result:
|
||||
@ -476,7 +452,8 @@ def end_sessions(sessions):
|
||||
s.end_session()
|
||||
|
||||
|
||||
def create_test(scenario_def, test):
|
||||
def create_test(scenario_def, test, name):
|
||||
@client_context.require_transactions
|
||||
def run_scenario(self):
|
||||
if test.get('skipReason'):
|
||||
raise unittest.SkipTest(test.get('skipReason'))
|
||||
@ -608,65 +585,17 @@ def create_test(scenario_def, test):
|
||||
read_concern=ReadConcern('local'))
|
||||
self.assertEqual(list(primary_coll.find()), expected_c['data'])
|
||||
|
||||
if 'secondary' in name:
|
||||
run_scenario = client_context._require(
|
||||
lambda: client_context.has_secondaries, 'No secondaries',
|
||||
run_scenario)
|
||||
|
||||
return run_scenario
|
||||
|
||||
|
||||
class ScenarioDict(dict):
|
||||
"""Dict that returns {} for any unknown key, recursively."""
|
||||
def __init__(self, data):
|
||||
def convert(v):
|
||||
if isinstance(v, collections.Mapping):
|
||||
return ScenarioDict(v)
|
||||
if isinstance(v, py3compat.string_type):
|
||||
return v
|
||||
if isinstance(v, collections.Sequence):
|
||||
return [convert(item) for item in v]
|
||||
return v
|
||||
test_creator = TestCreator(create_test, TestTransactions, _TEST_PATH)
|
||||
test_creator.create_tests()
|
||||
|
||||
dict.__init__(self, [(k, convert(v)) for k, v in data.items()])
|
||||
|
||||
def __getitem__(self, item):
|
||||
try:
|
||||
return dict.__getitem__(self, item)
|
||||
except KeyError:
|
||||
# Unlike a defaultdict, don't set the key, just return a dict.
|
||||
return ScenarioDict({})
|
||||
|
||||
|
||||
def create_tests():
|
||||
for dirpath, _, filenames in os.walk(_TEST_PATH):
|
||||
dirname = os.path.split(dirpath)[-1]
|
||||
|
||||
for filename in filenames:
|
||||
test_type, ext = os.path.splitext(filename)
|
||||
if ext != '.json':
|
||||
continue
|
||||
|
||||
with open(os.path.join(dirpath, filename)) as scenario_stream:
|
||||
scenario_def = ScenarioDict(
|
||||
json_util.loads(scenario_stream.read()))
|
||||
|
||||
# Construct test from scenario.
|
||||
for test in scenario_def['tests']:
|
||||
test_name = 'test_%s_%s_%s' % (
|
||||
dirname,
|
||||
test_type.replace("-", "_"),
|
||||
str(test['description'].replace(" ", "_")))
|
||||
|
||||
new_test = create_test(scenario_def, test)
|
||||
new_test = client_context.require_transactions(new_test)
|
||||
|
||||
if 'secondary' in test_name:
|
||||
new_test = client_context._require(
|
||||
lambda: client_context.has_secondaries,
|
||||
'No secondaries',
|
||||
new_test)
|
||||
|
||||
new_test.__name__ = test_name
|
||||
setattr(TestTransactions, new_test.__name__, new_test)
|
||||
|
||||
|
||||
create_tests()
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
126
test/utils.py
126
test/utils.py
@ -15,8 +15,11 @@
|
||||
"""Utilities for testing pymongo
|
||||
"""
|
||||
|
||||
import collections
|
||||
import contextlib
|
||||
import functools
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
@ -25,14 +28,16 @@ import warnings
|
||||
from collections import defaultdict
|
||||
from functools import partial
|
||||
|
||||
from bson import json_util, py3compat
|
||||
from bson.objectid import ObjectId
|
||||
from pymongo import MongoClient, monitoring
|
||||
from pymongo.errors import OperationFailure
|
||||
from pymongo.monitoring import _SENSITIVE_COMMANDS
|
||||
from pymongo.read_concern import ReadConcern
|
||||
from pymongo.server_selectors import (any_server_selector,
|
||||
writable_server_selector)
|
||||
from pymongo.write_concern import WriteConcern
|
||||
from test import (client_context, db_user, db_pwd)
|
||||
from test import client_context, db_user, db_pwd
|
||||
|
||||
|
||||
IMPOSSIBLE_WRITE_CONCERN = WriteConcern(w=1000)
|
||||
@ -120,6 +125,93 @@ class HeartbeatEventListener(monitoring.ServerHeartbeatListener):
|
||||
self.results.append(event)
|
||||
|
||||
|
||||
class ScenarioDict(dict):
|
||||
"""Dict that returns {} for any unknown key, recursively."""
|
||||
def __init__(self, data):
|
||||
def convert(v):
|
||||
if isinstance(v, collections.Mapping):
|
||||
return ScenarioDict(v)
|
||||
if isinstance(v, py3compat.string_type):
|
||||
return v
|
||||
if isinstance(v, collections.Sequence):
|
||||
return [convert(item) for item in v]
|
||||
return v
|
||||
|
||||
dict.__init__(self, [(k, convert(v)) for k, v in data.items()])
|
||||
|
||||
def __getitem__(self, item):
|
||||
try:
|
||||
return dict.__getitem__(self, item)
|
||||
except KeyError:
|
||||
# Unlike a defaultdict, don't set the key, just return a dict.
|
||||
return ScenarioDict({})
|
||||
|
||||
|
||||
class TestCreator(object):
|
||||
"""Class to create test cases from specifications."""
|
||||
def __init__(self, create_test, test_class, test_path):
|
||||
"""Create a TestCreator object.
|
||||
|
||||
:Parameters:
|
||||
- `create_test`: callback that returns a test case. The callback
|
||||
must accept the following arguments - a dictionary containing the
|
||||
entire test specification (the `scenario_def`), a dictionary
|
||||
containing the specification for which the test case will be
|
||||
generated (the `test_def`).
|
||||
- `test_class`: the unittest.TestCase class in which to create the
|
||||
test case.
|
||||
- `test_path`: path to the directory containing the JSON files with
|
||||
the test specifications.
|
||||
"""
|
||||
self._create_test = create_test
|
||||
self._test_class= test_class
|
||||
self.test_path = test_path
|
||||
|
||||
def _ensure_min_max_server_version(self, scenario_def, method):
|
||||
"""Test modifier that enforces a version range for the server on a
|
||||
test case."""
|
||||
if 'minServerVersion' in scenario_def:
|
||||
min_ver = tuple(
|
||||
int(elt) for
|
||||
elt in scenario_def['minServerVersion'].split('.'))
|
||||
if min_ver is not None:
|
||||
method = client_context.require_version_min(*min_ver)(method)
|
||||
|
||||
if 'maxServerVersion' in scenario_def:
|
||||
max_ver = tuple(
|
||||
int(elt) for
|
||||
elt in scenario_def['maxServerVersion'].split('.'))
|
||||
if max_ver is not None:
|
||||
method = client_context.require_version_max(*max_ver)(method)
|
||||
|
||||
return method
|
||||
|
||||
def create_tests(self):
|
||||
for dirpath, _, filenames in os.walk(self.test_path):
|
||||
dirname = os.path.split(dirpath)[-1]
|
||||
|
||||
for filename in filenames:
|
||||
with open(os.path.join(dirpath, filename)) as scenario_stream:
|
||||
scenario_def = ScenarioDict(
|
||||
json_util.loads(scenario_stream.read()))
|
||||
|
||||
test_type = os.path.splitext(filename)[0]
|
||||
|
||||
# Construct test from scenario.
|
||||
for test_def in scenario_def['tests']:
|
||||
test_name = 'test_%s_%s_%s' % (
|
||||
dirname, test_type,
|
||||
str(test_def['description'].replace(" ", "_")))
|
||||
|
||||
new_test = self._create_test(
|
||||
scenario_def, test_def, test_name)
|
||||
new_test = self._ensure_min_max_server_version(
|
||||
scenario_def, new_test)
|
||||
|
||||
new_test.__name__ = test_name
|
||||
setattr(self._test_class, new_test.__name__, new_test)
|
||||
|
||||
|
||||
def _connection_string(h, authenticate):
|
||||
if h.startswith("mongodb://"):
|
||||
return h
|
||||
@ -202,6 +294,38 @@ def get_command_line(client):
|
||||
return command_line
|
||||
|
||||
|
||||
def camel_to_snake(camel):
|
||||
# Regex to convert CamelCase to snake_case.
|
||||
snake = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', camel)
|
||||
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', snake).lower()
|
||||
|
||||
|
||||
def camel_to_upper_camel(camel):
|
||||
return camel[0].upper() + camel[1:]
|
||||
|
||||
|
||||
def camel_to_snake_args(arguments):
|
||||
for arg_name in list(arguments):
|
||||
c2s = camel_to_snake(arg_name)
|
||||
arguments[c2s] = arguments.pop(arg_name)
|
||||
return arguments
|
||||
|
||||
|
||||
def parse_collection_options(opts):
|
||||
if 'readPreference' in opts:
|
||||
opts['read_preference'] = parse_read_preference(
|
||||
opts.pop('readPreference'))
|
||||
|
||||
if 'writeConcern' in opts:
|
||||
opts['write_concern'] = WriteConcern(
|
||||
**dict(opts.pop('writeConcern')))
|
||||
|
||||
if 'readConcern' in opts:
|
||||
opts['read_concern'] = ReadConcern(
|
||||
**dict(opts.pop('readConcern')))
|
||||
return opts
|
||||
|
||||
|
||||
def server_started_with_option(client, cmdline_opt, config_opt):
|
||||
"""Check if the server was started with a particular option.
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user