From 44a56b664714855dceb8790fd1df64fc0be4b8fc Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Thu, 24 Jun 2021 15:12:05 -0400 Subject: [PATCH 01/11] PHPLIB-655: Sync Versioned API tests Synced with mongodb/specifications@72249aad5068f9a4d24930e723e90d92bb82b6cb --- .../crud-api-version-1-strict.json | 20 +- .../versioned-api/crud-api-version-1.json | 19 +- ...ommand-helper-no-api-version-declared.json | 12 +- .../test-commands-deprecation-errors.json | 2 +- .../test-commands-strict-mode.json | 5 +- .../versioned-api/transaction-handling.json | 207 ++---------------- 6 files changed, 53 insertions(+), 212 deletions(-) diff --git a/tests/UnifiedSpecTests/versioned-api/crud-api-version-1-strict.json b/tests/UnifiedSpecTests/versioned-api/crud-api-version-1-strict.json index e3bb4130b..29a0ec4e3 100644 --- a/tests/UnifiedSpecTests/versioned-api/crud-api-version-1-strict.json +++ b/tests/UnifiedSpecTests/versioned-api/crud-api-version-1-strict.json @@ -1,6 +1,6 @@ { "description": "CRUD Api Version 1 (strict)", - "schemaVersion": "1.1", + "schemaVersion": "1.4", "runOnRequirements": [ { "minServerVersion": "4.9" @@ -141,6 +141,11 @@ }, { "description": "aggregate on database appends declared API version", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], "operations": [ { "name": "aggregate", @@ -608,7 +613,6 @@ }, { "description": "estimatedDocumentCount appends declared API version", - "skipReason": "DRIVERS-1561 collStats is not in API version 1", "operations": [ { "name": "estimatedDocumentCount", @@ -652,7 +656,7 @@ ] }, { - "description": "find command with declared API version appends to the command, but getMore does not", + "description": "find and getMore append API version", "operations": [ { "name": "find", @@ -713,14 +717,10 @@ "long" ] }, - "apiVersion": { - "$$exists": false - }, - "apiStrict": { - "$$exists": false - }, + "apiVersion": "1", + "apiStrict": true, "apiDeprecationErrors": { - "$$exists": false + "$$unsetOrMatches": false } } } diff --git a/tests/UnifiedSpecTests/versioned-api/crud-api-version-1.json b/tests/UnifiedSpecTests/versioned-api/crud-api-version-1.json index 917185837..1f135eea1 100644 --- a/tests/UnifiedSpecTests/versioned-api/crud-api-version-1.json +++ b/tests/UnifiedSpecTests/versioned-api/crud-api-version-1.json @@ -1,6 +1,6 @@ { "description": "CRUD Api Version 1", - "schemaVersion": "1.1", + "schemaVersion": "1.4", "runOnRequirements": [ { "minServerVersion": "4.9" @@ -141,6 +141,11 @@ }, { "description": "aggregate on database appends declared API version", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], "operations": [ { "name": "aggregate", @@ -643,7 +648,7 @@ ] }, { - "description": "find command with declared API version appends to the command, but getMore does not", + "description": "find and getMore append API version", "operations": [ { "name": "find", @@ -704,15 +709,11 @@ "long" ] }, - "apiVersion": { - "$$exists": false - }, + "apiVersion": "1", "apiStrict": { - "$$exists": false + "$$unsetOrMatches": false }, - "apiDeprecationErrors": { - "$$exists": false - } + "apiDeprecationErrors": true } } } diff --git a/tests/UnifiedSpecTests/versioned-api/runcommand-helper-no-api-version-declared.json b/tests/UnifiedSpecTests/versioned-api/runcommand-helper-no-api-version-declared.json index e901887e4..17e0126d1 100644 --- a/tests/UnifiedSpecTests/versioned-api/runcommand-helper-no-api-version-declared.json +++ b/tests/UnifiedSpecTests/versioned-api/runcommand-helper-no-api-version-declared.json @@ -1,6 +1,6 @@ { "description": "RunCommand helper: No API version declared", - "schemaVersion": "1.1", + "schemaVersion": "1.4", "runOnRequirements": [ { "minServerVersion": "4.9", @@ -29,6 +29,11 @@ "tests": [ { "description": "runCommand does not inspect or change the command document", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], "operations": [ { "name": "runCommand", @@ -72,6 +77,11 @@ }, { "description": "runCommand does not prevent sending invalid API version declarations", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], "operations": [ { "name": "runCommand", diff --git a/tests/UnifiedSpecTests/versioned-api/test-commands-deprecation-errors.json b/tests/UnifiedSpecTests/versioned-api/test-commands-deprecation-errors.json index 6dc59a045..0668df830 100644 --- a/tests/UnifiedSpecTests/versioned-api/test-commands-deprecation-errors.json +++ b/tests/UnifiedSpecTests/versioned-api/test-commands-deprecation-errors.json @@ -6,7 +6,7 @@ "minServerVersion": "4.9", "serverParameters": { "enableTestCommands": true, - "acceptAPIVersion2": true, + "acceptApiVersion2": true, "requireApiVersion": false } } diff --git a/tests/UnifiedSpecTests/versioned-api/test-commands-strict-mode.json b/tests/UnifiedSpecTests/versioned-api/test-commands-strict-mode.json index 1705ba7bf..9c4ebea78 100644 --- a/tests/UnifiedSpecTests/versioned-api/test-commands-strict-mode.json +++ b/tests/UnifiedSpecTests/versioned-api/test-commands-strict-mode.json @@ -1,12 +1,13 @@ { "description": "Test commands: strict mode", - "schemaVersion": "1.1", + "schemaVersion": "1.4", "runOnRequirements": [ { "minServerVersion": "4.9", "serverParameters": { "enableTestCommands": true - } + }, + "serverless": "forbid" } ], "createEntities": [ diff --git a/tests/UnifiedSpecTests/versioned-api/transaction-handling.json b/tests/UnifiedSpecTests/versioned-api/transaction-handling.json index a740405d3..c00c5240a 100644 --- a/tests/UnifiedSpecTests/versioned-api/transaction-handling.json +++ b/tests/UnifiedSpecTests/versioned-api/transaction-handling.json @@ -1,12 +1,13 @@ { "description": "Transaction handling", - "schemaVersion": "1.1", + "schemaVersion": "1.3", "runOnRequirements": [ { "minServerVersion": "4.9", "topologies": [ "replicaset", - "sharded-replicaset" + "sharded-replicaset", + "load-balanced" ] } ], @@ -53,17 +54,6 @@ "apiDeprecationErrors": { "$$unsetOrMatches": false } - }, - { - "apiVersion": { - "$$exists": false - }, - "apiStrict": { - "$$exists": false - }, - "apiDeprecationErrors": { - "$$exists": false - } } ] }, @@ -97,12 +87,13 @@ ], "tests": [ { - "description": "Only the first command in a transaction declares an API version", + "description": "All commands in a transaction declare an API version", "runOnRequirements": [ { "topologies": [ "replicaset", - "sharded-replicaset" + "sharded-replicaset", + "load-balanced" ] } ], @@ -193,119 +184,6 @@ "lsid": { "$$sessionLsid": "session" }, - "apiVersion": { - "$$exists": false - }, - "apiStrict": { - "$$exists": false - }, - "apiDeprecationErrors": { - "$$exists": false - } - } - } - }, - { - "commandStartedEvent": { - "command": { - "commitTransaction": 1, - "lsid": { - "$$sessionLsid": "session" - }, - "apiVersion": { - "$$exists": false - }, - "apiStrict": { - "$$exists": false - }, - "apiDeprecationErrors": { - "$$exists": false - } - } - } - } - ] - } - ] - }, - { - "description": "Committing a transaction twice does not append server API options", - "runOnRequirements": [ - { - "topologies": [ - "replicaset", - "sharded-replicaset" - ] - } - ], - "operations": [ - { - "name": "startTransaction", - "object": "session" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session", - "document": { - "_id": 6, - "x": 66 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 6 - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session", - "document": { - "_id": 7, - "x": 77 - } - }, - "expectResult": { - "$$unsetOrMatches": { - "insertedId": { - "$$unsetOrMatches": 7 - } - } - } - }, - { - "name": "commitTransaction", - "object": "session" - }, - { - "name": "commitTransaction", - "object": "session" - } - ], - "expectEvents": [ - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 6, - "x": 66 - } - ], - "lsid": { - "$$sessionLsid": "session" - }, - "startTransaction": true, "apiVersion": "1", "apiStrict": { "$$unsetOrMatches": false @@ -316,31 +194,6 @@ } } }, - { - "commandStartedEvent": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 7, - "x": 77 - } - ], - "lsid": { - "$$sessionLsid": "session" - }, - "apiVersion": { - "$$exists": false - }, - "apiStrict": { - "$$exists": false - }, - "apiDeprecationErrors": { - "$$exists": false - } - } - } - }, { "commandStartedEvent": { "command": { @@ -348,33 +201,12 @@ "lsid": { "$$sessionLsid": "session" }, - "apiVersion": { - "$$exists": false - }, - "apiStrict": { - "$$exists": false - }, - "apiDeprecationErrors": { - "$$exists": false - } - } - } - }, - { - "commandStartedEvent": { - "command": { - "commitTransaction": 1, - "lsid": { - "$$sessionLsid": "session" - }, - "apiVersion": { - "$$exists": false - }, + "apiVersion": "1", "apiStrict": { - "$$exists": false + "$$unsetOrMatches": false }, "apiDeprecationErrors": { - "$$exists": false + "$$unsetOrMatches": false } } } @@ -384,12 +216,13 @@ ] }, { - "description": "abortTransaction does not include an API version", + "description": "abortTransaction includes an API version", "runOnRequirements": [ { "topologies": [ "replicaset", - "sharded-replicaset" + "sharded-replicaset", + "load-balanced" ] } ], @@ -480,14 +313,12 @@ "lsid": { "$$sessionLsid": "session" }, - "apiVersion": { - "$$exists": false - }, + "apiVersion": "1", "apiStrict": { - "$$exists": false + "$$unsetOrMatches": false }, "apiDeprecationErrors": { - "$$exists": false + "$$unsetOrMatches": false } } } @@ -499,14 +330,12 @@ "lsid": { "$$sessionLsid": "session" }, - "apiVersion": { - "$$exists": false - }, + "apiVersion": "1", "apiStrict": { - "$$exists": false + "$$unsetOrMatches": false }, "apiDeprecationErrors": { - "$$exists": false + "$$unsetOrMatches": false } } } From acc8f9633e07542cd105ee367b21ebbe4b62de5c Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Thu, 24 Jun 2021 15:18:50 -0400 Subject: [PATCH 02/11] PHPLIB-666: Sync command monitoring tests for 5.0 cursor behavior Synced with mongodb/specifications@9da20b4b28f99037215ddfd78f5b0df04897a0be --- tests/SpecTests/command-monitoring/find.json | 3 ++- tests/UnifiedSpecTests/valid-pass/poc-command-monitoring.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/SpecTests/command-monitoring/find.json b/tests/SpecTests/command-monitoring/find.json index 039c5fead..55b185cc5 100644 --- a/tests/SpecTests/command-monitoring/find.json +++ b/tests/SpecTests/command-monitoring/find.json @@ -413,8 +413,9 @@ ] }, { - "description": "A successful find event with a getmore and the server kills the cursor", + "description": "A successful find event with a getmore and the server kills the cursor (<= 4.4)", "ignore_if_server_version_less_than": "3.1", + "ignore_if_server_version_greater_than": "4.4", "ignore_if_topology_type": [ "sharded" ], diff --git a/tests/UnifiedSpecTests/valid-pass/poc-command-monitoring.json b/tests/UnifiedSpecTests/valid-pass/poc-command-monitoring.json index 499396e0b..fe0a5ae99 100644 --- a/tests/UnifiedSpecTests/valid-pass/poc-command-monitoring.json +++ b/tests/UnifiedSpecTests/valid-pass/poc-command-monitoring.json @@ -57,10 +57,11 @@ ], "tests": [ { - "description": "A successful find event with a getmore and the server kills the cursor", + "description": "A successful find event with a getmore and the server kills the cursor (<= 4.4)", "runOnRequirements": [ { "minServerVersion": "3.1", + "maxServerVersion": "4.4.99", "topologies": [ "single", "replicaset" From 21247acc5b203791faaed5cf20907263042e8369 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Mon, 28 Jun 2021 11:21:37 -0400 Subject: [PATCH 03/11] PHPLIB-665: Sync spec tests for dots/dollars This updates individual tests pertaining to dots/dollars. Synced with mongodb/specifications@44f83106033a609f2af40e3e4d99852a03096ba5 Removes skips previously added in a66f31f3fab2c9b08402770489e0f88d44dce9f3 and ea9853af30c3eb899745c4a67dfec21932a37c53 --- tests/SpecTests/TransactionsSpecTest.php | 2 - .../SpecTests/transactions/errors-client.json | 26 ++++---- tests/UnifiedSpecTests/UnifiedSpecTest.php | 61 ------------------- .../crud/insertMany-dots_and_dollars.json | 36 ++++++----- .../crud/insertOne-dots_and_dollars.json | 50 ++++++++------- .../UnifiedSpecTests/valid-pass/poc-crud.json | 19 +++--- .../valid-pass/poc-retryable-writes.json | 3 - .../valid-pass/poc-transactions.json | 11 ++-- 8 files changed, 80 insertions(+), 128 deletions(-) diff --git a/tests/SpecTests/TransactionsSpecTest.php b/tests/SpecTests/TransactionsSpecTest.php index f6359d529..0b2b67595 100644 --- a/tests/SpecTests/TransactionsSpecTest.php +++ b/tests/SpecTests/TransactionsSpecTest.php @@ -36,8 +36,6 @@ class TransactionsSpecTest extends FunctionalTestCase 'transactions/mongos-recovery-token: commitTransaction retry fails on new mongos' => 'isMaster failpoints cannot be disabled', 'transactions/pin-mongos: remain pinned after non-transient error on commit' => 'Blocked on SPEC-1320', 'transactions/pin-mongos: unpin after transient error within a transaction and commit' => 'isMaster failpoints cannot be disabled', - 'transactions/errors-client: Client side error in command starting transaction' => 'PHPLIB-665', - 'transactions/errors-client: Client side error when transaction is in progress' => 'PHPLIB-665', ]; public function setUp() : void diff --git a/tests/SpecTests/transactions/errors-client.json b/tests/SpecTests/transactions/errors-client.json index 4bd1c0eb3..15fae96fe 100644 --- a/tests/SpecTests/transactions/errors-client.json +++ b/tests/SpecTests/transactions/errors-client.json @@ -25,14 +25,15 @@ "object": "session0" }, { - "name": "insertOne", + "name": "updateOne", "object": "collection", "arguments": { "session": "session0", - "document": { - "_id": { - ".": "." - } + "filter": { + "_id": 1 + }, + "update": { + "x": 1 } }, "error": true @@ -60,22 +61,23 @@ "arguments": { "session": "session0", "document": { - "_id": 4 + "_id": 1 } }, "result": { - "insertedId": 4 + "insertedId": 1 } }, { - "name": "insertOne", + "name": "updateOne", "object": "collection", "arguments": { "session": "session0", - "document": { - "_id": { - ".": "." - } + "filter": { + "_id": 1 + }, + "update": { + "x": 1 } }, "error": true diff --git a/tests/UnifiedSpecTests/UnifiedSpecTest.php b/tests/UnifiedSpecTests/UnifiedSpecTest.php index 47f00cf32..c53ae256a 100644 --- a/tests/UnifiedSpecTests/UnifiedSpecTest.php +++ b/tests/UnifiedSpecTests/UnifiedSpecTest.php @@ -42,67 +42,6 @@ class UnifiedSpecTest extends FunctionalTestCase 'crud/unacknowledged-updateMany-hint-clientError: Unacknowledged updateMany with hint document fails with client-side error' => 'PHPLIB-573 and DRIVERS-1340', 'crud/unacknowledged-updateOne-hint-clientError: Unacknowledged updateOne with hint string fails with client-side error' => 'PHPLIB-573 and DRIVERS-1340', 'crud/unacknowledged-updateOne-hint-clientError: Unacknowledged updateOne with hint document fails with client-side error' => 'PHPLIB-573 and DRIVERS-1340', - // CDRIVER-3895 and PHPC-1765 - 'crud/bulkWrite-insertOne-dots_and_dollars: Inserting document with top-level dollar-prefixed key on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/bulkWrite-insertOne-dots_and_dollars: Inserting document with top-level dollar-prefixed key on pre-5.0 server yields server-side error' => 'CDRIVER-3895 and PHPC-1765', - 'crud/bulkWrite-insertOne-dots_and_dollars: Inserting document with top-level dotted key' => 'CDRIVER-3895 and PHPC-1765', - 'crud/bulkWrite-insertOne-dots_and_dollars: Inserting document with dollar-prefixed key in embedded doc' => 'CDRIVER-3895 and PHPC-1765', - 'crud/bulkWrite-insertOne-dots_and_dollars: Inserting document with dotted key in embedded doc' => 'CDRIVER-3895 and PHPC-1765', - 'crud/bulkWrite-replaceOne-dots_and_dollars: Replacing document with top-level dotted key on 3.6+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/bulkWrite-replaceOne-dots_and_dollars: Replacing document with top-level dotted key on pre-3.6 server yields server-side error' => 'CDRIVER-3895 and PHPC-1765', - 'crud/bulkWrite-replaceOne-dots_and_dollars: Replacing document with dollar-prefixed key in embedded doc on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/bulkWrite-replaceOne-dots_and_dollars: Replacing document with dollar-prefixed key in embedded doc on pre-5.0 server yields server-side error' => 'CDRIVER-3895 and PHPC-1765', - 'crud/bulkWrite-replaceOne-dots_and_dollars: Replacing document with dotted key in embedded doc on 3.6+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/bulkWrite-replaceOne-dots_and_dollars: Replacing document with dotted key in embedded doc on pre-3.6 server yields server-side error' => 'CDRIVER-3895 and PHPC-1765', - 'crud/bulkWrite-updateMany-dots_and_dollars: Updating document to set top-level dollar-prefixed key on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/bulkWrite-updateMany-dots_and_dollars: Updating document to set top-level dotted key on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/bulkWrite-updateMany-dots_and_dollars: Updating document to set dollar-prefixed key in embedded doc on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/bulkWrite-updateMany-dots_and_dollars: Updating document to set dotted key in embedded doc on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/bulkWrite-updateOne-dots_and_dollars: Updating document to set top-level dollar-prefixed key on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/bulkWrite-updateOne-dots_and_dollars: Updating document to set top-level dotted key on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/bulkWrite-updateOne-dots_and_dollars: Updating document to set dollar-prefixed key in embedded doc on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/bulkWrite-updateOne-dots_and_dollars: Updating document to set dotted key in embedded doc on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/findOneAndReplace-dots_and_dollars: Replacing document with top-level dotted key on 3.6+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/findOneAndReplace-dots_and_dollars: Replacing document with top-level dotted key on pre-3.6 server yields server-side error' => 'CDRIVER-3895 and PHPC-1765', - 'crud/findOneAndReplace-dots_and_dollars: Replacing document with dollar-prefixed key in embedded doc on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/findOneAndReplace-dots_and_dollars: Replacing document with dollar-prefixed key in embedded doc on pre-5.0 server yields server-side error' => 'CDRIVER-3895 and PHPC-1765', - 'crud/findOneAndReplace-dots_and_dollars: Replacing document with dotted key in embedded doc on 3.6+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/findOneAndReplace-dots_and_dollars: Replacing document with dotted key in embedded doc on pre-3.6 server yields server-side error' => 'CDRIVER-3895 and PHPC-1765', - 'crud/findOneAndUpdate-dots_and_dollars: Updating document to set top-level dollar-prefixed key on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/findOneAndUpdate-dots_and_dollars: Updating document to set top-level dotted key on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/findOneAndUpdate-dots_and_dollars: Updating document to set dollar-prefixed key in embedded doc on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/findOneAndUpdate-dots_and_dollars: Updating document to set dotted key in embedded doc on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/insertMany-dots_and_dollars: Inserting document with top-level dollar-prefixed key on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/insertMany-dots_and_dollars: Inserting document with top-level dollar-prefixed key on pre-5.0 server yields server-side error' => 'CDRIVER-3895 and PHPC-1765', - 'crud/insertMany-dots_and_dollars: Inserting document with top-level dotted key' => 'CDRIVER-3895 and PHPC-1765', - 'crud/insertMany-dots_and_dollars: Inserting document with dollar-prefixed key in embedded doc' => 'CDRIVER-3895 and PHPC-1765', - 'crud/insertMany-dots_and_dollars: Inserting document with dotted key in embedded doc' => 'CDRIVER-3895 and PHPC-1765', - 'crud/insertOne-dots_and_dollars: Inserting document with top-level dollar-prefixed key on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/insertOne-dots_and_dollars: Inserting document with top-level dollar-prefixed key on pre-5.0 server yields server-side error' => 'CDRIVER-3895 and PHPC-1765', - 'crud/insertOne-dots_and_dollars: Inserting document with top-level dotted key' => 'CDRIVER-3895 and PHPC-1765', - 'crud/insertOne-dots_and_dollars: Inserting document with dollar-prefixed key in embedded doc' => 'CDRIVER-3895 and PHPC-1765', - 'crud/insertOne-dots_and_dollars: Inserting document with dotted key in embedded doc' => 'CDRIVER-3895 and PHPC-1765', - 'crud/insertOne-dots_and_dollars: Inserting document with dollar-prefixed key in _id yields server-side error' => 'CDRIVER-3895 and PHPC-1765', - 'crud/insertOne-dots_and_dollars: Inserting document with dotted key in _id on 3.6+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/insertOne-dots_and_dollars: Inserting document with dotted key in _id on pre-3.6 server yields server-side error' => 'CDRIVER-3895 and PHPC-1765', - 'crud/insertOne-dots_and_dollars: Inserting document with DBRef-like keys' => 'CDRIVER-3895 and PHPC-1765', - 'crud/insertOne-dots_and_dollars: Unacknowledged write using dollar-prefixed or dotted keys may be silently rejected on pre-5.0 server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/replaceOne-dots_and_dollars: Replacing document with top-level dotted key on 3.6+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/replaceOne-dots_and_dollars: Replacing document with top-level dotted key on pre-3.6 server yields server-side error' => 'CDRIVER-3895 and PHPC-1765', - 'crud/replaceOne-dots_and_dollars: Replacing document with dollar-prefixed key in embedded doc on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/replaceOne-dots_and_dollars: Replacing document with dollar-prefixed key in embedded doc on pre-5.0 server yields server-side error' => 'CDRIVER-3895 and PHPC-1765', - 'crud/replaceOne-dots_and_dollars: Replacing document with dotted key in embedded doc on 3.6+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/replaceOne-dots_and_dollars: Replacing document with dotted key in embedded doc on pre-3.6 server yields server-side error' => 'CDRIVER-3895 and PHPC-1765', - 'crud/replaceOne-dots_and_dollars: Unacknowledged write using dollar-prefixed or dotted keys may be silently rejected on pre-5.0 server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/updateMany-dots_and_dollars: Updating document to set top-level dollar-prefixed key on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/updateMany-dots_and_dollars: Updating document to set top-level dotted key on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/updateMany-dots_and_dollars: Updating document to set dollar-prefixed key in embedded doc on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/updateMany-dots_and_dollars: Updating document to set dotted key in embedded doc on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/updateOne-dots_and_dollars: Updating document to set top-level dollar-prefixed key on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/updateOne-dots_and_dollars: Updating document to set top-level dotted key on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/updateOne-dots_and_dollars: Updating document to set dollar-prefixed key in embedded doc on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'crud/updateOne-dots_and_dollars: Updating document to set dotted key in embedded doc on 5.0+ server' => 'CDRIVER-3895 and PHPC-1765', - 'valid-pass/poc-transactions: Client side error in command starting transaction' => 'PHPLIB-665', ]; /** @var UnifiedTestRunner */ diff --git a/tests/UnifiedSpecTests/crud/insertMany-dots_and_dollars.json b/tests/UnifiedSpecTests/crud/insertMany-dots_and_dollars.json index 3b66ac062..eed8997df 100644 --- a/tests/UnifiedSpecTests/crud/insertMany-dots_and_dollars.json +++ b/tests/UnifiedSpecTests/crud/insertMany-dots_and_dollars.json @@ -53,10 +53,11 @@ ] }, "expectResult": { - "insertedCount": 1, - "insertedIds": { - "$$unsetOrMatches": { - "0": 1 + "$$unsetOrMatches": { + "insertedIds": { + "$$unsetOrMatches": { + "0": 1 + } } } } @@ -162,10 +163,11 @@ ] }, "expectResult": { - "insertedCount": 1, - "insertedIds": { - "$$unsetOrMatches": { - "0": 1 + "$$unsetOrMatches": { + "insertedIds": { + "$$unsetOrMatches": { + "0": 1 + } } } } @@ -221,10 +223,11 @@ ] }, "expectResult": { - "insertedCount": 1, - "insertedIds": { - "$$unsetOrMatches": { - "0": 1 + "$$unsetOrMatches": { + "insertedIds": { + "$$unsetOrMatches": { + "0": 1 + } } } } @@ -284,10 +287,11 @@ ] }, "expectResult": { - "insertedCount": 1, - "insertedIds": { - "$$unsetOrMatches": { - "0": 1 + "$$unsetOrMatches": { + "insertedIds": { + "$$unsetOrMatches": { + "0": 1 + } } } } diff --git a/tests/UnifiedSpecTests/crud/insertOne-dots_and_dollars.json b/tests/UnifiedSpecTests/crud/insertOne-dots_and_dollars.json index 1a30df4a0..fdc17af2e 100644 --- a/tests/UnifiedSpecTests/crud/insertOne-dots_and_dollars.json +++ b/tests/UnifiedSpecTests/crud/insertOne-dots_and_dollars.json @@ -63,9 +63,10 @@ } }, "expectResult": { - "insertedCount": 1, - "insertedId": { - "$$unsetOrMatches": 1 + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } } } } @@ -166,9 +167,10 @@ } }, "expectResult": { - "insertedCount": 1, - "insertedId": { - "$$unsetOrMatches": 1 + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } } } } @@ -221,9 +223,10 @@ } }, "expectResult": { - "insertedCount": 1, - "insertedId": { - "$$unsetOrMatches": 1 + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } } } } @@ -280,9 +283,10 @@ } }, "expectResult": { - "insertedCount": 1, - "insertedId": { - "$$unsetOrMatches": 1 + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } } } } @@ -390,10 +394,11 @@ } }, "expectResult": { - "insertedCount": 1, - "insertedId": { - "$$unsetOrMatches": { - "a.b": 1 + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": { + "a.b": 1 + } } } } @@ -501,9 +506,10 @@ } }, "expectResult": { - "insertedCount": 1, - "insertedId": { - "$$unsetOrMatches": 1 + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } } } } @@ -564,8 +570,10 @@ } }, "expectResult": { - "acknowledged": { - "$$unsetOrMatches": false + "$$unsetOrMatches": { + "acknowledged": { + "$$unsetOrMatches": false + } } } } diff --git a/tests/UnifiedSpecTests/valid-pass/poc-crud.json b/tests/UnifiedSpecTests/valid-pass/poc-crud.json index 2ed86d615..7bb072de8 100644 --- a/tests/UnifiedSpecTests/valid-pass/poc-crud.json +++ b/tests/UnifiedSpecTests/valid-pass/poc-crud.json @@ -1,6 +1,6 @@ { "description": "poc-crud", - "schemaVersion": "1.0", + "schemaVersion": "1.4", "createEntities": [ { "client": { @@ -242,12 +242,14 @@ }, "expectError": { "expectResult": { - "deletedCount": 0, - "insertedCount": 2, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} + "$$unsetOrMatches": { + "deletedCount": 0, + "insertedCount": 2, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + } } } } @@ -406,7 +408,8 @@ "description": "Aggregate with $listLocalSessions", "runOnRequirements": [ { - "minServerVersion": "3.6.0" + "minServerVersion": "3.6.0", + "serverless": "forbid" } ], "operations": [ diff --git a/tests/UnifiedSpecTests/valid-pass/poc-retryable-writes.json b/tests/UnifiedSpecTests/valid-pass/poc-retryable-writes.json index 30c1d5415..50160799f 100644 --- a/tests/UnifiedSpecTests/valid-pass/poc-retryable-writes.json +++ b/tests/UnifiedSpecTests/valid-pass/poc-retryable-writes.json @@ -298,9 +298,6 @@ }, "expectResult": { "$$unsetOrMatches": { - "insertedCount": { - "$$unsetOrMatches": 2 - }, "insertedIds": { "$$unsetOrMatches": { "0": 3, diff --git a/tests/UnifiedSpecTests/valid-pass/poc-transactions.json b/tests/UnifiedSpecTests/valid-pass/poc-transactions.json index 62528f9ce..0355ca206 100644 --- a/tests/UnifiedSpecTests/valid-pass/poc-transactions.json +++ b/tests/UnifiedSpecTests/valid-pass/poc-transactions.json @@ -61,14 +61,15 @@ "object": "session0" }, { - "name": "insertOne", + "name": "updateOne", "object": "collection0", "arguments": { "session": "session0", - "document": { - "_id": { - ".": "." - } + "filter": { + "_id": 1 + }, + "update": { + "x": 1 } }, "expectError": { From a3dc713b71953cd599b8f441ff9f88c46a8679be Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Wed, 26 May 2021 12:50:30 +0200 Subject: [PATCH 04/11] PHPLIB-660 Add 5.0 to the test matrix --- .evergreen/config.yml | 6 +++++- .github/workflows/tests.yml | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 0cbdb4d34..7cfd008f0 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -420,6 +420,10 @@ axes: display_name: "latest" variables: VERSION: "latest" + - id: "5.0" + display_name: "5.0" + variables: + VERSION: "5.0" - id: "4.4" display_name: "4.4" variables: @@ -626,7 +630,7 @@ buildvariants: - name: "test-atlas-data-lake" - matrix_name: "test-versioned-api" - matrix_spec: { "php-edge-versions": "latest-stable", "versions": "latest", "driver-versions": "latest" } + matrix_spec: { "php-edge-versions": "latest-stable", "versions": ["5.0", "latest"], "driver-versions": "latest" } display_name: "Versioned API - ${versions}" run_on: rhel70 tasks: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5e1a46d2a..dbde407ee 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,6 +30,11 @@ jobs: topology: - "server" include: + - os: "ubuntu-20.04" + php-version: "8.0" + mongodb-version: "5.0" + driver-version: "mongodb/mongo-php-driver@master" + topology: "server" - os: "ubuntu-20.04" php-version: "8.0" mongodb-version: "4.4" From 03a090fe09bc73a7aaa8bc75df8952f834549671 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Mon, 5 Jul 2021 14:45:20 -0400 Subject: [PATCH 05/11] Explicit result iteration and require filter/pipeline args Remove automatic result iteration in Operation::execute(), since ChangeStreams will no longer be the lone exception once createFindCursor is implemented. Additionally, filter/pipeline args need not default since they are required by the CRUD and change stream specs and present in all valid spec tests. The previous behavior would conflict with a forthcoming "createFindCursor fails if filter is not specified" test. --- tests/UnifiedSpecTests/Operation.php | 39 ++++++++++++++-------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/tests/UnifiedSpecTests/Operation.php b/tests/UnifiedSpecTests/Operation.php index 0f93d094b..7b015eadb 100644 --- a/tests/UnifiedSpecTests/Operation.php +++ b/tests/UnifiedSpecTests/Operation.php @@ -18,7 +18,6 @@ use PHPUnit\Framework\AssertionFailedError; use stdClass; use Throwable; -use Traversable; use function array_diff_key; use function array_key_exists; use function array_map; @@ -204,10 +203,6 @@ private function execute() Assert::fail('Unsupported entity type: ' . get_class($object)); } - if ($result instanceof Traversable && ! $result instanceof ChangeStream) { - return iterator_to_array($result); - } - return $result; } @@ -249,16 +244,16 @@ private function executeForClient(Client $client) switch ($this->name) { case 'createChangeStream': $changeStream = $client->watch( - $args['pipeline'] ?? [], + $args['pipeline'], array_diff_key($args, ['pipeline' => 1]) ); $changeStream->rewind(); return $changeStream; case 'listDatabaseNames': - return $client->listDatabaseNames($args); + return iterator_to_array($client->listDatabaseNames($args)); case 'listDatabases': - return $client->listDatabases($args); + return iterator_to_array($client->listDatabases($args)); default: Assert::fail('Unsupported client operation: ' . $this->name); } @@ -270,10 +265,10 @@ private function executeForCollection(Collection $collection) switch ($this->name) { case 'aggregate': - return $collection->aggregate( + return iterator_to_array($collection->aggregate( $args['pipeline'], array_diff_key($args, ['pipeline' => 1]) - ); + )); case 'bulkWrite': return $collection->bulkWrite( array_map('self::prepareBulkWriteRequest', $args['requests']), @@ -281,7 +276,7 @@ private function executeForCollection(Collection $collection) ); case 'createChangeStream': $changeStream = $collection->watch( - $args['pipeline'] ?? [], + $args['pipeline'], array_diff_key($args, ['pipeline' => 1]) ); $changeStream->rewind(); @@ -299,9 +294,8 @@ private function executeForCollection(Collection $collection) ); case 'count': case 'countDocuments': - case 'find': return $collection->{$this->name}( - $args['filter'] ?? [], + $args['filter'], array_diff_key($args, ['filter' => 1]) ); case 'estimatedDocumentCount': @@ -321,11 +315,16 @@ private function executeForCollection(Collection $collection) return $collection->distinct( $args['fieldName'], - $args['filter'] ?? [], + $args['filter'], array_diff_key($args, ['fieldName' => 1, 'filter' => 1]) ); case 'drop': return $collection->drop($args); + case 'find': + return iterator_to_array($collection->find( + $args['filter'], + array_diff_key($args, ['filter' => 1]) + )); case 'findOne': return $collection->findOne($args['filter'], array_diff_key($args, ['filter' => 1])); case 'findOneAndReplace': @@ -378,7 +377,7 @@ private function executeForCollection(Collection $collection) array_diff_key($args, ['document' => 1]) ); case 'listIndexes': - return $collection->listIndexes($args); + return iterator_to_array($collection->listIndexes($args)); case 'mapReduce': return $collection->mapReduce( $args['map'], @@ -397,13 +396,13 @@ private function executeForDatabase(Database $database) switch ($this->name) { case 'aggregate': - return $database->aggregate( + return iterator_to_array($database->aggregate( $args['pipeline'], array_diff_key($args, ['pipeline' => 1]) - ); + )); case 'createChangeStream': $changeStream = $database->watch( - $args['pipeline'] ?? [], + $args['pipeline'], array_diff_key($args, ['pipeline' => 1]) ); $changeStream->rewind(); @@ -420,9 +419,9 @@ private function executeForDatabase(Database $database) array_diff_key($args, ['collection' => 1]) ); case 'listCollectionNames': - return $database->listCollectionNames($args); + return iterator_to_array($database->listCollectionNames($args)); case 'listCollections': - return $database->listCollections($args); + return iterator_to_array($database->listCollections($args)); case 'runCommand': return $database->command( $args['command'], From e6df79f25fcb62492f679de538644ae7f7a91a70 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Mon, 5 Jul 2021 15:02:25 -0400 Subject: [PATCH 06/11] Remove extra call to rewind() in createChangeStream A newly created ChangeStream will have a null key, so we can rely on iterateUntilDocumentOrError to initially rewind the ChangeStream. --- tests/UnifiedSpecTests/Operation.php | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/tests/UnifiedSpecTests/Operation.php b/tests/UnifiedSpecTests/Operation.php index 7b015eadb..7f403d680 100644 --- a/tests/UnifiedSpecTests/Operation.php +++ b/tests/UnifiedSpecTests/Operation.php @@ -243,13 +243,10 @@ private function executeForClient(Client $client) switch ($this->name) { case 'createChangeStream': - $changeStream = $client->watch( + return $client->watch( $args['pipeline'], array_diff_key($args, ['pipeline' => 1]) ); - $changeStream->rewind(); - - return $changeStream; case 'listDatabaseNames': return iterator_to_array($client->listDatabaseNames($args)); case 'listDatabases': @@ -275,13 +272,10 @@ private function executeForCollection(Collection $collection) array_diff_key($args, ['requests' => 1]) ); case 'createChangeStream': - $changeStream = $collection->watch( + return $collection->watch( $args['pipeline'], array_diff_key($args, ['pipeline' => 1]) ); - $changeStream->rewind(); - - return $changeStream; case 'createIndex': return $collection->createIndex( $args['keys'], @@ -401,13 +395,10 @@ private function executeForDatabase(Database $database) array_diff_key($args, ['pipeline' => 1]) )); case 'createChangeStream': - $changeStream = $database->watch( + return $database->watch( $args['pipeline'], array_diff_key($args, ['pipeline' => 1]) ); - $changeStream->rewind(); - - return $changeStream; case 'createCollection': return $database->createCollection( $args['collection'], From 8879a2f286badd697b649a33251b3a31f4eaf8b6 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Mon, 5 Jul 2021 14:53:47 -0400 Subject: [PATCH 07/11] PHPLIB-669: Unified test runner changes for load balancer support Sync valid-pass and valid-fail tests with mongodb/specifications@ed4e62b48939378b814cbd7d9d4939a2c1076a6b Moves caching of server environment to checkRunOnRequirements Detection of load balancers will be addressed after driver support is implemented (PHPLIB-671) --- tests/UnifiedSpecTests/Context.php | 7 +- tests/UnifiedSpecTests/EntityMap.php | 14 ++ tests/UnifiedSpecTests/EventObserver.php | 3 + tests/UnifiedSpecTests/Operation.php | 77 +++++++- tests/UnifiedSpecTests/RunOnRequirement.php | 29 ++- tests/UnifiedSpecTests/UnifiedSpecTest.php | 5 + tests/UnifiedSpecTests/UnifiedTestRunner.php | 136 ++++++------- .../assertNumberConnectionsCheckedOut.json | 63 ++++++ .../valid-fail/entity-find-cursor.json | 62 ++++++ .../valid-fail/ignoreResultAndError.json | 72 +++++++ .../assertNumberConnectionsCheckedOut.json | 27 +++ .../valid-pass/entity-client-cmap-events.json | 71 +++++++ .../valid-pass/entity-find-cursor.json | 182 ++++++++++++++++++ .../expectedEventsForClient-eventType.json | 126 ++++++++++++ .../valid-pass/ignoreResultAndError.json | 59 ++++++ 15 files changed, 856 insertions(+), 77 deletions(-) create mode 100644 tests/UnifiedSpecTests/valid-fail/assertNumberConnectionsCheckedOut.json create mode 100644 tests/UnifiedSpecTests/valid-fail/entity-find-cursor.json create mode 100644 tests/UnifiedSpecTests/valid-fail/ignoreResultAndError.json create mode 100644 tests/UnifiedSpecTests/valid-pass/assertNumberConnectionsCheckedOut.json create mode 100644 tests/UnifiedSpecTests/valid-pass/entity-client-cmap-events.json create mode 100644 tests/UnifiedSpecTests/valid-pass/entity-find-cursor.json create mode 100644 tests/UnifiedSpecTests/valid-pass/expectedEventsForClient-eventType.json create mode 100644 tests/UnifiedSpecTests/valid-pass/ignoreResultAndError.json diff --git a/tests/UnifiedSpecTests/Context.php b/tests/UnifiedSpecTests/Context.php index cf2c27f20..82712e559 100644 --- a/tests/UnifiedSpecTests/Context.php +++ b/tests/UnifiedSpecTests/Context.php @@ -29,6 +29,7 @@ use function PHPUnit\Framework\assertNotEmpty; use function PHPUnit\Framework\assertNotFalse; use function PHPUnit\Framework\assertNotSame; +use function PHPUnit\Framework\assertSame; use function PHPUnit\Framework\assertStringContainsString; use function PHPUnit\Framework\assertStringStartsWith; use function strlen; @@ -171,13 +172,17 @@ public function assertExpectedEventsForClients(array $expectedEventsForClients) foreach ($expectedEventsForClients as $expectedEventsForClient) { assertIsObject($expectedEventsForClient); - Util::assertHasOnlyKeys($expectedEventsForClient, ['client', 'events']); + Util::assertHasOnlyKeys($expectedEventsForClient, ['client', 'events', 'eventType']); $client = $expectedEventsForClient->client ?? null; + $eventType = $expectedEventsForClient->eventType ?? 'command'; $expectedEvents = $expectedEventsForClient->events ?? null; assertIsString($client); assertArrayHasKey($client, $this->eventObserversByClient); + /* Note: PHPC does not implement CMAP. Any tests expecting CMAP + * events should be skipped explicitly. */ + assertSame('command', $eventType); assertIsArray($expectedEvents); $this->eventObserversByClient[$client]->assert($expectedEvents); diff --git a/tests/UnifiedSpecTests/EntityMap.php b/tests/UnifiedSpecTests/EntityMap.php index 5b983b6b1..8184cbc7f 100644 --- a/tests/UnifiedSpecTests/EntityMap.php +++ b/tests/UnifiedSpecTests/EntityMap.php @@ -7,6 +7,7 @@ use MongoDB\Client; use MongoDB\Collection; use MongoDB\Database; +use MongoDB\Driver\Cursor; use MongoDB\Driver\Session; use MongoDB\GridFS\Bucket; use MongoDB\Tests\UnifiedSpecTests\Constraint\IsBsonType; @@ -16,6 +17,7 @@ use function array_key_exists; use function PHPUnit\Framework\assertArrayHasKey; use function PHPUnit\Framework\assertArrayNotHasKey; +use function PHPUnit\Framework\assertInstanceOf; use function PHPUnit\Framework\assertIsString; use function PHPUnit\Framework\assertThat; use function PHPUnit\Framework\isInstanceOf; @@ -128,6 +130,17 @@ public function getRoot() : self }; } + /** + * Closes a cursor by removing it from the entity map. + * + * @see Operation::executeForCursor() + */ + public function closeCursor(string $cursorId) + { + assertInstanceOf(Cursor::class, $this[$cursorId]); + unset($this->map[$cursorId]); + } + public function getClient(string $clientId) : Client { return $this[$clientId]; @@ -170,6 +183,7 @@ private static function isSupportedType() : Constraint isInstanceOf(Session::class), isInstanceOf(Bucket::class), isInstanceOf(ChangeStream::class), + isInstanceOf(Cursor::class), IsBsonType::any() ); } diff --git a/tests/UnifiedSpecTests/EventObserver.php b/tests/UnifiedSpecTests/EventObserver.php index 19c86c19a..bd7e54d3f 100644 --- a/tests/UnifiedSpecTests/EventObserver.php +++ b/tests/UnifiedSpecTests/EventObserver.php @@ -199,6 +199,7 @@ private function assertEvent($actual, stdClass $expected, string $message) private function assertCommandStartedEvent(CommandStartedEvent $actual, stdClass $expected, string $message) { + // TODO: Assert hasServiceId (blocked on PHPC-1752) Util::assertHasOnlyKeys($expected, ['command', 'commandName', 'databaseName']); if (isset($expected->command)) { @@ -220,6 +221,7 @@ private function assertCommandStartedEvent(CommandStartedEvent $actual, stdClass private function assertCommandSucceededEvent(CommandSucceededEvent $actual, stdClass $expected, string $message) { + // TODO: Assert hasServiceId (blocked on PHPC-1752) Util::assertHasOnlyKeys($expected, ['reply', 'commandName']); if (isset($expected->reply)) { @@ -236,6 +238,7 @@ private function assertCommandSucceededEvent(CommandSucceededEvent $actual, stdC private function assertCommandFailedEvent(CommandFailedEvent $actual, stdClass $expected, string $message) { + // TODO: Assert hasServiceId (blocked on PHPC-1752) Util::assertHasOnlyKeys($expected, ['commandName']); if (isset($expected->commandName)) { diff --git a/tests/UnifiedSpecTests/Operation.php b/tests/UnifiedSpecTests/Operation.php index 7f403d680..026574e19 100644 --- a/tests/UnifiedSpecTests/Operation.php +++ b/tests/UnifiedSpecTests/Operation.php @@ -7,6 +7,7 @@ use MongoDB\Client; use MongoDB\Collection; use MongoDB\Database; +use MongoDB\Driver\Cursor; use MongoDB\Driver\Server; use MongoDB\Driver\Session; use MongoDB\GridFS\Bucket; @@ -35,6 +36,7 @@ use function PHPUnit\Framework\assertFalse; use function PHPUnit\Framework\assertInstanceOf; use function PHPUnit\Framework\assertIsArray; +use function PHPUnit\Framework\assertIsInt; use function PHPUnit\Framework\assertIsObject; use function PHPUnit\Framework\assertIsString; use function PHPUnit\Framework\assertMatchesRegularExpression; @@ -81,6 +83,9 @@ final class Operation /** @var ExpectedResult */ private $expectedResult; + /** @var bool */ + private $ignoreResultAndError; + /** @var string */ private $saveResultAsEntity; @@ -101,10 +106,15 @@ public function __construct(stdClass $o, Context $context) $this->arguments = (array) $o->arguments; } + if (isset($o->ignoreResultAndError) && (isset($o->expectError) || property_exists($o, 'expectResult') || isset($o->saveResultAsEntity))) { + Assert::fail('ignoreResultAndError is mutually exclusive with expectError, expectResult, and saveResultAsEntity'); + } + if (isset($o->expectError) && (property_exists($o, 'expectResult') || isset($o->saveResultAsEntity))) { Assert::fail('expectError is mutually exclusive with expectResult and saveResultAsEntity'); } + $this->ignoreResultAndError = $o->ignoreResultAndError ?? false; $this->expectError = new ExpectedError($o->expectError ?? null, $this->entityMap); $this->expectResult = new ExpectedResult($o, $this->entityMap, $this->object); @@ -158,8 +168,10 @@ public function assert(bool $rethrowExceptions = false) } } - $this->expectError->assert($error); - $this->expectResult->assert($result, $saveResultAsEntity); + if (! $this->ignoreResultAndError) { + $this->expectError->assert($error); + $this->expectResult->assert($result, $saveResultAsEntity); + } // Rethrowing is primarily used for withTransaction callbacks if ($error && $rethrowExceptions) { @@ -193,6 +205,9 @@ private function execute() case ChangeStream::class: $result = $this->executeForChangeStream($object); break; + case Cursor::class: + $result = $this->executeForCursor($object); + break; case Session::class: $result = $this->executeForSession($object); break; @@ -276,6 +291,11 @@ private function executeForCollection(Collection $collection) $args['pipeline'], array_diff_key($args, ['pipeline' => 1]) ); + case 'createFindCursor': + return $collection->find( + $args['filter'], + array_diff_key($args, ['filter' => 1]) + ); case 'createIndex': return $collection->createIndex( $args['keys'], @@ -384,6 +404,52 @@ private function executeForCollection(Collection $collection) } } + private function executeForCursor(Cursor $cursor) + { + $args = $this->prepareArguments(); + + switch ($this->name) { + case 'close': + /* PHPC does not provide an API to directly close a cursor. + * mongoc_cursor_destroy is only invoked from the Cursor's + * free_object handler, which requires unsetting the object from + * the entity map to trigger garbage collection. This will need + * a different approach if tests ever attempt to access the + * cursor entity after calling the "close" operation. */ + $this->entityMap->closeCursor($this->object); + assertFalse($this->entityMap->offsetExists($this->object)); + break; + case 'iterateUntilDocumentOrError': + /* Note: the first iteration should use rewind, otherwise we may + * miss a document from the initial batch (possible if using a + * resume token). We can infer this from a null key; however, + * if a test ever calls this operation consecutively to expect + * multiple errors from the same ChangeStream we will need a + * different approach (e.g. examining internal hasAdvanced + * property on the ChangeStream). */ + + /* Note: similar to iterateUntilDocumentOrError for ChangeStream + * entities, a different approach will be needed if a test ever + * calls this operation consecutively to expect multiple errors. + */ + if ($cursor->key() === null) { + $cursor->rewind(); + + if ($cursor->valid()) { + return $cursor->current(); + } + } + + do { + $cursor->next(); + } while (! $cursor->valid()); + + return $cursor->current(); + default: + Assert::fail('Unsupported cursor operation: ' . $this->name); + } + } + private function executeForDatabase(Database $database) { $args = $this->prepareArguments(); @@ -532,6 +598,13 @@ private function executeForTestRunner() $eventObserver = $this->context->getEventObserverForClient($this->arguments['client']); assertNotEquals(...$eventObserver->getLsidsOnLastTwoCommands()); break; + case 'assertNumberConnectionsCheckedOut': + assertIsInt($args['connections']); + /* PHP does not implement connection pooling. Check parameters + * for the sake of valid-fail tests, but otherwise raise an + * error. */ + Assert::fail('Tests using assertNumberConnectionsCheckedOut should be skipped'); + break; case 'assertSessionDirty': assertTrue($this->context->isDirtySession($this->arguments['session'])); break; diff --git a/tests/UnifiedSpecTests/RunOnRequirement.php b/tests/UnifiedSpecTests/RunOnRequirement.php index 0c372e03d..5fe6c0314 100644 --- a/tests/UnifiedSpecTests/RunOnRequirement.php +++ b/tests/UnifiedSpecTests/RunOnRequirement.php @@ -4,8 +4,10 @@ use MongoDB\Tests\UnifiedSpecTests\Constraint\Matches; use stdClass; +use function array_diff; use function in_array; use function PHPUnit\Framework\assertContainsOnly; +use function PHPUnit\Framework\assertEmpty; use function PHPUnit\Framework\assertIsArray; use function PHPUnit\Framework\assertIsObject; use function PHPUnit\Framework\assertIsString; @@ -18,6 +20,7 @@ class RunOnRequirement const TOPOLOGY_REPLICASET = 'replicaset'; const TOPOLOGY_SHARDED = 'sharded'; const TOPOLOGY_SHARDED_REPLICASET = 'sharded-replicaset'; + const TOPOLOGY_LOAD_BALANCED = 'load-balanced'; const VERSION_PATTERN = '/^[0-9]+(\\.[0-9]+){1,2}$/'; @@ -33,8 +36,22 @@ class RunOnRequirement /** @var stdClass */ private $serverParameters; + /** @var bool */ + private $auth; + + /** @var array */ + private static $supportedTopologies = [ + self::TOPOLOGY_SINGLE, + self::TOPOLOGY_REPLICASET, + self::TOPOLOGY_SHARDED, + self::TOPOLOGY_SHARDED_REPLICASET, + self::TOPOLOGY_LOAD_BALANCED, + ]; + public function __construct(stdClass $o) { + Util::assertHasOnlyKeys($o, ['minServerVersion', 'maxServerVersion', 'topologies', 'serverParameters', 'auth']); + if (isset($o->minServerVersion)) { assertIsString($o->minServerVersion); assertMatchesRegularExpression(self::VERSION_PATTERN, $o->minServerVersion); @@ -50,6 +67,7 @@ public function __construct(stdClass $o) if (isset($o->topologies)) { assertIsArray($o->topologies); assertContainsOnly('string', $o->topologies); + assertEmpty(array_diff($o->topologies, self::$supportedTopologies)); $this->topologies = $o->topologies; } @@ -57,9 +75,14 @@ public function __construct(stdClass $o) assertIsObject($o->serverParameters); $this->serverParameters = $o->serverParameters; } + + if (isset($o->auth)) { + assertIsBool($o->auth); + $this->auth = $o->auth; + } } - public function isSatisfied(string $serverVersion, string $topology, stdClass $serverParameters) : bool + public function isSatisfied(string $serverVersion, string $topology, stdClass $serverParameters, bool $isAuthenticated) : bool { if (isset($this->minServerVersion) && version_compare($serverVersion, $this->minServerVersion, '<')) { return false; @@ -90,6 +113,10 @@ public function isSatisfied(string $serverVersion, string $topology, stdClass $s } } + if (isset($this->auth) && $isAuthenticated !== $this->auth) { + return false; + } + return true; } } diff --git a/tests/UnifiedSpecTests/UnifiedSpecTest.php b/tests/UnifiedSpecTests/UnifiedSpecTest.php index c53ae256a..b18684f1b 100644 --- a/tests/UnifiedSpecTests/UnifiedSpecTest.php +++ b/tests/UnifiedSpecTests/UnifiedSpecTest.php @@ -42,6 +42,11 @@ class UnifiedSpecTest extends FunctionalTestCase 'crud/unacknowledged-updateMany-hint-clientError: Unacknowledged updateMany with hint document fails with client-side error' => 'PHPLIB-573 and DRIVERS-1340', 'crud/unacknowledged-updateOne-hint-clientError: Unacknowledged updateOne with hint string fails with client-side error' => 'PHPLIB-573 and DRIVERS-1340', 'crud/unacknowledged-updateOne-hint-clientError: Unacknowledged updateOne with hint document fails with client-side error' => 'PHPLIB-573 and DRIVERS-1340', + // PHPC does not implement CMAP + 'valid-pass/assertNumberConnectionsCheckedOut: basic assertion succeeds' => 'PHPC does not implement CMAP', + 'valid-pass/entity-client-cmap-events: events are captured during an operation' => 'PHPC does not implement CMAP', + 'valid-pass/expectedEventsForClient-eventType: eventType can be set to command and cmap' => 'PHPC does not implement CMAP', + 'valid-pass/expectedEventsForClient-eventType: eventType defaults to command if unset' => 'PHPC does not implement CMAP', ]; /** @var UnifiedTestRunner */ diff --git a/tests/UnifiedSpecTests/UnifiedTestRunner.php b/tests/UnifiedSpecTests/UnifiedTestRunner.php index a55697f60..ea5e0b2da 100644 --- a/tests/UnifiedSpecTests/UnifiedTestRunner.php +++ b/tests/UnifiedSpecTests/UnifiedTestRunner.php @@ -6,6 +6,7 @@ use MongoDB\Driver\Exception\ServerException; use MongoDB\Driver\ReadPreference; use MongoDB\Driver\Server; +use MongoDB\Model\BSONArray; use MongoDB\Operation\DatabaseCommand; use MongoDB\Tests\FunctionalTestCase; use PHPUnit\Framework\Assert; @@ -16,6 +17,7 @@ use Throwable; use UnexpectedValueException; use function call_user_func; +use function count; use function gc_collect_cycles; use function in_array; use function is_string; @@ -43,7 +45,7 @@ final class UnifiedTestRunner const SERVER_ERROR_UNAUTHORIZED = 13; const MIN_SCHEMA_VERSION = '1.0'; - const MAX_SCHEMA_VERSION = '1.2'; + const MAX_SCHEMA_VERSION = '1.3'; /** @var MongoDB\Client */ private $internalClient; @@ -213,93 +215,40 @@ private function doTestCase(stdClass $test, string $schemaVersion, array $runOnR /** * Checks server version and topology requirements. * + * Arguments for RunOnRequirement::isSatisfied() will be cached internally. + * * @throws SkippedTest unless one or more runOnRequirements are met */ private function checkRunOnRequirements(array $runOnRequirements) { + static $cachedIsSatisfiedArgs; + assertNotEmpty($runOnRequirements); assertContainsOnly('object', $runOnRequirements); - $serverVersion = $this->getCachedServerVersion(); - $topology = $this->getCachedTopology(); - $serverParameters = $this->getCachedServerParameters(); + if (! isset($cachedIsSatisfiedArgs)) { + $cachedIsSatisfiedArgs = [ + $this->getServerVersion(), + $this->getTopology(), + $this->getServerParameters(), + $this->isAuthenticated(), + ]; + } foreach ($runOnRequirements as $o) { $runOnRequirement = new RunOnRequirement($o); - if ($runOnRequirement->isSatisfied($serverVersion, $topology, $serverParameters)) { + if ($runOnRequirement->isSatisfied(...$cachedIsSatisfiedArgs)) { return; } } // @todo Add server parameter requirements? - Assert::markTestSkipped(sprintf('Server version "%s" and topology "%s" do not meet test requirements', $serverVersion, $topology)); - } - - /** - * Return the server parameters (cached for subsequent calls). - */ - private function getCachedServerParameters() : stdClass - { - static $cachedServerParameters; - - if (isset($cachedServerParameters)) { - return $cachedServerParameters; - } - - $cachedServerParameters = $this->getServerParameters(); - - return $cachedServerParameters; - } - - /** - * Return the server version (cached for subsequent calls). - */ - private function getCachedServerVersion() : string - { - static $cachedServerVersion; - - if (isset($cachedServerVersion)) { - return $cachedServerVersion; - } - - $cachedServerVersion = $this->getServerVersion(); - - return $cachedServerVersion; - } - - /** - * Return the topology type (cached for subsequent calls). - * - * @throws UnexpectedValueException if topology is neither single nor RS nor sharded - */ - private function getCachedTopology() : string - { - static $cachedTopology = null; - - if (isset($cachedTopology)) { - return $cachedTopology; - } - - switch ($this->getPrimaryServer()->getType()) { - case Server::TYPE_STANDALONE: - $cachedTopology = RunOnRequirement::TOPOLOGY_SINGLE; - break; - - case Server::TYPE_RS_PRIMARY: - $cachedTopology = RunOnRequirement::TOPOLOGY_REPLICASET; - break; - - case Server::TYPE_MONGOS: - $cachedTopology = $this->isShardedClusterUsingReplicasets() - ? RunOnRequirement::TOPOLOGY_SHARDED_REPLICASET - : RunOnRequirement::TOPOLOGY_SHARDED; - break; - - default: - throw new UnexpectedValueException('Toplogy is neither single nor RS nor sharded'); - } - - return $cachedTopology; + Assert::markTestSkipped(sprintf( + 'Server (version=%s, toplogy=%s, auth=%s) does not meet test requirements', + $cachedIsSatisfiedArgs[0], + $cachedIsSatisfiedArgs[1], + $cachedIsSatisfiedArgs[3] ? 'yes' : 'no' + )); } private function getPrimaryServer() : Server @@ -339,6 +288,47 @@ private function getServerVersion() : string throw new UnexpectedValueException('Could not determine server version'); } + /** + * Return the topology type. + * + * @throws UnexpectedValueException if topology is neither single nor RS nor sharded + */ + private function getTopology() : string + { + // TODO: detect load-balanced topologies once PHPLIB-671 is implemented + switch ($this->getPrimaryServer()->getType()) { + case Server::TYPE_STANDALONE: + return RunOnRequirement::TOPOLOGY_SINGLE; + case Server::TYPE_RS_PRIMARY: + return RunOnRequirement::TOPOLOGY_REPLICASET; + case Server::TYPE_MONGOS: + return $this->isShardedClusterUsingReplicasets() + ? RunOnRequirement::TOPOLOGY_SHARDED_REPLICASET + : RunOnRequirement::TOPOLOGY_SHARDED; + default: + throw new UnexpectedValueException('Toplogy is neither single nor RS nor sharded'); + } + } + + /** + * Return whether the connection is authenticated. + * + * Note: if the connectionStatus command is not portable for serverless, it + * may be necessary to rewrite this to instead inspect the connection string + * or consult an environment variable, as is done in libmongoc. + */ + private function isAuthenticated() : bool + { + $database = $this->internalClient->selectDatabase('admin'); + $connectionStatus = $database->command(['connectionStatus' => 1])->toArray()[0]; + + if (isset($connectionStatus->authInfo->authenticatedUsers) && $connectionStatus->authInfo->authenticatedUsers instanceof BSONArray) { + return count($connectionStatus->authInfo->authenticatedUsers) > 0; + } + + throw new UnexpectedValueException('Could not determine authentication status'); + } + /** * Checks is a test format schema version is supported. */ diff --git a/tests/UnifiedSpecTests/valid-fail/assertNumberConnectionsCheckedOut.json b/tests/UnifiedSpecTests/valid-fail/assertNumberConnectionsCheckedOut.json new file mode 100644 index 000000000..9799bb2f6 --- /dev/null +++ b/tests/UnifiedSpecTests/valid-fail/assertNumberConnectionsCheckedOut.json @@ -0,0 +1,63 @@ +{ + "description": "assertNumberConnectionsCheckedOut", + "schemaVersion": "1.3", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": true + } + } + ], + "tests": [ + { + "description": "operation fails if client field is not specified", + "operations": [ + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "connections": 1 + } + } + ] + }, + { + "description": "operation fails if connections field is not specified", + "operations": [ + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0" + } + } + ] + }, + { + "description": "operation fails if client entity does not exist", + "operations": [ + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client1" + } + } + ] + }, + { + "description": "operation fails if number of connections is incorrect", + "operations": [ + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 1 + } + } + ] + } + ] +} diff --git a/tests/UnifiedSpecTests/valid-fail/entity-find-cursor.json b/tests/UnifiedSpecTests/valid-fail/entity-find-cursor.json new file mode 100644 index 000000000..f4c5bcdf4 --- /dev/null +++ b/tests/UnifiedSpecTests/valid-fail/entity-find-cursor.json @@ -0,0 +1,62 @@ +{ + "description": "entity-find-cursor", + "schemaVersion": "1.3", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0Name" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "databaseName": "database0Name", + "collectionName": "coll0", + "documents": [] + } + ], + "tests": [ + { + "description": "createFindCursor fails if filter is not specified", + "operations": [ + { + "name": "createFindCursor", + "object": "collection0", + "saveResultAsEntity": "cursor0" + } + ] + }, + { + "description": "iterateUntilDocumentOrError fails if it references a nonexistent entity", + "operations": [ + { + "name": "iterateUntilDocumentOrError", + "object": "cursor0" + } + ] + }, + { + "description": "close fails if it references a nonexistent entity", + "operations": [ + { + "name": "close", + "object": "cursor0" + } + ] + } + ] +} diff --git a/tests/UnifiedSpecTests/valid-fail/ignoreResultAndError.json b/tests/UnifiedSpecTests/valid-fail/ignoreResultAndError.json new file mode 100644 index 000000000..4457040b4 --- /dev/null +++ b/tests/UnifiedSpecTests/valid-fail/ignoreResultAndError.json @@ -0,0 +1,72 @@ +{ + "description": "ignoreResultAndError", + "schemaVersion": "1.3", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": true + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0Name" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "database0Name", + "documents": [] + } + ], + "tests": [ + { + "description": "operation errors are not ignored if ignoreResultAndError is false", + "operations": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 1 + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 1 + } + }, + "ignoreResultAndError": false + } + ] + }, + { + "description": "malformed operation fails if ignoreResultAndError is true", + "operations": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "foo": "bar" + }, + "ignoreResultAndError": true + } + ] + } + ] +} diff --git a/tests/UnifiedSpecTests/valid-pass/assertNumberConnectionsCheckedOut.json b/tests/UnifiedSpecTests/valid-pass/assertNumberConnectionsCheckedOut.json new file mode 100644 index 000000000..a9fc063f3 --- /dev/null +++ b/tests/UnifiedSpecTests/valid-pass/assertNumberConnectionsCheckedOut.json @@ -0,0 +1,27 @@ +{ + "description": "assertNumberConnectionsCheckedOut", + "schemaVersion": "1.3", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": true + } + } + ], + "tests": [ + { + "description": "basic assertion succeeds", + "operations": [ + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + } + ] + } + ] +} diff --git a/tests/UnifiedSpecTests/valid-pass/entity-client-cmap-events.json b/tests/UnifiedSpecTests/valid-pass/entity-client-cmap-events.json new file mode 100644 index 000000000..3209033de --- /dev/null +++ b/tests/UnifiedSpecTests/valid-pass/entity-client-cmap-events.json @@ -0,0 +1,71 @@ +{ + "description": "entity-client-cmap-events", + "schemaVersion": "1.3", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": true, + "observeEvents": [ + "connectionReadyEvent", + "connectionCheckedOutEvent", + "connectionCheckedInEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0Name" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "database0Name", + "documents": [] + } + ], + "tests": [ + { + "description": "events are captured during an operation", + "operations": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + } + ] +} diff --git a/tests/UnifiedSpecTests/valid-pass/entity-find-cursor.json b/tests/UnifiedSpecTests/valid-pass/entity-find-cursor.json new file mode 100644 index 000000000..85b8f69d7 --- /dev/null +++ b/tests/UnifiedSpecTests/valid-pass/entity-find-cursor.json @@ -0,0 +1,182 @@ +{ + "description": "entity-find-cursor", + "schemaVersion": "1.3", + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0Name" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "databaseName": "database0Name", + "collectionName": "coll0", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + }, + { + "_id": 5 + } + ] + } + ], + "tests": [ + { + "description": "cursors can be created, iterated, and closed", + "operations": [ + { + "name": "createFindCursor", + "object": "collection0", + "arguments": { + "filter": {}, + "batchSize": 2 + }, + "saveResultAsEntity": "cursor0" + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor0", + "expectResult": { + "_id": 1 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor0", + "expectResult": { + "_id": 2 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor0", + "expectResult": { + "_id": 3 + } + }, + { + "name": "close", + "object": "cursor0" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll0", + "filter": {}, + "batchSize": 2 + }, + "commandName": "find", + "databaseName": "database0Name" + } + }, + { + "commandSucceededEvent": { + "reply": { + "cursor": { + "id": { + "$$type": "long" + }, + "ns": { + "$$type": "string" + }, + "firstBatch": { + "$$type": "array" + } + } + }, + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": "long" + }, + "collection": "coll0" + }, + "commandName": "getMore" + } + }, + { + "commandSucceededEvent": { + "reply": { + "cursor": { + "id": { + "$$type": "long" + }, + "ns": { + "$$type": "string" + }, + "nextBatch": { + "$$type": "array" + } + } + }, + "commandName": "getMore" + } + }, + { + "commandStartedEvent": { + "command": { + "killCursors": "coll0", + "cursors": { + "$$type": "array" + } + }, + "commandName": "killCursors" + } + }, + { + "commandSucceededEvent": { + "reply": { + "cursorsKilled": { + "$$unsetOrMatches": { + "$$type": "array" + } + } + }, + "commandName": "killCursors" + } + } + ] + } + ] + } + ] +} diff --git a/tests/UnifiedSpecTests/valid-pass/expectedEventsForClient-eventType.json b/tests/UnifiedSpecTests/valid-pass/expectedEventsForClient-eventType.json new file mode 100644 index 000000000..fe308df96 --- /dev/null +++ b/tests/UnifiedSpecTests/valid-pass/expectedEventsForClient-eventType.json @@ -0,0 +1,126 @@ +{ + "description": "expectedEventsForClient-eventType", + "schemaVersion": "1.3", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": true, + "observeEvents": [ + "commandStartedEvent", + "connectionReadyEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0Name" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "database0Name", + "documents": [] + } + ], + "tests": [ + { + "description": "eventType can be set to command and cmap", + "operations": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 1 + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "eventType": "command", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "coll0", + "documents": [ + { + "_id": 1 + } + ] + }, + "commandName": "insert" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + } + ] + } + ] + }, + { + "description": "eventType defaults to command if unset", + "operations": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 1 + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "coll0", + "documents": [ + { + "_id": 1 + } + ] + }, + "commandName": "insert" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + } + ] + } + ] + } + ] +} diff --git a/tests/UnifiedSpecTests/valid-pass/ignoreResultAndError.json b/tests/UnifiedSpecTests/valid-pass/ignoreResultAndError.json new file mode 100644 index 000000000..2e9b1c58a --- /dev/null +++ b/tests/UnifiedSpecTests/valid-pass/ignoreResultAndError.json @@ -0,0 +1,59 @@ +{ + "description": "ignoreResultAndError", + "schemaVersion": "1.3", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": true + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0Name" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "database0Name", + "documents": [] + } + ], + "tests": [ + { + "description": "operation errors are ignored if ignoreResultAndError is true", + "operations": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 1 + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 1 + } + }, + "ignoreResultAndError": true + } + ] + } + ] +} From fd39b3a419c1303fec14496760688fa9acc89c80 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Mon, 5 Jul 2021 16:35:01 -0400 Subject: [PATCH 08/11] PHPLIB-679: Check presence and type of operation args Explicit assertions can avoid differing behavior for undefined array keys (notice/warning on PHP 7/8, respectively). --- tests/UnifiedSpecTests/Operation.php | 140 ++++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 1 deletion(-) diff --git a/tests/UnifiedSpecTests/Operation.php b/tests/UnifiedSpecTests/Operation.php index 026574e19..64015062f 100644 --- a/tests/UnifiedSpecTests/Operation.php +++ b/tests/UnifiedSpecTests/Operation.php @@ -3,6 +3,7 @@ namespace MongoDB\Tests\UnifiedSpecTests; use Error; +use MongoDB\BSON\Javascript; use MongoDB\ChangeStream; use MongoDB\Client; use MongoDB\Collection; @@ -17,6 +18,7 @@ use MongoDB\Operation\FindOneAndUpdate; use PHPUnit\Framework\Assert; use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\Constraint\IsType; use stdClass; use Throwable; use function array_diff_key; @@ -30,6 +32,7 @@ use function iterator_to_array; use function key; use function MongoDB\with_transaction; +use function PHPUnit\Framework\assertArrayHasKey; use function PHPUnit\Framework\assertContains; use function PHPUnit\Framework\assertCount; use function PHPUnit\Framework\assertEquals; @@ -258,6 +261,9 @@ private function executeForClient(Client $client) switch ($this->name) { case 'createChangeStream': + assertArrayHasKey('pipeline', $args); + assertIsArray($args['pipeline']); + return $client->watch( $args['pipeline'], array_diff_key($args, ['pipeline' => 1]) @@ -277,37 +283,58 @@ private function executeForCollection(Collection $collection) switch ($this->name) { case 'aggregate': + assertArrayHasKey('pipeline', $args); + assertIsArray($args['pipeline']); + return iterator_to_array($collection->aggregate( $args['pipeline'], array_diff_key($args, ['pipeline' => 1]) )); case 'bulkWrite': + assertArrayHasKey('requests', $args); + assertIsArray($args['requests']); + return $collection->bulkWrite( array_map('self::prepareBulkWriteRequest', $args['requests']), array_diff_key($args, ['requests' => 1]) ); case 'createChangeStream': + assertArrayHasKey('pipeline', $args); + assertIsArray($args['pipeline']); + return $collection->watch( $args['pipeline'], array_diff_key($args, ['pipeline' => 1]) ); case 'createFindCursor': + assertArrayHasKey('filter', $args); + assertInstanceOf(stdClass::class, $args['filter']); + return $collection->find( $args['filter'], array_diff_key($args, ['filter' => 1]) ); case 'createIndex': + assertArrayHasKey('keys', $args); + assertInstanceOf(stdClass::class, $args['keys']); + return $collection->createIndex( $args['keys'], array_diff_key($args, ['keys' => 1]) ); case 'dropIndex': + assertArrayHasKey('name', $args); + assertIsString($args['name']); + return $collection->dropIndex( $args['name'], array_diff_key($args, ['name' => 1]) ); case 'count': case 'countDocuments': + assertArrayHasKey('filter', $args); + assertInstanceOf(stdClass::class, $args['filter']); + return $collection->{$this->name}( $args['filter'], array_diff_key($args, ['filter' => 1]) @@ -317,6 +344,9 @@ private function executeForCollection(Collection $collection) case 'deleteMany': case 'deleteOne': case 'findOneAndDelete': + assertArrayHasKey('filter', $args); + assertInstanceOf(stdClass::class, $args['filter']); + return $collection->{$this->name}( $args['filter'], array_diff_key($args, ['filter' => 1]) @@ -327,6 +357,11 @@ private function executeForCollection(Collection $collection) $collection->distinct('foo'); } + assertArrayHasKey('fieldName', $args); + assertArrayHasKey('filter', $args); + assertIsString($args['fieldName']); + assertInstanceOf(stdClass::class, $args['filter']); + return $collection->distinct( $args['fieldName'], $args['filter'], @@ -335,12 +370,21 @@ private function executeForCollection(Collection $collection) case 'drop': return $collection->drop($args); case 'find': + assertArrayHasKey('filter', $args); + assertInstanceOf(stdClass::class, $args['filter']); + return iterator_to_array($collection->find( $args['filter'], array_diff_key($args, ['filter' => 1]) )); case 'findOne': - return $collection->findOne($args['filter'], array_diff_key($args, ['filter' => 1])); + assertArrayHasKey('filter', $args); + assertInstanceOf(stdClass::class, $args['filter']); + + return $collection->findOne( + $args['filter'], + array_diff_key($args, ['filter' => 1]) + ); case 'findOneAndReplace': if (isset($args['returnDocument'])) { $args['returnDocument'] = strtolower($args['returnDocument']); @@ -353,6 +397,11 @@ private function executeForCollection(Collection $collection) // Fall through case 'replaceOne': + assertArrayHasKey('filter', $args); + assertArrayHasKey('replacement', $args); + assertInstanceOf(stdClass::class, $args['filter']); + assertInstanceOf(stdClass::class, $args['replacement']); + return $collection->{$this->name}( $args['filter'], $args['replacement'], @@ -371,6 +420,11 @@ private function executeForCollection(Collection $collection) case 'updateMany': case 'updateOne': + assertArrayHasKey('filter', $args); + assertArrayHasKey('update', $args); + assertInstanceOf(stdClass::class, $args['filter']); + assertThat($args['update'], logicalOr(new IsType('array'), new IsType('object'))); + return $collection->{$this->name}( $args['filter'], $args['update'], @@ -381,11 +435,17 @@ private function executeForCollection(Collection $collection) $options = isset($args['options']) ? (array) $args['options'] : []; $options += array_diff_key($args, ['documents' => 1]); + assertArrayHasKey('documents', $args); + assertIsArray($args['documents']); + return $collection->insertMany( $args['documents'], $options ); case 'insertOne': + assertArrayHasKey('document', $args); + assertInstanceOf(stdClass::class, $args['document']); + return $collection->insertOne( $args['document'], array_diff_key($args, ['document' => 1]) @@ -393,6 +453,13 @@ private function executeForCollection(Collection $collection) case 'listIndexes': return iterator_to_array($collection->listIndexes($args)); case 'mapReduce': + assertArrayHasKey('map', $args); + assertArrayHasKey('reduce', $args); + assertArrayHasKey('out', $args); + assertInstanceOf(Javascript::class, $args['map']); + assertInstanceOf(Javascript::class, $args['reduce']); + assertIsString($args['out']); + return $collection->mapReduce( $args['map'], $args['reduce'], @@ -456,21 +523,33 @@ private function executeForDatabase(Database $database) switch ($this->name) { case 'aggregate': + assertArrayHasKey('pipeline', $args); + assertIsArray($args['pipeline']); + return iterator_to_array($database->aggregate( $args['pipeline'], array_diff_key($args, ['pipeline' => 1]) )); case 'createChangeStream': + assertArrayHasKey('pipeline', $args); + assertIsArray($args['pipeline']); + return $database->watch( $args['pipeline'], array_diff_key($args, ['pipeline' => 1]) ); case 'createCollection': + assertArrayHasKey('collection', $args); + assertIsString($args['collection']); + return $database->createCollection( $args['collection'], array_diff_key($args, ['collection' => 1]) ); case 'dropCollection': + assertArrayHasKey('collection', $args); + assertIsString($args['collection']); + return $database->dropCollection( $args['collection'], array_diff_key($args, ['collection' => 1]) @@ -480,6 +559,9 @@ private function executeForDatabase(Database $database) case 'listCollections': return iterator_to_array($database->listCollections($args)); case 'runCommand': + assertArrayHasKey('command', $args); + assertInstanceOf(stdClass::class, $args['command']); + return $database->command( $args['command'], array_diff_key($args, ['command' => 1]) @@ -503,6 +585,7 @@ private function executeForSession(Session $session) case 'startTransaction': return $session->startTransaction($args); case 'withTransaction': + assertArrayHasKey('callback', $args); assertIsArray($args['callback']); $operations = array_map(function ($o) { @@ -529,15 +612,23 @@ private function executeForBucket(Bucket $bucket) switch ($this->name) { case 'delete': + assertArrayHasKey('id', $args); + return $bucket->delete($args['id']); case 'downloadByName': + assertArrayHasKey('filename', $args); + assertIsString($args['filename']); + return stream_get_contents($bucket->openDownloadStreamByName( $args['filename'], array_diff_key($args, ['filename' => 1]) )); case 'download': + assertArrayHasKey('id', $args); + return stream_get_contents($bucket->openDownloadStream($args['id'])); case 'uploadWithId': + assertArrayHasKey('id', $args); $args['_id'] = $args['id']; unset($args['id']); @@ -567,38 +658,55 @@ private function executeForTestRunner() switch ($this->name) { case 'assertCollectionExists': + assertArrayHasKey('databaseName', $args); + assertArrayHasKey('collectionName', $args); assertIsString($args['databaseName']); assertIsString($args['collectionName']); $database = $this->context->getInternalClient()->selectDatabase($args['databaseName']); assertContains($args['collectionName'], $database->listCollectionNames()); break; case 'assertCollectionNotExists': + assertArrayHasKey('databaseName', $args); + assertArrayHasKey('collectionName', $args); assertIsString($args['databaseName']); assertIsString($args['collectionName']); $database = $this->context->getInternalClient()->selectDatabase($args['databaseName']); assertNotContains($args['collectionName'], $database->listCollectionNames()); break; case 'assertIndexExists': + assertArrayHasKey('databaseName', $args); + assertArrayHasKey('collectionName', $args); + assertArrayHasKey('indexName', $args); assertIsString($args['databaseName']); assertIsString($args['collectionName']); assertIsString($args['indexName']); assertContains($args['indexName'], $this->getIndexNames($args['databaseName'], $args['collectionName'])); break; case 'assertIndexNotExists': + assertArrayHasKey('databaseName', $args); + assertArrayHasKey('collectionName', $args); + assertArrayHasKey('indexName', $args); assertIsString($args['databaseName']); assertIsString($args['collectionName']); assertIsString($args['indexName']); assertNotContains($args['indexName'], $this->getIndexNames($args['databaseName'], $args['collectionName'])); break; case 'assertSameLsidOnLastTwoCommands': + /* Context::getEventObserverForClient() requires the client ID. + * Avoid checking $args['client'], which is already resolved. */ + assertArrayHasKey('client', $this->arguments); $eventObserver = $this->context->getEventObserverForClient($this->arguments['client']); assertEquals(...$eventObserver->getLsidsOnLastTwoCommands()); break; case 'assertDifferentLsidOnLastTwoCommands': + /* Context::getEventObserverForClient() requires the client ID. + * Avoid checking $args['client'], which is already resolved. */ + assertArrayHasKey('client', $this->arguments); $eventObserver = $this->context->getEventObserverForClient($this->arguments['client']); assertNotEquals(...$eventObserver->getLsidsOnLastTwoCommands()); break; case 'assertNumberConnectionsCheckedOut': + assertArrayHasKey('connections', $args); assertIsInt($args['connections']); /* PHP does not implement connection pooling. Check parameters * for the sake of valid-fail tests, but otherwise raise an @@ -606,28 +714,42 @@ private function executeForTestRunner() Assert::fail('Tests using assertNumberConnectionsCheckedOut should be skipped'); break; case 'assertSessionDirty': + /* Context::isDirtySession() requires the session ID. Avoid + * checking $args['session'], which is already resolved. */ + assertArrayHasKey('session', $this->arguments); assertTrue($this->context->isDirtySession($this->arguments['session'])); break; case 'assertSessionNotDirty': + /* Context::isDirtySession() requires the session ID. Avoid + * checking $args['session'], which is already resolved. */ + assertArrayHasKey('session', $this->arguments); assertFalse($this->context->isDirtySession($this->arguments['session'])); break; case 'assertSessionPinned': + assertArrayHasKey('session', $args); assertInstanceOf(Session::class, $args['session']); assertInstanceOf(Server::class, $args['session']->getServer()); break; case 'assertSessionTransactionState': + assertArrayHasKey('session', $args); assertInstanceOf(Session::class, $args['session']); assertSame($this->arguments['state'], $args['session']->getTransactionState()); break; case 'assertSessionUnpinned': + assertArrayHasKey('session', $args); assertInstanceOf(Session::class, $args['session']); assertNull($args['session']->getServer()); break; case 'failPoint': + assertArrayHasKey('client', $args); + assertArrayHasKey('failPoint', $args); + assertInstanceOf(Client::class, $args['client']); assertInstanceOf(stdClass::class, $args['failPoint']); $args['client']->selectDatabase('admin')->command($args['failPoint']); break; case 'targetedFailPoint': + assertArrayHasKey('session', $args); + assertArrayHasKey('failPoint', $args); assertInstanceOf(Session::class, $args['session']); assertInstanceOf(stdClass::class, $args['failPoint']); assertNotNull($args['session']->getServer(), 'Session is pinned'); @@ -635,6 +757,7 @@ private function executeForTestRunner() $operation->execute($args['session']->getServer()); break; case 'loop': + assertArrayHasKey('operations', $args); assertIsArray($args['operations']); $operations = array_map(function ($o) { @@ -685,6 +808,9 @@ private static function prepareBulkWriteRequest(stdClass $request) : array switch ($type) { case 'deleteMany': case 'deleteOne': + assertArrayHasKey('filter', $args); + assertInstanceOf(stdClass::class, $args['filter']); + return [ $type => [ $args['filter'], @@ -692,8 +818,15 @@ private static function prepareBulkWriteRequest(stdClass $request) : array ], ]; case 'insertOne': + assertArrayHasKey('document', $args); + return [ 'insertOne' => [ $args['document']]]; case 'replaceOne': + assertArrayHasKey('filter', $args); + assertArrayHasKey('replacement', $args); + assertInstanceOf(stdClass::class, $args['filter']); + assertInstanceOf(stdClass::class, $args['replacement']); + return [ 'replaceOne' => [ $args['filter'], @@ -703,6 +836,11 @@ private static function prepareBulkWriteRequest(stdClass $request) : array ]; case 'updateMany': case 'updateOne': + assertArrayHasKey('filter', $args); + assertArrayHasKey('update', $args); + assertInstanceOf(stdClass::class, $args['filter']); + assertThat($args['update'], logicalOr(new IsType('array'), new IsType('object'))); + return [ $type => [ $args['filter'], From 95cd2a772b6d1caceada0bb7bc58f9d0191058dc Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Mon, 5 Jul 2021 16:37:29 -0400 Subject: [PATCH 09/11] Resolve "client" operation arg as a common option This is presently only used for test runner ops, but the spec does define it as a common option that should always be resolved (like "session"). --- tests/UnifiedSpecTests/Operation.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/UnifiedSpecTests/Operation.php b/tests/UnifiedSpecTests/Operation.php index 64015062f..9c169fd60 100644 --- a/tests/UnifiedSpecTests/Operation.php +++ b/tests/UnifiedSpecTests/Operation.php @@ -651,11 +651,6 @@ private function executeForTestRunner() { $args = $this->prepareArguments(); - if (array_key_exists('client', $args)) { - assertIsString($args['client']); - $args['client'] = $this->entityMap->getClient($args['client']); - } - switch ($this->name) { case 'assertCollectionExists': assertArrayHasKey('databaseName', $args); @@ -786,6 +781,11 @@ private function prepareArguments() : array { $args = $this->arguments; + if (array_key_exists('client', $args)) { + assertIsString($args['client']); + $args['client'] = $this->entityMap->getClient($args['client']); + } + if (array_key_exists('session', $args)) { assertIsString($args['session']); $args['session'] = $this->entityMap->getSession($args['session']); From d34335477792630a032440c3ca46346640191a1a Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Mon, 5 Jul 2021 17:27:04 -0400 Subject: [PATCH 10/11] PHPLIB-680: Topology match should not preempt later checks --- tests/UnifiedSpecTests/RunOnRequirement.php | 27 ++++++++++++--------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/tests/UnifiedSpecTests/RunOnRequirement.php b/tests/UnifiedSpecTests/RunOnRequirement.php index 5fe6c0314..1ddf0b927 100644 --- a/tests/UnifiedSpecTests/RunOnRequirement.php +++ b/tests/UnifiedSpecTests/RunOnRequirement.php @@ -92,17 +92,7 @@ public function isSatisfied(string $serverVersion, string $topology, stdClass $s return false; } - if (isset($this->topologies)) { - if (in_array($topology, $this->topologies)) { - return true; - } - - /* Ensure "sharded-replicaset" is also accepted for topologies that - * only include "sharded" (agnostic about the shard topology) */ - if ($topology === self::TOPOLOGY_SHARDED_REPLICASET && in_array(self::TOPOLOGY_SHARDED, $this->topologies)) { - return true; - } - + if (isset($this->topologies) && ! $this->isTopologySatisfied($topology)) { return false; } @@ -119,4 +109,19 @@ public function isSatisfied(string $serverVersion, string $topology, stdClass $s return true; } + + private function isTopologySatisfied(string $topology) : bool + { + if (in_array($topology, $this->topologies)) { + return true; + } + + /* Ensure "sharded-replicaset" is also accepted for topologies that + * only include "sharded" (agnostic about the shard topology) */ + if ($topology === self::TOPOLOGY_SHARDED_REPLICASET && in_array(self::TOPOLOGY_SHARDED, $this->topologies)) { + return true; + } + + return false; + } } From 19b5fe35f7dfa12a9ce2f704a2e0eca7deead732 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Mon, 5 Jul 2021 17:36:01 -0400 Subject: [PATCH 11/11] PHPLIB-670: Unified test runner changes for serverless support Sync unified transaction tests with mongodb/specifications@ba8a3f7587f1330cd7ca476371ac92c176152c1c Detection of serverless will be addressed after driver support is implemented (PHPC-1755) --- tests/UnifiedSpecTests/RunOnRequirement.php | 35 ++++++++- tests/UnifiedSpecTests/UnifiedTestRunner.php | 12 +++- .../transactions/mongos-unpin.json | 72 +++++++++++++++++-- 3 files changed, 112 insertions(+), 7 deletions(-) diff --git a/tests/UnifiedSpecTests/RunOnRequirement.php b/tests/UnifiedSpecTests/RunOnRequirement.php index 1ddf0b927..4fd46f6e3 100644 --- a/tests/UnifiedSpecTests/RunOnRequirement.php +++ b/tests/UnifiedSpecTests/RunOnRequirement.php @@ -6,6 +6,7 @@ use stdClass; use function array_diff; use function in_array; +use function PHPUnit\Framework\assertContains; use function PHPUnit\Framework\assertContainsOnly; use function PHPUnit\Framework\assertEmpty; use function PHPUnit\Framework\assertIsArray; @@ -22,6 +23,10 @@ class RunOnRequirement const TOPOLOGY_SHARDED_REPLICASET = 'sharded-replicaset'; const TOPOLOGY_LOAD_BALANCED = 'load-balanced'; + const SERVERLESS_REQUIRE = 'require'; + const SERVERLESS_FORBID = 'forbid'; + const SERVERLESS_ALLOW = 'allow'; + const VERSION_PATTERN = '/^[0-9]+(\\.[0-9]+){1,2}$/'; /** @var string */ @@ -39,6 +44,9 @@ class RunOnRequirement /** @var bool */ private $auth; + /** @var string */ + private $serverless; + /** @var array */ private static $supportedTopologies = [ self::TOPOLOGY_SINGLE, @@ -48,9 +56,16 @@ class RunOnRequirement self::TOPOLOGY_LOAD_BALANCED, ]; + /** @var array */ + private static $supportedServerless = [ + self::SERVERLESS_REQUIRE, + self::SERVERLESS_FORBID, + self::SERVERLESS_ALLOW, + ]; + public function __construct(stdClass $o) { - Util::assertHasOnlyKeys($o, ['minServerVersion', 'maxServerVersion', 'topologies', 'serverParameters', 'auth']); + Util::assertHasOnlyKeys($o, ['minServerVersion', 'maxServerVersion', 'topologies', 'serverParameters', 'auth', 'serverless']); if (isset($o->minServerVersion)) { assertIsString($o->minServerVersion); @@ -80,9 +95,15 @@ public function __construct(stdClass $o) assertIsBool($o->auth); $this->auth = $o->auth; } + + if (isset($o->serverless)) { + assertIsString($o->serverless); + assertContains($o->serverless, self::$supportedServerless); + $this->serverless = $o->serverless; + } } - public function isSatisfied(string $serverVersion, string $topology, stdClass $serverParameters, bool $isAuthenticated) : bool + public function isSatisfied(string $serverVersion, string $topology, stdClass $serverParameters, bool $isAuthenticated, bool $isServerless) : bool { if (isset($this->minServerVersion) && version_compare($serverVersion, $this->minServerVersion, '<')) { return false; @@ -107,6 +128,16 @@ public function isSatisfied(string $serverVersion, string $topology, stdClass $s return false; } + if (isset($this->serverless)) { + if (! $isServerless && $this->serverless === self::SERVERLESS_REQUIRE) { + return false; + } + + if ($isServerless && $this->serverless === self::SERVERLESS_FORBID) { + return false; + } + } + return true; } diff --git a/tests/UnifiedSpecTests/UnifiedTestRunner.php b/tests/UnifiedSpecTests/UnifiedTestRunner.php index ea5e0b2da..d30a7d6ec 100644 --- a/tests/UnifiedSpecTests/UnifiedTestRunner.php +++ b/tests/UnifiedSpecTests/UnifiedTestRunner.php @@ -45,7 +45,7 @@ final class UnifiedTestRunner const SERVER_ERROR_UNAUTHORIZED = 13; const MIN_SCHEMA_VERSION = '1.0'; - const MAX_SCHEMA_VERSION = '1.3'; + const MAX_SCHEMA_VERSION = '1.4'; /** @var MongoDB\Client */ private $internalClient; @@ -232,6 +232,7 @@ private function checkRunOnRequirements(array $runOnRequirements) $this->getTopology(), $this->getServerParameters(), $this->isAuthenticated(), + $this->isServerless(), ]; } @@ -329,6 +330,15 @@ private function isAuthenticated() : bool throw new UnexpectedValueException('Could not determine authentication status'); } + /** + * Return whether serverless (i.e. proxy as mongos) is being utilized. + */ + private function isServerless() : bool + { + // TODO: detect serverless once PHPC-1755 is implemented + return false; + } + /** * Checks is a test format schema version is supported. */ diff --git a/tests/UnifiedSpecTests/transactions/mongos-unpin.json b/tests/UnifiedSpecTests/transactions/mongos-unpin.json index 33127198a..4f7ae4379 100644 --- a/tests/UnifiedSpecTests/transactions/mongos-unpin.json +++ b/tests/UnifiedSpecTests/transactions/mongos-unpin.json @@ -1,6 +1,6 @@ { "description": "mongos-unpin", - "schemaVersion": "1.1", + "schemaVersion": "1.4", "runOnRequirements": [ { "minServerVersion": "4.2", @@ -49,7 +49,12 @@ }, "tests": [ { - "description": "unpin after TransientTransctionError error on commit", + "description": "unpin after TransientTransactionError error on commit", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], "operations": [ { "name": "startTransaction", @@ -103,6 +108,24 @@ "arguments": { "session": "session0" } + }, + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "abortTransaction", + "object": "session0" } ] }, @@ -137,7 +160,12 @@ ] }, { - "description": "unpin after TransientTransctionError error on abort", + "description": "unpin after non-transient error on abort", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], "operations": [ { "name": "startTransaction", @@ -182,11 +210,29 @@ "arguments": { "session": "session0" } + }, + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "abortTransaction", + "object": "session0" } ] }, { - "description": "unpin after non-transient error on abort", + "description": "unpin after TransientTransactionError error on abort", "operations": [ { "name": "startTransaction", @@ -231,6 +277,24 @@ "arguments": { "session": "session0" } + }, + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "abortTransaction", + "object": "session0" } ] },