Skip to content

Commit c212cb5

Browse files
committed
PHPLIB-1459: Retryable writes prose tests for mongos selection
This also moves the existing, third prose test into its own file.
1 parent a23a4b8 commit c212cb5

File tree

4 files changed

+206
-19
lines changed

4 files changed

+206
-19
lines changed

phpcs.xml.dist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@
161161
<exclude-pattern>/examples</exclude-pattern>
162162
</rule>
163163
<rule ref="Squiz.Classes.ValidClassName.NotCamelCaps">
164-
<exclude-pattern>/tests/SpecTests/ClientSideEncryption/Prose*</exclude-pattern>
164+
<exclude-pattern>/tests/SpecTests/*/Prose*</exclude-pattern>
165165
</rule>
166166

167167
<!-- **************************************** -->

tests/SpecTests/RetryableWritesSpecTest.php renamed to tests/SpecTests/RetryableWrites/Prose3_ReturnOriginalErrorTest.php

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,25 @@
11
<?php
22

3-
namespace MongoDB\Tests\SpecTests;
3+
namespace MongoDB\Tests\SpecTests\RetryableWrites;
44

55
use MongoDB\Driver\Exception\BulkWriteException;
66
use MongoDB\Driver\Monitoring\CommandFailedEvent;
77
use MongoDB\Driver\Monitoring\CommandStartedEvent;
88
use MongoDB\Driver\Monitoring\CommandSubscriber;
99
use MongoDB\Driver\Monitoring\CommandSucceededEvent;
10+
use MongoDB\Tests\SpecTests\FunctionalTestCase;
1011

1112
/**
12-
* Retryable writes spec tests.
13+
* Prose test 3: Return Original Error
1314
*
14-
* @see https://github.com/mongodb/specifications/tree/master/source/retryable-writes
15+
* @see https://github.com/mongodb/specifications/blob/master/source/retryable-writes/tests/README.md
1516
* @group serverless
1617
*/
17-
class RetryableWritesSpecTest extends FunctionalTestCase
18+
class Prose3_ReturnOriginalErrorTest extends FunctionalTestCase
1819
{
19-
public const NOT_PRIMARY = 10107;
20+
public const NOT_WRITABLE_PRIMARY = 10107;
2021
public const SHUTDOWN_IN_PROGRESS = 91;
2122

22-
/**
23-
* Prose test 3: when encountering a NoWritesPerformed error after an error with a RetryableWriteError label
24-
*/
2523
public function testNoWritesPerformedErrorReturnsOriginalError(): void
2624
{
2725
if (! $this->isReplicaSet()) {
@@ -46,9 +44,9 @@ public function testNoWritesPerformedErrorReturnsOriginalError(): void
4644
]);
4745

4846
$subscriber = new class ($this) implements CommandSubscriber {
49-
private RetryableWritesSpecTest $testCase;
47+
private FunctionalTestCase $testCase;
5048

51-
public function __construct(RetryableWritesSpecTest $testCase)
49+
public function __construct(FunctionalTestCase $testCase)
5250
{
5351
$this->testCase = $testCase;
5452
}
@@ -65,7 +63,7 @@ public function commandSucceeded(CommandSucceededEvent $event): void
6563
'configureFailPoint' => 'failCommand',
6664
'mode' => ['times' => 1],
6765
'data' => [
68-
'errorCode' => RetryableWritesSpecTest::NOT_PRIMARY,
66+
'errorCode' => Prose3_ReturnOriginalErrorTest::NOT_WRITABLE_PRIMARY,
6967
'errorLabels' => ['RetryableWriteError', 'NoWritesPerformed'],
7068
'failCommands' => ['insert'],
7169
],
@@ -78,7 +76,7 @@ public function commandFailed(CommandFailedEvent $event): void
7876
}
7977
};
8078

81-
$client->getManager()->addSubscriber($subscriber);
79+
$client->addSubscriber($subscriber);
8280

8381
// Step 4: Run insertOne
8482
try {
@@ -91,11 +89,8 @@ public function commandFailed(CommandFailedEvent $event): void
9189
$this->assertSame(self::SHUTDOWN_IN_PROGRESS, $writeConcernError->getCode());
9290
}
9391

94-
// Step 5: Disable the fail point
95-
$client->getManager()->removeSubscriber($subscriber);
96-
$this->configureFailPoint([
97-
'configureFailPoint' => 'failCommand',
98-
'mode' => 'off',
99-
]);
92+
$client->removeSubscriber($subscriber);
93+
94+
// Step 5: The fail point will be disabled during tearDown()
10095
}
10196
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
namespace MongoDB\Tests\SpecTests\RetryableWrites;
4+
5+
use MongoDB\Driver\Exception\BulkWriteException;
6+
use MongoDB\Driver\Monitoring\CommandFailedEvent;
7+
use MongoDB\Driver\Monitoring\CommandStartedEvent;
8+
use MongoDB\Driver\Monitoring\CommandSubscriber;
9+
use MongoDB\Driver\Monitoring\CommandSucceededEvent;
10+
use MongoDB\Tests\SpecTests\FunctionalTestCase;
11+
12+
use function assert;
13+
use function count;
14+
15+
/**
16+
* Prose test 4: Retry on different mongos when one is available
17+
*
18+
* @see https://github.com/mongodb/specifications/blob/master/source/retryable-writes/tests/README.md
19+
*/
20+
class Prose4_RetryOnDifferentMongosTest extends FunctionalTestCase
21+
{
22+
public const HOST_UNREACHABLE = 6;
23+
24+
public function testRetryOnDifferentMongos(): void
25+
{
26+
if (! $this->isMongos()) {
27+
$this->markTestSkipped('Test requires connections to mongos');
28+
}
29+
30+
$this->skipIfServerVersion('<', '4.3.1', 'Test requires configureFailPoint to support errorLabels');
31+
32+
$this->dropCollection($this->getDatabaseName(), $this->getCollectionName());
33+
34+
/* By default, the Manager under test is created with a single-mongos
35+
* URI. Explicitly create a Client with multiple mongoses and invoke
36+
* server selection to initialize SDAM. */
37+
$client = static::createTestClient(static::getUri(true), ['retryWrites' => true]);
38+
$client->getManager()->selectServer();
39+
40+
/* Step 1: Select servers for each mongos in the cluster.
41+
*
42+
* TODO: Support topologies with 3+ servers by selecting only two and
43+
* recreating a client URI.
44+
*/
45+
$servers = $client->getManager()->getServers();
46+
assert(count($servers) === 2);
47+
48+
// Step 2: Configure the following fail point on each mongos
49+
foreach ($servers as $server) {
50+
$this->configureFailPoint(
51+
[
52+
'configureFailPoint' => 'failCommand',
53+
'mode' => ['times' => 1],
54+
'data' => [
55+
'failCommands' => ['insert'],
56+
'errorCode' => self::HOST_UNREACHABLE,
57+
'errorLabels' => ['RetryableWriteError'],
58+
],
59+
],
60+
$server,
61+
);
62+
}
63+
64+
/* Step 3: Use the previously created client with retryWrites=true,
65+
* which is connected to a cluster with two mongoses */
66+
67+
// Step 4: Enable failed command event monitoring for client
68+
$subscriber = new class implements CommandSubscriber {
69+
public $commandFailedServers = [];
70+
71+
public function commandStarted(CommandStartedEvent $event): void
72+
{
73+
}
74+
75+
public function commandSucceeded(CommandSucceededEvent $event): void
76+
{
77+
}
78+
79+
public function commandFailed(CommandFailedEvent $event): void
80+
{
81+
$this->commandFailedServers[] = $event->getServer();
82+
}
83+
};
84+
85+
$client->addSubscriber($subscriber);
86+
87+
// Step 5: Execute an insert command. Assert that the command failed.
88+
try {
89+
$client->selectCollection($this->getDatabaseName(), $this->getCollectionName())->insertOne(['x' => 1]);
90+
$this->fail('BulkWriteException was not thrown');
91+
} catch (BulkWriteException $e) {
92+
$this->assertSame(self::HOST_UNREACHABLE, $e->getCode());
93+
}
94+
95+
$client->removeSubscriber($subscriber);
96+
97+
/* Step 6: Assert that two failed command events occurred. Assert that
98+
* the failed command events occurred on different mongoses. */
99+
$this->assertCount(2, $subscriber->commandFailedServers);
100+
$this->assertNotEquals($subscriber->commandFailedServers[0], $subscriber->commandFailedServers[1]);
101+
102+
// Step 7: The fail points will be disabled during tearDown()
103+
}
104+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
namespace MongoDB\Tests\SpecTests\RetryableWrites;
4+
5+
use MongoDB\Driver\Monitoring\CommandFailedEvent;
6+
use MongoDB\Driver\Monitoring\CommandStartedEvent;
7+
use MongoDB\Driver\Monitoring\CommandSubscriber;
8+
use MongoDB\Driver\Monitoring\CommandSucceededEvent;
9+
use MongoDB\Driver\Server;
10+
use MongoDB\Tests\SpecTests\FunctionalTestCase;
11+
12+
/**
13+
* Prose test 5: Retry on same mongos when not others are available
14+
*
15+
* @see https://github.com/mongodb/specifications/blob/master/source/retryable-writes/tests/README.md
16+
*/
17+
class Prose5_RetryOnSameMongosTest extends FunctionalTestCase
18+
{
19+
public const HOST_UNREACHABLE = 6;
20+
21+
public function testRetryOnSameMongos(): void
22+
{
23+
if (! $this->isMongos()) {
24+
$this->markTestSkipped('Test requires connections to mongos');
25+
}
26+
27+
$this->skipIfServerVersion('<', '4.3.1', 'Test requires configureFailPoint to support errorLabels');
28+
29+
$this->dropCollection($this->getDatabaseName(), $this->getCollectionName());
30+
31+
// Step 1: Create a client that connects to a single mongos
32+
$client = static::createTestClient(null, ['directConnection' => false, 'retryWrites' => true]);
33+
$server = $client->getManager()->selectServer();
34+
35+
// Step 2: Configure the following fail point on the mongos
36+
$this->configureFailPoint(
37+
[
38+
'configureFailPoint' => 'failCommand',
39+
'mode' => ['times' => 1],
40+
'data' => [
41+
'failCommands' => ['insert'],
42+
'errorCode' => self::HOST_UNREACHABLE,
43+
'errorLabels' => ['RetryableWriteError'],
44+
],
45+
],
46+
$server,
47+
);
48+
49+
// Step 3 is omitted because we can re-use the same client
50+
51+
// Step 4: Enable succeeded and failed command event monitoring
52+
$subscriber = new class implements CommandSubscriber {
53+
public Server $commandSucceededServer;
54+
public Server $commandFailedServer;
55+
56+
public function commandStarted(CommandStartedEvent $event): void
57+
{
58+
}
59+
60+
public function commandSucceeded(CommandSucceededEvent $event): void
61+
{
62+
$this->commandSucceededServer = $event->getServer();
63+
}
64+
65+
public function commandFailed(CommandFailedEvent $event): void
66+
{
67+
$this->commandFailedServer = $event->getServer();
68+
}
69+
};
70+
71+
$client->addSubscriber($subscriber);
72+
73+
// Step 5: Execute an insert command. Assert that the command succeeded.
74+
$insertOneResult = $client->selectCollection($this->getDatabaseName(), $this->getCollectionName())->insertOne(['x' => 1]);
75+
$this->assertSame(1, $insertOneResult->getInsertedCount());
76+
77+
$client->removeSubscriber($subscriber);
78+
79+
/* Step 6: Assert that exactly one failed command event and one
80+
* succeeded command event occurred. Assert that both events occurred on
81+
* the same mongos. */
82+
$this->assertNotNull($subscriber->commandSucceededServer);
83+
$this->assertNotNull($subscriber->commandFailedServer);
84+
$this->assertEquals($subscriber->commandSucceededServer, $subscriber->commandFailedServer);
85+
86+
// Step 7: The fail point will be disabled during tearDown()
87+
}
88+
}

0 commit comments

Comments
 (0)