diff options
Diffstat (limited to 'tests/phpunit/includes/db')
| -rw-r--r-- | tests/phpunit/includes/db/DatabaseMysqlBaseTest.php | 209 | ||||
| -rw-r--r-- | tests/phpunit/includes/db/DatabaseSQLTest.php | 666 | ||||
| -rw-r--r-- | tests/phpunit/includes/db/DatabaseSqliteTest.php | 141 | ||||
| -rw-r--r-- | tests/phpunit/includes/db/DatabaseTest.php | 75 | ||||
| -rw-r--r-- | tests/phpunit/includes/db/DatabaseTestHelper.php | 166 | ||||
| -rw-r--r-- | tests/phpunit/includes/db/ORMRowTest.php | 20 | ||||
| -rw-r--r-- | tests/phpunit/includes/db/ORMTableTest.php | 146 | ||||
| -rw-r--r-- | tests/phpunit/includes/db/TestORMRowTest.php | 75 | 
8 files changed, 1370 insertions, 128 deletions
| diff --git a/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php b/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php new file mode 100644 index 00000000..ba63c091 --- /dev/null +++ b/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php @@ -0,0 +1,209 @@ +<?php +/** + * Holds tests for DatabaseMysqlBase MediaWiki class. + * + * @section LICENSE + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @author Antoine Musso + * @author Bryan Davis + * @copyright © 2013 Antoine Musso + * @copyright © 2013 Bryan Davis + * @copyright © 2013 Wikimedia Foundation Inc. + */ + +/** + * Fake class around abstract class so we can call concrete methods. + */ +class FakeDatabaseMysqlBase extends DatabaseMysqlBase { +	// From DatabaseBase +	protected function closeConnection() {} +	protected function doQuery( $sql ) {} + +	// From DatabaseMysql +	protected function mysqlConnect( $realServer ) {} +	protected function mysqlFreeResult( $res ) {} +	protected function mysqlFetchObject( $res ) {} +	protected function mysqlFetchArray( $res ) {} +	protected function mysqlNumRows( $res ) {} +	protected function mysqlNumFields( $res ) {} +	protected function mysqlFieldName( $res, $n ) {} +	protected function mysqlDataSeek( $res, $row ) {} +	protected function mysqlError( $conn = null ) {} +	protected function mysqlFetchField( $res, $n ) {} +	protected function mysqlPing() {} + +	// From interface DatabaseType +	function insertId() {} +	function lastErrno() {} +	function affectedRows() {} +	function getServerVersion() {} +} + +class DatabaseMysqlBaseTest extends MediaWikiTestCase { + +	/** +	 * @dataProvider provideDiapers +	 * @covers DatabaseMysqlBase::addIdentifierQuotes +	 */ +	public function testAddIdentifierQuotes( $expected, $in ) { +		$db = new FakeDatabaseMysqlBase(); +		$quoted = $db->addIdentifierQuotes( $in ); +		$this->assertEquals($expected, $quoted); +	} + + +	/** +	 * Feeds testAddIdentifierQuotes +	 * +	 * Named per bug 20281 convention. +	 */ +	function provideDiapers() { +		return array( +			// Format: expected, input +			array( '``', '' ), + +			// Yeah I really hate loosely typed PHP idiocies nowadays +			array( '``', null ), + +			// Dear codereviewer, guess what addIdentifierQuotes() +			// will return with thoses: +			array( '``', false ), +			array( '`1`', true ), + +			// We never know what could happen +			array( '`0`', 0 ), +			array( '`1`', 1 ), + +			// Whatchout! Should probably use something more meaningful +			array( "`'`", "'" ),  # single quote +			array( '`"`', '"' ),  # double quote +			array( '````', '`' ), # backtick +			array( '`’`', '’' ),  # apostrophe (look at your encyclopedia) + +			// sneaky NUL bytes are lurking everywhere +			array( '``', "\0" ), +			array( '`xyzzy`', "\0x\0y\0z\0z\0y\0" ), + +			// unicode chars +			array( +				self::createUnicodeString( '`\u0001a\uFFFFb`' ), +				self::createUnicodeString( '\u0001a\uFFFFb' ) +			), +			array( +				self::createUnicodeString( '`\u0001\uFFFF`' ), +				self::createUnicodeString( '\u0001\u0000\uFFFF\u0000' ) +			), +			array( '`☃`', '☃' ), +			array( '`メインページ`', 'メインページ' ), +			array( '`Басты_бет`', 'Басты_бет' ), + +			// Real world: +			array( '`Alix`', 'Alix' ),  # while( ! $recovered ) { sleep(); } +			array( '`Backtick: ```', 'Backtick: `' ), +			array( '`This is a test`', 'This is a test' ), +		); +	} + +	private static function createUnicodeString($str) { +		return json_decode( '"' . $str . '"' ); +	} + +	function getMockForViews() { +		$db = $this->getMockBuilder( 'DatabaseMysql' ) +			->disableOriginalConstructor() +			->setMethods( array( 'fetchRow', 'query' ) ) +			->getMock(); + +		$db->expects( $this->any() ) +			->method( 'query' ) +			->with( $this->anything() ) +			->will( +				$this->returnValue( null ) +			); + +		$db->expects( $this->any() ) +			->method( 'fetchRow' ) +			->with( $this->anything() ) +			->will( $this->onConsecutiveCalls( +				array( 'Tables_in_' => 'view1' ), +				array( 'Tables_in_' => 'view2' ), +				array( 'Tables_in_' => 'myview' ), +				false  # no more rows +			)); +		return $db; +	} +	/** +	 * @covers DatabaseMysqlBase::listViews +	 */ +	function testListviews() { +		$db = $this->getMockForViews(); + +		// The first call populate an internal cache of views +		$this->assertEquals( array( 'view1', 'view2', 'myview'), +			$db->listViews() ); +		$this->assertEquals( array( 'view1', 'view2', 'myview'), +			$db->listViews() ); + +		// Prefix filtering +		$this->assertEquals( array( 'view1', 'view2' ), +			$db->listViews( 'view' ) ); +		$this->assertEquals( array( 'myview' ), +			$db->listViews( 'my' ) ); +		$this->assertEquals( array(), +			$db->listViews( 'UNUSED_PREFIX' ) ); +		$this->assertEquals( array( 'view1', 'view2', 'myview'), +			$db->listViews( '' ) ); +	} + +	/** +	 * @covers DatabaseMysqlBase::isView +	 * @dataProvider provideViewExistanceChecks +	 */ +	function testIsView( $isView, $viewName ) { +		$db = $this->getMockForViews(); + +		switch( $isView ) { +			case true: +				$this->assertTrue( $db->isView( $viewName ), +					"$viewName should be considered a view" ); +			break; + +			case false: +				$this->assertFalse( $db->isView( $viewName ), +					"$viewName has not been defined as a view" ); +			break; +		} + +	} + +	function provideViewExistanceChecks() { +		return array( +			// format: whether it is a view, view name +			array( true, 'view1' ), +			array( true, 'view2' ), +			array( true, 'myview' ), + +			array( false, 'user' ), + +			array( false, 'view10' ), +			array( false, 'my' ), +			array( false, 'OH_MY_GOD' ),  # they killed kenny! +		); +	} + +} diff --git a/tests/phpunit/includes/db/DatabaseSQLTest.php b/tests/phpunit/includes/db/DatabaseSQLTest.php index e37cd445..bdd567e7 100644 --- a/tests/phpunit/includes/db/DatabaseSQLTest.php +++ b/tests/phpunit/includes/db/DatabaseSQLTest.php @@ -2,34 +2,44 @@  /**   * Test the abstract database layer - * Using Mysql for the sql at the moment TODO - * - * @group Database + * This is a non DBMS depending test.   */  class DatabaseSQLTest extends MediaWikiTestCase { -	public function setUp() { -		// TODO support other DBMS or find another way to do it -		if( $this->db->getType() !== 'mysql' ) { -			$this->markTestSkipped( 'No mysql database' ); -		} +	/** +	 * @var DatabaseTestHelper +	 */ +	private $database; + +	protected function setUp() { +		parent::setUp(); +		$this->database = new DatabaseTestHelper( __CLASS__ ); +	} + +	protected function assertLastSql( $sqlText ) { +		$this->assertEquals( +			$this->database->getLastSqls(), +			$sqlText +		);  	}  	/** -	 * @dataProvider dataSelectSQLText +	 * @dataProvider provideSelect +	 * @covers DatabaseBase::select  	 */ -	function testSelectSQLText( $sql, $sqlText ) { -		$this->assertEquals( trim( $this->db->selectSQLText( -			isset( $sql['tables'] ) ? $sql['tables'] : array(), -			isset( $sql['fields'] ) ? $sql['fields'] : array(), +	public function testSelect( $sql, $sqlText ) { +		$this->database->select( +			$sql['tables'], +			$sql['fields'],  			isset( $sql['conds'] ) ? $sql['conds'] : array(),  			__METHOD__,  			isset( $sql['options'] ) ? $sql['options'] : array(),  			isset( $sql['join_conds'] ) ? $sql['join_conds'] : array() -		) ), $sqlText ); +		); +		$this->assertLastSql( $sqlText );  	} -	function dataSelectSQLText() { +	public static function provideSelect() {  		return array(  			array(  				array( @@ -37,9 +47,9 @@ class DatabaseSQLTest extends MediaWikiTestCase {  					'fields' => array( 'field', 'alias' => 'field2' ),  					'conds' => array( 'alias' => 'text' ),  				), -				"SELECT  field,field2 AS alias  " . -				"FROM `unittest_table`  " . -				"WHERE alias = 'text'" +				"SELECT field,field2 AS alias " . +					"FROM table " . +					"WHERE alias = 'text'"  			),  			array(  				array( @@ -48,11 +58,11 @@ class DatabaseSQLTest extends MediaWikiTestCase {  					'conds' => array( 'alias' => 'text' ),  					'options' => array( 'LIMIT' => 1, 'ORDER BY' => 'field' ),  				), -				"SELECT  field,field2 AS alias  " . -				"FROM `unittest_table`  " . -				"WHERE alias = 'text'  " . -				"ORDER BY field " . -				"LIMIT 1" +				"SELECT field,field2 AS alias " . +					"FROM table " . +					"WHERE alias = 'text' " . +					"ORDER BY field " . +					"LIMIT 1"  			),  			array(  				array( @@ -62,13 +72,13 @@ class DatabaseSQLTest extends MediaWikiTestCase {  					'options' => array( 'LIMIT' => 1, 'ORDER BY' => 'field' ),  					'join_conds' => array( 't2' => array(  						'LEFT JOIN', 'tid = t2.id' -					)), +					) ),  				), -				"SELECT  tid,field,field2 AS alias,t2.id  " . -				"FROM `unittest_table` LEFT JOIN `unittest_table2` `t2` ON ((tid = t2.id))  " . -				"WHERE alias = 'text'  " . -				"ORDER BY field " . -				"LIMIT 1" +				"SELECT tid,field,field2 AS alias,t2.id " . +					"FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " . +					"WHERE alias = 'text' " . +					"ORDER BY field " . +					"LIMIT 1"  			),  			array(  				array( @@ -78,13 +88,13 @@ class DatabaseSQLTest extends MediaWikiTestCase {  					'options' => array( 'LIMIT' => 1, 'GROUP BY' => 'field', 'HAVING' => 'COUNT(*) > 1' ),  					'join_conds' => array( 't2' => array(  						'LEFT JOIN', 'tid = t2.id' -					)), +					) ),  				), -				"SELECT  tid,field,field2 AS alias,t2.id  " . -				"FROM `unittest_table` LEFT JOIN `unittest_table2` `t2` ON ((tid = t2.id))  " . -				"WHERE alias = 'text'  " . -				"GROUP BY field HAVING COUNT(*) > 1 " . -				"LIMIT 1" +				"SELECT tid,field,field2 AS alias,t2.id " . +					"FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " . +					"WHERE alias = 'text' " . +					"GROUP BY field HAVING COUNT(*) > 1 " . +					"LIMIT 1"  			),  			array(  				array( @@ -94,29 +104,466 @@ class DatabaseSQLTest extends MediaWikiTestCase {  					'options' => array( 'LIMIT' => 1, 'GROUP BY' => array( 'field', 'field2' ), 'HAVING' => array( 'COUNT(*) > 1', 'field' => 1 ) ),  					'join_conds' => array( 't2' => array(  						'LEFT JOIN', 'tid = t2.id' -					)), +					) ), +				), +				"SELECT tid,field,field2 AS alias,t2.id " . +					"FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " . +					"WHERE alias = 'text' " . +					"GROUP BY field,field2 HAVING (COUNT(*) > 1) AND field = '1' " . +					"LIMIT 1" +			), +			array( +				array( +					'tables' => array( 'table' ), +					'fields' => array( 'alias' => 'field' ), +					'conds' => array( 'alias' => array( 1, 2, 3, 4 ) ), +				), +				"SELECT field AS alias " . +					"FROM table " . +					"WHERE alias IN ('1','2','3','4')" +			), +		); +	} + +	/** +	 * @dataProvider provideUpdate +	 * @covers DatabaseBase::update +	 */ +	public function testUpdate( $sql, $sqlText ) { +		$this->database->update( +			$sql['table'], +			$sql['values'], +			$sql['conds'], +			__METHOD__, +			isset( $sql['options'] ) ? $sql['options'] : array() +		); +		$this->assertLastSql( $sqlText ); +	} + +	public static function provideUpdate() { +		return array( +			array( +				array( +					'table' => 'table', +					'values' => array( 'field' => 'text', 'field2' => 'text2' ), +					'conds' => array( 'alias' => 'text' ), +				), +				"UPDATE table " . +					"SET field = 'text'" . +					",field2 = 'text2' " . +					"WHERE alias = 'text'" +			), +			array( +				array( +					'table' => 'table', +					'values' => array( 'field = other', 'field2' => 'text2' ), +					'conds' => array( 'id' => '1' ), +				), +				"UPDATE table " . +					"SET field = other" . +					",field2 = 'text2' " . +					"WHERE id = '1'" +			), +			array( +				array( +					'table' => 'table', +					'values' => array( 'field = other', 'field2' => 'text2' ), +					'conds' => '*', +				), +				"UPDATE table " . +					"SET field = other" . +					",field2 = 'text2'" +			), +		); +	} + +	/** +	 * @dataProvider provideDelete +	 * @covers DatabaseBase::delete +	 */ +	public function testDelete( $sql, $sqlText ) { +		$this->database->delete( +			$sql['table'], +			$sql['conds'], +			__METHOD__ +		); +		$this->assertLastSql( $sqlText ); +	} + +	public static function provideDelete() { +		return array( +			array( +				array( +					'table' => 'table', +					'conds' => array( 'alias' => 'text' ), +				), +				"DELETE FROM table " . +					"WHERE alias = 'text'" +			), +			array( +				array( +					'table' => 'table', +					'conds' => '*', +				), +				"DELETE FROM table" +			), +		); +	} + +	/** +	 * @dataProvider provideUpsert +	 * @covers DatabaseBase::upsert +	 */ +	public function testUpsert( $sql, $sqlText ) { +		$this->database->upsert( +			$sql['table'], +			$sql['rows'], +			$sql['uniqueIndexes'], +			$sql['set'], +			__METHOD__ +		); +		$this->assertLastSql( $sqlText ); +	} + +	public static function provideUpsert() { +		return array( +			array( +				array( +					'table' => 'upsert_table', +					'rows' => array( 'field' => 'text', 'field2' => 'text2' ), +					'uniqueIndexes' => array( 'field' ), +					'set' => array( 'field' => 'set' ), +				), +				"BEGIN; " . +					"UPDATE upsert_table " . +					"SET field = 'set' " . +					"WHERE ((field = 'text')); " . +					"INSERT IGNORE INTO upsert_table " . +					"(field,field2) " . +					"VALUES ('text','text2'); " . +					"COMMIT" +			), +		); +	} + +	/** +	 * @dataProvider provideDeleteJoin +	 * @covers DatabaseBase::deleteJoin +	 */ +	public function testDeleteJoin( $sql, $sqlText ) { +		$this->database->deleteJoin( +			$sql['delTable'], +			$sql['joinTable'], +			$sql['delVar'], +			$sql['joinVar'], +			$sql['conds'], +			__METHOD__ +		); +		$this->assertLastSql( $sqlText ); +	} + +	public static function provideDeleteJoin() { +		return array( +			array( +				array( +					'delTable' => 'table', +					'joinTable' => 'table_join', +					'delVar' => 'field', +					'joinVar' => 'field_join', +					'conds' => array( 'alias' => 'text' ), +				), +				"DELETE FROM table " . +					"WHERE field IN (" . +					"SELECT field_join FROM table_join WHERE alias = 'text'" . +					")" +			), +			array( +				array( +					'delTable' => 'table', +					'joinTable' => 'table_join', +					'delVar' => 'field', +					'joinVar' => 'field_join', +					'conds' => '*', +				), +				"DELETE FROM table " . +					"WHERE field IN (" . +					"SELECT field_join FROM table_join " . +					")" +			), +		); +	} + +	/** +	 * @dataProvider provideInsert +	 * @covers DatabaseBase::insert +	 */ +	public function testInsert( $sql, $sqlText ) { +		$this->database->insert( +			$sql['table'], +			$sql['rows'], +			__METHOD__, +			isset( $sql['options'] ) ? $sql['options'] : array() +		); +		$this->assertLastSql( $sqlText ); +	} + +	public static function provideInsert() { +		return array( +			array( +				array( +					'table' => 'table', +					'rows' => array( 'field' => 'text', 'field2' => 2 ), +				), +				"INSERT INTO table " . +					"(field,field2) " . +					"VALUES ('text','2')" +			), +			array( +				array( +					'table' => 'table', +					'rows' => array( 'field' => 'text', 'field2' => 2 ), +					'options' => 'IGNORE', +				), +				"INSERT IGNORE INTO table " . +					"(field,field2) " . +					"VALUES ('text','2')" +			), +			array( +				array( +					'table' => 'table', +					'rows' => array( +						array( 'field' => 'text', 'field2' => 2 ), +						array( 'field' => 'multi', 'field2' => 3 ), +					), +					'options' => 'IGNORE', +				), +				"INSERT IGNORE INTO table " . +					"(field,field2) " . +					"VALUES " . +					"('text','2')," . +					"('multi','3')" +			), +		); +	} + +	/** +	 * @dataProvider provideInsertSelect +	 * @covers DatabaseBase::insertSelect +	 */ +	public function testInsertSelect( $sql, $sqlText ) { +		$this->database->insertSelect( +			$sql['destTable'], +			$sql['srcTable'], +			$sql['varMap'], +			$sql['conds'], +			__METHOD__, +			isset( $sql['insertOptions'] ) ? $sql['insertOptions'] : array(), +			isset( $sql['selectOptions'] ) ? $sql['selectOptions'] : array() +		); +		$this->assertLastSql( $sqlText ); +	} + +	public static function provideInsertSelect() { +		return array( +			array( +				array( +					'destTable' => 'insert_table', +					'srcTable' => 'select_table', +					'varMap' => array( 'field_insert' => 'field_select', 'field' => 'field2' ), +					'conds' => '*', +				), +				"INSERT INTO insert_table " . +					"(field_insert,field) " . +					"SELECT field_select,field2 " . +					"FROM select_table" +			), +			array( +				array( +					'destTable' => 'insert_table', +					'srcTable' => 'select_table', +					'varMap' => array( 'field_insert' => 'field_select', 'field' => 'field2' ), +					'conds' => array( 'field' => 2 ), +				), +				"INSERT INTO insert_table " . +					"(field_insert,field) " . +					"SELECT field_select,field2 " . +					"FROM select_table " . +					"WHERE field = '2'" +			), +			array( +				array( +					'destTable' => 'insert_table', +					'srcTable' => 'select_table', +					'varMap' => array( 'field_insert' => 'field_select', 'field' => 'field2' ), +					'conds' => array( 'field' => 2 ), +					'insertOptions' => 'IGNORE', +					'selectOptions' => array( 'ORDER BY' => 'field' ),  				), -				"SELECT  tid,field,field2 AS alias,t2.id  " . -				"FROM `unittest_table` LEFT JOIN `unittest_table2` `t2` ON ((tid = t2.id))  " . -				"WHERE alias = 'text'  " . -				"GROUP BY field,field2 HAVING (COUNT(*) > 1) AND field = '1' " . -				"LIMIT 1" +				"INSERT IGNORE INTO insert_table " . +					"(field_insert,field) " . +					"SELECT field_select,field2 " . +					"FROM select_table " . +					"WHERE field = '2' " . +					"ORDER BY field"  			),  		);  	}  	/** -	 * @dataProvider dataConditional +	 * @dataProvider provideReplace +	 * @covers DatabaseBase::replace  	 */ -	function testConditional( $sql, $sqlText ) { -		$this->assertEquals( trim( $this->db->conditional( +	public function testReplace( $sql, $sqlText ) { +		$this->database->replace( +			$sql['table'], +			$sql['uniqueIndexes'], +			$sql['rows'], +			__METHOD__ +		); +		$this->assertLastSql( $sqlText ); +	} + +	public static function provideReplace() { +		return array( +			array( +				array( +					'table' => 'replace_table', +					'uniqueIndexes' => array( 'field' ), +					'rows' => array( 'field' => 'text', 'field2' => 'text2' ), +				), +				"DELETE FROM replace_table " . +					"WHERE ( field='text' ); " . +					"INSERT INTO replace_table " . +					"(field,field2) " . +					"VALUES ('text','text2')" +			), +			array( +				array( +					'table' => 'module_deps', +					'uniqueIndexes' => array( array( 'md_module', 'md_skin' ) ), +					'rows' => array( +						'md_module' => 'module', +						'md_skin' => 'skin', +						'md_deps' => 'deps', +					), +				), +				"DELETE FROM module_deps " . +					"WHERE ( md_module='module' AND md_skin='skin' ); " . +					"INSERT INTO module_deps " . +					"(md_module,md_skin,md_deps) " . +					"VALUES ('module','skin','deps')" +			), +			array( +				array( +					'table' => 'module_deps', +					'uniqueIndexes' => array( array( 'md_module', 'md_skin' ) ), +					'rows' => array( +						array( +							'md_module' => 'module', +							'md_skin' => 'skin', +							'md_deps' => 'deps', +						), array( +							'md_module' => 'module2', +							'md_skin' => 'skin2', +							'md_deps' => 'deps2', +						), +					), +				), +				"DELETE FROM module_deps " . +					"WHERE ( md_module='module' AND md_skin='skin' ); " . +					"INSERT INTO module_deps " . +					"(md_module,md_skin,md_deps) " . +					"VALUES ('module','skin','deps'); " . +					"DELETE FROM module_deps " . +					"WHERE ( md_module='module2' AND md_skin='skin2' ); " . +					"INSERT INTO module_deps " . +					"(md_module,md_skin,md_deps) " . +					"VALUES ('module2','skin2','deps2')" +			), +			array( +				array( +					'table' => 'module_deps', +					'uniqueIndexes' => array( 'md_module', 'md_skin' ), +					'rows' => array( +						array( +							'md_module' => 'module', +							'md_skin' => 'skin', +							'md_deps' => 'deps', +						), array( +							'md_module' => 'module2', +							'md_skin' => 'skin2', +							'md_deps' => 'deps2', +						), +					), +				), +				"DELETE FROM module_deps " . +					"WHERE ( md_module='module' ) OR ( md_skin='skin' ); " . +					"INSERT INTO module_deps " . +					"(md_module,md_skin,md_deps) " . +					"VALUES ('module','skin','deps'); " . +					"DELETE FROM module_deps " . +					"WHERE ( md_module='module2' ) OR ( md_skin='skin2' ); " . +					"INSERT INTO module_deps " . +					"(md_module,md_skin,md_deps) " . +					"VALUES ('module2','skin2','deps2')" +			), +			array( +				array( +					'table' => 'module_deps', +					'uniqueIndexes' => array(), +					'rows' => array( +						'md_module' => 'module', +						'md_skin' => 'skin', +						'md_deps' => 'deps', +					), +				), +				"INSERT INTO module_deps " . +					"(md_module,md_skin,md_deps) " . +					"VALUES ('module','skin','deps')" +			), +		); +	} + +	/** +	 * @dataProvider provideNativeReplace +	 * @covers DatabaseBase::nativeReplace +	 */ +	public function testNativeReplace( $sql, $sqlText ) { +		$this->database->nativeReplace( +			$sql['table'], +			$sql['rows'], +			__METHOD__ +		); +		$this->assertLastSql( $sqlText ); +	} + +	public static function provideNativeReplace() { +		return array( +			array( +				array( +					'table' => 'replace_table', +					'rows' => array( 'field' => 'text', 'field2' => 'text2' ), +				), +				"REPLACE INTO replace_table " . +					"(field,field2) " . +					"VALUES ('text','text2')" +			), +		); +	} + +	/** +	 * @dataProvider provideConditional +	 * @covers DatabaseBase::conditional +	 */ +	public function testConditional( $sql, $sqlText ) { +		$this->assertEquals( trim( $this->database->conditional(  			$sql['conds'],  			$sql['true'],  			$sql['false']  		) ), $sqlText );  	} -	function dataConditional() { +	public static function provideConditional() {  		return array(  			array(  				array( @@ -144,4 +591,131 @@ class DatabaseSQLTest extends MediaWikiTestCase {  			),  		);  	} -}
\ No newline at end of file + +	/** +	 * @dataProvider provideBuildConcat +	 * @covers DatabaseBase::buildConcat +	 */ +	public function testBuildConcat( $stringList, $sqlText ) { +		$this->assertEquals( trim( $this->database->buildConcat( +			$stringList +		) ), $sqlText ); +	} + +	public static function provideBuildConcat() { +		return array( +			array( +				array( 'field', 'field2' ), +				"CONCAT(field,field2)" +			), +			array( +				array( "'test'", 'field2' ), +				"CONCAT('test',field2)" +			), +		); +	} + +	/** +	 * @dataProvider provideBuildLike +	 * @covers DatabaseBase::buildLike +	 */ +	public function testBuildLike( $array, $sqlText ) { +		$this->assertEquals( trim( $this->database->buildLike( +			$array +		) ), $sqlText ); +	} + +	public static function provideBuildLike() { +		return array( +			array( +				'text', +				"LIKE 'text'" +			), +			array( +				array( 'text', new LikeMatch( '%' ) ), +				"LIKE 'text%'" +			), +			array( +				array( 'text', new LikeMatch( '%' ), 'text2' ), +				"LIKE 'text%text2'" +			), +			array( +				array( 'text', new LikeMatch( '_' ) ), +				"LIKE 'text_'" +			), +		); +	} + +	/** +	 * @dataProvider provideUnionQueries +	 * @covers DatabaseBase::unionQueries +	 */ +	public function testUnionQueries( $sql, $sqlText ) { +		$this->assertEquals( trim( $this->database->unionQueries( +			$sql['sqls'], +			$sql['all'] +		) ), $sqlText ); +	} + +	public static function provideUnionQueries() { +		return array( +			array( +				array( +					'sqls' => array( 'RAW SQL', 'RAW2SQL' ), +					'all' => true, +				), +				"(RAW SQL) UNION ALL (RAW2SQL)" +			), +			array( +				array( +					'sqls' => array( 'RAW SQL', 'RAW2SQL' ), +					'all' => false, +				), +				"(RAW SQL) UNION (RAW2SQL)" +			), +			array( +				array( +					'sqls' => array( 'RAW SQL', 'RAW2SQL', 'RAW3SQL' ), +					'all' => false, +				), +				"(RAW SQL) UNION (RAW2SQL) UNION (RAW3SQL)" +			), +		); +	} + +	/** +	 * @covers DatabaseBase::commit +	 */ +	public function testTransactionCommit() { +		$this->database->begin( __METHOD__ ); +		$this->database->commit( __METHOD__ ); +		$this->assertLastSql( 'BEGIN; COMMIT' ); +	} + +	/** +	 * @covers DatabaseBase::rollback +	 */ +	public function testTransactionRollback() { +		$this->database->begin( __METHOD__ ); +		$this->database->rollback( __METHOD__ ); +		$this->assertLastSql( 'BEGIN; ROLLBACK' ); +	} + +	/** +	 * @covers DatabaseBase::dropTable +	 */ +	public function testDropTable() { +		$this->database->setExistingTables( array( 'table' ) ); +		$this->database->dropTable( 'table', __METHOD__ ); +		$this->assertLastSql( 'DROP TABLE table' ); +	} + +	/** +	 * @covers DatabaseBase::dropTable +	 */ +	public function testDropNonExistingTable() { +		$this->assertFalse( +			$this->database->dropTable( 'non_existing', __METHOD__ ) +		); +	} +} diff --git a/tests/phpunit/includes/db/DatabaseSqliteTest.php b/tests/phpunit/includes/db/DatabaseSqliteTest.php index d226598b..70ee9465 100644 --- a/tests/phpunit/includes/db/DatabaseSqliteTest.php +++ b/tests/phpunit/includes/db/DatabaseSqliteTest.php @@ -3,16 +3,20 @@  class MockDatabaseSqlite extends DatabaseSqliteStandalone {  	var $lastQuery; -	function __construct( ) { +	function __construct() {  		parent::__construct( ':memory:' );  	}  	function query( $sql, $fname = '', $tempIgnore = false ) {  		$this->lastQuery = $sql; +  		return true;  	} -	function replaceVars( $s ) { +	/** +	 * Override parent visibility to public +	 */ +	public function replaceVars( $s ) {  		return parent::replaceVars( $s );  	}  } @@ -20,11 +24,18 @@ class MockDatabaseSqlite extends DatabaseSqliteStandalone {  /**   * @group sqlite   * @group Database + * @group medium   */  class DatabaseSqliteTest extends MediaWikiTestCase { + +	/** +	 * @var MockDatabaseSqlite +	 */  	var $db; -	public function setUp() { +	protected function setUp() { +		parent::setUp(); +  		if ( !Sqlite::isPresent() ) {  			$this->markTestSkipped( 'No SQLite support detected' );  		} @@ -42,8 +53,8 @@ class DatabaseSqliteTest extends MediaWikiTestCase {  	private function assertResultIs( $expected, $res ) {  		$this->assertNotNull( $res );  		$i = 0; -		foreach( $res as $row ) { -			foreach( $expected[$i] as $key => $value ) { +		foreach ( $res as $row ) { +			foreach ( $expected[$i] as $key => $value ) {  				$this->assertTrue( isset( $row->$key ) );  				$this->assertEquals( $value, $row->$key );  			} @@ -52,44 +63,97 @@ class DatabaseSqliteTest extends MediaWikiTestCase {  		$this->assertEquals( count( $expected ), $i, 'Unexpected number of rows' );  	} +	public static function provideAddQuotes() { +		return array( +			array( // #0: empty +				'', "''" +			), +			array( // #1: simple +				'foo bar', "'foo bar'" +			), +			array( // #2: including quote +				'foo\'bar', "'foo''bar'" +			), +			array( // #3: including \0 (must be represented as hex, per https://bugs.php.net/bug.php?id=63419) +				"x\0y", +				"x'780079'", +			), +			array( // #4: blob object (must be represented as hex) +				new Blob( "hello" ), +				"x'68656c6c6f'", +			), +		); +	} + +	/** +	 * @dataProvider provideAddQuotes() +	 * @covers DatabaseSqlite::addQuotes +	 */ +	public function testAddQuotes( $value, $expected ) { +		// check quoting +		$db = new DatabaseSqliteStandalone( ':memory:' ); +		$this->assertEquals( $expected, $db->addQuotes( $value ), 'string not quoted as expected' ); + +		// ok, quoting works as expected, now try a round trip. +		$re = $db->query( 'select ' . $db->addQuotes( $value ) ); + +		$this->assertTrue( $re !== false, 'query failed' ); + +		if ( $row = $re->fetchRow() ) { +			if ( $value instanceof Blob ) { +				$value = $value->fetch(); +			} + +			$this->assertEquals( $value, $row[0], 'string mangled by the database' ); +		} else { +			$this->fail( 'query returned no result' ); +		} +	} + +	/** +	 * @covers DatabaseSqlite::replaceVars +	 */  	public function testReplaceVars() {  		$this->assertEquals( 'foo', $this->replaceVars( 'foo' ), "Don't break anything accidentally" );  		$this->assertEquals( "CREATE TABLE /**/foo (foo_key INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " -			. "foo_bar TEXT, foo_name TEXT NOT NULL DEFAULT '', foo_int INTEGER, foo_int2 INTEGER );", +				. "foo_bar TEXT, foo_name TEXT NOT NULL DEFAULT '', foo_int INTEGER, foo_int2 INTEGER );",  			$this->replaceVars( "CREATE TABLE /**/foo (foo_key int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,  			foo_bar char(13), foo_name varchar(255) binary NOT NULL DEFAULT '', foo_int tinyint ( 8 ), foo_int2 int(16) ) ENGINE=MyISAM;" ) -			); +		);  		$this->assertEquals( "CREATE TABLE foo ( foo1 REAL, foo2 REAL, foo3 REAL );",  			$this->replaceVars( "CREATE TABLE foo ( foo1 FLOAT, foo2 DOUBLE( 1,10), foo3 DOUBLE PRECISION );" ) -			); +		);  		$this->assertEquals( "CREATE TABLE foo ( foo_binary1 BLOB, foo_binary2 BLOB );",  			$this->replaceVars( "CREATE TABLE foo ( foo_binary1 binary(16), foo_binary2 varbinary(32) );" ) -			); +		);  		$this->assertEquals( "CREATE TABLE text ( text_foo TEXT );",  			$this->replaceVars( "CREATE TABLE text ( text_foo tinytext );" ),  			'Table name changed' -			); +		);  		$this->assertEquals( "CREATE TABLE foo ( foobar INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL );", -			$this->replaceVars("CREATE TABLE foo ( foobar INT PRIMARY KEY NOT NULL AUTO_INCREMENT );" ) -			); +			$this->replaceVars( "CREATE TABLE foo ( foobar INT PRIMARY KEY NOT NULL AUTO_INCREMENT );" ) +		);  		$this->assertEquals( "CREATE TABLE foo ( foobar INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL );", -			$this->replaceVars("CREATE TABLE foo ( foobar INT PRIMARY KEY AUTO_INCREMENT NOT NULL );" ) -			); +			$this->replaceVars( "CREATE TABLE foo ( foobar INT PRIMARY KEY AUTO_INCREMENT NOT NULL );" ) +		);  		$this->assertEquals( "CREATE TABLE enums( enum1 TEXT, myenum TEXT)",  			$this->replaceVars( "CREATE TABLE enums( enum1 ENUM('A', 'B'), myenum ENUM ('X', 'Y'))" ) -			); +		);  		$this->assertEquals( "ALTER TABLE foo ADD COLUMN foo_bar INTEGER DEFAULT 42",  			$this->replaceVars( "ALTER TABLE foo\nADD COLUMN foo_bar int(10) unsigned DEFAULT 42" ) -			); +		);  	} +	/** +	 * @covers DatabaseSqlite::tableName +	 */  	public function testTableName() {  		// @todo Moar!  		$db = new DatabaseSqliteStandalone( ':memory:' ); @@ -100,6 +164,9 @@ class DatabaseSqliteTest extends MediaWikiTestCase {  		$this->assertEquals( 'foobar', $db->tableName( 'bar' ) );  	} +	/** +	 * @covers DatabaseSqlite::duplicateTableStructure +	 */  	public function testDuplicateTableStructure() {  		$db = new DatabaseSqliteStandalone( ':memory:' );  		$db->query( 'CREATE TABLE foo(foo, barfoo)' ); @@ -121,6 +188,9 @@ class DatabaseSqliteTest extends MediaWikiTestCase {  		);  	} +	/** +	 * @covers DatabaseSqlite::duplicateTableStructure +	 */  	public function testDuplicateTableStructureVirtual() {  		$db = new DatabaseSqliteStandalone( ':memory:' );  		if ( $db->getFulltextSearchModule() != 'FTS3' ) { @@ -141,6 +211,9 @@ class DatabaseSqliteTest extends MediaWikiTestCase {  		);  	} +	/** +	 * @covers DatabaseSqlite::deleteJoin +	 */  	public function testDeleteJoin() {  		$db = new DatabaseSqliteStandalone( ':memory:' );  		$db->query( 'CREATE TABLE a (a_1)', __METHOD__ ); @@ -180,10 +253,10 @@ class DatabaseSqliteTest extends MediaWikiTestCase {  	/**  	 * Runs upgrades of older databases and compares results with current schema -	 * @todo: currently only checks list of tables +	 * @todo Currently only checks list of tables  	 */  	public function testUpgrades() { -		global $IP, $wgVersion; +		global $IP, $wgVersion, $wgProfileToDatabase;  		// Versions tested  		$versions = array( @@ -202,6 +275,9 @@ class DatabaseSqliteTest extends MediaWikiTestCase {  		$currentDB = new DatabaseSqliteStandalone( ':memory:' );  		$currentDB->sourceFile( "$IP/maintenance/tables.sql" ); +		if ( $wgProfileToDatabase ) { +			$currentDB->sourceFile( "$IP/maintenance/sqlite/archives/patch-profiling.sql" ); +		}  		$currentTables = $this->getTables( $currentDB );  		sort( $currentTables ); @@ -250,13 +326,19 @@ class DatabaseSqliteTest extends MediaWikiTestCase {  		}  	} +	/** +	 * @covers DatabaseSqlite::insertId +	 */  	public function testInsertIdType() {  		$db = new DatabaseSqliteStandalone( ':memory:' ); -		$this->assertInstanceOf( 'ResultWrapper', -			$db->query( 'CREATE TABLE a ( a_1 )', __METHOD__ ), "Database creationg" ); -		$this->assertTrue( $db->insert( 'a', array( 'a_1' => 10 ), __METHOD__ ), -			"Insertion worked" ); -		$this->assertEquals( "integer", gettype( $db->insertId() ), "Actual typecheck" ); + +		$databaseCreation = $db->query( 'CREATE TABLE a ( a_1 )', __METHOD__ ); +		$this->assertInstanceOf( 'ResultWrapper', $databaseCreation, "Database creation" ); + +		$insertion = $db->insert( 'a', array( 'a_1' => 10 ), __METHOD__ ); +		$this->assertTrue( $insertion, "Insertion worked" ); + +		$this->assertInternalType( 'integer', $db->insertId(), "Actual typecheck" );  		$this->assertTrue( $db->close(), "closing database" );  	} @@ -272,12 +354,14 @@ class DatabaseSqliteTest extends MediaWikiTestCase {  		$db->sourceFile( "$IP/tests/phpunit/data/db/sqlite/tables-$version.sql" );  		$updater = DatabaseUpdater::newForDB( $db, false, $maint );  		$updater->doUpdates( array( 'core' ) ); +  		return $db;  	}  	private function getTables( $db ) {  		$list = array_flip( $db->listTables() );  		$excluded = array( +			'external_user', // removed from core in 1.22  			'math', // moved out of core in 1.18  			'trackbacks', // removed from core in 1.19  			'searchindex', @@ -293,6 +377,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase {  		}  		$list = array_flip( $list );  		sort( $list ); +  		return $list;  	} @@ -304,6 +389,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase {  			$cols[$col->name] = $col;  		}  		ksort( $cols ); +  		return $cols;  	} @@ -321,6 +407,15 @@ class DatabaseSqliteTest extends MediaWikiTestCase {  			$indexes[$index->name] = $index;  		}  		ksort( $indexes ); +  		return $indexes;  	} + +	public function testCaseInsensitiveLike() { +		// TODO: Test this for all databases +		$db = new DatabaseSqliteStandalone( ':memory:' ); +		$res = $db->query( 'SELECT "a" LIKE "A" AS a' ); +		$row = $res->fetchRow(); +		$this->assertFalse( (bool)$row['a'] ); +	}  } diff --git a/tests/phpunit/includes/db/DatabaseTest.php b/tests/phpunit/includes/db/DatabaseTest.php index 379ffb17..301fc990 100644 --- a/tests/phpunit/includes/db/DatabaseTest.php +++ b/tests/phpunit/includes/db/DatabaseTest.php @@ -5,20 +5,28 @@   * @group DatabaseBase   */  class DatabaseTest extends MediaWikiTestCase { -	var $db, $functionTest = false; +	/** +	 * @var DatabaseBase +	 */ +	var $db; +	var $functionTest = false; -	function setUp() { +	protected function setUp() { +		parent::setUp();  		$this->db = wfGetDB( DB_MASTER );  	} -	function tearDown() { +	protected function tearDown() { +		parent::tearDown();  		if ( $this->functionTest ) {  			$this->dropFunctions();  			$this->functionTest = false;  		}  	} - -	function testAddQuotesNull() { +	/** +	 * @covers DatabaseBase::dropTable +	 */ +	public function testAddQuotesNull() {  		$check = "NULL";  		if ( $this->db->getType() === 'sqlite' || $this->db->getType() === 'oracle' ) {  			$check = "''"; @@ -26,7 +34,7 @@ class DatabaseTest extends MediaWikiTestCase {  		$this->assertEquals( $check, $this->db->addQuotes( null ) );  	} -	function testAddQuotesInt() { +	public function testAddQuotesInt() {  		# returning just "1234" should be ok too, though...  		# maybe  		$this->assertEquals( @@ -34,20 +42,20 @@ class DatabaseTest extends MediaWikiTestCase {  			$this->db->addQuotes( 1234 ) );  	} -	function testAddQuotesFloat() { +	public function testAddQuotesFloat() {  		# returning just "1234.5678" would be ok too, though  		$this->assertEquals(  			"'1234.5678'",  			$this->db->addQuotes( 1234.5678 ) );  	} -	function testAddQuotesString() { +	public function testAddQuotesString() {  		$this->assertEquals(  			"'string'",  			$this->db->addQuotes( 'string' ) );  	} -	function testAddQuotesStringQuote() { +	public function testAddQuotesStringQuote() {  		$check = "'string''s cause trouble'";  		if ( $this->db->getType() === 'mysql' ) {  			$check = "'string\'s cause trouble'"; @@ -82,36 +90,46 @@ class DatabaseTest extends MediaWikiTestCase {  			$quote = '';  		} elseif ( $this->db->getType() === 'mysql' ) {  			$quote = '`'; +		} elseif ( $this->db->getType() === 'oracle' ) { +			$quote = '/*Q*/';  		} else {  			$quote = '"';  		}  		if ( $database !== null ) { -			$database = $quote . $database . $quote . '.'; +			if ( $this->db->getType() === 'oracle' ) { +				$database = $quote . $database . '.'; +			} else { +				$database = $quote . $database . $quote . '.'; +			}  		}  		if ( $prefix === null ) {  			$prefix = $this->dbPrefix();  		} -		return $database . $quote . $prefix . $table . $quote; +		if ( $this->db->getType() === 'oracle' ) { +			return strtoupper($database . $quote . $prefix . $table); +		} else { +			return $database . $quote . $prefix . $table . $quote; +		}  	} -	function testTableNameLocal() { +	public function testTableNameLocal() {  		$this->assertEquals(  			$this->prefixAndQuote( 'tablename' ),  			$this->db->tableName( 'tablename' )  		);  	} -	function testTableNameRawLocal() { +	public function testTableNameRawLocal() {  		$this->assertEquals(  			$this->prefixAndQuote( 'tablename', null, null, 'raw' ),  			$this->db->tableName( 'tablename', 'raw' )  		);  	} -	function testTableNameShared() { +	public function testTableNameShared() {  		$this->assertEquals(  			$this->prefixAndQuote( 'tablename', 'sharedatabase', 'sh_' ),  			$this->getSharedTableName( 'tablename', 'sharedatabase', 'sh_' ) @@ -123,7 +141,7 @@ class DatabaseTest extends MediaWikiTestCase {  		);  	} -	function testTableNameRawShared() { +	public function testTableNameRawShared() {  		$this->assertEquals(  			$this->prefixAndQuote( 'tablename', 'sharedatabase', 'sh_', 'raw' ),  			$this->getSharedTableName( 'tablename', 'sharedatabase', 'sh_', 'raw' ) @@ -135,21 +153,21 @@ class DatabaseTest extends MediaWikiTestCase {  		);  	} -	function testTableNameForeign() { +	public function testTableNameForeign() {  		$this->assertEquals(  			$this->prefixAndQuote( 'tablename', 'databasename', '' ),  			$this->db->tableName( 'databasename.tablename' )  		);  	} -	function testTableNameRawForeign() { +	public function testTableNameRawForeign() {  		$this->assertEquals(  			$this->prefixAndQuote( 'tablename', 'databasename', '', 'raw' ),  			$this->db->tableName( 'databasename.tablename', 'raw' )  		);  	} -	function testFillPreparedEmpty() { +	public function testFillPreparedEmpty() {  		$sql = $this->db->fillPrepared(  			'SELECT * FROM interwiki', array() );  		$this->assertEquals( @@ -157,7 +175,7 @@ class DatabaseTest extends MediaWikiTestCase {  			$sql );  	} -	function testFillPreparedQuestion() { +	public function testFillPreparedQuestion() {  		$sql = $this->db->fillPrepared(  			'SELECT * FROM cur WHERE cur_namespace=? AND cur_title=?',  			array( 4, "Snicker's_paradox" ) ); @@ -169,7 +187,7 @@ class DatabaseTest extends MediaWikiTestCase {  		$this->assertEquals( $check, $sql );  	} -	function testFillPreparedBang() { +	public function testFillPreparedBang() {  		$sql = $this->db->fillPrepared(  			'SELECT user_id FROM ! WHERE user_name=?',  			array( '"user"', "Slash's Dot" ) ); @@ -181,7 +199,7 @@ class DatabaseTest extends MediaWikiTestCase {  		$this->assertEquals( $check, $sql );  	} -	function testFillPreparedRaw() { +	public function testFillPreparedRaw() {  		$sql = $this->db->fillPrepared(  			"SELECT * FROM cur WHERE cur_title='This_\\&_that,_WTF\\?\\!'",  			array( '"user"', "Slash's Dot" ) ); @@ -190,10 +208,7 @@ class DatabaseTest extends MediaWikiTestCase {  			$sql );  	} -	/** -	 * @group Broken -	 */ -	function testStoredFunctions() { +	public function testStoredFunctions() {  		if ( !in_array( wfGetDB( DB_MASTER )->getType(), array( 'mysql', 'postgres' ) ) ) {  			$this->markTestSkipped( 'MySQL or Postgres required' );  		} @@ -207,9 +222,13 @@ class DatabaseTest extends MediaWikiTestCase {  	private function dropFunctions() {  		$this->db->query( 'DROP FUNCTION IF EXISTS mw_test_function' -			. ( $this->db->getType() == 'postgres'  ? '()' : '' ) +				. ( $this->db->getType() == 'postgres' ? '()' : '' )  		);  	} -} - +	public function testUnknownTableCorruptsResults() { +		$res = $this->db->select( 'page', '*', array( 'page_id' => 1 ) ); +		$this->assertFalse( $this->db->tableExists( 'foobarbaz' ) ); +		$this->assertInternalType( 'int', $res->numRows() ); +	} +} diff --git a/tests/phpunit/includes/db/DatabaseTestHelper.php b/tests/phpunit/includes/db/DatabaseTestHelper.php new file mode 100644 index 00000000..790f273c --- /dev/null +++ b/tests/phpunit/includes/db/DatabaseTestHelper.php @@ -0,0 +1,166 @@ +<?php + +/** + * Helper for testing the methods from the DatabaseBase class + * @since 1.22 + */ +class DatabaseTestHelper extends DatabaseBase { + +	/** +	 * __CLASS__ of the test suite, +	 * used to determine, if the function name is passed every time to query() +	 */ +	protected $testName = array(); + +	/** +	 * Array of lastSqls passed to query(), +	 * This is an array since some methods in DatabaseBase can do more than one +	 * query. Cleared when calling getLastSqls(). +	 */ +	protected $lastSqls = array(); + +	/** +	 * Array of tables to be considered as existing by tableExist() +	 * Use setExistingTables() to alter. +	 */ +	protected $tablesExists; + +	public function __construct( $testName ) { +		$this->testName = $testName; +	} + +	/** +	 * Returns SQL queries grouped by '; ' +	 * Clear the list of queries that have been done so far. +	 */ +	public function getLastSqls() { +		$lastSqls = implode( '; ', $this->lastSqls ); +		$this->lastSqls = array(); + +		return $lastSqls; +	} + +	public function setExistingTables( $tablesExists ) { +		$this->tablesExists = (array)$tablesExists; +	} + +	protected function addSql( $sql ) { +		// clean up spaces before and after some words and the whole string +		$this->lastSqls[] = trim( preg_replace( +			'/\s{2,}(?=FROM|WHERE|GROUP BY|ORDER BY|LIMIT)|(?<=SELECT|INSERT|UPDATE)\s{2,}/', +			' ', $sql +		) ); +	} + +	protected function checkFunctionName( $fname ) { +		if ( substr( $fname, 0, strlen( $this->testName ) ) !== $this->testName ) { +			throw new MWException( 'function name does not start with test class. ' . +				$fname . ' vs. ' . $this->testName . '. ' . +				'Please provide __METHOD__ to database methods.' ); +		} +	} + +	function strencode( $s ) { +		// Choose apos to avoid handling of escaping double quotes in quoted text +		return str_replace( "'", "\'", $s ); +	} + +	public function addIdentifierQuotes( $s ) { +		// no escaping to avoid handling of double quotes in quoted text +		return $s; +	} + +	public function query( $sql, $fname = '', $tempIgnore = false ) { +		$this->checkFunctionName( $fname ); +		$this->addSql( $sql ); + +		return parent::query( $sql, $fname, $tempIgnore ); +	} + +	public function tableExists( $table, $fname = __METHOD__ ) { +		$this->checkFunctionName( $fname ); + +		return in_array( $table, (array)$this->tablesExists ); +	} + +	// Redeclare parent method to make it public +	public function nativeReplace( $table, $rows, $fname ) { +		return parent::nativeReplace( $table, $rows, $fname ); +	} + +	function getType() { +		return 'test'; +	} + +	function open( $server, $user, $password, $dbName ) { +		return false; +	} + +	function fetchObject( $res ) { +		return false; +	} + +	function fetchRow( $res ) { +		return false; +	} + +	function numRows( $res ) { +		return -1; +	} + +	function numFields( $res ) { +		return -1; +	} + +	function fieldName( $res, $n ) { +		return 'test'; +	} + +	function insertId() { +		return -1; +	} + +	function dataSeek( $res, $row ) { +		/* nop */ +	} + +	function lastErrno() { +		return -1; +	} + +	function lastError() { +		return 'test'; +	} + +	function fieldInfo( $table, $field ) { +		return false; +	} + +	function indexInfo( $table, $index, $fname = 'Database::indexInfo' ) { +		return false; +	} + +	function affectedRows() { +		return -1; +	} + +	function getSoftwareLink() { +		return 'test'; +	} + +	function getServerVersion() { +		return 'test'; +	} + +	function getServerInfo() { +		return 'test'; +	} + +	protected function closeConnection() { +		return false; +	} + +	protected function doQuery( $sql ) { +		return array(); +	} +} diff --git a/tests/phpunit/includes/db/ORMRowTest.php b/tests/phpunit/includes/db/ORMRowTest.php index 9dcaf2b3..27d4d0e8 100644 --- a/tests/phpunit/includes/db/ORMRowTest.php +++ b/tests/phpunit/includes/db/ORMRowTest.php @@ -43,19 +43,19 @@ abstract class ORMRowTest extends \MediaWikiTestCase {  	 * @since 1.20  	 * @return string  	 */ -	protected abstract function getRowClass(); +	abstract protected function getRowClass();  	/**  	 * @since 1.20  	 * @return IORMTable  	 */ -	protected abstract function getTableInstance(); +	abstract protected function getTableInstance();  	/**  	 * @since 1.20  	 * @return array  	 */ -	public abstract function constructorTestProvider(); +	abstract public function constructorTestProvider();  	/**  	 * @since 1.20 @@ -76,6 +76,7 @@ abstract class ORMRowTest extends \MediaWikiTestCase {  	 */  	protected function getRowInstance( array $data, $loadDefaults ) {  		$class = $this->getRowClass(); +  		return new $class( $this->getTableInstance(), $data, $loadDefaults );  	} @@ -136,7 +137,7 @@ abstract class ORMRowTest extends \MediaWikiTestCase {  	/**  	 * @dataProvider constructorTestProvider  	 */ -	public function testSave( array $data, $loadDefaults ) { +	public function testSaveAndRemove( array $data, $loadDefaults ) {  		$item = $this->getRowInstance( $data, $loadDefaults );  		$this->assertTrue( $item->save() ); @@ -151,15 +152,6 @@ abstract class ORMRowTest extends \MediaWikiTestCase {  		$this->assertEquals( $id, $item->getId() );  		$this->verifyFields( $item, $data ); -	} - -	/** -	 * @dataProvider constructorTestProvider -	 */ -	public function testRemove( array $data, $loadDefaults ) { -		$item = $this->getRowInstance( $data, $loadDefaults ); - -		$this->assertTrue( $item->save() );  		$this->assertTrue( $item->remove() ); @@ -231,4 +223,4 @@ abstract class ORMRowTest extends \MediaWikiTestCase {  	// TODO: test all of the methods! -}
\ No newline at end of file +} diff --git a/tests/phpunit/includes/db/ORMTableTest.php b/tests/phpunit/includes/db/ORMTableTest.php new file mode 100644 index 00000000..e583d1bc --- /dev/null +++ b/tests/phpunit/includes/db/ORMTableTest.php @@ -0,0 +1,146 @@ +<?php +/** + * Abstract class to construct tests for ORMTable deriving classes. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @since 1.21 + * + * @ingroup Test + * + * @group ORM + * @group Database + * + * @licence GNU GPL v2+ + * @author Jeroen De Dauw < jeroendedauw@gmail.com > + * @author Daniel Kinzler + */ +class ORMTableTest extends MediaWikiTestCase { + +	/** +	 * @since 1.21 +	 * @return string +	 */ +	protected function getTableClass() { +		return 'PageORMTableForTesting'; +	} + +	/** +	 * @since 1.21 +	 * @return IORMTable +	 */ +	public function getTable() { +		$class = $this->getTableClass(); + +		return $class::singleton(); +	} + +	/** +	 * @since 1.21 +	 * @return string +	 */ +	public function getRowClass() { +		return $this->getTable()->getRowClass(); +	} + +	/** +	 * @since 1.21 +	 */ +	public function testSingleton() { +		$class = $this->getTableClass(); + +		$this->assertInstanceOf( $class, $class::singleton() ); +		$this->assertTrue( $class::singleton() === $class::singleton() ); +	} + +	/** +	 * @since 1.21 +	 */ +	public function testIgnoreErrorsOverride() { +		$table = $this->getTable(); + +		$db = $table->getReadDbConnection(); +		$db->ignoreErrors( true ); + +		try { +			$table->rawSelect( "this is invalid" ); +			$this->fail( "An invalid query should trigger a DBQueryError even if ignoreErrors is enabled." ); +		} catch ( DBQueryError $ex ) { +			$this->assertTrue( true, "just making phpunit happy" ); +		} + +		$db->ignoreErrors( false ); +	} +} + +/** + * Dummy ORM table for testing, reading Title objects from the page table. + * + * @since 1.21 + */ + +class PageORMTableForTesting extends ORMTable { + +	/** +	 * @see ORMTable::getName +	 * +	 * @return string +	 */ +	public function getName() { +		return 'page'; +	} + +	/** +	 * @see ORMTable::getRowClass +	 * +	 * @return string +	 */ +	public function getRowClass() { +		return 'Title'; +	} + +	/** +	 * @see ORMTable::newRow +	 * +	 * @return IORMRow +	 */ +	public function newRow( array $data, $loadDefaults = false ) { +		return Title::makeTitle( $data['namespace'], $data['title'] ); +	} + +	/** +	 * @see ORMTable::getFields +	 * +	 * @return array +	 */ +	public function getFields() { +		return array( +			'id' => 'int', +			'namespace' => 'int', +			'title' => 'str', +		); +	} + +	/** +	 * @see ORMTable::getFieldPrefix +	 * +	 * @return string +	 */ +	protected function getFieldPrefix() { +		return 'page_'; +	} +} diff --git a/tests/phpunit/includes/db/TestORMRowTest.php b/tests/phpunit/includes/db/TestORMRowTest.php index afd1cb80..f65642b8 100644 --- a/tests/phpunit/includes/db/TestORMRowTest.php +++ b/tests/phpunit/includes/db/TestORMRowTest.php @@ -58,35 +58,62 @@ class TestORMRowTest extends ORMRowTest {  		return TestORMTable::singleton();  	} -	public function setUp() { +	protected function setUp() {  		parent::setUp();  		$dbw = wfGetDB( DB_MASTER );  		$isSqlite = $GLOBALS['wgDBtype'] === 'sqlite'; +		$isPostgres = $GLOBALS['wgDBtype'] === 'postgres';  		$idField = $isSqlite ? 'INTEGER' : 'INT unsigned';  		$primaryKey = $isSqlite ? 'PRIMARY KEY AUTOINCREMENT' : 'auto_increment PRIMARY KEY'; -		$dbw->query( -			'CREATE TABLE IF NOT EXISTS ' . $dbw->tableName( 'orm_test' ) . '( -				test_id                    ' . $idField . '        NOT NULL ' . $primaryKey . ', -				test_name                  VARCHAR(255)        NOT NULL, -				test_age                   TINYINT unsigned    NOT NULL, -				test_height                FLOAT               NOT NULL, -				test_awesome               TINYINT unsigned    NOT NULL, -				test_stuff                 BLOB                NOT NULL, -				test_moarstuff             BLOB                NOT NULL, -				test_time                  varbinary(14)       NOT NULL -			);' -		); +		if ( $isPostgres ) { +			$dbw->query( +				'CREATE TABLE IF NOT EXISTS ' . $dbw->tableName( 'orm_test' ) . "( +					test_id serial PRIMARY KEY, +					test_name TEXT NOT NULL DEFAULT '', +					test_age INTEGER NOT NULL DEFAULT 0, +					test_height REAL NOT NULL DEFAULT 0, +					test_awesome INTEGER NOT NULL DEFAULT 0, +					test_stuff BYTEA, +					test_moarstuff BYTEA, +					test_time TIMESTAMPTZ +					);", +					__METHOD__ +				); +		} else { +			$dbw->query( +				'CREATE TABLE IF NOT EXISTS ' . $dbw->tableName( 'orm_test' ) . '( +					test_id                    ' . $idField . '        NOT NULL ' . $primaryKey . ', +					test_name                  VARCHAR(255)        NOT NULL, +					test_age                   TINYINT unsigned    NOT NULL, +					test_height                FLOAT               NOT NULL, +					test_awesome               TINYINT unsigned    NOT NULL, +					test_stuff                 BLOB                NOT NULL, +					test_moarstuff             BLOB                NOT NULL, +					test_time                  varbinary(14)       NOT NULL +				);', +				__METHOD__ +			); +		} +	} + +	protected function tearDown() { +		$dbw = wfGetDB( DB_MASTER ); +		$dbw->dropTable( 'orm_test', __METHOD__ ); + +		parent::tearDown();  	}  	public function constructorTestProvider() { +		$dbw = wfGetDB( DB_MASTER );  		return array(  			array(  				array(  					'name' => 'Foobar', +					'time' => $dbw->timestamp( '20120101020202' ),  					'age' => 42,  					'height' => 9000.1,  					'awesome' => true, @@ -98,9 +125,25 @@ class TestORMRowTest extends ORMRowTest {  		);  	} +	/** +	 * @since 1.21 +	 * @return array +	 */ +	protected function getMockValues() { +		return array( +			'id' => 1, +			'str' => 'foobar4645645', +			'int' => 42, +			'float' => 4.2, +			'bool' => '', +			'array' => array( 42, 'foobar' ), +			'blob' => new stdClass() +		); +	}  } -class TestORMRow extends ORMRow {} +class TestORMRow extends ORMRow { +}  class TestORMTable extends ORMTable { @@ -155,7 +198,7 @@ class TestORMTable extends ORMTable {  			'awesome' => 'bool',  			'stuff' => 'array',  			'moarstuff' => 'blob', -			'time' => 'int', // TS_MW +			'time' => 'str', // TS_MW  		);  	} @@ -169,6 +212,4 @@ class TestORMTable extends ORMTable {  	protected function getFieldPrefix() {  		return 'test_';  	} - -  } | 
