]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / tests / phpunit / includes / libs / objectcache / WANObjectCacheTest.php
1 <?php
2
3 use Wikimedia\TestingAccessWrapper;
4
5 class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
6         /** @var WANObjectCache */
7         private $cache;
8         /**@var BagOStuff */
9         private $internalCache;
10
11         protected function setUp() {
12                 parent::setUp();
13
14                 $this->cache = new WANObjectCache( [
15                         'cache' => new HashBagOStuff(),
16                         'pool' => 'testcache-hash',
17                         'relayer' => new EventRelayerNull( [] )
18                 ] );
19
20                 $wanCache = TestingAccessWrapper::newFromObject( $this->cache );
21                 /** @noinspection PhpUndefinedFieldInspection */
22                 $this->internalCache = $wanCache->cache;
23         }
24
25         /**
26          * @dataProvider provideSetAndGet
27          * @covers WANObjectCache::set()
28          * @covers WANObjectCache::get()
29          * @covers WANObjectCache::makeKey()
30          * @param mixed $value
31          * @param int $ttl
32          */
33         public function testSetAndGet( $value, $ttl ) {
34                 $curTTL = null;
35                 $asOf = null;
36                 $key = $this->cache->makeKey( 'x', wfRandomString() );
37
38                 $this->cache->get( $key, $curTTL, [], $asOf );
39                 $this->assertNull( $curTTL, "Current TTL is null" );
40                 $this->assertNull( $asOf, "Current as-of-time is infinite" );
41
42                 $t = microtime( true );
43                 $this->cache->set( $key, $value, $ttl );
44
45                 $this->assertEquals( $value, $this->cache->get( $key, $curTTL, [], $asOf ) );
46                 if ( is_infinite( $ttl ) || $ttl == 0 ) {
47                         $this->assertTrue( is_infinite( $curTTL ), "Current TTL is infinite" );
48                 } else {
49                         $this->assertGreaterThan( 0, $curTTL, "Current TTL > 0" );
50                         $this->assertLessThanOrEqual( $ttl, $curTTL, "Current TTL < nominal TTL" );
51                 }
52                 $this->assertGreaterThanOrEqual( $t - 1, $asOf, "As-of-time in range of set() time" );
53                 $this->assertLessThanOrEqual( $t + 1, $asOf, "As-of-time in range of set() time" );
54         }
55
56         public static function provideSetAndGet() {
57                 return [
58                         [ 14141, 3 ],
59                         [ 3535.666, 3 ],
60                         [ [], 3 ],
61                         [ null, 3 ],
62                         [ '0', 3 ],
63                         [ (object)[ 'meow' ], 3 ],
64                         [ INF, 3 ],
65                         [ '', 3 ],
66                         [ 'pizzacat', INF ],
67                 ];
68         }
69
70         /**
71          * @covers WANObjectCache::get()
72          * @covers WANObjectCache::makeGlobalKey()
73          */
74         public function testGetNotExists() {
75                 $key = $this->cache->makeGlobalKey( 'y', wfRandomString(), 'p' );
76                 $curTTL = null;
77                 $value = $this->cache->get( $key, $curTTL );
78
79                 $this->assertFalse( $value, "Non-existing key has false value" );
80                 $this->assertNull( $curTTL, "Non-existing key has null current TTL" );
81         }
82
83         /**
84          * @covers WANObjectCache::set()
85          */
86         public function testSetOver() {
87                 $key = wfRandomString();
88                 for ( $i = 0; $i < 3; ++$i ) {
89                         $value = wfRandomString();
90                         $this->cache->set( $key, $value, 3 );
91
92                         $this->assertEquals( $this->cache->get( $key ), $value );
93                 }
94         }
95
96         /**
97          * @covers WANObjectCache::set()
98          */
99         public function testStaleSet() {
100                 $key = wfRandomString();
101                 $value = wfRandomString();
102                 $this->cache->set( $key, $value, 3, [ 'since' => microtime( true ) - 30 ] );
103
104                 $this->assertFalse( $this->cache->get( $key ), "Stale set() value ignored" );
105         }
106
107         public function testProcessCache() {
108                 $hit = 0;
109                 $callback = function () use ( &$hit ) {
110                         ++$hit;
111                         return 42;
112                 };
113                 $keys = [ wfRandomString(), wfRandomString(), wfRandomString() ];
114                 $groups = [ 'thiscache:1', 'thatcache:1', 'somecache:1' ];
115
116                 foreach ( $keys as $i => $key ) {
117                         $this->cache->getWithSetCallback(
118                                 $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
119                 }
120                 $this->assertEquals( 3, $hit );
121
122                 foreach ( $keys as $i => $key ) {
123                         $this->cache->getWithSetCallback(
124                                 $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
125                 }
126                 $this->assertEquals( 3, $hit, "Values cached" );
127
128                 foreach ( $keys as $i => $key ) {
129                         $this->cache->getWithSetCallback(
130                                 "$key-2", 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
131                 }
132                 $this->assertEquals( 6, $hit );
133
134                 foreach ( $keys as $i => $key ) {
135                         $this->cache->getWithSetCallback(
136                                 "$key-2", 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
137                 }
138                 $this->assertEquals( 6, $hit, "New values cached" );
139
140                 foreach ( $keys as $i => $key ) {
141                         $this->cache->delete( $key );
142                         $this->cache->getWithSetCallback(
143                                 $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
144                 }
145                 $this->assertEquals( 9, $hit, "Values evicted" );
146
147                 $key = reset( $keys );
148                 // Get into cache
149                 $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
150                 $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
151                 $this->assertEquals( 10, $hit, "Value cached" );
152                 $outerCallback = function () use ( &$callback, $key ) {
153                         $v = $this->cache->getWithSetCallback( $key, 100, $callback, [ 'pcTTL' => 5 ] );
154
155                         return 43 + $v;
156                 };
157                 $this->cache->getWithSetCallback( $key, 100, $outerCallback );
158                 $this->assertEquals( 11, $hit, "Nested callback value process cache skipped" );
159         }
160
161         /**
162          * @dataProvider getWithSetCallback_provider
163          * @covers WANObjectCache::getWithSetCallback()
164          * @covers WANObjectCache::doGetWithSetCallback()
165          * @param array $extOpts
166          * @param bool $versioned
167          */
168         public function testGetWithSetCallback( array $extOpts, $versioned ) {
169                 $cache = $this->cache;
170
171                 $key = wfRandomString();
172                 $value = wfRandomString();
173                 $cKey1 = wfRandomString();
174                 $cKey2 = wfRandomString();
175
176                 $priorValue = null;
177                 $priorAsOf = null;
178                 $wasSet = 0;
179                 $func = function ( $old, &$ttl, &$opts, $asOf )
180                 use ( &$wasSet, &$priorValue, &$priorAsOf, $value )
181                 {
182                         ++$wasSet;
183                         $priorValue = $old;
184                         $priorAsOf = $asOf;
185                         $ttl = 20; // override with another value
186                         return $value;
187                 };
188
189                 $wasSet = 0;
190                 $v = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] + $extOpts );
191                 $this->assertEquals( $value, $v, "Value returned" );
192                 $this->assertEquals( 1, $wasSet, "Value regenerated" );
193                 $this->assertFalse( $priorValue, "No prior value" );
194                 $this->assertNull( $priorAsOf, "No prior value" );
195
196                 $curTTL = null;
197                 $cache->get( $key, $curTTL );
198                 $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' );
199                 $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' );
200
201                 $wasSet = 0;
202                 $v = $cache->getWithSetCallback( $key, 30, $func, [
203                                 'lowTTL' => 0,
204                                 'lockTSE' => 5,
205                         ] + $extOpts );
206                 $this->assertEquals( $value, $v, "Value returned" );
207                 $this->assertEquals( 0, $wasSet, "Value not regenerated" );
208
209                 $priorTime = microtime( true );
210                 usleep( 1 );
211                 $wasSet = 0;
212                 $v = $cache->getWithSetCallback(
213                         $key, 30, $func, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
214                 );
215                 $this->assertEquals( $value, $v, "Value returned" );
216                 $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
217                 $this->assertEquals( $value, $priorValue, "Has prior value" );
218                 $this->assertInternalType( 'float', $priorAsOf, "Has prior value" );
219                 $t1 = $cache->getCheckKeyTime( $cKey1 );
220                 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
221                 $t2 = $cache->getCheckKeyTime( $cKey2 );
222                 $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
223
224                 $priorTime = microtime( true );
225                 $wasSet = 0;
226                 $v = $cache->getWithSetCallback(
227                         $key, 30, $func, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
228                 );
229                 $this->assertEquals( $value, $v, "Value returned" );
230                 $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" );
231                 $t1 = $cache->getCheckKeyTime( $cKey1 );
232                 $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' );
233                 $t2 = $cache->getCheckKeyTime( $cKey2 );
234                 $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' );
235
236                 $curTTL = null;
237                 $v = $cache->get( $key, $curTTL, [ $cKey1, $cKey2 ] );
238                 if ( $versioned ) {
239                         $this->assertEquals( $value, $v[$cache::VFLD_DATA], "Value returned" );
240                 } else {
241                         $this->assertEquals( $value, $v, "Value returned" );
242                 }
243                 $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
244
245                 $wasSet = 0;
246                 $key = wfRandomString();
247                 $v = $cache->getWithSetCallback( $key, 30, $func, [ 'pcTTL' => 5 ] + $extOpts );
248                 $this->assertEquals( $value, $v, "Value returned" );
249                 $cache->delete( $key );
250                 $v = $cache->getWithSetCallback( $key, 30, $func, [ 'pcTTL' => 5 ] + $extOpts );
251                 $this->assertEquals( $value, $v, "Value still returned after deleted" );
252                 $this->assertEquals( 1, $wasSet, "Value process cached while deleted" );
253         }
254
255         public static function getWithSetCallback_provider() {
256                 return [
257                         [ [], false ],
258                         [ [ 'version' => 1 ], true ]
259                 ];
260         }
261
262         /**
263          * @dataProvider getMultiWithSetCallback_provider
264          * @covers WANObjectCache::getMultiWithSetCallback()
265          * @covers WANObjectCache::makeMultiKeys()
266          * @param array $extOpts
267          * @param bool $versioned
268          */
269         public function testGetMultiWithSetCallback( array $extOpts, $versioned ) {
270                 $cache = $this->cache;
271
272                 $keyA = wfRandomString();
273                 $keyB = wfRandomString();
274                 $keyC = wfRandomString();
275                 $cKey1 = wfRandomString();
276                 $cKey2 = wfRandomString();
277
278                 $priorValue = null;
279                 $priorAsOf = null;
280                 $wasSet = 0;
281                 $genFunc = function ( $id, $old, &$ttl, &$opts, $asOf ) use (
282                         &$wasSet, &$priorValue, &$priorAsOf
283                 ) {
284                         ++$wasSet;
285                         $priorValue = $old;
286                         $priorAsOf = $asOf;
287                         $ttl = 20; // override with another value
288                         return "@$id$";
289                 };
290
291                 $wasSet = 0;
292                 $keyedIds = new ArrayIterator( [ $keyA => 3353 ] );
293                 $value = "@3353$";
294                 $v = $cache->getMultiWithSetCallback(
295                         $keyedIds, 30, $genFunc, [ 'lockTSE' => 5 ] + $extOpts );
296                 $this->assertEquals( $value, $v[$keyA], "Value returned" );
297                 $this->assertEquals( 1, $wasSet, "Value regenerated" );
298                 $this->assertFalse( $priorValue, "No prior value" );
299                 $this->assertNull( $priorAsOf, "No prior value" );
300
301                 $curTTL = null;
302                 $cache->get( $keyA, $curTTL );
303                 $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' );
304                 $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' );
305
306                 $wasSet = 0;
307                 $value = "@efef$";
308                 $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
309                 $v = $cache->getMultiWithSetCallback(
310                         $keyedIds, 30, $genFunc, [ 'lowTTL' => 0, 'lockTSE' => 5, ] + $extOpts );
311                 $this->assertEquals( $value, $v[$keyB], "Value returned" );
312                 $this->assertEquals( 1, $wasSet, "Value regenerated" );
313                 $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed yet in process cache" );
314                 $v = $cache->getMultiWithSetCallback(
315                         $keyedIds, 30, $genFunc, [ 'lowTTL' => 0, 'lockTSE' => 5, ] + $extOpts );
316                 $this->assertEquals( $value, $v[$keyB], "Value returned" );
317                 $this->assertEquals( 1, $wasSet, "Value not regenerated" );
318                 $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in process cache" );
319
320                 $priorTime = microtime( true );
321                 usleep( 1 );
322                 $wasSet = 0;
323                 $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
324                 $v = $cache->getMultiWithSetCallback(
325                         $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
326                 );
327                 $this->assertEquals( $value, $v[$keyB], "Value returned" );
328                 $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
329                 $this->assertEquals( $value, $priorValue, "Has prior value" );
330                 $this->assertInternalType( 'float', $priorAsOf, "Has prior value" );
331                 $t1 = $cache->getCheckKeyTime( $cKey1 );
332                 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
333                 $t2 = $cache->getCheckKeyTime( $cKey2 );
334                 $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
335
336                 $priorTime = microtime( true );
337                 $value = "@43636$";
338                 $wasSet = 0;
339                 $keyedIds = new ArrayIterator( [ $keyC => 43636 ] );
340                 $v = $cache->getMultiWithSetCallback(
341                         $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
342                 );
343                 $this->assertEquals( $value, $v[$keyC], "Value returned" );
344                 $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" );
345                 $t1 = $cache->getCheckKeyTime( $cKey1 );
346                 $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' );
347                 $t2 = $cache->getCheckKeyTime( $cKey2 );
348                 $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' );
349
350                 $curTTL = null;
351                 $v = $cache->get( $keyC, $curTTL, [ $cKey1, $cKey2 ] );
352                 if ( $versioned ) {
353                         $this->assertEquals( $value, $v[$cache::VFLD_DATA], "Value returned" );
354                 } else {
355                         $this->assertEquals( $value, $v, "Value returned" );
356                 }
357                 $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
358
359                 $wasSet = 0;
360                 $key = wfRandomString();
361                 $keyedIds = new ArrayIterator( [ $key => 242424 ] );
362                 $v = $cache->getMultiWithSetCallback(
363                         $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] + $extOpts );
364                 $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value returned" );
365                 $cache->delete( $key );
366                 $keyedIds = new ArrayIterator( [ $key => 242424 ] );
367                 $v = $cache->getMultiWithSetCallback(
368                         $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] + $extOpts );
369                 $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value still returned after deleted" );
370                 $this->assertEquals( 1, $wasSet, "Value process cached while deleted" );
371
372                 $calls = 0;
373                 $ids = [ 1, 2, 3, 4, 5, 6 ];
374                 $keyFunc = function ( $id, WANObjectCache $wanCache ) {
375                         return $wanCache->makeKey( 'test', $id );
376                 };
377                 $keyedIds = $cache->makeMultiKeys( $ids, $keyFunc );
378                 $genFunc = function ( $id, $oldValue, &$ttl, array &$setops ) use ( &$calls ) {
379                         ++$calls;
380
381                         return "val-{$id}";
382                 };
383                 $values = $cache->getMultiWithSetCallback( $keyedIds, 10, $genFunc );
384
385                 $this->assertEquals(
386                         [ "val-1", "val-2", "val-3", "val-4", "val-5", "val-6" ],
387                         array_values( $values ),
388                         "Correct values in correct order"
389                 );
390                 $this->assertEquals(
391                         array_map( $keyFunc, $ids, array_fill( 0, count( $ids ), $this->cache ) ),
392                         array_keys( $values ),
393                         "Correct keys in correct order"
394                 );
395                 $this->assertEquals( count( $ids ), $calls );
396
397                 $cache->getMultiWithSetCallback( $keyedIds, 10, $genFunc );
398                 $this->assertEquals( count( $ids ), $calls, "Values cached" );
399
400                 // Mock the BagOStuff to assure only one getMulti() call given process caching
401                 $localBag = $this->getMockBuilder( 'HashBagOStuff' )
402                         ->setMethods( [ 'getMulti' ] )->getMock();
403                 $localBag->expects( $this->exactly( 1 ) )->method( 'getMulti' )->willReturn( [
404                         WANObjectCache::VALUE_KEY_PREFIX . 'k1' => 'val-id1',
405                         WANObjectCache::VALUE_KEY_PREFIX . 'k2' => 'val-id2'
406                 ] );
407                 $wanCache = new WANObjectCache( [ 'cache' => $localBag, 'pool' => 'testcache-hash' ] );
408
409                 // Warm the process cache
410                 $keyedIds = new ArrayIterator( [ 'k1' => 'id1', 'k2' => 'id2' ] );
411                 $this->assertEquals(
412                         [ 'k1' => 'val-id1', 'k2' => 'val-id2' ],
413                         $wanCache->getMultiWithSetCallback( $keyedIds, 10, $genFunc, [ 'pcTTL' => 5 ] )
414                 );
415                 // Use the process cache
416                 $this->assertEquals(
417                         [ 'k1' => 'val-id1', 'k2' => 'val-id2' ],
418                         $wanCache->getMultiWithSetCallback( $keyedIds, 10, $genFunc, [ 'pcTTL' => 5 ] )
419                 );
420         }
421
422         public static function getMultiWithSetCallback_provider() {
423                 return [
424                         [ [], false ],
425                         [ [ 'version' => 1 ], true ]
426                 ];
427         }
428
429         /**
430          * @dataProvider getMultiWithUnionSetCallback_provider
431          * @covers WANObjectCache::getMultiWithUnionSetCallback()
432          * @covers WANObjectCache::makeMultiKeys()
433          * @param array $extOpts
434          * @param bool $versioned
435          */
436         public function testGetMultiWithUnionSetCallback( array $extOpts, $versioned ) {
437                 $cache = $this->cache;
438
439                 $keyA = wfRandomString();
440                 $keyB = wfRandomString();
441                 $keyC = wfRandomString();
442                 $cKey1 = wfRandomString();
443                 $cKey2 = wfRandomString();
444
445                 $wasSet = 0;
446                 $genFunc = function ( array $ids, array &$ttls, array &$setOpts ) use (
447                         &$wasSet, &$priorValue, &$priorAsOf
448                 ) {
449                         $newValues = [];
450                         foreach ( $ids as $id ) {
451                                 ++$wasSet;
452                                 $newValues[$id] = "@$id$";
453                                 $ttls[$id] = 20; // override with another value
454                         }
455
456                         return $newValues;
457                 };
458
459                 $wasSet = 0;
460                 $keyedIds = new ArrayIterator( [ $keyA => 3353 ] );
461                 $value = "@3353$";
462                 $v = $cache->getMultiWithUnionSetCallback(
463                         $keyedIds, 30, $genFunc, $extOpts );
464                 $this->assertEquals( $value, $v[$keyA], "Value returned" );
465                 $this->assertEquals( 1, $wasSet, "Value regenerated" );
466
467                 $curTTL = null;
468                 $cache->get( $keyA, $curTTL );
469                 $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' );
470                 $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' );
471
472                 $wasSet = 0;
473                 $value = "@efef$";
474                 $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
475                 $v = $cache->getMultiWithUnionSetCallback(
476                         $keyedIds, 30, $genFunc, [ 'lowTTL' => 0 ] + $extOpts );
477                 $this->assertEquals( $value, $v[$keyB], "Value returned" );
478                 $this->assertEquals( 1, $wasSet, "Value regenerated" );
479                 $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed yet in process cache" );
480                 $v = $cache->getMultiWithUnionSetCallback(
481                         $keyedIds, 30, $genFunc, [ 'lowTTL' => 0 ] + $extOpts );
482                 $this->assertEquals( $value, $v[$keyB], "Value returned" );
483                 $this->assertEquals( 1, $wasSet, "Value not regenerated" );
484                 $this->assertEquals( 0, $cache->getWarmupKeyMisses(), "Keys warmed in process cache" );
485
486                 $priorTime = microtime( true );
487                 usleep( 1 );
488                 $wasSet = 0;
489                 $keyedIds = new ArrayIterator( [ $keyB => 'efef' ] );
490                 $v = $cache->getMultiWithUnionSetCallback(
491                         $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
492                 );
493                 $this->assertEquals( $value, $v[$keyB], "Value returned" );
494                 $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
495                 $t1 = $cache->getCheckKeyTime( $cKey1 );
496                 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
497                 $t2 = $cache->getCheckKeyTime( $cKey2 );
498                 $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
499
500                 $priorTime = microtime( true );
501                 $value = "@43636$";
502                 $wasSet = 0;
503                 $keyedIds = new ArrayIterator( [ $keyC => 43636 ] );
504                 $v = $cache->getMultiWithUnionSetCallback(
505                         $keyedIds, 30, $genFunc, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
506                 );
507                 $this->assertEquals( $value, $v[$keyC], "Value returned" );
508                 $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" );
509                 $t1 = $cache->getCheckKeyTime( $cKey1 );
510                 $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' );
511                 $t2 = $cache->getCheckKeyTime( $cKey2 );
512                 $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' );
513
514                 $curTTL = null;
515                 $v = $cache->get( $keyC, $curTTL, [ $cKey1, $cKey2 ] );
516                 if ( $versioned ) {
517                         $this->assertEquals( $value, $v[$cache::VFLD_DATA], "Value returned" );
518                 } else {
519                         $this->assertEquals( $value, $v, "Value returned" );
520                 }
521                 $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
522
523                 $wasSet = 0;
524                 $key = wfRandomString();
525                 $keyedIds = new ArrayIterator( [ $key => 242424 ] );
526                 $v = $cache->getMultiWithUnionSetCallback(
527                         $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] + $extOpts );
528                 $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value returned" );
529                 $cache->delete( $key );
530                 $keyedIds = new ArrayIterator( [ $key => 242424 ] );
531                 $v = $cache->getMultiWithUnionSetCallback(
532                         $keyedIds, 30, $genFunc, [ 'pcTTL' => 5 ] + $extOpts );
533                 $this->assertEquals( "@{$keyedIds[$key]}$", $v[$key], "Value still returned after deleted" );
534                 $this->assertEquals( 1, $wasSet, "Value process cached while deleted" );
535
536                 $calls = 0;
537                 $ids = [ 1, 2, 3, 4, 5, 6 ];
538                 $keyFunc = function ( $id, WANObjectCache $wanCache ) {
539                         return $wanCache->makeKey( 'test', $id );
540                 };
541                 $keyedIds = $cache->makeMultiKeys( $ids, $keyFunc );
542                 $genFunc = function ( array $ids, array &$ttls, array &$setOpts ) use ( &$calls ) {
543                         $newValues = [];
544                         foreach ( $ids as $id ) {
545                                 ++$calls;
546                                 $newValues[$id] = "val-{$id}";
547                         }
548
549                         return $newValues;
550                 };
551                 $values = $cache->getMultiWithUnionSetCallback( $keyedIds, 10, $genFunc );
552
553                 $this->assertEquals(
554                         [ "val-1", "val-2", "val-3", "val-4", "val-5", "val-6" ],
555                         array_values( $values ),
556                         "Correct values in correct order"
557                 );
558                 $this->assertEquals(
559                         array_map( $keyFunc, $ids, array_fill( 0, count( $ids ), $this->cache ) ),
560                         array_keys( $values ),
561                         "Correct keys in correct order"
562                 );
563                 $this->assertEquals( count( $ids ), $calls );
564
565                 $cache->getMultiWithUnionSetCallback( $keyedIds, 10, $genFunc );
566                 $this->assertEquals( count( $ids ), $calls, "Values cached" );
567         }
568
569         public static function getMultiWithUnionSetCallback_provider() {
570                 return [
571                         [ [], false ],
572                         [ [ 'version' => 1 ], true ]
573                 ];
574         }
575
576         /**
577          * @covers WANObjectCache::getWithSetCallback()
578          * @covers WANObjectCache::doGetWithSetCallback()
579          */
580         public function testLockTSE() {
581                 $cache = $this->cache;
582                 $key = wfRandomString();
583                 $value = wfRandomString();
584
585                 $calls = 0;
586                 $func = function () use ( &$calls, $value, $cache, $key ) {
587                         ++$calls;
588                         // Immediately kill any mutex rather than waiting a second
589                         $cache->delete( $cache::MUTEX_KEY_PREFIX . $key );
590                         return $value;
591                 };
592
593                 $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] );
594                 $this->assertEquals( $value, $ret );
595                 $this->assertEquals( 1, $calls, 'Value was populated' );
596
597                 // Acquire a lock to verify that getWithSetCallback uses lockTSE properly
598                 $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
599
600                 $checkKeys = [ wfRandomString() ]; // new check keys => force misses
601                 $ret = $cache->getWithSetCallback( $key, 30, $func,
602                         [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
603                 $this->assertEquals( $value, $ret, 'Old value used' );
604                 $this->assertEquals( 1, $calls, 'Callback was not used' );
605
606                 $cache->delete( $key );
607                 $ret = $cache->getWithSetCallback( $key, 30, $func,
608                         [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
609                 $this->assertEquals( $value, $ret, 'Callback was used; interim saved' );
610                 $this->assertEquals( 2, $calls, 'Callback was used; interim saved' );
611
612                 $ret = $cache->getWithSetCallback( $key, 30, $func,
613                         [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
614                 $this->assertEquals( $value, $ret, 'Callback was not used; used interim' );
615                 $this->assertEquals( 2, $calls, 'Callback was not used; used interim' );
616         }
617
618         /**
619          * @covers WANObjectCache::getWithSetCallback()
620          * @covers WANObjectCache::doGetWithSetCallback()
621          */
622         public function testLockTSESlow() {
623                 $cache = $this->cache;
624                 $key = wfRandomString();
625                 $value = wfRandomString();
626
627                 $calls = 0;
628                 $func = function ( $oldValue, &$ttl, &$setOpts ) use ( &$calls, $value, $cache, $key ) {
629                         ++$calls;
630                         $setOpts['since'] = microtime( true ) - 10;
631                         // Immediately kill any mutex rather than waiting a second
632                         $cache->delete( $cache::MUTEX_KEY_PREFIX . $key );
633                         return $value;
634                 };
635
636                 // Value should be marked as stale due to snapshot lag
637                 $curTTL = null;
638                 $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] );
639                 $this->assertEquals( $value, $ret );
640                 $this->assertEquals( $value, $cache->get( $key, $curTTL ), 'Value was populated' );
641                 $this->assertLessThan( 0, $curTTL, 'Value has negative curTTL' );
642                 $this->assertEquals( 1, $calls, 'Value was generated' );
643
644                 // Acquire a lock to verify that getWithSetCallback uses lockTSE properly
645                 $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
646                 $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] );
647                 $this->assertEquals( $value, $ret );
648                 $this->assertEquals( 1, $calls, 'Callback was not used' );
649         }
650
651         /**
652          * @covers WANObjectCache::getWithSetCallback()
653          * @covers WANObjectCache::doGetWithSetCallback()
654          */
655         public function testBusyValue() {
656                 $cache = $this->cache;
657                 $key = wfRandomString();
658                 $value = wfRandomString();
659                 $busyValue = wfRandomString();
660
661                 $calls = 0;
662                 $func = function () use ( &$calls, $value, $cache, $key ) {
663                         ++$calls;
664                         // Immediately kill any mutex rather than waiting a second
665                         $cache->delete( $cache::MUTEX_KEY_PREFIX . $key );
666                         return $value;
667                 };
668
669                 $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'busyValue' => $busyValue ] );
670                 $this->assertEquals( $value, $ret );
671                 $this->assertEquals( 1, $calls, 'Value was populated' );
672
673                 // Acquire a lock to verify that getWithSetCallback uses busyValue properly
674                 $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
675
676                 $checkKeys = [ wfRandomString() ]; // new check keys => force misses
677                 $ret = $cache->getWithSetCallback( $key, 30, $func,
678                         [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
679                 $this->assertEquals( $value, $ret, 'Callback used' );
680                 $this->assertEquals( 2, $calls, 'Callback used' );
681
682                 $ret = $cache->getWithSetCallback( $key, 30, $func,
683                         [ 'lockTSE' => 30, 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
684                 $this->assertEquals( $value, $ret, 'Old value used' );
685                 $this->assertEquals( 2, $calls, 'Callback was not used' );
686
687                 $cache->delete( $key ); // no value at all anymore and still locked
688                 $ret = $cache->getWithSetCallback( $key, 30, $func,
689                         [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
690                 $this->assertEquals( $busyValue, $ret, 'Callback was not used; used busy value' );
691                 $this->assertEquals( 2, $calls, 'Callback was not used; used busy value' );
692
693                 $this->internalCache->delete( $cache::MUTEX_KEY_PREFIX . $key );
694                 $ret = $cache->getWithSetCallback( $key, 30, $func,
695                         [ 'lockTSE' => 30, 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
696                 $this->assertEquals( $value, $ret, 'Callback was used; saved interim' );
697                 $this->assertEquals( 3, $calls, 'Callback was used; saved interim' );
698
699                 $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
700                 $ret = $cache->getWithSetCallback( $key, 30, $func,
701                         [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
702                 $this->assertEquals( $value, $ret, 'Callback was not used; used interim' );
703                 $this->assertEquals( 3, $calls, 'Callback was not used; used interim' );
704         }
705
706         /**
707          * @covers WANObjectCache::getMulti()
708          */
709         public function testGetMulti() {
710                 $cache = $this->cache;
711
712                 $value1 = [ 'this' => 'is', 'a' => 'test' ];
713                 $value2 = [ 'this' => 'is', 'another' => 'test' ];
714
715                 $key1 = wfRandomString();
716                 $key2 = wfRandomString();
717                 $key3 = wfRandomString();
718
719                 $cache->set( $key1, $value1, 5 );
720                 $cache->set( $key2, $value2, 10 );
721
722                 $curTTLs = [];
723                 $this->assertEquals(
724                         [ $key1 => $value1, $key2 => $value2 ],
725                         $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs ),
726                         'Result array populated'
727                 );
728
729                 $this->assertEquals( 2, count( $curTTLs ), "Two current TTLs in array" );
730                 $this->assertGreaterThan( 0, $curTTLs[$key1], "Key 1 has current TTL > 0" );
731                 $this->assertGreaterThan( 0, $curTTLs[$key2], "Key 2 has current TTL > 0" );
732
733                 $cKey1 = wfRandomString();
734                 $cKey2 = wfRandomString();
735
736                 $priorTime = microtime( true );
737                 usleep( 1 );
738                 $curTTLs = [];
739                 $this->assertEquals(
740                         [ $key1 => $value1, $key2 => $value2 ],
741                         $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs, [ $cKey1, $cKey2 ] ),
742                         "Result array populated even with new check keys"
743                 );
744                 $t1 = $cache->getCheckKeyTime( $cKey1 );
745                 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key 1 generated on miss' );
746                 $t2 = $cache->getCheckKeyTime( $cKey2 );
747                 $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check key 2 generated on miss' );
748                 $this->assertEquals( 2, count( $curTTLs ), "Current TTLs array set" );
749                 $this->assertLessThanOrEqual( 0, $curTTLs[$key1], 'Key 1 has current TTL <= 0' );
750                 $this->assertLessThanOrEqual( 0, $curTTLs[$key2], 'Key 2 has current TTL <= 0' );
751
752                 usleep( 1 );
753                 $curTTLs = [];
754                 $this->assertEquals(
755                         [ $key1 => $value1, $key2 => $value2 ],
756                         $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs, [ $cKey1, $cKey2 ] ),
757                         "Result array still populated even with new check keys"
758                 );
759                 $this->assertEquals( 2, count( $curTTLs ), "Current TTLs still array set" );
760                 $this->assertLessThan( 0, $curTTLs[$key1], 'Key 1 has negative current TTL' );
761                 $this->assertLessThan( 0, $curTTLs[$key2], 'Key 2 has negative current TTL' );
762         }
763
764         /**
765          * @covers WANObjectCache::getMulti()
766          * @covers WANObjectCache::processCheckKeys()
767          */
768         public function testGetMultiCheckKeys() {
769                 $cache = $this->cache;
770
771                 $checkAll = wfRandomString();
772                 $check1 = wfRandomString();
773                 $check2 = wfRandomString();
774                 $check3 = wfRandomString();
775                 $value1 = wfRandomString();
776                 $value2 = wfRandomString();
777
778                 // Fake initial check key to be set in the past. Otherwise we'd have to sleep for
779                 // several seconds during the test to assert the behaviour.
780                 foreach ( [ $checkAll, $check1, $check2 ] as $checkKey ) {
781                         $cache->touchCheckKey( $checkKey, WANObjectCache::HOLDOFF_NONE );
782                 }
783                 usleep( 100 );
784
785                 $cache->set( 'key1', $value1, 10 );
786                 $cache->set( 'key2', $value2, 10 );
787
788                 $curTTLs = [];
789                 $result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [
790                         'key1' => $check1,
791                         $checkAll,
792                         'key2' => $check2,
793                         'key3' => $check3,
794                 ] );
795                 $this->assertEquals(
796                         [ 'key1' => $value1, 'key2' => $value2 ],
797                         $result,
798                         'Initial values'
799                 );
800                 $this->assertGreaterThanOrEqual( 9.5, $curTTLs['key1'], 'Initial ttls' );
801                 $this->assertLessThanOrEqual( 10.5, $curTTLs['key1'], 'Initial ttls' );
802                 $this->assertGreaterThanOrEqual( 9.5, $curTTLs['key2'], 'Initial ttls' );
803                 $this->assertLessThanOrEqual( 10.5, $curTTLs['key2'], 'Initial ttls' );
804
805                 $cache->touchCheckKey( $check1 );
806
807                 $curTTLs = [];
808                 $result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [
809                         'key1' => $check1,
810                         $checkAll,
811                         'key2' => $check2,
812                         'key3' => $check3,
813                 ] );
814                 $this->assertEquals(
815                         [ 'key1' => $value1, 'key2' => $value2 ],
816                         $result,
817                         'key1 expired by check1, but value still provided'
818                 );
819                 $this->assertLessThan( 0, $curTTLs['key1'], 'key1 TTL expired' );
820                 $this->assertGreaterThan( 0, $curTTLs['key2'], 'key2 still valid' );
821
822                 $cache->touchCheckKey( $checkAll );
823
824                 $curTTLs = [];
825                 $result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [
826                         'key1' => $check1,
827                         $checkAll,
828                         'key2' => $check2,
829                         'key3' => $check3,
830                 ] );
831                 $this->assertEquals(
832                         [ 'key1' => $value1, 'key2' => $value2 ],
833                         $result,
834                         'All keys expired by checkAll, but value still provided'
835                 );
836                 $this->assertLessThan( 0, $curTTLs['key1'], 'key1 expired by checkAll' );
837                 $this->assertLessThan( 0, $curTTLs['key2'], 'key2 expired by checkAll' );
838         }
839
840         /**
841          * @covers WANObjectCache::get()
842          * @covers WANObjectCache::processCheckKeys()
843          */
844         public function testCheckKeyInitHoldoff() {
845                 $cache = $this->cache;
846
847                 for ( $i = 0; $i < 500; ++$i ) {
848                         $key = wfRandomString();
849                         $checkKey = wfRandomString();
850                         // miss, set, hit
851                         $cache->get( $key, $curTTL, [ $checkKey ] );
852                         $cache->set( $key, 'val', 10 );
853                         $curTTL = null;
854                         $v = $cache->get( $key, $curTTL, [ $checkKey ] );
855
856                         $this->assertEquals( 'val', $v );
857                         $this->assertLessThan( 0, $curTTL, "Step $i: CTL < 0 (miss/set/hit)" );
858                 }
859
860                 for ( $i = 0; $i < 500; ++$i ) {
861                         $key = wfRandomString();
862                         $checkKey = wfRandomString();
863                         // set, hit
864                         $cache->set( $key, 'val', 10 );
865                         $curTTL = null;
866                         $v = $cache->get( $key, $curTTL, [ $checkKey ] );
867
868                         $this->assertEquals( 'val', $v );
869                         $this->assertLessThan( 0, $curTTL, "Step $i: CTL < 0 (set/hit)" );
870                 }
871         }
872
873         /**
874          * @covers WANObjectCache::delete()
875          */
876         public function testDelete() {
877                 $key = wfRandomString();
878                 $value = wfRandomString();
879                 $this->cache->set( $key, $value );
880
881                 $curTTL = null;
882                 $v = $this->cache->get( $key, $curTTL );
883                 $this->assertEquals( $value, $v, "Key was created with value" );
884                 $this->assertGreaterThan( 0, $curTTL, "Existing key has current TTL > 0" );
885
886                 $this->cache->delete( $key );
887
888                 $curTTL = null;
889                 $v = $this->cache->get( $key, $curTTL );
890                 $this->assertFalse( $v, "Deleted key has false value" );
891                 $this->assertLessThan( 0, $curTTL, "Deleted key has current TTL < 0" );
892
893                 $this->cache->set( $key, $value . 'more' );
894                 $v = $this->cache->get( $key, $curTTL );
895                 $this->assertFalse( $v, "Deleted key is tombstoned and has false value" );
896                 $this->assertLessThan( 0, $curTTL, "Deleted key is tombstoned and has current TTL < 0" );
897
898                 $this->cache->set( $key, $value );
899                 $this->cache->delete( $key, WANObjectCache::HOLDOFF_NONE );
900
901                 $curTTL = null;
902                 $v = $this->cache->get( $key, $curTTL );
903                 $this->assertFalse( $v, "Deleted key has false value" );
904                 $this->assertNull( $curTTL, "Deleted key has null current TTL" );
905
906                 $this->cache->set( $key, $value );
907                 $v = $this->cache->get( $key, $curTTL );
908                 $this->assertEquals( $value, $v, "Key was created with value" );
909                 $this->assertGreaterThan( 0, $curTTL, "Existing key has current TTL > 0" );
910         }
911
912         /**
913          * @dataProvider getWithSetCallback_versions_provider
914          * @param array $extOpts
915          * @param bool $versioned
916          */
917         public function testGetWithSetCallback_versions( array $extOpts, $versioned ) {
918                 $cache = $this->cache;
919
920                 $key = wfRandomString();
921                 $value = wfRandomString();
922
923                 $wasSet = 0;
924                 $func = function ( $old, &$ttl ) use ( &$wasSet, $value ) {
925                         ++$wasSet;
926                         return $value;
927                 };
928
929                 // Set the main key (version N if versioned)
930                 $wasSet = 0;
931                 $v = $cache->getWithSetCallback( $key, 30, $func, $extOpts );
932                 $this->assertEquals( $value, $v, "Value returned" );
933                 $this->assertEquals( 1, $wasSet, "Value regenerated" );
934                 $cache->getWithSetCallback( $key, 30, $func, $extOpts );
935                 $this->assertEquals( 1, $wasSet, "Value not regenerated" );
936                 // Set the key for version N+1 (if versioned)
937                 if ( $versioned ) {
938                         $verOpts = [ 'version' => $extOpts['version'] + 1 ];
939
940                         $wasSet = 0;
941                         $v = $cache->getWithSetCallback( $key, 30, $func, $verOpts + $extOpts );
942                         $this->assertEquals( $value, $v, "Value returned" );
943                         $this->assertEquals( 1, $wasSet, "Value regenerated" );
944
945                         $wasSet = 0;
946                         $v = $cache->getWithSetCallback( $key, 30, $func, $verOpts + $extOpts );
947                         $this->assertEquals( $value, $v, "Value returned" );
948                         $this->assertEquals( 0, $wasSet, "Value not regenerated" );
949                 }
950
951                 $wasSet = 0;
952                 $cache->getWithSetCallback( $key, 30, $func, $extOpts );
953                 $this->assertEquals( 0, $wasSet, "Value not regenerated" );
954
955                 $wasSet = 0;
956                 $cache->delete( $key );
957                 $v = $cache->getWithSetCallback( $key, 30, $func, $extOpts );
958                 $this->assertEquals( $value, $v, "Value returned" );
959                 $this->assertEquals( 1, $wasSet, "Value regenerated" );
960
961                 if ( $versioned ) {
962                         $wasSet = 0;
963                         $verOpts = [ 'version' => $extOpts['version'] + 1 ];
964                         $v = $cache->getWithSetCallback( $key, 30, $func, $verOpts + $extOpts );
965                         $this->assertEquals( $value, $v, "Value returned" );
966                         $this->assertEquals( 1, $wasSet, "Value regenerated" );
967                 }
968         }
969
970         public static function getWithSetCallback_versions_provider() {
971                 return [
972                         [ [], false ],
973                         [ [ 'version' => 1 ], true ]
974                 ];
975         }
976
977         /**
978          * @covers WANObjectCache::touchCheckKey()
979          * @covers WANObjectCache::resetCheckKey()
980          * @covers WANObjectCache::getCheckKeyTime()
981          */
982         public function testTouchKeys() {
983                 $key = wfRandomString();
984
985                 $priorTime = microtime( true );
986                 usleep( 100 );
987                 $t0 = $this->cache->getCheckKeyTime( $key );
988                 $this->assertGreaterThanOrEqual( $priorTime, $t0, 'Check key auto-created' );
989
990                 $priorTime = microtime( true );
991                 usleep( 100 );
992                 $this->cache->touchCheckKey( $key );
993                 $t1 = $this->cache->getCheckKeyTime( $key );
994                 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key created' );
995
996                 $t2 = $this->cache->getCheckKeyTime( $key );
997                 $this->assertEquals( $t1, $t2, 'Check key time did not change' );
998
999                 usleep( 100 );
1000                 $this->cache->touchCheckKey( $key );
1001                 $t3 = $this->cache->getCheckKeyTime( $key );
1002                 $this->assertGreaterThan( $t2, $t3, 'Check key time increased' );
1003
1004                 $t4 = $this->cache->getCheckKeyTime( $key );
1005                 $this->assertEquals( $t3, $t4, 'Check key time did not change' );
1006
1007                 usleep( 100 );
1008                 $this->cache->resetCheckKey( $key );
1009                 $t5 = $this->cache->getCheckKeyTime( $key );
1010                 $this->assertGreaterThan( $t4, $t5, 'Check key time increased' );
1011
1012                 $t6 = $this->cache->getCheckKeyTime( $key );
1013                 $this->assertEquals( $t5, $t6, 'Check key time did not change' );
1014         }
1015
1016         /**
1017          * @covers WANObjectCache::getMulti()
1018          */
1019         public function testGetWithSeveralCheckKeys() {
1020                 $key = wfRandomString();
1021                 $tKey1 = wfRandomString();
1022                 $tKey2 = wfRandomString();
1023                 $value = 'meow';
1024
1025                 // Two check keys are newer (given hold-off) than $key, another is older
1026                 $this->internalCache->set(
1027                         WANObjectCache::TIME_KEY_PREFIX . $tKey2,
1028                         WANObjectCache::PURGE_VAL_PREFIX . ( microtime( true ) - 3 )
1029                 );
1030                 $this->internalCache->set(
1031                         WANObjectCache::TIME_KEY_PREFIX . $tKey2,
1032                         WANObjectCache::PURGE_VAL_PREFIX . ( microtime( true ) - 5 )
1033                 );
1034                 $this->internalCache->set(
1035                         WANObjectCache::TIME_KEY_PREFIX . $tKey1,
1036                         WANObjectCache::PURGE_VAL_PREFIX . ( microtime( true ) - 30 )
1037                 );
1038                 $this->cache->set( $key, $value, 30 );
1039
1040                 $curTTL = null;
1041                 $v = $this->cache->get( $key, $curTTL, [ $tKey1, $tKey2 ] );
1042                 $this->assertEquals( $value, $v, "Value matches" );
1043                 $this->assertLessThan( -4.9, $curTTL, "Correct CTL" );
1044                 $this->assertGreaterThan( -5.1, $curTTL, "Correct CTL" );
1045         }
1046
1047         /**
1048          * @covers WANObjectCache::reap()
1049          * @covers WANObjectCache::reapCheckKey()
1050          */
1051         public function testReap() {
1052                 $vKey1 = wfRandomString();
1053                 $vKey2 = wfRandomString();
1054                 $tKey1 = wfRandomString();
1055                 $tKey2 = wfRandomString();
1056                 $value = 'moo';
1057
1058                 $knownPurge = time() - 60;
1059                 $goodTime = microtime( true ) - 5;
1060                 $badTime = microtime( true ) - 300;
1061
1062                 $this->internalCache->set(
1063                         WANObjectCache::VALUE_KEY_PREFIX . $vKey1,
1064                         [
1065                                 WANObjectCache::FLD_VERSION => WANObjectCache::VERSION,
1066                                 WANObjectCache::FLD_VALUE => $value,
1067                                 WANObjectCache::FLD_TTL => 3600,
1068                                 WANObjectCache::FLD_TIME => $goodTime
1069                         ]
1070                 );
1071                 $this->internalCache->set(
1072                         WANObjectCache::VALUE_KEY_PREFIX . $vKey2,
1073                         [
1074                                 WANObjectCache::FLD_VERSION => WANObjectCache::VERSION,
1075                                 WANObjectCache::FLD_VALUE => $value,
1076                                 WANObjectCache::FLD_TTL => 3600,
1077                                 WANObjectCache::FLD_TIME => $badTime
1078                         ]
1079                 );
1080                 $this->internalCache->set(
1081                         WANObjectCache::TIME_KEY_PREFIX . $tKey1,
1082                         WANObjectCache::PURGE_VAL_PREFIX . $goodTime
1083                 );
1084                 $this->internalCache->set(
1085                         WANObjectCache::TIME_KEY_PREFIX . $tKey2,
1086                         WANObjectCache::PURGE_VAL_PREFIX . $badTime
1087                 );
1088
1089                 $this->assertEquals( $value, $this->cache->get( $vKey1 ) );
1090                 $this->assertEquals( $value, $this->cache->get( $vKey2 ) );
1091                 $this->cache->reap( $vKey1, $knownPurge, $bad1 );
1092                 $this->cache->reap( $vKey2, $knownPurge, $bad2 );
1093
1094                 $this->assertFalse( $bad1 );
1095                 $this->assertTrue( $bad2 );
1096
1097                 $this->cache->reapCheckKey( $tKey1, $knownPurge, $tBad1 );
1098                 $this->cache->reapCheckKey( $tKey2, $knownPurge, $tBad2 );
1099                 $this->assertFalse( $tBad1 );
1100                 $this->assertTrue( $tBad2 );
1101         }
1102
1103         /**
1104          * @covers WANObjectCache::set()
1105          */
1106         public function testSetWithLag() {
1107                 $value = 1;
1108
1109                 $key = wfRandomString();
1110                 $opts = [ 'lag' => 300, 'since' => microtime( true ) ];
1111                 $this->cache->set( $key, $value, 30, $opts );
1112                 $this->assertEquals( $value, $this->cache->get( $key ), "Rep-lagged value written." );
1113
1114                 $key = wfRandomString();
1115                 $opts = [ 'lag' => 0, 'since' => microtime( true ) - 300 ];
1116                 $this->cache->set( $key, $value, 30, $opts );
1117                 $this->assertEquals( false, $this->cache->get( $key ), "Trx-lagged value not written." );
1118
1119                 $key = wfRandomString();
1120                 $opts = [ 'lag' => 5, 'since' => microtime( true ) - 5 ];
1121                 $this->cache->set( $key, $value, 30, $opts );
1122                 $this->assertEquals( false, $this->cache->get( $key ), "Lagged value not written." );
1123         }
1124
1125         /**
1126          * @covers WANObjectCache::set()
1127          */
1128         public function testWritePending() {
1129                 $value = 1;
1130
1131                 $key = wfRandomString();
1132                 $opts = [ 'pending' => true ];
1133                 $this->cache->set( $key, $value, 30, $opts );
1134                 $this->assertEquals( false, $this->cache->get( $key ), "Pending value not written." );
1135         }
1136
1137         public function testMcRouterSupport() {
1138                 $localBag = $this->getMockBuilder( 'EmptyBagOStuff' )
1139                         ->setMethods( [ 'set', 'delete' ] )->getMock();
1140                 $localBag->expects( $this->never() )->method( 'set' );
1141                 $localBag->expects( $this->never() )->method( 'delete' );
1142                 $wanCache = new WANObjectCache( [
1143                         'cache' => $localBag,
1144                         'pool' => 'testcache-hash',
1145                         'relayer' => new EventRelayerNull( [] )
1146                 ] );
1147                 $valFunc = function () {
1148                         return 1;
1149                 };
1150
1151                 // None of these should use broadcasting commands (e.g. SET, DELETE)
1152                 $wanCache->get( 'x' );
1153                 $wanCache->get( 'x', $ctl, [ 'check1' ] );
1154                 $wanCache->getMulti( [ 'x', 'y' ] );
1155                 $wanCache->getMulti( [ 'x', 'y' ], $ctls, [ 'check2' ] );
1156                 $wanCache->getWithSetCallback( 'p', 30, $valFunc );
1157                 $wanCache->getCheckKeyTime( 'zzz' );
1158                 $wanCache->reap( 'x', time() - 300 );
1159                 $wanCache->reap( 'zzz', time() - 300 );
1160         }
1161
1162         /**
1163          * @dataProvider provideAdaptiveTTL
1164          * @covers WANObjectCache::adaptiveTTL()
1165          * @param float|int $ago
1166          * @param int $maxTTL
1167          * @param int $minTTL
1168          * @param float $factor
1169          * @param int $adaptiveTTL
1170          */
1171         public function testAdaptiveTTL( $ago, $maxTTL, $minTTL, $factor, $adaptiveTTL ) {
1172                 $mtime = $ago ? time() - $ago : $ago;
1173                 $margin = 5;
1174                 $ttl = $this->cache->adaptiveTTL( $mtime, $maxTTL, $minTTL, $factor );
1175
1176                 $this->assertGreaterThanOrEqual( $adaptiveTTL - $margin, $ttl );
1177                 $this->assertLessThanOrEqual( $adaptiveTTL + $margin, $ttl );
1178
1179                 $ttl = $this->cache->adaptiveTTL( (string)$mtime, $maxTTL, $minTTL, $factor );
1180
1181                 $this->assertGreaterThanOrEqual( $adaptiveTTL - $margin, $ttl );
1182                 $this->assertLessThanOrEqual( $adaptiveTTL + $margin, $ttl );
1183         }
1184
1185         public static function provideAdaptiveTTL() {
1186                 return [
1187                         [ 3600, 900, 30, 0.2, 720 ],
1188                         [ 3600, 500, 30, 0.2, 500 ],
1189                         [ 3600, 86400, 800, 0.2, 800 ],
1190                         [ false, 86400, 800, 0.2, 800 ],
1191                         [ null, 86400, 800, 0.2, 800 ]
1192                 ];
1193         }
1194
1195         /**
1196          * @covers WANObjectCache::makeKey
1197          */
1198         public function testMakeKey() {
1199                 $backend = $this->getMockBuilder( HashBagOStuff::class )
1200                         ->setMethods( [ 'makeKey' ] )->getMock();
1201                 $backend->expects( $this->once() )->method( 'makeKey' )
1202                         ->willReturn( 'special' );
1203
1204                 $wanCache = new WANObjectCache( [
1205                         'cache' => $backend,
1206                         'pool' => 'testcache-hash',
1207                         'relayer' => new EventRelayerNull( [] )
1208                 ] );
1209
1210                 $this->assertSame( 'special', $wanCache->makeKey( 'a', 'b' ) );
1211         }
1212
1213         /**
1214          * @covers WANObjectCache::makeGlobalKey
1215          */
1216         public function testMakeGlobalKey() {
1217                 $backend = $this->getMockBuilder( HashBagOStuff::class )
1218                         ->setMethods( [ 'makeGlobalKey' ] )->getMock();
1219                 $backend->expects( $this->once() )->method( 'makeGlobalKey' )
1220                         ->willReturn( 'special' );
1221
1222                 $wanCache = new WANObjectCache( [
1223                         'cache' => $backend,
1224                         'pool' => 'testcache-hash',
1225                         'relayer' => new EventRelayerNull( [] )
1226                 ] );
1227
1228                 $this->assertSame( 'special', $wanCache->makeGlobalKey( 'a', 'b' ) );
1229         }
1230 }