b6709a0f29a66abb951f7d2ff3ec3603792dcb9d
3 use Wikimedia\ScopedCallback
;
6 * @author Matthias Mullie <mmullie@wikimedia.org>
9 class BagOStuffTest
extends MediaWikiTestCase
{
13 const TEST_KEY
= 'test';
15 protected function setUp() {
18 // type defined through parameter
19 if ( $this->getCliArg( 'use-bagostuff' ) ) {
20 $name = $this->getCliArg( 'use-bagostuff' );
22 $this->cache
= ObjectCache
::newFromId( $name );
24 // no type defined - use simple hash
25 $this->cache
= new HashBagOStuff
;
28 $this->cache
->delete( $this->cache
->makeKey( self
::TEST_KEY
) );
32 * @covers BagOStuff::makeGlobalKey
33 * @covers BagOStuff::makeKeyInternal
35 public function testMakeKey() {
36 $cache = ObjectCache
::newFromId( 'hash' );
38 $localKey = $cache->makeKey( 'first', 'second', 'third' );
39 $globalKey = $cache->makeGlobalKey( 'first', 'second', 'third' );
41 $this->assertStringMatchesFormat(
42 '%Sfirst%Ssecond%Sthird%S',
44 'Local key interpolates parameters'
47 $this->assertStringMatchesFormat(
48 'global%Sfirst%Ssecond%Sthird%S',
50 'Global key interpolates parameters and contains global prefix'
53 $this->assertNotEquals(
56 'Local key and global key with same parameters should not be equal'
59 $this->assertNotEquals(
60 $cache->makeKeyInternal( 'prefix', [ 'a', 'bc:', 'de' ] ),
61 $cache->makeKeyInternal( 'prefix', [ 'a', 'bc', ':de' ] )
66 * @covers BagOStuff::merge
67 * @covers BagOStuff::mergeViaLock
68 * @covers BagOStuff::mergeViaCas
70 public function testMerge() {
71 $key = $this->cache
->makeKey( self
::TEST_KEY
);
72 $callback = function ( BagOStuff
$cache, $key, $oldVal ) {
73 return ( $oldVal === false ) ?
'merged' : $oldVal . 'merged';
76 // merge on non-existing value
77 $merged = $this->cache
->merge( $key, $callback, 5 );
78 $this->assertTrue( $merged );
79 $this->assertEquals( 'merged', $this->cache
->get( $key ) );
81 // merge on existing value
82 $merged = $this->cache
->merge( $key, $callback, 5 );
83 $this->assertTrue( $merged );
84 $this->assertEquals( 'mergedmerged', $this->cache
->get( $key ) );
88 * @covers BagOStuff::merge
89 * @covers BagOStuff::mergeViaLock
91 public function testMerge_fork() {
92 $key = $this->cache
->makeKey( self
::TEST_KEY
);
93 $callback = function ( BagOStuff
$cache, $key, $oldVal ) {
94 return ( $oldVal === false ) ?
'merged' : $oldVal . 'merged';
97 * Test concurrent merges by forking this process, if:
98 * - not manually called with --use-bagostuff
99 * - pcntl_fork is supported by the system
100 * - cache type will correctly support calls over forks
102 $fork = (bool)$this->getCliArg( 'use-bagostuff' );
103 $fork &= function_exists( 'pcntl_fork' );
104 $fork &= !$this->cache
instanceof HashBagOStuff
;
105 $fork &= !$this->cache
instanceof EmptyBagOStuff
;
106 $fork &= !$this->cache
instanceof MultiWriteBagOStuff
;
109 // Function to start merge(), run another merge() midway through, then finish
110 $outerFunc = function ( BagOStuff
$cache, $key, $oldVal ) use ( $callback, &$pid ) {
115 pcntl_wait( $status );
117 return $callback( $cache, $key, $oldVal );
119 $this->cache
->merge( $key, $callback, 0, 1 );
120 // Bail out of the outer merge() in the child process since it does not
121 // need to attempt to write anything. Success is checked by the parent.
122 parent
::tearDown(); // avoid phpunit notices
127 // attempt a merge - this should fail
128 $merged = $this->cache
->merge( $key, $outerFunc, 0, 1 );
131 return; // can't fork, ignore this test...
134 // merge has failed because child process was merging (and we only attempted once)
135 $this->assertFalse( $merged );
137 // make sure the child's merge is completed and verify
138 $this->assertEquals( $this->cache
->get( $key ), 'mergedmerged' );
140 $this->markTestSkipped( 'No pcntl methods available' );
145 * @covers BagOStuff::changeTTL
147 public function testChangeTTL() {
148 $key = $this->cache
->makeKey( self
::TEST_KEY
);
151 $this->cache
->add( $key, $value, 5 );
152 $this->assertTrue( $this->cache
->changeTTL( $key, 5 ) );
153 $this->assertEquals( $this->cache
->get( $key ), $value );
154 $this->cache
->delete( $key );
155 $this->assertFalse( $this->cache
->changeTTL( $key, 5 ) );
159 * @covers BagOStuff::add
161 public function testAdd() {
162 $key = $this->cache
->makeKey( self
::TEST_KEY
);
163 $this->assertTrue( $this->cache
->add( $key, 'test', 5 ) );
167 * @covers BagOStuff::get
169 public function testGet() {
170 $value = [ 'this' => 'is', 'a' => 'test' ];
172 $key = $this->cache
->makeKey( self
::TEST_KEY
);
173 $this->cache
->add( $key, $value, 5 );
174 $this->assertEquals( $this->cache
->get( $key ), $value );
178 * @covers BagOStuff::get
179 * @covers BagOStuff::set
180 * @covers BagOStuff::getWithSetCallback
182 public function testGetWithSetCallback() {
183 $key = $this->cache
->makeKey( self
::TEST_KEY
);
184 $value = $this->cache
->getWithSetCallback(
188 return 'hello kitty';
192 $this->assertEquals( 'hello kitty', $value );
193 $this->assertEquals( $value, $this->cache
->get( $key ) );
197 * @covers BagOStuff::incr
199 public function testIncr() {
200 $key = $this->cache
->makeKey( self
::TEST_KEY
);
201 $this->cache
->add( $key, 0, 5 );
202 $this->cache
->incr( $key );
204 $actualValue = $this->cache
->get( $key );
205 $this->assertEquals( $expectedValue, $actualValue, 'Value should be 1 after incrementing' );
209 * @covers BagOStuff::incrWithInit
211 public function testIncrWithInit() {
212 $key = $this->cache
->makeKey( self
::TEST_KEY
);
213 $val = $this->cache
->incrWithInit( $key, 0, 1, 3 );
214 $this->assertEquals( 3, $val, "Correct init value" );
216 $val = $this->cache
->incrWithInit( $key, 0, 1, 3 );
217 $this->assertEquals( 4, $val, "Correct init value" );
221 * @covers BagOStuff::getMulti
223 public function testGetMulti() {
224 $value1 = [ 'this' => 'is', 'a' => 'test' ];
225 $value2 = [ 'this' => 'is', 'another' => 'test' ];
226 $value3 = [ 'testing a key that may be encoded when sent to cache backend' ];
227 $value4 = [ 'another test where chars in key will be encoded' ];
229 $key1 = $this->cache
->makeKey( 'test-1' );
230 $key2 = $this->cache
->makeKey( 'test-2' );
231 // internally, MemcachedBagOStuffs will encode to will-%25-encode
232 $key3 = $this->cache
->makeKey( 'will-%-encode' );
233 $key4 = $this->cache
->makeKey(
234 'flowdb:flow_ref:wiki:by-source:v3:Parser\'s_"broken"_+_(page)_&_grill:testwiki:1:4.7'
238 $this->cache
->delete( $key1 );
239 $this->cache
->delete( $key2 );
240 $this->cache
->delete( $key3 );
241 $this->cache
->delete( $key4 );
243 $this->cache
->add( $key1, $value1, 5 );
244 $this->cache
->add( $key2, $value2, 5 );
245 $this->cache
->add( $key3, $value3, 5 );
246 $this->cache
->add( $key4, $value4, 5 );
249 [ $key1 => $value1, $key2 => $value2, $key3 => $value3, $key4 => $value4 ],
250 $this->cache
->getMulti( [ $key1, $key2, $key3, $key4 ] )
254 $this->cache
->delete( $key1 );
255 $this->cache
->delete( $key2 );
256 $this->cache
->delete( $key3 );
257 $this->cache
->delete( $key4 );
261 * @covers BagOStuff::getScopedLock
263 public function testGetScopedLock() {
264 $key = $this->cache
->makeKey( self
::TEST_KEY
);
265 $value1 = $this->cache
->getScopedLock( $key, 0 );
266 $value2 = $this->cache
->getScopedLock( $key, 0 );
268 $this->assertType( ScopedCallback
::class, $value1, 'First call returned lock' );
269 $this->assertNull( $value2, 'Duplicate call returned no lock' );
273 $value3 = $this->cache
->getScopedLock( $key, 0 );
274 $this->assertType( ScopedCallback
::class, $value3, 'Lock returned callback after release' );
277 $value1 = $this->cache
->getScopedLock( $key, 0, 5, 'reentry' );
278 $value2 = $this->cache
->getScopedLock( $key, 0, 5, 'reentry' );
280 $this->assertType( ScopedCallback
::class, $value1, 'First reentrant call returned lock' );
281 $this->assertType( ScopedCallback
::class, $value1, 'Second reentrant call returned lock' );
285 * @covers BagOStuff::__construct
286 * @covers BagOStuff::trackDuplicateKeys
288 public function testReportDupes() {
289 $logger = $this->createMock( Psr\Log\NullLogger
::class );
290 $logger->expects( $this->once() )
291 ->method( 'warning' )
292 ->with( 'Duplicate get(): "{key}" fetched {count} times', [
297 $cache = new HashBagOStuff( [
298 'reportDupes' => true,
299 'asyncHandler' => 'DeferredUpdates::addCallableUpdate',
302 $cache->get( 'foo' );
303 $cache->get( 'bar' );
304 $cache->get( 'foo' );
306 DeferredUpdates
::doUpdates();