Page MenuHomeWickedGov Phorge

ParserCacheTest.php
No OneTemporary

Size
29 KB
Referenced Files
None
Subscribers
None

ParserCacheTest.php

<?php
namespace MediaWiki\Tests\Parser;
use InvalidArgumentException;
use MediaWiki\HookContainer\HookContainer;
use MediaWiki\Json\JsonCodec;
use MediaWiki\Page\PageRecord;
use MediaWiki\Page\PageStoreRecord;
use MediaWiki\Page\WikiPage;
use MediaWiki\Page\WikiPageFactory;
use MediaWiki\Parser\CacheTime;
use MediaWiki\Parser\ParserCache;
use MediaWiki\Parser\ParserCacheFilter;
use MediaWiki\Parser\ParserOptions;
use MediaWiki\Parser\ParserOutput;
use MediaWiki\Tests\Json\JsonDeserializableSuperClass;
use MediaWiki\Title\Title;
use MediaWiki\Title\TitleFactory;
use MediaWiki\User\User;
use MediaWikiIntegrationTestCase;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Psr\Log\NullLogger;
use TestLogger;
use Wikimedia\ObjectCache\BagOStuff;
use Wikimedia\ObjectCache\EmptyBagOStuff;
use Wikimedia\ObjectCache\HashBagOStuff;
use Wikimedia\Stats\StatsFactory;
use Wikimedia\TestingAccessWrapper;
use Wikimedia\Timestamp\ConvertibleTimestamp;
use Wikimedia\UUID\GlobalIdGenerator;
/**
* @covers \MediaWiki\Parser\ParserCache
*/
class ParserCacheTest extends MediaWikiIntegrationTestCase {
/** @var int */
private $time;
/** @var string */
private $cacheTime;
/** @var PageRecord */
private $page;
protected function setUp(): void {
parent::setUp();
$this->time = time();
$this->cacheTime = ConvertibleTimestamp::convert( TS_MW, $this->time + 1 );
$this->page = $this->createPageRecord();
ConvertibleTimestamp::setFakeTime( $this->time );
}
private function createPageRecord( array $overrides = [] ): PageRecord {
return new PageStoreRecord(
(object)array_merge( [
'page_id' => 42,
'page_namespace' => NS_MAIN,
'page_title' => 'Testing_Testing',
'page_latest' => 24,
'page_is_new' => false,
'page_is_redirect' => false,
'page_touched' => $this->time,
'page_lang' => 'qqx',
],
$overrides ),
PageRecord::LOCAL
);
}
/**
* @param HookContainer|null $hookContainer
* @param BagOStuff|null $storage
* @param LoggerInterface|null $logger
* @param WikiPageFactory|null $wikiPageFactory
* @return ParserCache
*/
private function createParserCache(
?HookContainer $hookContainer = null,
?BagOStuff $storage = null,
?LoggerInterface $logger = null,
?WikiPageFactory $wikiPageFactory = null
): ParserCache {
if ( !$wikiPageFactory ) {
$wikiPageMock = $this->createMock( WikiPage::class );
$wikiPageMock->method( 'getContentModel' )->willReturn( CONTENT_MODEL_WIKITEXT );
$wikiPageFactory = $this->createMock( WikiPageFactory::class );
$wikiPageFactory->method( 'newFromTitle' )->willReturn( $wikiPageMock );
}
$globalIdGenerator = $this->createMock( GlobalIdGenerator::class );
$globalIdGenerator->method( 'newUUIDv1' )->willReturn( 'uuid-uuid' );
return new ParserCache(
'test',
$storage ?: new HashBagOStuff(),
'19900220000000',
$hookContainer ?: $this->createHookContainer( [] ),
new JsonCodec(),
StatsFactory::newNull(),
$logger ?: new NullLogger(),
$this->createMock( TitleFactory::class ),
$wikiPageFactory,
$globalIdGenerator
);
}
private function getDummyUsedOptions(): array {
return array_slice(
ParserOptions::allCacheVaryingOptions(),
0,
2
);
}
private function createDummyParserOutput(): ParserOutput {
$parserOutput = new ParserOutput();
$parserOutput->setRawText( 'TEST' );
foreach ( $this->getDummyUsedOptions() as $option ) {
$parserOutput->recordOption( $option );
}
$parserOutput->updateCacheExpiry( 4242 );
$parserOutput->setRenderId( 'dummy-render-id' );
$parserOutput->setCacheRevisionId( 0 );
// ParserOutput::getCacheTime() also sets it as a side effect
$parserOutput->setRevisionTimestamp( $parserOutput->getCacheTime() );
return $parserOutput;
}
public function testGetMetadataMissing() {
$cache = $this->createParserCache();
$metadataFromCache = $cache->getMetadata(
$this->page,
ParserCache::USE_CURRENT_ONLY
);
$this->assertNull( $metadataFromCache );
}
public function testGetMetadataAllGood() {
$cache = $this->createParserCache();
$parserOutput = $this->createDummyParserOutput();
$cache->save(
$parserOutput,
$this->page,
ParserOptions::newFromAnon(),
$this->cacheTime
);
$metadataFromCache = $cache->getMetadata(
$this->page,
ParserCache::USE_CURRENT_ONLY
);
$this->assertNotNull( $metadataFromCache );
$this->assertSame(
$this->getDummyUsedOptions(),
$metadataFromCache->getUsedOptions()
);
$this->assertSame(
4242,
$metadataFromCache->getCacheExpiry()
);
$this->assertSame(
$this->page->getLatest(),
$metadataFromCache->getCacheRevisionId()
);
$this->assertSame(
$this->cacheTime,
$metadataFromCache->getCacheTime()
);
}
public function testGetMetadataExpired() {
$cache = $this->createParserCache();
$parserOutput = $this->createDummyParserOutput();
$cache->save(
$parserOutput,
$this->page,
ParserOptions::newFromAnon(),
$this->cacheTime
);
$this->page = $this->createPageRecord( [ 'page_touched' => $this->time + 10000 ] );
$this->assertNull(
$cache->getMetadata(
$this->page,
ParserCache::USE_CURRENT_ONLY
)
);
$metadataFromCache = $cache->getMetadata(
$this->page,
ParserCache::USE_EXPIRED
);
$this->assertNotNull( $metadataFromCache );
$this->assertSame(
$this->getDummyUsedOptions(),
$metadataFromCache->getUsedOptions()
);
$this->assertSame(
4242,
$metadataFromCache->getCacheExpiry()
);
$this->assertSame(
$this->page->getLatest(),
$metadataFromCache->getCacheRevisionId()
);
$this->assertSame(
$this->cacheTime,
$metadataFromCache->getCacheTime()
);
}
public function testGetMetadataOutdated() {
$cache = $this->createParserCache();
$parserOutput = $this->createDummyParserOutput();
$cache->save(
$parserOutput,
$this->page,
ParserOptions::newFromAnon(),
$this->cacheTime
);
$this->page = $this->createPageRecord( [ 'page_latest' => $this->page->getLatest() + 1 ] );
$this->assertNull(
$cache->getMetadata(
$this->page,
ParserCache::USE_CURRENT_ONLY
)
);
$this->assertNull(
$cache->getMetadata(
$this->page,
ParserCache::USE_EXPIRED
)
);
$metadataFromCache = $cache->getMetadata(
$this->page,
ParserCache::USE_OUTDATED
);
$this->assertSame(
$this->getDummyUsedOptions(),
$metadataFromCache->getUsedOptions()
);
$this->assertSame(
4242,
$metadataFromCache->getCacheExpiry()
);
$this->assertNotSame(
$this->page->getLatest(),
$metadataFromCache->getCacheRevisionId()
);
$this->assertSame(
$this->cacheTime,
$metadataFromCache->getCacheTime()
);
}
public function testMakeParserOutputKey() {
$cache = $this->createParserCache();
$options1 = ParserOptions::newFromAnon();
$options1->setOption(
$this->getDummyUsedOptions()[0],
'value1'
);
$key1 = $cache->makeParserOutputKey(
$this->page,
$options1,
$this->getDummyUsedOptions()
);
$this->assertNotNull( $key1 );
$options2 = ParserOptions::newFromAnon();
$options2->setOption(
$this->getDummyUsedOptions()[0],
'value2'
);
$key2 = $cache->makeParserOutputKey(
$this->page,
$options2,
$this->getDummyUsedOptions()
);
$this->assertNotNull( $key2 );
$this->assertNotSame(
$key1,
$key2
);
}
/*
* Test that fetching without storing first returns false.
*/
public function testGetEmpty() {
$cache = $this->createParserCache();
$options = ParserOptions::newFromAnon();
$this->assertFalse(
$cache->get(
$this->page,
$options
)
);
}
/**
* Test that fetching with the same options return the saved value.
*/
public function testSaveGetSameOptions() {
$cache = $this->createParserCache();
$parserOutput = new ParserOutput( 'TEST_TEXT' );
$options1 = ParserOptions::newFromAnon();
$options1->setOption(
$this->getDummyUsedOptions()[0],
'value1'
);
$cache->save(
$parserOutput,
$this->page,
$options1,
$this->cacheTime
);
$savedOutput = $cache->get(
$this->page,
$options1
);
$this->assertInstanceOf(
ParserOutput::class,
$savedOutput
);
// ParserCache adds a comment to the HTML, so check if the result starts with page content.
$this->assertStringStartsWith(
'TEST_TEXT',
$savedOutput->getRawText()
);
$this->assertSame(
$this->cacheTime,
$savedOutput->getCacheTime()
);
$this->assertSame(
$this->page->getLatest(),
$savedOutput->getCacheRevisionId()
);
}
/**
* Test that fetching with different unused option returns a value.
*/
public function testSaveGetDifferentUnusedOption() {
$cache = $this->createParserCache();
$optionName = $this->getDummyUsedOptions()[0];
$parserOutput = new ParserOutput( 'TEST_TEXT' );
$options1 = ParserOptions::newFromAnon();
$options1->setOption(
$optionName,
'value1'
);
$cache->save(
$parserOutput,
$this->page,
$options1,
$this->cacheTime
);
$options2 = ParserOptions::newFromAnon();
$options2->setOption(
$optionName,
'value2'
);
$savedOutput = $cache->get(
$this->page,
$options2
);
$this->assertInstanceOf(
ParserOutput::class,
$savedOutput
);
// ParserCache adds a comment to the HTML, so check if the result starts with page content.
$this->assertStringStartsWith(
'TEST_TEXT',
$savedOutput->getRawText()
);
$this->assertSame(
$this->cacheTime,
$savedOutput->getCacheTime()
);
$this->assertSame(
$this->page->getLatest(),
$savedOutput->getCacheRevisionId()
);
}
/**
* Test that non-cacheable output is not stored
*/
public function testDoesNotStoreNonCacheable() {
$cache = $this->createParserCache();
$parserOutput = new ParserOutput( 'TEST_TEXT' );
$parserOutput->updateCacheExpiry( 0 );
$options1 = ParserOptions::newFromAnon();
$cache->save(
$parserOutput,
$this->page,
$options1,
$this->cacheTime
);
$this->assertFalse(
$cache->get(
$this->page,
$options1
)
);
$this->assertFalse(
$cache->get(
$this->page,
$options1,
true
)
);
$this->assertFalse(
$cache->getDirty(
$this->page,
$options1
)
);
}
/**
* Allow ParserCacheFilter hook to prevent content from being cached
*/
public function testDoesNotStoreFiltered() {
$cache = $this->createParserCache();
$parserOutput = new ParserOutput( 'TEST_TEXT' );
$parserOutput->resetParseStartTime();
$parserOutput->recordTimeProfile();
// Only cache output that took at least 100 seconds of CPU to generate
$cache->setFilter( new ParserCacheFilter( [
'default' => [ 'minCpuTime' => 100 ],
] ) );
$options1 = ParserOptions::newFromAnon();
$cache->save(
$parserOutput,
$this->page,
$options1,
$this->cacheTime
);
$this->assertFalse(
$cache->get(
$this->page,
$options1
)
);
$this->assertFalse(
$cache->get(
$this->page,
$options1,
true
)
);
$this->assertFalse(
$cache->getDirty(
$this->page,
$options1
)
);
}
/**
* ParserCache::save must respect ParserOptions::isSafeToCache
*/
public function testDoesNotStoreNotSafeToCacheAndUsed() {
$cache = $this->createParserCache();
$parserOutput = new ParserOutput( 'TEST_TEXT' );
$parserOutput->recordOption( 'wrapclass' );
$options = ParserOptions::newFromAnon();
$options->setOption(
'wrapclass',
'wrapwrap'
);
$cache->save(
$parserOutput,
$this->page,
$options,
$this->cacheTime
);
$this->assertFalse(
$cache->get(
$this->page,
$options
)
);
$this->assertFalse(
$cache->get(
$this->page,
$options,
true
)
);
$this->assertFalse(
$cache->getDirty(
$this->page,
$options
)
);
}
/**
* get() must respect ParserOptions::isSafeToCache.
*/
public function testDoesNotGetNotSafeToCache() {
$cache = $this->createParserCache();
$parserOutput = new ParserOutput( 'TEST_TEXT' );
$parserOutput->recordOption( 'wrapclass' );
$cache->save(
$parserOutput,
$this->page,
ParserOptions::newFromAnon(),
$this->cacheTime
);
$otherOptions = ParserOptions::newFromAnon();
$otherOptions->setOption(
'wrapclass',
'wrapwrap'
);
$this->assertFalse(
$cache->get(
$this->page,
$otherOptions
)
);
$this->assertFalse(
$cache->get(
$this->page,
$otherOptions,
true
)
);
$this->assertFalse(
$cache->getDirty(
$this->page,
$otherOptions
)
);
}
/**
* save() must respect ParserOptions::isSafeToCache
*/
public function testStoresNotSafeToCacheAndUnused() {
$cache = $this->createParserCache();
$parserOutput = new ParserOutput( 'TEST_TEXT' );
$options = ParserOptions::newFromAnon();
$options->setOption(
'wrapclass',
'wrapwrap'
);
$cache->save(
$parserOutput,
$this->page,
$options,
$this->cacheTime
);
$this->assertStringContainsString(
'TEST_TEXT',
$cache->get(
$this->page,
$options
)->getRawText()
);
}
/**
* get() with an option should not return a saved match for a different option.
*/
public function testSaveGetDifferentUsedOption() {
$cache = $this->createParserCache();
$parserOutput = new ParserOutput( 'TEST_TEXT' );
$optionName = $this->getDummyUsedOptions()[0];
$parserOutput->recordOption( $optionName );
$options1 = ParserOptions::newFromAnon();
$options1->setOption(
$optionName,
'value1'
);
$cache->save(
$parserOutput,
$this->page,
$options1,
$this->cacheTime
);
$options2 = ParserOptions::newFromAnon();
$options2->setOption(
$optionName,
'value2'
);
$this->assertFalse(
$cache->get(
$this->page,
$options2
)
);
}
/**
* Allow retrieval of expired metadata via getDirty()
*/
public function testGetExpiredMetadata() {
$cache = $this->createParserCache();
$parserOutput = new ParserOutput( 'TEST_TEXT' );
$parserOutput->updateCacheExpiry( 10 );
$options1 = ParserOptions::newFromAnon();
$cache->save(
$parserOutput,
$this->page,
$options1,
$this->cacheTime
);
ConvertibleTimestamp::setFakeTime( $this->time + 15 * 1000 );
$this->assertFalse(
$cache->get(
$this->page,
$options1
)
);
$this->assertInstanceOf(
ParserOutput::class,
$cache->get(
$this->page,
$options1,
true
)
);
$this->assertInstanceOf(
ParserOutput::class,
$cache->getDirty(
$this->page,
$options1
)
);
}
/**
* Allow retrieval of expired output with good metadata via getDirty()
*/
public function testGetExpiredContent() {
$cache = $this->createParserCache();
$optionName = $this->getDummyUsedOptions()[0];
$parserOutput1 = new ParserOutput( 'TEST_TEXT1' );
$parserOutput1->recordOption( $optionName );
$parserOutput1->updateCacheExpiry( 10 );
$options1 = ParserOptions::newFromAnon();
$options1->setOption(
$optionName,
'value1'
);
$cache->save(
$parserOutput1,
$this->page,
$options1,
$this->cacheTime
);
$parserOutput2 = new ParserOutput( 'TEST_TEXT2' );
$parserOutput2->recordOption( $optionName );
$parserOutput2->updateCacheExpiry( 100500600 );
$options2 = ParserOptions::newFromAnon();
$options2->setOption(
$optionName,
'value2'
);
$cache->save(
$parserOutput2,
$this->page,
$options2,
$this->cacheTime
);
ConvertibleTimestamp::setFakeTime( $this->time + 15 * 1000 );
$this->assertFalse(
$cache->get(
$this->page,
$options1
)
);
$this->assertInstanceOf(
ParserOutput::class,
$cache->get(
$this->page,
$options1,
true
)
);
$this->assertInstanceOf(
ParserOutput::class,
$cache->getDirty(
$this->page,
$options1
)
);
}
/**
* Allow retrieval of expired output with expired metadata via getDirty()
*/
public function testGetOutdatedMetadata() {
$cache = $this->createParserCache();
$parserOutput = new ParserOutput( 'TEST_TEXT' );
$options1 = ParserOptions::newFromAnon();
$cache->save(
$parserOutput,
$this->page,
$options1,
$this->cacheTime
);
$this->assertInstanceOf(
ParserOutput::class,
$cache->get(
$this->page,
$options1
)
);
$this->page = $this->createPageRecord( [ 'page_latest' => $this->page->getLatest() + 1 ] );
$this->assertFalse(
$cache->get(
$this->page,
$options1
)
);
$this->assertInstanceOf(
ParserOutput::class,
$cache->get(
$this->page,
$options1,
true
)
);
$this->assertInstanceOf(
ParserOutput::class,
$cache->getDirty(
$this->page,
$options1
)
);
}
/**
* Allow retrieval of outdated output with good metadata via getDirty()
*/
public function testGetOutdatedContent() {
$cache = $this->createParserCache();
$optionName = $this->getDummyUsedOptions()[0];
$parserOutput1 = new ParserOutput( 'TEST_TEXT' );
$parserOutput1->recordOption( $optionName );
$options1 = ParserOptions::newFromAnon();
$options1->setOption(
$optionName,
'value1'
);
$cache->save(
$parserOutput1,
$this->page,
$options1,
$this->cacheTime
);
$this->page = $this->createPageRecord( [ 'page_latest' => $this->page->getLatest() + 1 ] );
$parserOutput2 = new ParserOutput( 'TEST_TEXT' );
$parserOutput2->recordOption( $optionName );
$options2 = ParserOptions::newFromAnon();
$options2->setOption(
$optionName,
'value2'
);
$cache->save(
$parserOutput2,
$this->page,
$options2,
$this->cacheTime
);
$this->assertFalse(
$cache->get(
$this->page,
$options1
)
);
$this->assertInstanceOf(
ParserOutput::class,
$cache->get(
$this->page,
$options1,
true
)
);
$this->assertInstanceOf(
ParserOutput::class,
$cache->getDirty(
$this->page,
$options1
)
);
}
/**
* get() must return false after deleting a key.
*/
public function testDeleteOptionsKey() {
$cache = $this->createParserCache();
$parserOutput = new ParserOutput( 'TEST_TEXT' );
$options1 = ParserOptions::newFromAnon();
$cache->save(
$parserOutput,
$this->page,
$options1,
$this->cacheTime
);
$this->assertInstanceOf(
ParserOutput::class,
$cache->get(
$this->page,
$options1
)
);
$cache->deleteOptionsKey( $this->page );
$this->assertFalse(
$cache->get(
$this->page,
$options1
)
);
}
/**
* Allow RejectParserCacheValue hook to reject a ParserOutput cache hit.
*/
public function testRejectedByHook() {
$parserOutput = new ParserOutput( 'TEST_TEXT' );
$options = ParserOptions::newFromAnon();
$options->setOption(
$this->getDummyUsedOptions()[0],
'value1'
);
$wikiPageMock = $this->createMock( WikiPage::class );
$wikiPageMock->method( 'getContentModel' )->willReturn( CONTENT_MODEL_WIKITEXT );
$wikiPageFactory = $this->createMock( WikiPageFactory::class );
$wikiPageFactory->method( 'newFromTitle' )->with( $this->page )->willReturn(
$wikiPageMock
);
$hookContainer = $this->createHookContainer( [
'RejectParserCacheValue' => function (
ParserOutput $value,
WikiPage $hookPage,
ParserOptions $popts
) use ( $wikiPageMock, $parserOutput, $options ) {
// parse start time is not saved/restored in the cache
$parserOutput->clearParseStartTime();
$this->assertEquals(
$parserOutput,
$value
);
$this->assertSame(
$wikiPageMock,
$hookPage
);
$this->assertSame(
$options,
$popts
);
return false;
},
] );
$cache = $this->createParserCache(
$hookContainer,
null,
null,
$wikiPageFactory
);
$cache->save(
$parserOutput,
$this->page,
$options,
$this->cacheTime
);
$this->assertFalse(
$cache->get(
$this->page,
$options
)
);
}
/**
* ParserCacheSaveComplete hook must run
*/
public function testParserCacheSaveCompleteHook() {
$parserOutput = new ParserOutput( 'TEST_TEXT' );
$options = ParserOptions::newFromAnon();
$options->setOption(
$this->getDummyUsedOptions()[0],
'value1'
);
$hookContainer = $this->createHookContainer( [
'ParserCacheSaveComplete' => function (
ParserCache $hookCache,
ParserOutput $value,
Title $hookTitle,
ParserOptions $popts,
int $revId
) use ( $parserOutput, $options ) {
$this->assertSame(
$parserOutput,
$value
);
$this->assertSame(
$options,
$popts
);
$this->assertSame(
42,
$revId
);
},
] );
$cache = $this->createParserCache( $hookContainer );
$cache->save(
$parserOutput,
$this->page,
$options,
$this->cacheTime,
42
);
}
/**
* get() gracefully skips non-existing pages
*/
public function testSkipIfNotExist() {
$mockPage = $this->createNoOpMock(
PageRecord::class,
[
'exists',
'assertWiki',
]
);
$mockPage->method( 'exists' )->willReturn( false );
$wikiPageMock = $this->createMock( WikiPage::class );
$wikiPageMock->method( 'getContentModel' )->willReturn( 'wikitext' );
$wikiPageFactoryMock = $this->createMock( WikiPageFactory::class );
$wikiPageFactoryMock->method( 'newFromTitle' )->with( $mockPage )->willReturn(
$wikiPageMock
);
$cache = $this->createParserCache(
null,
null,
null,
$wikiPageFactoryMock
);
$this->assertFalse(
$cache->get(
$mockPage,
ParserOptions::newFromAnon()
)
);
}
/**
* Tests that parser cache skips entries for redirect pages.
* Regression test for T389591.
*/
public function testGet_redirect() {
$cache = $this->createParserCache();
$popt = ParserOptions::newFromAnon();
$pout = $this->createDummyParserOutput();
$page = $this->createPageRecord( [
'page_is_redirect' => true,
] );
$cache->save( $pout, $page, $popt );
$result = $cache->get( $page, $popt );
$this->assertInstanceOf( ParserOutput::class, $result );
$this->assertSame( 'TEST', $result->getRawText() );
}
/**
* Tests that getCacheStorage returns underlying BagOStuff
*
* @covers \MediaWiki\Parser\ParserCache::getCacheStorage
*/
public function testGetCacheStorage() {
$storage = new EmptyBagOStuff();
$cache = $this->createParserCache(
null,
$storage
);
$this->assertSame(
$storage,
$cache->getCacheStorage()
);
}
/**
* @covers \MediaWiki\Parser\ParserCache::save
*/
public function testSaveNoText() {
$this->expectException( InvalidArgumentException::class );
$this->createParserCache()->save(
new ParserOutput( null ),
$this->page,
ParserOptions::newFromAnon()
);
}
public static function provideCorruptData() {
yield 'JSON serialization, bad data' => [ 'bla bla' ];
yield 'JSON serialization, no _class_' => [ '{"test":"test"}' ];
yield 'JSON serialization, non-existing _class_' => [ '{"_class_":"NonExistentBogusClass"}' ];
$wrongInstance = new JsonDeserializableSuperClass( 'test' );
yield 'JSON serialization, wrong class' => [
json_encode(
$wrongInstance->jsonSerialize()
),
];
}
/**
* Test that we handle corrupt data gracefully.
* This is important for forward-compatibility with JSON serialization.
* We want to be sure that we don't crash horribly if we have to roll
* back to a version of the code that doesn't know about JSON.
*
* @dataProvider provideCorruptData
* @covers \MediaWiki\Parser\ParserCache::get
* @covers \MediaWiki\Parser\ParserCache::restoreFromJson
*
* @param string $data
*/
public function testCorruptData( string $data ) {
$cache = $this->createParserCache(
null,
new HashBagOStuff()
);
$parserOutput = new ParserOutput( 'TEST_TEXT' );
$options1 = ParserOptions::newFromAnon();
$cache->save(
$parserOutput,
$this->page,
$options1,
$this->cacheTime
);
$outputKey = $cache->makeParserOutputKey(
$this->page,
$options1,
$parserOutput->getUsedOptions()
);
$cache->getCacheStorage()->set(
$outputKey,
$data
);
// just make sure we don't crash and burn
$this->assertFalse(
$cache->get(
$this->page,
$options1
)
);
}
/**
* Test that we handle corrupt data gracefully.
* This is important for forward-compatibility with JSON serialization.
* We want to be sure that we don't crash horribly if we have to roll
* back to a version of the code that doesn't know about JSON.
*
* @covers \MediaWiki\Parser\ParserCache::getMetadata
*/
public function testCorruptMetadata() {
$cacheStorage = new HashBagOStuff();
$cache = $this->createParserCache(
null,
$cacheStorage
);
$parserOutput = new ParserOutput( 'TEST_TEXT' );
$options1 = ParserOptions::newFromAnon();
$cache->save(
$parserOutput,
$this->page,
$options1,
$this->cacheTime
);
// Mess up the metadata
$optionsKey = TestingAccessWrapper::newFromObject( $cache )->makeMetadataKey(
$this->page
);
$cacheStorage->set(
$optionsKey,
'bad data'
);
// Recreate the cache to drop in-memory cached metadata.
$cache = $this->createParserCache(
null,
$cacheStorage
);
// just make sure we don't crash and burn
$this->assertNull( $cache->getMetadata( $this->page ) );
}
/**
* Test what happens when upgrading from 1.35 or earlier,
* when old cache entries do not yet use JSON.
*
* @covers \MediaWiki\Parser\ParserCache::get
*/
public function testMigrationToJson() {
$bagOStuff = new HashBagOStuff();
$wikiPageMock = $this->createMock( WikiPage::class );
$wikiPageMock->method( 'getContentModel' )->willReturn( CONTENT_MODEL_WIKITEXT );
$wikiPageFactory = $this->createMock( WikiPageFactory::class );
$wikiPageFactory->method( 'newFromTitle' )->willReturn( $wikiPageMock );
$globalIdGenerator = $this->createMock( GlobalIdGenerator::class );
$globalIdGenerator->method( 'newUUIDv1' )->willReturn( 'uuid-uuid' );
$cache = $this->getMockBuilder( ParserCache::class )->setConstructorArgs( [
'test',
$bagOStuff,
'19900220000000',
$this->createHookContainer( [] ),
new JsonCodec(),
StatsFactory::newNull(),
new NullLogger(),
$this->createMock( TitleFactory::class ),
$wikiPageFactory,
$globalIdGenerator,
] )->onlyMethods( [ 'convertForCache' ] )->getMock();
// Emulate pre-1.36 behavior: rely on native PHP serialization.
// Note that backwards compatibility of the actual serialization is covered
// by ParserOutputTest which uses various versions of serialized data
// under tests/phpunit/data/ParserCache.
$cache->method( 'convertForCache' )->willReturnCallback(
static function ( CacheTime $obj, string $key ) {
return $obj;
}
);
$parserOutput1 = new ParserOutput( 'Lorem Ipsum' );
$options = ParserOptions::newFromAnon();
$cache->save(
$parserOutput1,
$this->page,
$options,
$this->cacheTime
);
// emulate migration to JSON
$cache = $this->createParserCache(
null,
$bagOStuff
);
// make sure we can load non-json cache data
$cachedOutput = $cache->get(
$this->page,
$options
);
$parserOutput1->clearParseStartTime(); // not saved/restored
$this->assertEquals(
$parserOutput1,
$cachedOutput
);
// now test that the cache works when using JSON
$parserOutput2 = new ParserOutput( 'dolor sit amet' );
$cache->save(
$parserOutput2,
$this->page,
$options,
$this->cacheTime
);
// make sure we can load json cache data
$cachedOutput = $cache->get(
$this->page,
$options
);
$parserOutput2->clearParseStartTime(); // not saved/restored
$this->assertEquals(
$parserOutput2,
$cachedOutput
);
}
/**
* @covers \MediaWiki\Parser\ParserCache::convertForCache
*/
public function testNonSerializableJsonIsReported() {
$testLogger = new TestLogger( true );
$cache = $this->createParserCache(
null,
null,
$testLogger
);
$parserOutput = $this->createDummyParserOutput();
$parserOutput->setExtensionData(
'test',
new User()
);
$cache->save(
$parserOutput,
$this->page,
ParserOptions::newFromAnon()
);
$this->assertArraySubmapSame(
[
[
LogLevel::ERROR,
'Unable to serialize JSON',
],
],
$testLogger->getBuffer()
);
}
/**
* @covers \MediaWiki\Parser\ParserCache::convertForCache
*/
public function testCyclicStructuresDoNotBlowUpInJson() {
$this->markTestSkipped( 'Temporarily disabled: T314338' );
$testLogger = new TestLogger( true );
$cache = $this->createParserCache(
null,
null,
$testLogger
);
$parserOutput = $this->createDummyParserOutput();
$cyclicArray = [ 'a' => 'b' ];
$cyclicArray['c'] = &$cyclicArray;
$parserOutput->setExtensionData(
'test',
$cyclicArray
);
$cache->save(
$parserOutput,
$this->page,
ParserOptions::newFromAnon()
);
$this->assertArraySubmapSame(
[
[
LogLevel::ERROR,
'Unable to serialize JSON',
],
],
$testLogger->getBuffer()
);
}
/**
* Avoid needless JSON-escaping of Unicode characters so that
* values do not become too large and waste storage space (T263579)
*
* @see \MediaWiki\Parser\ParserCache::convertForCache
*/
public function testJsonEncodeUnicode() {
$unicodeCharacter = "Э";
$bag = new HashBagOStuff();
$parserCache = $this->createParserCache( null, $bag );
$parserOutput = $this->createDummyParserOutput();
$parserOutput->setRawText( $unicodeCharacter );
$parserCache->save(
$parserOutput,
$this->page,
ParserOptions::newFromAnon()
);
$key = $parserCache->makeParserOutputKey(
$this->page,
ParserOptions::newFromAnon()
);
$json = $bag->get( $key );
$this->assertStringNotContainsString(
"\u003E",
$json
);
$this->assertStringContainsString(
$unicodeCharacter,
$json
);
}
}

File Metadata

Mime Type
text/x-php
Expires
Sun, Oct 4, 00:23 (2 w, 6 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
6a/f4/ae415af626fb109851c1764c88f9
Default Alt Text
ParserCacheTest.php (29 KB)

Event Timeline