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