Krinkle has uploaded a new change for review. ( https://gerrit.wikimedia.org/r/366773 )
Change subject: rdbms: Refactor DatabaseTest ...................................................................... rdbms: Refactor DatabaseTest * Move DatabaseTest and DatabaseSQLTest to libs, and remove MediaWikiTestCase dependency. * Refactor DatabaseTest to be a test of the Database abstract class, not of whatever current DB backend is configured by LocalSettings. - Remove most switches/conditionals and other tests for specific database backends. Move those to individual test classes for those backends instead. - Some tests appear to have been integration tests for the PHP driver and/or the db backend itself. Moved to a new DatabaseIntegrationTest. - Now that only the abstract Database is invoked, the test runs a bit faster (no real connections/queries). * Add missing @covers tags, and remove or fix broken ones (follows-up 26e52f0c491a4). TODO: * testAddQuotesNull for Oracle * testAddQuotesStringQuote for mysql Change-Id: I9dc4a558e701d00e95789e7eb8e02926783b65ad --- M tests/phpunit/includes/db/DatabaseSqliteTest.php D tests/phpunit/includes/db/DatabaseTest.php M tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php R tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php A tests/phpunit/includes/libs/rdbms/database/DatabaseTest.php A tests/phpunit/structure/DatabaseIntegrationTest.php 6 files changed, 443 insertions(+), 443 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core refs/changes/73/366773/1 diff --git a/tests/phpunit/includes/db/DatabaseSqliteTest.php b/tests/phpunit/includes/db/DatabaseSqliteTest.php index b90b1ad..ae61070 100644 --- a/tests/phpunit/includes/db/DatabaseSqliteTest.php +++ b/tests/phpunit/includes/db/DatabaseSqliteTest.php @@ -87,6 +87,10 @@ new Blob( "hello" ), "x'68656c6c6f'", ], + [ // #5: null + null, + "''", + ], ]; } diff --git a/tests/phpunit/includes/db/DatabaseTest.php b/tests/phpunit/includes/db/DatabaseTest.php deleted file mode 100644 index 45791e2..0000000 --- a/tests/phpunit/includes/db/DatabaseTest.php +++ /dev/null @@ -1,415 +0,0 @@ -<?php - -use Wikimedia\Rdbms\IDatabase; - -/** - * @group Database - * @group Database - */ -class DatabaseTest extends MediaWikiTestCase { - /** - * @var Database - */ - protected $db; - - private $functionTest = false; - - protected function setUp() { - parent::setUp(); - $this->db = wfGetDB( DB_MASTER ); - } - - protected function tearDown() { - parent::tearDown(); - if ( $this->functionTest ) { - $this->dropFunctions(); - $this->functionTest = false; - } - $this->db->restoreFlags( IDatabase::RESTORE_INITIAL ); - } - - /** - * @covers Database::dropTable - */ - public function testAddQuotesNull() { - $check = "NULL"; - if ( $this->db->getType() === 'sqlite' || $this->db->getType() === 'oracle' ) { - $check = "''"; - } - $this->assertEquals( $check, $this->db->addQuotes( null ) ); - } - - public function testAddQuotesInt() { - # returning just "1234" should be ok too, though... - # maybe - $this->assertEquals( - "'1234'", - $this->db->addQuotes( 1234 ) ); - } - - public function testAddQuotesFloat() { - # returning just "1234.5678" would be ok too, though - $this->assertEquals( - "'1234.5678'", - $this->db->addQuotes( 1234.5678 ) ); - } - - public function testAddQuotesString() { - $this->assertEquals( - "'string'", - $this->db->addQuotes( 'string' ) ); - } - - public function testAddQuotesStringQuote() { - $check = "'string''s cause trouble'"; - if ( $this->db->getType() === 'mysql' ) { - $check = "'string\'s cause trouble'"; - } - $this->assertEquals( - $check, - $this->db->addQuotes( "string's cause trouble" ) ); - } - - private function getSharedTableName( $table, $database, $prefix, $format = 'quoted' ) { - global $wgSharedDB, $wgSharedTables, $wgSharedPrefix, $wgSharedSchema; - - $this->db->setTableAliases( [ - $table => [ - 'dbname' => $database, - 'schema' => null, - 'prefix' => $prefix - ] - ] ); - - $ret = $this->db->tableName( $table, $format ); - - $this->db->setTableAliases( array_fill_keys( - $wgSharedDB ? $wgSharedTables : [], - [ - 'dbname' => $wgSharedDB, - 'schema' => $wgSharedSchema, - 'prefix' => $wgSharedPrefix - ] - ) ); - - return $ret; - } - - private function prefixAndQuote( $table, $database = null, $prefix = null, $format = 'quoted' ) { - if ( $this->db->getType() === 'sqlite' || $format !== 'quoted' ) { - $quote = ''; - } elseif ( $this->db->getType() === 'mysql' ) { - $quote = '`'; - } elseif ( $this->db->getType() === 'oracle' ) { - $quote = '/*Q*/'; - } else { - $quote = '"'; - } - - if ( $database !== null ) { - if ( $this->db->getType() === 'oracle' ) { - $database = $quote . $database . '.'; - } else { - $database = $quote . $database . $quote . '.'; - } - } - - if ( $prefix === null ) { - $prefix = $this->dbPrefix(); - } - - if ( $this->db->getType() === 'oracle' ) { - return strtoupper( $database . $quote . $prefix . $table ); - } else { - return $database . $quote . $prefix . $table . $quote; - } - } - - public function testTableNameLocal() { - $this->assertEquals( - $this->prefixAndQuote( 'tablename' ), - $this->db->tableName( 'tablename' ) - ); - } - - public function testTableNameRawLocal() { - $this->assertEquals( - $this->prefixAndQuote( 'tablename', null, null, 'raw' ), - $this->db->tableName( 'tablename', 'raw' ) - ); - } - - public function testTableNameShared() { - $this->assertEquals( - $this->prefixAndQuote( 'tablename', 'sharedatabase', 'sh_' ), - $this->getSharedTableName( 'tablename', 'sharedatabase', 'sh_' ) - ); - - $this->assertEquals( - $this->prefixAndQuote( 'tablename', 'sharedatabase', null ), - $this->getSharedTableName( 'tablename', 'sharedatabase', null ) - ); - } - - public function testTableNameRawShared() { - $this->assertEquals( - $this->prefixAndQuote( 'tablename', 'sharedatabase', 'sh_', 'raw' ), - $this->getSharedTableName( 'tablename', 'sharedatabase', 'sh_', 'raw' ) - ); - - $this->assertEquals( - $this->prefixAndQuote( 'tablename', 'sharedatabase', null, 'raw' ), - $this->getSharedTableName( 'tablename', 'sharedatabase', null, 'raw' ) - ); - } - - public function testTableNameForeign() { - $this->assertEquals( - $this->prefixAndQuote( 'tablename', 'databasename', '' ), - $this->db->tableName( 'databasename.tablename' ) - ); - } - - public function testTableNameRawForeign() { - $this->assertEquals( - $this->prefixAndQuote( 'tablename', 'databasename', '', 'raw' ), - $this->db->tableName( 'databasename.tablename', 'raw' ) - ); - } - - public function testStoredFunctions() { - if ( !in_array( wfGetDB( DB_MASTER )->getType(), [ 'mysql', 'postgres' ] ) ) { - $this->markTestSkipped( 'MySQL or Postgres required' ); - } - global $IP; - $this->dropFunctions(); - $this->functionTest = true; - $this->assertTrue( - $this->db->sourceFile( "$IP/tests/phpunit/data/db/{$this->db->getType()}/functions.sql" ) - ); - $res = $this->db->query( 'SELECT mw_test_function() AS test', __METHOD__ ); - $this->assertEquals( 42, $res->fetchObject()->test ); - } - - private function dropFunctions() { - $this->db->query( 'DROP FUNCTION IF EXISTS mw_test_function' - . ( $this->db->getType() == 'postgres' ? '()' : '' ) - ); - } - - public function testUnknownTableCorruptsResults() { - $res = $this->db->select( 'page', '*', [ 'page_id' => 1 ] ); - $this->assertFalse( $this->db->tableExists( 'foobarbaz' ) ); - $this->assertInternalType( 'int', $res->numRows() ); - } - - public function testTransactionIdle() { - $db = $this->db; - - $db->setFlag( DBO_TRX ); - $called = false; - $flagSet = null; - $db->onTransactionIdle( - function () use ( $db, &$flagSet, &$called ) { - $called = true; - $flagSet = $db->getFlag( DBO_TRX ); - }, - __METHOD__ - ); - $this->assertFalse( $flagSet, 'DBO_TRX off in callback' ); - $this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); - $this->assertTrue( $called, 'Callback reached' ); - - $db->clearFlag( DBO_TRX ); - $flagSet = null; - $db->onTransactionIdle( - function () use ( $db, &$flagSet ) { - $flagSet = $db->getFlag( DBO_TRX ); - }, - __METHOD__ - ); - $this->assertFalse( $flagSet, 'DBO_TRX off in callback' ); - $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); - - $db->clearFlag( DBO_TRX ); - $db->onTransactionIdle( - function () use ( $db ) { - $db->setFlag( DBO_TRX ); - }, - __METHOD__ - ); - $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); - } - - public function testTransactionResolution() { - $db = $this->db; - - $db->clearFlag( DBO_TRX ); - $db->begin( __METHOD__ ); - $called = false; - $db->onTransactionResolution( function () use ( $db, &$called ) { - $called = true; - $db->setFlag( DBO_TRX ); - } ); - $db->commit( __METHOD__ ); - $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); - $this->assertTrue( $called, 'Callback reached' ); - - $db->clearFlag( DBO_TRX ); - $db->begin( __METHOD__ ); - $called = false; - $db->onTransactionResolution( function () use ( $db, &$called ) { - $called = true; - $db->setFlag( DBO_TRX ); - } ); - $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS ); - $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); - $this->assertTrue( $called, 'Callback reached' ); - } - - /** - * @covers Database::setTransactionListener() - */ - public function testTransactionListener() { - $db = $this->db; - - $db->setTransactionListener( 'ping', function () use ( $db, &$called ) { - $called = true; - } ); - - $called = false; - $db->begin( __METHOD__ ); - $db->commit( __METHOD__ ); - $this->assertTrue( $called, 'Callback reached' ); - - $called = false; - $db->begin( __METHOD__ ); - $db->commit( __METHOD__ ); - $this->assertTrue( $called, 'Callback still reached' ); - - $called = false; - $db->begin( __METHOD__ ); - $db->rollback( __METHOD__ ); - $this->assertTrue( $called, 'Callback reached' ); - - $db->setTransactionListener( 'ping', null ); - $called = false; - $db->begin( __METHOD__ ); - $db->commit( __METHOD__ ); - $this->assertFalse( $called, 'Callback not reached' ); - } - - /** - * @covers Database::flushSnapshot() - */ - public function testFlushSnapshot() { - $db = $this->db; - - $db->flushSnapshot( __METHOD__ ); // ok - $db->flushSnapshot( __METHOD__ ); // ok - - $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR ); - $db->query( 'SELECT 1', __METHOD__ ); - $this->assertTrue( (bool)$db->trxLevel(), "Transaction started." ); - $db->flushSnapshot( __METHOD__ ); // ok - $db->restoreFlags( $db::RESTORE_PRIOR ); - - $this->assertFalse( (bool)$db->trxLevel(), "Transaction cleared." ); - } - - public function testGetScopedLock() { - $db = $this->db; - - $db->setFlag( DBO_TRX ); - try { - $this->badLockingMethodImplicit( $db ); - } catch ( RunTimeException $e ) { - $this->assertTrue( $db->trxLevel() > 0, "Transaction not committed." ); - } - $db->clearFlag( DBO_TRX ); - $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS ); - $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) ); - - try { - $this->badLockingMethodExplicit( $db ); - } catch ( RunTimeException $e ) { - $this->assertTrue( $db->trxLevel() > 0, "Transaction not committed." ); - } - $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS ); - $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) ); - } - - private function badLockingMethodImplicit( IDatabase $db ) { - $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 ); - $db->query( "SELECT 1" ); // trigger DBO_TRX - throw new RunTimeException( "Uh oh!" ); - } - - private function badLockingMethodExplicit( IDatabase $db ) { - $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 ); - $db->begin( __METHOD__ ); - throw new RunTimeException( "Uh oh!" ); - } - - /** - * @covers Database::getFlag( - * @covers Database::setFlag() - * @covers Database::restoreFlags() - */ - public function testFlagSetting() { - $db = $this->db; - $origTrx = $db->getFlag( DBO_TRX ); - $origSsl = $db->getFlag( DBO_SSL ); - - $origTrx - ? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR ) - : $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR ); - $this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) ); - - $origSsl - ? $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR ) - : $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR ); - $this->assertEquals( !$origSsl, $db->getFlag( DBO_SSL ) ); - - $db->restoreFlags( $db::RESTORE_INITIAL ); - $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) ); - $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) ); - - $origTrx - ? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR ) - : $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR ); - $origSsl - ? $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR ) - : $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR ); - - $db->restoreFlags(); - $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) ); - $this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) ); - - $db->restoreFlags(); - $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) ); - $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) ); - } - - /** - * @covers Database::tablePrefix() - * @covers Database::dbSchema() - */ - public function testMutators() { - $old = $this->db->tablePrefix(); - $this->assertType( 'string', $old, 'Prefix is string' ); - $this->assertEquals( $old, $this->db->tablePrefix(), "Prefix unchanged" ); - $this->assertEquals( $old, $this->db->tablePrefix( 'xxx' ) ); - $this->assertEquals( 'xxx', $this->db->tablePrefix(), "Prefix set" ); - $this->db->tablePrefix( $old ); - $this->assertNotEquals( 'xxx', $this->db->tablePrefix() ); - - $old = $this->db->dbSchema(); - $this->assertType( 'string', $old, 'Schema is string' ); - $this->assertEquals( $old, $this->db->dbSchema(), "Schema unchanged" ); - $this->assertEquals( $old, $this->db->dbSchema( 'xxx' ) ); - $this->assertEquals( 'xxx', $this->db->dbSchema(), "Schema set" ); - $this->db->dbSchema( $old ); - $this->assertNotEquals( 'xxx', $this->db->dbSchema() ); - } -} diff --git a/tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php b/tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php index 0d817fa..b564310 100644 --- a/tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php +++ b/tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php @@ -107,7 +107,7 @@ class DatabaseMysqlBaseTest extends PHPUnit_Framework_TestCase { /** * @dataProvider provideDiapers - * @covers DatabaseMysqlBase::addIdentifierQuotes + * @covers Wikimedia\Rdbms\DatabaseMysqlBase::addIdentifierQuotes */ public function testAddIdentifierQuotes( $expected, $in ) { $db = new FakeDatabaseMysqlBase(); @@ -189,7 +189,7 @@ } /** - * @covers DatabaseMysqlBase::listViews + * @covers Wikimedia\Rdbms\DatabaseMysqlBase::listViews */ public function testListviews() { $db = $this->getMockForViews(); @@ -210,7 +210,7 @@ /** * @dataProvider provideComparePositions - * @covers MySQLMasterPos + * @covers Wikimedia\Rdbms\MySQLMasterPos */ public function testHasReached( MySQLMasterPos $lowerPos, MySQLMasterPos $higherPos, $match ) { if ( $match ) { @@ -283,7 +283,7 @@ /** * @dataProvider provideChannelPositions - * @covers MySQLMasterPos + * @covers Wikimedia\Rdbms\MySQLMasterPos */ public function testChannelsMatch( MySQLMasterPos $pos1, MySQLMasterPos $pos2, $matches ) { $this->assertEquals( $matches, $pos1->channelsMatch( $pos2 ) ); @@ -317,8 +317,8 @@ /** * @dataProvider provideLagAmounts - * @covers DatabaseMysqlBase::getLag - * @covers DatabaseMysqlBase::getLagFromPtHeartbeat + * @covers Wikimedia\Rdbms\DatabaseMysqlBase::getLag + * @covers Wikimedia\Rdbms\DatabaseMysqlBase::getLagFromPtHeartbeat */ public function testPtHeartbeat( $lag ) { $db = $this->getMockBuilder( 'DatabaseMysqli' ) diff --git a/tests/phpunit/includes/db/DatabaseSQLTest.php b/tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php similarity index 95% rename from tests/phpunit/includes/db/DatabaseSQLTest.php rename to tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php index 2b587db..57666bd 100644 --- a/tests/phpunit/includes/db/DatabaseSQLTest.php +++ b/tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php @@ -3,10 +3,10 @@ use Wikimedia\Rdbms\LikeMatch; /** - * Test the abstract database layer - * This is a non DBMS depending test. + * Test the parts of the Database abstract class that deal + * with creating SQL text. */ -class DatabaseSQLTest extends MediaWikiTestCase { +class DatabaseSQLTest extends PHPUnit_Framework_TestCase { /** @var DatabaseTestHelper */ private $database; @@ -28,7 +28,7 @@ /** * @dataProvider provideSelect - * @covers Database::select + * @covers Wikimedia\Rdbms\Database::select */ public function testSelect( $sql, $sqlText ) { $this->database->select( @@ -134,7 +134,7 @@ /** * @dataProvider provideUpdate - * @covers Database::update + * @covers Wikimedia\Rdbms\Database::update */ public function testUpdate( $sql, $sqlText ) { $this->database->update( @@ -186,7 +186,7 @@ /** * @dataProvider provideDelete - * @covers Database::delete + * @covers Wikimedia\Rdbms\Database::delete */ public function testDelete( $sql, $sqlText ) { $this->database->delete( @@ -219,7 +219,7 @@ /** * @dataProvider provideUpsert - * @covers Database::upsert + * @covers Wikimedia\Rdbms\Database::upsert */ public function testUpsert( $sql, $sqlText ) { $this->database->upsert( @@ -255,7 +255,7 @@ /** * @dataProvider provideDeleteJoin - * @covers Database::deleteJoin + * @covers Wikimedia\Rdbms\Database::deleteJoin */ public function testDeleteJoin( $sql, $sqlText ) { $this->database->deleteJoin( @@ -302,7 +302,7 @@ /** * @dataProvider provideInsert - * @covers Database::insert + * @covers Wikimedia\Rdbms\Database::insert */ public function testInsert( $sql, $sqlText ) { $this->database->insert( @@ -355,7 +355,7 @@ /** * @dataProvider provideInsertSelect - * @covers Database::insertSelect + * @covers Wikimedia\Rdbms\Database::insertSelect */ public function testInsertSelect( $sql, $sqlTextNative, $sqlSelect, $sqlInsert ) { $this->database->insertSelect( @@ -466,7 +466,7 @@ /** * @dataProvider provideReplace - * @covers Database::replace + * @covers Wikimedia\Rdbms\Database::replace */ public function testReplace( $sql, $sqlText ) { $this->database->replace( @@ -581,7 +581,7 @@ /** * @dataProvider provideNativeReplace - * @covers Database::nativeReplace + * @covers Wikimedia\Rdbms\Database::nativeReplace */ public function testNativeReplace( $sql, $sqlText ) { $this->database->nativeReplace( @@ -608,7 +608,7 @@ /** * @dataProvider provideConditional - * @covers Database::conditional + * @covers Wikimedia\Rdbms\Database::conditional */ public function testConditional( $sql, $sqlText ) { $this->assertEquals( trim( $this->database->conditional( @@ -649,7 +649,7 @@ /** * @dataProvider provideBuildConcat - * @covers Database::buildConcat + * @covers Wikimedia\Rdbms\Database::buildConcat */ public function testBuildConcat( $stringList, $sqlText ) { $this->assertEquals( trim( $this->database->buildConcat( @@ -672,7 +672,7 @@ /** * @dataProvider provideBuildLike - * @covers Database::buildLike + * @covers Wikimedia\Rdbms\Database::buildLike */ public function testBuildLike( $array, $sqlText ) { $this->assertEquals( trim( $this->database->buildLike( @@ -715,7 +715,7 @@ /** * @dataProvider provideUnionQueries - * @covers Database::unionQueries + * @covers Wikimedia\Rdbms\Database::unionQueries */ public function testUnionQueries( $sql, $sqlText ) { $this->assertEquals( trim( $this->database->unionQueries( @@ -752,7 +752,7 @@ /** * @dataProvider provideUnionConditionPermutations - * @covers Database::unionConditionPermutations + * @covers Wikimedia\Rdbms\Database::unionConditionPermutations */ public function testUnionConditionPermutations( $params, $expect ) { if ( isset( $params['unionSupportsOrderAndLimit'] ) ) { @@ -920,7 +920,7 @@ } /** - * @covers Database::commit + * @covers Wikimedia\Rdbms\Database::commit */ public function testTransactionCommit() { $this->database->begin( __METHOD__ ); @@ -929,7 +929,7 @@ } /** - * @covers Database::rollback + * @covers Wikimedia\Rdbms\Database::rollback */ public function testTransactionRollback() { $this->database->begin( __METHOD__ ); @@ -938,7 +938,7 @@ } /** - * @covers Database::dropTable + * @covers Wikimedia\Rdbms\Database::dropTable */ public function testDropTable() { $this->database->setExistingTables( [ 'table' ] ); @@ -947,7 +947,7 @@ } /** - * @covers Database::dropTable + * @covers Wikimedia\Rdbms\Database::dropTable */ public function testDropNonExistingTable() { $this->assertFalse( @@ -957,7 +957,7 @@ /** * @dataProvider provideMakeList - * @covers Database::makeList + * @covers Wikimedia\Rdbms\Database::makeList */ public function testMakeList( $list, $mode, $sqlText ) { $this->assertEquals( trim( $this->database->makeList( diff --git a/tests/phpunit/includes/libs/rdbms/database/DatabaseTest.php b/tests/phpunit/includes/libs/rdbms/database/DatabaseTest.php new file mode 100644 index 0000000..9bea7ff --- /dev/null +++ b/tests/phpunit/includes/libs/rdbms/database/DatabaseTest.php @@ -0,0 +1,355 @@ +<?php + +use Wikimedia\Rdbms\IDatabase; +use Wikimedia\Rdbms\TransactionProfiler; +use Wikimedia\TestingAccessWrapper; + +class DatabaseTest extends PHPUnit_Framework_TestCase { + + protected function setUp() { + $this->db = new DatabaseTestHelper( __CLASS__ . '::' . $this->getName() ); + } + + public static function provideAddQuotes() { + return [ + [ null, 'NULL' ], + [ 1234, "'1234'" ], + [ 1234.5678, "'1234.5678'" ], + [ 'string', "'string'" ], + [ 'string\'s cause trouble', "'string\'s cause trouble'" ], + ]; + } + + /** + * @dataProvider provideAddQuotes + * @covers Wikimedia\Rdbms\Database::addQuotes + */ + public function testAddQuotes( $input, $expected ) { + $this->assertEquals( $expected, $this->db->addQuotes( $input ) ); + } + + public static function provideTableName() { + // Formatting is mostly ignored since addIdentifierQuotes is abstract. + // For testing of addIdentifierQuotes, see actual Database subclas tests. + return [ + 'local' => [ + 'tablename', + 'tablename', + 'quoted', + ], + 'local-raw' => [ + 'tablename', + 'tablename', + 'raw', + ], + 'shared' => [ + 'sharedb.tablename', + 'tablename', + 'quoted', + [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => '' ], + ], + 'shared-raw' => [ + 'sharedb.tablename', + 'tablename', + 'raw', + [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => '' ], + ], + 'shared-prefix' => [ + 'sharedb.sh_tablename', + 'tablename', + 'quoted', + [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => 'sh_' ], + ], + 'shared-prefix-raw' => [ + 'sharedb.sh_tablename', + 'tablename', + 'raw', + [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => 'sh_' ], + ], + 'foreign' => [ + 'databasename.tablename', + 'databasename.tablename', + 'quoted', + ], + 'foreign-raw' => [ + 'databasename.tablename', + 'databasename.tablename', + 'raw', + ], + ]; + } + + /** + * @dataProvider provideTableName + * @covers Wikimedia\Rdbms\Database::tableName + */ + public function testTableName( $expected, $table, $format, array $alias = null ) { + if ( $alias ) { + $this->db->setTableAliases( [ $table => $alias ] ); + } + $this->assertEquals( + $expected, + $this->db->tableName( $table, $format ?: 'quoted' ) + ); + } + + /** + * @covers Wikimedia\Rdbms\Database::onTransactionIdle + * @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks + */ + public function testTransactionIdle() { + $db = $this->db; + + $db->setFlag( DBO_TRX ); + $called = false; + $flagSet = null; + $db->onTransactionIdle( + function () use ( $db, &$flagSet, &$called ) { + $called = true; + $flagSet = $db->getFlag( DBO_TRX ); + }, + __METHOD__ + ); + $this->assertFalse( $flagSet, 'DBO_TRX off in callback' ); + $this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); + $this->assertTrue( $called, 'Callback reached' ); + + $db->clearFlag( DBO_TRX ); + $flagSet = null; + $db->onTransactionIdle( + function () use ( $db, &$flagSet ) { + $flagSet = $db->getFlag( DBO_TRX ); + }, + __METHOD__ + ); + $this->assertFalse( $flagSet, 'DBO_TRX off in callback' ); + $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); + + $db->clearFlag( DBO_TRX ); + $db->onTransactionIdle( + function () use ( $db ) { + $db->setFlag( DBO_TRX ); + }, + __METHOD__ + ); + $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); + } + + /** + * @covers Wikimedia\Rdbms\Database::onTransactionResolution + * @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks + */ + public function testTransactionResolution() { + $db = $this->db; + + $db->clearFlag( DBO_TRX ); + $db->begin( __METHOD__ ); + $called = false; + $db->onTransactionResolution( function () use ( $db, &$called ) { + $called = true; + $db->setFlag( DBO_TRX ); + } ); + $db->commit( __METHOD__ ); + $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); + $this->assertTrue( $called, 'Callback reached' ); + + $db->clearFlag( DBO_TRX ); + $db->begin( __METHOD__ ); + $called = false; + $db->onTransactionResolution( function () use ( $db, &$called ) { + $called = true; + $db->setFlag( DBO_TRX ); + } ); + $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS ); + $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); + $this->assertTrue( $called, 'Callback reached' ); + } + + /** + * @covers Wikimedia\Rdbms\Database::setTransactionListener + */ + public function testTransactionListener() { + $db = $this->db; + + $db->setTransactionListener( 'ping', function () use ( $db, &$called ) { + $called = true; + } ); + + $called = false; + $db->begin( __METHOD__ ); + $db->commit( __METHOD__ ); + $this->assertTrue( $called, 'Callback reached' ); + + $called = false; + $db->begin( __METHOD__ ); + $db->commit( __METHOD__ ); + $this->assertTrue( $called, 'Callback still reached' ); + + $called = false; + $db->begin( __METHOD__ ); + $db->rollback( __METHOD__ ); + $this->assertTrue( $called, 'Callback reached' ); + + $db->setTransactionListener( 'ping', null ); + $called = false; + $db->begin( __METHOD__ ); + $db->commit( __METHOD__ ); + $this->assertFalse( $called, 'Callback not reached' ); + } + + /** + * Use this mock instead of DatabaseTestHelper for cases where + * DatabaseTestHelper is too inflexibile due to mocking too much + * or being too restrictive about fname matching (e.g. for tests + * that assert behaviour when the name is a mismatch, we need to + * catch the error here instead of there). + * + * @return Database + */ + private function getMockDB( $methods = [] ) { + static $abstractMethods = [ + 'affectedRows', + 'closeConnection', + 'dataSeek', + 'doQuery', + 'fetchObject', 'fetchRow', + 'fieldInfo', 'fieldName', + 'getSoftwareLink', 'getServerVersion', + 'getType', + 'indexInfo', + 'insertId', + 'lastError', 'lastErrno', + 'numFields', 'numRows', + 'open', + 'strencode', + ]; + $db = $this->getMockBuilder( Database::class ) + ->disableOriginalConstructor() + ->setMethods( array_values( array_unique( array_merge( + $abstractMethods, + $methods + ) ) ) ) + ->getMock(); + $wdb = TestingAccessWrapper::newFromObject( $db ); + $wdb->trxProfiler = new TransactionProfiler(); + $wdb->connLogger = new \Psr\Log\NullLogger(); + $wdb->queryLogger = new \Psr\Log\NullLogger(); + return $db; + } + + /** + * @covers Wikimedia\Rdbms\Database::flushSnapshot + */ + public function testFlushSnapshot() { + $db = $this->getMockDB( [ 'isOpen' ] ); + $db->method( 'isOpen' )->willReturn( true ); + + $db->flushSnapshot( __METHOD__ ); // ok + $db->flushSnapshot( __METHOD__ ); // ok + + $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR ); + $db->query( 'SELECT 1', __METHOD__ ); + $this->assertTrue( (bool)$db->trxLevel(), "Transaction started." ); + $db->flushSnapshot( __METHOD__ ); // ok + $db->restoreFlags( $db::RESTORE_PRIOR ); + + $this->assertFalse( (bool)$db->trxLevel(), "Transaction cleared." ); + } + + public function testGetScopedLock() { + $db = $this->getMockDB( [ 'isOpen' ] ); + $db->method( 'isOpen' )->willReturn( true ); + + $db->setFlag( DBO_TRX ); + try { + $this->badLockingMethodImplicit( $db ); + } catch ( RunTimeException $e ) { + $this->assertTrue( $db->trxLevel() > 0, "Transaction not committed." ); + } + $db->clearFlag( DBO_TRX ); + $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS ); + $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) ); + + try { + $this->badLockingMethodExplicit( $db ); + } catch ( RunTimeException $e ) { + $this->assertTrue( $db->trxLevel() > 0, "Transaction not committed." ); + } + $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS ); + $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) ); + } + + private function badLockingMethodImplicit( IDatabase $db ) { + $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 ); + $db->query( "SELECT 1" ); // trigger DBO_TRX + throw new RunTimeException( "Uh oh!" ); + } + + private function badLockingMethodExplicit( IDatabase $db ) { + $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 ); + $db->begin( __METHOD__ ); + throw new RunTimeException( "Uh oh!" ); + } + + /** + * @covers Wikimedia\Rdbms\Database::getFlag + * @covers Wikimedia\Rdbms\Database::setFlag + * @covers Wikimedia\Rdbms\Database::restoreFlags + */ + public function testFlagSetting() { + $db = $this->db; + $origTrx = $db->getFlag( DBO_TRX ); + $origSsl = $db->getFlag( DBO_SSL ); + + $origTrx + ? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR ) + : $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR ); + $this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) ); + + $origSsl + ? $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR ) + : $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR ); + $this->assertEquals( !$origSsl, $db->getFlag( DBO_SSL ) ); + + $db->restoreFlags( $db::RESTORE_INITIAL ); + $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) ); + $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) ); + + $origTrx + ? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR ) + : $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR ); + $origSsl + ? $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR ) + : $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR ); + + $db->restoreFlags(); + $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) ); + $this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) ); + + $db->restoreFlags(); + $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) ); + $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) ); + } + + /** + * @covers Wikimedia\Rdbms\Database::tablePrefix + * @covers Wikimedia\Rdbms\Database::dbSchema + */ + public function testMutators() { + $old = $this->db->tablePrefix(); + $this->assertInternalType( 'string', $old, 'Prefix is string' ); + $this->assertEquals( $old, $this->db->tablePrefix(), "Prefix unchanged" ); + $this->assertEquals( $old, $this->db->tablePrefix( 'xxx' ) ); + $this->assertEquals( 'xxx', $this->db->tablePrefix(), "Prefix set" ); + $this->db->tablePrefix( $old ); + $this->assertNotEquals( 'xxx', $this->db->tablePrefix() ); + + $old = $this->db->dbSchema(); + $this->assertInternalType( 'string', $old, 'Schema is string' ); + $this->assertEquals( $old, $this->db->dbSchema(), "Schema unchanged" ); + $this->assertEquals( $old, $this->db->dbSchema( 'xxx' ) ); + $this->assertEquals( 'xxx', $this->db->dbSchema(), "Schema set" ); + $this->db->dbSchema( $old ); + $this->assertNotEquals( 'xxx', $this->db->dbSchema() ); + } +} diff --git a/tests/phpunit/structure/DatabaseIntegrationTest.php b/tests/phpunit/structure/DatabaseIntegrationTest.php new file mode 100644 index 0000000..b0c1c8f --- /dev/null +++ b/tests/phpunit/structure/DatabaseIntegrationTest.php @@ -0,0 +1,56 @@ +<?php + +use Wikimedia\Rdbms\IDatabase; +use Wikimedia\Rdbms\Database; + +/** + * @group Database + */ +class DatabaseIntegrationTest extends MediaWikiTestCase { + /** + * @var Database + */ + protected $db; + + private $functionTest = false; + + protected function setUp() { + parent::setUp(); + $this->db = wfGetDB( DB_MASTER ); + } + + protected function tearDown() { + parent::tearDown(); + if ( $this->functionTest ) { + $this->dropFunctions(); + $this->functionTest = false; + } + $this->db->restoreFlags( IDatabase::RESTORE_INITIAL ); + } + + public function testStoredFunctions() { + if ( !in_array( wfGetDB( DB_MASTER )->getType(), [ 'mysql', 'postgres' ] ) ) { + $this->markTestSkipped( 'MySQL or Postgres required' ); + } + global $IP; + $this->dropFunctions(); + $this->functionTest = true; + $this->assertTrue( + $this->db->sourceFile( "$IP/tests/phpunit/data/db/{$this->db->getType()}/functions.sql" ) + ); + $res = $this->db->query( 'SELECT mw_test_function() AS test', __METHOD__ ); + $this->assertEquals( 42, $res->fetchObject()->test ); + } + + private function dropFunctions() { + $this->db->query( 'DROP FUNCTION IF EXISTS mw_test_function' + . ( $this->db->getType() == 'postgres' ? '()' : '' ) + ); + } + + public function testUnknownTableCorruptsResults() { + $res = $this->db->select( 'page', '*', [ 'page_id' => 1 ] ); + $this->assertFalse( $this->db->tableExists( 'foobarbaz' ) ); + $this->assertInternalType( 'int', $res->numRows() ); + } +} -- To view, visit https://gerrit.wikimedia.org/r/366773 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I9dc4a558e701d00e95789e7eb8e02926783b65ad Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/core Gerrit-Branch: master Gerrit-Owner: Krinkle <krinklem...@gmail.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits