Localisation updates from https://translatewiki.net.
[mediawiki.git] / tests / phpunit / includes / deferred / DeferredUpdatesTest.php
blob6faedf679a89f7c56ff5a94caa538f27cb20dd51
1 <?php
3 use MediaWiki\Deferred\DeferrableUpdate;
4 use MediaWiki\Deferred\DeferredUpdates;
5 use MediaWiki\Deferred\MergeableUpdate;
6 use MediaWiki\Deferred\MWCallableUpdate;
7 use MediaWiki\Deferred\TransactionRoundDefiningUpdate;
9 /**
10 * @group Database
11 * @covers \MediaWiki\Deferred\DeferredUpdates
12 * @covers \MediaWiki\Deferred\DeferredUpdatesScopeStack
13 * @covers \MediaWiki\Deferred\DeferredUpdatesScope
15 class DeferredUpdatesTest extends MediaWikiIntegrationTestCase {
17 public function testAddAndRun() {
18 $update = $this->getMockBuilder( DeferrableUpdate::class )
19 ->onlyMethods( [ 'doUpdate' ] )->getMock();
20 $update->expects( $this->once() )->method( 'doUpdate' );
22 DeferredUpdates::addUpdate( $update );
23 DeferredUpdates::doUpdates();
26 public function testAddMergeable() {
27 $cleanup = DeferredUpdates::preventOpportunisticUpdates();
29 $update1 = $this->getMockBuilder( MergeableUpdate::class )
30 ->onlyMethods( [ 'merge', 'doUpdate' ] )->getMock();
31 $update1->expects( $this->once() )->method( 'merge' );
32 $update1->expects( $this->never() )->method( 'doUpdate' );
34 $update2 = $this->getMockBuilder( MergeableUpdate::class )
35 ->onlyMethods( [ 'merge', 'doUpdate' ] )->getMock();
36 $update2->expects( $this->never() )->method( 'merge' );
37 $update2->expects( $this->never() )->method( 'doUpdate' );
39 DeferredUpdates::addUpdate( $update1 );
40 DeferredUpdates::addUpdate( $update2 );
43 public function testAddCallableUpdate() {
44 $ran = 0;
45 DeferredUpdates::addCallableUpdate( static function () use ( &$ran ) {
46 $ran++;
47 } );
48 // Opportunistic updates should be enabled, so the updates should have already executed
49 $this->assertSame( 0, DeferredUpdates::pendingUpdatesCount() );
51 $this->assertSame( 1, $ran, 'Update ran' );
54 public function testGetPendingUpdates() {
55 $cleanup = DeferredUpdates::preventOpportunisticUpdates();
57 $pre = DeferredUpdates::PRESEND;
58 $post = DeferredUpdates::POSTSEND;
59 $all = DeferredUpdates::ALL;
61 $update = $this->createMock( DeferrableUpdate::class );
62 $update->expects( $this->never() )
63 ->method( 'doUpdate' );
65 DeferredUpdates::addUpdate( $update, $pre );
66 $this->assertCount( 1, DeferredUpdates::getPendingUpdates( $pre ) );
67 $this->assertSame( [], DeferredUpdates::getPendingUpdates( $post ) );
68 $this->assertCount( 1, DeferredUpdates::getPendingUpdates( $all ) );
69 $this->assertCount( 1, DeferredUpdates::getPendingUpdates() );
70 DeferredUpdates::clearPendingUpdates();
71 $this->assertSame( [], DeferredUpdates::getPendingUpdates() );
73 DeferredUpdates::addUpdate( $update, $post );
74 $this->assertSame( [], DeferredUpdates::getPendingUpdates( $pre ) );
75 $this->assertCount( 1, DeferredUpdates::getPendingUpdates( $post ) );
76 $this->assertCount( 1, DeferredUpdates::getPendingUpdates( $all ) );
77 $this->assertCount( 1, DeferredUpdates::getPendingUpdates() );
78 DeferredUpdates::clearPendingUpdates();
79 $this->assertSame( [], DeferredUpdates::getPendingUpdates() );
82 public function testDoUpdatesWeb() {
83 $cleanup = DeferredUpdates::preventOpportunisticUpdates();
85 $updates = [
86 '1' => "deferred update 1;\n",
87 '2' => "deferred update 2;\n",
88 '2-1' => "deferred update 1 within deferred update 2;\n",
89 '2-2' => "deferred update 2 within deferred update 2;\n",
90 '3' => "deferred update 3;\n",
91 '3-1' => "deferred update 1 within deferred update 3;\n",
92 '3-2' => "deferred update 2 within deferred update 3;\n",
93 '3-1-1' => "deferred update 1 within deferred update 1 within deferred update 3;\n",
94 '3-2-1' => "deferred update 1 within deferred update 2 with deferred update 3;\n",
96 DeferredUpdates::addCallableUpdate(
97 static function () use ( $updates ) {
98 echo $updates['1'];
101 DeferredUpdates::addCallableUpdate(
102 static function () use ( $updates ) {
103 echo $updates['2'];
104 DeferredUpdates::addCallableUpdate(
105 static function () use ( $updates ) {
106 echo $updates['2-1'];
109 DeferredUpdates::addCallableUpdate(
110 static function () use ( $updates ) {
111 echo $updates['2-2'];
116 DeferredUpdates::addCallableUpdate(
117 static function () use ( $updates ) {
118 echo $updates['3'];
119 DeferredUpdates::addCallableUpdate(
120 static function () use ( $updates ) {
121 echo $updates['3-1'];
122 DeferredUpdates::addCallableUpdate(
123 static function () use ( $updates ) {
124 echo $updates['3-1-1'];
129 DeferredUpdates::addCallableUpdate(
130 static function () use ( $updates ) {
131 echo $updates['3-2'];
132 DeferredUpdates::addCallableUpdate(
133 static function () use ( $updates ) {
134 echo $updates['3-2-1'];
142 $this->assertEquals( 3, DeferredUpdates::pendingUpdatesCount() );
144 $this->expectOutputString( implode( '', $updates ) );
146 DeferredUpdates::doUpdates();
148 $x = null;
149 $y = null;
150 DeferredUpdates::addCallableUpdate(
151 static function () use ( &$x ) {
152 $x = 'Sherity';
154 DeferredUpdates::PRESEND
156 DeferredUpdates::addCallableUpdate(
157 static function () use ( &$y ) {
158 $y = 'Marychu';
160 DeferredUpdates::POSTSEND
163 $this->assertNull( $x, "Update not run yet" );
164 $this->assertNull( $y, "Update not run yet" );
166 DeferredUpdates::doUpdates( DeferredUpdates::PRESEND );
167 $this->assertEquals( "Sherity", $x, "PRESEND update ran" );
168 $this->assertNull( $y, "POSTSEND update not run yet" );
170 DeferredUpdates::doUpdates( DeferredUpdates::POSTSEND );
171 $this->assertEquals( "Marychu", $y, "POSTSEND update ran" );
174 public function testDoUpdatesCLI() {
175 $updates = [
176 '1' => "deferred update 1;\n",
177 '2' => "deferred update 2;\n",
178 '2-1' => "deferred update 1 within deferred update 2;\n",
179 '2-2' => "deferred update 2 within deferred update 2;\n",
180 '3' => "deferred update 3;\n",
181 '3-1' => "deferred update 1 within deferred update 3;\n",
182 '3-2' => "deferred update 2 within deferred update 3;\n",
183 '3-1-1' => "deferred update 1 within deferred update 1 within deferred update 3;\n",
184 '3-2-1' => "deferred update 1 within deferred update 2 with deferred update 3;\n",
187 // clear anything
188 $lbFactory = $this->getServiceContainer()->getDBLoadBalancerFactory();
189 $lbFactory->commitPrimaryChanges( __METHOD__ );
191 DeferredUpdates::addCallableUpdate(
192 static function () use ( $updates ) {
193 echo $updates['1'];
196 DeferredUpdates::addCallableUpdate(
197 static function () use ( $updates ) {
198 echo $updates['2'];
199 DeferredUpdates::addCallableUpdate(
200 static function () use ( $updates ) {
201 echo $updates['2-1'];
204 DeferredUpdates::addCallableUpdate(
205 static function () use ( $updates ) {
206 echo $updates['2-2'];
211 DeferredUpdates::addCallableUpdate(
212 static function () use ( $updates ) {
213 echo $updates['3'];
214 DeferredUpdates::addCallableUpdate(
215 static function () use ( $updates ) {
216 echo $updates['3-1'];
217 DeferredUpdates::addCallableUpdate(
218 static function () use ( $updates ) {
219 echo $updates['3-1-1'];
224 DeferredUpdates::addCallableUpdate(
225 static function () use ( $updates ) {
226 echo $updates['3-2'];
227 DeferredUpdates::addCallableUpdate(
228 static function () use ( $updates ) {
229 echo $updates['3-2-1'];
237 $this->expectOutputString( implode( '', $updates ) );
239 // Opportunistic updates should be enabled, so the updates should have already executed
240 $this->assertSame( 0, DeferredUpdates::pendingUpdatesCount() );
243 public function testPresendAddOnPostsendRun() {
244 $x = false;
245 $y = false;
246 // clear anything
247 $lbFactory = $this->getServiceContainer()->getDBLoadBalancerFactory();
248 $lbFactory->commitPrimaryChanges( __METHOD__ );
250 DeferredUpdates::addCallableUpdate(
251 static function () use ( &$x, &$y ) {
252 $x = true;
253 DeferredUpdates::addCallableUpdate(
254 static function () use ( &$y ) {
255 $y = true;
257 DeferredUpdates::PRESEND
260 DeferredUpdates::POSTSEND
263 // Opportunistic updates should be enabled, so the updates should have already executed
264 $this->assertSame( 0, DeferredUpdates::pendingUpdatesCount() );
266 $this->assertTrue( $x, "Outer POSTSEND update ran" );
267 $this->assertTrue( $y, "Nested PRESEND update ran" );
270 public function testRunUpdateTransactionScope() {
271 $cleanup = DeferredUpdates::preventOpportunisticUpdates();
273 $lbFactory = $this->getServiceContainer()->getDBLoadBalancerFactory();
274 $this->assertFalse( $lbFactory->hasTransactionRound(), 'Initial state' );
276 $ran = 0;
277 DeferredUpdates::addCallableUpdate( function () use ( &$ran, $lbFactory ) {
278 $ran++;
279 $this->assertTrue( $lbFactory->hasTransactionRound(), 'Has transaction' );
280 } );
281 DeferredUpdates::doUpdates();
283 $this->assertSame( 1, $ran, 'Update ran' );
284 $this->assertFalse( $lbFactory->hasTransactionRound(), 'Final state' );
288 * @covers \MediaWiki\Deferred\TransactionRoundDefiningUpdate
290 public function testRunOuterScopeUpdate() {
291 $cleanup = DeferredUpdates::preventOpportunisticUpdates();
293 $lbFactory = $this->getServiceContainer()->getDBLoadBalancerFactory();
294 $this->assertFalse( $lbFactory->hasTransactionRound(), 'Initial state' );
296 $ran = 0;
297 DeferredUpdates::addUpdate( new TransactionRoundDefiningUpdate(
298 function () use ( &$ran, $lbFactory ) {
299 $ran++;
300 $this->assertFalse( $lbFactory->hasTransactionRound(), 'No transaction' );
303 DeferredUpdates::doUpdates();
305 $this->assertSame( 1, $ran, 'Update ran' );
308 public function testTryOpportunisticExecute() {
309 $calls = [];
310 $callback1 = static function () use ( &$calls ) {
311 $calls[] = 1;
313 $callback2 = static function () use ( &$calls ) {
314 $calls[] = 2;
317 $lbFactory = $this->getServiceContainer()->getDBLoadBalancerFactory();
318 $lbFactory->beginPrimaryChanges( __METHOD__ );
320 DeferredUpdates::addCallableUpdate( $callback1 );
321 $this->assertEquals( [], $calls );
323 DeferredUpdates::tryOpportunisticExecute();
324 $this->assertEquals( [], $calls );
326 $dbw = $this->getDb();
327 $dbw->onTransactionCommitOrIdle( function () use ( &$calls, $callback2 ) {
328 DeferredUpdates::addCallableUpdate( $callback2 );
329 $this->assertEquals( [], $calls );
330 $calls[] = 'oti';
331 } );
332 $this->assertSame( 1, $dbw->trxLevel() );
333 $this->assertEquals( [], $calls );
335 $lbFactory->commitPrimaryChanges( __METHOD__ );
337 $this->assertEquals( [ 'oti' ], $calls );
339 DeferredUpdates::tryOpportunisticExecute();
340 $this->assertEquals( [ 'oti', 1, 2 ], $calls );
344 * @covers \MediaWiki\Deferred\MWCallableUpdate
346 public function testCallbackUpdateRounds() {
347 $lbFactory = $this->getServiceContainer()->getDBLoadBalancerFactory();
349 $fname = __METHOD__;
350 $called = false;
351 // This confirms that DeferredUpdates sets the transaction owner in LBFactory
352 // based on MWCallableUpdate::getOrigin, thus allowing the callback to control
353 // over the transaction and e.g. perform a commit.
354 DeferredUpdates::attemptUpdate(
355 new MWCallableUpdate(
356 static function () use ( $lbFactory, $fname, &$called ) {
357 $lbFactory->commitPrimaryChanges( $fname );
358 $called = true;
360 $fname
364 $this->assertTrue( $called, "Callback ran" );
367 public function testNestedExecution() {
368 $cleanup = DeferredUpdates::preventOpportunisticUpdates();
370 $res = null;
371 $resSub = null;
372 $resSubSub = null;
373 $resA = null;
375 DeferredUpdates::clearPendingUpdates();
377 $this->assertSame( 0, DeferredUpdates::pendingUpdatesCount() );
378 $this->assertSame( 0, DeferredUpdates::getRecursiveExecutionStackDepth() );
380 // T249069: TransactionRoundDefiningUpdate => JobRunner => DeferredUpdates::doUpdates()
381 DeferredUpdates::addUpdate( new TransactionRoundDefiningUpdate(
382 function () use ( &$res, &$resSub, &$resSubSub, &$resA ) {
383 $res = 1;
385 $this->assertSame( 0, DeferredUpdates::pendingUpdatesCount() );
386 $this->assertSame( 1, DeferredUpdates::getRecursiveExecutionStackDepth() );
388 // Add update to subqueue of in-progress top-queue job
389 DeferredUpdates::addCallableUpdate( function () use ( &$resSub, &$resSubSub ) {
390 $resSub = 'a';
392 $this->assertSame( 0, DeferredUpdates::pendingUpdatesCount() );
393 $this->assertSame( 2, DeferredUpdates::getRecursiveExecutionStackDepth() );
395 // Add update to subqueue of in-progress top-queue job (not recursive)
396 DeferredUpdates::addCallableUpdate( static function () use ( &$resSubSub ) {
397 $resSubSub = 'b';
398 } );
400 $this->assertSame( 1, DeferredUpdates::pendingUpdatesCount() );
401 } );
403 $this->assertSame( 1, DeferredUpdates::pendingUpdatesCount() );
404 $this->assertSame( 1, DeferredUpdates::getRecursiveExecutionStackDepth() );
406 if ( $resSub === null && $resA === null && $resSubSub === null ) {
407 $res = 418;
410 DeferredUpdates::doUpdates();
412 ) );
414 $this->assertSame( 1, DeferredUpdates::pendingUpdatesCount() );
415 $this->assertSame( 0, DeferredUpdates::getRecursiveExecutionStackDepth() );
417 DeferredUpdates::addCallableUpdate( static function () use ( &$resA ) {
418 $resA = 93;
419 } );
421 $this->assertSame( 2, DeferredUpdates::pendingUpdatesCount() );
422 $this->assertSame( 0, DeferredUpdates::getRecursiveExecutionStackDepth() );
424 $this->assertNull( $resA );
425 $this->assertNull( $res );
426 $this->assertNull( $resSub );
427 $this->assertNull( $resSubSub );
429 DeferredUpdates::doUpdates();
431 $this->assertSame( 0, DeferredUpdates::pendingUpdatesCount() );
432 $this->assertSame( 0, DeferredUpdates::getRecursiveExecutionStackDepth() );
433 $this->assertSame( 418, $res );
434 $this->assertSame( 'a', $resSub );
435 $this->assertSame( 'b', $resSubSub );
436 $this->assertSame( 93, $resA );