Skip to content

Commit 8a484d9

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 8a484d9

File tree

4 files changed

+202
-19
lines changed

4 files changed

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

0 commit comments

Comments
 (0)