Merge "FauxRequest: don’t override getValues()"
[lhc/web/wiklou.git] / tests / phpunit / includes / cache / MessageCacheTest.php
1 <?php
2
3 use MediaWiki\MediaWikiServices;
4 use Wikimedia\TestingAccessWrapper;
5
6 /**
7 * @group Database
8 * @group Cache
9 * @covers MessageCache
10 */
11 class MessageCacheTest extends MediaWikiLangTestCase {
12
13 protected function setUp() {
14 parent::setUp();
15 $this->configureLanguages();
16 MessageCache::singleton()->enable();
17 }
18
19 /**
20 * Helper function -- setup site language for testing
21 */
22 protected function configureLanguages() {
23 // for the test, we need the content language to be anything but English,
24 // let's choose e.g. German (de)
25 $this->setUserLang( 'de' );
26 $this->setContentLang( 'de' );
27 }
28
29 function addDBDataOnce() {
30 $this->configureLanguages();
31
32 // Set up messages and fallbacks ab -> ru -> de
33 $this->makePage( 'FallbackLanguageTest-Full', 'ab' );
34 $this->makePage( 'FallbackLanguageTest-Full', 'ru' );
35 $this->makePage( 'FallbackLanguageTest-Full', 'de' );
36
37 // Fallbacks where ab does not exist
38 $this->makePage( 'FallbackLanguageTest-Partial', 'ru' );
39 $this->makePage( 'FallbackLanguageTest-Partial', 'de' );
40
41 // Fallback to the content language
42 $this->makePage( 'FallbackLanguageTest-ContLang', 'de' );
43
44 // Add customizations for an existing message.
45 $this->makePage( 'sunday', 'ru' );
46
47 // Full key tests -- always want russian
48 $this->makePage( 'MessageCacheTest-FullKeyTest', 'ab' );
49 $this->makePage( 'MessageCacheTest-FullKeyTest', 'ru' );
50
51 // In content language -- get base if no derivative
52 $this->makePage( 'FallbackLanguageTest-NoDervContLang', 'de', 'de/none' );
53 }
54
55 /**
56 * Helper function for addDBData -- adds a simple page to the database
57 *
58 * @param string $title Title of page to be created
59 * @param string $lang Language and content of the created page
60 * @param string|null $content Content of the created page, or null for a generic string
61 *
62 * @return Revision
63 */
64 protected function makePage( $title, $lang, $content = null ) {
65 if ( $content === null ) {
66 $content = $lang;
67 }
68 if ( $lang !== MediaWikiServices::getInstance()->getContentLanguage()->getCode() ) {
69 $title = "$title/$lang";
70 }
71
72 $title = Title::newFromText( $title, NS_MEDIAWIKI );
73 $wikiPage = new WikiPage( $title );
74 $contentHandler = ContentHandler::makeContent( $content, $title );
75 $status = $wikiPage->doEditContent( $contentHandler, "$lang translation test case" );
76
77 // sanity
78 $this->assertTrue( $status->isOK(), 'Create page ' . $title->getPrefixedDBkey() );
79 return $status->value['revision'];
80 }
81
82 /**
83 * Test message fallbacks, T3495
84 *
85 * @dataProvider provideMessagesForFallback
86 */
87 public function testMessageFallbacks( $message, $lang, $expectedContent ) {
88 $result = MessageCache::singleton()->get( $message, true, $lang );
89 $this->assertEquals( $expectedContent, $result, "Message fallback failed." );
90 }
91
92 function provideMessagesForFallback() {
93 return [
94 [ 'FallbackLanguageTest-Full', 'ab', 'ab' ],
95 [ 'FallbackLanguageTest-Partial', 'ab', 'ru' ],
96 [ 'FallbackLanguageTest-ContLang', 'ab', 'de' ],
97 [ 'FallbackLanguageTest-None', 'ab', false ],
98
99 // Existing message with customizations on the fallbacks
100 [ 'sunday', 'ab', 'амҽыш' ],
101
102 // T48579
103 [ 'FallbackLanguageTest-NoDervContLang', 'de', 'de/none' ],
104 // UI language different from content language should only use de/none as last option
105 [ 'FallbackLanguageTest-NoDervContLang', 'fit', 'de/none' ],
106 ];
107 }
108
109 public function testReplaceMsg() {
110 $messageCache = MessageCache::singleton();
111 $message = 'go';
112 $uckey = MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $message );
113 $oldText = $messageCache->get( $message ); // "Ausführen"
114
115 $dbw = wfGetDB( DB_MASTER );
116 $dbw->startAtomic( __METHOD__ ); // simulate request and block deferred updates
117 $messageCache->replace( $uckey, 'Allez!' );
118 $this->assertEquals( 'Allez!',
119 $messageCache->getMsgFromNamespace( $uckey, 'de' ),
120 'Updates are reflected in-process immediately' );
121 $this->assertEquals( 'Allez!',
122 $messageCache->get( $message ),
123 'Updates are reflected in-process immediately' );
124 $this->makePage( 'Go', 'de', 'Race!' );
125 $dbw->endAtomic( __METHOD__ );
126
127 $this->assertSame( 0,
128 DeferredUpdates::pendingUpdatesCount(),
129 'Post-commit deferred update triggers a run of all updates' );
130
131 $this->assertEquals( 'Race!', $messageCache->get( $message ), 'Correct final contents' );
132
133 $this->makePage( 'Go', 'de', $oldText );
134 $messageCache->replace( $uckey, $oldText ); // deferred update runs immediately
135 $this->assertEquals( $oldText, $messageCache->get( $message ), 'Content restored' );
136 }
137
138 public function testReplaceCache() {
139 global $wgWANObjectCaches;
140
141 // We need a WAN cache for this.
142 $this->setMwGlobals( [
143 'wgMainWANCache' => 'hash',
144 'wgWANObjectCaches' => $wgWANObjectCaches + [
145 'hash' => [
146 'class' => WANObjectCache::class,
147 'cacheId' => 'hash',
148 'channels' => []
149 ]
150 ]
151 ] );
152
153 $messageCache = MessageCache::singleton();
154 $messageCache->enable();
155
156 // Populate one key
157 $this->makePage( 'Key1', 'de', 'Value1' );
158 $this->assertSame( 0,
159 DeferredUpdates::pendingUpdatesCount(),
160 'Post-commit deferred update triggers a run of all updates' );
161 $this->assertEquals( 'Value1', $messageCache->get( 'Key1' ), 'Key1 was successfully edited' );
162
163 // Screw up the database so MessageCache::loadFromDB() will
164 // produce the wrong result for reloading Key1
165 $this->db->delete(
166 'page', [ 'page_namespace' => NS_MEDIAWIKI, 'page_title' => 'Key1' ], __METHOD__
167 );
168
169 // Populate the second key
170 $this->makePage( 'Key2', 'de', 'Value2' );
171 $this->assertSame( 0,
172 DeferredUpdates::pendingUpdatesCount(),
173 'Post-commit deferred update triggers a run of all updates' );
174 $this->assertEquals( 'Value2', $messageCache->get( 'Key2' ), 'Key2 was successfully edited' );
175
176 // Now test that the second edit didn't reload Key1
177 $this->assertEquals( 'Value1', $messageCache->get( 'Key1' ),
178 'Key1 wasn\'t reloaded by edit of Key2' );
179 }
180
181 /**
182 * @dataProvider provideNormalizeKey
183 */
184 public function testNormalizeKey( $key, $expected ) {
185 $actual = MessageCache::normalizeKey( $key );
186 $this->assertEquals( $expected, $actual );
187 }
188
189 public function provideNormalizeKey() {
190 return [
191 [ 'Foo', 'foo' ],
192 [ 'foo', 'foo' ],
193 [ 'fOo', 'fOo' ],
194 [ 'FOO', 'fOO' ],
195 [ 'Foo bar', 'foo_bar' ],
196 [ 'Ćab', 'ćab' ],
197 [ 'Ćab_e 3', 'ćab_e_3' ],
198 [ 'ĆAB', 'ćAB' ],
199 [ 'ćab', 'ćab' ],
200 [ 'ćaB', 'ćaB' ],
201 ];
202 }
203
204 public function testNoDBAccessContentLanguage() {
205 global $wgContLanguageCode;
206
207 $dbr = wfGetDB( DB_REPLICA );
208
209 MessageCache::singleton()->getMsgFromNamespace( 'allpages', $wgContLanguageCode );
210
211 $this->assertSame( 0, $dbr->trxLevel() );
212 $dbr->setFlag( DBO_TRX, $dbr::REMEMBER_PRIOR ); // make queries trigger TRX
213
214 MessageCache::singleton()->getMsgFromNamespace( 'go', $wgContLanguageCode );
215
216 $dbr->restoreFlags();
217
218 $this->assertSame( 0, $dbr->trxLevel(), "No DB read queries (content language)" );
219 }
220
221 public function testNoDBAccessNonContentLanguage() {
222 $dbr = wfGetDB( DB_REPLICA );
223
224 MessageCache::singleton()->getMsgFromNamespace( 'allpages/nl', 'nl' );
225
226 $this->assertSame( 0, $dbr->trxLevel() );
227 $dbr->setFlag( DBO_TRX, $dbr::REMEMBER_PRIOR ); // make queries trigger TRX
228
229 MessageCache::singleton()->getMsgFromNamespace( 'go/nl', 'nl' );
230
231 $dbr->restoreFlags();
232
233 $this->assertSame( 0, $dbr->trxLevel(), "No DB read queries (non-content language)" );
234 }
235
236 /**
237 * Regression test for T218918
238 */
239 public function testLoadFromDB_fetchLatestRevision() {
240 // Create three revisions of the same message page.
241 // Must be an existing message key.
242 $key = 'Log';
243 $this->makePage( $key, 'de', 'Test eins' );
244 $this->makePage( $key, 'de', 'Test zwei' );
245 $r3 = $this->makePage( $key, 'de', 'Test drei' );
246
247 // Create an out-of-sequence revision by importing a
248 // revision with an old timestamp. Hacky.
249 $importRevision = new WikiRevision( new HashConfig() );
250 $importRevision->setTitle( $r3->getTitle() );
251 $importRevision->setComment( 'Imported edit' );
252 $importRevision->setTimestamp( '19991122001122' );
253 $importRevision->setText( 'IMPORTED OLD TEST' );
254 $importRevision->setUsername( 'ext>Alan Smithee' );
255
256 $importer = MediaWikiServices::getInstance()->getWikiRevisionOldRevisionImporterNoUpdates();
257 $importer->import( $importRevision );
258
259 // Now, load the message from the wiki page
260 $messageCache = MessageCache::singleton();
261 $messageCache->enable();
262 $messageCache = TestingAccessWrapper::newFromObject( $messageCache );
263
264 $cache = $messageCache->loadFromDB( 'de' );
265
266 $this->assertArrayHasKey( $key, $cache );
267
268 // Text in the cache has an extra space in front!
269 $this->assertSame( ' ' . 'Test drei', $cache[$key] );
270 }
271
272 }