Merge "Register a default value for the timecorrection preference"
[lhc/web/wiklou.git] / tests / phpunit / includes / libs / objectcache / WANObjectCacheTest.php
1 <?php
2
3 class WANObjectCacheTest extends MediaWikiTestCase {
4 /** @var WANObjectCache */
5 private $cache;
6 /**@var BagOStuff */
7 private $internalCache;
8
9 protected function setUp() {
10 parent::setUp();
11
12 if ( $this->getCliArg( 'use-wanobjectcache' ) ) {
13 $name = $this->getCliArg( 'use-wanobjectcache' );
14
15 $this->cache = ObjectCache::getWANInstance( $name );
16 } else {
17 $this->cache = new WANObjectCache( [
18 'cache' => new HashBagOStuff(),
19 'pool' => 'testcache-hash',
20 'relayer' => new EventRelayerNull( [] )
21 ] );
22 }
23
24 $wanCache = TestingAccessWrapper::newFromObject( $this->cache );
25 $this->internalCache = $wanCache->cache;
26 }
27
28 /**
29 * @dataProvider provideSetAndGet
30 * @covers WANObjectCache::set()
31 * @covers WANObjectCache::get()
32 * @param mixed $value
33 * @param integer $ttl
34 */
35 public function testSetAndGet( $value, $ttl ) {
36 $curTTL = null;
37 $asOf = null;
38 $key = wfRandomString();
39
40 $this->cache->get( $key, $curTTL, [], $asOf );
41 $this->assertNull( $curTTL, "Current TTL is null" );
42 $this->assertNull( $asOf, "Current as-of-time is infinite" );
43
44 $t = microtime( true );
45 $this->cache->set( $key, $value, $ttl );
46
47 $this->assertEquals( $value, $this->cache->get( $key, $curTTL, [], $asOf ) );
48 if ( is_infinite( $ttl ) || $ttl == 0 ) {
49 $this->assertTrue( is_infinite( $curTTL ), "Current TTL is infinite" );
50 } else {
51 $this->assertGreaterThan( 0, $curTTL, "Current TTL > 0" );
52 $this->assertLessThanOrEqual( $ttl, $curTTL, "Current TTL < nominal TTL" );
53 }
54 $this->assertGreaterThanOrEqual( $t - 1, $asOf, "As-of-time in range of set() time" );
55 $this->assertLessThanOrEqual( $t + 1, $asOf, "As-of-time in range of set() time" );
56 }
57
58 public static function provideSetAndGet() {
59 return [
60 [ 14141, 3 ],
61 [ 3535.666, 3 ],
62 [ [], 3 ],
63 [ null, 3 ],
64 [ '0', 3 ],
65 [ (object)[ 'meow' ], 3 ],
66 [ INF, 3 ],
67 [ '', 3 ],
68 [ 'pizzacat', INF ],
69 ];
70 }
71
72 /**
73 * @covers WANObjectCache::get()
74 */
75 public function testGetNotExists() {
76 $key = wfRandomString();
77 $curTTL = null;
78 $value = $this->cache->get( $key, $curTTL );
79
80 $this->assertFalse( $value, "Non-existing key has false value" );
81 $this->assertNull( $curTTL, "Non-existing key has null current TTL" );
82 }
83
84 /**
85 * @covers WANObjectCache::set()
86 */
87 public function testSetOver() {
88 $key = wfRandomString();
89 for ( $i = 0; $i < 3; ++$i ) {
90 $value = wfRandomString();
91 $this->cache->set( $key, $value, 3 );
92
93 $this->assertEquals( $this->cache->get( $key ), $value );
94 }
95 }
96
97 /**
98 * @covers WANObjectCache::set()
99 */
100 public function testStaleSet() {
101 $key = wfRandomString();
102 $value = wfRandomString();
103 $this->cache->set( $key, $value, 3, [ 'since' => microtime( true ) - 30 ] );
104
105 $this->assertFalse( $this->cache->get( $key ), "Stale set() value ignored" );
106 }
107
108 /**
109 * @dataProvider getWithSetCallback_provider
110 * @covers WANObjectCache::getWithSetCallback()
111 * @covers WANObjectCache::doGetWithSetCallback()
112 * @param array $extOpts
113 * @param bool $versioned
114 */
115 public function testGetWithSetCallback( array $extOpts, $versioned ) {
116 $cache = $this->cache;
117
118 $key = wfRandomString();
119 $value = wfRandomString();
120 $cKey1 = wfRandomString();
121 $cKey2 = wfRandomString();
122
123 $wasSet = 0;
124 $func = function( $old, &$ttl ) use ( &$wasSet, $value ) {
125 ++$wasSet;
126 $ttl = 20; // override with another value
127 return $value;
128 };
129
130 $wasSet = 0;
131 $v = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] + $extOpts );
132 $this->assertEquals( $value, $v, "Value returned" );
133 $this->assertEquals( 1, $wasSet, "Value regenerated" );
134
135 $curTTL = null;
136 $cache->get( $key, $curTTL );
137 $this->assertLessThanOrEqual( 20, $curTTL, 'Current TTL between 19-20 (overriden)' );
138 $this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' );
139
140 $wasSet = 0;
141 $v = $cache->getWithSetCallback( $key, 30, $func, [
142 'lowTTL' => 0,
143 'lockTSE' => 5,
144 ] + $extOpts );
145 $this->assertEquals( $value, $v, "Value returned" );
146 $this->assertEquals( 0, $wasSet, "Value not regenerated" );
147
148 $priorTime = microtime( true );
149 usleep( 1 );
150 $wasSet = 0;
151 $v = $cache->getWithSetCallback(
152 $key, 30, $func, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
153 );
154 $this->assertEquals( $value, $v, "Value returned" );
155 $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
156 $t1 = $cache->getCheckKeyTime( $cKey1 );
157 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
158 $t2 = $cache->getCheckKeyTime( $cKey2 );
159 $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check keys generated on miss' );
160
161 $priorTime = microtime( true );
162 $wasSet = 0;
163 $v = $cache->getWithSetCallback(
164 $key, 30, $func, [ 'checkKeys' => [ $cKey1, $cKey2 ] ] + $extOpts
165 );
166 $this->assertEquals( $value, $v, "Value returned" );
167 $this->assertEquals( 1, $wasSet, "Value regenerated due to still-recent check keys" );
168 $t1 = $cache->getCheckKeyTime( $cKey1 );
169 $this->assertLessThanOrEqual( $priorTime, $t1, 'Check keys did not change again' );
170 $t2 = $cache->getCheckKeyTime( $cKey2 );
171 $this->assertLessThanOrEqual( $priorTime, $t2, 'Check keys did not change again' );
172
173 $curTTL = null;
174 $v = $cache->get( $key, $curTTL, [ $cKey1, $cKey2 ] );
175 if ( $versioned ) {
176 $this->assertEquals( $value, $v[$cache::VFLD_DATA], "Value returned" );
177 } else {
178 $this->assertEquals( $value, $v, "Value returned" );
179 }
180 $this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
181
182 $wasSet = 0;
183 $key = wfRandomString();
184 $v = $cache->getWithSetCallback( $key, 30, $func, [ 'pcTTL' => 5 ] + $extOpts );
185 $this->assertEquals( $value, $v, "Value returned" );
186 $cache->delete( $key );
187 $v = $cache->getWithSetCallback( $key, 30, $func, [ 'pcTTL' => 5 ] + $extOpts );
188 $this->assertEquals( $value, $v, "Value still returned after deleted" );
189 $this->assertEquals( 1, $wasSet, "Value process cached while deleted" );
190 }
191
192 public static function getWithSetCallback_provider() {
193 return [
194 [ [], false ],
195 [ [ 'version' => 1 ], true ]
196 ];
197 }
198
199 /**
200 * @covers WANObjectCache::getWithSetCallback()
201 * @covers WANObjectCache::doGetWithSetCallback()
202 */
203 public function testLockTSE() {
204 $cache = $this->cache;
205 $key = wfRandomString();
206 $value = wfRandomString();
207
208 $calls = 0;
209 $func = function() use ( &$calls, $value, $cache, $key ) {
210 ++$calls;
211 // Immediately kill any mutex rather than waiting a second
212 $cache->delete( $cache::MUTEX_KEY_PREFIX . $key );
213 return $value;
214 };
215
216 $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] );
217 $this->assertEquals( $value, $ret );
218 $this->assertEquals( 1, $calls, 'Value was populated' );
219
220 // Acquire a lock to verify that getWithSetCallback uses lockTSE properly
221 $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
222
223 $checkKeys = [ wfRandomString() ]; // new check keys => force misses
224 $ret = $cache->getWithSetCallback( $key, 30, $func,
225 [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
226 $this->assertEquals( $value, $ret, 'Old value used' );
227 $this->assertEquals( 1, $calls, 'Callback was not used' );
228
229 $cache->delete( $key );
230 $ret = $cache->getWithSetCallback( $key, 30, $func,
231 [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
232 $this->assertEquals( $value, $ret, 'Callback was used; interim saved' );
233 $this->assertEquals( 2, $calls, 'Callback was used; interim saved' );
234
235 $ret = $cache->getWithSetCallback( $key, 30, $func,
236 [ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
237 $this->assertEquals( $value, $ret, 'Callback was not used; used interim' );
238 $this->assertEquals( 2, $calls, 'Callback was not used; used interim' );
239 }
240
241 /**
242 * @covers WANObjectCache::getWithSetCallback()
243 * @covers WANObjectCache::doGetWithSetCallback()
244 */
245 public function testLockTSESlow() {
246 $cache = $this->cache;
247 $key = wfRandomString();
248 $value = wfRandomString();
249
250 $calls = 0;
251 $func = function( $oldValue, &$ttl, &$setOpts ) use ( &$calls, $value, $cache, $key ) {
252 ++$calls;
253 $setOpts['since'] = microtime( true ) - 10;
254 // Immediately kill any mutex rather than waiting a second
255 $cache->delete( $cache::MUTEX_KEY_PREFIX . $key );
256 return $value;
257 };
258
259 // Value should be marked as stale due to snapshot lag
260 $curTTL = null;
261 $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] );
262 $this->assertEquals( $value, $ret );
263 $this->assertEquals( $value, $cache->get( $key, $curTTL ), 'Value was populated' );
264 $this->assertLessThan( 0, $curTTL, 'Value has negative curTTL' );
265 $this->assertEquals( 1, $calls, 'Value was generated' );
266
267 // Acquire a lock to verify that getWithSetCallback uses lockTSE properly
268 $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
269 $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] );
270 $this->assertEquals( $value, $ret );
271 $this->assertEquals( 1, $calls, 'Callback was not used' );
272 }
273
274 /**
275 * @covers WANObjectCache::getWithSetCallback()
276 * @covers WANObjectCache::doGetWithSetCallback()
277 */
278 public function testBusyValue() {
279 $cache = $this->cache;
280 $key = wfRandomString();
281 $value = wfRandomString();
282 $busyValue = wfRandomString();
283
284 $calls = 0;
285 $func = function() use ( &$calls, $value, $cache, $key ) {
286 ++$calls;
287 // Immediately kill any mutex rather than waiting a second
288 $cache->delete( $cache::MUTEX_KEY_PREFIX . $key );
289 return $value;
290 };
291
292 $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'busyValue' => $busyValue ] );
293 $this->assertEquals( $value, $ret );
294 $this->assertEquals( 1, $calls, 'Value was populated' );
295
296 // Acquire a lock to verify that getWithSetCallback uses busyValue properly
297 $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
298
299 $checkKeys = [ wfRandomString() ]; // new check keys => force misses
300 $ret = $cache->getWithSetCallback( $key, 30, $func,
301 [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
302 $this->assertEquals( $value, $ret, 'Callback used' );
303 $this->assertEquals( 2, $calls, 'Callback used' );
304
305 $ret = $cache->getWithSetCallback( $key, 30, $func,
306 [ 'lockTSE' => 30, 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
307 $this->assertEquals( $value, $ret, 'Old value used' );
308 $this->assertEquals( 2, $calls, 'Callback was not used' );
309
310 $cache->delete( $key ); // no value at all anymore and still locked
311 $ret = $cache->getWithSetCallback( $key, 30, $func,
312 [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
313 $this->assertEquals( $busyValue, $ret, 'Callback was not used; used busy value' );
314 $this->assertEquals( 2, $calls, 'Callback was not used; used busy value' );
315
316 $this->internalCache->delete( $cache::MUTEX_KEY_PREFIX . $key );
317 $ret = $cache->getWithSetCallback( $key, 30, $func,
318 [ 'lockTSE' => 30, 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
319 $this->assertEquals( $value, $ret, 'Callback was used; saved interim' );
320 $this->assertEquals( 3, $calls, 'Callback was used; saved interim' );
321
322 $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
323 $ret = $cache->getWithSetCallback( $key, 30, $func,
324 [ 'busyValue' => $busyValue, 'checkKeys' => $checkKeys ] );
325 $this->assertEquals( $value, $ret, 'Callback was not used; used interim' );
326 $this->assertEquals( 3, $calls, 'Callback was not used; used interim' );
327 }
328
329 /**
330 * @covers WANObjectCache::getMulti()
331 */
332 public function testGetMulti() {
333 $cache = $this->cache;
334
335 $value1 = [ 'this' => 'is', 'a' => 'test' ];
336 $value2 = [ 'this' => 'is', 'another' => 'test' ];
337
338 $key1 = wfRandomString();
339 $key2 = wfRandomString();
340 $key3 = wfRandomString();
341
342 $cache->set( $key1, $value1, 5 );
343 $cache->set( $key2, $value2, 10 );
344
345 $curTTLs = [];
346 $this->assertEquals(
347 [ $key1 => $value1, $key2 => $value2 ],
348 $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs ),
349 'Result array populated'
350 );
351
352 $this->assertEquals( 2, count( $curTTLs ), "Two current TTLs in array" );
353 $this->assertGreaterThan( 0, $curTTLs[$key1], "Key 1 has current TTL > 0" );
354 $this->assertGreaterThan( 0, $curTTLs[$key2], "Key 2 has current TTL > 0" );
355
356 $cKey1 = wfRandomString();
357 $cKey2 = wfRandomString();
358
359 $priorTime = microtime( true );
360 usleep( 1 );
361 $curTTLs = [];
362 $this->assertEquals(
363 [ $key1 => $value1, $key2 => $value2 ],
364 $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs, [ $cKey1, $cKey2 ] ),
365 "Result array populated even with new check keys"
366 );
367 $t1 = $cache->getCheckKeyTime( $cKey1 );
368 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key 1 generated on miss' );
369 $t2 = $cache->getCheckKeyTime( $cKey2 );
370 $this->assertGreaterThanOrEqual( $priorTime, $t2, 'Check key 2 generated on miss' );
371 $this->assertEquals( 2, count( $curTTLs ), "Current TTLs array set" );
372 $this->assertLessThanOrEqual( 0, $curTTLs[$key1], 'Key 1 has current TTL <= 0' );
373 $this->assertLessThanOrEqual( 0, $curTTLs[$key2], 'Key 2 has current TTL <= 0' );
374
375 usleep( 1 );
376 $curTTLs = [];
377 $this->assertEquals(
378 [ $key1 => $value1, $key2 => $value2 ],
379 $cache->getMulti( [ $key1, $key2, $key3 ], $curTTLs, [ $cKey1, $cKey2 ] ),
380 "Result array still populated even with new check keys"
381 );
382 $this->assertEquals( 2, count( $curTTLs ), "Current TTLs still array set" );
383 $this->assertLessThan( 0, $curTTLs[$key1], 'Key 1 has negative current TTL' );
384 $this->assertLessThan( 0, $curTTLs[$key2], 'Key 2 has negative current TTL' );
385 }
386
387 /**
388 * @covers WANObjectCache::getMulti()
389 * @covers WANObjectCache::processCheckKeys()
390 */
391 public function testGetMultiCheckKeys() {
392 $cache = $this->cache;
393
394 $checkAll = wfRandomString();
395 $check1 = wfRandomString();
396 $check2 = wfRandomString();
397 $check3 = wfRandomString();
398 $value1 = wfRandomString();
399 $value2 = wfRandomString();
400
401 // Fake initial check key to be set in the past. Otherwise we'd have to sleep for
402 // several seconds during the test to assert the behaviour.
403 foreach ( [ $checkAll, $check1, $check2 ] as $checkKey ) {
404 $cache->touchCheckKey( $checkKey, WANObjectCache::HOLDOFF_NONE );
405 }
406 usleep( 100 );
407
408 $cache->set( 'key1', $value1, 10 );
409 $cache->set( 'key2', $value2, 10 );
410
411 $curTTLs = [];
412 $result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [
413 'key1' => $check1,
414 $checkAll,
415 'key2' => $check2,
416 'key3' => $check3,
417 ] );
418 $this->assertEquals(
419 [ 'key1' => $value1, 'key2' => $value2 ],
420 $result,
421 'Initial values'
422 );
423 $this->assertGreaterThanOrEqual( 9.5, $curTTLs['key1'], 'Initial ttls' );
424 $this->assertLessThanOrEqual( 10.5, $curTTLs['key1'], 'Initial ttls' );
425 $this->assertGreaterThanOrEqual( 9.5, $curTTLs['key2'], 'Initial ttls' );
426 $this->assertLessThanOrEqual( 10.5, $curTTLs['key2'], 'Initial ttls' );
427
428 $cache->touchCheckKey( $check1 );
429
430 $curTTLs = [];
431 $result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [
432 'key1' => $check1,
433 $checkAll,
434 'key2' => $check2,
435 'key3' => $check3,
436 ] );
437 $this->assertEquals(
438 [ 'key1' => $value1, 'key2' => $value2 ],
439 $result,
440 'key1 expired by check1, but value still provided'
441 );
442 $this->assertLessThan( 0, $curTTLs['key1'], 'key1 TTL expired' );
443 $this->assertGreaterThan( 0, $curTTLs['key2'], 'key2 still valid' );
444
445 $cache->touchCheckKey( $checkAll );
446
447 $curTTLs = [];
448 $result = $cache->getMulti( [ 'key1', 'key2', 'key3' ], $curTTLs, [
449 'key1' => $check1,
450 $checkAll,
451 'key2' => $check2,
452 'key3' => $check3,
453 ] );
454 $this->assertEquals(
455 [ 'key1' => $value1, 'key2' => $value2 ],
456 $result,
457 'All keys expired by checkAll, but value still provided'
458 );
459 $this->assertLessThan( 0, $curTTLs['key1'], 'key1 expired by checkAll' );
460 $this->assertLessThan( 0, $curTTLs['key2'], 'key2 expired by checkAll' );
461 }
462
463 /**
464 * @covers WANObjectCache::get()
465 * @covers WANObjectCache::processCheckKeys()
466 */
467 public function testCheckKeyInitHoldoff() {
468 $cache = $this->cache;
469
470 for ( $i = 0; $i < 500; ++$i ) {
471 $key = wfRandomString();
472 $checkKey = wfRandomString();
473 // miss, set, hit
474 $cache->get( $key, $curTTL, [ $checkKey ] );
475 $cache->set( $key, 'val', 10 );
476 $curTTL = null;
477 $v = $cache->get( $key, $curTTL, [ $checkKey ] );
478
479 $this->assertEquals( 'val', $v );
480 $this->assertLessThan( 0, $curTTL, "Step $i: CTL < 0 (miss/set/hit)" );
481 }
482
483 for ( $i = 0; $i < 500; ++$i ) {
484 $key = wfRandomString();
485 $checkKey = wfRandomString();
486 // set, hit
487 $cache->set( $key, 'val', 10 );
488 $curTTL = null;
489 $v = $cache->get( $key, $curTTL, [ $checkKey ] );
490
491 $this->assertEquals( 'val', $v );
492 $this->assertLessThan( 0, $curTTL, "Step $i: CTL < 0 (set/hit)" );
493 }
494 }
495
496 /**
497 * @covers WANObjectCache::delete()
498 */
499 public function testDelete() {
500 $key = wfRandomString();
501 $value = wfRandomString();
502 $this->cache->set( $key, $value );
503
504 $curTTL = null;
505 $v = $this->cache->get( $key, $curTTL );
506 $this->assertEquals( $value, $v, "Key was created with value" );
507 $this->assertGreaterThan( 0, $curTTL, "Existing key has current TTL > 0" );
508
509 $this->cache->delete( $key );
510
511 $curTTL = null;
512 $v = $this->cache->get( $key, $curTTL );
513 $this->assertFalse( $v, "Deleted key has false value" );
514 $this->assertLessThan( 0, $curTTL, "Deleted key has current TTL < 0" );
515
516 $this->cache->set( $key, $value . 'more' );
517 $v = $this->cache->get( $key, $curTTL );
518 $this->assertFalse( $v, "Deleted key is tombstoned and has false value" );
519 $this->assertLessThan( 0, $curTTL, "Deleted key is tombstoned and has current TTL < 0" );
520
521 $this->cache->set( $key, $value );
522 $this->cache->delete( $key, WANObjectCache::HOLDOFF_NONE );
523
524 $curTTL = null;
525 $v = $this->cache->get( $key, $curTTL );
526 $this->assertFalse( $v, "Deleted key has false value" );
527 $this->assertNull( $curTTL, "Deleted key has null current TTL" );
528
529 $this->cache->set( $key, $value );
530 $v = $this->cache->get( $key, $curTTL );
531 $this->assertEquals( $value, $v, "Key was created with value" );
532 $this->assertGreaterThan( 0, $curTTL, "Existing key has current TTL > 0" );
533 }
534
535 /**
536 * @dataProvider getWithSetCallback_versions_provider
537 * @param array $extOpts
538 * @param $versioned
539 */
540 public function testGetWithSetCallback_versions( array $extOpts, $versioned ) {
541 $cache = $this->cache;
542
543 $key = wfRandomString();
544 $value = wfRandomString();
545
546 $wasSet = 0;
547 $func = function( $old, &$ttl ) use ( &$wasSet, $value ) {
548 ++$wasSet;
549 return $value;
550 };
551
552 // Set the main key (version N if versioned)
553 $wasSet = 0;
554 $v = $cache->getWithSetCallback( $key, 30, $func, $extOpts );
555 $this->assertEquals( $value, $v, "Value returned" );
556 $this->assertEquals( 1, $wasSet, "Value regenerated" );
557 $cache->getWithSetCallback( $key, 30, $func, $extOpts );
558 $this->assertEquals( 1, $wasSet, "Value not regenerated" );
559 // Set the key for version N+1 (if versioned)
560 if ( $versioned ) {
561 $verOpts = [ 'version' => $extOpts['version'] + 1 ];
562
563 $wasSet = 0;
564 $v = $cache->getWithSetCallback( $key, 30, $func, $verOpts + $extOpts );
565 $this->assertEquals( $value, $v, "Value returned" );
566 $this->assertEquals( 1, $wasSet, "Value regenerated" );
567
568 $wasSet = 0;
569 $v = $cache->getWithSetCallback( $key, 30, $func, $verOpts + $extOpts );
570 $this->assertEquals( $value, $v, "Value returned" );
571 $this->assertEquals( 0, $wasSet, "Value not regenerated" );
572 }
573
574 $wasSet = 0;
575 $cache->getWithSetCallback( $key, 30, $func, $extOpts );
576 $this->assertEquals( 0, $wasSet, "Value not regenerated" );
577
578 $wasSet = 0;
579 $cache->delete( $key );
580 $v = $cache->getWithSetCallback( $key, 30, $func, $extOpts );
581 $this->assertEquals( $value, $v, "Value returned" );
582 $this->assertEquals( 1, $wasSet, "Value regenerated" );
583
584 if ( $versioned ) {
585 $wasSet = 0;
586 $verOpts = [ 'version' => $extOpts['version'] + 1 ];
587 $v = $cache->getWithSetCallback( $key, 30, $func, $verOpts + $extOpts );
588 $this->assertEquals( $value, $v, "Value returned" );
589 $this->assertEquals( 1, $wasSet, "Value regenerated" );
590 }
591 }
592
593 public static function getWithSetCallback_versions_provider() {
594 return [
595 [ [], false ],
596 [ [ 'version' => 1 ], true ]
597 ];
598 }
599
600 /**
601 * @covers WANObjectCache::touchCheckKey()
602 * @covers WANObjectCache::resetCheckKey()
603 * @covers WANObjectCache::getCheckKeyTime()
604 */
605 public function testTouchKeys() {
606 $key = wfRandomString();
607
608 $priorTime = microtime( true );
609 usleep( 100 );
610 $t0 = $this->cache->getCheckKeyTime( $key );
611 $this->assertGreaterThanOrEqual( $priorTime, $t0, 'Check key auto-created' );
612
613 $priorTime = microtime( true );
614 usleep( 100 );
615 $this->cache->touchCheckKey( $key );
616 $t1 = $this->cache->getCheckKeyTime( $key );
617 $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key created' );
618
619 $t2 = $this->cache->getCheckKeyTime( $key );
620 $this->assertEquals( $t1, $t2, 'Check key time did not change' );
621
622 usleep( 100 );
623 $this->cache->touchCheckKey( $key );
624 $t3 = $this->cache->getCheckKeyTime( $key );
625 $this->assertGreaterThan( $t2, $t3, 'Check key time increased' );
626
627 $t4 = $this->cache->getCheckKeyTime( $key );
628 $this->assertEquals( $t3, $t4, 'Check key time did not change' );
629
630 usleep( 100 );
631 $this->cache->resetCheckKey( $key );
632 $t5 = $this->cache->getCheckKeyTime( $key );
633 $this->assertGreaterThan( $t4, $t5, 'Check key time increased' );
634
635 $t6 = $this->cache->getCheckKeyTime( $key );
636 $this->assertEquals( $t5, $t6, 'Check key time did not change' );
637 }
638
639 /**
640 * @covers WANObjectCache::getMulti()
641 */
642 public function testGetWithSeveralCheckKeys() {
643 $key = wfRandomString();
644 $tKey1 = wfRandomString();
645 $tKey2 = wfRandomString();
646 $value = 'meow';
647
648 // Two check keys are newer (given hold-off) than $key, another is older
649 $this->internalCache->set(
650 WANObjectCache::TIME_KEY_PREFIX . $tKey2,
651 WANObjectCache::PURGE_VAL_PREFIX . ( microtime( true ) - 3 )
652 );
653 $this->internalCache->set(
654 WANObjectCache::TIME_KEY_PREFIX . $tKey2,
655 WANObjectCache::PURGE_VAL_PREFIX . ( microtime( true ) - 5 )
656 );
657 $this->internalCache->set(
658 WANObjectCache::TIME_KEY_PREFIX . $tKey1,
659 WANObjectCache::PURGE_VAL_PREFIX . ( microtime( true ) - 30 )
660 );
661 $this->cache->set( $key, $value, 30 );
662
663 $curTTL = null;
664 $v = $this->cache->get( $key, $curTTL, [ $tKey1, $tKey2 ] );
665 $this->assertEquals( $value, $v, "Value matches" );
666 $this->assertLessThan( -4.9, $curTTL, "Correct CTL" );
667 $this->assertGreaterThan( -5.1, $curTTL, "Correct CTL" );
668 }
669
670 /**
671 * @covers WANObjectCache::set()
672 */
673 public function testSetWithLag() {
674 $value = 1;
675
676 $key = wfRandomString();
677 $opts = [ 'lag' => 300, 'since' => microtime( true ) ];
678 $this->cache->set( $key, $value, 30, $opts );
679 $this->assertEquals( $value, $this->cache->get( $key ), "Rep-lagged value written." );
680
681 $key = wfRandomString();
682 $opts = [ 'lag' => 0, 'since' => microtime( true ) - 300 ];
683 $this->cache->set( $key, $value, 30, $opts );
684 $this->assertEquals( false, $this->cache->get( $key ), "Trx-lagged value not written." );
685
686 $key = wfRandomString();
687 $opts = [ 'lag' => 5, 'since' => microtime( true ) - 5 ];
688 $this->cache->set( $key, $value, 30, $opts );
689 $this->assertEquals( false, $this->cache->get( $key ), "Lagged value not written." );
690 }
691
692 /**
693 * @covers WANObjectCache::set()
694 */
695 public function testWritePending() {
696 $value = 1;
697
698 $key = wfRandomString();
699 $opts = [ 'pending' => true ];
700 $this->cache->set( $key, $value, 30, $opts );
701 $this->assertEquals( false, $this->cache->get( $key ), "Pending value not written." );
702 }
703
704 public function testMcRouterSupport() {
705 $localBag = $this->getMock( 'EmptyBagOStuff', [ 'set', 'delete' ] );
706 $localBag->expects( $this->never() )->method( 'set' );
707 $localBag->expects( $this->never() )->method( 'delete' );
708 $wanCache = new WANObjectCache( [
709 'cache' => $localBag,
710 'pool' => 'testcache-hash',
711 'relayer' => new EventRelayerNull( [] )
712 ] );
713 $valFunc = function () {
714 return 1;
715 };
716
717 // None of these should use broadcasting commands (e.g. SET, DELETE)
718 $wanCache->get( 'x' );
719 $wanCache->get( 'x', $ctl, [ 'check1' ] );
720 $wanCache->getMulti( [ 'x', 'y' ] );
721 $wanCache->getMulti( [ 'x', 'y' ], $ctls, [ 'check2' ] );
722 $wanCache->getWithSetCallback( 'p', 30, $valFunc );
723 $wanCache->getCheckKeyTime( 'zzz' );
724 }
725
726 /**
727 * @dataProvider provideAdaptiveTTL
728 * @covers WANObjectCache::adaptiveTTL()
729 */
730 public function testAdaptiveTTL( $ago, $maxTTL, $minTTL, $factor, $adaptiveTTL ) {
731 $mtime = is_int( $ago ) ? time() - $ago : $ago;
732 $margin = 5;
733 $ttl = $this->cache->adaptiveTTL( $mtime, $maxTTL, $minTTL, $factor );
734
735 $this->assertGreaterThanOrEqual( $adaptiveTTL - $margin, $ttl );
736 $this->assertLessThanOrEqual( $adaptiveTTL + $margin, $ttl );
737 }
738
739 public static function provideAdaptiveTTL() {
740 return [
741 [ 3600, 900, 30, .2, 720 ],
742 [ 3600, 500, 30, .2, 500 ],
743 [ 3600, 86400, 800, .2, 800 ],
744 [ false, 86400, 800, .2, 800 ],
745 [ null, 86400, 800, .2, 800 ]
746 ];
747 }
748 }