X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=tests%2Fphpunit%2Fincludes%2Flibs%2Fobjectcache%2FBagOStuffTest.php;h=b68ffaf8d6ae3f053144e737b41ed2b970b8d514;hb=4d1478c76fc650787ee9d7fb12c7ee62a707e907;hp=b6709a0f29a66abb951f7d2ff3ec3603792dcb9d;hpb=1abed55d47e661b5d9f4c1e894bf0cb5ac00fa5e;p=lhc%2Fweb%2Fwiklou.git diff --git a/tests/phpunit/includes/libs/objectcache/BagOStuffTest.php b/tests/phpunit/includes/libs/objectcache/BagOStuffTest.php index b6709a0f29..b68ffaf8d6 100644 --- a/tests/phpunit/includes/libs/objectcache/BagOStuffTest.php +++ b/tests/phpunit/includes/libs/objectcache/BagOStuffTest.php @@ -26,6 +26,7 @@ class BagOStuffTest extends MediaWikiTestCase { } $this->cache->delete( $this->cache->makeKey( self::TEST_KEY ) ); + $this->cache->delete( $this->cache->makeKey( self::TEST_KEY ) . ':lock' ); } /** @@ -69,7 +70,25 @@ class BagOStuffTest extends MediaWikiTestCase { */ public function testMerge() { $key = $this->cache->makeKey( self::TEST_KEY ); - $callback = function ( BagOStuff $cache, $key, $oldVal ) { + $locks = false; + $checkLockingCallback = function ( BagOStuff $cache, $key, $oldVal ) use ( &$locks ) { + $locks = $cache->get( "$key:lock" ); + + return false; + }; + + $this->cache->merge( $key, $checkLockingCallback, 5 ); + $this->assertFalse( $this->cache->get( $key ) ); + + $calls = 0; + $casRace = false; // emulate a race + $callback = function ( BagOStuff $cache, $key, $oldVal ) use ( &$calls, &$casRace ) { + ++$calls; + if ( $casRace ) { + // Uses CAS instead? + $cache->set( $key, 'conflict', 5 ); + } + return ( $oldVal === false ) ? 'merged' : $oldVal . 'merged'; }; @@ -82,17 +101,45 @@ class BagOStuffTest extends MediaWikiTestCase { $merged = $this->cache->merge( $key, $callback, 5 ); $this->assertTrue( $merged ); $this->assertEquals( 'mergedmerged', $this->cache->get( $key ) ); + + $calls = 0; + if ( $locks ) { + // merge were something else already was merging (e.g. had the lock) + $this->cache->lock( $key ); + $this->assertFalse( + $this->cache->merge( $key, $callback, 5, 1 ), + 'Non-blocking merge (locking)' + ); + $this->cache->unlock( $key ); + $this->assertEquals( 0, $calls ); + } else { + $casRace = true; + $this->assertFalse( + $this->cache->merge( $key, $callback, 5, 1 ), + 'Non-blocking merge (CAS)' + ); + $this->assertEquals( 1, $calls ); + } } /** * @covers BagOStuff::merge * @covers BagOStuff::mergeViaLock + * @dataProvider provideTestMerge_fork */ - public function testMerge_fork() { + public function testMerge_fork( $exists, $winsLocking, $resLocking, $resCAS ) { $key = $this->cache->makeKey( self::TEST_KEY ); - $callback = function ( BagOStuff $cache, $key, $oldVal ) { - return ( $oldVal === false ) ? 'merged' : $oldVal . 'merged'; + $pCallback = function ( BagOStuff $cache, $key, $oldVal ) { + return ( $oldVal === false ) ? 'init-parent' : $oldVal . '-merged-parent'; }; + $cCallback = function ( BagOStuff $cache, $key, $oldVal ) { + return ( $oldVal === false ) ? 'init-child' : $oldVal . '-merged-child'; + }; + + if ( $exists ) { + $this->cache->set( $key, 'x', 5 ); + } + /* * Test concurrent merges by forking this process, if: * - not manually called with --use-bagostuff @@ -106,17 +153,21 @@ class BagOStuffTest extends MediaWikiTestCase { $fork &= !$this->cache instanceof MultiWriteBagOStuff; if ( $fork ) { $pid = null; + $locked = false; // Function to start merge(), run another merge() midway through, then finish - $outerFunc = function ( BagOStuff $cache, $key, $oldVal ) use ( $callback, &$pid ) { + $func = function ( BagOStuff $cache, $key, $cur ) + use ( $pCallback, $cCallback, &$pid, &$locked ) + { $pid = pcntl_fork(); if ( $pid == -1 ) { return false; } elseif ( $pid ) { + $locked = $cache->get( "$key:lock" ); // parent has lock? pcntl_wait( $status ); - return $callback( $cache, $key, $oldVal ); + return $pCallback( $cache, $key, $cur ); } else { - $this->cache->merge( $key, $callback, 0, 1 ); + $this->cache->merge( $key, $cCallback, 0, 1 ); // Bail out of the outer merge() in the child process since it does not // need to attempt to write anything. Success is checked by the parent. parent::tearDown(); // avoid phpunit notices @@ -125,22 +176,34 @@ class BagOStuffTest extends MediaWikiTestCase { }; // attempt a merge - this should fail - $merged = $this->cache->merge( $key, $outerFunc, 0, 1 ); + $merged = $this->cache->merge( $key, $func, 0, 1 ); if ( $pid == -1 ) { return; // can't fork, ignore this test... } - // merge has failed because child process was merging (and we only attempted once) - $this->assertFalse( $merged ); - - // make sure the child's merge is completed and verify - $this->assertEquals( $this->cache->get( $key ), 'mergedmerged' ); + if ( $locked ) { + // merge succeed since child was locked out + $this->assertEquals( $winsLocking, $merged ); + $this->assertEquals( $this->cache->get( $key ), $resLocking ); + } else { + // merge has failed because child process was merging (and we only attempted once) + $this->assertEquals( !$winsLocking, $merged ); + $this->assertEquals( $this->cache->get( $key ), $resCAS ); + } } else { $this->markTestSkipped( 'No pcntl methods available' ); } } + function provideTestMerge_fork() { + return [ + // (already exists, parent wins if locking, result if locking, result if CAS) + [ false, true, 'init-parent', 'init-child' ], + [ true, true, 'x-merged-parent', 'x-merged-child' ] + ]; + } + /** * @covers BagOStuff::changeTTL */ @@ -257,6 +320,34 @@ class BagOStuffTest extends MediaWikiTestCase { $this->cache->delete( $key4 ); } + /** + * @covers BagOStuff::setMulti + * @covers BagOStuff::deleteMulti + */ + public function testSetDeleteMulti() { + $map = [ + $this->cache->makeKey( 'test-1' ) => 'Siberian', + $this->cache->makeKey( 'test-2' ) => [ 'Huskies' ], + $this->cache->makeKey( 'test-3' ) => [ 'are' => 'the' ], + $this->cache->makeKey( 'test-4' ) => (object)[ 'greatest' => 'animal' ], + $this->cache->makeKey( 'test-5' ) => 4, + $this->cache->makeKey( 'test-6' ) => 'ever' + ]; + + $this->cache->setMulti( $map, 5 ); + $this->assertEquals( + $map, + $this->cache->getMulti( array_keys( $map ) ) + ); + + $this->assertTrue( $this->cache->deleteMulti( array_keys( $map ), 5 ) ); + + $this->assertEquals( + [], + $this->cache->getMulti( array_keys( $map ) ) + ); + } + /** * @covers BagOStuff::getScopedLock */ @@ -305,4 +396,21 @@ class BagOStuffTest extends MediaWikiTestCase { DeferredUpdates::doUpdates(); } + + /** + * @covers BagOStuff::lock() + * @covers BagOStuff::unlock() + */ + public function testLocking() { + $key = 'test'; + $this->assertTrue( $this->cache->lock( $key ) ); + $this->assertFalse( $this->cache->lock( $key ) ); + $this->assertTrue( $this->cache->unlock( $key ) ); + + $key2 = 'test2'; + $this->assertTrue( $this->cache->lock( $key2, 5, 5, 'rclass' ) ); + $this->assertTrue( $this->cache->lock( $key2, 5, 5, 'rclass' ) ); + $this->assertTrue( $this->cache->unlock( $key2 ) ); + $this->assertTrue( $this->cache->unlock( $key2 ) ); + } }