<?php
-class WANObjectCacheTest extends MediaWikiTestCase {
+use Wikimedia\TestingAccessWrapper;
+
+class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
/** @var WANObjectCache */
private $cache;
/**@var BagOStuff */
protected function setUp() {
parent::setUp();
- if ( $this->getCliArg( 'use-wanobjectcache' ) ) {
- $name = $this->getCliArg( 'use-wanobjectcache' );
-
- $this->cache = ObjectCache::getWANInstance( $name );
- } else {
- $this->cache = new WANObjectCache( [
- 'cache' => new HashBagOStuff(),
- 'pool' => 'testcache-hash',
- 'relayer' => new EventRelayerNull( [] )
- ] );
- }
+ $this->cache = new WANObjectCache( [
+ 'cache' => new HashBagOStuff(),
+ 'pool' => 'testcache-hash',
+ 'relayer' => new EventRelayerNull( [] )
+ ] );
$wanCache = TestingAccessWrapper::newFromObject( $this->cache );
/** @noinspection PhpUndefinedFieldInspection */
$key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
}
$this->assertEquals( 9, $hit, "Values evicted" );
+
+ $key = reset( $keys );
+ // Get into cache
+ $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
+ $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
+ $this->assertEquals( 10, $hit, "Value cached" );
+ $outerCallback = function () use ( &$callback, $key ) {
+ $v = $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
+
+ return 43 + $v;
+ };
+ $this->cache->getWithSetCallback( $key, 100, $outerCallback );
+ $this->assertEquals( 11, $hit, "Nested callback value process cache skipped" );
}
/**
$this->assertEquals( $value, $v, "Value returned" );
$this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
$this->assertEquals( $value, $priorValue, "Has prior value" );
- $this->assertType( 'float', $priorAsOf, "Has prior value" );
+ $this->assertInternalType( 'float', $priorAsOf, "Has prior value" );
$t1 = $cache->getCheckKeyTime( $cKey1 );
$this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
$t2 = $cache->getCheckKeyTime( $cKey2 );
$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" );
$v = $cache->getMultiWithSetCallback(
$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" );
$priorTime = microtime( true );
usleep( 1 );
$this->assertEquals( $value, $v[$keyB], "Value returned" );
$this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
$this->assertEquals( $value, $priorValue, "Has prior value" );
- $this->assertType( 'float', $priorAsOf, "Has prior value" );
+ $this->assertInternalType( 'float', $priorAsOf, "Has prior value" );
$t1 = $cache->getCheckKeyTime( $cKey1 );
$this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
$t2 = $cache->getCheckKeyTime( $cKey2 );
$cache->getMultiWithSetCallback( $keyedIds, 10, $genFunc );
$this->assertEquals( count( $ids ), $calls, "Values cached" );
+
+ // Mock the BagOStuff to assure only one getMulti() call given process caching
+ $localBag = $this->getMockBuilder( 'HashBagOStuff' )
+ ->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 = new WANObjectCache( [ 'cache' => $localBag, 'pool' => 'testcache-hash' ] );
+
+ // Warm the process cache
+ $keyedIds = new ArrayIterator( [ 'k1' => 'id1', 'k2' => 'id2' ] );
+ $this->assertEquals(
+ [ 'k1' => 'val-id1', 'k2' => 'val-id2' ],
+ $wanCache->getMultiWithSetCallback( $keyedIds, 10, $genFunc, [ 'pcTTL' => 5 ] )
+ );
+ // Use the process cache
+ $this->assertEquals(
+ [ 'k1' => 'val-id1', 'k2' => 'val-id2' ],
+ $wanCache->getMultiWithSetCallback( $keyedIds, 10, $genFunc, [ 'pcTTL' => 5 ] )
+ );
}
public static function getMultiWithSetCallback_provider() {
];
}
+ /**
+ * @dataProvider getMultiWithUnionSetCallback_provider
+ * @covers WANObjectCache::getMultiWithUnionSetCallback()
+ * @covers WANObjectCache::makeMultiKeys()
+ * @param array $extOpts
+ * @param bool $versioned
+ */
+ public function testGetMultiWithUnionSetCallback( array $extOpts, $versioned ) {
+ $cache = $this->cache;
+
+ $keyA = wfRandomString();
+ $keyB = wfRandomString();
+ $keyC = wfRandomString();
+ $cKey1 = wfRandomString();
+ $cKey2 = wfRandomString();
+
+ $wasSet = 0;
+ $genFunc = function ( array $ids, array &$ttls, array &$setOpts ) use (
+ &$wasSet, &$priorValue, &$priorAsOf
+ ) {
+ $newValues = [];
+ foreach ( $ids as $id ) {
+ ++$wasSet;
+ $newValues[$id] = "@$id$";
+ $ttls[$id] = 20; // override with another value
+ }
+
+ return $newValues;
+ };
+
+ $wasSet = 0;
+ $keyedIds = new ArrayIterator( [ $keyA => 3353 ] );
+ $value = "@3353$";
+ $v = $cache->getMultiWithUnionSetCallback(
+ $keyedIds, 30, $genFunc, $extOpts );
+ $this->assertEquals( $value, $v[$keyA], "Value returned" );
+ $this->assertEquals( 1, $wasSet, "Value regenerated" );
+
+ $curTTL = null;
+ $cache->get( $keyA, $curTTL );
+ $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' );
+ $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' );
+
+ $wasSet = 0;
+ $value = "@efef$";
+ $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
+ $v = $cache->getMultiWithUnionSetCallback(
+ $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" );
+ $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" );
+
+ $priorTime = microtime( true );
+ usleep( 1 );
+ $wasSet = 0;
+ $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
+ $v = $cache->getMultiWithUnionSetCallback(
+ $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
+ );
+ $this->assertEquals( $value, $v[$keyB], "Value returned" );
+ $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
+ $t1 = $cache->getCheckKeyTime( $cKey1 );
+ $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
+ $t2 = $cache->getCheckKeyTime( $cKey2 );
+ $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
+
+ $priorTime = microtime( true );
+ $value = "@43636$";
+ $wasSet = 0;
+ $keyedIds = new ArrayIterator( [ $keyC => 43636 ] );
+ $v = $cache->getMultiWithUnionSetCallback(
+ $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
+ );
+ $this->assertEquals( $value, $v[$keyC], "Value returned" );
+ $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" );
+ $t1 = $cache->getCheckKeyTime( $cKey1 );
+ $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' );
+ $t2 = $cache->getCheckKeyTime( $cKey2 );
+ $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' );
+
+ $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->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
+
+ $wasSet = 0;
+ $key = wfRandomString();
+ $keyedIds = new ArrayIterator( [ $key => 242424 ] );
+ $v = $cache->getMultiWithUnionSetCallback(
+ $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] + $extOpts );
+ $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value returned" );
+ $cache->delete( $key );
+ $keyedIds = new ArrayIterator( [ $key => 242424 ] );
+ $v = $cache->getMultiWithUnionSetCallback(
+ $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] + $extOpts );
+ $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value still returned after deleted" );
+ $this->assertEquals( 1, $wasSet, "Value process cached while deleted" );
+
+ $calls = 0;
+ $ids = [ 1, 2, 3, 4, 5, 6 ];
+ $keyFunc = function ( $id, WANObjectCache $wanCache ) {
+ return $wanCache->makeKey( 'test', $id );
+ };
+ $keyedIds = $cache->makeMultiKeys( $ids, $keyFunc );
+ $genFunc = function ( array $ids, array &$ttls, array &$setOpts ) use ( &$calls ) {
+ $newValues = [];
+ foreach ( $ids as $id ) {
+ ++$calls;
+ $newValues[$id] = "val-{$id}";
+ }
+
+ return $newValues;
+ };
+ $values = $cache->getMultiWithUnionSetCallback( $keyedIds, 10, $genFunc );
+
+ $this->assertEquals(
+ [ "val-1", "val-2", "val-3", "val-4", "val-5", "val-6" ],
+ array_values( $values ),
+ "Correct values in correct order"
+ );
+ $this->assertEquals(
+ array_map( $keyFunc, $ids, array_fill( 0, count( $ids ), $this->cache ) ),
+ array_keys( $values ),
+ "Correct keys in correct order"
+ );
+ $this->assertEquals( count( $ids ), $calls );
+
+ $cache->getMultiWithUnionSetCallback( $keyedIds, 10, $genFunc );
+ $this->assertEquals( count( $ids ), $calls, "Values cached" );
+ }
+
+ public static function getMultiWithUnionSetCallback_provider() {
+ return [
+ [ [], false ],
+ [ [ 'version' => 1 ], true ]
+ ];
+ }
+
/**
* @covers WANObjectCache::getWithSetCallback()
* @covers WANObjectCache::doGetWithSetCallback()
$this->assertGreaterThan( -5.1, $curTTL, "Correct CTL" );
}
+ /**
+ * @covers WANObjectCache::reap()
+ * @covers WANObjectCache::reapCheckKey()
+ */
+ public function testReap() {
+ $vKey1 = wfRandomString();
+ $vKey2 = wfRandomString();
+ $tKey1 = wfRandomString();
+ $tKey2 = wfRandomString();
+ $value = 'moo';
+
+ $knownPurge = time() - 60;
+ $goodTime = microtime( true ) - 5;
+ $badTime = microtime( true ) - 300;
+
+ $this->internalCache->set(
+ WANObjectCache::VALUE_KEY_PREFIX . $vKey1,
+ [
+ WANObjectCache::FLD_VERSION => WANObjectCache::VERSION,
+ WANObjectCache::FLD_VALUE => $value,
+ WANObjectCache::FLD_TTL => 3600,
+ WANObjectCache::FLD_TIME => $goodTime
+ ]
+ );
+ $this->internalCache->set(
+ WANObjectCache::VALUE_KEY_PREFIX . $vKey2,
+ [
+ WANObjectCache::FLD_VERSION => WANObjectCache::VERSION,
+ WANObjectCache::FLD_VALUE => $value,
+ WANObjectCache::FLD_TTL => 3600,
+ WANObjectCache::FLD_TIME => $badTime
+ ]
+ );
+ $this->internalCache->set(
+ WANObjectCache::TIME_KEY_PREFIX . $tKey1,
+ WANObjectCache::PURGE_VAL_PREFIX . $goodTime
+ );
+ $this->internalCache->set(
+ WANObjectCache::TIME_KEY_PREFIX . $tKey2,
+ WANObjectCache::PURGE_VAL_PREFIX . $badTime
+ );
+
+ $this->assertEquals( $value, $this->cache->get( $vKey1 ) );
+ $this->assertEquals( $value, $this->cache->get( $vKey2 ) );
+ $this->cache->reap( $vKey1, $knownPurge, $bad1 );
+ $this->cache->reap( $vKey2, $knownPurge, $bad2 );
+
+ $this->assertFalse( $bad1 );
+ $this->assertTrue( $bad2 );
+
+ $this->cache->reapCheckKey( $tKey1, $knownPurge, $tBad1 );
+ $this->cache->reapCheckKey( $tKey2, $knownPurge, $tBad2 );
+ $this->assertFalse( $tBad1 );
+ $this->assertTrue( $tBad2 );
+ }
+
/**
* @covers WANObjectCache::set()
*/
}
public function testMcRouterSupport() {
- $localBag = $this->getMock( 'EmptyBagOStuff', [ 'set', 'delete' ] );
+ $localBag = $this->getMockBuilder( 'EmptyBagOStuff' )
+ ->setMethods( [ 'set', 'delete' ] )->getMock();
$localBag->expects( $this->never() )->method( 'set' );
$localBag->expects( $this->never() )->method( 'delete' );
$wanCache = new WANObjectCache( [
$wanCache->getMulti( [ 'x', 'y' ], $ctls, [ 'check2' ] );
$wanCache->getWithSetCallback( 'p', 30, $valFunc );
$wanCache->getCheckKeyTime( 'zzz' );
+ $wanCache->reap( 'x', time() - 300 );
+ $wanCache->reap( 'zzz', time() - 300 );
}
/**
[ null, 86400, 800, .2, 800 ]
];
}
+
+ /**
+ * @covers WANObjectCache::makeKey
+ */
+ public function testMakeKey() {
+ $backend = $this->getMockBuilder( HashBagOStuff::class )
+ ->setMethods( [ 'makeKey' ] )->getMock();
+ $backend->expects( $this->once() )->method( 'makeKey' )
+ ->willReturn( 'special' );
+
+ $wanCache = new WANObjectCache( [
+ 'cache' => $backend,
+ 'pool' => 'testcache-hash',
+ 'relayer' => new EventRelayerNull( [] )
+ ] );
+
+ $this->assertSame( 'special', $wanCache->makeKey( 'a', 'b' ) );
+ }
+
+ /**
+ * @covers WANObjectCache::makeGlobalKey
+ */
+ public function testMakeGlobalKey() {
+ $backend = $this->getMockBuilder( HashBagOStuff::class )
+ ->setMethods( [ 'makeGlobalKey' ] )->getMock();
+ $backend->expects( $this->once() )->method( 'makeGlobalKey' )
+ ->willReturn( 'special' );
+
+ $wanCache = new WANObjectCache( [
+ 'cache' => $backend,
+ 'pool' => 'testcache-hash',
+ 'relayer' => new EventRelayerNull( [] )
+ ] );
+
+ $this->assertSame( 'special', $wanCache->makeGlobalKey( 'a', 'b' ) );
+ }
}