|
2 | 2 |
|
3 | 3 | namespace MongoDB\Tests\SpecTests;
|
4 | 4 |
|
5 |
| -use MongoDB\BSON\Int64; |
6 |
| -use MongoDB\BSON\Timestamp; |
7 |
| -use MongoDB\Driver\Command; |
8 |
| -use MongoDB\Driver\Exception\ServerException; |
9 | 5 | use MongoDB\Driver\Server;
|
10 |
| -use stdClass; |
11 | 6 |
|
12 | 7 | use function array_unique;
|
13 |
| -use function basename; |
14 | 8 | use function count;
|
15 |
| -use function dirname; |
16 |
| -use function file_get_contents; |
17 |
| -use function get_object_vars; |
18 |
| -use function glob; |
19 | 9 |
|
20 | 10 | /**
|
21 |
| - * Transactions Convenient API spec tests. |
| 11 | + * Transactions spec prose tests. |
22 | 12 | *
|
23 |
| - * @see https://github.com/mongodb/specifications/tree/master/source/transactions-convenient-api |
| 13 | + * @see https://github.com/mongodb/specifications/blob/master/source/transactions/tests/README.rst#mongos-pinning-prose-tests |
24 | 14 | */
|
25 | 15 | class TransactionsSpecTest extends FunctionalTestCase
|
26 | 16 | {
|
27 |
| - public const INTERRUPTED = 11601; |
28 |
| - |
29 |
| - public function setUp(): void |
30 |
| - { |
31 |
| - parent::setUp(); |
32 |
| - |
33 |
| - static::killAllSessions(); |
34 |
| - |
35 |
| - $this->skipIfTransactionsAreNotSupported(); |
36 |
| - } |
37 |
| - |
38 |
| - public function tearDown(): void |
39 |
| - { |
40 |
| - if ($this->hasFailed()) { |
41 |
| - static::killAllSessions(); |
42 |
| - } |
43 |
| - |
44 |
| - parent::tearDown(); |
45 |
| - } |
46 |
| - |
47 |
| - /** |
48 |
| - * Assert that the expected and actual command documents match. |
49 |
| - * |
50 |
| - * Note: this method may modify the $expected object. |
51 |
| - * |
52 |
| - * @see https://github.com/mongodb/specifications/blob/master/source/transactions/tests/README.rst#command-started-events |
53 |
| - * @param stdClass $expected Expected command document |
54 |
| - * @param stdClass $actual Actual command document |
55 |
| - */ |
56 |
| - public static function assertCommandMatches(stdClass $expected, stdClass $actual): void |
57 |
| - { |
58 |
| - if (isset($expected->getMore) && $expected->getMore === 42) { |
59 |
| - static::assertObjectHasAttribute('getMore', $actual); |
60 |
| - static::assertThat($actual->getMore, static::logicalOr( |
61 |
| - static::isInstanceOf(Int64::class), |
62 |
| - static::isType('integer'), |
63 |
| - )); |
64 |
| - unset($expected->getMore); |
65 |
| - } |
66 |
| - |
67 |
| - if (isset($expected->recoveryToken) && $expected->recoveryToken === 42) { |
68 |
| - static::assertObjectHasAttribute('recoveryToken', $actual); |
69 |
| - static::assertIsObject($actual->recoveryToken); |
70 |
| - unset($expected->recoveryToken); |
71 |
| - } |
72 |
| - |
73 |
| - if (isset($expected->readConcern->afterClusterTime) && $expected->readConcern->afterClusterTime === 42) { |
74 |
| - static::assertObjectHasAttribute('readConcern', $actual); |
75 |
| - static::assertIsObject($actual->readConcern); |
76 |
| - static::assertObjectHasAttribute('afterClusterTime', $actual->readConcern); |
77 |
| - static::assertInstanceOf(Timestamp::class, $actual->readConcern->afterClusterTime); |
78 |
| - unset($expected->readConcern->afterClusterTime); |
79 |
| - |
80 |
| - /* If "afterClusterTime" was the only assertion for "readConcern", |
81 |
| - * unset the field to avoid expecting an empty document later. */ |
82 |
| - if (get_object_vars($expected->readConcern) === []) { |
83 |
| - unset($expected->readConcern); |
84 |
| - } |
85 |
| - } |
86 |
| - |
87 |
| - /* TODO: Determine if forcing a new libmongoc client in Context is |
88 |
| - * preferable to skipping the txnNumber assertion. */ |
89 |
| - //unset($expected['txnNumber']); |
90 |
| - |
91 |
| - static::assertCommandOmittedFields($expected, $actual); |
92 |
| - |
93 |
| - static::assertDocumentsMatch($expected, $actual); |
94 |
| - } |
95 |
| - |
96 |
| - /** @dataProvider provideTransactionsConvenientApiTests */ |
97 |
| - public function testTransactionsConvenientApi(stdClass $test, ?array $runOn, array $data, ?string $databaseName = null, ?string $collectionName = null): void |
98 |
| - { |
99 |
| - $this->runTransactionTest($test, $runOn, $data, $databaseName, $collectionName); |
100 |
| - } |
101 |
| - |
102 |
| - public function provideTransactionsConvenientApiTests(): array |
103 |
| - { |
104 |
| - return $this->provideTests('transactions-convenient-api'); |
105 |
| - } |
106 |
| - |
107 |
| - /** |
108 |
| - * Execute an individual test case from the specification. |
109 |
| - * |
110 |
| - * @param stdClass $test Individual "tests[]" document |
111 |
| - * @param array $runOn Top-level "runOn" array with server requirements |
112 |
| - * @param array $data Top-level "data" array to initialize collection |
113 |
| - * @param string $databaseName Name of database under test |
114 |
| - * @param string $collectionName Name of collection under test |
115 |
| - */ |
116 |
| - private function runTransactionTest(stdClass $test, ?array $runOn, array $data, ?string $databaseName = null, ?string $collectionName = null): void |
117 |
| - { |
118 |
| - if (isset($runOn)) { |
119 |
| - $this->checkServerRequirements($runOn); |
120 |
| - } |
121 |
| - |
122 |
| - if (isset($test->skipReason)) { |
123 |
| - $this->markTestSkipped($test->skipReason); |
124 |
| - } |
125 |
| - |
126 |
| - $databaseName ??= $this->getDatabaseName(); |
127 |
| - $collectionName ??= $this->getCollectionName(); |
128 |
| - |
129 |
| - // Serverless uses a load balancer fronting a single proxy (PHPLIB-757) |
130 |
| - $useMultipleMongoses = $this->isMongos() || ($this->isLoadBalanced() && ! $this->isServerless()) |
131 |
| - ? ($test->useMultipleMongoses ?? false) |
132 |
| - : false; |
133 |
| - |
134 |
| - $context = Context::fromTransactions($test, $databaseName, $collectionName, $useMultipleMongoses); |
135 |
| - $this->setContext($context); |
136 |
| - |
137 |
| - $this->dropTestAndOutcomeCollections(); |
138 |
| - $this->createTestCollection(); |
139 |
| - $this->insertDataFixtures($data); |
140 |
| - $this->preventStaleDbVersionError($test->operations); |
141 |
| - |
142 |
| - if (isset($test->failPoint)) { |
143 |
| - $this->configureFailPoint($test->failPoint); |
144 |
| - } |
145 |
| - |
146 |
| - if (isset($test->expectations)) { |
147 |
| - $commandExpectations = CommandExpectations::fromTransactions($context->getClient(), $test->expectations); |
148 |
| - $commandExpectations->startMonitoring(); |
149 |
| - } |
150 |
| - |
151 |
| - foreach ($test->operations as $operation) { |
152 |
| - Operation::fromTransactions($operation)->assert($this, $context); |
153 |
| - } |
154 |
| - |
155 |
| - $context->session0->endSession(); |
156 |
| - $context->session1->endSession(); |
157 |
| - |
158 |
| - if (isset($commandExpectations)) { |
159 |
| - $commandExpectations->stopMonitoring(); |
160 |
| - $commandExpectations->assert($this, $context); |
161 |
| - } |
162 |
| - |
163 |
| - if (isset($test->outcome->collection->data)) { |
164 |
| - $this->assertOutcomeCollectionData($test->outcome->collection->data); |
165 |
| - } |
166 |
| - } |
167 |
| - |
168 |
| - private function provideTests(string $dir): array |
169 |
| - { |
170 |
| - $testArgs = []; |
171 |
| - |
172 |
| - foreach (glob(__DIR__ . '/' . $dir . '/*.json') as $filename) { |
173 |
| - $json = $this->decodeJson(file_get_contents($filename)); |
174 |
| - $group = basename(dirname($filename)) . '/' . basename($filename, '.json'); |
175 |
| - $runOn = $json->runOn ?? null; |
176 |
| - $data = $json->data ?? []; |
177 |
| - // phpcs:disable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps |
178 |
| - $databaseName = $json->database_name ?? null; |
179 |
| - $collectionName = $json->collection_name ?? null; |
180 |
| - // phpcs:enable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps |
181 |
| - |
182 |
| - foreach ($json->tests as $test) { |
183 |
| - $name = $group . ': ' . $test->description; |
184 |
| - $testArgs[$name] = [$test, $runOn, $data, $databaseName, $collectionName]; |
185 |
| - } |
186 |
| - } |
187 |
| - |
188 |
| - return $testArgs; |
189 |
| - } |
190 |
| - |
191 | 17 | /**
|
192 | 18 | * Prose test 1: Test that starting a new transaction on a pinned
|
193 | 19 | * ClientSession unpins the session and normal server selection is performed
|
@@ -265,73 +91,4 @@ public function testRunningNonTransactionOperationOnPinnedSessionUnpinsSession()
|
265 | 91 |
|
266 | 92 | $session->endSession();
|
267 | 93 | }
|
268 |
| - |
269 |
| - /** |
270 |
| - * Create the collection, since it cannot be created within a transaction. |
271 |
| - */ |
272 |
| - protected function createTestCollection(): void |
273 |
| - { |
274 |
| - $context = $this->getContext(); |
275 |
| - |
276 |
| - $database = $context->getDatabase(); |
277 |
| - $database->createCollection($context->collectionName, $context->defaultWriteOptions); |
278 |
| - } |
279 |
| - |
280 |
| - /** |
281 |
| - * Kill all sessions on the cluster. |
282 |
| - * |
283 |
| - * This will clean up any open transactions that may remain from a |
284 |
| - * previously failed test. For sharded clusters, this command will be run |
285 |
| - * on all mongos nodes. |
286 |
| - */ |
287 |
| - private static function killAllSessions(): void |
288 |
| - { |
289 |
| - // killAllSessions is not supported on serverless, see CLOUDP-84298 |
290 |
| - if (static::isServerless()) { |
291 |
| - return; |
292 |
| - } |
293 |
| - |
294 |
| - $manager = static::createTestManager(); |
295 |
| - $primary = $manager->selectServer(); |
296 |
| - |
297 |
| - $servers = $primary->getType() === Server::TYPE_MONGOS |
298 |
| - ? $manager->getServers() |
299 |
| - : [$primary]; |
300 |
| - |
301 |
| - foreach ($servers as $server) { |
302 |
| - try { |
303 |
| - // Skip servers that do not support sessions |
304 |
| - if (! isset($server->getInfo()['logicalSessionTimeoutMinutes'])) { |
305 |
| - continue; |
306 |
| - } |
307 |
| - |
308 |
| - $server->executeCommand('admin', new Command(['killAllSessions' => []])); |
309 |
| - } catch (ServerException $e) { |
310 |
| - // Interrupted error is safe to ignore (see: SERVER-38335) |
311 |
| - if ($e->getCode() != self::INTERRUPTED) { |
312 |
| - throw $e; |
313 |
| - } |
314 |
| - } |
315 |
| - } |
316 |
| - } |
317 |
| - |
318 |
| - /** |
319 |
| - * Work around potential error executing distinct on sharded clusters. |
320 |
| - * |
321 |
| - * @see https://github.com/mongodb/specifications/tree/master/source/transactions/tests#why-do-tests-that-run-distinct-sometimes-fail-with-staledbversion |
322 |
| - */ |
323 |
| - private function preventStaleDbVersionError(array $operations): void |
324 |
| - { |
325 |
| - if (! $this->isShardedCluster()) { |
326 |
| - return; |
327 |
| - } |
328 |
| - |
329 |
| - foreach ($operations as $operation) { |
330 |
| - if ($operation->name === 'distinct') { |
331 |
| - $this->getContext()->getCollection()->distinct('foo'); |
332 |
| - |
333 |
| - return; |
334 |
| - } |
335 |
| - } |
336 |
| - } |
337 | 94 | }
|
0 commit comments