summaryrefslogtreecommitdiff
path: root/tests/phpunit/includes/db
diff options
context:
space:
mode:
Diffstat (limited to 'tests/phpunit/includes/db')
-rw-r--r--tests/phpunit/includes/db/DatabaseMysqlBaseTest.php209
-rw-r--r--tests/phpunit/includes/db/DatabaseSQLTest.php666
-rw-r--r--tests/phpunit/includes/db/DatabaseSqliteTest.php141
-rw-r--r--tests/phpunit/includes/db/DatabaseTest.php75
-rw-r--r--tests/phpunit/includes/db/DatabaseTestHelper.php166
-rw-r--r--tests/phpunit/includes/db/ORMRowTest.php20
-rw-r--r--tests/phpunit/includes/db/ORMTableTest.php146
-rw-r--r--tests/phpunit/includes/db/TestORMRowTest.php75
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_';
}
-
-
}