Make LBFactoryTest.php pass for sqlite
[lhc/web/wiklou.git] / tests / phpunit / includes / db / LBFactoryTest.php
1 <?php
2 /**
3 * Holds tests for LBFactory abstract MediaWiki class.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @group Database
21 * @file
22 * @author Antoine Musso
23 * @copyright © 2013 Antoine Musso
24 * @copyright © 2013 Wikimedia Foundation Inc.
25 */
26 class LBFactoryTest extends MediaWikiTestCase {
27
28 /**
29 * @dataProvider getLBFactoryClassProvider
30 */
31 public function testGetLBFactoryClass( $expected, $deprecated ) {
32 $mockDB = $this->getMockBuilder( 'DatabaseMysql' )
33 ->disableOriginalConstructor()
34 ->getMock();
35
36 $config = [
37 'class' => $deprecated,
38 'connection' => $mockDB,
39 # Various other parameters required:
40 'sectionsByDB' => [],
41 'sectionLoads' => [],
42 'serverTemplate' => [],
43 ];
44
45 $this->hideDeprecated( '$wgLBFactoryConf must be updated. See RELEASE-NOTES for details' );
46 $result = MWLBFactory::getLBFactoryClass( $config );
47
48 $this->assertEquals( $expected, $result );
49 }
50
51 public function getLBFactoryClassProvider() {
52 return [
53 # Format: new class, old class
54 [ 'LBFactorySimple', 'LBFactory_Simple' ],
55 [ 'LBFactorySingle', 'LBFactory_Single' ],
56 [ 'LBFactoryMulti', 'LBFactory_Multi' ],
57 ];
58 }
59
60 public function testLBFactorySimpleServer() {
61 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
62
63 $servers = [
64 [
65 'host' => $wgDBserver,
66 'dbname' => $wgDBname,
67 'user' => $wgDBuser,
68 'password' => $wgDBpassword,
69 'type' => $wgDBtype,
70 'dbDirectory' => $wgSQLiteDataDir,
71 'load' => 0,
72 'flags' => DBO_TRX // REPEATABLE-READ for consistency
73 ],
74 ];
75
76 $factory = new LBFactorySimple( [ 'servers' => $servers ] );
77 $lb = $factory->getMainLB();
78
79 $dbw = $lb->getConnection( DB_MASTER );
80 $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
81
82 $dbr = $lb->getConnection( DB_SLAVE );
83 $this->assertTrue( $dbr->getLBInfo( 'master' ), 'DB_SLAVE also gets the master' );
84
85 $factory->shutdown();
86 $lb->closeAll();
87 }
88
89 public function testLBFactorySimpleServers() {
90 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
91
92 $servers = [
93 [ // master
94 'host' => $wgDBserver,
95 'dbname' => $wgDBname,
96 'user' => $wgDBuser,
97 'password' => $wgDBpassword,
98 'type' => $wgDBtype,
99 'dbDirectory' => $wgSQLiteDataDir,
100 'load' => 0,
101 'flags' => DBO_TRX // REPEATABLE-READ for consistency
102 ],
103 [ // emulated slave
104 'host' => $wgDBserver,
105 'dbname' => $wgDBname,
106 'user' => $wgDBuser,
107 'password' => $wgDBpassword,
108 'type' => $wgDBtype,
109 'dbDirectory' => $wgSQLiteDataDir,
110 'load' => 100,
111 'flags' => DBO_TRX // REPEATABLE-READ for consistency
112 ]
113 ];
114
115 $factory = new LBFactorySimple( [
116 'servers' => $servers,
117 'loadMonitorClass' => 'LoadMonitorNull'
118 ] );
119 $lb = $factory->getMainLB();
120
121 $dbw = $lb->getConnection( DB_MASTER );
122 $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
123 $this->assertEquals(
124 ( $wgDBserver != '' ) ? $wgDBserver : 'localhost',
125 $dbw->getLBInfo( 'clusterMasterHost' ),
126 'cluster master set' );
127
128 $dbr = $lb->getConnection( DB_SLAVE );
129 $this->assertTrue( $dbr->getLBInfo( 'replica' ), 'slave shows as slave' );
130 $this->assertEquals(
131 ( $wgDBserver != '' ) ? $wgDBserver : 'localhost',
132 $dbr->getLBInfo( 'clusterMasterHost' ),
133 'cluster master set' );
134
135 $factory->shutdown();
136 $lb->closeAll();
137 }
138
139 public function testLBFactoryMulti() {
140 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
141
142 $factory = new LBFactoryMulti( [
143 'sectionsByDB' => [],
144 'sectionLoads' => [
145 'DEFAULT' => [
146 'test-db1' => 0,
147 'test-db2' => 100,
148 ],
149 ],
150 'serverTemplate' => [
151 'dbname' => $wgDBname,
152 'user' => $wgDBuser,
153 'password' => $wgDBpassword,
154 'type' => $wgDBtype,
155 'dbDirectory' => $wgSQLiteDataDir,
156 'flags' => DBO_DEFAULT
157 ],
158 'hostsByName' => [
159 'test-db1' => $wgDBserver,
160 'test-db2' => $wgDBserver
161 ],
162 'loadMonitorClass' => 'LoadMonitorNull'
163 ] );
164 $lb = $factory->getMainLB();
165
166 $dbw = $lb->getConnection( DB_MASTER );
167 $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
168
169 $dbr = $lb->getConnection( DB_SLAVE );
170 $this->assertTrue( $dbr->getLBInfo( 'replica' ), 'slave shows as slave' );
171
172 $factory->shutdown();
173 $lb->closeAll();
174 }
175
176 public function testChronologyProtector() {
177 // (a) First HTTP request
178 $mPos = new MySQLMasterPos( 'db1034-bin.000976', '843431247' );
179
180 $now = microtime( true );
181 $mockDB = $this->getMockBuilder( 'DatabaseMysql' )
182 ->disableOriginalConstructor()
183 ->getMock();
184 $mockDB->method( 'writesOrCallbacksPending' )->willReturn( true );
185 $mockDB->method( 'lastDoneWrites' )->willReturn( $now );
186 $mockDB->method( 'getMasterPos' )->willReturn( $mPos );
187
188 $lb = $this->getMockBuilder( 'LoadBalancer' )
189 ->disableOriginalConstructor()
190 ->getMock();
191 $lb->method( 'getConnection' )->willReturn( $mockDB );
192 $lb->method( 'getServerCount' )->willReturn( 2 );
193 $lb->method( 'parentInfo' )->willReturn( [ 'id' => "main-DEFAULT" ] );
194 $lb->method( 'getAnyOpenConnection' )->willReturn( $mockDB );
195 $lb->method( 'hasOrMadeRecentMasterChanges' )->will( $this->returnCallback(
196 function () use ( $mockDB ) {
197 $p = 0;
198 $p |= call_user_func( [ $mockDB, 'writesOrCallbacksPending' ] );
199 $p |= call_user_func( [ $mockDB, 'lastDoneWrites' ] );
200
201 return (bool)$p;
202 }
203 ) );
204 $lb->method( 'getMasterPos' )->willReturn( $mPos );
205
206 $bag = new HashBagOStuff();
207 $cp = new ChronologyProtector(
208 $bag,
209 [
210 'ip' => '127.0.0.1',
211 'agent' => "Totally-Not-FireFox"
212 ]
213 );
214
215 $mockDB->expects( $this->exactly( 2 ) )->method( 'writesOrCallbacksPending' );
216 $mockDB->expects( $this->exactly( 2 ) )->method( 'lastDoneWrites' );
217
218 // Nothing to wait for
219 $cp->initLB( $lb );
220 // Record in stash
221 $cp->shutdownLB( $lb );
222 $cp->shutdown();
223
224 // (b) Second HTTP request
225 $cp = new ChronologyProtector(
226 $bag,
227 [
228 'ip' => '127.0.0.1',
229 'agent' => "Totally-Not-FireFox"
230 ]
231 );
232
233 $lb->expects( $this->once() )
234 ->method( 'waitFor' )->with( $this->equalTo( $mPos ) );
235
236 // Wait
237 $cp->initLB( $lb );
238 // Record in stash
239 $cp->shutdownLB( $lb );
240 $cp->shutdown();
241 }
242
243 private function newLBFactoryMulti( array $baseOverride = [], array $serverOverride = [] ) {
244 global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, $wgDBtype, $wgSQLiteDataDir;
245
246 return new LBFactoryMulti( $baseOverride + [
247 'sectionsByDB' => [],
248 'sectionLoads' => [
249 'DEFAULT' => [
250 'test-db1' => 1,
251 ],
252 ],
253 'serverTemplate' => $serverOverride + [
254 'dbname' => $wgDBname,
255 'user' => $wgDBuser,
256 'password' => $wgDBpassword,
257 'type' => $wgDBtype,
258 'dbDirectory' => $wgSQLiteDataDir,
259 'flags' => DBO_DEFAULT
260 ],
261 'hostsByName' => [
262 'test-db1' => $wgDBserver,
263 ],
264 'loadMonitorClass' => 'LoadMonitorNull',
265 'localDomain' => wfWikiID()
266 ] );
267 }
268
269 public function testNiceDomains() {
270 global $wgDBname, $wgDBtype;
271
272 if ( $wgDBtype === 'sqlite' ) {
273 $tmpDir = $this->getNewTempDirectory();
274 $dbPath = "$tmpDir/unit_test_db.sqlite";
275 file_put_contents( $dbPath, '' );
276 $tempFsFile = new TempFSFile( $dbPath );
277 $tempFsFile->autocollect();
278 } else {
279 $dbPath = null;
280 }
281
282 $factory = $this->newLBFactoryMulti(
283 [],
284 [ 'dbFilePath' => $dbPath ]
285 );
286 $lb = $factory->getMainLB();
287
288 if ( $wgDBtype !== 'sqlite' ) {
289 $db = $lb->getConnectionRef( DB_MASTER );
290 $this->assertEquals(
291 $wgDBname,
292 $db->getDomainID()
293 );
294 unset( $db );
295 }
296
297 /** @var Database $db */
298 $db = $lb->getConnection( DB_MASTER, [], '' );
299 $lb->reuseConnection( $db ); // don't care
300
301 $this->assertEquals(
302 '',
303 $db->getDomainID()
304 );
305
306 $this->assertEquals(
307 $this->quoteTable( $db, 'page' ),
308 $db->tableName( 'page' ),
309 "Correct full table name"
310 );
311
312 $this->assertEquals(
313 $this->quoteTable( $db, $wgDBname ) . '.' . $this->quoteTable( $db, 'page' ),
314 $db->tableName( "$wgDBname.page" ),
315 "Correct full table name"
316 );
317
318 $this->assertEquals(
319 $this->quoteTable( $db, 'nice_db' ) . '.' . $this->quoteTable( $db, 'page' ),
320 $db->tableName( 'nice_db.page' ),
321 "Correct full table name"
322 );
323
324 $factory->setDomainPrefix( 'my_' );
325 $this->assertEquals(
326 '',
327 $db->getDomainID()
328 );
329 $this->assertEquals(
330 $this->quoteTable( $db, 'my_page' ),
331 $db->tableName( 'page' ),
332 "Correct full table name"
333 );
334 $this->assertEquals(
335 $this->quoteTable( $db, 'other_nice_db' ) . '.' . $this->quoteTable( $db, 'page' ),
336 $db->tableName( 'other_nice_db.page' ),
337 "Correct full table name"
338 );
339
340 $factory->closeAll();
341 $factory->destroy();
342 }
343
344 public function testTrickyDomain() {
345 global $wgDBtype;
346
347 if ( $wgDBtype === 'sqlite' ) {
348 $tmpDir = $this->getNewTempDirectory();
349 $dbPath = "$tmpDir/unit_test_db.sqlite";
350 file_put_contents( $dbPath, '' );
351 $tempFsFile = new TempFSFile( $dbPath );
352 $tempFsFile->autocollect();
353 } else {
354 $dbPath = null;
355 }
356
357 $dbname = 'unittest-domain';
358 $factory = $this->newLBFactoryMulti(
359 [ 'localDomain' => $dbname ],
360 [ 'dbname' => $dbname, 'dbFilePath' => $dbPath ]
361 );
362 $lb = $factory->getMainLB();
363 /** @var Database $db */
364 $db = $lb->getConnection( DB_MASTER, [], '' );
365 $lb->reuseConnection( $db ); // don't care
366
367 $this->assertEquals(
368 '',
369 $db->getDomainID()
370 );
371
372 $this->assertEquals(
373 $this->quoteTable( $db, 'page' ),
374 $db->tableName( 'page' ),
375 "Correct full table name"
376 );
377
378 $this->assertEquals(
379 $this->quoteTable( $db, $dbname ) . '.' . $this->quoteTable( $db, 'page' ),
380 $db->tableName( "$dbname.page" ),
381 "Correct full table name"
382 );
383
384 $this->assertEquals(
385 $this->quoteTable( $db, 'nice_db' ) . '.' . $this->quoteTable( $db, 'page' ),
386 $db->tableName( 'nice_db.page' ),
387 "Correct full table name"
388 );
389
390 $factory->setDomainPrefix( 'my_' );
391
392 $this->assertEquals(
393 $this->quoteTable( $db, 'my_page' ),
394 $db->tableName( 'page' ),
395 "Correct full table name"
396 );
397 $this->assertEquals(
398 $this->quoteTable( $db, 'other_nice_db' ) . '.' . $this->quoteTable( $db, 'page' ),
399 $db->tableName( 'other_nice_db.page' ),
400 "Correct full table name"
401 );
402
403 \MediaWiki\suppressWarnings();
404 $this->assertFalse( $db->selectDB( 'garbage-db' ) );
405 \MediaWiki\restoreWarnings();
406
407 $this->assertEquals(
408 $this->quoteTable( $db, 'garbage-db' ) . '.' . $this->quoteTable( $db, 'page' ),
409 $db->tableName( 'garbage-db.page' ),
410 "Correct full table name"
411 );
412
413 $factory->closeAll();
414 $factory->destroy();
415 }
416
417 private function quoteTable( Database $db, $table ) {
418 if ( $db->getType() === 'sqlite' ) {
419 return $table;
420 } else {
421 return $db->addIdentifierQuotes( $table );
422 }
423 }
424 }