Merge "AutoLoader: Use require_once rather than require"
[mediawiki.git] / tests / phpunit / includes / cache / LinkCacheTest.php
blobe66470d51c2e2f7db549272688d874cc745903d4
1 <?php
3 use MediaWiki\Cache\LinkCache;
4 use MediaWiki\Page\PageReference;
5 use MediaWiki\Page\PageReferenceValue;
6 use MediaWiki\Title\Title;
7 use MediaWiki\Title\TitleValue;
8 use Wikimedia\ObjectCache\EmptyBagOStuff;
9 use Wikimedia\ObjectCache\HashBagOStuff;
10 use Wikimedia\ObjectCache\WANObjectCache;
11 use Wikimedia\Rdbms\IDBAccessObject;
13 /**
14 * @group Database
15 * @group Cache
16 * @covers \MediaWiki\Cache\LinkCache
18 class LinkCacheTest extends MediaWikiIntegrationTestCase {
19 use LinkCacheTestTrait;
21 private function newLinkCache( ?WANObjectCache $wanCache = null ) {
22 if ( !$wanCache ) {
23 $wanCache = new WANObjectCache( [ 'cache' => new EmptyBagOStuff() ] );
26 return new LinkCache(
27 $this->getServiceContainer()->getTitleFormatter(),
28 $wanCache,
29 $this->getServiceContainer()->getNamespaceInfo(),
30 $this->getServiceContainer()->getDBLoadBalancer()
34 public static function providePageAndLink() {
35 return [
36 [ new PageReferenceValue( NS_USER, __METHOD__, PageReference::LOCAL ) ],
37 [ new TitleValue( NS_USER, __METHOD__ ) ]
41 public static function providePageAndLinkAndArray() {
42 return [
43 [ new PageReferenceValue( NS_USER, __METHOD__, PageReference::LOCAL ) ],
44 [ new TitleValue( NS_USER, __METHOD__ ) ],
45 [ [ 'page_namespace' => NS_USER, 'page_title' => __METHOD__ ] ],
49 private function getPageRow( $offset = 0 ) {
50 return (object)[
51 'page_id' => 8 + $offset,
52 'page_namespace' => 0,
53 'page_title' => 'Test ' . $offset,
54 'page_len' => 18,
55 'page_is_redirect' => 0,
56 'page_latest' => 118 + $offset,
57 'page_content_model' => CONTENT_MODEL_TEXT,
58 'page_lang' => 'xyz',
59 'page_is_new' => 0,
60 'page_touched' => '20200202020202',
64 /**
65 * @dataProvider providePageAndLinkAndArray
66 * @covers \MediaWiki\Cache\LinkCache::addGoodLinkObjFromRow()
67 * @covers \MediaWiki\Cache\LinkCache::getGoodLinkRow()
68 * @covers \MediaWiki\Cache\LinkCache::getGoodLinkID()
69 * @covers \MediaWiki\Cache\LinkCache::getGoodLinkFieldObj()
70 * @covers \MediaWiki\Cache\LinkCache::clearLink()
72 public function testAddGoodLinkObjFromRow( $page ) {
73 $linkCache = $this->newLinkCache();
75 $row = $this->getPageRow();
77 $dbkey = is_array( $page ) ? $page['page_title'] : $page->getDBkey();
78 $ns = is_array( $page ) ? $page['page_namespace'] : $page->getNamespace();
80 $linkCache->addBadLinkObj( $page );
81 $linkCache->addGoodLinkObjFromRow( $page, $row );
83 $this->assertEquals(
84 $row,
85 $linkCache->getGoodLinkRow( $ns, $dbkey )
88 $this->assertSame( $row->page_id, $linkCache->getGoodLinkID( $page ) );
89 $this->assertFalse( $linkCache->isBadLink( $page ) );
91 $this->assertSame(
92 $row->page_id,
93 $linkCache->getGoodLinkFieldObj( $page, 'id' )
95 $this->assertSame(
96 $row->page_len,
97 $linkCache->getGoodLinkFieldObj( $page, 'length' )
99 $this->assertSame(
100 $row->page_is_redirect,
101 $linkCache->getGoodLinkFieldObj( $page, 'redirect' )
103 $this->assertSame(
104 $row->page_latest,
105 $linkCache->getGoodLinkFieldObj( $page, 'revision' )
107 $this->assertSame(
108 $row->page_content_model,
109 $linkCache->getGoodLinkFieldObj( $page, 'model' )
111 $this->assertSame(
112 $row->page_lang,
113 $linkCache->getGoodLinkFieldObj( $page, 'lang' )
116 $this->assertEquals(
117 $row,
118 $linkCache->getGoodLinkRow( $ns, $dbkey )
121 $linkCache->clearBadLink( $page );
122 $this->assertNotNull( $linkCache->getGoodLinkID( $page ) );
123 $this->assertNotNull( $linkCache->getGoodLinkFieldObj( $page, 'length' ) );
125 $linkCache->clearLink( $page );
126 $this->assertSame( 0, $linkCache->getGoodLinkID( $page ) );
127 $this->assertNull( $linkCache->getGoodLinkFieldObj( $page, 'length' ) );
128 $this->assertNull( $linkCache->getGoodLinkRow( $ns, $dbkey ) );
132 * @covers \MediaWiki\Cache\LinkCache::addGoodLinkObjFromRow()
133 * @covers \MediaWiki\Cache\LinkCache::getGoodLinkRow()
134 * @covers \MediaWiki\Cache\LinkCache::getGoodLinkID()
135 * @covers \MediaWiki\Cache\LinkCache::getGoodLinkFieldObj()
137 public function testAddGoodLinkObjWithAllParameters() {
138 $linkCache = $this->getServiceContainer()->getLinkCache();
140 $page = new PageReferenceValue( NS_USER, __METHOD__, PageReference::LOCAL );
141 $this->addGoodLinkObject( 8, $page, 18, 0, 118, CONTENT_MODEL_TEXT, 'xyz' );
143 $row = $linkCache->getGoodLinkRow( $page->getNamespace(), $page->getDBkey() );
144 $this->assertEquals( 8, (int)$row->page_id );
145 $this->assertSame( 8, $linkCache->getGoodLinkID( $page ) );
146 $this->assertSame( 8, $linkCache->getGoodLinkFieldObj( $page, 'id' ) );
148 $this->assertSame(
150 $linkCache->getGoodLinkFieldObj( $page, 'length' )
152 $this->assertSame(
154 $linkCache->getGoodLinkFieldObj( $page, 'redirect' )
156 $this->assertSame(
157 118,
158 $linkCache->getGoodLinkFieldObj( $page, 'revision' )
160 $this->assertSame(
161 CONTENT_MODEL_TEXT,
162 $linkCache->getGoodLinkFieldObj( $page, 'model' )
164 $this->assertSame(
165 'xyz',
166 $linkCache->getGoodLinkFieldObj( $page, 'lang' )
171 * @covers \MediaWiki\Cache\LinkCache::addGoodLinkObjFromRow()
172 * @covers \MediaWiki\Cache\LinkCache::getGoodLinkRow()
173 * @covers \MediaWiki\Cache\LinkCache::getGoodLinkID()
174 * @covers \MediaWiki\Cache\LinkCache::getGoodLinkFieldObj()
176 public function testAddGoodLinkObjFromRowWithMinimalParameters() {
177 $linkCache = $this->getServiceContainer()->getLinkCache();
179 $page = new PageReferenceValue( NS_USER, __METHOD__, PageReference::LOCAL );
181 $this->addGoodLinkObject( 8, $page );
182 $expectedRow = [
183 'page_id' => 8,
184 'page_len' => -1,
185 'page_is_redirect' => 0,
186 'page_latest' => 0,
187 'page_content_model' => null,
188 'page_lang' => null,
191 $actualRow = (array)$linkCache->getGoodLinkRow( $page->getNamespace(), $page->getDBkey() );
192 $this->assertEquals(
193 $expectedRow,
194 array_intersect_key( $actualRow, $expectedRow )
197 $this->assertSame( 8, $linkCache->getGoodLinkID( $page ) );
198 $this->assertSame( 8, $linkCache->getGoodLinkFieldObj( $page, 'id' ) );
200 $this->assertSame(
202 $linkCache->getGoodLinkFieldObj( $page, 'length' )
204 $this->assertSame(
206 $linkCache->getGoodLinkFieldObj( $page, 'redirect' )
208 $this->assertSame(
210 $linkCache->getGoodLinkFieldObj( $page, 'revision' )
212 $this->assertSame(
213 null,
214 $linkCache->getGoodLinkFieldObj( $page, 'model' )
216 $this->assertSame(
217 null,
218 $linkCache->getGoodLinkFieldObj( $page, 'lang' )
223 * @covers \MediaWiki\Cache\LinkCache::addGoodLinkObjFromRow()
225 public function testAddGoodLinkObjFromRowWithInterwikiLink() {
226 $linkCache = $this->getServiceContainer()->getLinkCache();
228 $page = new TitleValue( NS_USER, __METHOD__, '', 'acme' );
230 $this->addGoodLinkObject( 8, $page );
232 $this->assertSame( 0, $linkCache->getGoodLinkID( $page ) );
236 * @dataProvider providePageAndLink
237 * @covers \MediaWiki\Cache\LinkCache::addBadLinkObj()
238 * @covers \MediaWiki\Cache\LinkCache::isBadLink()
239 * @covers \MediaWiki\Cache\LinkCache::clearLink()
241 public function testAddBadLinkObj( $key ) {
242 $linkCache = $this->getServiceContainer()->getLinkCache();
243 $this->assertFalse( $linkCache->isBadLink( $key ) );
245 $this->addGoodLinkObject( 17, $key );
247 $linkCache->addBadLinkObj( $key );
248 $this->assertTrue( $linkCache->isBadLink( $key ) );
249 $this->assertSame( 0, $linkCache->getGoodLinkID( $key ) );
251 $linkCache->clearLink( $key );
252 $this->assertFalse( $linkCache->isBadLink( $key ) );
256 * @covers \MediaWiki\Cache\LinkCache::addBadLinkObj()
258 public function testAddBadLinkObjWithInterwikiLink() {
259 $linkCache = $this->newLinkCache();
261 $page = new TitleValue( NS_USER, __METHOD__, '', 'acme' );
262 $linkCache->addBadLinkObj( $page );
264 $this->assertFalse( $linkCache->isBadLink( $page ) );
268 * @covers \MediaWiki\Cache\LinkCache::addLinkObj()
269 * @covers \MediaWiki\Cache\LinkCache::getGoodLinkFieldObj
271 public function testAddLinkObj() {
272 $existing = $this->getExistingTestPage();
273 $missing = $this->getNonexistingTestPage();
275 $linkCache = $this->newLinkCache();
277 $linkCache->addLinkObj( $existing );
278 $linkCache->addLinkObj( $missing );
280 $this->assertTrue( $linkCache->isBadLink( $missing ) );
281 $this->assertFalse( $linkCache->isBadLink( $existing ) );
283 $this->assertSame( $existing->getId(), $linkCache->getGoodLinkID( $existing ) );
284 $this->assertTrue( $linkCache->isBadLink( $missing ) );
286 // Make sure nothing explodes when getting a field from a non-existing entry
287 $this->assertNull( $linkCache->getGoodLinkFieldObj( $missing, 'length' ) );
291 * @covers \MediaWiki\Cache\LinkCache::addLinkObj()
293 public function testAddLinkObjUsesCachedInfo() {
294 $existing = $this->getExistingTestPage();
295 $missing = $this->getNonexistingTestPage();
297 $fakeRow = $this->getPageRow( $existing->getId() + 100 );
299 $linkCache = $this->newLinkCache();
301 // pretend the existing page is missing, and the missing page exists
302 $linkCache->addGoodLinkObjFromRow( $missing, $fakeRow );
303 $linkCache->addBadLinkObj( $existing );
305 // the LinkCache should use the cached info and not look into the database
306 $this->assertSame( (int)$fakeRow->page_id, $linkCache->addLinkObj( $missing ) );
307 $this->assertSame( 0, $linkCache->addLinkObj( $existing ) );
309 // now set the "read latest" flag and try again
310 $flags = IDBAccessObject::READ_LATEST;
311 $this->assertSame( 0, $linkCache->addLinkObj( $missing, $flags ) );
312 $this->assertSame( $existing->getId(), $linkCache->addLinkObj( $existing, $flags ) );
316 * @covers \MediaWiki\Cache\LinkCache::addLinkObj()
318 public function testAddLinkObjUsesWANCache() {
319 // For some namespaces we cache data (Template, File, etc)
320 $existing = $this->getExistingTestPage( Title::makeTitle( NS_TEMPLATE, __METHOD__ ) );
321 $wanCache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
322 $linkCache = $this->newLinkCache( $wanCache );
324 // load the page row into the cache
325 $linkCache->addLinkObj( $existing );
327 // replace real row data with fake, and assert that it gets used
328 $key = $wanCache->makeKey( 'page', $existing->getNamespace(), sha1( $existing->getDBkey() ) );
329 $fakeRow = $this->getPageRow( $existing->getId() + 100 );
330 $wanCache->set( $key, $fakeRow );
331 // clear in-class cache
332 $linkCache->clearLink( $existing );
333 $this->assertSame( (int)$fakeRow->page_id, $linkCache->addLinkObj( $existing ) );
335 // set the "read latest" flag and try again
336 $flags = IDBAccessObject::READ_LATEST;
337 $this->assertSame( $existing->getId(), $linkCache->addLinkObj( $existing, $flags ) );
340 public function testFalsyPageName() {
341 $linkCache = $this->newLinkCache();
343 // The stringified value is "0", which is falsy in PHP!
344 $link = new TitleValue( NS_MAIN, '0' );
346 $linkCache->addBadLinkObj( $link );
347 $this->assertTrue( $linkCache->isBadLink( $link ) );
349 $row = $this->getPageRow();
350 $linkCache->addGoodLinkObjFromRow( $link, $row );
351 $this->assertGreaterThan( 0, $linkCache->getGoodLinkID( $link ) );
353 $this->assertSame( $row, $linkCache->getGoodLinkRow( NS_MAIN, '0' ) );
356 public function testClearBadLinkWithString() {
357 $linkCache = $this->newLinkCache();
358 $linkCache->clearBadLink( 'Xyzzy' );
359 $this->addToAssertionCount( 1 );
362 public function testIsBadLinkWithString() {
363 $linkCache = $this->newLinkCache();
364 $this->assertFalse( $linkCache->isBadLink( 'Xyzzy' ) );
367 public function testGetGoodLinkIdWithString() {
368 $linkCache = $this->newLinkCache();
369 $this->assertSame( 0, $linkCache->getGoodLinkID( 'Xyzzy' ) );
372 public static function provideInvalidPageParams() {
373 return [
374 'empty' => [ NS_MAIN, '' ],
375 'bad chars' => [ NS_MAIN, '_|_' ],
376 'empty in namspace' => [ NS_USER, '' ],
377 'special' => [ NS_SPECIAL, 'RecentChanges' ],
382 * @dataProvider provideInvalidPageParams
383 * @covers \MediaWiki\Cache\LinkCache::getGoodLinkRow()
385 public function testGetGoodLinkRowWithBadParams( $ns, $dbkey ) {
386 $linkCache = $this->newLinkCache();
387 $this->assertNull( $linkCache->getGoodLinkRow( $ns, $dbkey ) );
390 public function getRowIfExisting( $db, $ns, $dbkey, $queryOptions ) {
391 if ( $dbkey === 'Existing' ) {
392 return $this->getPageRow();
395 return null;
399 * @covers \MediaWiki\Cache\LinkCache::getGoodLinkRow()
400 * @covers \MediaWiki\Cache\LinkCache::getGoodLinkFieldObj
402 public function testGetGoodLinkRow() {
403 $existing = new TitleValue( NS_MAIN, 'Existing' );
404 $missing = new TitleValue( NS_MAIN, 'Missing' );
406 $linkCache = $this->newLinkCache();
407 $callback = [ $this, 'getRowIfExisting' ];
409 $linkCache->getGoodLinkRow( $existing->getNamespace(), $existing->getDBkey(), $callback );
410 $linkCache->getGoodLinkRow( $missing->getNamespace(), $missing->getDBkey(), $callback );
412 $this->assertTrue( $linkCache->isBadLink( $missing ) );
413 $this->assertFalse( $linkCache->isBadLink( $existing ) );
415 $this->assertGreaterThan( 0, $linkCache->getGoodLinkID( $existing ) );
416 $this->assertTrue( $linkCache->isBadLink( $missing ) );
418 // Make sure nothing explodes when getting a field from a non-existing entry
419 $this->assertNull( $linkCache->getGoodLinkFieldObj( $missing, 'length' ) );
423 * @covers \MediaWiki\Cache\LinkCache::getGoodLinkRow()
425 public function testGetGoodLinkRowUsesCachedInfo() {
426 $existing = new TitleValue( NS_MAIN, 'Existing' );
427 $missing = new TitleValue( NS_MAIN, 'Missing' );
428 $callback = [ $this, 'getRowIfExisting' ];
430 $existingRow = $this->getPageRow( 0 );
431 $fakeRow = $this->getPageRow( 3 );
433 $linkCache = $this->newLinkCache();
435 // pretend the existing page is missing, and the missing page exists
436 $linkCache->addGoodLinkObjFromRow( $missing, $fakeRow );
437 $linkCache->addBadLinkObj( $existing );
439 // the LinkCache should use the cached info and not look into the database
440 $this->assertSame(
441 $fakeRow,
442 $linkCache->getGoodLinkRow( $missing->getNamespace(), $missing->getDBkey(), $callback )
444 $this->assertNull(
445 $linkCache->getGoodLinkRow( $existing->getNamespace(), $existing->getDBkey(), $callback )
448 // now set the "read latest" flag and try again
449 $flags = IDBAccessObject::READ_LATEST;
450 $this->assertNull(
451 $linkCache->getGoodLinkRow(
452 $missing->getNamespace(),
453 $missing->getDBkey(),
454 $callback,
455 $flags
458 $this->assertEquals(
459 $existingRow,
460 $linkCache->getGoodLinkRow(
461 $existing->getNamespace(),
462 $existing->getDBkey(),
463 $callback,
464 $flags
468 // pretend again that the missing page exists, but pretend even harder
469 $linkCache->addGoodLinkObjFromRow( $missing, $fakeRow, IDBAccessObject::READ_LATEST );
471 // the LinkCache should use the cached info and not look into the database
472 $this->assertSame(
473 $fakeRow,
474 $linkCache->getGoodLinkRow( $missing->getNamespace(), $missing->getDBkey(), $callback )
477 // now set the "read latest" flag and try again
478 $flags = IDBAccessObject::READ_LATEST;
479 $this->assertEquals(
480 $fakeRow,
481 $linkCache->getGoodLinkRow(
482 $missing->getNamespace(),
483 $missing->getDBkey(),
484 $callback,
485 $flags