rdbms: Rename "memCache" to "memStash" in LBFactory
[mediawiki.git] / tests / phpunit / includes / db / LBFactoryTest.php
blob1efeeebdafa54876d94f3a8953604e6e16464e16
1 <?php
3 use Wikimedia\Rdbms\LBFactorySimple;
4 use Wikimedia\Rdbms\LBFactoryMulti;
5 use Wikimedia\Rdbms\ChronologyProtector;
6 use Wikimedia\Rdbms\MySQLMasterPos;
8 /**
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
26 * @group Database
27 * @file
28 * @author Antoine Musso
29 * @copyright © 2013 Antoine Musso
30 * @copyright © 2013 Wikimedia Foundation Inc.
32 class LBFactoryTest extends MediaWikiTestCase {
34 /**
35 * @dataProvider getLBFactoryClassProvider
37 public function testGetLBFactoryClass( $expected, $deprecated ) {
38 $mockDB = $this->getMockBuilder( 'DatabaseMysqli' )
39 ->disableOriginalConstructor()
40 ->getMock();
42 $config = [
43 'class' => $deprecated,
44 'connection' => $mockDB,
45 # Various other parameters required:
46 'sectionsByDB' => [],
47 'sectionLoads' => [],
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() {
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' ],
69 public function testLBFactorySimpleServer() {
70 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
72 $servers = [
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
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' );
94 $factory->shutdown();
95 $lb->closeAll();
98 public function testLBFactorySimpleServers() {
99 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
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
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
124 $factory = new LBFactorySimple( [
125 'servers' => $servers,
126 'loadMonitorClass' => 'LoadMonitorNull'
127 ] );
128 $lb = $factory->getMainLB();
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' );
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' );
144 $factory->shutdown();
145 $lb->closeAll();
148 public function testLBFactoryMulti() {
149 global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
151 $factory = new LBFactoryMulti( [
152 'sectionsByDB' => [],
153 'sectionLoads' => [
154 'DEFAULT' => [
155 'test-db1' => 0,
156 'test-db2' => 100,
159 'serverTemplate' => [
160 'dbname' => $wgDBname,
161 'user' => $wgDBuser,
162 'password' => $wgDBpassword,
163 'type' => $wgDBtype,
164 'dbDirectory' => $wgSQLiteDataDir,
165 'flags' => DBO_DEFAULT
167 'hostsByName' => [
168 'test-db1' => $wgDBserver,
169 'test-db2' => $wgDBserver
171 'loadMonitorClass' => 'LoadMonitorNull'
172 ] );
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();
182 $lb->closeAll();
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()
192 ->getMock();
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()
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' ] );
210 return (bool)$p;
212 ) );
213 $lb->method( 'getMasterPos' )->willReturn( $mPos );
215 $bag = new HashBagOStuff();
216 $cp = new ChronologyProtector(
217 $bag,
219 'ip' => '127.0.0.1',
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
228 $cp->initLB( $lb );
229 // Record in stash
230 $cp->shutdownLB( $lb );
231 $cp->shutdown();
233 // (b) Second HTTP request
234 $cp = new ChronologyProtector(
235 $bag,
237 'ip' => '127.0.0.1',
238 'agent' => "Totally-Not-FireFox"
242 $lb->expects( $this->once() )
243 ->method( 'waitFor' )->with( $this->equalTo( $mPos ) );
245 // Wait
246 $cp->initLB( $lb );
247 // Record in stash
248 $cp->shutdownLB( $lb );
249 $cp->shutdown();
252 private function newLBFactoryMulti( array $baseOverride = [], array $serverOverride = [] ) {
253 global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, $wgDBtype, $wgSQLiteDataDir;
255 return new LBFactoryMulti( $baseOverride + [
256 'sectionsByDB' => [],
257 'sectionLoads' => [
258 'DEFAULT' => [
259 'test-db1' => 1,
262 'serverTemplate' => $serverOverride + [
263 'dbname' => $wgDBname,
264 'user' => $wgDBuser,
265 'password' => $wgDBpassword,
266 'type' => $wgDBtype,
267 'dbDirectory' => $wgSQLiteDataDir,
268 'flags' => DBO_DEFAULT
270 'hostsByName' => [
271 'test-db1' => $wgDBserver,
273 'loadMonitorClass' => 'LoadMonitorNull',
274 'localDomain' => wfWikiID()
275 ] );
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();
287 } else {
288 $dbPath = null;
291 $factory = $this->newLBFactoryMulti(
293 [ 'dbFilePath' => $dbPath ]
295 $lb = $factory->getMainLB();
297 if ( $wgDBtype !== 'sqlite' ) {
298 $db = $lb->getConnectionRef( DB_MASTER );
299 $this->assertEquals(
300 $wgDBname,
301 $db->getDomainID()
303 unset( $db );
306 /** @var Database $db */
307 $db = $lb->getConnection( DB_MASTER, [], '' );
308 $lb->reuseConnection( $db ); // don't care
310 $this->assertEquals(
312 $db->getDomainID()
315 $this->assertEquals(
316 $this->quoteTable( $db, 'page' ),
317 $db->tableName( 'page' ),
318 "Correct full table name"
321 $this->assertEquals(
322 $this->quoteTable( $db, $wgDBname ) . '.' . $this->quoteTable( $db, 'page' ),
323 $db->tableName( "$wgDBname.page" ),
324 "Correct full table name"
327 $this->assertEquals(
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_' );
334 $this->assertEquals(
336 $db->getDomainID()
338 $this->assertEquals(
339 $this->quoteTable( $db, 'my_page' ),
340 $db->tableName( 'page' ),
341 "Correct full table name"
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"
349 $factory->closeAll();
350 $factory->destroy();
353 public function testTrickyDomain() {
354 global $wgDBtype;
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;
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
376 $this->assertEquals(
378 $db->getDomainID()
381 $this->assertEquals(
382 $this->quoteTable( $db, 'page' ),
383 $db->tableName( 'page' ),
384 "Correct full table name"
387 $this->assertEquals(
388 $this->quoteTable( $db, $dbname ) . '.' . $this->quoteTable( $db, 'page' ),
389 $db->tableName( "$dbname.page" ),
390 "Correct full table name"
393 $this->assertEquals(
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_' );
401 $this->assertEquals(
402 $this->quoteTable( $db, 'my_page' ),
403 $db->tableName( 'page' ),
404 "Correct full table name"
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"
412 $this->assertEquals(
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() ) {
419 try {
420 $e = null;
421 $db->selectDB( 'garbage-db' );
422 } catch ( \Wikimedia\Rdbms\DBConnectionError $e ) {
423 // expected
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();
433 $factory->closeAll();
434 $factory->destroy();
437 private function quoteTable( Database $db, $table ) {
438 if ( $db->getType() === 'sqlite' ) {
439 return $table;
440 } else {
441 return $db->addIdentifierQuotes( $table );