Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4107085
RevisionOutputCacheTest.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
12 KB
Referenced Files
None
Subscribers
None
RevisionOutputCacheTest.php
View Options
<?php
namespace
MediaWiki\Tests\Parser
;
use
InvalidArgumentException
;
use
MediaWiki\Json\JsonCodec
;
use
MediaWiki\Page\PageIdentity
;
use
MediaWiki\Page\PageIdentityValue
;
use
MediaWiki\Parser\ParserOptions
;
use
MediaWiki\Parser\ParserOutput
;
use
MediaWiki\Parser\RevisionOutputCache
;
use
MediaWiki\Revision\MutableRevisionRecord
;
use
MediaWiki\Revision\RevisionRecord
;
use
MediaWiki\Tests\Json\JsonDeserializableSuperClass
;
use
MediaWiki\User\User
;
use
MediaWiki\Utils\MWTimestamp
;
use
MediaWikiIntegrationTestCase
;
use
Psr\Log\LoggerInterface
;
use
Psr\Log\LogLevel
;
use
Psr\Log\NullLogger
;
use
TestLogger
;
use
Wikimedia\ObjectCache\BagOStuff
;
use
Wikimedia\ObjectCache\HashBagOStuff
;
use
Wikimedia\ObjectCache\WANObjectCache
;
use
Wikimedia\Stats\StatsFactory
;
use
Wikimedia\TestingAccessWrapper
;
use
Wikimedia\UUID\GlobalIdGenerator
;
/**
* @covers \MediaWiki\Parser\RevisionOutputCache
*/
class
RevisionOutputCacheTest
extends
MediaWikiIntegrationTestCase
{
/** @var int */
private
$time
;
/** @var string */
private
$cacheTime
;
/** @var RevisionRecord */
private
$revision
;
protected
function
setUp
():
void
{
parent
::
setUp
();
$this
->
time
=
time
();
$this
->
cacheTime
=
MWTimestamp
::
convert
(
TS_MW
,
$this
->
time
+
1
);
MWTimestamp
::
setFakeTime
(
$this
->
time
);
$this
->
revision
=
new
MutableRevisionRecord
(
new
PageIdentityValue
(
42
,
NS_MAIN
,
'Testing_Testing'
,
PageIdentity
::
LOCAL
),
RevisionRecord
::
LOCAL
);
$this
->
revision
->
setId
(
24
);
$this
->
revision
->
setTimestamp
(
MWTimestamp
::
convert
(
TS_MW
,
$this
->
time
)
);
}
/**
* @param BagOStuff|null $storage
* @param LoggerInterface|null $logger
* @param int $expiry
* @param string $epoch
*
* @return RevisionOutputCache
*/
private
function
createRevisionOutputCache
(
?
BagOStuff
$storage
=
null
,
?
LoggerInterface
$logger
=
null
,
$expiry
=
3600
,
$epoch
=
'19900220000000'
):
RevisionOutputCache
{
$globalIdGenerator
=
$this
->
createMock
(
GlobalIdGenerator
::
class
);
$globalIdGenerator
->
method
(
'newUUIDv1'
)->
willReturn
(
'uuid-uuid'
);
return
new
RevisionOutputCache
(
'test'
,
new
WANObjectCache
(
[
'cache'
=>
$storage
?:
new
HashBagOStuff
()
]
),
$expiry
,
$epoch
,
new
JsonCodec
(),
StatsFactory
::
newNull
(),
$logger
?:
new
NullLogger
(),
$globalIdGenerator
);
}
/**
* @return array
*/
private
function
getDummyUsedOptions
():
array
{
return
array_slice
(
ParserOptions
::
allCacheVaryingOptions
(),
0
,
2
);
}
/**
* @return ParserOutput
*/
private
function
createDummyParserOutput
():
ParserOutput
{
$parserOutput
=
new
ParserOutput
();
$parserOutput
->
setRawText
(
'TEST'
);
foreach
(
$this
->
getDummyUsedOptions
()
as
$option
)
{
$parserOutput
->
recordOption
(
$option
);
}
$parserOutput
->
updateCacheExpiry
(
4242
);
return
$parserOutput
;
}
/**
* @covers \MediaWiki\Parser\RevisionOutputCache::makeParserOutputKey
*/
public
function
testMakeParserOutputKey
()
{
$cache
=
$this
->
createRevisionOutputCache
();
$options1
=
ParserOptions
::
newFromAnon
();
$options1
->
setOption
(
$this
->
getDummyUsedOptions
()[
0
],
'value1'
);
$key1
=
$cache
->
makeParserOutputKey
(
$this
->
revision
,
$options1
,
$this
->
getDummyUsedOptions
()
);
$this
->
assertNotNull
(
$key1
);
$options2
=
ParserOptions
::
newFromAnon
();
$options2
->
setOption
(
$this
->
getDummyUsedOptions
()[
0
],
'value2'
);
$key2
=
$cache
->
makeParserOutputKey
(
$this
->
revision
,
$options2
,
$this
->
getDummyUsedOptions
()
);
$this
->
assertNotNull
(
$key2
);
$this
->
assertNotSame
(
$key1
,
$key2
);
}
/*
* Test that fetching without storing first returns false.
* @covers \MediaWiki\Parser\RevisionOutputCache::get
*/
public
function
testGetEmpty
()
{
$cache
=
$this
->
createRevisionOutputCache
();
$options
=
ParserOptions
::
newFromAnon
();
$this
->
assertFalse
(
$cache
->
get
(
$this
->
revision
,
$options
)
);
}
/**
* Test that fetching with the same options return the saved value.
* @covers \MediaWiki\Parser\RevisionOutputCache::get
* @covers \MediaWiki\Parser\RevisionOutputCache::save
*/
public
function
testSaveGetSameOptions
()
{
$cache
=
$this
->
createRevisionOutputCache
();
$parserOutput
=
new
ParserOutput
(
'TEST_TEXT'
);
$options1
=
ParserOptions
::
newFromAnon
();
$options1
->
setOption
(
$this
->
getDummyUsedOptions
()[
0
],
'value1'
);
$cache
->
save
(
$parserOutput
,
$this
->
revision
,
$options1
,
$this
->
cacheTime
);
$savedOutput
=
$cache
->
get
(
$this
->
revision
,
$options1
);
$this
->
assertInstanceOf
(
ParserOutput
::
class
,
$savedOutput
);
// RevisionOutputCache 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
->
revision
->
getId
(),
$savedOutput
->
getCacheRevisionId
()
);
}
/**
* Test that non-cacheable output is not stored
* @covers \MediaWiki\Parser\RevisionOutputCache::save
*/
public
function
testDoesNotStoreNonCacheable
()
{
$cache
=
$this
->
createRevisionOutputCache
();
$parserOutput
=
new
ParserOutput
(
'TEST_TEXT'
);
$parserOutput
->
updateCacheExpiry
(
0
);
$options1
=
ParserOptions
::
newFromAnon
();
$cache
->
save
(
$parserOutput
,
$this
->
revision
,
$options1
,
$this
->
cacheTime
);
$this
->
assertFalse
(
$cache
->
get
(
$this
->
revision
,
$options1
)
);
}
/**
* Test that setting the cache epoch will cause outdated entries to be ignored
* @covers \MediaWiki\Parser\RevisionOutputCache::get
*/
public
function
testExpiresByEpoch
()
{
$store
=
new
HashBagOStuff
();
$cache
=
$this
->
createRevisionOutputCache
(
$store
);
$parserOutput
=
new
ParserOutput
(
'TEST_TEXT'
);
$options
=
ParserOptions
::
newFromAnon
();
$cache
->
save
(
$parserOutput
,
$this
->
revision
,
$options
,
$this
->
cacheTime
);
// determine cache epoch younger than cache time
$cacheTime
=
MWTimestamp
::
convert
(
TS_UNIX
,
$parserOutput
->
getCacheTime
()
);
$epoch
=
MWTimestamp
::
convert
(
TS_MW
,
$cacheTime
+
60
);
// create a cache with the new epoch
$cache
=
$this
->
createRevisionOutputCache
(
$store
,
null
,
60
*
60
,
$epoch
);
$this
->
assertFalse
(
$cache
->
get
(
$this
->
revision
,
$options
)
);
}
/**
* Test that setting the cache expiry period will cause outdated entries to be ignored
* @covers \MediaWiki\Parser\RevisionOutputCache::get
*/
public
function
testExpiresByDuration
()
{
$store
=
new
HashBagOStuff
();
// original cache is good for an hour
$cache
=
$this
->
createRevisionOutputCache
(
$store
);
$parserOutput
=
new
ParserOutput
(
'TEST_TEXT'
);
$options
=
ParserOptions
::
newFromAnon
();
$cache
->
save
(
$parserOutput
,
$this
->
revision
,
$options
,
$this
->
cacheTime
);
// move the clock forward by 60 seconds
$cacheTime
=
MWTimestamp
::
convert
(
TS_UNIX
,
$parserOutput
->
getCacheTime
()
);
MWTimestamp
::
setFakeTime
(
$cacheTime
+
60
);
// create a cache that expires after 30 seconds
$cache
=
$this
->
createRevisionOutputCache
(
$store
,
null
,
30
);
$this
->
assertFalse
(
$cache
->
get
(
$this
->
revision
,
$options
)
);
}
/**
* Test that ParserOptions::isSafeToCache is respected on save
* @covers \MediaWiki\Parser\RevisionOutputCache::save
*/
public
function
testDoesNotStoreNotSafeToCache
()
{
$cache
=
$this
->
createRevisionOutputCache
();
$parserOutput
=
new
ParserOutput
(
'TEST_TEXT'
);
$options
=
ParserOptions
::
newFromAnon
();
$options
->
setOption
(
'wrapclass'
,
'wrapwrap'
);
$cache
->
save
(
$parserOutput
,
$this
->
revision
,
$options
,
$this
->
cacheTime
);
$this
->
assertFalse
(
$cache
->
get
(
$this
->
revision
,
$options
)
);
}
/**
* Test that ParserOptions::isSafeToCache is respected on get
* @covers \MediaWiki\Parser\RevisionOutputCache::get
*/
public
function
testDoesNotGetNotSafeToCache
()
{
$cache
=
$this
->
createRevisionOutputCache
();
$parserOutput
=
new
ParserOutput
(
'TEST_TEXT'
);
$cache
->
save
(
$parserOutput
,
$this
->
revision
,
ParserOptions
::
newFromAnon
(),
$this
->
cacheTime
);
$otherOptions
=
ParserOptions
::
newFromAnon
();
$otherOptions
->
setOption
(
'wrapclass'
,
'wrapwrap'
);
$this
->
assertFalse
(
$cache
->
get
(
$this
->
revision
,
$otherOptions
)
);
}
/**
* Test that fetching with different used option don't return a value.
* @covers \MediaWiki\Parser\RevisionOutputCache::get
* @covers \MediaWiki\Parser\RevisionOutputCache::save
*/
public
function
testSaveGetDifferentUsedOption
()
{
$cache
=
$this
->
createRevisionOutputCache
();
$parserOutput
=
new
ParserOutput
(
'TEST_TEXT'
);
$optionName
=
$this
->
getDummyUsedOptions
()[
0
];
$parserOutput
->
recordOption
(
$optionName
);
$options1
=
ParserOptions
::
newFromAnon
();
$options1
->
setOption
(
$optionName
,
'value1'
);
$cache
->
save
(
$parserOutput
,
$this
->
revision
,
$options1
,
$this
->
cacheTime
);
$options2
=
ParserOptions
::
newFromAnon
();
$options2
->
setOption
(
$optionName
,
'value2'
);
$this
->
assertFalse
(
$cache
->
get
(
$this
->
revision
,
$options2
)
);
}
/**
* @covers \MediaWiki\Parser\RevisionOutputCache::save
*/
public
function
testSaveNoText
()
{
$this
->
expectException
(
InvalidArgumentException
::
class
);
$this
->
createRevisionOutputCache
()->
save
(
new
ParserOutput
(
null
),
$this
->
revision
,
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\RevisionOutputCache::get
* @covers \MediaWiki\Parser\RevisionOutputCache::restoreFromJson
* @param string $data
*/
public
function
testCorruptData
(
string
$data
)
{
$cache
=
$this
->
createRevisionOutputCache
();
$parserOutput
=
new
ParserOutput
(
'TEST_TEXT'
);
$options1
=
ParserOptions
::
newFromAnon
();
$cache
->
save
(
$parserOutput
,
$this
->
revision
,
$options1
,
$this
->
cacheTime
);
$outputKey
=
$cache
->
makeParserOutputKey
(
$this
->
revision
,
$options1
,
$parserOutput
->
getUsedOptions
()
);
$backend
=
TestingAccessWrapper
::
newFromObject
(
$cache
)->
cache
;
$backend
->
set
(
$outputKey
,
$data
);
// just make sure we don't crash and burn
$this
->
assertFalse
(
$cache
->
get
(
$this
->
revision
,
$options1
)
);
}
/**
* @covers \MediaWiki\Parser\RevisionOutputCache::encodeAsJson
*/
public
function
testNonSerializableJsonIsReported
()
{
$testLogger
=
new
TestLogger
(
true
);
$cache
=
$this
->
createRevisionOutputCache
(
null
,
$testLogger
);
$parserOutput
=
$this
->
createDummyParserOutput
();
$parserOutput
->
setExtensionData
(
'test'
,
new
User
()
);
$cache
->
save
(
$parserOutput
,
$this
->
revision
,
ParserOptions
::
newFromAnon
()
);
$this
->
assertArraySubmapSame
(
[
[
LogLevel
::
ERROR
,
'Unable to serialize JSON'
]
],
$testLogger
->
getBuffer
()
);
}
/**
* @covers \MediaWiki\Parser\RevisionOutputCache::encodeAsJson
*/
public
function
testCyclicStructuresDoNotBlowUpInJson
()
{
$this
->
markTestSkipped
(
'Temporarily disabled: T314338'
);
$testLogger
=
new
TestLogger
(
true
);
$cache
=
$this
->
createRevisionOutputCache
(
null
,
$testLogger
);
$parserOutput
=
$this
->
createDummyParserOutput
();
$cyclicArray
=
[
'a'
=>
'b'
];
$cyclicArray
[
'c'
]
=
&
$cyclicArray
;
$parserOutput
->
setExtensionData
(
'test'
,
$cyclicArray
);
$cache
->
save
(
$parserOutput
,
$this
->
revision
,
ParserOptions
::
newFromAnon
()
);
$this
->
assertArraySubmapSame
(
[
[
LogLevel
::
ERROR
,
'Unable to serialize JSON'
]
],
$testLogger
->
getBuffer
()
);
}
/**
* Tests that unicode characters are not \u escaped
* @covers \MediaWiki\Parser\RevisionOutputCache::encodeAsJson
*/
public
function
testJsonEncodeUnicode
()
{
$unicodeCharacter
=
"Э"
;
$cache
=
$this
->
createRevisionOutputCache
(
new
HashBagOStuff
()
);
$parserOutput
=
$this
->
createDummyParserOutput
();
$parserOutput
->
setRawText
(
$unicodeCharacter
);
$cache
->
save
(
$parserOutput
,
$this
->
revision
,
ParserOptions
::
newFromAnon
()
);
$backend
=
TestingAccessWrapper
::
newFromObject
(
$cache
)->
cache
;
$json
=
$backend
->
get
(
$cache
->
makeParserOutputKey
(
$this
->
revision
,
ParserOptions
::
newFromAnon
()
)
);
$this
->
assertStringNotContainsString
(
"
\u
003E"
,
$json
);
$this
->
assertStringContainsString
(
$unicodeCharacter
,
$json
);
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Tue, Aug 18, 19:53 (2 w, 3 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
fd/03/4df374fcfddb7e5079e5ff350416
Default Alt Text
RevisionOutputCacheTest.php (12 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment