3 * Holds tests for DatabaseMysqlBase 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
21 * @author Antoine Musso
23 * @copyright © 2013 Antoine Musso
24 * @copyright © 2013 Bryan Davis
25 * @copyright © 2013 Wikimedia Foundation Inc.
29 * Fake class around abstract class so we can call concrete methods.
31 class FakeDatabaseMysqlBase
extends DatabaseMysqlBase
{
33 function __construct() {
36 protected function closeConnection() {
39 protected function doQuery( $sql ) {
43 protected function mysqlConnect( $realServer ) {
46 protected function mysqlSetCharset( $charset ) {
49 protected function mysqlFreeResult( $res ) {
52 protected function mysqlFetchObject( $res ) {
55 protected function mysqlFetchArray( $res ) {
58 protected function mysqlNumRows( $res ) {
61 protected function mysqlNumFields( $res ) {
64 protected function mysqlFieldName( $res, $n ) {
67 protected function mysqlFieldType( $res, $n ) {
70 protected function mysqlDataSeek( $res, $row ) {
73 protected function mysqlError( $conn = null ) {
76 protected function mysqlFetchField( $res, $n ) {
79 protected function mysqlPing() {
82 protected function mysqlRealEscapeString( $s ) {
86 // From interface DatabaseType
90 function lastErrno() {
93 function affectedRows() {
96 function getServerVersion() {
100 class DatabaseMysqlBaseTest
extends MediaWikiTestCase
{
102 * @dataProvider provideDiapers
103 * @covers DatabaseMysqlBase::addIdentifierQuotes
105 public function testAddIdentifierQuotes( $expected, $in ) {
106 $db = new FakeDatabaseMysqlBase();
107 $quoted = $db->addIdentifierQuotes( $in );
108 $this->assertEquals( $expected, $quoted );
112 * Feeds testAddIdentifierQuotes
114 * Named per bug 20281 convention.
116 function provideDiapers() {
118 // Format: expected, input
121 // Yeah I really hate loosely typed PHP idiocies nowadays
124 // Dear codereviewer, guess what addIdentifierQuotes()
125 // will return with thoses:
126 array( '``', false ),
127 array( '`1`', true ),
129 // We never know what could happen
133 // Whatchout! Should probably use something more meaningful
134 array( "`'`", "'" ), # single quote
135 array( '`"`', '"' ), # double quote
136 array( '````', '`' ), # backtick
137 array( '`’`', '’' ), # apostrophe (look at your encyclopedia)
139 // sneaky NUL bytes are lurking everywhere
141 array( '`xyzzy`', "\0x\0y\0z\0z\0y\0" ),
145 self
::createUnicodeString( '`\u0001a\uFFFFb`' ),
146 self
::createUnicodeString( '\u0001a\uFFFFb' )
149 self
::createUnicodeString( '`\u0001\uFFFF`' ),
150 self
::createUnicodeString( '\u0001\u0000\uFFFF\u0000' )
153 array( '`メインページ`', 'メインページ' ),
154 array( '`Басты_бет`', 'Басты_бет' ),
157 array( '`Alix`', 'Alix' ), # while( ! $recovered ) { sleep(); }
158 array( '`Backtick: ```', 'Backtick: `' ),
159 array( '`This is a test`', 'This is a test' ),
163 private static function createUnicodeString( $str ) {
164 return json_decode( '"' . $str . '"' );
167 function getMockForViews() {
168 $db = $this->getMockBuilder( 'DatabaseMysql' )
169 ->disableOriginalConstructor()
170 ->setMethods( array( 'fetchRow', 'query' ) )
173 $db->expects( $this->any() )
175 ->with( $this->anything() )
177 $this->returnValue( null )
180 $db->expects( $this->any() )
181 ->method( 'fetchRow' )
182 ->with( $this->anything() )
183 ->will( $this->onConsecutiveCalls(
184 array( 'Tables_in_' => 'view1' ),
185 array( 'Tables_in_' => 'view2' ),
186 array( 'Tables_in_' => 'myview' ),
192 * @covers DatabaseMysqlBase::listViews
194 function testListviews() {
195 $db = $this->getMockForViews();
197 // The first call populate an internal cache of views
198 $this->assertEquals( array( 'view1', 'view2', 'myview' ),
200 $this->assertEquals( array( 'view1', 'view2', 'myview' ),
204 $this->assertEquals( array( 'view1', 'view2' ),
205 $db->listViews( 'view' ) );
206 $this->assertEquals( array( 'myview' ),
207 $db->listViews( 'my' ) );
208 $this->assertEquals( array(),
209 $db->listViews( 'UNUSED_PREFIX' ) );
210 $this->assertEquals( array( 'view1', 'view2', 'myview' ),
211 $db->listViews( '' ) );
215 * @covers DatabaseMysqlBase::isView
216 * @dataProvider provideViewExistanceChecks
218 function testIsView( $isView, $viewName ) {
219 $db = $this->getMockForViews();
223 $this->assertTrue( $db->isView( $viewName ),
224 "$viewName should be considered a view" );
228 $this->assertFalse( $db->isView( $viewName ),
229 "$viewName has not been defined as a view" );
235 function provideViewExistanceChecks() {
237 // format: whether it is a view, view name
238 array( true, 'view1' ),
239 array( true, 'view2' ),
240 array( true, 'myview' ),
242 array( false, 'user' ),
244 array( false, 'view10' ),
245 array( false, 'my' ),
246 array( false, 'OH_MY_GOD' ), # they killed kenny!
250 function testMasterPos() {
251 $pos1 = new MySQLMasterPos( 'db1034-bin.000976', '843431247' );
252 $pos2 = new MySQLMasterPos( 'db1034-bin.000976', '843431248' );
254 $this->assertTrue( $pos1->hasReached( $pos1 ) );
255 $this->assertTrue( $pos2->hasReached( $pos2 ) );
256 $this->assertTrue( $pos2->hasReached( $pos1 ) );
257 $this->assertFalse( $pos1->hasReached( $pos2 ) );
261 * @dataProvider provideLagAmounts
263 function testPtHeartbeat( $lag ) {
264 $db = $this->getMockBuilder( 'DatabaseMysql' )
265 ->disableOriginalConstructor()
267 'getLagDetectionMethod', 'getHeartbeatData', 'getMasterServerInfo' ) )
270 $db->expects( $this->any() )
271 ->method( 'getLagDetectionMethod' )
272 ->will( $this->returnValue( 'pt-heartbeat' ) );
274 $db->expects( $this->any() )
275 ->method( 'getMasterServerInfo' )
276 ->will( $this->returnValue( array( 'serverId' => 172, 'asOf' => time() ) ) );
278 // Fake the current time.
279 list( $nowSecFrac, $nowSec ) = explode( ' ', microtime() );
280 $now = (float)$nowSec +
(float)$nowSecFrac;
281 // Fake the heartbeat time.
282 // Work arounds for weak DataTime microseconds support.
283 $ptTime = $now - $lag;
284 $ptSec = (int)$ptTime;
285 $ptSecFrac = ( $ptTime - $ptSec );
286 $ptDateTime = new DateTime( "@$ptSec" );
287 $ptTimeISO = $ptDateTime->format( 'Y-m-d\TH:i:s' );
288 $ptTimeISO .= ltrim( number_format( $ptSecFrac, 6 ), '0' );
290 $db->expects( $this->any() )
291 ->method( 'getHeartbeatData' )
293 ->will( $this->returnValue( array( $ptTimeISO, $now ) ) );
295 $db->setLBInfo( 'clusterMasterHost', 'db1052' );
296 $lagEst = $db->getLag();
298 $this->assertGreaterThan( $lag - .010, $lagEst, "Correct heatbeat lag" );
299 $this->assertLessThan( $lag +
.010, $lagEst, "Correct heatbeat lag" );
302 function provideLagAmounts() {