3 use MediaWiki\MediaWikiServices
;
4 use Wikimedia\TestingAccessWrapper
;
11 class MessageCacheTest
extends MediaWikiLangTestCase
{
13 protected function setUp() {
15 $this->configureLanguages();
16 MessageCache
::destroyInstance();
17 MessageCache
::singleton()->enable();
21 * Helper function -- setup site language for testing
23 protected function configureLanguages() {
24 // for the test, we need the content language to be anything but English,
25 // let's choose e.g. German (de)
26 $this->setUserLang( 'de' );
27 $this->setContentLang( 'de' );
30 function addDBDataOnce() {
31 $this->configureLanguages();
33 // Set up messages and fallbacks ab -> ru -> de
34 $this->makePage( 'FallbackLanguageTest-Full', 'ab' );
35 $this->makePage( 'FallbackLanguageTest-Full', 'ru' );
36 $this->makePage( 'FallbackLanguageTest-Full', 'de' );
38 // Fallbacks where ab does not exist
39 $this->makePage( 'FallbackLanguageTest-Partial', 'ru' );
40 $this->makePage( 'FallbackLanguageTest-Partial', 'de' );
42 // Fallback to the content language
43 $this->makePage( 'FallbackLanguageTest-ContLang', 'de' );
45 // Add customizations for an existing message.
46 $this->makePage( 'sunday', 'ru' );
48 // Full key tests -- always want russian
49 $this->makePage( 'MessageCacheTest-FullKeyTest', 'ab' );
50 $this->makePage( 'MessageCacheTest-FullKeyTest', 'ru' );
52 // In content language -- get base if no derivative
53 $this->makePage( 'FallbackLanguageTest-NoDervContLang', 'de', 'de/none' );
57 * Helper function for addDBData -- adds a simple page to the database
59 * @param string $title Title of page to be created
60 * @param string $lang Language and content of the created page
61 * @param string|null $content Content of the created page, or null for a generic string
65 protected function makePage( $title, $lang, $content = null ) {
66 if ( $content === null ) {
69 if ( $lang !== MediaWikiServices
::getInstance()->getContentLanguage()->getCode() ) {
70 $title = "$title/$lang";
73 $title = Title
::newFromText( $title, NS_MEDIAWIKI
);
74 $wikiPage = new WikiPage( $title );
75 $contentHandler = ContentHandler
::makeContent( $content, $title );
76 $status = $wikiPage->doEditContent( $contentHandler, "$lang translation test case" );
79 $this->assertTrue( $status->isOK(), 'Create page ' . $title->getPrefixedDBkey() );
80 return $status->value
['revision'];
84 * Test message fallbacks, T3495
86 * @dataProvider provideMessagesForFallback
88 public function testMessageFallbacks( $message, $lang, $expectedContent ) {
89 $result = MessageCache
::singleton()->get( $message, true, $lang );
90 $this->assertEquals( $expectedContent, $result, "Message fallback failed." );
93 function provideMessagesForFallback() {
95 [ 'FallbackLanguageTest-Full', 'ab', 'ab' ],
96 [ 'FallbackLanguageTest-Partial', 'ab', 'ru' ],
97 [ 'FallbackLanguageTest-ContLang', 'ab', 'de' ],
98 [ 'FallbackLanguageTest-None', 'ab', false ],
100 // Existing message with customizations on the fallbacks
101 [ 'sunday', 'ab', 'амҽыш' ],
104 [ 'FallbackLanguageTest-NoDervContLang', 'de', 'de/none' ],
105 // UI language different from content language should only use de/none as last option
106 [ 'FallbackLanguageTest-NoDervContLang', 'fit', 'de/none' ],
110 public function testReplaceMsg() {
111 $messageCache = MessageCache
::singleton();
113 $uckey = MediaWikiServices
::getInstance()->getContentLanguage()->ucfirst( $message );
114 $oldText = $messageCache->get( $message ); // "Ausführen"
116 $dbw = wfGetDB( DB_MASTER
);
117 $dbw->startAtomic( __METHOD__
); // simulate request and block deferred updates
118 $messageCache->replace( $uckey, 'Allez!' );
119 $this->assertEquals( 'Allez!',
120 $messageCache->getMsgFromNamespace( $uckey, 'de' ),
121 'Updates are reflected in-process immediately' );
122 $this->assertEquals( 'Allez!',
123 $messageCache->get( $message ),
124 'Updates are reflected in-process immediately' );
125 $this->makePage( 'Go', 'de', 'Race!' );
126 $dbw->endAtomic( __METHOD__
);
128 $this->assertEquals( 0,
129 DeferredUpdates
::pendingUpdatesCount(),
130 'Post-commit deferred update triggers a run of all updates' );
132 $this->assertEquals( 'Race!', $messageCache->get( $message ), 'Correct final contents' );
134 $this->makePage( 'Go', 'de', $oldText );
135 $messageCache->replace( $uckey, $oldText ); // deferred update runs immediately
136 $this->assertEquals( $oldText, $messageCache->get( $message ), 'Content restored' );
139 public function testReplaceCache() {
140 global $wgWANObjectCaches;
142 // We need a WAN cache for this.
143 $this->setMwGlobals( [
144 'wgMainWANCache' => 'hash',
145 'wgWANObjectCaches' => $wgWANObjectCaches +
[
147 'class' => WANObjectCache
::class,
153 $this->overrideMwServices();
155 MessageCache
::destroyInstance();
156 $messageCache = MessageCache
::singleton();
157 $messageCache->enable();
160 $this->makePage( 'Key1', 'de', 'Value1' );
161 $this->assertEquals( 0,
162 DeferredUpdates
::pendingUpdatesCount(),
163 'Post-commit deferred update triggers a run of all updates' );
164 $this->assertEquals( 'Value1', $messageCache->get( 'Key1' ), 'Key1 was successfully edited' );
166 // Screw up the database so MessageCache::loadFromDB() will
167 // produce the wrong result for reloading Key1
169 'page', [ 'page_namespace' => NS_MEDIAWIKI
, 'page_title' => 'Key1' ], __METHOD__
172 // Populate the second key
173 $this->makePage( 'Key2', 'de', 'Value2' );
174 $this->assertEquals( 0,
175 DeferredUpdates
::pendingUpdatesCount(),
176 'Post-commit deferred update triggers a run of all updates' );
177 $this->assertEquals( 'Value2', $messageCache->get( 'Key2' ), 'Key2 was successfully edited' );
179 // Now test that the second edit didn't reload Key1
180 $this->assertEquals( 'Value1', $messageCache->get( 'Key1' ),
181 'Key1 wasn\'t reloaded by edit of Key2' );
185 * @dataProvider provideNormalizeKey
187 public function testNormalizeKey( $key, $expected ) {
188 $actual = MessageCache
::normalizeKey( $key );
189 $this->assertEquals( $expected, $actual );
192 public function provideNormalizeKey() {
198 [ 'Foo bar', 'foo_bar' ],
200 [ 'Ćab_e 3', 'ćab_e_3' ],
207 public function testNoDBAccess() {
208 global $wgContLanguageCode;
210 $dbr = wfGetDB( DB_REPLICA
);
212 MessageCache
::singleton()->getMsgFromNamespace( 'allpages', $wgContLanguageCode );
214 $this->assertEquals( 0, $dbr->trxLevel() );
215 $dbr->setFlag( DBO_TRX
, $dbr::REMEMBER_PRIOR
); // make queries trigger TRX
217 MessageCache
::singleton()->getMsgFromNamespace( 'go', $wgContLanguageCode );
219 $dbr->restoreFlags();
221 $this->assertEquals( 0, $dbr->trxLevel(), "No DB read queries" );
225 * Regression test for T218918
227 * @fixme Disabled per https://phabricator.wikimedia.org/T219042
229 public function testLoadFromDB_fetchLatestRevision() {
230 // Create three revisions of the same message page.
231 // Must be an existing message key.
233 $this->makePage( $key, 'de', 'Test eins' );
234 $this->makePage( $key, 'de', 'Test zwei' );
235 $r3 = $this->makePage( $key, 'de', 'Test drei' );
237 // Create an out-of-sequence revision by importing a
238 // revision with an old timestamp. Hacky.
239 $importRevision = new WikiRevision( new HashConfig() );
240 $importRevision->setTitle( $r3->getTitle() );
241 $importRevision->setComment( 'Imported edit' );
242 $importRevision->setTimestamp( '19991122334455' );
243 $importRevision->setText( 'IMPORTED OLD TEST' );
244 $importRevision->setUsername( 'Alan Smithee' );
246 $importer = MediaWikiServices
::getInstance()->getWikiRevisionOldRevisionImporterNoUpdates();
247 $importer->import( $importRevision );
249 // Now, load the message from the wiki page
250 MessageCache
::destroyInstance();
251 $messageCache = MessageCache
::singleton();
252 $messageCache->enable();
253 $messageCache = TestingAccessWrapper
::newFromObject( $messageCache );
255 $cache = $messageCache->loadFromDB( 'de' );
257 $this->assertArrayHasKey( $key, $cache );
259 // Text in the cache has an extra space in front!
260 $this->assertSame( ' ' . 'Test drei', $cache[$key] );