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