wiki.techinc.nl/tests/phpunit/includes/ResourceLoader/MessageBlobStoreTest.php
Timo Tijhof 39ead04e98 objectcache: Remove $wgMainWANCache and $wgWANObjectCaches
We always wrap the local cluster cache, and there are no subclasses
of WANObjectCache. It was never documented or recommended how these
would be used. It is a left-over from the original 2015 Multi-DC plan
in which WANObjectCache would work differently. See task for details.

Note that this requires no configuration changes, even in the
theoretical case of these variables being used, as the only
option is to use the main cache, and that's also the default.

* Update WAN overrides to override the underlying main cache
  instead.
* Fix EditPageTest which was previously implicitly using a 'hash'
  as main cache but also relying on wan cache to be 'none'.
  The part that it actually needs is the 'none'. When WAN cache is
  enabled, testUpdateNoMinor fails due to an edit conflict because
  one of the edits it makes is made with a current timestamp whereas
  it expects to simulate wpEdittime in the year 2012 which, when
  caching is enabled, is ignored and becomes the current time instead.
  I don't understand exactly why, but I'm going to conserve that
  behaviour for now.
* Fix TemplateCategoriesTest, which was failing due to an unexpected
  cache hit:
  > [objectcache] fetchOrRegenerate(…:page:10:…): volatile hit
  This could be solved in a more realistic way by splitting the test,
  or by explicitly resetting services half-way the test to clear
  WikiPageFactory, PageStore and WANCache process state.
  For now, keep the prior behaviour of no cache in this test.

Bug: T305093
Bug: T329680
Depends-On: If890622eed0d0f8b4bd73d36ba1815a3d760ea05
Depends-On: Ie1def75208822bdf19bb2cfd7e6edf32c2000e6b
Depends-On: I35cce61dc3ee90dcee3dd6f0b36f84133be029ed
Change-Id: I53781a8c06ebb2583f6ca83dd91bbfe8a5c88b13
2023-03-07 22:47:04 +00:00

217 lines
6.5 KiB
PHP

<?php
namespace MediaWiki\Tests\ResourceLoader;
use EmptyResourceLoader;
use HashBagOStuff;
use MediaWiki\ResourceLoader\MessageBlobStore;
use MediaWiki\ResourceLoader\ResourceLoader;
use MediaWikiCoversValidator;
use Psr\Log\NullLogger;
use ResourceLoaderTestModule;
use WANObjectCache;
/**
* @group ResourceLoader
* @covers \MediaWiki\ResourceLoader\MessageBlobStore
*/
class MessageBlobStoreTest extends \PHPUnit\Framework\TestCase {
use MediaWikiCoversValidator;
private const NAME = 'test.blobstore';
/** @var WANObjectCache */
private $wanCache;
/** @var float */
private $clock;
protected function setUp(): void {
parent::setUp();
$this->wanCache = new WANObjectCache( [
'cache' => new HashBagOStuff()
] );
$this->clock = 1301655600.000;
$this->wanCache->setMockTime( $this->clock );
}
public function testBlobCreation() {
$rl = new EmptyResourceLoader();
$rl->register( self::NAME, [
'factory' => function () {
return $this->makeModule( [ 'mainpage' ] );
}
] );
$blobStore = $this->makeBlobStore( null, $rl );
$blob = $blobStore->getBlob( $rl->getModule( self::NAME ), 'en' );
$this->assertEquals( '{"mainpage":"Main Page"}', $blob, 'Generated blob' );
}
public function testBlobCreation_empty() {
$module = $this->makeModule( [] );
$rl = new EmptyResourceLoader();
$blobStore = $this->makeBlobStore( null, $rl );
$blob = $blobStore->getBlob( $module, 'en' );
$this->assertEquals( '{}', $blob, 'Generated blob' );
}
public function testBlobCreation_unknownMessage() {
$module = $this->makeModule( [ 'i-dont-exist', 'mainpage', 'i-dont-exist2' ] );
$rl = new EmptyResourceLoader();
$blobStore = $this->makeBlobStore( null, $rl );
// Generating a blob should continue without errors,
// with keys of unknown messages excluded from the blob.
$blob = $blobStore->getBlob( $module, 'en' );
$this->assertEquals( '{"mainpage":"Main Page"}', $blob, 'Generated blob' );
}
public function testMessageCachingAndPurging() {
$rl = new EmptyResourceLoader();
// Register it so that MessageBlobStore::updateMessage can
// discover it from the registry as a module that uses this message.
$rl->register( self::NAME, [
'factory' => function () {
return $this->makeModule( [ 'example' ] );
}
] );
$module = $rl->getModule( self::NAME );
$blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
// Advance this new WANObjectCache instance to a normal state,
// by doing one "get" and letting its hold off period expire.
// Without this, the first real "get" would lazy-initialise the
// checkKey and thus reject the first "set".
$blobStore->getBlob( $module, 'en' );
$this->clock += 20;
// Arrange version 1 of a message
$blobStore->expects( $this->once() )
->method( 'fetchMessage' )
->willReturn( 'First version' );
// Assert
$blob = $blobStore->getBlob( $module, 'en' );
$this->assertEquals( '{"example":"First version"}', $blob, 'Blob for v1' );
// Arrange version 2
$blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
$blobStore->expects( $this->once() )
->method( 'fetchMessage' )
->willReturn( 'Second version' );
$this->clock += 20;
// Assert
// We do not validate whether a cached message is up-to-date.
// Instead, changes to messages will send us a purge.
// When cache is not purged or expired, it must be used.
$blob = $blobStore->getBlob( $module, 'en' );
$this->assertEquals( '{"example":"First version"}', $blob, 'Reuse cached v1 blob' );
// Purge cache
$blobStore->updateMessage( 'example' );
$this->clock += 20;
// Assert
$blob = $blobStore->getBlob( $module, 'en' );
$this->assertEquals( '{"example":"Second version"}', $blob, 'Updated blob for v2' );
}
public function testPurgeEverything() {
$module = $this->makeModule( [ 'example' ] );
$rl = new EmptyResourceLoader();
$blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
// Advance this new WANObjectCache instance to a normal state.
$blobStore->getBlob( $module, 'en' );
$this->clock += 20;
// Arrange version 1 and 2
$blobStore->expects( $this->exactly( 2 ) )
->method( 'fetchMessage' )
->will( $this->onConsecutiveCalls( 'First', 'Second' ) );
// Assert
$blob = $blobStore->getBlob( $module, 'en' );
$this->assertEquals( '{"example":"First"}', $blob, 'Blob for v1' );
$this->clock += 20;
// Assert
$blob = $blobStore->getBlob( $module, 'en' );
$this->assertEquals( '{"example":"First"}', $blob, 'Blob for v1 again' );
// Purge everything
$blobStore->clear();
$this->clock += 20;
// Assert
$blob = $blobStore->getBlob( $module, 'en' );
$this->assertEquals( '{"example":"Second"}', $blob, 'Blob for v2' );
}
public function testValidateAgainstModuleRegistry() {
// Arrange version 1 of a module
$module = $this->makeModule( [ 'foo' ] );
$rl = new EmptyResourceLoader();
$blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
$blobStore->expects( $this->once() )
->method( 'fetchMessage' )
->willReturnMap( [
// message key, language code, message value
[ 'foo', 'en', 'Hello' ],
] );
// Assert
$blob = $blobStore->getBlob( $module, 'en' );
$this->assertEquals( '{"foo":"Hello"}', $blob, 'Blob for v1' );
// Arrange version 2 of module
// While message values may be out of date, the set of messages returned
// must always match the set of message keys required by the module.
// We do not receive purges for this because no messages were changed.
$module = $this->makeModule( [ 'foo', 'bar' ] );
$rl = new EmptyResourceLoader();
$blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
$blobStore->expects( $this->exactly( 2 ) )
->method( 'fetchMessage' )
->willReturnMap( [
// message key, language code, message value
[ 'foo', 'en', 'Hello' ],
[ 'bar', 'en', 'World' ],
] );
// Assert
$blob = $blobStore->getBlob( $module, 'en' );
$this->assertEquals( '{"foo":"Hello","bar":"World"}', $blob, 'Blob for v2' );
}
public function testSetLoggedIsVoid() {
$blobStore = $this->makeBlobStore();
$this->assertNull( $blobStore->setLogger( new NullLogger() ) );
}
private function makeBlobStore( $methods = null, $rl = null ) {
$blobStore = $this->getMockBuilder( MessageBlobStore::class )
->setConstructorArgs( [
$rl ?? $this->createMock( ResourceLoader::class ),
null,
$this->wanCache
] )
->onlyMethods( $methods ?: [] )
->getMock();
return $blobStore;
}
private function makeModule( array $messages ) {
$module = new ResourceLoaderTestModule( [ 'messages' => $messages ] );
$module->setName( self::NAME );
return $module;
}
}