3 use Wikimedia\TestingAccessWrapper
;
6 * @covers WANObjectCache::wrap
7 * @covers WANObjectCache::unwrap
8 * @covers WANObjectCache::worthRefreshExpiring
9 * @covers WANObjectCache::worthRefreshPopular
10 * @covers WANObjectCache::isValid
11 * @covers WANObjectCache::getWarmupKeyMisses
12 * @covers WANObjectCache::prefixCacheKeys
13 * @covers WANObjectCache::getProcessCache
14 * @covers WANObjectCache::getNonProcessCachedKeys
15 * @covers WANObjectCache::getRawKeysForWarmup
16 * @covers WANObjectCache::getInterimValue
17 * @covers WANObjectCache::setInterimValue
19 class WANObjectCacheTest
extends PHPUnit\Framework\TestCase
{
21 use MediaWikiCoversValidator
;
22 use PHPUnit4And6Compat
;
24 /** @var WANObjectCache */
27 private $internalCache;
29 protected function setUp() {
32 $this->cache
= new WANObjectCache( [
33 'cache' => new HashBagOStuff()
36 $wanCache = TestingAccessWrapper
::newFromObject( $this->cache
);
37 /** @noinspection PhpUndefinedFieldInspection */
38 $this->internalCache
= $wanCache->cache
;
42 * @dataProvider provideSetAndGet
43 * @covers WANObjectCache::set()
44 * @covers WANObjectCache::get()
45 * @covers WANObjectCache::makeKey()
49 public function testSetAndGet( $value, $ttl ) {
52 $key = $this->cache
->makeKey( 'x', wfRandomString() );
54 $this->cache
->get( $key, $curTTL, [], $asOf );
55 $this->assertNull( $curTTL, "Current TTL is null" );
56 $this->assertNull( $asOf, "Current as-of-time is infinite" );
58 $t = microtime( true );
59 $this->cache
->set( $key, $value, $ttl );
61 $this->assertEquals( $value, $this->cache
->get( $key, $curTTL, [], $asOf ) );
62 if ( is_infinite( $ttl ) ||
$ttl == 0 ) {
63 $this->assertTrue( is_infinite( $curTTL ), "Current TTL is infinite" );
65 $this->assertGreaterThan( 0, $curTTL, "Current TTL > 0" );
66 $this->assertLessThanOrEqual( $ttl, $curTTL, "Current TTL < nominal TTL" );
68 $this->assertGreaterThanOrEqual( $t - 1, $asOf, "As-of-time in range of set() time" );
69 $this->assertLessThanOrEqual( $t +
1, $asOf, "As-of-time in range of set() time" );
72 public static function provideSetAndGet() {
79 [ (object)[ 'meow' ], 3 ],
87 * @covers WANObjectCache::get()
88 * @covers WANObjectCache::makeGlobalKey()
90 public function testGetNotExists() {
91 $key = $this->cache
->makeGlobalKey( 'y', wfRandomString(), 'p' );
93 $value = $this->cache
->get( $key, $curTTL );
95 $this->assertFalse( $value, "Non-existing key has false value" );
96 $this->assertNull( $curTTL, "Non-existing key has null current TTL" );
100 * @covers WANObjectCache::set()
102 public function testSetOver() {
103 $key = wfRandomString();
104 for ( $i = 0; $i < 3; ++
$i ) {
105 $value = wfRandomString();
106 $this->cache
->set( $key, $value, 3 );
108 $this->assertEquals( $this->cache
->get( $key ), $value );
113 * @covers WANObjectCache::set()
115 public function testStaleSet() {
116 $key = wfRandomString();
117 $value = wfRandomString();
118 $this->cache
->set( $key, $value, 3, [ 'since' => microtime( true ) - 30 ] );
120 $this->assertFalse( $this->cache
->get( $key ), "Stale set() value ignored" );
123 public function testProcessCache() {
124 $mockWallClock = 1549343530.2053;
125 $this->cache
->setMockTime( $mockWallClock );
128 $callback = function () use ( &$hit ) {
132 $keys = [ wfRandomString(), wfRandomString(), wfRandomString() ];
133 $groups = [ 'thiscache:1', 'thatcache:1', 'somecache:1' ];
135 foreach ( $keys as $i => $key ) {
136 $this->cache
->getWithSetCallback(
137 $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
139 $this->assertEquals( 3, $hit );
141 foreach ( $keys as $i => $key ) {
142 $this->cache
->getWithSetCallback(
143 $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
145 $this->assertEquals( 3, $hit, "Values cached" );
147 foreach ( $keys as $i => $key ) {
148 $this->cache
->getWithSetCallback(
149 "$key-2", 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
151 $this->assertEquals( 6, $hit );
153 foreach ( $keys as $i => $key ) {
154 $this->cache
->getWithSetCallback(
155 "$key-2", 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
157 $this->assertEquals( 6, $hit, "New values cached" );
159 foreach ( $keys as $i => $key ) {
160 // Should evict from process cache
161 $this->cache
->delete( $key );
162 $mockWallClock +
= 0.001; // cached values will be newer than tombstone
163 // Get into cache (specific process cache group)
164 $this->cache
->getWithSetCallback(
165 $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
167 $this->assertEquals( 9, $hit, "Values evicted by delete()" );
169 // Get into cache (default process cache group)
170 $key = reset( $keys );
171 $this->cache
->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
172 $this->assertEquals( 9, $hit, "Value recently interim-cached" );
174 $mockWallClock +
= 0.2; // interim key not brand new
175 $this->cache
->clearProcessCache();
176 $this->cache
->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
177 $this->assertEquals( 10, $hit, "Value calculated (interim key not recent and reset)" );
178 $this->cache
->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
179 $this->assertEquals( 10, $hit, "Value process cached" );
181 $mockWallClock +
= 0.2; // interim key not brand new
182 $outerCallback = function () use ( &$callback, $key ) {
183 $v = $this->cache
->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
187 // Outer key misses and refuses inner key process cache value
188 $this->cache
->getWithSetCallback( "$key-miss-outer", 100, $outerCallback );
189 $this->assertEquals( 11, $hit, "Nested callback value process cache skipped" );
193 * @dataProvider getWithSetCallback_provider
194 * @covers WANObjectCache::getWithSetCallback()
195 * @covers WANObjectCache::doGetWithSetCallback()
196 * @param array $extOpts
197 * @param bool $versioned
199 public function testGetWithSetCallback( array $extOpts, $versioned ) {
200 $cache = $this->cache
;
202 $key = wfRandomString();
203 $value = wfRandomString();
204 $cKey1 = wfRandomString();
205 $cKey2 = wfRandomString();
210 $func = function ( $old, &$ttl, &$opts, $asOf )
211 use ( &$wasSet, &$priorValue, &$priorAsOf, $value ) {
215 $ttl = 20; // override with another value
219 $mockWallClock = 1549343530.2053;
220 $priorTime = $mockWallClock; // reference time
221 $cache->setMockTime( $mockWallClock );
224 $v = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] +
$extOpts );
225 $this->assertEquals( $value, $v, "Value returned" );
226 $this->assertEquals( 1, $wasSet, "Value regenerated" );
227 $this->assertFalse( $priorValue, "No prior value" );
228 $this->assertNull( $priorAsOf, "No prior value" );
231 $cache->get( $key, $curTTL );
232 $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' );
233 $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' );
236 $v = $cache->getWithSetCallback(
237 $key, 30, $func, [ 'lowTTL' => 0, 'lockTSE' => 5 ] +
$extOpts );
238 $this->assertEquals( $value, $v, "Value returned" );
239 $this->assertEquals( 0, $wasSet, "Value not regenerated" );
244 $v = $cache->getWithSetCallback(
245 $key, 30, $func, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] +
$extOpts
247 $this->assertEquals( $value, $v, "Value returned" );
248 $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
249 $this->assertEquals( $value, $priorValue, "Has prior value" );
250 $this->assertInternalType( 'float', $priorAsOf, "Has prior value" );
251 $t1 = $cache->getCheckKeyTime( $cKey1 );
252 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
253 $t2 = $cache->getCheckKeyTime( $cKey2 );
254 $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
256 $mockWallClock +
= 0.2; // interim key is not brand new and check keys have past values
257 $priorTime = $mockWallClock; // reference time
259 $v = $cache->getWithSetCallback(
260 $key, 30, $func, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] +
$extOpts
262 $this->assertEquals( $value, $v, "Value returned" );
263 $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" );
264 $t1 = $cache->getCheckKeyTime( $cKey1 );
265 $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' );
266 $t2 = $cache->getCheckKeyTime( $cKey2 );
267 $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' );
270 $v = $cache->get( $key, $curTTL, [ $cKey1, $cKey2 ] );
272 $this->assertEquals( $value, $v[$cache::VFLD_DATA
], "Value returned" );
274 $this->assertEquals( $value, $v, "Value returned" );
276 $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
279 $key = wfRandomString();
280 $v = $cache->getWithSetCallback( $key, 30, $func, [ 'pcTTL' => 5 ] +
$extOpts );
281 $this->assertEquals( $value, $v, "Value returned" );
282 $cache->delete( $key );
283 $v = $cache->getWithSetCallback( $key, 30, $func, [ 'pcTTL' => 5 ] +
$extOpts );
284 $this->assertEquals( $value, $v, "Value still returned after deleted" );
285 $this->assertEquals( 1, $wasSet, "Value process cached while deleted" );
287 $oldValReceived = -1;
288 $oldAsOfReceived = -1;
289 $checkFunc = function ( $oldVal, &$ttl, array $setOpts, $oldAsOf )
290 use ( &$oldValReceived, &$oldAsOfReceived, &$wasSet ) {
292 $oldValReceived = $oldVal;
293 $oldAsOfReceived = $oldAsOf;
295 return 'xxx' . $wasSet;
298 $mockWallClock = 1549343530.2053;
299 $priorTime = $mockWallClock; // reference time
302 $key = wfRandomString();
303 $v = $cache->getWithSetCallback(
304 $key, 30, $checkFunc, [ 'staleTTL' => 50 ] +
$extOpts );
305 $this->assertEquals( 'xxx1', $v, "Value returned" );
306 $this->assertEquals( false, $oldValReceived, "Callback got no stale value" );
307 $this->assertEquals( null, $oldAsOfReceived, "Callback got no stale value" );
309 $mockWallClock +
= 40;
310 $v = $cache->getWithSetCallback(
311 $key, 30, $checkFunc, [ 'staleTTL' => 50 ] +
$extOpts );
312 $this->assertEquals( 'xxx2', $v, "Value still returned after expired" );
313 $this->assertEquals( 2, $wasSet, "Value recalculated while expired" );
314 $this->assertEquals( 'xxx1', $oldValReceived, "Callback got stale value" );
315 $this->assertNotEquals( null, $oldAsOfReceived, "Callback got stale value" );
317 $mockWallClock +
= 260;
318 $v = $cache->getWithSetCallback(
319 $key, 30, $checkFunc, [ 'staleTTL' => 50 ] +
$extOpts );
320 $this->assertEquals( 'xxx3', $v, "Value still returned after expired" );
321 $this->assertEquals( 3, $wasSet, "Value recalculated while expired" );
322 $this->assertEquals( false, $oldValReceived, "Callback got no stale value" );
323 $this->assertEquals( null, $oldAsOfReceived, "Callback got no stale value" );
325 $mockWallClock = ( $priorTime - $cache::HOLDOFF_TTL
- 1 );
327 $key = wfRandomString();
328 $checkKey = $cache->makeKey( 'template', 'X' );
329 $cache->touchCheckKey( $checkKey ); // init check key
330 $mockWallClock = $priorTime;
331 $v = $cache->getWithSetCallback(
333 $cache::TTL_INDEFINITE
,
335 [ 'graceTTL' => $cache::TTL_WEEK
, 'checkKeys' => [ $checkKey ] ] +
$extOpts
337 $this->assertEquals( 'xxx1', $v, "Value returned" );
338 $this->assertEquals( 1, $wasSet, "Value computed" );
339 $this->assertEquals( false, $oldValReceived, "Callback got no stale value" );
340 $this->assertEquals( null, $oldAsOfReceived, "Callback got no stale value" );
342 $mockWallClock +
= $cache::TTL_HOUR
; // some time passes
343 $v = $cache->getWithSetCallback(
345 $cache::TTL_INDEFINITE
,
347 [ 'graceTTL' => $cache::TTL_WEEK
, 'checkKeys' => [ $checkKey ] ] +
$extOpts
349 $this->assertEquals( 'xxx1', $v, "Cached value returned" );
350 $this->assertEquals( 1, $wasSet, "Cached value returned" );
352 $cache->touchCheckKey( $checkKey ); // make key stale
353 $mockWallClock +
= 0.01; // ~1 week left of grace (barely stale to avoid refreshes)
355 $v = $cache->getWithSetCallback(
357 $cache::TTL_INDEFINITE
,
359 [ 'graceTTL' => $cache::TTL_WEEK
, 'checkKeys' => [ $checkKey ] ] +
$extOpts
361 $this->assertEquals( 'xxx1', $v, "Value still returned after expired (in grace)" );
362 $this->assertEquals( 1, $wasSet, "Value still returned after expired (in grace)" );
364 // Chance of refresh increase to unity as staleness approaches graceTTL
365 $mockWallClock +
= $cache::TTL_WEEK
; // 8 days of being stale
366 $v = $cache->getWithSetCallback(
368 $cache::TTL_INDEFINITE
,
370 [ 'graceTTL' => $cache::TTL_WEEK
, 'checkKeys' => [ $checkKey ] ] +
$extOpts
372 $this->assertEquals( 'xxx2', $v, "Value was recomputed (past grace)" );
373 $this->assertEquals( 2, $wasSet, "Value was recomputed (past grace)" );
374 $this->assertEquals( 'xxx1', $oldValReceived, "Callback got post-grace stale value" );
375 $this->assertNotEquals( null, $oldAsOfReceived, "Callback got post-grace stale value" );
379 * @dataProvider getWithSetCallback_provider
380 * @covers WANObjectCache::getWithSetCallback()
381 * @covers WANObjectCache::doGetWithSetCallback()
382 * @param array $extOpts
383 * @param bool $versioned
385 function testGetWithSetcallback_touched( array $extOpts, $versioned ) {
386 $cache = $this->cache
;
388 $mockWallClock = 1549343530.2053;
389 $cache->setMockTime( $mockWallClock );
391 $checkFunc = function ( $oldVal, &$ttl, array $setOpts, $oldAsOf )
395 return 'xxx' . $wasSet;
398 $key = wfRandomString();
401 $touchedCallback = function () use ( &$touched ) {
404 $v = $cache->getWithSetCallback(
406 $cache::TTL_INDEFINITE
,
408 [ 'touchedCallback' => $touchedCallback ] +
$extOpts
410 $mockWallClock +
= 60;
411 $v = $cache->getWithSetCallback(
413 $cache::TTL_INDEFINITE
,
415 [ 'touchedCallback' => $touchedCallback ] +
$extOpts
417 $this->assertEquals( 'xxx1', $v, "Value was computed once" );
418 $this->assertEquals( 1, $wasSet, "Value was computed once" );
420 $touched = $mockWallClock - 10;
421 $v = $cache->getWithSetCallback(
423 $cache::TTL_INDEFINITE
,
425 [ 'touchedCallback' => $touchedCallback ] +
$extOpts
427 $v = $cache->getWithSetCallback(
429 $cache::TTL_INDEFINITE
,
431 [ 'touchedCallback' => $touchedCallback ] +
$extOpts
433 $this->assertEquals( 'xxx2', $v, "Value was recomputed once" );
434 $this->assertEquals( 2, $wasSet, "Value was recomputed once" );
437 public static function getWithSetCallback_provider() {
440 [ [ 'version' => 1 ], true ]
444 public function testPreemtiveRefresh() {
447 $func = function ( $old, &$ttl, &$opts, $asOf ) use ( &$wasSet, &$value )
453 $cache = new NearExpiringWANObjectCache( [ 'cache' => new HashBagOStuff() ] );
454 $mockWallClock = 1549343530.2053;
455 $cache->setMockTime( $mockWallClock );
458 $key = wfRandomString();
459 $opts = [ 'lowTTL' => 30 ];
460 $v = $cache->getWithSetCallback( $key, 20, $func, $opts );
461 $this->assertEquals( $value, $v, "Value returned" );
462 $this->assertEquals( 1, $wasSet, "Value calculated" );
464 $mockWallClock +
= 0.2; // interim key is not brand new
465 $v = $cache->getWithSetCallback( $key, 20, $func, $opts );
466 $this->assertEquals( 2, $wasSet, "Value re-calculated" );
469 $key = wfRandomString();
470 $opts = [ 'lowTTL' => 1 ];
471 $v = $cache->getWithSetCallback( $key, 30, $func, $opts );
472 $this->assertEquals( $value, $v, "Value returned" );
473 $this->assertEquals( 1, $wasSet, "Value calculated" );
474 $v = $cache->getWithSetCallback( $key, 30, $func, $opts );
475 $this->assertEquals( 1, $wasSet, "Value cached" );
478 $asyncHandler = function ( $callback ) use ( &$asycList ) {
479 $asycList[] = $callback;
481 $cache = new NearExpiringWANObjectCache( [
482 'cache' => new HashBagOStuff(),
483 'asyncHandler' => $asyncHandler
486 $mockWallClock = 1549343530.2053;
487 $priorTime = $mockWallClock; // reference time
488 $cache->setMockTime( $mockWallClock );
491 $key = wfRandomString();
492 $opts = [ 'lowTTL' => 100 ];
493 $v = $cache->getWithSetCallback( $key, 300, $func, $opts );
494 $this->assertEquals( $value, $v, "Value returned" );
495 $this->assertEquals( 1, $wasSet, "Value calculated" );
496 $v = $cache->getWithSetCallback( $key, 300, $func, $opts );
497 $this->assertEquals( 1, $wasSet, "Cached value used" );
498 $this->assertEquals( $v, $value, "Value cached" );
500 $mockWallClock +
= 250;
501 $v = $cache->getWithSetCallback( $key, 300, $func, $opts );
502 $this->assertEquals( $value, $v, "Value returned" );
503 $this->assertEquals( 1, $wasSet, "Stale value used" );
504 $this->assertEquals( 1, count( $asycList ), "Refresh deferred." );
505 $value = 'NewCatsInTown'; // change callback return value
506 $asycList[0](); // run the refresh callback
508 $this->assertEquals( 2, $wasSet, "Value calculated at later time" );
509 $this->assertEquals( 0, count( $asycList ), "No deferred refreshes added." );
510 $v = $cache->getWithSetCallback( $key, 300, $func, $opts );
511 $this->assertEquals( $value, $v, "New value stored" );
513 $cache = new PopularityRefreshingWANObjectCache( [
514 'cache' => new HashBagOStuff()
517 $mockWallClock = $priorTime;
518 $cache->setMockTime( $mockWallClock );
521 $key = wfRandomString();
522 $opts = [ 'hotTTR' => 900 ];
523 $v = $cache->getWithSetCallback( $key, 60, $func, $opts );
524 $this->assertEquals( $value, $v, "Value returned" );
525 $this->assertEquals( 1, $wasSet, "Value calculated" );
527 $mockWallClock +
= 30;
529 $v = $cache->getWithSetCallback( $key, 60, $func, $opts );
530 $this->assertEquals( 1, $wasSet, "Value cached" );
532 $mockWallClock = $priorTime;
534 $key = wfRandomString();
535 $opts = [ 'hotTTR' => 10 ];
536 $v = $cache->getWithSetCallback( $key, 60, $func, $opts );
537 $this->assertEquals( $value, $v, "Value returned" );
538 $this->assertEquals( 1, $wasSet, "Value calculated" );
540 $mockWallClock +
= 30;
542 $v = $cache->getWithSetCallback( $key, 60, $func, $opts );
543 $this->assertEquals( $value, $v, "Value returned" );
544 $this->assertEquals( 2, $wasSet, "Value re-calculated" );
548 * @covers WANObjectCache::getWithSetCallback()
549 * @covers WANObjectCache::doGetWithSetCallback()
551 public function testGetWithSetCallback_invalidCallback() {
552 $this->setExpectedException( InvalidArgumentException
::class );
553 $this->cache
->getWithSetCallback( 'key', 30, 'invalid callback' );
557 * @dataProvider getMultiWithSetCallback_provider
558 * @covers WANObjectCache::getMultiWithSetCallback
559 * @covers WANObjectCache::makeMultiKeys
560 * @covers WANObjectCache::getMulti
561 * @param array $extOpts
562 * @param bool $versioned
564 public function testGetMultiWithSetCallback( array $extOpts, $versioned ) {
565 $cache = $this->cache
;
567 $keyA = wfRandomString();
568 $keyB = wfRandomString();
569 $keyC = wfRandomString();
570 $cKey1 = wfRandomString();
571 $cKey2 = wfRandomString();
576 $genFunc = function ( $id, $old, &$ttl, &$opts, $asOf ) use (
577 &$wasSet, &$priorValue, &$priorAsOf
582 $ttl = 20; // override with another value
586 $mockWallClock = 1549343530.2053;
587 $priorTime = $mockWallClock; // reference time
588 $cache->setMockTime( $mockWallClock );
591 $keyedIds = new ArrayIterator( [ $keyA => 3353 ] );
593 $v = $cache->getMultiWithSetCallback(
594 $keyedIds, 30, $genFunc, [ 'lockTSE' => 5 ] +
$extOpts );
595 $this->assertEquals( $value, $v[$keyA], "Value returned" );
596 $this->assertEquals( 1, $wasSet, "Value regenerated" );
597 $this->assertFalse( $priorValue, "No prior value" );
598 $this->assertNull( $priorAsOf, "No prior value" );
601 $cache->get( $keyA, $curTTL );
602 $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' );
603 $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' );
607 $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
608 $v = $cache->getMultiWithSetCallback(
609 $keyedIds, 30, $genFunc, [ 'lowTTL' => 0, 'lockTSE' => 5, ] +
$extOpts );
610 $this->assertEquals( $value, $v[$keyB], "Value returned" );
611 $this->assertEquals( 1, $wasSet, "Value regenerated" );
612 $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed yet in process cache" );
613 $v = $cache->getMultiWithSetCallback(
614 $keyedIds, 30, $genFunc, [ 'lowTTL' => 0, 'lockTSE' => 5, ] +
$extOpts );
615 $this->assertEquals( $value, $v[$keyB], "Value returned" );
616 $this->assertEquals( 1, $wasSet, "Value not regenerated" );
617 $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in process cache" );
622 $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
623 $v = $cache->getMultiWithSetCallback(
624 $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] +
$extOpts
626 $this->assertEquals( $value, $v[$keyB], "Value returned" );
627 $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
628 $this->assertEquals( $value, $priorValue, "Has prior value" );
629 $this->assertInternalType( 'float', $priorAsOf, "Has prior value" );
630 $t1 = $cache->getCheckKeyTime( $cKey1 );
631 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
632 $t2 = $cache->getCheckKeyTime( $cKey2 );
633 $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
635 $mockWallClock +
= 0.01;
636 $priorTime = $mockWallClock;
639 $keyedIds = new ArrayIterator( [ $keyC => 43636 ] );
640 $v = $cache->getMultiWithSetCallback(
641 $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] +
$extOpts
643 $this->assertEquals( $value, $v[$keyC], "Value returned" );
644 $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" );
645 $t1 = $cache->getCheckKeyTime( $cKey1 );
646 $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' );
647 $t2 = $cache->getCheckKeyTime( $cKey2 );
648 $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' );
651 $v = $cache->get( $keyC, $curTTL, [ $cKey1, $cKey2 ] );
653 $this->assertEquals( $value, $v[$cache::VFLD_DATA
], "Value returned" );
655 $this->assertEquals( $value, $v, "Value returned" );
657 $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
660 $key = wfRandomString();
661 $keyedIds = new ArrayIterator( [ $key => 242424 ] );
662 $v = $cache->getMultiWithSetCallback(
663 $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] +
$extOpts );
664 $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value returned" );
665 $cache->delete( $key );
666 $keyedIds = new ArrayIterator( [ $key => 242424 ] );
667 $v = $cache->getMultiWithSetCallback(
668 $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] +
$extOpts );
669 $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value still returned after deleted" );
670 $this->assertEquals( 1, $wasSet, "Value process cached while deleted" );
673 $ids = [ 1, 2, 3, 4, 5, 6 ];
674 $keyFunc = function ( $id, WANObjectCache
$wanCache ) {
675 return $wanCache->makeKey( 'test', $id );
677 $keyedIds = $cache->makeMultiKeys( $ids, $keyFunc );
678 $genFunc = function ( $id, $oldValue, &$ttl, array &$setops ) use ( &$calls ) {
683 $values = $cache->getMultiWithSetCallback( $keyedIds, 10, $genFunc );
686 [ "val-1", "val-2", "val-3", "val-4", "val-5", "val-6" ],
687 array_values( $values ),
688 "Correct values in correct order"
691 array_map( $keyFunc, $ids, array_fill( 0, count( $ids ), $this->cache
) ),
692 array_keys( $values ),
693 "Correct keys in correct order"
695 $this->assertEquals( count( $ids ), $calls );
697 $cache->getMultiWithSetCallback( $keyedIds, 10, $genFunc );
698 $this->assertEquals( count( $ids ), $calls, "Values cached" );
700 // Mock the BagOStuff to assure only one getMulti() call given process caching
701 $localBag = $this->getMockBuilder( HashBagOStuff
::class )
702 ->setMethods( [ 'getMulti' ] )->getMock();
703 $localBag->expects( $this->exactly( 1 ) )->method( 'getMulti' )->willReturn( [
704 WANObjectCache
::VALUE_KEY_PREFIX
. 'k1' => 'val-id1',
705 WANObjectCache
::VALUE_KEY_PREFIX
. 'k2' => 'val-id2'
707 $wanCache = new WANObjectCache( [ 'cache' => $localBag ] );
709 // Warm the process cache
710 $keyedIds = new ArrayIterator( [ 'k1' => 'id1', 'k2' => 'id2' ] );
712 [ 'k1' => 'val-id1', 'k2' => 'val-id2' ],
713 $wanCache->getMultiWithSetCallback( $keyedIds, 10, $genFunc, [ 'pcTTL' => 5 ] )
715 // Use the process cache
717 [ 'k1' => 'val-id1', 'k2' => 'val-id2' ],
718 $wanCache->getMultiWithSetCallback( $keyedIds, 10, $genFunc, [ 'pcTTL' => 5 ] )
722 public static function getMultiWithSetCallback_provider() {
725 [ [ 'version' => 1 ], true ]
730 * @dataProvider getMultiWithUnionSetCallback_provider
731 * @covers WANObjectCache::getMultiWithUnionSetCallback()
732 * @covers WANObjectCache::makeMultiKeys()
733 * @param array $extOpts
734 * @param bool $versioned
736 public function testGetMultiWithUnionSetCallback( array $extOpts, $versioned ) {
737 $cache = $this->cache
;
739 $keyA = wfRandomString();
740 $keyB = wfRandomString();
741 $keyC = wfRandomString();
742 $cKey1 = wfRandomString();
743 $cKey2 = wfRandomString();
746 $genFunc = function ( array $ids, array &$ttls, array &$setOpts ) use (
747 &$wasSet, &$priorValue, &$priorAsOf
750 foreach ( $ids as $id ) {
752 $newValues[$id] = "@$id$";
753 $ttls[$id] = 20; // override with another value
759 $mockWallClock = 1549343530.2053;
760 $priorTime = $mockWallClock; // reference time
761 $cache->setMockTime( $mockWallClock );
764 $keyedIds = new ArrayIterator( [ $keyA => 3353 ] );
766 $v = $cache->getMultiWithUnionSetCallback(
767 $keyedIds, 30, $genFunc, $extOpts );
768 $this->assertEquals( $value, $v[$keyA], "Value returned" );
769 $this->assertEquals( 1, $wasSet, "Value regenerated" );
772 $cache->get( $keyA, $curTTL );
773 $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' );
774 $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' );
778 $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
779 $v = $cache->getMultiWithUnionSetCallback(
780 $keyedIds, 30, $genFunc, [ 'lowTTL' => 0 ] +
$extOpts );
781 $this->assertEquals( $value, $v[$keyB], "Value returned" );
782 $this->assertEquals( 1, $wasSet, "Value regenerated" );
783 $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed yet in process cache" );
784 $v = $cache->getMultiWithUnionSetCallback(
785 $keyedIds, 30, $genFunc, [ 'lowTTL' => 0 ] +
$extOpts );
786 $this->assertEquals( $value, $v[$keyB], "Value returned" );
787 $this->assertEquals( 1, $wasSet, "Value not regenerated" );
788 $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in process cache" );
793 $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
794 $v = $cache->getMultiWithUnionSetCallback(
795 $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] +
$extOpts
797 $this->assertEquals( $value, $v[$keyB], "Value returned" );
798 $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
799 $t1 = $cache->getCheckKeyTime( $cKey1 );
800 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
801 $t2 = $cache->getCheckKeyTime( $cKey2 );
802 $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
804 $mockWallClock +
= 0.01;
805 $priorTime = $mockWallClock;
808 $keyedIds = new ArrayIterator( [ $keyC => 43636 ] );
809 $v = $cache->getMultiWithUnionSetCallback(
810 $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] +
$extOpts
812 $this->assertEquals( $value, $v[$keyC], "Value returned" );
813 $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" );
814 $t1 = $cache->getCheckKeyTime( $cKey1 );
815 $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' );
816 $t2 = $cache->getCheckKeyTime( $cKey2 );
817 $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' );
820 $v = $cache->get( $keyC, $curTTL, [ $cKey1, $cKey2 ] );
822 $this->assertEquals( $value, $v[$cache::VFLD_DATA
], "Value returned" );
824 $this->assertEquals( $value, $v, "Value returned" );
826 $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
829 $key = wfRandomString();
830 $keyedIds = new ArrayIterator( [ $key => 242424 ] );
831 $v = $cache->getMultiWithUnionSetCallback(
832 $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] +
$extOpts );
833 $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value returned" );
834 $cache->delete( $key );
835 $keyedIds = new ArrayIterator( [ $key => 242424 ] );
836 $v = $cache->getMultiWithUnionSetCallback(
837 $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] +
$extOpts );
838 $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value still returned after deleted" );
839 $this->assertEquals( 1, $wasSet, "Value process cached while deleted" );
842 $ids = [ 1, 2, 3, 4, 5, 6 ];
843 $keyFunc = function ( $id, WANObjectCache
$wanCache ) {
844 return $wanCache->makeKey( 'test', $id );
846 $keyedIds = $cache->makeMultiKeys( $ids, $keyFunc );
847 $genFunc = function ( array $ids, array &$ttls, array &$setOpts ) use ( &$calls ) {
849 foreach ( $ids as $id ) {
851 $newValues[$id] = "val-{$id}";
856 $values = $cache->getMultiWithUnionSetCallback( $keyedIds, 10, $genFunc );
859 [ "val-1", "val-2", "val-3", "val-4", "val-5", "val-6" ],
860 array_values( $values ),
861 "Correct values in correct order"
864 array_map( $keyFunc, $ids, array_fill( 0, count( $ids ), $this->cache
) ),
865 array_keys( $values ),
866 "Correct keys in correct order"
868 $this->assertEquals( count( $ids ), $calls );
870 $cache->getMultiWithUnionSetCallback( $keyedIds, 10, $genFunc );
871 $this->assertEquals( count( $ids ), $calls, "Values cached" );
874 public static function getMultiWithUnionSetCallback_provider() {
877 [ [ 'version' => 1 ], true ]
882 * @covers WANObjectCache::getWithSetCallback()
883 * @covers WANObjectCache::doGetWithSetCallback()
885 public function testLockTSE() {
886 $cache = $this->cache
;
887 $key = wfRandomString();
888 $value = wfRandomString();
890 $mockWallClock = 1549343530.2053;
891 $cache->setMockTime( $mockWallClock );
894 $func = function () use ( &$calls, $value, $cache, $key ) {
899 $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] );
900 $this->assertEquals( $value, $ret );
901 $this->assertEquals( 1, $calls, 'Value was populated' );
903 // Acquire the mutex to verify that getWithSetCallback uses lockTSE properly
904 $this->internalCache
->add( $cache::MUTEX_KEY_PREFIX
. $key, 1, 0 );
906 $checkKeys = [ wfRandomString() ]; // new check keys => force misses
907 $ret = $cache->getWithSetCallback( $key, 30, $func,
908 [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
909 $this->assertEquals( $value, $ret, 'Old value used' );
910 $this->assertEquals( 1, $calls, 'Callback was not used' );
912 $cache->delete( $key );
913 $mockWallClock +
= 0.001; // cached values will be newer than tombstone
914 $ret = $cache->getWithSetCallback( $key, 30, $func,
915 [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
916 $this->assertEquals( $value, $ret, 'Callback was used; interim saved' );
917 $this->assertEquals( 2, $calls, 'Callback was used; interim saved' );
919 $ret = $cache->getWithSetCallback( $key, 30, $func,
920 [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
921 $this->assertEquals( $value, $ret, 'Callback was not used; used interim (mutex failed)' );
922 $this->assertEquals( 2, $calls, 'Callback was not used; used interim (mutex failed)' );
926 * @covers WANObjectCache::getWithSetCallback()
927 * @covers WANObjectCache::doGetWithSetCallback()
928 * @covers WANObjectCache::set()
930 public function testLockTSESlow() {
931 $cache = $this->cache
;
932 $key = wfRandomString();
933 $key2 = wfRandomString();
934 $value = wfRandomString();
936 $mockWallClock = 1549343530.2053;
937 $cache->setMockTime( $mockWallClock );
940 $func = function ( $oldValue, &$ttl, &$setOpts ) use ( &$calls, $value, &$mockWallClock ) {
942 $setOpts['since'] = $mockWallClock - 10;
946 // Value should be given a low logical TTL due to snapshot lag
948 $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
949 $this->assertEquals( $value, $ret );
950 $this->assertEquals( $value, $cache->get( $key, $curTTL ), 'Value was populated' );
951 $this->assertEquals( 1, $curTTL, 'Value has reduced logical TTL', 0.01 );
952 $this->assertEquals( 1, $calls, 'Value was generated' );
954 $mockWallClock +
= 2; // low logical TTL expired
956 $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
957 $this->assertEquals( $value, $ret );
958 $this->assertEquals( 2, $calls, 'Callback used (mutex acquired)' );
960 $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
961 $this->assertEquals( $value, $ret );
962 $this->assertEquals( 2, $calls, 'Callback was not used (interim value used)' );
964 $mockWallClock +
= 2; // low logical TTL expired
965 // Acquire a lock to verify that getWithSetCallback uses lockTSE properly
966 $this->internalCache
->add( $cache::MUTEX_KEY_PREFIX
. $key, 1, 0 );
968 $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
969 $this->assertEquals( $value, $ret );
970 $this->assertEquals( 2, $calls, 'Callback was not used (mutex not acquired)' );
972 $mockWallClock +
= 301; // physical TTL expired
973 // Acquire a lock to verify that getWithSetCallback uses lockTSE properly
974 $this->internalCache
->add( $cache::MUTEX_KEY_PREFIX
. $key, 1, 0 );
976 $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
977 $this->assertEquals( $value, $ret );
978 $this->assertEquals( 3, $calls, 'Callback was used (mutex not acquired, not in cache)' );
981 $func2 = function ( $oldValue, &$ttl, &$setOpts ) use ( &$calls, $value ) {
983 $setOpts['lag'] = 15;
987 // Value should be given a low logical TTL due to replication lag
989 $ret = $cache->getWithSetCallback( $key2, 300, $func2, [ 'lockTSE' => 5 ] );
990 $this->assertEquals( $value, $ret );
991 $this->assertEquals( $value, $cache->get( $key2, $curTTL ), 'Value was populated' );
992 $this->assertEquals( 30, $curTTL, 'Value has reduced logical TTL', 0.01 );
993 $this->assertEquals( 1, $calls, 'Value was generated' );
995 $ret = $cache->getWithSetCallback( $key2, 300, $func2, [ 'lockTSE' => 5 ] );
996 $this->assertEquals( $value, $ret );
997 $this->assertEquals( 1, $calls, 'Callback was used (not expired)' );
999 $mockWallClock +
= 31;
1001 $ret = $cache->getWithSetCallback( $key2, 300, $func2, [ 'lockTSE' => 5 ] );
1002 $this->assertEquals( $value, $ret );
1003 $this->assertEquals( 2, $calls, 'Callback was used (mutex acquired)' );
1007 * @covers WANObjectCache::getWithSetCallback()
1008 * @covers WANObjectCache::doGetWithSetCallback()
1010 public function testBusyValue() {
1011 $cache = $this->cache
;
1012 $key = wfRandomString();
1013 $value = wfRandomString();
1014 $busyValue = wfRandomString();
1016 $mockWallClock = 1549343530.2053;
1017 $cache->setMockTime( $mockWallClock );
1020 $func = function () use ( &$calls, $value, $cache, $key ) {
1025 $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'busyValue' => $busyValue ] );
1026 $this->assertEquals( $value, $ret );
1027 $this->assertEquals( 1, $calls, 'Value was populated' );
1029 $mockWallClock +
= 0.2; // interim keys not brand new
1031 // Acquire a lock to verify that getWithSetCallback uses busyValue properly
1032 $this->internalCache
->add( $cache::MUTEX_KEY_PREFIX
. $key, 1, 0 );
1034 $checkKeys = [ wfRandomString() ]; // new check keys => force misses
1035 $ret = $cache->getWithSetCallback( $key, 30, $func,
1036 [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
1037 $this->assertEquals( $value, $ret, 'Callback used' );
1038 $this->assertEquals( 2, $calls, 'Callback used' );
1040 $ret = $cache->getWithSetCallback( $key, 30, $func,
1041 [ 'lockTSE' => 30, 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
1042 $this->assertEquals( $value, $ret, 'Old value used' );
1043 $this->assertEquals( 2, $calls, 'Callback was not used' );
1045 $cache->delete( $key ); // no value at all anymore and still locked
1046 $ret = $cache->getWithSetCallback( $key, 30, $func,
1047 [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
1048 $this->assertEquals( $busyValue, $ret, 'Callback was not used; used busy value' );
1049 $this->assertEquals( 2, $calls, 'Callback was not used; used busy value' );
1051 $this->internalCache
->delete( $cache::MUTEX_KEY_PREFIX
. $key );
1052 $mockWallClock +
= 0.001; // cached values will be newer than tombstone
1053 $ret = $cache->getWithSetCallback( $key, 30, $func,
1054 [ 'lockTSE' => 30, 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
1055 $this->assertEquals( $value, $ret, 'Callback was used; saved interim' );
1056 $this->assertEquals( 3, $calls, 'Callback was used; saved interim' );
1058 $this->internalCache
->add( $cache::MUTEX_KEY_PREFIX
. $key, 1, 0 );
1059 $ret = $cache->getWithSetCallback( $key, 30, $func,
1060 [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
1061 $this->assertEquals( $value, $ret, 'Callback was not used; used interim' );
1062 $this->assertEquals( 3, $calls, 'Callback was not used; used interim' );
1066 * @covers WANObjectCache::getMulti()
1068 public function testGetMulti() {
1069 $cache = $this->cache
;
1071 $value1 = [ 'this' => 'is', 'a' => 'test' ];
1072 $value2 = [ 'this' => 'is', 'another' => 'test' ];
1074 $key1 = wfRandomString();
1075 $key2 = wfRandomString();
1076 $key3 = wfRandomString();
1078 $mockWallClock = 1549343530.2053;
1079 $priorTime = $mockWallClock; // reference time
1080 $cache->setMockTime( $mockWallClock );
1082 $cache->set( $key1, $value1, 5 );
1083 $cache->set( $key2, $value2, 10 );
1086 $this->assertEquals(
1087 [ $key1 => $value1, $key2 => $value2 ],
1088 $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs ),
1089 'Result array populated'
1092 $this->assertEquals( 2, count( $curTTLs ), "Two current TTLs in array" );
1093 $this->assertGreaterThan( 0, $curTTLs[$key1], "Key 1 has current TTL > 0" );
1094 $this->assertGreaterThan( 0, $curTTLs[$key2], "Key 2 has current TTL > 0" );
1096 $cKey1 = wfRandomString();
1097 $cKey2 = wfRandomString();
1099 $mockWallClock +
= 1;
1102 $this->assertEquals(
1103 [ $key1 => $value1, $key2 => $value2 ],
1104 $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs, [ $cKey1, $cKey2 ] ),
1105 "Result array populated even with new check keys"
1107 $t1 = $cache->getCheckKeyTime( $cKey1 );
1108 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key 1 generated on miss' );
1109 $t2 = $cache->getCheckKeyTime( $cKey2 );
1110 $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check key 2 generated on miss' );
1111 $this->assertEquals( 2, count( $curTTLs ), "Current TTLs array set" );
1112 $this->assertLessThanOrEqual( 0, $curTTLs[$key1], 'Key 1 has current TTL <= 0' );
1113 $this->assertLessThanOrEqual( 0, $curTTLs[$key2], 'Key 2 has current TTL <= 0' );
1115 $mockWallClock +
= 1;
1118 $this->assertEquals(
1119 [ $key1 => $value1, $key2 => $value2 ],
1120 $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs, [ $cKey1, $cKey2 ] ),
1121 "Result array still populated even with new check keys"
1123 $this->assertEquals( 2, count( $curTTLs ), "Current TTLs still array set" );
1124 $this->assertLessThan( 0, $curTTLs[$key1], 'Key 1 has negative current TTL' );
1125 $this->assertLessThan( 0, $curTTLs[$key2], 'Key 2 has negative current TTL' );
1129 * @covers WANObjectCache::getMulti()
1130 * @covers WANObjectCache::processCheckKeys()
1132 public function testGetMultiCheckKeys() {
1133 $cache = $this->cache
;
1135 $checkAll = wfRandomString();
1136 $check1 = wfRandomString();
1137 $check2 = wfRandomString();
1138 $check3 = wfRandomString();
1139 $value1 = wfRandomString();
1140 $value2 = wfRandomString();
1142 $mockWallClock = 1549343530.2053;
1143 $cache->setMockTime( $mockWallClock );
1145 // Fake initial check key to be set in the past. Otherwise we'd have to sleep for
1146 // several seconds during the test to assert the behaviour.
1147 foreach ( [ $checkAll, $check1, $check2 ] as $checkKey ) {
1148 $cache->touchCheckKey( $checkKey, WANObjectCache
::HOLDOFF_NONE
);
1151 $mockWallClock +
= 0.100;
1153 $cache->set( 'key1', $value1, 10 );
1154 $cache->set( 'key2', $value2, 10 );
1157 $result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [
1163 $this->assertEquals(
1164 [ 'key1' => $value1, 'key2' => $value2 ],
1168 $this->assertGreaterThanOrEqual( 9.5, $curTTLs['key1'], 'Initial ttls' );
1169 $this->assertLessThanOrEqual( 10.5, $curTTLs['key1'], 'Initial ttls' );
1170 $this->assertGreaterThanOrEqual( 9.5, $curTTLs['key2'], 'Initial ttls' );
1171 $this->assertLessThanOrEqual( 10.5, $curTTLs['key2'], 'Initial ttls' );
1173 $mockWallClock +
= 0.100;
1174 $cache->touchCheckKey( $check1 );
1177 $result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [
1183 $this->assertEquals(
1184 [ 'key1' => $value1, 'key2' => $value2 ],
1186 'key1 expired by check1, but value still provided'
1188 $this->assertLessThan( 0, $curTTLs['key1'], 'key1 TTL expired' );
1189 $this->assertGreaterThan( 0, $curTTLs['key2'], 'key2 still valid' );
1191 $cache->touchCheckKey( $checkAll );
1194 $result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [
1200 $this->assertEquals(
1201 [ 'key1' => $value1, 'key2' => $value2 ],
1203 'All keys expired by checkAll, but value still provided'
1205 $this->assertLessThan( 0, $curTTLs['key1'], 'key1 expired by checkAll' );
1206 $this->assertLessThan( 0, $curTTLs['key2'], 'key2 expired by checkAll' );
1210 * @covers WANObjectCache::get()
1211 * @covers WANObjectCache::processCheckKeys()
1213 public function testCheckKeyInitHoldoff() {
1214 $cache = $this->cache
;
1216 for ( $i = 0; $i < 500; ++
$i ) {
1217 $key = wfRandomString();
1218 $checkKey = wfRandomString();
1220 $cache->get( $key, $curTTL, [ $checkKey ] );
1221 $cache->set( $key, 'val', 10 );
1223 $v = $cache->get( $key, $curTTL, [ $checkKey ] );
1225 $this->assertEquals( 'val', $v );
1226 $this->assertLessThan( 0, $curTTL, "Step $i: CTL < 0 (miss/set/hit)" );
1229 for ( $i = 0; $i < 500; ++
$i ) {
1230 $key = wfRandomString();
1231 $checkKey = wfRandomString();
1233 $cache->set( $key, 'val', 10 );
1235 $v = $cache->get( $key, $curTTL, [ $checkKey ] );
1237 $this->assertEquals( 'val', $v );
1238 $this->assertLessThan( 0, $curTTL, "Step $i: CTL < 0 (set/hit)" );
1243 * @covers WANObjectCache::delete
1244 * @covers WANObjectCache::relayDelete
1245 * @covers WANObjectCache::relayPurge
1247 public function testDelete() {
1248 $key = wfRandomString();
1249 $value = wfRandomString();
1250 $this->cache
->set( $key, $value );
1253 $v = $this->cache
->get( $key, $curTTL );
1254 $this->assertEquals( $value, $v, "Key was created with value" );
1255 $this->assertGreaterThan( 0, $curTTL, "Existing key has current TTL > 0" );
1257 $this->cache
->delete( $key );
1260 $v = $this->cache
->get( $key, $curTTL );
1261 $this->assertFalse( $v, "Deleted key has false value" );
1262 $this->assertLessThan( 0, $curTTL, "Deleted key has current TTL < 0" );
1264 $this->cache
->set( $key, $value . 'more' );
1265 $v = $this->cache
->get( $key, $curTTL );
1266 $this->assertFalse( $v, "Deleted key is tombstoned and has false value" );
1267 $this->assertLessThan( 0, $curTTL, "Deleted key is tombstoned and has current TTL < 0" );
1269 $this->cache
->set( $key, $value );
1270 $this->cache
->delete( $key, WANObjectCache
::HOLDOFF_NONE
);
1273 $v = $this->cache
->get( $key, $curTTL );
1274 $this->assertFalse( $v, "Deleted key has false value" );
1275 $this->assertNull( $curTTL, "Deleted key has null current TTL" );
1277 $this->cache
->set( $key, $value );
1278 $v = $this->cache
->get( $key, $curTTL );
1279 $this->assertEquals( $value, $v, "Key was created with value" );
1280 $this->assertGreaterThan( 0, $curTTL, "Existing key has current TTL > 0" );
1284 * @dataProvider getWithSetCallback_versions_provider
1285 * @covers WANObjectCache::getWithSetCallback()
1286 * @covers WANObjectCache::doGetWithSetCallback()
1287 * @param array $extOpts
1288 * @param bool $versioned
1290 public function testGetWithSetCallback_versions( array $extOpts, $versioned ) {
1291 $cache = $this->cache
;
1293 $key = wfRandomString();
1294 $valueV1 = wfRandomString();
1295 $valueV2 = [ wfRandomString() ];
1298 $funcV1 = function () use ( &$wasSet, $valueV1 ) {
1304 $priorValue = false;
1306 $funcV2 = function ( $oldValue, &$ttl, $setOpts, $oldAsOf )
1307 use ( &$wasSet, $valueV2, &$priorValue, &$priorAsOf ) {
1308 $priorValue = $oldValue;
1309 $priorAsOf = $oldAsOf;
1312 return $valueV2; // new array format
1315 // Set the main key (version N if versioned)
1317 $v = $cache->getWithSetCallback( $key, 30, $funcV1, $extOpts );
1318 $this->assertEquals( $valueV1, $v, "Value returned" );
1319 $this->assertEquals( 1, $wasSet, "Value regenerated" );
1320 $cache->getWithSetCallback( $key, 30, $funcV1, $extOpts );
1321 $this->assertEquals( 1, $wasSet, "Value not regenerated" );
1322 $this->assertEquals( $valueV1, $v, "Value not regenerated" );
1325 // Set the key for version N+1 format
1326 $verOpts = [ 'version' => $extOpts['version'] +
1 ];
1328 // Start versioning now with the unversioned key still there
1329 $verOpts = [ 'version' => 1 ];
1332 // Value goes to secondary key since V1 already used $key
1334 $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts +
$extOpts );
1335 $this->assertEquals( $valueV2, $v, "Value returned" );
1336 $this->assertEquals( 1, $wasSet, "Value regenerated" );
1337 $this->assertEquals( false, $priorValue, "Old value not given due to old format" );
1338 $this->assertEquals( null, $priorAsOf, "Old value not given due to old format" );
1341 $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts +
$extOpts );
1342 $this->assertEquals( $valueV2, $v, "Value not regenerated (secondary key)" );
1343 $this->assertEquals( 0, $wasSet, "Value not regenerated (secondary key)" );
1345 // Clear out the older or unversioned key
1346 $cache->delete( $key, 0 );
1348 // Set the key for next/first versioned format
1350 $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts +
$extOpts );
1351 $this->assertEquals( $valueV2, $v, "Value returned" );
1352 $this->assertEquals( 1, $wasSet, "Value regenerated" );
1354 $v = $cache->getWithSetCallback( $key, 30, $funcV2, $verOpts +
$extOpts );
1355 $this->assertEquals( $valueV2, $v, "Value not regenerated (main key)" );
1356 $this->assertEquals( 1, $wasSet, "Value not regenerated (main key)" );
1359 public static function getWithSetCallback_versions_provider() {
1362 [ [ 'version' => 1 ], true ]
1367 * @covers WANObjectCache::useInterimHoldOffCaching
1368 * @covers WANObjectCache::getInterimValue
1370 public function testInterimHoldOffCaching() {
1371 $cache = $this->cache
;
1373 $mockWallClock = 1549343530.2053;
1374 $cache->setMockTime( $mockWallClock );
1376 $value = 'CRL-40-940';
1378 $func = function () use ( &$wasCalled, $value ) {
1384 $cache->useInterimHoldOffCaching( true );
1386 $key = wfRandomString( 32 );
1387 $v = $cache->getWithSetCallback( $key, 60, $func );
1388 $v = $cache->getWithSetCallback( $key, 60, $func );
1389 $this->assertEquals( 1, $wasCalled, 'Value cached' );
1391 $cache->delete( $key );
1392 $mockWallClock +
= 0.001; // cached values will be newer than tombstone
1393 $v = $cache->getWithSetCallback( $key, 60, $func );
1394 $this->assertEquals( 2, $wasCalled, 'Value regenerated (got mutex)' ); // sets interim
1395 $v = $cache->getWithSetCallback( $key, 60, $func );
1396 $this->assertEquals( 2, $wasCalled, 'Value interim cached' ); // reuses interim
1398 $mockWallClock +
= 0.2; // interim key not brand new
1399 $v = $cache->getWithSetCallback( $key, 60, $func );
1400 $this->assertEquals( 3, $wasCalled, 'Value regenerated (got mutex)' ); // sets interim
1401 // Lock up the mutex so interim cache is used
1402 $this->internalCache
->add( $cache::MUTEX_KEY_PREFIX
. $key, 1, 0 );
1403 $v = $cache->getWithSetCallback( $key, 60, $func );
1404 $this->assertEquals( 3, $wasCalled, 'Value interim cached (failed mutex)' );
1405 $this->internalCache
->delete( $cache::MUTEX_KEY_PREFIX
. $key );
1407 $cache->useInterimHoldOffCaching( false );
1410 $key = wfRandomString( 32 );
1411 $v = $cache->getWithSetCallback( $key, 60, $func );
1412 $v = $cache->getWithSetCallback( $key, 60, $func );
1413 $this->assertEquals( 1, $wasCalled, 'Value cached' );
1414 $cache->delete( $key );
1415 $v = $cache->getWithSetCallback( $key, 60, $func );
1416 $this->assertEquals( 2, $wasCalled, 'Value regenerated (got mutex)' );
1417 $v = $cache->getWithSetCallback( $key, 60, $func );
1418 $this->assertEquals( 3, $wasCalled, 'Value still regenerated (got mutex)' );
1419 $v = $cache->getWithSetCallback( $key, 60, $func );
1420 $this->assertEquals( 4, $wasCalled, 'Value still regenerated (got mutex)' );
1421 // Lock up the mutex so interim cache is used
1422 $this->internalCache
->add( $cache::MUTEX_KEY_PREFIX
. $key, 1, 0 );
1423 $v = $cache->getWithSetCallback( $key, 60, $func );
1424 $this->assertEquals( 5, $wasCalled, 'Value still regenerated (failed mutex)' );
1428 * @covers WANObjectCache::touchCheckKey
1429 * @covers WANObjectCache::resetCheckKey
1430 * @covers WANObjectCache::getCheckKeyTime
1431 * @covers WANObjectCache::getMultiCheckKeyTime
1432 * @covers WANObjectCache::makePurgeValue
1433 * @covers WANObjectCache::parsePurgeValue
1435 public function testTouchKeys() {
1436 $cache = $this->cache
;
1437 $key = wfRandomString();
1439 $mockWallClock = 1549343530.2053;
1440 $priorTime = $mockWallClock; // reference time
1441 $cache->setMockTime( $mockWallClock );
1443 $mockWallClock +
= 0.100;
1444 $t0 = $cache->getCheckKeyTime( $key );
1445 $this->assertGreaterThanOrEqual( $priorTime, $t0, 'Check key auto-created' );
1447 $priorTime = $mockWallClock;
1448 $mockWallClock +
= 0.100;
1449 $cache->touchCheckKey( $key );
1450 $t1 = $cache->getCheckKeyTime( $key );
1451 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key created' );
1453 $t2 = $cache->getCheckKeyTime( $key );
1454 $this->assertEquals( $t1, $t2, 'Check key time did not change' );
1456 $mockWallClock +
= 0.100;
1457 $cache->touchCheckKey( $key );
1458 $t3 = $cache->getCheckKeyTime( $key );
1459 $this->assertGreaterThan( $t2, $t3, 'Check key time increased' );
1461 $t4 = $cache->getCheckKeyTime( $key );
1462 $this->assertEquals( $t3, $t4, 'Check key time did not change' );
1464 $mockWallClock +
= 0.100;
1465 $cache->resetCheckKey( $key );
1466 $t5 = $cache->getCheckKeyTime( $key );
1467 $this->assertGreaterThan( $t4, $t5, 'Check key time increased' );
1469 $t6 = $cache->getCheckKeyTime( $key );
1470 $this->assertEquals( $t5, $t6, 'Check key time did not change' );
1474 * @covers WANObjectCache::getMulti()
1476 public function testGetWithSeveralCheckKeys() {
1477 $key = wfRandomString();
1478 $tKey1 = wfRandomString();
1479 $tKey2 = wfRandomString();
1482 $mockWallClock = 1549343530.2053;
1483 $priorTime = $mockWallClock; // reference time
1484 $this->cache
->setMockTime( $mockWallClock );
1486 // Two check keys are newer (given hold-off) than $key, another is older
1487 $this->internalCache
->set(
1488 WANObjectCache
::TIME_KEY_PREFIX
. $tKey2,
1489 WANObjectCache
::PURGE_VAL_PREFIX
. ( $priorTime - 3 )
1491 $this->internalCache
->set(
1492 WANObjectCache
::TIME_KEY_PREFIX
. $tKey2,
1493 WANObjectCache
::PURGE_VAL_PREFIX
. ( $priorTime - 5 )
1495 $this->internalCache
->set(
1496 WANObjectCache
::TIME_KEY_PREFIX
. $tKey1,
1497 WANObjectCache
::PURGE_VAL_PREFIX
. ( $priorTime - 30 )
1499 $this->cache
->set( $key, $value, 30 );
1502 $v = $this->cache
->get( $key, $curTTL, [ $tKey1, $tKey2 ] );
1503 $this->assertEquals( $value, $v, "Value matches" );
1504 $this->assertLessThan( -4.9, $curTTL, "Correct CTL" );
1505 $this->assertGreaterThan( -5.1, $curTTL, "Correct CTL" );
1509 * @covers WANObjectCache::reap()
1510 * @covers WANObjectCache::reapCheckKey()
1512 public function testReap() {
1513 $vKey1 = wfRandomString();
1514 $vKey2 = wfRandomString();
1515 $tKey1 = wfRandomString();
1516 $tKey2 = wfRandomString();
1519 $knownPurge = time() - 60;
1520 $goodTime = microtime( true ) - 5;
1521 $badTime = microtime( true ) - 300;
1523 $this->internalCache
->set(
1524 WANObjectCache
::VALUE_KEY_PREFIX
. $vKey1,
1526 WANObjectCache
::FLD_VERSION
=> WANObjectCache
::VERSION
,
1527 WANObjectCache
::FLD_VALUE
=> $value,
1528 WANObjectCache
::FLD_TTL
=> 3600,
1529 WANObjectCache
::FLD_TIME
=> $goodTime
1532 $this->internalCache
->set(
1533 WANObjectCache
::VALUE_KEY_PREFIX
. $vKey2,
1535 WANObjectCache
::FLD_VERSION
=> WANObjectCache
::VERSION
,
1536 WANObjectCache
::FLD_VALUE
=> $value,
1537 WANObjectCache
::FLD_TTL
=> 3600,
1538 WANObjectCache
::FLD_TIME
=> $badTime
1541 $this->internalCache
->set(
1542 WANObjectCache
::TIME_KEY_PREFIX
. $tKey1,
1543 WANObjectCache
::PURGE_VAL_PREFIX
. $goodTime
1545 $this->internalCache
->set(
1546 WANObjectCache
::TIME_KEY_PREFIX
. $tKey2,
1547 WANObjectCache
::PURGE_VAL_PREFIX
. $badTime
1550 $this->assertEquals( $value, $this->cache
->get( $vKey1 ) );
1551 $this->assertEquals( $value, $this->cache
->get( $vKey2 ) );
1552 $this->cache
->reap( $vKey1, $knownPurge, $bad1 );
1553 $this->cache
->reap( $vKey2, $knownPurge, $bad2 );
1555 $this->assertFalse( $bad1 );
1556 $this->assertTrue( $bad2 );
1558 $this->cache
->reapCheckKey( $tKey1, $knownPurge, $tBad1 );
1559 $this->cache
->reapCheckKey( $tKey2, $knownPurge, $tBad2 );
1560 $this->assertFalse( $tBad1 );
1561 $this->assertTrue( $tBad2 );
1565 * @covers WANObjectCache::reap()
1567 public function testReap_fail() {
1568 $backend = $this->getMockBuilder( EmptyBagOStuff
::class )
1569 ->setMethods( [ 'get', 'changeTTL' ] )->getMock();
1570 $backend->expects( $this->once() )->method( 'get' )
1572 WANObjectCache
::FLD_VERSION
=> WANObjectCache
::VERSION
,
1573 WANObjectCache
::FLD_VALUE
=> 'value',
1574 WANObjectCache
::FLD_TTL
=> 3600,
1575 WANObjectCache
::FLD_TIME
=> 300,
1577 $backend->expects( $this->once() )->method( 'changeTTL' )
1578 ->willReturn( false );
1580 $wanCache = new WANObjectCache( [
1585 $ret = $wanCache->reap( 'key', 360, $isStale );
1586 $this->assertTrue( $isStale, 'value was stale' );
1587 $this->assertFalse( $ret, 'changeTTL failed' );
1591 * @covers WANObjectCache::set()
1593 public function testSetWithLag() {
1596 $key = wfRandomString();
1597 $opts = [ 'lag' => 300, 'since' => microtime( true ) ];
1598 $this->cache
->set( $key, $value, 30, $opts );
1599 $this->assertEquals( $value, $this->cache
->get( $key ), "Rep-lagged value written." );
1601 $key = wfRandomString();
1602 $opts = [ 'lag' => 0, 'since' => microtime( true ) - 300 ];
1603 $this->cache
->set( $key, $value, 30, $opts );
1604 $this->assertEquals( false, $this->cache
->get( $key ), "Trx-lagged value not written." );
1606 $key = wfRandomString();
1607 $opts = [ 'lag' => 5, 'since' => microtime( true ) - 5 ];
1608 $this->cache
->set( $key, $value, 30, $opts );
1609 $this->assertEquals( false, $this->cache
->get( $key ), "Lagged value not written." );
1613 * @covers WANObjectCache::set()
1615 public function testWritePending() {
1618 $key = wfRandomString();
1619 $opts = [ 'pending' => true ];
1620 $this->cache
->set( $key, $value, 30, $opts );
1621 $this->assertEquals( false, $this->cache
->get( $key ), "Pending value not written." );
1624 public function testMcRouterSupport() {
1625 $localBag = $this->getMockBuilder( EmptyBagOStuff
::class )
1626 ->setMethods( [ 'set', 'delete' ] )->getMock();
1627 $localBag->expects( $this->never() )->method( 'set' );
1628 $localBag->expects( $this->never() )->method( 'delete' );
1629 $wanCache = new WANObjectCache( [
1630 'cache' => $localBag,
1631 'mcrouterAware' => true,
1632 'region' => 'pmtpa',
1633 'cluster' => 'mw-wan'
1635 $valFunc = function () {
1639 // None of these should use broadcasting commands (e.g. SET, DELETE)
1640 $wanCache->get( 'x' );
1641 $wanCache->get( 'x', $ctl, [ 'check1' ] );
1642 $wanCache->getMulti( [ 'x', 'y' ] );
1643 $wanCache->getMulti( [ 'x', 'y' ], $ctls, [ 'check2' ] );
1644 $wanCache->getWithSetCallback( 'p', 30, $valFunc );
1645 $wanCache->getCheckKeyTime( 'zzz' );
1646 $wanCache->reap( 'x', time() - 300 );
1647 $wanCache->reap( 'zzz', time() - 300 );
1650 public function testMcRouterSupportBroadcastDelete() {
1651 $localBag = $this->getMockBuilder( EmptyBagOStuff
::class )
1652 ->setMethods( [ 'set' ] )->getMock();
1653 $wanCache = new WANObjectCache( [
1654 'cache' => $localBag,
1655 'mcrouterAware' => true,
1656 'region' => 'pmtpa',
1657 'cluster' => 'mw-wan'
1660 $localBag->expects( $this->once() )->method( 'set' )
1661 ->with( "/*/mw-wan/" . $wanCache::VALUE_KEY_PREFIX
. "test" );
1663 $wanCache->delete( 'test' );
1666 public function testMcRouterSupportBroadcastTouchCK() {
1667 $localBag = $this->getMockBuilder( EmptyBagOStuff
::class )
1668 ->setMethods( [ 'set' ] )->getMock();
1669 $wanCache = new WANObjectCache( [
1670 'cache' => $localBag,
1671 'mcrouterAware' => true,
1672 'region' => 'pmtpa',
1673 'cluster' => 'mw-wan'
1676 $localBag->expects( $this->once() )->method( 'set' )
1677 ->with( "/*/mw-wan/" . $wanCache::TIME_KEY_PREFIX
. "test" );
1679 $wanCache->touchCheckKey( 'test' );
1682 public function testMcRouterSupportBroadcastResetCK() {
1683 $localBag = $this->getMockBuilder( EmptyBagOStuff
::class )
1684 ->setMethods( [ 'delete' ] )->getMock();
1685 $wanCache = new WANObjectCache( [
1686 'cache' => $localBag,
1687 'mcrouterAware' => true,
1688 'region' => 'pmtpa',
1689 'cluster' => 'mw-wan'
1692 $localBag->expects( $this->once() )->method( 'delete' )
1693 ->with( "/*/mw-wan/" . $wanCache::TIME_KEY_PREFIX
. "test" );
1695 $wanCache->resetCheckKey( 'test' );
1698 public function testEpoch() {
1699 $bag = new HashBagOStuff();
1700 $cache = new WANObjectCache( [ 'cache' => $bag ] );
1701 $key = $cache->makeGlobalKey( 'The whole of the Law' );
1703 $now = microtime( true );
1704 $cache->setMockTime( $now );
1706 $cache->set( $key, 'Do what thou Wilt' );
1707 $cache->touchCheckKey( $key );
1711 $this->assertEquals( 'Do what thou Wilt', $cache->get( $key ) );
1712 $this->assertEquals( $then, $cache->getCheckKeyTime( $key ), 'Check key init', 0.01 );
1714 $cache = new WANObjectCache( [
1716 'epoch' => $now - 3600
1718 $cache->setMockTime( $now );
1720 $this->assertEquals( 'Do what thou Wilt', $cache->get( $key ) );
1721 $this->assertEquals( $then, $cache->getCheckKeyTime( $key ), 'Check key kept', 0.01 );
1724 $cache = new WANObjectCache( [
1726 'epoch' => $now +
3600
1728 $cache->setMockTime( $now );
1730 $this->assertFalse( $cache->get( $key ), 'Key rejected due to epoch' );
1731 $this->assertEquals( $now, $cache->getCheckKeyTime( $key ), 'Check key reset', 0.01 );
1735 * @dataProvider provideAdaptiveTTL
1736 * @covers WANObjectCache::adaptiveTTL()
1737 * @param float|int $ago
1738 * @param int $maxTTL
1739 * @param int $minTTL
1740 * @param float $factor
1741 * @param int $adaptiveTTL
1743 public function testAdaptiveTTL( $ago, $maxTTL, $minTTL, $factor, $adaptiveTTL ) {
1744 $mtime = $ago ?
time() - $ago : $ago;
1746 $ttl = $this->cache
->adaptiveTTL( $mtime, $maxTTL, $minTTL, $factor );
1748 $this->assertGreaterThanOrEqual( $adaptiveTTL - $margin, $ttl );
1749 $this->assertLessThanOrEqual( $adaptiveTTL +
$margin, $ttl );
1751 $ttl = $this->cache
->adaptiveTTL( (string)$mtime, $maxTTL, $minTTL, $factor );
1753 $this->assertGreaterThanOrEqual( $adaptiveTTL - $margin, $ttl );
1754 $this->assertLessThanOrEqual( $adaptiveTTL +
$margin, $ttl );
1757 public static function provideAdaptiveTTL() {
1759 [ 3600, 900, 30, 0.2, 720 ],
1760 [ 3600, 500, 30, 0.2, 500 ],
1761 [ 3600, 86400, 800, 0.2, 800 ],
1762 [ false, 86400, 800, 0.2, 800 ],
1763 [ null, 86400, 800, 0.2, 800 ]
1768 * @covers WANObjectCache::__construct
1769 * @covers WANObjectCache::newEmpty
1771 public function testNewEmpty() {
1772 $this->assertInstanceOf(
1773 WANObjectCache
::class,
1774 WANObjectCache
::newEmpty()
1779 * @covers WANObjectCache::setLogger
1781 public function testSetLogger() {
1782 $this->assertSame( null, $this->cache
->setLogger( new Psr\Log\NullLogger
) );
1786 * @covers WANObjectCache::getQoS
1788 public function testGetQoS() {
1789 $backend = $this->getMockBuilder( HashBagOStuff
::class )
1790 ->setMethods( [ 'getQoS' ] )->getMock();
1791 $backend->expects( $this->once() )->method( 'getQoS' )
1792 ->willReturn( BagOStuff
::QOS_UNKNOWN
);
1793 $wanCache = new WANObjectCache( [ 'cache' => $backend ] );
1796 $wanCache::QOS_UNKNOWN
,
1797 $wanCache->getQoS( $wanCache::ATTR_EMULATION
)
1802 * @covers WANObjectCache::makeKey
1804 public function testMakeKey() {
1805 $backend = $this->getMockBuilder( HashBagOStuff
::class )
1806 ->setMethods( [ 'makeKey' ] )->getMock();
1807 $backend->expects( $this->once() )->method( 'makeKey' )
1808 ->willReturn( 'special' );
1810 $wanCache = new WANObjectCache( [
1814 $this->assertSame( 'special', $wanCache->makeKey( 'a', 'b' ) );
1818 * @covers WANObjectCache::makeGlobalKey
1820 public function testMakeGlobalKey() {
1821 $backend = $this->getMockBuilder( HashBagOStuff
::class )
1822 ->setMethods( [ 'makeGlobalKey' ] )->getMock();
1823 $backend->expects( $this->once() )->method( 'makeGlobalKey' )
1824 ->willReturn( 'special' );
1826 $wanCache = new WANObjectCache( [
1830 $this->assertSame( 'special', $wanCache->makeGlobalKey( 'a', 'b' ) );
1833 public static function statsKeyProvider() {
1835 [ 'domain:page:5', 'page' ],
1836 [ 'domain:main-key', 'main-key' ],
1837 [ 'domain:page:history', 'page' ],
1838 [ 'missingdomainkey', 'missingdomainkey' ]
1843 * @dataProvider statsKeyProvider
1844 * @covers WANObjectCache::determineKeyClassForStats
1846 public function testStatsKeyClass( $key, $class ) {
1847 $wanCache = TestingAccessWrapper
::newFromObject( new WANObjectCache( [
1848 'cache' => new HashBagOStuff
1851 $this->assertEquals( $class, $wanCache->determineKeyClassForStats( $key ) );
1855 class NearExpiringWANObjectCache
extends WANObjectCache
{
1856 const CLOCK_SKEW
= 1;
1858 protected function worthRefreshExpiring( $curTTL, $lowTTL ) {
1859 return ( $curTTL > 0 && ( $curTTL + self
::CLOCK_SKEW
) < $lowTTL );
1863 class PopularityRefreshingWANObjectCache
extends WANObjectCache
{
1864 protected function worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now ) {
1865 return ( ( $now - $asOf ) > $timeTillRefresh );