3 * Holds tests for LBFactory abstract MediaWiki class.
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.
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.
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
22 * @author Antoine Musso
23 * @copyright © 2013 Antoine Musso
24 * @copyright © 2013 Wikimedia Foundation Inc.
26 class LBFactoryTest
extends MediaWikiTestCase
{
29 * @dataProvider getLBFactoryClassProvider
31 public function testGetLBFactoryClass( $expected, $deprecated ) {
32 $mockDB = $this->getMockBuilder( 'DatabaseMysqli' )
33 ->disableOriginalConstructor()
37 'class' => $deprecated,
38 'connection' => $mockDB,
39 # Various other parameters required:
42 'serverTemplate' => [],
45 $this->hideDeprecated( '$wgLBFactoryConf must be updated. See RELEASE-NOTES for details' );
46 $result = MWLBFactory
::getLBFactoryClass( $config );
48 $this->assertEquals( $expected, $result );
51 public function getLBFactoryClassProvider() {
53 # Format: new class, old class
54 [ 'LBFactorySimple', 'LBFactory_Simple' ],
55 [ 'LBFactorySingle', 'LBFactory_Single' ],
56 [ 'LBFactoryMulti', 'LBFactory_Multi' ],
60 public function testLBFactorySimpleServer() {
61 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
65 'host' => $wgDBserver,
66 'dbname' => $wgDBname,
68 'password' => $wgDBpassword,
70 'dbDirectory' => $wgSQLiteDataDir,
72 'flags' => DBO_TRX
// REPEATABLE-READ for consistency
76 $factory = new LBFactorySimple( [ 'servers' => $servers ] );
77 $lb = $factory->getMainLB();
79 $dbw = $lb->getConnection( DB_MASTER
);
80 $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
82 $dbr = $lb->getConnection( DB_SLAVE
);
83 $this->assertTrue( $dbr->getLBInfo( 'master' ), 'DB_SLAVE also gets the master' );
89 public function testLBFactorySimpleServers() {
90 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
94 'host' => $wgDBserver,
95 'dbname' => $wgDBname,
97 'password' => $wgDBpassword,
99 'dbDirectory' => $wgSQLiteDataDir,
101 'flags' => DBO_TRX
// REPEATABLE-READ for consistency
104 'host' => $wgDBserver,
105 'dbname' => $wgDBname,
107 'password' => $wgDBpassword,
109 'dbDirectory' => $wgSQLiteDataDir,
111 'flags' => DBO_TRX
// REPEATABLE-READ for consistency
115 $factory = new LBFactorySimple( [
116 'servers' => $servers,
117 'loadMonitorClass' => 'LoadMonitorNull'
119 $lb = $factory->getMainLB();
121 $dbw = $lb->getConnection( DB_MASTER
);
122 $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
124 ( $wgDBserver != '' ) ?
$wgDBserver : 'localhost',
125 $dbw->getLBInfo( 'clusterMasterHost' ),
126 'cluster master set' );
128 $dbr = $lb->getConnection( DB_SLAVE
);
129 $this->assertTrue( $dbr->getLBInfo( 'replica' ), 'slave shows as slave' );
131 ( $wgDBserver != '' ) ?
$wgDBserver : 'localhost',
132 $dbr->getLBInfo( 'clusterMasterHost' ),
133 'cluster master set' );
135 $factory->shutdown();
139 public function testLBFactoryMulti() {
140 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
142 $factory = new LBFactoryMulti( [
143 'sectionsByDB' => [],
150 'serverTemplate' => [
151 'dbname' => $wgDBname,
153 'password' => $wgDBpassword,
155 'dbDirectory' => $wgSQLiteDataDir,
156 'flags' => DBO_DEFAULT
159 'test-db1' => $wgDBserver,
160 'test-db2' => $wgDBserver
162 'loadMonitorClass' => 'LoadMonitorNull'
164 $lb = $factory->getMainLB();
166 $dbw = $lb->getConnection( DB_MASTER
);
167 $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
169 $dbr = $lb->getConnection( DB_SLAVE
);
170 $this->assertTrue( $dbr->getLBInfo( 'replica' ), 'slave shows as slave' );
172 $factory->shutdown();
176 public function testChronologyProtector() {
177 // (a) First HTTP request
178 $mPos = new MySQLMasterPos( 'db1034-bin.000976', '843431247' );
180 $now = microtime( true );
181 $mockDB = $this->getMockBuilder( 'DatabaseMysqli' )
182 ->disableOriginalConstructor()
184 $mockDB->method( 'writesOrCallbacksPending' )->willReturn( true );
185 $mockDB->method( 'lastDoneWrites' )->willReturn( $now );
186 $mockDB->method( 'getMasterPos' )->willReturn( $mPos );
188 $lb = $this->getMockBuilder( 'LoadBalancer' )
189 ->disableOriginalConstructor()
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 ) {
198 $p |
= call_user_func( [ $mockDB, 'writesOrCallbacksPending' ] );
199 $p |
= call_user_func( [ $mockDB, 'lastDoneWrites' ] );
204 $lb->method( 'getMasterPos' )->willReturn( $mPos );
206 $bag = new HashBagOStuff();
207 $cp = new ChronologyProtector(
211 'agent' => "Totally-Not-FireFox"
215 $mockDB->expects( $this->exactly( 2 ) )->method( 'writesOrCallbacksPending' );
216 $mockDB->expects( $this->exactly( 2 ) )->method( 'lastDoneWrites' );
218 // Nothing to wait for
221 $cp->shutdownLB( $lb );
224 // (b) Second HTTP request
225 $cp = new ChronologyProtector(
229 'agent' => "Totally-Not-FireFox"
233 $lb->expects( $this->once() )
234 ->method( 'waitFor' )->with( $this->equalTo( $mPos ) );
239 $cp->shutdownLB( $lb );
243 private function newLBFactoryMulti( array $baseOverride = [], array $serverOverride = [] ) {
244 global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, $wgDBtype, $wgSQLiteDataDir;
246 return new LBFactoryMulti( $baseOverride +
[
247 'sectionsByDB' => [],
253 'serverTemplate' => $serverOverride +
[
254 'dbname' => $wgDBname,
256 'password' => $wgDBpassword,
258 'dbDirectory' => $wgSQLiteDataDir,
259 'flags' => DBO_DEFAULT
262 'test-db1' => $wgDBserver,
264 'loadMonitorClass' => 'LoadMonitorNull',
265 'localDomain' => wfWikiID()
269 public function testNiceDomains() {
270 global $wgDBname, $wgDBtype;
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();
282 $factory = $this->newLBFactoryMulti(
284 [ 'dbFilePath' => $dbPath ]
286 $lb = $factory->getMainLB();
288 if ( $wgDBtype !== 'sqlite' ) {
289 $db = $lb->getConnectionRef( DB_MASTER
);
297 /** @var Database $db */
298 $db = $lb->getConnection( DB_MASTER
, [], '' );
299 $lb->reuseConnection( $db ); // don't care
307 $this->quoteTable( $db, 'page' ),
308 $db->tableName( 'page' ),
309 "Correct full table name"
313 $this->quoteTable( $db, $wgDBname ) . '.' . $this->quoteTable( $db, 'page' ),
314 $db->tableName( "$wgDBname.page" ),
315 "Correct full table name"
319 $this->quoteTable( $db, 'nice_db' ) . '.' . $this->quoteTable( $db, 'page' ),
320 $db->tableName( 'nice_db.page' ),
321 "Correct full table name"
324 $factory->setDomainPrefix( 'my_' );
330 $this->quoteTable( $db, 'my_page' ),
331 $db->tableName( 'page' ),
332 "Correct full table name"
335 $this->quoteTable( $db, 'other_nice_db' ) . '.' . $this->quoteTable( $db, 'page' ),
336 $db->tableName( 'other_nice_db.page' ),
337 "Correct full table name"
340 $factory->closeAll();
344 public function testTrickyDomain() {
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();
357 $dbname = 'unittest-domain';
358 $factory = $this->newLBFactoryMulti(
359 [ 'localDomain' => $dbname ],
360 [ 'dbname' => $dbname, 'dbFilePath' => $dbPath ]
362 $lb = $factory->getMainLB();
363 /** @var Database $db */
364 $db = $lb->getConnection( DB_MASTER
, [], '' );
365 $lb->reuseConnection( $db ); // don't care
373 $this->quoteTable( $db, 'page' ),
374 $db->tableName( 'page' ),
375 "Correct full table name"
379 $this->quoteTable( $db, $dbname ) . '.' . $this->quoteTable( $db, 'page' ),
380 $db->tableName( "$dbname.page" ),
381 "Correct full table name"
385 $this->quoteTable( $db, 'nice_db' ) . '.' . $this->quoteTable( $db, 'page' ),
386 $db->tableName( 'nice_db.page' ),
387 "Correct full table name"
390 $factory->setDomainPrefix( 'my_' );
393 $this->quoteTable( $db, 'my_page' ),
394 $db->tableName( 'page' ),
395 "Correct full table name"
398 $this->quoteTable( $db, 'other_nice_db' ) . '.' . $this->quoteTable( $db, 'page' ),
399 $db->tableName( 'other_nice_db.page' ),
400 "Correct full table name"
403 \MediaWiki\
suppressWarnings();
404 $this->assertFalse( $db->selectDB( 'garbage-db' ) );
405 \MediaWiki\restoreWarnings
();
408 $this->quoteTable( $db, 'garbage-db' ) . '.' . $this->quoteTable( $db, 'page' ),
409 $db->tableName( 'garbage-db.page' ),
410 "Correct full table name"
413 $factory->closeAll();
417 private function quoteTable( Database
$db, $table ) {
418 if ( $db->getType() === 'sqlite' ) {
421 return $db->addIdentifierQuotes( $table );