3 use Wikimedia\Rdbms\LBFactorySimple
;
4 use Wikimedia\Rdbms\LBFactoryMulti
;
5 use Wikimedia\Rdbms\ChronologyProtector
;
6 use Wikimedia\Rdbms\MySQLMasterPos
;
9 * Holds tests for LBFactory abstract MediaWiki class.
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.
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.
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
28 * @author Antoine Musso
29 * @copyright © 2013 Antoine Musso
30 * @copyright © 2013 Wikimedia Foundation Inc.
32 class LBFactoryTest
extends MediaWikiTestCase
{
35 * @dataProvider getLBFactoryClassProvider
37 public function testGetLBFactoryClass( $expected, $deprecated ) {
38 $mockDB = $this->getMockBuilder( 'DatabaseMysqli' )
39 ->disableOriginalConstructor()
43 'class' => $deprecated,
44 'connection' => $mockDB,
45 # Various other parameters required:
48 'serverTemplate' => [],
51 $this->hideDeprecated( '$wgLBFactoryConf must be updated. See RELEASE-NOTES for details' );
52 $result = MWLBFactory
::getLBFactoryClass( $config );
54 $this->assertEquals( $expected, $result );
57 public function getLBFactoryClassProvider() {
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' ],
69 public function testLBFactorySimpleServer() {
70 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
74 'host' => $wgDBserver,
75 'dbname' => $wgDBname,
77 'password' => $wgDBpassword,
79 'dbDirectory' => $wgSQLiteDataDir,
81 'flags' => DBO_TRX
// REPEATABLE-READ for consistency
85 $factory = new LBFactorySimple( [ 'servers' => $servers ] );
86 $lb = $factory->getMainLB();
88 $dbw = $lb->getConnection( DB_MASTER
);
89 $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
91 $dbr = $lb->getConnection( DB_REPLICA
);
92 $this->assertTrue( $dbr->getLBInfo( 'master' ), 'DB_REPLICA also gets the master' );
98 public function testLBFactorySimpleServers() {
99 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
103 'host' => $wgDBserver,
104 'dbname' => $wgDBname,
106 'password' => $wgDBpassword,
108 'dbDirectory' => $wgSQLiteDataDir,
110 'flags' => DBO_TRX
// REPEATABLE-READ for consistency
113 'host' => $wgDBserver,
114 'dbname' => $wgDBname,
116 'password' => $wgDBpassword,
118 'dbDirectory' => $wgSQLiteDataDir,
120 'flags' => DBO_TRX
// REPEATABLE-READ for consistency
124 $factory = new LBFactorySimple( [
125 'servers' => $servers,
126 'loadMonitorClass' => 'LoadMonitorNull'
128 $lb = $factory->getMainLB();
130 $dbw = $lb->getConnection( DB_MASTER
);
131 $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
133 ( $wgDBserver != '' ) ?
$wgDBserver : 'localhost',
134 $dbw->getLBInfo( 'clusterMasterHost' ),
135 'cluster master set' );
137 $dbr = $lb->getConnection( DB_REPLICA
);
138 $this->assertTrue( $dbr->getLBInfo( 'replica' ), 'slave shows as slave' );
140 ( $wgDBserver != '' ) ?
$wgDBserver : 'localhost',
141 $dbr->getLBInfo( 'clusterMasterHost' ),
142 'cluster master set' );
144 $factory->shutdown();
148 public function testLBFactoryMulti() {
149 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
151 $factory = new LBFactoryMulti( [
152 'sectionsByDB' => [],
159 'serverTemplate' => [
160 'dbname' => $wgDBname,
162 'password' => $wgDBpassword,
164 'dbDirectory' => $wgSQLiteDataDir,
165 'flags' => DBO_DEFAULT
168 'test-db1' => $wgDBserver,
169 'test-db2' => $wgDBserver
171 'loadMonitorClass' => 'LoadMonitorNull'
173 $lb = $factory->getMainLB();
175 $dbw = $lb->getConnection( DB_MASTER
);
176 $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
178 $dbr = $lb->getConnection( DB_REPLICA
);
179 $this->assertTrue( $dbr->getLBInfo( 'replica' ), 'slave shows as slave' );
181 $factory->shutdown();
185 public function testChronologyProtector() {
186 // (a) First HTTP request
187 $mPos = new MySQLMasterPos( 'db1034-bin.000976', '843431247' );
189 $now = microtime( true );
190 $mockDB = $this->getMockBuilder( 'DatabaseMysqli' )
191 ->disableOriginalConstructor()
193 $mockDB->method( 'writesOrCallbacksPending' )->willReturn( true );
194 $mockDB->method( 'lastDoneWrites' )->willReturn( $now );
195 $mockDB->method( 'getMasterPos' )->willReturn( $mPos );
197 $lb = $this->getMockBuilder( 'LoadBalancer' )
198 ->disableOriginalConstructor()
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 ) {
207 $p |
= call_user_func( [ $mockDB, 'writesOrCallbacksPending' ] );
208 $p |
= call_user_func( [ $mockDB, 'lastDoneWrites' ] );
213 $lb->method( 'getMasterPos' )->willReturn( $mPos );
215 $bag = new HashBagOStuff();
216 $cp = new ChronologyProtector(
220 'agent' => "Totally-Not-FireFox"
224 $mockDB->expects( $this->exactly( 2 ) )->method( 'writesOrCallbacksPending' );
225 $mockDB->expects( $this->exactly( 2 ) )->method( 'lastDoneWrites' );
227 // Nothing to wait for
230 $cp->shutdownLB( $lb );
233 // (b) Second HTTP request
234 $cp = new ChronologyProtector(
238 'agent' => "Totally-Not-FireFox"
242 $lb->expects( $this->once() )
243 ->method( 'waitFor' )->with( $this->equalTo( $mPos ) );
248 $cp->shutdownLB( $lb );
252 private function newLBFactoryMulti( array $baseOverride = [], array $serverOverride = [] ) {
253 global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, $wgDBtype, $wgSQLiteDataDir;
255 return new LBFactoryMulti( $baseOverride +
[
256 'sectionsByDB' => [],
262 'serverTemplate' => $serverOverride +
[
263 'dbname' => $wgDBname,
265 'password' => $wgDBpassword,
267 'dbDirectory' => $wgSQLiteDataDir,
268 'flags' => DBO_DEFAULT
271 'test-db1' => $wgDBserver,
273 'loadMonitorClass' => 'LoadMonitorNull',
274 'localDomain' => wfWikiID()
278 public function testNiceDomains() {
279 global $wgDBname, $wgDBtype;
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();
291 $factory = $this->newLBFactoryMulti(
293 [ 'dbFilePath' => $dbPath ]
295 $lb = $factory->getMainLB();
297 if ( $wgDBtype !== 'sqlite' ) {
298 $db = $lb->getConnectionRef( DB_MASTER
);
306 /** @var Database $db */
307 $db = $lb->getConnection( DB_MASTER
, [], '' );
308 $lb->reuseConnection( $db ); // don't care
316 $this->quoteTable( $db, 'page' ),
317 $db->tableName( 'page' ),
318 "Correct full table name"
322 $this->quoteTable( $db, $wgDBname ) . '.' . $this->quoteTable( $db, 'page' ),
323 $db->tableName( "$wgDBname.page" ),
324 "Correct full table name"
328 $this->quoteTable( $db, 'nice_db' ) . '.' . $this->quoteTable( $db, 'page' ),
329 $db->tableName( 'nice_db.page' ),
330 "Correct full table name"
333 $factory->setDomainPrefix( 'my_' );
339 $this->quoteTable( $db, 'my_page' ),
340 $db->tableName( 'page' ),
341 "Correct full table name"
344 $this->quoteTable( $db, 'other_nice_db' ) . '.' . $this->quoteTable( $db, 'page' ),
345 $db->tableName( 'other_nice_db.page' ),
346 "Correct full table name"
349 $factory->closeAll();
353 public function testTrickyDomain() {
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();
366 $dbname = 'unittest-domain';
367 $factory = $this->newLBFactoryMulti(
368 [ 'localDomain' => $dbname ],
369 [ 'dbname' => $dbname, 'dbFilePath' => $dbPath ]
371 $lb = $factory->getMainLB();
372 /** @var Database $db */
373 $db = $lb->getConnection( DB_MASTER
, [], '' );
374 $lb->reuseConnection( $db ); // don't care
382 $this->quoteTable( $db, 'page' ),
383 $db->tableName( 'page' ),
384 "Correct full table name"
388 $this->quoteTable( $db, $dbname ) . '.' . $this->quoteTable( $db, 'page' ),
389 $db->tableName( "$dbname.page" ),
390 "Correct full table name"
394 $this->quoteTable( $db, 'nice_db' ) . '.' . $this->quoteTable( $db, 'page' ),
395 $db->tableName( 'nice_db.page' ),
396 "Correct full table name"
399 $factory->setDomainPrefix( 'my_' );
402 $this->quoteTable( $db, 'my_page' ),
403 $db->tableName( 'page' ),
404 "Correct full table name"
407 $this->quoteTable( $db, 'other_nice_db' ) . '.' . $this->quoteTable( $db, 'page' ),
408 $db->tableName( 'other_nice_db.page' ),
409 "Correct full table name"
413 $this->quoteTable( $db, 'garbage-db' ) . '.' . $this->quoteTable( $db, 'page' ),
414 $db->tableName( 'garbage-db.page' ),
415 "Correct full table name"
418 if ( $db->databasesAreIndependent() ) {
421 $db->selectDB( 'garbage-db' );
422 } catch ( \Wikimedia\Rdbms\DBConnectionError
$e ) {
425 $this->assertInstanceOf( '\Wikimedia\Rdbms\DBConnectionError', $e );
426 $this->assertFalse( $db->isOpen() );
428 \MediaWiki\
suppressWarnings();
429 $this->assertFalse( $db->selectDB( 'garbage-db' ) );
430 \MediaWiki\restoreWarnings
();
433 $factory->closeAll();
437 private function quoteTable( Database
$db, $table ) {
438 if ( $db->getType() === 'sqlite' ) {
441 return $db->addIdentifierQuotes( $table );