Skip to content

Commit 2fc7e89

Browse files
committed
PHPLIB-669: Unified test runner changes for load balancer support
Sync valid-pass and valid-fail tests with mongodb/specifications@ed4e62b Moves caching of server environment to checkRunOnRequirements Detection of load balancers will be addressed after driver support is implemented (PHPLIB-671)
1 parent 237397c commit 2fc7e89

15 files changed

+856
-77
lines changed

tests/UnifiedSpecTests/Context.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use function PHPUnit\Framework\assertNotEmpty;
3030
use function PHPUnit\Framework\assertNotFalse;
3131
use function PHPUnit\Framework\assertNotSame;
32+
use function PHPUnit\Framework\assertSame;
3233
use function PHPUnit\Framework\assertStringContainsString;
3334
use function PHPUnit\Framework\assertStringStartsWith;
3435
use function strlen;
@@ -171,13 +172,17 @@ public function assertExpectedEventsForClients(array $expectedEventsForClients)
171172

172173
foreach ($expectedEventsForClients as $expectedEventsForClient) {
173174
assertIsObject($expectedEventsForClient);
174-
Util::assertHasOnlyKeys($expectedEventsForClient, ['client', 'events']);
175+
Util::assertHasOnlyKeys($expectedEventsForClient, ['client', 'events', 'eventType']);
175176

176177
$client = $expectedEventsForClient->client ?? null;
178+
$eventType = $expectedEventsForClient->eventType ?? 'command';
177179
$expectedEvents = $expectedEventsForClient->events ?? null;
178180

179181
assertIsString($client);
180182
assertArrayHasKey($client, $this->eventObserversByClient);
183+
/* Note: PHPC does not implement CMAP. Any tests expecting CMAP
184+
* events should be skipped explicitly. */
185+
assertSame('command', $eventType);
181186
assertIsArray($expectedEvents);
182187

183188
$this->eventObserversByClient[$client]->assert($expectedEvents);

tests/UnifiedSpecTests/EntityMap.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use MongoDB\Client;
88
use MongoDB\Collection;
99
use MongoDB\Database;
10+
use MongoDB\Driver\Cursor;
1011
use MongoDB\Driver\Session;
1112
use MongoDB\GridFS\Bucket;
1213
use MongoDB\Tests\UnifiedSpecTests\Constraint\IsBsonType;
@@ -16,6 +17,7 @@
1617
use function array_key_exists;
1718
use function PHPUnit\Framework\assertArrayHasKey;
1819
use function PHPUnit\Framework\assertArrayNotHasKey;
20+
use function PHPUnit\Framework\assertInstanceOf;
1921
use function PHPUnit\Framework\assertIsString;
2022
use function PHPUnit\Framework\assertThat;
2123
use function PHPUnit\Framework\isInstanceOf;
@@ -128,6 +130,17 @@ public function getRoot() : self
128130
};
129131
}
130132

133+
/**
134+
* Closes a cursor by removing it from the entity map.
135+
*
136+
* @see Operation::executeForCursor()
137+
*/
138+
public function closeCursor(string $cursorId)
139+
{
140+
assertInstanceOf(Cursor::class, $this[$cursorId]);
141+
unset($this->map[$cursorId]);
142+
}
143+
131144
public function getClient(string $clientId) : Client
132145
{
133146
return $this[$clientId];
@@ -170,6 +183,7 @@ private static function isSupportedType() : Constraint
170183
isInstanceOf(Session::class),
171184
isInstanceOf(Bucket::class),
172185
isInstanceOf(ChangeStream::class),
186+
isInstanceOf(Cursor::class),
173187
IsBsonType::any()
174188
);
175189
}

tests/UnifiedSpecTests/EventObserver.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ private function assertEvent($actual, stdClass $expected, string $message)
199199

200200
private function assertCommandStartedEvent(CommandStartedEvent $actual, stdClass $expected, string $message)
201201
{
202+
// TODO: Assert hasServiceId (blocked on PHPC-1752)
202203
Util::assertHasOnlyKeys($expected, ['command', 'commandName', 'databaseName']);
203204

204205
if (isset($expected->command)) {
@@ -220,6 +221,7 @@ private function assertCommandStartedEvent(CommandStartedEvent $actual, stdClass
220221

221222
private function assertCommandSucceededEvent(CommandSucceededEvent $actual, stdClass $expected, string $message)
222223
{
224+
// TODO: Assert hasServiceId (blocked on PHPC-1752)
223225
Util::assertHasOnlyKeys($expected, ['reply', 'commandName']);
224226

225227
if (isset($expected->reply)) {
@@ -236,6 +238,7 @@ private function assertCommandSucceededEvent(CommandSucceededEvent $actual, stdC
236238

237239
private function assertCommandFailedEvent(CommandFailedEvent $actual, stdClass $expected, string $message)
238240
{
241+
// TODO: Assert hasServiceId (blocked on PHPC-1752)
239242
Util::assertHasOnlyKeys($expected, ['commandName']);
240243

241244
if (isset($expected->commandName)) {

tests/UnifiedSpecTests/Operation.php

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use MongoDB\Client;
88
use MongoDB\Collection;
99
use MongoDB\Database;
10+
use MongoDB\Driver\Cursor;
1011
use MongoDB\Driver\Server;
1112
use MongoDB\Driver\Session;
1213
use MongoDB\GridFS\Bucket;
@@ -35,6 +36,7 @@
3536
use function PHPUnit\Framework\assertFalse;
3637
use function PHPUnit\Framework\assertInstanceOf;
3738
use function PHPUnit\Framework\assertIsArray;
39+
use function PHPUnit\Framework\assertIsInt;
3840
use function PHPUnit\Framework\assertIsObject;
3941
use function PHPUnit\Framework\assertIsString;
4042
use function PHPUnit\Framework\assertMatchesRegularExpression;
@@ -81,6 +83,9 @@ final class Operation
8183
/** @var ExpectedResult */
8284
private $expectedResult;
8385

86+
/** @var bool */
87+
private $ignoreResultAndError;
88+
8489
/** @var string */
8590
private $saveResultAsEntity;
8691

@@ -101,10 +106,15 @@ public function __construct(stdClass $o, Context $context)
101106
$this->arguments = (array) $o->arguments;
102107
}
103108

109+
if (isset($o->ignoreResultAndError) && (isset($o->expectError) || property_exists($o, 'expectResult') || isset($o->saveResultAsEntity))) {
110+
Assert::fail('ignoreResultAndError is mutually exclusive with expectError, expectResult, and saveResultAsEntity');
111+
}
112+
104113
if (isset($o->expectError) && (property_exists($o, 'expectResult') || isset($o->saveResultAsEntity))) {
105114
Assert::fail('expectError is mutually exclusive with expectResult and saveResultAsEntity');
106115
}
107116

117+
$this->ignoreResultAndError = $o->ignoreResultAndError ?? false;
108118
$this->expectError = new ExpectedError($o->expectError ?? null, $this->entityMap);
109119
$this->expectResult = new ExpectedResult($o, $this->entityMap, $this->object);
110120

@@ -158,8 +168,10 @@ public function assert(bool $rethrowExceptions = false)
158168
}
159169
}
160170

161-
$this->expectError->assert($error);
162-
$this->expectResult->assert($result, $saveResultAsEntity);
171+
if (! $this->ignoreResultAndError) {
172+
$this->expectError->assert($error);
173+
$this->expectResult->assert($result, $saveResultAsEntity);
174+
}
163175

164176
// Rethrowing is primarily used for withTransaction callbacks
165177
if ($error && $rethrowExceptions) {
@@ -193,6 +205,9 @@ private function execute()
193205
case ChangeStream::class:
194206
$result = $this->executeForChangeStream($object);
195207
break;
208+
case Cursor::class:
209+
$result = $this->executeForCursor($object);
210+
break;
196211
case Session::class:
197212
$result = $this->executeForSession($object);
198213
break;
@@ -276,6 +291,11 @@ private function executeForCollection(Collection $collection)
276291
$args['pipeline'],
277292
array_diff_key($args, ['pipeline' => 1])
278293
);
294+
case 'createFindCursor':
295+
return $collection->find(
296+
$args['filter'],
297+
array_diff_key($args, ['filter' => 1])
298+
);
279299
case 'createIndex':
280300
return $collection->createIndex(
281301
$args['keys'],
@@ -384,6 +404,52 @@ private function executeForCollection(Collection $collection)
384404
}
385405
}
386406

407+
private function executeForCursor(Cursor $cursor)
408+
{
409+
$args = $this->prepareArguments();
410+
411+
switch ($this->name) {
412+
case 'close':
413+
/* PHPC does not provide an API to directly close a cursor.
414+
* mongoc_cursor_destroy is only invoked from the Cursor's
415+
* free_object handler, which requires unsetting the object from
416+
* the entity map to trigger garbage collection. This will need
417+
* a different approach if tests ever attempt to access the
418+
* cursor entity after calling the "close" operation. */
419+
$this->entityMap->closeCursor($this->object);
420+
assertFalse($this->entityMap->offsetExists($this->object));
421+
break;
422+
case 'iterateUntilDocumentOrError':
423+
/* Note: the first iteration should use rewind, otherwise we may
424+
* miss a document from the initial batch (possible if using a
425+
* resume token). We can infer this from a null key; however,
426+
* if a test ever calls this operation consecutively to expect
427+
* multiple errors from the same ChangeStream we will need a
428+
* different approach (e.g. examining internal hasAdvanced
429+
* property on the ChangeStream). */
430+
431+
/* Note: similar to iterateUntilDocumentOrError for ChangeStream
432+
* entities, a different approach will be needed if a test ever
433+
* calls this operation consecutively to expect multiple errors.
434+
*/
435+
if ($cursor->key() === null) {
436+
$cursor->rewind();
437+
438+
if ($cursor->valid()) {
439+
return $cursor->current();
440+
}
441+
}
442+
443+
do {
444+
$cursor->next();
445+
} while (! $cursor->valid());
446+
447+
return $cursor->current();
448+
default:
449+
Assert::fail('Unsupported cursor operation: ' . $this->name);
450+
}
451+
}
452+
387453
private function executeForDatabase(Database $database)
388454
{
389455
$args = $this->prepareArguments();
@@ -532,6 +598,13 @@ private function executeForTestRunner()
532598
$eventObserver = $this->context->getEventObserverForClient($this->arguments['client']);
533599
assertNotEquals(...$eventObserver->getLsidsOnLastTwoCommands());
534600
break;
601+
case 'assertNumberConnectionsCheckedOut':
602+
assertIsInt($args['connections']);
603+
/* PHP does not implement connection pooling. Check parameters
604+
* for the sake of valid-fail tests, but otherwise raise an
605+
* error. */
606+
Assert::fail('Tests using assertNumberConnectionsCheckedOut should be skipped');
607+
break;
535608
case 'assertSessionDirty':
536609
assertTrue($this->context->isDirtySession($this->arguments['session']));
537610
break;

tests/UnifiedSpecTests/RunOnRequirement.php

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
use MongoDB\Tests\UnifiedSpecTests\Constraint\Matches;
66
use stdClass;
7+
use function array_diff;
78
use function in_array;
89
use function PHPUnit\Framework\assertContainsOnly;
10+
use function PHPUnit\Framework\assertEmpty;
911
use function PHPUnit\Framework\assertIsArray;
1012
use function PHPUnit\Framework\assertIsObject;
1113
use function PHPUnit\Framework\assertIsString;
@@ -18,6 +20,7 @@ class RunOnRequirement
1820
const TOPOLOGY_REPLICASET = 'replicaset';
1921
const TOPOLOGY_SHARDED = 'sharded';
2022
const TOPOLOGY_SHARDED_REPLICASET = 'sharded-replicaset';
23+
const TOPOLOGY_LOAD_BALANCED = 'load-balanced';
2124

2225
const VERSION_PATTERN = '/^[0-9]+(\\.[0-9]+){1,2}$/';
2326

@@ -33,8 +36,22 @@ class RunOnRequirement
3336
/** @var stdClass */
3437
private $serverParameters;
3538

39+
/** @var bool */
40+
private $auth;
41+
42+
/** @var array */
43+
private static $supportedTopologies = [
44+
self::TOPOLOGY_SINGLE,
45+
self::TOPOLOGY_REPLICASET,
46+
self::TOPOLOGY_SHARDED,
47+
self::TOPOLOGY_SHARDED_REPLICASET,
48+
self::TOPOLOGY_LOAD_BALANCED,
49+
];
50+
3651
public function __construct(stdClass $o)
3752
{
53+
Util::assertHasOnlyKeys($o, ['minServerVersion', 'maxServerVersion', 'topologies', 'serverParameters', 'auth']);
54+
3855
if (isset($o->minServerVersion)) {
3956
assertIsString($o->minServerVersion);
4057
assertMatchesRegularExpression(self::VERSION_PATTERN, $o->minServerVersion);
@@ -50,16 +67,22 @@ public function __construct(stdClass $o)
5067
if (isset($o->topologies)) {
5168
assertIsArray($o->topologies);
5269
assertContainsOnly('string', $o->topologies);
70+
assertEmpty(array_diff($o->topologies, self::$supportedTopologies));
5371
$this->topologies = $o->topologies;
5472
}
5573

5674
if (isset($o->serverParameters)) {
5775
assertIsObject($o->serverParameters);
5876
$this->serverParameters = $o->serverParameters;
5977
}
78+
79+
if (isset($o->auth)) {
80+
assertIsBool($o->auth);
81+
$this->auth = $o->auth;
82+
}
6083
}
6184

62-
public function isSatisfied(string $serverVersion, string $topology, stdClass $serverParameters) : bool
85+
public function isSatisfied(string $serverVersion, string $topology, stdClass $serverParameters, bool $isAuthenticated) : bool
6386
{
6487
if (isset($this->minServerVersion) && version_compare($serverVersion, $this->minServerVersion, '<')) {
6588
return false;
@@ -90,6 +113,10 @@ public function isSatisfied(string $serverVersion, string $topology, stdClass $s
90113
}
91114
}
92115

116+
if (isset($this->auth) && $isAuthenticated !== $this->auth) {
117+
return false;
118+
}
119+
93120
return true;
94121
}
95122
}

tests/UnifiedSpecTests/UnifiedSpecTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ class UnifiedSpecTest extends FunctionalTestCase
4242
'crud/unacknowledged-updateMany-hint-clientError: Unacknowledged updateMany with hint document fails with client-side error' => 'PHPLIB-573 and DRIVERS-1340',
4343
'crud/unacknowledged-updateOne-hint-clientError: Unacknowledged updateOne with hint string fails with client-side error' => 'PHPLIB-573 and DRIVERS-1340',
4444
'crud/unacknowledged-updateOne-hint-clientError: Unacknowledged updateOne with hint document fails with client-side error' => 'PHPLIB-573 and DRIVERS-1340',
45+
// PHPC does not implement CMAP
46+
'valid-pass/assertNumberConnectionsCheckedOut: basic assertion succeeds' => 'PHPC does not implement CMAP',
47+
'valid-pass/entity-client-cmap-events: events are captured during an operation' => 'PHPC does not implement CMAP',
48+
'valid-pass/expectedEventsForClient-eventType: eventType can be set to command and cmap' => 'PHPC does not implement CMAP',
49+
'valid-pass/expectedEventsForClient-eventType: eventType defaults to command if unset' => 'PHPC does not implement CMAP',
4550
];
4651

4752
/** @var UnifiedTestRunner */

0 commit comments

Comments
 (0)