'MergeMessageFileList' => __DIR__ . '/maintenance/mergeMessageFileList.php',
'MergeableUpdate' => __DIR__ . '/includes/deferred/MergeableUpdate.php',
'Message' => __DIR__ . '/includes/Message.php',
- 'MessageBlobStore' => __DIR__ . '/includes/cache/MessageBlobStore.php',
+ 'MessageBlobStore' => __DIR__ . '/includes/resourceloader/MessageBlobStore.php',
'MessageCache' => __DIR__ . '/includes/cache/MessageCache.php',
'MessageCacheUpdate' => __DIR__ . '/includes/deferred/MessageCacheUpdate.php',
'MessageContent' => __DIR__ . '/includes/content/MessageContent.php',
+++ /dev/null
-<?php
-/**
- * Message blobs storage used by ResourceLoader.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @author Roan Kattouw
- * @author Trevor Parscal
- * @author Timo Tijhof
- */
-
-use MediaWiki\MediaWikiServices;
-use Psr\Log\LoggerAwareInterface;
-use Psr\Log\LoggerInterface;
-use Psr\Log\NullLogger;
-use Wikimedia\Rdbms\Database;
-
-/**
- * This class generates message blobs for use by ResourceLoader modules.
- *
- * A message blob is a JSON object containing the interface messages for a certain module in
- * a certain language.
- */
-class MessageBlobStore implements LoggerAwareInterface {
-
- /* @var ResourceLoader */
- private $resourceloader;
-
- /**
- * @var LoggerInterface
- */
- protected $logger;
-
- /**
- * @var WANObjectCache
- */
- protected $wanCache;
-
- /**
- * @param ResourceLoader $rl
- * @param LoggerInterface|null $logger
- */
- public function __construct( ResourceLoader $rl, LoggerInterface $logger = null ) {
- $this->resourceloader = $rl;
- $this->logger = $logger ?: new NullLogger();
- $this->wanCache = MediaWikiServices::getInstance()->getMainWANObjectCache();
- }
-
- /**
- * @since 1.27
- * @param LoggerInterface $logger
- */
- public function setLogger( LoggerInterface $logger ) {
- $this->logger = $logger;
- }
-
- /**
- * Get the message blob for a module
- *
- * @since 1.27
- * @param ResourceLoaderModule $module
- * @param string $lang Language code
- * @return string JSON
- */
- public function getBlob( ResourceLoaderModule $module, $lang ) {
- $blobs = $this->getBlobs( [ $module->getName() => $module ], $lang );
- return $blobs[$module->getName()];
- }
-
- /**
- * Get the message blobs for a set of modules
- *
- * @since 1.27
- * @param ResourceLoaderModule[] $modules Array of module objects keyed by name
- * @param string $lang Language code
- * @return array An array mapping module names to message blobs
- */
- public function getBlobs( array $modules, $lang ) {
- // Each cache key for a message blob by module name and language code also has a generic
- // check key without language code. This is used to invalidate any and all language subkeys
- // that exist for a module from the updateMessage() method.
- $cache = $this->wanCache;
- $checkKeys = [
- // Global check key, see clear()
- $cache->makeKey( __CLASS__ )
- ];
- $cacheKeys = [];
- foreach ( $modules as $name => $module ) {
- $cacheKey = $this->makeCacheKey( $module, $lang );
- $cacheKeys[$name] = $cacheKey;
- // Per-module check key, see updateMessage()
- $checkKeys[$cacheKey][] = $cache->makeKey( __CLASS__, $name );
- }
- $curTTLs = [];
- $result = $cache->getMulti( array_values( $cacheKeys ), $curTTLs, $checkKeys );
-
- $blobs = [];
- foreach ( $modules as $name => $module ) {
- $key = $cacheKeys[$name];
- if ( !isset( $result[$key] ) || $curTTLs[$key] === null || $curTTLs[$key] < 0 ) {
- $blobs[$name] = $this->recacheMessageBlob( $key, $module, $lang );
- } else {
- // Use unexpired cache
- $blobs[$name] = $result[$key];
- }
- }
- return $blobs;
- }
-
- /**
- * @deprecated since 1.27 Use getBlobs() instead
- * @return array
- */
- public function get( ResourceLoader $resourceLoader, $modules, $lang ) {
- return $this->getBlobs( $modules, $lang );
- }
-
- /**
- * @since 1.27
- * @param ResourceLoaderModule $module
- * @param string $lang
- * @return string Cache key
- */
- private function makeCacheKey( ResourceLoaderModule $module, $lang ) {
- $messages = array_values( array_unique( $module->getMessages() ) );
- sort( $messages );
- return $this->wanCache->makeKey( __CLASS__, $module->getName(), $lang,
- md5( json_encode( $messages ) )
- );
- }
-
- /**
- * @since 1.27
- * @param string $cacheKey
- * @param ResourceLoaderModule $module
- * @param string $lang
- * @return string JSON blob
- */
- protected function recacheMessageBlob( $cacheKey, ResourceLoaderModule $module, $lang ) {
- $blob = $this->generateMessageBlob( $module, $lang );
- $cache = $this->wanCache;
- $cache->set( $cacheKey, $blob,
- // Add part of a day to TTL to avoid all modules expiring at once
- $cache::TTL_WEEK + mt_rand( 0, $cache::TTL_DAY ),
- Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) )
- );
- return $blob;
- }
-
- /**
- * Invalidate cache keys for modules using this message key.
- * Called by MessageCache when a message has changed.
- *
- * @param string $key Message key
- */
- public function updateMessage( $key ) {
- $moduleNames = $this->getResourceLoader()->getModulesByMessage( $key );
- foreach ( $moduleNames as $moduleName ) {
- // Uses a holdoff to account for database replica DB lag (for MessageCache)
- $this->wanCache->touchCheckKey( $this->wanCache->makeKey( __CLASS__, $moduleName ) );
- }
- }
-
- /**
- * Invalidate cache keys for all known modules.
- * Called by LocalisationCache after cache is regenerated.
- */
- public function clear() {
- $cache = $this->wanCache;
- // Disable holdoff because this invalidates all modules and also not needed since
- // LocalisationCache is stored outside the database and doesn't have lag.
- $cache->touchCheckKey( $cache->makeKey( __CLASS__ ), $cache::HOLDOFF_NONE );
- }
-
- /**
- * @since 1.27
- * @return ResourceLoader
- */
- protected function getResourceLoader() {
- return $this->resourceloader;
- }
-
- /**
- * @since 1.27
- * @param string $key Message key
- * @param string $lang Language code
- * @return string
- */
- protected function fetchMessage( $key, $lang ) {
- $message = wfMessage( $key )->inLanguage( $lang );
- $value = $message->plain();
- if ( !$message->exists() ) {
- $this->logger->warning( 'Failed to find {messageKey} ({lang})', [
- 'messageKey' => $key,
- 'lang' => $lang,
- ] );
- }
- return $value;
- }
-
- /**
- * Generate the message blob for a given module in a given language.
- *
- * @param ResourceLoaderModule $module
- * @param string $lang Language code
- * @return string JSON blob
- */
- private function generateMessageBlob( ResourceLoaderModule $module, $lang ) {
- $messages = [];
- foreach ( $module->getMessages() as $key ) {
- $messages[$key] = $this->fetchMessage( $key, $lang );
- }
-
- $json = FormatJson::encode( (object)$messages );
- // @codeCoverageIgnoreStart
- if ( $json === false ) {
- $this->logger->warning( 'Failed to encode message blob for {module} ({lang})', [
- 'module' => $module->getName(),
- 'lang' => $lang,
- ] );
- $json = '{}';
- }
- // codeCoverageIgnoreEnd
- return $json;
- }
-}
--- /dev/null
+<?php
+/**
+ * Message blobs storage used by ResourceLoader.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @author Roan Kattouw
+ * @author Trevor Parscal
+ * @author Timo Tijhof
+ */
+
+use MediaWiki\MediaWikiServices;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use Wikimedia\Rdbms\Database;
+
+/**
+ * This class generates message blobs for use by ResourceLoader modules.
+ *
+ * A message blob is a JSON object containing the interface messages for a certain module in
+ * a certain language.
+ */
+class MessageBlobStore implements LoggerAwareInterface {
+
+ /* @var ResourceLoader */
+ private $resourceloader;
+
+ /**
+ * @var LoggerInterface
+ */
+ protected $logger;
+
+ /**
+ * @var WANObjectCache
+ */
+ protected $wanCache;
+
+ /**
+ * @param ResourceLoader $rl
+ * @param LoggerInterface|null $logger
+ */
+ public function __construct( ResourceLoader $rl, LoggerInterface $logger = null ) {
+ $this->resourceloader = $rl;
+ $this->logger = $logger ?: new NullLogger();
+ $this->wanCache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+ }
+
+ /**
+ * @since 1.27
+ * @param LoggerInterface $logger
+ */
+ public function setLogger( LoggerInterface $logger ) {
+ $this->logger = $logger;
+ }
+
+ /**
+ * Get the message blob for a module
+ *
+ * @since 1.27
+ * @param ResourceLoaderModule $module
+ * @param string $lang Language code
+ * @return string JSON
+ */
+ public function getBlob( ResourceLoaderModule $module, $lang ) {
+ $blobs = $this->getBlobs( [ $module->getName() => $module ], $lang );
+ return $blobs[$module->getName()];
+ }
+
+ /**
+ * Get the message blobs for a set of modules
+ *
+ * @since 1.27
+ * @param ResourceLoaderModule[] $modules Array of module objects keyed by name
+ * @param string $lang Language code
+ * @return array An array mapping module names to message blobs
+ */
+ public function getBlobs( array $modules, $lang ) {
+ // Each cache key for a message blob by module name and language code also has a generic
+ // check key without language code. This is used to invalidate any and all language subkeys
+ // that exist for a module from the updateMessage() method.
+ $cache = $this->wanCache;
+ $checkKeys = [
+ // Global check key, see clear()
+ $cache->makeKey( __CLASS__ )
+ ];
+ $cacheKeys = [];
+ foreach ( $modules as $name => $module ) {
+ $cacheKey = $this->makeCacheKey( $module, $lang );
+ $cacheKeys[$name] = $cacheKey;
+ // Per-module check key, see updateMessage()
+ $checkKeys[$cacheKey][] = $cache->makeKey( __CLASS__, $name );
+ }
+ $curTTLs = [];
+ $result = $cache->getMulti( array_values( $cacheKeys ), $curTTLs, $checkKeys );
+
+ $blobs = [];
+ foreach ( $modules as $name => $module ) {
+ $key = $cacheKeys[$name];
+ if ( !isset( $result[$key] ) || $curTTLs[$key] === null || $curTTLs[$key] < 0 ) {
+ $blobs[$name] = $this->recacheMessageBlob( $key, $module, $lang );
+ } else {
+ // Use unexpired cache
+ $blobs[$name] = $result[$key];
+ }
+ }
+ return $blobs;
+ }
+
+ /**
+ * @deprecated since 1.27 Use getBlobs() instead
+ * @return array
+ */
+ public function get( ResourceLoader $resourceLoader, $modules, $lang ) {
+ return $this->getBlobs( $modules, $lang );
+ }
+
+ /**
+ * @since 1.27
+ * @param ResourceLoaderModule $module
+ * @param string $lang
+ * @return string Cache key
+ */
+ private function makeCacheKey( ResourceLoaderModule $module, $lang ) {
+ $messages = array_values( array_unique( $module->getMessages() ) );
+ sort( $messages );
+ return $this->wanCache->makeKey( __CLASS__, $module->getName(), $lang,
+ md5( json_encode( $messages ) )
+ );
+ }
+
+ /**
+ * @since 1.27
+ * @param string $cacheKey
+ * @param ResourceLoaderModule $module
+ * @param string $lang
+ * @return string JSON blob
+ */
+ protected function recacheMessageBlob( $cacheKey, ResourceLoaderModule $module, $lang ) {
+ $blob = $this->generateMessageBlob( $module, $lang );
+ $cache = $this->wanCache;
+ $cache->set( $cacheKey, $blob,
+ // Add part of a day to TTL to avoid all modules expiring at once
+ $cache::TTL_WEEK + mt_rand( 0, $cache::TTL_DAY ),
+ Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) )
+ );
+ return $blob;
+ }
+
+ /**
+ * Invalidate cache keys for modules using this message key.
+ * Called by MessageCache when a message has changed.
+ *
+ * @param string $key Message key
+ */
+ public function updateMessage( $key ) {
+ $moduleNames = $this->getResourceLoader()->getModulesByMessage( $key );
+ foreach ( $moduleNames as $moduleName ) {
+ // Uses a holdoff to account for database replica DB lag (for MessageCache)
+ $this->wanCache->touchCheckKey( $this->wanCache->makeKey( __CLASS__, $moduleName ) );
+ }
+ }
+
+ /**
+ * Invalidate cache keys for all known modules.
+ * Called by LocalisationCache after cache is regenerated.
+ */
+ public function clear() {
+ $cache = $this->wanCache;
+ // Disable holdoff because this invalidates all modules and also not needed since
+ // LocalisationCache is stored outside the database and doesn't have lag.
+ $cache->touchCheckKey( $cache->makeKey( __CLASS__ ), $cache::HOLDOFF_NONE );
+ }
+
+ /**
+ * @since 1.27
+ * @return ResourceLoader
+ */
+ protected function getResourceLoader() {
+ return $this->resourceloader;
+ }
+
+ /**
+ * @since 1.27
+ * @param string $key Message key
+ * @param string $lang Language code
+ * @return string
+ */
+ protected function fetchMessage( $key, $lang ) {
+ $message = wfMessage( $key )->inLanguage( $lang );
+ $value = $message->plain();
+ if ( !$message->exists() ) {
+ $this->logger->warning( 'Failed to find {messageKey} ({lang})', [
+ 'messageKey' => $key,
+ 'lang' => $lang,
+ ] );
+ }
+ return $value;
+ }
+
+ /**
+ * Generate the message blob for a given module in a given language.
+ *
+ * @param ResourceLoaderModule $module
+ * @param string $lang Language code
+ * @return string JSON blob
+ */
+ private function generateMessageBlob( ResourceLoaderModule $module, $lang ) {
+ $messages = [];
+ foreach ( $module->getMessages() as $key ) {
+ $messages[$key] = $this->fetchMessage( $key, $lang );
+ }
+
+ $json = FormatJson::encode( (object)$messages );
+ // @codeCoverageIgnoreStart
+ if ( $json === false ) {
+ $this->logger->warning( 'Failed to encode message blob for {module} ({lang})', [
+ 'module' => $module->getName(),
+ 'lang' => $lang,
+ ] );
+ $json = '{}';
+ }
+ // codeCoverageIgnoreEnd
+ return $json;
+ }
+}
use Wikimedia\TestingAccessWrapper;
/**
- * @group Cache
+ * @group ResourceLoader
* @covers MessageBlobStore
*/
class MessageBlobStoreTest extends PHPUnit\Framework\TestCase {
protected function setUp() {
parent::setUp();
- // MediaWiki tests defaults $wgMainWANCache to CACHE_NONE.
- // Use hash instead so that caching is observed
- $this->wanCache = $this->getMockBuilder( WANObjectCache::class )
- ->setConstructorArgs( [ [
- 'cache' => new HashBagOStuff(),
- 'pool' => 'test',
- 'relayer' => new EventRelayerNull( [] )
- ] ] )
- ->setMethods( [ 'makePurgeValue' ] )
- ->getMock();
-
- $this->wanCache->expects( $this->any() )
- ->method( 'makePurgeValue' )
- ->will( $this->returnCallback( function ( $timestamp, $holdoff ) {
- // Disable holdoff as it messes with testing. Aside from a 0-second holdoff,
- // make sure that "time" passes between getMulti() check init and the set()
- // in recacheMessageBlob(). This especially matters for Windows clocks.
- $ts = (float)$timestamp - 0.0001;
-
- return WANObjectCache::PURGE_VAL_PREFIX . $ts . ':0';
- } ) );
- }
-
- protected function makeBlobStore( $methods = null, $rl = null ) {
- $blobStore = $this->getMockBuilder( MessageBlobStore::class )
- ->setConstructorArgs( [ $rl ?? $this->createMock( ResourceLoader::class ) ] )
- ->setMethods( $methods )
- ->getMock();
-
- $access = TestingAccessWrapper::newFromObject( $blobStore );
- $access->wanCache = $this->wanCache;
- return $blobStore;
- }
-
- protected function makeModule( array $messages ) {
- $module = new ResourceLoaderTestModule( [ 'messages' => $messages ] );
- $module->setName( 'test.blobstore' );
- return $module;
- }
-
- /** @covers MessageBlobStore::setLogger */
- public function testSetLogger() {
- $blobStore = $this->makeBlobStore();
- $this->assertSame( null, $blobStore->setLogger( new Psr\Log\NullLogger() ) );
+ // MediaWiki's test wrapper sets $wgMainWANCache to CACHE_NONE.
+ // Use HashBagOStuff here so that we can observe caching.
+ $this->wanCache = new WANObjectCache( [
+ 'cache' => new HashBagOStuff()
+ ] );
+
+ $this->clock = 1301655600.000;
+ $this->wanCache->setMockTime( $this->clock );
}
- /** @covers MessageBlobStore::getResourceLoader */
- public function testGetResourceLoader() {
- // Call protected method
- $blobStore = TestingAccessWrapper::newFromObject( $this->makeBlobStore() );
- $this->assertInstanceOf(
- ResourceLoader::class,
- $blobStore->getResourceLoader()
- );
- }
-
- /** @covers MessageBlobStore::fetchMessage */
- public function testFetchMessage() {
+ public function testBlobCreation() {
$module = $this->makeModule( [ 'mainpage' ] );
$rl = new ResourceLoader();
$rl->register( $module->getName(), $module );
$this->assertEquals( '{"mainpage":"Main Page"}', $blob, 'Generated blob' );
}
- /** @covers MessageBlobStore::fetchMessage */
- public function testFetchMessageFail() {
+ public function testBlobCreation_unknownMessage() {
$module = $this->makeModule( [ 'i-dont-exist' ] );
$rl = new ResourceLoader();
$rl->register( $module->getName(), $module );
-
$blobStore = $this->makeBlobStore( null, $rl );
- $blob = $blobStore->getBlob( $module, 'en' );
+ // Generating a blob should succeed without errors,
+ // even if a message is unknown.
+ $blob = $blobStore->getBlob( $module, 'en' );
$this->assertEquals( '{"i-dont-exist":"\u29fci-dont-exist\u29fd"}', $blob, 'Generated blob' );
}
- public function testGetBlob() {
- $module = $this->makeModule( [ 'foo' ] );
+ public function testMessageCachingAndPurging() {
+ $module = $this->makeModule( [ 'example' ] );
$rl = new ResourceLoader();
$rl->register( $module->getName(), $module );
-
$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' )
- ->will( $this->returnValue( 'Example' ) );
+ ->will( $this->returnValue( 'First version' ) );
+ // Assert
$blob = $blobStore->getBlob( $module, 'en' );
+ $this->assertEquals( '{"example":"First version"}', $blob, 'Blob for v1' );
- $this->assertEquals( '{"foo":"Example"}', $blob, 'Generated blob' );
- }
-
- public function testGetBlobCached() {
- $module = $this->makeModule( [ 'example' ] );
- $rl = new ResourceLoader();
- $rl->register( $module->getName(), $module );
-
+ // Arrange version 2
$blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
$blobStore->expects( $this->once() )
->method( 'fetchMessage' )
- ->will( $this->returnValue( 'First' ) );
+ ->will( $this->returnValue( 'Second version' ) );
+ $this->clock += 20;
- $module = $this->makeModule( [ 'example' ] );
+ // 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"}', $blob, 'Generated blob' );
+ $this->assertEquals( '{"example":"First version"}', $blob, 'Reuse cached v1 blob' );
- $blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
- $blobStore->expects( $this->never() )
- ->method( 'fetchMessage' )
- ->will( $this->returnValue( 'Second' ) );
+ // Purge cache
+ $blobStore->updateMessage( 'example' );
+ $this->clock += 20;
- $module = $this->makeModule( [ 'example' ] );
+ // Assert
$blob = $blobStore->getBlob( $module, 'en' );
- $this->assertEquals( '{"example":"First"}', $blob, 'Cache hit' );
+ $this->assertEquals( '{"example":"Second version"}', $blob, 'Updated blob for v2' );
}
- public function testUpdateMessage() {
+ public function testPurgeEverything() {
$module = $this->makeModule( [ 'example' ] );
$rl = new ResourceLoader();
$rl->register( $module->getName(), $module );
$blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
- $blobStore->expects( $this->once() )
+ // 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->returnValue( 'First' ) );
+ ->will( $this->onConsecutiveCalls( 'First', 'Second' ) );
+ // Assert
$blob = $blobStore->getBlob( $module, 'en' );
- $this->assertEquals( '{"example":"First"}', $blob, 'Generated blob' );
+ $this->assertEquals( '{"example":"First"}', $blob, 'Blob for v1' );
- $blobStore->updateMessage( 'example' );
+ $this->clock += 20;
- $module = $this->makeModule( [ 'example' ] );
- $rl = new ResourceLoader();
- $rl->register( $module->getName(), $module );
- $blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
- $blobStore->expects( $this->once() )
- ->method( 'fetchMessage' )
- ->will( $this->returnValue( 'Second' ) );
+ // 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, 'Updated blob' );
+ $this->assertEquals( '{"example":"Second"}', $blob, 'Blob for v2' );
}
- public function testValidation() {
+ public function testValidateAgainstModuleRegistry() {
+ // Arrange version 1 of a module
$module = $this->makeModule( [ 'foo' ] );
$rl = new ResourceLoader();
$rl->register( $module->getName(), $module );
-
$blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
$blobStore->expects( $this->once() )
->method( 'fetchMessage' )
->will( $this->returnValueMap( [
+ // message key, language code, message value
[ 'foo', 'en', 'Hello' ],
] ) );
+ // Assert
$blob = $blobStore->getBlob( $module, 'en' );
- $this->assertEquals( '{"foo":"Hello"}', $blob, 'Generated blob' );
+ $this->assertEquals( '{"foo":"Hello"}', $blob, 'Blob for v1' );
- // Now, imagine a change to the module is deployed. The module now contains
- // message 'foo' and 'bar'. While updateMessage() was not called (since no
- // message values were changed) it should detect the change in list of
- // message keys.
+ // 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 ResourceLoader();
$rl->register( $module->getName(), $module );
-
$blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
$blobStore->expects( $this->exactly( 2 ) )
->method( 'fetchMessage' )
->will( $this->returnValueMap( [
+ // 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, 'Updated blob' );
+ $this->assertEquals( '{"foo":"Hello","bar":"World"}', $blob, 'Blob for v2' );
}
- public function testClear() {
- $module = $this->makeModule( [ 'example' ] );
- $rl = new ResourceLoader();
- $rl->register( $module->getName(), $module );
- $blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
- $blobStore->expects( $this->exactly( 2 ) )
- ->method( 'fetchMessage' )
- ->will( $this->onConsecutiveCalls( 'First', 'Second' ) );
-
- $now = microtime( true );
- $this->wanCache->setMockTime( $now );
-
- $blob = $blobStore->getBlob( $module, 'en' );
- $this->assertEquals( '{"example":"First"}', $blob, 'Generated blob' );
+ public function testSetLoggedIsVoid() {
+ $blobStore = $this->makeBlobStore();
+ $this->assertSame( null, $blobStore->setLogger( new Psr\Log\NullLogger() ) );
+ }
- $blob = $blobStore->getBlob( $module, 'en' );
- $this->assertEquals( '{"example":"First"}', $blob, 'Cache-hit' );
+ private function makeBlobStore( $methods = null, $rl = null ) {
+ $blobStore = $this->getMockBuilder( MessageBlobStore::class )
+ ->setConstructorArgs( [ $rl ?? $this->createMock( ResourceLoader::class ) ] )
+ ->setMethods( $methods )
+ ->getMock();
- $now += 1;
- $blobStore->clear();
+ $access = TestingAccessWrapper::newFromObject( $blobStore );
+ $access->wanCache = $this->wanCache;
+ return $blobStore;
+ }
- $blob = $blobStore->getBlob( $module, 'en' );
- $this->assertEquals( '{"example":"Second"}', $blob, 'Updated blob' );
+ private function makeModule( array $messages ) {
+ $module = new ResourceLoaderTestModule( [ 'messages' => $messages ] );
+ $module->setName( 'test.blobstore' );
+ return $module;
}
}