X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=tests%2Fphpunit%2Fincludes%2Flibs%2Fobjectcache%2FWANObjectCacheTest.php;h=9e4a5d7d6271b25f465d18bf28b904a18cea91d8;hb=82bd6b026ef6ecb7f2fe15acaa40c608680dfff1;hp=017d745e49ed54bd91404ede2777481d9fd5f819;hpb=b6a1f3bc774d043c69e9ed2875210049cdda9d68;p=lhc%2Fweb%2Fwiklou.git diff --git a/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php b/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php index 017d745e49..7c4c9bf944 100644 --- a/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php +++ b/tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php @@ -11,7 +11,7 @@ use Wikimedia\TestingAccessWrapper; * @covers WANObjectCache::getWarmupKeyMisses * @covers WANObjectCache::prefixCacheKeys * @covers WANObjectCache::getProcessCache - * @covers WANObjectCache::getNonProcessCachedKeys + * @covers WANObjectCache::getNonProcessCachedMultiKeys * @covers WANObjectCache::getRawKeysForWarmup * @covers WANObjectCache::getInterimValue * @covers WANObjectCache::setInterimValue @@ -47,18 +47,26 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { * @param int $ttl */ public function testSetAndGet( $value, $ttl ) { + $cache = $this->cache; + $curTTL = null; $asOf = null; - $key = $this->cache->makeKey( 'x', wfRandomString() ); + $key = $cache->makeKey( 'x', wfRandomString() ); - $this->cache->get( $key, $curTTL, [], $asOf ); + $cache->get( $key, $curTTL, [], $asOf ); $this->assertNull( $curTTL, "Current TTL is null" ); $this->assertNull( $asOf, "Current as-of-time is infinite" ); $t = microtime( true ); - $this->cache->set( $key, $value, $ttl ); - $this->assertEquals( $value, $this->cache->get( $key, $curTTL, [], $asOf ) ); + $cache->set( $key, $value, $cache::TTL_UNCACHEABLE ); + $cache->get( $key, $curTTL, [], $asOf ); + $this->assertNull( $curTTL, "Current TTL is null (TTL_UNCACHEABLE)" ); + $this->assertNull( $asOf, "Current as-of-time is infinite (TTL_UNCACHEABLE)" ); + + $cache->set( $key, $value, $ttl ); + + $this->assertEquals( $value, $cache->get( $key, $curTTL, [], $asOf ) ); if ( is_infinite( $ttl ) || $ttl == 0 ) { $this->assertTrue( is_infinite( $curTTL ), "Current TTL is infinite" ); } else { @@ -120,79 +128,153 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $this->assertFalse( $this->cache->get( $key ), "Stale set() value ignored" ); } - public function testProcessCache() { + /** + * @covers WANObjectCache::getWithSetCallback + */ + public function testProcessCacheLruAndDelete() { + $cache = $this->cache; $mockWallClock = 1549343530.2053; - $this->cache->setMockTime( $mockWallClock ); + $cache->setMockTime( $mockWallClock ); $hit = 0; - $callback = function () use ( &$hit ) { + $fn = function () use ( &$hit ) { ++$hit; return 42; }; - $keys = [ wfRandomString(), wfRandomString(), wfRandomString() ]; - $groups = [ 'thiscache:1', 'thatcache:1', 'somecache:1' ]; + $keysA = [ wfRandomString(), wfRandomString(), wfRandomString() ]; + $keysB = [ wfRandomString(), wfRandomString(), wfRandomString() ]; + $pcg = [ 'thiscache:1', 'thatcache:1', 'somecache:1' ]; - foreach ( $keys as $i => $key ) { - $this->cache->getWithSetCallback( - $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] ); + foreach ( $keysA as $i => $key ) { + $cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5, 'pcGroup' => $pcg[$i] ] ); } - $this->assertEquals( 3, $hit ); + $this->assertEquals( 3, $hit, "Values not cached yet" ); - foreach ( $keys as $i => $key ) { - $this->cache->getWithSetCallback( - $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] ); + foreach ( $keysA as $i => $key ) { + // Should not evict from process cache + $cache->delete( $key ); + $cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5, 'pcGroup' => $pcg[$i] ] ); } - $this->assertEquals( 3, $hit, "Values cached" ); + $this->assertEquals( 3, $hit, "Values cached; not cleared by delete()" ); - foreach ( $keys as $i => $key ) { - $this->cache->getWithSetCallback( - "$key-2", 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] ); + foreach ( $keysB as $i => $key ) { + $cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5, 'pcGroup' => $pcg[$i] ] ); } - $this->assertEquals( 6, $hit ); + $this->assertEquals( 6, $hit, "New values not cached yet" ); - foreach ( $keys as $i => $key ) { - $this->cache->getWithSetCallback( - "$key-2", 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] ); + foreach ( $keysB as $i => $key ) { + $cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5, 'pcGroup' => $pcg[$i] ] ); } $this->assertEquals( 6, $hit, "New values cached" ); - foreach ( $keys as $i => $key ) { - // Should evict from process cache - $this->cache->delete( $key ); + foreach ( $keysA as $i => $key ) { + $cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5, 'pcGroup' => $pcg[$i] ] ); + } + $this->assertEquals( 9, $hit, "Prior values evicted by new values" ); + } + + /** + * @covers WANObjectCache::getWithSetCallback + */ + public function testProcessCacheInterimKeys() { + $cache = $this->cache; + $mockWallClock = 1549343530.2053; + $cache->setMockTime( $mockWallClock ); + + $hit = 0; + $fn = function () use ( &$hit ) { + ++$hit; + return 42; + }; + $keysA = [ wfRandomString(), wfRandomString(), wfRandomString() ]; + $pcg = [ 'thiscache:1', 'thatcache:1', 'somecache:1' ]; + + foreach ( $keysA as $i => $key ) { + $cache->delete( $key ); // tombstone key $mockWallClock += 0.001; // cached values will be newer than tombstone - // Get into cache (specific process cache group) - $this->cache->getWithSetCallback( - $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] ); + // Get into process cache (specific group) and interim cache + $cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5, 'pcGroup' => $pcg[$i] ] ); } - $this->assertEquals( 9, $hit, "Values evicted by delete()" ); + $this->assertEquals( 3, $hit ); - // Get into cache (default process cache group) - $key = reset( $keys ); - $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] ); - $this->assertEquals( 9, $hit, "Value recently interim-cached" ); + // Get into process cache (default group) + $key = reset( $keysA ); + $cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5 ] ); + $this->assertEquals( 3, $hit, "Value recently interim-cached" ); $mockWallClock += 0.2; // interim key not brand new - $this->cache->clearProcessCache(); - $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] ); - $this->assertEquals( 10, $hit, "Value calculated (interim key not recent and reset)" ); - $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] ); - $this->assertEquals( 10, $hit, "Value process cached" ); + $cache->clearProcessCache(); + $cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5 ] ); + $this->assertEquals( 4, $hit, "Value calculated (interim key not recent and reset)" ); + $cache->getWithSetCallback( $key, 100, $fn, [ 'pcTTL' => 5 ] ); + $this->assertEquals( 4, $hit, "Value process cached" ); + } - $mockWallClock += 0.2; // interim key not brand new - $outerCallback = function () use ( &$callback, $key ) { - $v = $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] ); + /** + * @covers WANObjectCache::getWithSetCallback + */ + public function testProcessCacheNesting() { + $cache = $this->cache; + $mockWallClock = 1549343530.2053; + $cache->setMockTime( $mockWallClock ); + + $keyOuter = "outer-" . wfRandomString(); + $keyInner = "inner-" . wfRandomString(); + + $innerHit = 0; + $innerFn = function () use ( &$innerHit ) { + ++$innerHit; + return 42; + }; + + $outerHit = 0; + $outerFn = function () use ( $keyInner, $innerFn, $cache, &$outerHit ) { + ++$outerHit; + $v = $cache->getWithSetCallback( $keyInner, 100, $innerFn, [ 'pcTTL' => 5 ] ); return 43 + $v; }; - // Outer key misses and refuses inner key process cache value - $this->cache->getWithSetCallback( "$key-miss-outer", 100, $outerCallback ); - $this->assertEquals( 11, $hit, "Nested callback value process cache skipped" ); + + $cache->getWithSetCallback( $keyInner, 100, $innerFn, [ 'pcTTL' => 5 ] ); + $cache->getWithSetCallback( $keyInner, 100, $innerFn, [ 'pcTTL' => 5 ] ); + + $this->assertEquals( 1, $innerHit, "Inner callback value cached" ); + $cache->delete( $keyInner, $cache::HOLDOFF_NONE ); + $mockWallClock += 1; + + $cache->getWithSetCallback( $keyInner, 100, $innerFn, [ 'pcTTL' => 5 ] ); + $this->assertEquals( 1, $innerHit, "Inner callback process cached" ); + + // Outer key misses and inner key process cache value is refused + $cache->getWithSetCallback( $keyOuter, 100, $outerFn ); + + $this->assertEquals( 1, $outerHit, "Outer callback value not yet cached" ); + $this->assertEquals( 2, $innerHit, "Inner callback value process cache skipped" ); + + $cache->getWithSetCallback( $keyOuter, 100, $outerFn ); + + $this->assertEquals( 1, $outerHit, "Outer callback value cached" ); + + $cache->delete( $keyInner, $cache::HOLDOFF_NONE ); + $cache->delete( $keyOuter, $cache::HOLDOFF_NONE ); + $mockWallClock += 1; + $cache->clearProcessCache(); + $cache->getWithSetCallback( $keyOuter, 100, $outerFn ); + + $this->assertEquals( 2, $outerHit, "Outer callback value not yet cached" ); + $this->assertEquals( 3, $innerHit, "Inner callback value not yet cached" ); + + $cache->delete( $keyInner, $cache::HOLDOFF_NONE ); + $mockWallClock += 1; + $cache->getWithSetCallback( $keyInner, 100, $innerFn, [ 'pcTTL' => 5 ] ); + + $this->assertEquals( 3, $innerHit, "Inner callback value process cached" ); } /** * @dataProvider getWithSetCallback_provider * @covers WANObjectCache::getWithSetCallback() - * @covers WANObjectCache::doGetWithSetCallback() + * @covers WANObjectCache::fetchOrRegenerate() * @param array $extOpts * @param bool $versioned */ @@ -236,7 +318,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $v = $cache->getWithSetCallback( $key, 30, $func, [ 'lowTTL' => 0, 'lockTSE' => 5 ] + $extOpts ); $this->assertEquals( $value, $v, "Value returned" ); - $this->assertEquals( 0, $wasSet, "Value not regenerated" ); + $this->assertSame( 0, $wasSet, "Value not regenerated" ); $mockWallClock += 1; @@ -268,11 +350,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $curTTL = null; $v = $cache->get( $key, $curTTL, [ $cKey1, $cKey2 ] ); - if ( $versioned ) { - $this->assertEquals( $value, $v[$cache::VFLD_DATA], "Value returned" ); - } else { - $this->assertEquals( $value, $v, "Value returned" ); - } + $this->assertEquals( $value, $v, "Value returned" ); $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" ); $wasSet = 0; @@ -378,7 +456,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { /** * @dataProvider getWithSetCallback_provider * @covers WANObjectCache::getWithSetCallback() - * @covers WANObjectCache::doGetWithSetCallback() + * @covers WANObjectCache::fetchOrRegenerate() * @param array $extOpts * @param bool $versioned */ @@ -506,7 +584,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $asycList[0](); // run the refresh callback $asycList = []; $this->assertEquals( 2, $wasSet, "Value calculated at later time" ); - $this->assertEquals( 0, count( $asycList ), "No deferred refreshes added." ); + $this->assertSame( 0, count( $asycList ), "No deferred refreshes added." ); $v = $cache->getWithSetCallback( $key, 300, $func, $opts ); $this->assertEquals( $value, $v, "New value stored" ); @@ -544,15 +622,6 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $this->assertEquals( 2, $wasSet, "Value re-calculated" ); } - /** - * @covers WANObjectCache::getWithSetCallback() - * @covers WANObjectCache::doGetWithSetCallback() - */ - public function testGetWithSetCallback_invalidCallback() { - $this->setExpectedException( InvalidArgumentException::class ); - $this->cache->getWithSetCallback( 'key', 30, 'invalid callback' ); - } - /** * @dataProvider getMultiWithSetCallback_provider * @covers WANObjectCache::getMultiWithSetCallback @@ -606,15 +675,16 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $value = "@efef$"; $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] ); $v = $cache->getMultiWithSetCallback( - $keyedIds, 30, $genFunc, [ 'lowTTL' => 0, 'lockTSE' => 5, ] + $extOpts ); + $keyedIds, 30, $genFunc, [ 'lowTTL' => 0, 'lockTSE' => 5 ] + $extOpts ); $this->assertEquals( $value, $v[$keyB], "Value returned" ); $this->assertEquals( 1, $wasSet, "Value regenerated" ); - $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed yet in process cache" ); + $this->assertSame( 0, $cache->getWarmupKeyMisses(), "Keys warmed in warmup cache" ); + $v = $cache->getMultiWithSetCallback( - $keyedIds, 30, $genFunc, [ 'lowTTL' => 0, 'lockTSE' => 5, ] + $extOpts ); + $keyedIds, 30, $genFunc, [ 'lowTTL' => 0, 'lockTSE' => 5 ] + $extOpts ); $this->assertEquals( $value, $v[$keyB], "Value returned" ); $this->assertEquals( 1, $wasSet, "Value not regenerated" ); - $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in process cache" ); + $this->assertSame( 0, $cache->getWarmupKeyMisses(), "Keys warmed in warmup cache" ); $mockWallClock += 1; @@ -649,11 +719,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $curTTL = null; $v = $cache->get( $keyC, $curTTL, [ $cKey1, $cKey2 ] ); - if ( $versioned ) { - $this->assertEquals( $value, $v[$cache::VFLD_DATA], "Value returned" ); - } else { - $this->assertEquals( $value, $v, "Value returned" ); - } + $this->assertEquals( $value, $v, "Value returned" ); $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" ); $wasSet = 0; @@ -701,8 +767,8 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $localBag = $this->getMockBuilder( HashBagOStuff::class ) ->setMethods( [ 'getMulti' ] )->getMock(); $localBag->expects( $this->exactly( 1 ) )->method( 'getMulti' )->willReturn( [ - WANObjectCache::VALUE_KEY_PREFIX . 'k1' => 'val-id1', - WANObjectCache::VALUE_KEY_PREFIX . 'k2' => 'val-id2' + 'WANCache:v:' . 'k1' => 'val-id1', + 'WANCache:v:' . 'k2' => 'val-id2' ] ); $wanCache = new WANObjectCache( [ 'cache' => $localBag ] ); @@ -780,12 +846,13 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $keyedIds, 30, $genFunc, [ 'lowTTL' => 0 ] + $extOpts ); $this->assertEquals( $value, $v[$keyB], "Value returned" ); $this->assertEquals( 1, $wasSet, "Value regenerated" ); - $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed yet in process cache" ); + $this->assertSame( 0, $cache->getWarmupKeyMisses(), "Keys warmed in warmup cache" ); + $v = $cache->getMultiWithUnionSetCallback( $keyedIds, 30, $genFunc, [ 'lowTTL' => 0 ] + $extOpts ); $this->assertEquals( $value, $v[$keyB], "Value returned" ); $this->assertEquals( 1, $wasSet, "Value not regenerated" ); - $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in process cache" ); + $this->assertSame( 0, $cache->getWarmupKeyMisses(), "Keys warmed in warmup cache" ); $mockWallClock += 1; @@ -818,11 +885,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $curTTL = null; $v = $cache->get( $keyC, $curTTL, [ $cKey1, $cKey2 ] ); - if ( $versioned ) { - $this->assertEquals( $value, $v[$cache::VFLD_DATA], "Value returned" ); - } else { - $this->assertEquals( $value, $v, "Value returned" ); - } + $this->assertEquals( $value, $v, "Value returned" ); $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" ); $wasSet = 0; @@ -880,7 +943,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { /** * @covers WANObjectCache::getWithSetCallback() - * @covers WANObjectCache::doGetWithSetCallback() + * @covers WANObjectCache::fetchOrRegenerate() */ public function testLockTSE() { $cache = $this->cache; @@ -901,7 +964,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $this->assertEquals( 1, $calls, 'Value was populated' ); // Acquire the mutex to verify that getWithSetCallback uses lockTSE properly - $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 ); + $this->internalCache->add( 'WANCache:m:' . $key, 1, 0 ); $checkKeys = [ wfRandomString() ]; // new check keys => force misses $ret = $cache->getWithSetCallback( $key, 30, $func, @@ -924,7 +987,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { /** * @covers WANObjectCache::getWithSetCallback() - * @covers WANObjectCache::doGetWithSetCallback() + * @covers WANObjectCache::fetchOrRegenerate() * @covers WANObjectCache::set() */ public function testLockTSESlow() { @@ -963,7 +1026,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $mockWallClock += 2; // low logical TTL expired // Acquire a lock to verify that getWithSetCallback uses lockTSE properly - $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 ); + $this->internalCache->add( 'WANCache:m:' . $key, 1, 0 ); $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] ); $this->assertEquals( $value, $ret ); @@ -971,7 +1034,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $mockWallClock += 301; // physical TTL expired // Acquire a lock to verify that getWithSetCallback uses lockTSE properly - $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 ); + $this->internalCache->add( 'WANCache:m:' . $key, 1, 0 ); $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] ); $this->assertEquals( $value, $ret ); @@ -1005,9 +1068,9 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { /** * @covers WANObjectCache::getWithSetCallback() - * @covers WANObjectCache::doGetWithSetCallback() + * @covers WANObjectCache::fetchOrRegenerate() */ - public function testBusyValue() { + public function testBusyValueBasic() { $cache = $this->cache; $key = wfRandomString(); $value = wfRandomString(); @@ -1017,7 +1080,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $cache->setMockTime( $mockWallClock ); $calls = 0; - $func = function () use ( &$calls, $value, $cache, $key ) { + $func = function () use ( &$calls, $value ) { ++$calls; return $value; }; @@ -1029,7 +1092,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $mockWallClock += 0.2; // interim keys not brand new // Acquire a lock to verify that getWithSetCallback uses busyValue properly - $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 ); + $this->internalCache->add( 'WANCache:m:' . $key, 1, 0 ); $checkKeys = [ wfRandomString() ]; // new check keys => force misses $ret = $cache->getWithSetCallback( $key, 30, $func, @@ -1048,20 +1111,66 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $this->assertEquals( $busyValue, $ret, 'Callback was not used; used busy value' ); $this->assertEquals( 2, $calls, 'Callback was not used; used busy value' ); - $this->internalCache->delete( $cache::MUTEX_KEY_PREFIX . $key ); + $this->internalCache->delete( 'WANCache:m:' . $key ); $mockWallClock += 0.001; // cached values will be newer than tombstone $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 30, 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] ); $this->assertEquals( $value, $ret, 'Callback was used; saved interim' ); $this->assertEquals( 3, $calls, 'Callback was used; saved interim' ); - $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 ); + $this->internalCache->add( 'WANCache:m:' . $key, 1, 0 ); $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] ); $this->assertEquals( $value, $ret, 'Callback was not used; used interim' ); $this->assertEquals( 3, $calls, 'Callback was not used; used interim' ); } + public function getBusyValues_Provider() { + $hash = new HashBagOStuff( [] ); + + return [ + [ + function () { + return "Saint Oliver Plunckett"; + }, + 'Saint Oliver Plunckett' + ], + [ 'strlen', 'strlen' ], + [ 'WANObjectCache::newEmpty', 'WANObjectCache::newEmpty' ], + [ [ 'WANObjectCache', 'newEmpty' ], [ 'WANObjectCache', 'newEmpty' ] ], + [ [ $hash, 'getLastError' ], [ $hash, 'getLastError' ] ], + [ [ 1, 2, 3 ], [ 1, 2, 3 ] ] + ]; + } + + /** + * @covers WANObjectCache::getWithSetCallback() + * @covers WANObjectCache::fetchOrRegenerate() + * @dataProvider getBusyValues_Provider + * @param mixed $busyValue + * @param mixed $expected + */ + public function testBusyValueTypes( $busyValue, $expected ) { + $cache = $this->cache; + $key = wfRandomString(); + + $mockWallClock = 1549343530.2053; + $cache->setMockTime( $mockWallClock ); + + $calls = 0; + $func = function () use ( &$calls ) { + ++$calls; + return 418; + }; + + // Acquire a lock to verify that getWithSetCallback uses busyValue properly + $this->internalCache->add( 'WANCache:m:' . $key, 1, 0 ); + + $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'busyValue' => $busyValue ] ); + $this->assertSame( $expected, $ret, 'busyValue used as expected' ); + $this->assertSame( 0, $calls, 'busyValue was used' ); + } + /** * @covers WANObjectCache::getMulti() */ @@ -1083,7 +1192,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $cache->set( $key2, $value2, 10 ); $curTTLs = []; - $this->assertEquals( + $this->assertSame( [ $key1 => $value1, $key2 => $value2 ], $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs ), 'Result array populated' @@ -1099,7 +1208,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $mockWallClock += 1; $curTTLs = []; - $this->assertEquals( + $this->assertSame( [ $key1 => $value1, $key2 => $value2 ], $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs, [ $cKey1, $cKey2 ] ), "Result array populated even with new check keys" @@ -1145,7 +1254,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { // Fake initial check key to be set in the past. Otherwise we'd have to sleep for // several seconds during the test to assert the behaviour. foreach ( [ $checkAll, $check1, $check2 ] as $checkKey ) { - $cache->touchCheckKey( $checkKey, WANObjectCache::HOLDOFF_NONE ); + $cache->touchCheckKey( $checkKey, WANObjectCache::HOLDOFF_TTL_NONE ); } $mockWallClock += 0.100; @@ -1160,7 +1269,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { 'key2' => $check2, 'key3' => $check3, ] ); - $this->assertEquals( + $this->assertSame( [ 'key1' => $value1, 'key2' => $value2 ], $result, 'Initial values' @@ -1180,7 +1289,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { 'key2' => $check2, 'key3' => $check3, ] ); - $this->assertEquals( + $this->assertSame( [ 'key1' => $value1, 'key2' => $value2 ], $result, 'key1 expired by check1, but value still provided' @@ -1239,6 +1348,35 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { } } + /** + * @covers WANObjectCache::get() + * @covers WANObjectCache::processCheckKeys() + */ + public function testCheckKeyHoldoff() { + $cache = $this->cache; + $key = wfRandomString(); + $checkKey = wfRandomString(); + + $mockWallClock = 1549343530.2053; + $cache->setMockTime( $mockWallClock ); + $cache->touchCheckKey( $checkKey, 8 ); + + $mockWallClock += 1; + $cache->set( $key, 1, 60 ); + $this->assertEquals( 1, $cache->get( $key, $curTTL, [ $checkKey ] ) ); + $this->assertLessThan( 0, $curTTL, "Key in hold-off due to check key" ); + + $mockWallClock += 3; + $cache->set( $key, 1, 60 ); + $this->assertEquals( 1, $cache->get( $key, $curTTL, [ $checkKey ] ) ); + $this->assertLessThan( 0, $curTTL, "Key in hold-off due to check key" ); + + $mockWallClock += 10; + $cache->set( $key, 1, 60 ); + $this->assertEquals( 1, $cache->get( $key, $curTTL, [ $checkKey ] ) ); + $this->assertGreaterThan( 0, $curTTL, "Key not in hold-off due to check key" ); + } + /** * @covers WANObjectCache::delete * @covers WANObjectCache::relayDelete @@ -1267,7 +1405,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $this->assertLessThan( 0, $curTTL, "Deleted key is tombstoned and has current TTL < 0" ); $this->cache->set( $key, $value ); - $this->cache->delete( $key, WANObjectCache::HOLDOFF_NONE ); + $this->cache->delete( $key, WANObjectCache::HOLDOFF_TTL_NONE ); $curTTL = null; $v = $this->cache->get( $key, $curTTL ); @@ -1283,7 +1421,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { /** * @dataProvider getWithSetCallback_versions_provider * @covers WANObjectCache::getWithSetCallback() - * @covers WANObjectCache::doGetWithSetCallback() + * @covers WANObjectCache::fetchOrRegenerate() * @param array $extOpts * @param bool $versioned */ @@ -1340,7 +1478,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $wasSet = 0; $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts + $extOpts ); $this->assertEquals( $valueV2, $v, "Value not regenerated (secondary key)" ); - $this->assertEquals( 0, $wasSet, "Value not regenerated (secondary key)" ); + $this->assertSame( 0, $wasSet, "Value not regenerated (secondary key)" ); // Clear out the older or unversioned key $cache->delete( $key, 0 ); @@ -1399,10 +1537,10 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $v = $cache->getWithSetCallback( $key, 60, $func ); $this->assertEquals( 3, $wasCalled, 'Value regenerated (got mutex)' ); // sets interim // Lock up the mutex so interim cache is used - $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 ); + $this->internalCache->add( 'WANCache:m:' . $key, 1, 0 ); $v = $cache->getWithSetCallback( $key, 60, $func ); $this->assertEquals( 3, $wasCalled, 'Value interim cached (failed mutex)' ); - $this->internalCache->delete( $cache::MUTEX_KEY_PREFIX . $key ); + $this->internalCache->delete( 'WANCache:m:' . $key ); $cache->useInterimHoldOffCaching( false ); @@ -1419,7 +1557,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $v = $cache->getWithSetCallback( $key, 60, $func ); $this->assertEquals( 4, $wasCalled, 'Value still regenerated (got mutex)' ); // Lock up the mutex so interim cache is used - $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 ); + $this->internalCache->add( 'WANCache:m:' . $key, 1, 0 ); $v = $cache->getWithSetCallback( $key, 60, $func ); $this->assertEquals( 5, $wasCalled, 'Value still regenerated (failed mutex)' ); } @@ -1485,16 +1623,16 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { // Two check keys are newer (given hold-off) than $key, another is older $this->internalCache->set( - WANObjectCache::TIME_KEY_PREFIX . $tKey2, - WANObjectCache::PURGE_VAL_PREFIX . ( $priorTime - 3 ) + 'WANCache:t:' . $tKey2, + 'PURGED:' . ( $priorTime - 3 ) ); $this->internalCache->set( - WANObjectCache::TIME_KEY_PREFIX . $tKey2, - WANObjectCache::PURGE_VAL_PREFIX . ( $priorTime - 5 ) + 'WANCache:t:' . $tKey2, + 'PURGED:' . ( $priorTime - 5 ) ); $this->internalCache->set( - WANObjectCache::TIME_KEY_PREFIX . $tKey1, - WANObjectCache::PURGE_VAL_PREFIX . ( $priorTime - 30 ) + 'WANCache:t:' . $tKey1, + 'PURGED:' . ( $priorTime - 30 ) ); $this->cache->set( $key, $value, 30 ); @@ -1521,30 +1659,30 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $badTime = microtime( true ) - 300; $this->internalCache->set( - WANObjectCache::VALUE_KEY_PREFIX . $vKey1, + 'WANCache:v:' . $vKey1, [ - WANObjectCache::FLD_VERSION => WANObjectCache::VERSION, - WANObjectCache::FLD_VALUE => $value, - WANObjectCache::FLD_TTL => 3600, - WANObjectCache::FLD_TIME => $goodTime + 0 => 1, + 1 => $value, + 2 => 3600, + 3 => $goodTime ] ); $this->internalCache->set( - WANObjectCache::VALUE_KEY_PREFIX . $vKey2, + 'WANCache:v:' . $vKey2, [ - WANObjectCache::FLD_VERSION => WANObjectCache::VERSION, - WANObjectCache::FLD_VALUE => $value, - WANObjectCache::FLD_TTL => 3600, - WANObjectCache::FLD_TIME => $badTime + 0 => 1, + 1 => $value, + 2 => 3600, + 3 => $badTime ] ); $this->internalCache->set( - WANObjectCache::TIME_KEY_PREFIX . $tKey1, - WANObjectCache::PURGE_VAL_PREFIX . $goodTime + 'WANCache:t:' . $tKey1, + 'PURGED:' . $goodTime ); $this->internalCache->set( - WANObjectCache::TIME_KEY_PREFIX . $tKey2, - WANObjectCache::PURGE_VAL_PREFIX . $badTime + 'WANCache:t:' . $tKey2, + 'PURGED:' . $badTime ); $this->assertEquals( $value, $this->cache->get( $vKey1 ) ); @@ -1569,10 +1707,10 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { ->setMethods( [ 'get', 'changeTTL' ] )->getMock(); $backend->expects( $this->once() )->method( 'get' ) ->willReturn( [ - WANObjectCache::FLD_VERSION => WANObjectCache::VERSION, - WANObjectCache::FLD_VALUE => 'value', - WANObjectCache::FLD_TTL => 3600, - WANObjectCache::FLD_TIME => 300, + 0 => 1, + 1 => 'value', + 2 => 3600, + 3 => 300, ] ); $backend->expects( $this->once() )->method( 'changeTTL' ) ->willReturn( false ); @@ -1658,7 +1796,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { ] ); $localBag->expects( $this->once() )->method( 'set' ) - ->with( "/*/mw-wan/" . $wanCache::VALUE_KEY_PREFIX . "test" ); + ->with( "/*/mw-wan/" . 'WANCache:v:' . "test" ); $wanCache->delete( 'test' ); } @@ -1674,7 +1812,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { ] ); $localBag->expects( $this->once() )->method( 'set' ) - ->with( "/*/mw-wan/" . $wanCache::TIME_KEY_PREFIX . "test" ); + ->with( "/*/mw-wan/" . 'WANCache:t:' . "test" ); $wanCache->touchCheckKey( 'test' ); } @@ -1690,7 +1828,7 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { ] ); $localBag->expects( $this->once() )->method( 'delete' ) - ->with( "/*/mw-wan/" . $wanCache::TIME_KEY_PREFIX . "test" ); + ->with( "/*/mw-wan/" . 'WANCache:t:' . "test" ); $wanCache->resetCheckKey( 'test' ); } @@ -1802,6 +1940,10 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { * @covers WANObjectCache::makeKey */ public function testMakeKey() { + if ( defined( 'HHVM_VERSION' ) ) { + $this->markTestSkipped( 'HHVM Reflection buggy' ); + } + $backend = $this->getMockBuilder( HashBagOStuff::class ) ->setMethods( [ 'makeKey' ] )->getMock(); $backend->expects( $this->once() )->method( 'makeKey' ) @@ -1818,6 +1960,10 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { * @covers WANObjectCache::makeGlobalKey */ public function testMakeGlobalKey() { + if ( defined( 'HHVM_VERSION' ) ) { + $this->markTestSkipped( 'HHVM Reflection buggy' ); + } + $backend = $this->getMockBuilder( HashBagOStuff::class ) ->setMethods( [ 'makeGlobalKey' ] )->getMock(); $backend->expects( $this->once() )->method( 'makeGlobalKey' ) @@ -1835,6 +1981,8 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { [ 'domain:page:5', 'page' ], [ 'domain:main-key', 'main-key' ], [ 'domain:page:history', 'page' ], + // Regression test for T232907 + [ 'domain:foo-bar-1.2:abc:v2', 'foo-bar-1_2' ], [ 'missingdomainkey', 'missingdomainkey' ] ]; } @@ -1850,6 +1998,137 @@ class WANObjectCacheTest extends PHPUnit\Framework\TestCase { $this->assertEquals( $class, $wanCache->determineKeyClassForStats( $key ) ); } + + /** + * @covers WANObjectCache::makeMultiKeys + */ + public function testMakeMultiKeys() { + $cache = $this->cache; + + $ids = [ 1, 2, 3, 4, 4, 5, 6, 6, 7, 7 ]; + $keyCallback = function ( $id, WANObjectCache $cache ) { + return $cache->makeKey( 'key', $id ); + }; + $keyedIds = $cache->makeMultiKeys( $ids, $keyCallback ); + + $expected = [ + "local:key:1" => 1, + "local:key:2" => 2, + "local:key:3" => 3, + "local:key:4" => 4, + "local:key:5" => 5, + "local:key:6" => 6, + "local:key:7" => 7 + ]; + $this->assertSame( $expected, iterator_to_array( $keyedIds ) ); + + $ids = [ '1', '2', '3', '4', '4', '5', '6', '6', '7', '7' ]; + $keyCallback = function ( $id, WANObjectCache $cache ) { + return $cache->makeGlobalKey( 'key', $id, 'a', $id, 'b' ); + }; + $keyedIds = $cache->makeMultiKeys( $ids, $keyCallback ); + + $expected = [ + "global:key:1:a:1:b" => '1', + "global:key:2:a:2:b" => '2', + "global:key:3:a:3:b" => '3', + "global:key:4:a:4:b" => '4', + "global:key:5:a:5:b" => '5', + "global:key:6:a:6:b" => '6', + "global:key:7:a:7:b" => '7' + ]; + $this->assertSame( $expected, iterator_to_array( $keyedIds ) ); + } + + /** + * @covers WANObjectCache::makeMultiKeys + */ + public function testMakeMultiKeysIntString() { + $cache = $this->cache; + $ids = [ 1, 2, 3, 4, '4', 5, 6, 6, 7, '7' ]; + $keyCallback = function ( $id, WANObjectCache $cache ) { + return $cache->makeGlobalKey( 'key', $id, 'a', $id, 'b' ); + }; + + $keyedIds = $cache->makeMultiKeys( $ids, $keyCallback ); + + $expected = [ + "global:key:1:a:1:b" => 1, + "global:key:2:a:2:b" => 2, + "global:key:3:a:3:b" => 3, + "global:key:4:a:4:b" => 4, + "global:key:5:a:5:b" => 5, + "global:key:6:a:6:b" => 6, + "global:key:7:a:7:b" => 7 + ]; + $this->assertSame( $expected, iterator_to_array( $keyedIds ) ); + } + + /** + * @covers WANObjectCache::makeMultiKeys + * @expectedException UnexpectedValueException + */ + public function testMakeMultiKeysCollision() { + $ids = [ 1, 2, 3, 4, '4', 5, 6, 6, 7 ]; + + $this->cache->makeMultiKeys( + $ids, + function ( $id ) { + return "keymod:" . $id % 3; + } + ); + } + + /** + * @covers WANObjectCache::multiRemap + */ + public function testMultiRemap() { + $a = [ 'a', 'b', 'c' ]; + $res = [ 'keyA' => 1, 'keyB' => 2, 'keyC' => 3 ]; + + $this->assertEquals( + [ 'a' => 1, 'b' => 2, 'c' => 3 ], + $this->cache->multiRemap( $a, $res ) + ); + + $a = [ 'a', 'b', 'c', 'c', 'd' ]; + $res = [ 'keyA' => 1, 'keyB' => 2, 'keyC' => 3, 'keyD' => 4 ]; + + $this->assertEquals( + [ 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4 ], + $this->cache->multiRemap( $a, $res ) + ); + } + + /** + * @covers WANObjectCache::hash256 + */ + public function testHash256() { + $bag = new HashBagOStuff(); + $cache = new WANObjectCache( [ 'cache' => $bag, 'epoch' => 5 ] ); + $this->assertEquals( + 'f402bce76bfa1136adc705d8d5719911ce1fe61f0ad82ddf79a15f3c4de6ec4c', + $cache->hash256( 'x' ) + ); + + $cache = new WANObjectCache( [ 'cache' => $bag, 'epoch' => 50 ] ); + $this->assertEquals( + 'f79a126722f0a682c4c500509f1b61e836e56c4803f92edc89fc281da5caa54e', + $cache->hash256( 'x' ) + ); + + $cache = new WANObjectCache( [ 'cache' => $bag, 'secret' => 'garden' ] ); + $this->assertEquals( + '48cd57016ffe29981a1114c45e5daef327d30fc6206cb73edc3cb94b4d8fe093', + $cache->hash256( 'x' ) + ); + + $cache = new WANObjectCache( [ 'cache' => $bag, 'secret' => 'garden', 'epoch' => 3 ] ); + $this->assertEquals( + '48cd57016ffe29981a1114c45e5daef327d30fc6206cb73edc3cb94b4d8fe093', + $cache->hash256( 'x' ) + ); + } } class NearExpiringWANObjectCache extends WANObjectCache {