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" 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/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/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/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 0f93d094b..9c169fd60 100644 --- a/tests/UnifiedSpecTests/Operation.php +++ b/tests/UnifiedSpecTests/Operation.php @@ -3,10 +3,12 @@ namespace MongoDB\Tests\UnifiedSpecTests; use Error; +use MongoDB\BSON\Javascript; use MongoDB\ChangeStream; 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; @@ -16,9 +18,9 @@ use MongoDB\Operation\FindOneAndUpdate; use PHPUnit\Framework\Assert; use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\Constraint\IsType; use stdClass; use Throwable; -use Traversable; use function array_diff_key; use function array_key_exists; use function array_map; @@ -30,12 +32,14 @@ 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; 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; @@ -82,6 +86,9 @@ final class Operation /** @var ExpectedResult */ private $expectedResult; + /** @var bool */ + private $ignoreResultAndError; + /** @var string */ private $saveResultAsEntity; @@ -102,10 +109,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); @@ -159,8 +171,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) { @@ -194,6 +208,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; @@ -204,10 +221,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; } @@ -248,17 +261,17 @@ private function executeForClient(Client $client) switch ($this->name) { case 'createChangeStream': - $changeStream = $client->watch( - $args['pipeline'] ?? [], + assertArrayHasKey('pipeline', $args); + assertIsArray($args['pipeline']); + + return $client->watch( + $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,38 +283,60 @@ private function executeForCollection(Collection $collection) switch ($this->name) { case 'aggregate': - return $collection->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': - $changeStream = $collection->watch( - $args['pipeline'] ?? [], + assertArrayHasKey('pipeline', $args); + assertIsArray($args['pipeline']); + + return $collection->watch( + $args['pipeline'], array_diff_key($args, ['pipeline' => 1]) ); - $changeStream->rewind(); + case 'createFindCursor': + assertArrayHasKey('filter', $args); + assertInstanceOf(stdClass::class, $args['filter']); - return $changeStream; + 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': - case 'find': + assertArrayHasKey('filter', $args); + assertInstanceOf(stdClass::class, $args['filter']); + return $collection->{$this->name}( - $args['filter'] ?? [], + $args['filter'], array_diff_key($args, ['filter' => 1]) ); case 'estimatedDocumentCount': @@ -309,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]) @@ -319,15 +357,34 @@ 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'] ?? [], + $args['filter'], array_diff_key($args, ['fieldName' => 1, 'filter' => 1]) ); 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']); @@ -340,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'], @@ -358,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'], @@ -368,18 +435,31 @@ 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]) ); case 'listIndexes': - return $collection->listIndexes($args); + 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'], @@ -391,39 +471,97 @@ 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(); switch ($this->name) { case 'aggregate': - return $database->aggregate( + assertArrayHasKey('pipeline', $args); + assertIsArray($args['pipeline']); + + return iterator_to_array($database->aggregate( $args['pipeline'], array_diff_key($args, ['pipeline' => 1]) - ); + )); case 'createChangeStream': - $changeStream = $database->watch( - $args['pipeline'] ?? [], + assertArrayHasKey('pipeline', $args); + assertIsArray($args['pipeline']); + + return $database->watch( + $args['pipeline'], array_diff_key($args, ['pipeline' => 1]) ); - $changeStream->rewind(); - - return $changeStream; 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]) ); 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': + assertArrayHasKey('command', $args); + assertInstanceOf(stdClass::class, $args['command']); + return $database->command( $args['command'], array_diff_key($args, ['command' => 1]) @@ -447,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) { @@ -473,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']); @@ -504,67 +651,100 @@ 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); + 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 + * error. */ + 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'); @@ -572,6 +752,7 @@ private function executeForTestRunner() $operation->execute($args['session']->getServer()); break; case 'loop': + assertArrayHasKey('operations', $args); assertIsArray($args['operations']); $operations = array_map(function ($o) { @@ -600,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']); @@ -622,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'], @@ -629,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'], @@ -640,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'], diff --git a/tests/UnifiedSpecTests/RunOnRequirement.php b/tests/UnifiedSpecTests/RunOnRequirement.php index 0c372e03d..4fd46f6e3 100644 --- a/tests/UnifiedSpecTests/RunOnRequirement.php +++ b/tests/UnifiedSpecTests/RunOnRequirement.php @@ -4,8 +4,11 @@ use MongoDB\Tests\UnifiedSpecTests\Constraint\Matches; 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; use function PHPUnit\Framework\assertIsObject; use function PHPUnit\Framework\assertIsString; @@ -18,6 +21,11 @@ class RunOnRequirement const TOPOLOGY_REPLICASET = 'replicaset'; const TOPOLOGY_SHARDED = 'sharded'; 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}$/'; @@ -33,8 +41,32 @@ class RunOnRequirement /** @var stdClass */ private $serverParameters; + /** @var bool */ + private $auth; + + /** @var string */ + private $serverless; + + /** @var array */ + private static $supportedTopologies = [ + self::TOPOLOGY_SINGLE, + self::TOPOLOGY_REPLICASET, + self::TOPOLOGY_SHARDED, + self::TOPOLOGY_SHARDED_REPLICASET, + 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', 'serverless']); + if (isset($o->minServerVersion)) { assertIsString($o->minServerVersion); assertMatchesRegularExpression(self::VERSION_PATTERN, $o->minServerVersion); @@ -50,6 +82,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 +90,20 @@ public function __construct(stdClass $o) assertIsObject($o->serverParameters); $this->serverParameters = $o->serverParameters; } + + if (isset($o->auth)) { + 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 + 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; @@ -69,17 +113,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; } @@ -90,6 +124,35 @@ public function isSatisfied(string $serverVersion, string $topology, stdClass $s } } + if (isset($this->auth) && $isAuthenticated !== $this->auth) { + 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; } + + 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; + } } diff --git a/tests/UnifiedSpecTests/UnifiedSpecTest.php b/tests/UnifiedSpecTests/UnifiedSpecTest.php index 47f00cf32..b18684f1b 100644 --- a/tests/UnifiedSpecTests/UnifiedSpecTest.php +++ b/tests/UnifiedSpecTests/UnifiedSpecTest.php @@ -42,67 +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', - // 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', + // 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..d30a7d6ec 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.4'; /** @var MongoDB\Client */ private $internalClient; @@ -213,93 +215,41 @@ 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(), + $this->isServerless(), + ]; + } 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 +289,56 @@ 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'); + } + + /** + * 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/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/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" } ] }, 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 + } + ] + } + ] +} 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" 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": { 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 } } }