Skip to content

Commit f236312

Browse files
authored
PHPLIB-935: Propagate Original Error for Write Errors Labeled NoWritesPerformed (#1034)
1 parent fd37f1b commit f236312

File tree

1 file changed

+91
-0
lines changed

1 file changed

+91
-0
lines changed

tests/SpecTests/RetryableWritesSpecTest.php

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@
22

33
namespace MongoDB\Tests\SpecTests;
44

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;
510
use stdClass;
611

712
use function basename;
813
use function file_get_contents;
914
use function glob;
15+
use function version_compare;
1016

1117
/**
1218
* Retryable writes spec tests.
@@ -16,6 +22,9 @@
1622
*/
1723
class RetryableWritesSpecTest extends FunctionalTestCase
1824
{
25+
public const NOT_PRIMARY = 10107;
26+
public const SHUTDOWN_IN_PROGRESS = 91;
27+
1928
/**
2029
* Execute an individual test case from the specification.
2130
*
@@ -71,4 +80,86 @@ public function provideTests()
7180

7281
return $testArgs;
7382
}
83+
84+
/**
85+
* Prose test 1: when encountering a NoWritesPerformed error after an error with a RetryableWriteError label
86+
*/
87+
public function testNoWritesPerformedErrorReturnsOriginalError(): void
88+
{
89+
if (! $this->isReplicaSet()) {
90+
$this->markTestSkipped('Test only applies to replica sets');
91+
}
92+
93+
if (version_compare($this->getServerVersion(), '4.4.0', '<')) {
94+
$this->markTestSkipped('NoWritesPerformed error label is only supported on MongoDB 4.4+');
95+
}
96+
97+
$client = self::createTestClient(null, ['retryWrites' => true]);
98+
99+
// Step 2: Configure a fail point with error code 91
100+
$this->configureFailPoint([
101+
'configureFailPoint' => 'failCommand',
102+
'mode' => ['times' => 1],
103+
'data' => [
104+
'writeConcernError' => [
105+
'code' => self::SHUTDOWN_IN_PROGRESS,
106+
'errorLabels' => ['RetryableWriteError'],
107+
],
108+
'failCommands' => ['insert'],
109+
],
110+
]);
111+
112+
$subscriber = new class ($this) implements CommandSubscriber {
113+
private $testCase;
114+
115+
public function __construct(FunctionalTestCase $testCase)
116+
{
117+
$this->testCase = $testCase;
118+
}
119+
120+
public function commandStarted(CommandStartedEvent $event): void
121+
{
122+
}
123+
124+
public function commandSucceeded(CommandSucceededEvent $event): void
125+
{
126+
if ($event->getCommandName() === 'insert') {
127+
// Step 3: Configure a fail point with code 10107
128+
$this->testCase->configureFailPoint([
129+
'configureFailPoint' => 'failCommand',
130+
'mode' => ['times' => 1],
131+
'data' => [
132+
'errorCode' => RetryableWritesSpecTest::NOT_PRIMARY,
133+
'errorLabels' => ['RetryableWriteError', 'NoWritesPerformed'],
134+
'failCommands' => ['insert'],
135+
],
136+
]);
137+
}
138+
}
139+
140+
public function commandFailed(CommandFailedEvent $event): void
141+
{
142+
}
143+
};
144+
145+
$client->getManager()->addSubscriber($subscriber);
146+
147+
// Step 4: Run insertOne
148+
try {
149+
$client->selectCollection('db', 'retryable_writes')->insertOne(['write' => 1]);
150+
} catch (BulkWriteException $e) {
151+
$writeConcernError = $e->getWriteResult()->getWriteConcernError();
152+
$this->assertNotNull($writeConcernError);
153+
154+
// Assert that the write concern error is from the first failpoint
155+
$this->assertSame(self::SHUTDOWN_IN_PROGRESS, $writeConcernError->getCode());
156+
}
157+
158+
// Step 5: Disable the fail point
159+
$client->getManager()->removeSubscriber($subscriber);
160+
$this->configureFailPoint([
161+
'configureFailPoint' => 'failCommand',
162+
'mode' => 'off',
163+
]);
164+
}
74165
}

0 commit comments

Comments
 (0)