jenkins-bot has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/391866 )

Change subject: Introduce DB schema overrides for unit tests.
......................................................................


Introduce DB schema overrides for unit tests.

This introduces MediaWikiTestCase::getSchemaOverrides,  which can be overwritten
to return information about which tables are going to be altered, and which SQL
files should be used to set up the target schema. This allows tests for a class
that interacts with the database can have a subclass for each supported database
schema.

NOTE: this has only been tested with MySQL.

Bug: T180705
Change-Id: I7a4071072d802a82ecf7d16fbf8882ff8c79287f
---
M includes/db/CloneDatabase.php
M includes/libs/rdbms/database/Database.php
M tests/phpunit/MediaWikiTestCase.php
A tests/phpunit/tests/MediaWikiTestCaseSchema1Test.php
A tests/phpunit/tests/MediaWikiTestCaseSchema2Test.php
A tests/phpunit/tests/MediaWikiTestCaseSchemaTest.sql
M tests/phpunit/tests/MediaWikiTestCaseTest.php
7 files changed, 240 insertions(+), 4 deletions(-)

Approvals:
  Daniel Kinzler: Looks good to me, but someone else must approve
  Addshore: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/includes/db/CloneDatabase.php b/includes/db/CloneDatabase.php
index 16d10d1..edb54ae 100644
--- a/includes/db/CloneDatabase.php
+++ b/includes/db/CloneDatabase.php
@@ -50,12 +50,12 @@
         * @param bool $dropCurrentTables
         */
        public function __construct( IMaintainableDatabase $db, array 
$tablesToClone,
-               $newTablePrefix, $oldTablePrefix = '', $dropCurrentTables = true
+               $newTablePrefix, $oldTablePrefix = null, $dropCurrentTables = 
true
        ) {
                $this->db = $db;
                $this->tablesToClone = $tablesToClone;
                $this->newTablePrefix = $newTablePrefix;
-               $this->oldTablePrefix = $oldTablePrefix ? $oldTablePrefix : 
$this->db->tablePrefix();
+               $this->oldTablePrefix = $oldTablePrefix !== null ? 
$oldTablePrefix : $this->db->tablePrefix();
                $this->dropCurrentTables = $dropCurrentTables;
        }
 
diff --git a/includes/libs/rdbms/database/Database.php 
b/includes/libs/rdbms/database/Database.php
index 30c9cdd..0ee10ed 100644
--- a/includes/libs/rdbms/database/Database.php
+++ b/includes/libs/rdbms/database/Database.php
@@ -3424,7 +3424,15 @@
                        if ( $done || feof( $fp ) ) {
                                $cmd = $this->replaceVars( $cmd );
 
-                               if ( !$inputCallback || call_user_func( 
$inputCallback, $cmd ) ) {
+                               if ( $inputCallback ) {
+                                       $callbackResult = call_user_func( 
$inputCallback, $cmd );
+
+                                       if ( is_string( $callbackResult ) || 
!$callbackResult ) {
+                                               $cmd = $callbackResult;
+                                       }
+                               }
+
+                               if ( $cmd ) {
                                        $res = $this->query( $cmd, $fname );
 
                                        if ( $resultCallback ) {
diff --git a/tests/phpunit/MediaWikiTestCase.php 
b/tests/phpunit/MediaWikiTestCase.php
index d542826..10f5d41 100644
--- a/tests/phpunit/MediaWikiTestCase.php
+++ b/tests/phpunit/MediaWikiTestCase.php
@@ -5,8 +5,10 @@
 use MediaWiki\Logger\MonologSpi;
 use MediaWiki\MediaWikiServices;
 use Psr\Log\LoggerInterface;
+use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\IMaintainableDatabase;
 use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\LBFactory;
 use Wikimedia\TestingAccessWrapper;
 
 /**
@@ -408,6 +410,7 @@
                        // is available in subclass's setUpBeforeClass() and 
setUp() methods.
                        // This would also remove the need for the HACK that is 
oncePerClass().
                        if ( $this->oncePerClass() ) {
+                               $this->setUpSchema( $this->db );
                                $this->addDBDataOnce();
                        }
 
@@ -1152,6 +1155,8 @@
                $dbClone = new CloneDatabase( $db, $tablesCloned, $prefix );
                $dbClone->useTemporaryTables( self::$useTemporaryTables );
 
+               $db->_originalTablePrefix = $db->tablePrefix();
+
                if ( ( $db->getType() == 'oracle' || !self::$useTemporaryTables 
) && self::$reuseDB ) {
                        CloneDatabase::changePrefix( $prefix );
 
@@ -1296,6 +1301,133 @@
        }
 
        /**
+        * @throws LogicException if the given database connection is not a set 
up to use
+        * mock tables.
+        */
+       private function ensureMockDatabaseConnection( IDatabase $db ) {
+               if ( $db->tablePrefix() !== $this->dbPrefix() ) {
+                       throw new LogicException(
+                               'Trying to delete mock tables, but table prefix 
does not indicate a mock database.'
+                       );
+               }
+       }
+
+       /**
+        * Stub. If a test suite needs to test against a specific database 
schema, it should
+        * override this method and return the appropriate information from it.
+        *
+        * @return [ $tables, $scripts ] A tuple of two lists, with $tables 
being a list of tables
+        *         that will be re-created by the scripts, and $scripts being a 
list of SQL script
+        *         files for creating the tables listed.
+        */
+       protected function getSchemaOverrides() {
+               return [ [], [] ];
+       }
+
+       /**
+        * Applies any schema changes requested by calling setDbSchema().
+        * Called once per test class, just before addDataOnce().
+        */
+       private function setUpSchema( IMaintainableDatabase $db ) {
+               list( $tablesToAlter, $scriptsToRun ) = 
$this->getSchemaOverrides();
+
+               if ( $tablesToAlter && !$scriptsToRun ) {
+                       throw new InvalidArgumentException(
+                               'No scripts supplied for applying the database 
schema.'
+                       );
+               }
+
+               if ( !$tablesToAlter && $scriptsToRun ) {
+                       throw new InvalidArgumentException(
+                               'No tables declared to be altered by schema 
scripts.'
+                       );
+               }
+
+               $this->ensureMockDatabaseConnection( $db );
+
+               $previouslyAlteredTables = isset( $db->_alteredMockTables ) ? 
$db->_alteredMockTables : [];
+
+               if ( !$tablesToAlter && !$previouslyAlteredTables ) {
+                       return; // nothing to do
+               }
+
+               $tablesToDrop = array_merge( $previouslyAlteredTables, 
$tablesToAlter );
+               $tablesToRestore = array_diff( $previouslyAlteredTables, 
$tablesToAlter );
+
+               if ( $tablesToDrop ) {
+                       $this->dropMockTables( $db, $tablesToDrop );
+               }
+
+               if ( $tablesToRestore ) {
+                       $this->recloneMockTables( $db, $tablesToRestore );
+               }
+
+               foreach ( $scriptsToRun as $script ) {
+                       $db->sourceFile(
+                               $script,
+                               null,
+                               null,
+                               __METHOD__,
+                               function ( $cmd ) {
+                                       return $this->mungeSchemaUpdateQuery( 
$cmd );
+                               }
+                       );
+               }
+
+               $db->_alteredMockTables = $tablesToAlter;
+       }
+
+       private function mungeSchemaUpdateQuery( $cmd ) {
+               return self::$useTemporaryTables
+                       ? preg_replace( '/\bCREATE\s+TABLE\b/i', 'CREATE 
TEMPORARY TABLE', $cmd )
+                       : $cmd;
+       }
+
+       /**
+        * Drops the given mock tables.
+        *
+        * @param IMaintainableDatabase $db
+        * @param array $tables
+        */
+       private function dropMockTables( IMaintainableDatabase $db, array 
$tables ) {
+               $this->ensureMockDatabaseConnection( $db );
+
+               foreach ( $tables as $tbl ) {
+                       $tmp = self::$useTemporaryTables ? ' TEMPORARY ' : '';
+                       $tbl = $db->tableName( $tbl );
+                       $db->query( "DROP $tmp TABLE IF EXISTS $tbl", 
__METHOD__ );
+
+                       if ( $tbl === 'page' ) {
+                               // Forget about the pages since they don't
+                               // exist in the DB.
+                               LinkCache::singleton()->clear();
+                       }
+               }
+       }
+
+       /**
+        * Re-clones the given mock tables to restore them based on the live 
database schema.
+        *
+        * @param IMaintainableDatabase $db
+        * @param array $tables
+        */
+       private function recloneMockTables( IMaintainableDatabase $db, array 
$tables ) {
+               $this->ensureMockDatabaseConnection( $db );
+
+               if ( !isset( $db->_originalTablePrefix ) ) {
+                       throw new LogicException( 'No original table prefix 
know, cannot restore tables!' );
+               }
+
+               $originalTables = $db->listTables( $db->_originalTablePrefix, 
__METHOD__ );
+               $tables = array_intersect( $tables, $originalTables );
+
+               $dbClone = new CloneDatabase( $db, $tables, $db->tablePrefix(), 
$db->_originalTablePrefix );
+               $dbClone->useTemporaryTables( self::$useTemporaryTables );
+
+               $dbClone->cloneTableStructure();
+       }
+
+       /**
         * Empty all tables so they can be repopulated for tests
         *
         * @param Database $db|null Database to reset
@@ -1386,7 +1518,7 @@
        }
 
        private static function isNotUnittest( $table ) {
-               return strpos( $table, 'unittest_' ) !== 0;
+               return strpos( $table, self::DB_PREFIX ) !== 0;
        }
 
        /**
diff --git a/tests/phpunit/tests/MediaWikiTestCaseSchema1Test.php 
b/tests/phpunit/tests/MediaWikiTestCaseSchema1Test.php
new file mode 100644
index 0000000..4b0e0bf
--- /dev/null
+++ b/tests/phpunit/tests/MediaWikiTestCaseSchema1Test.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @covers MediaWikiTestCase
+ *
+ * @group Database
+ * @group MediaWikiTestCaseTest
+ */
+class MediaWikiTestCaseSchema1Test extends MediaWikiTestCase {
+
+       public function getSchemaOverrides() {
+               return [
+                       [ 'imagelinks', 'MediaWikiTestCaseTestTable' ],
+                       [ __DIR__ . '/MediaWikiTestCaseSchemaTest.sql' ]
+               ];
+       }
+
+       public function testSchemaExtension() {
+               // make sure we can use the MediaWikiTestCaseTestTable table
+
+               $input = [ 'id' => '5', 'name' => 'Test' ];
+
+               $this->db->insert(
+                       'MediaWikiTestCaseTestTable',
+                       $input
+               );
+
+               $output = $this->db->selectRow( 'MediaWikiTestCaseTestTable', 
array_keys( $input ), [] );
+               $this->assertEquals( (object)$input, $output );
+       }
+
+       public function testSchemaOverride() {
+               // make sure we can use the il_frobniz field
+
+               $input = [
+                       'il_from' => '7',
+                       'il_from_namespace' => '0',
+                       'il_to' => 'Foo.jpg',
+                       'il_frobniz' => 'Xyzzy',
+               ];
+
+               $this->db->insert(
+                       'imagelinks',
+                       $input
+               );
+
+               $output = $this->db->selectRow( 'imagelinks', array_keys( 
$input ), [] );
+               $this->assertEquals( (object)$input, $output );
+       }
+
+}
diff --git a/tests/phpunit/tests/MediaWikiTestCaseSchema2Test.php 
b/tests/phpunit/tests/MediaWikiTestCaseSchema2Test.php
new file mode 100644
index 0000000..b1c65ee
--- /dev/null
+++ b/tests/phpunit/tests/MediaWikiTestCaseSchema2Test.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @covers MediaWikiTestCase
+ *
+ * @group Database
+ * @group MediaWikiTestCaseTest
+ *
+ * This test is intended to be executed AFTER MediaWikiTestCaseSchema1Test to 
ensure
+ * that any schema modifications have been cleaned up between test cases.
+ * As there seems to be no way to force execution order, we currently rely on
+ * test classes getting run in anpha-numerical order.
+ */
+class MediaWikiTestCaseSchema2Test extends MediaWikiTestCase {
+
+       public function testSchemaExtension() {
+               // Make sure MediaWikiTestCaseTestTable created by 
MediaWikiTestCaseSchema1Test
+               // was dropped before executing MediaWikiTestCaseSchema2Test.
+               $this->assertFalse( $this->db->tableExists( 
'MediaWikiTestCaseTestTable' ) );
+       }
+
+       public function testSchemaOverride() {
+               // Make sure imagelinks modified by MediaWikiTestCaseSchema1Test
+               // was restored to the original schema before executing 
MediaWikiTestCaseSchema2Test.
+               $this->assertTrue( $this->db->tableExists( 'imagelinks' ) );
+               $this->assertFalse( $this->db->fieldExists( 'imagelinks', 
'il_frobniz' ) );
+       }
+
+}
diff --git a/tests/phpunit/tests/MediaWikiTestCaseSchemaTest.sql 
b/tests/phpunit/tests/MediaWikiTestCaseSchemaTest.sql
new file mode 100644
index 0000000..e5ef5c6
--- /dev/null
+++ b/tests/phpunit/tests/MediaWikiTestCaseSchemaTest.sql
@@ -0,0 +1,13 @@
+CREATE TABLE /*_*/MediaWikiTestCaseTestTable (
+  id INT NOT NULL,
+  name VARCHAR(20) NOT NULL,
+  PRIMARY KEY (id)
+) /*$wgDBTableOptions*/;
+
+CREATE TABLE /*_*/imagelinks (
+  il_from int(10) unsigned NOT NULL DEFAULT 0,
+  il_from_namespace int(11) NOT NULL DEFAULT 0,
+  il_to varbinary(255) NOT NULL DEFAULT '',
+  il_frobniz varchar(255) NOT NULL DEFAULT 'FROB',
+  PRIMARY KEY (il_from,il_to)
+) /*$wgDBTableOptions*/;
diff --git a/tests/phpunit/tests/MediaWikiTestCaseTest.php 
b/tests/phpunit/tests/MediaWikiTestCaseTest.php
index 7d75ffe..fb2957b 100644
--- a/tests/phpunit/tests/MediaWikiTestCaseTest.php
+++ b/tests/phpunit/tests/MediaWikiTestCaseTest.php
@@ -2,9 +2,12 @@
 use MediaWiki\Logger\LoggerFactory;
 use MediaWiki\MediaWikiServices;
 use Psr\Log\LoggerInterface;
+use Wikimedia\Rdbms\LoadBalancer;
 
 /**
  * @covers MediaWikiTestCase
+ * @group MediaWikiTestCaseTest
+ *
  * @author Addshore
  */
 class MediaWikiTestCaseTest extends MediaWikiTestCase {

-- 
To view, visit https://gerrit.wikimedia.org/r/391866
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I7a4071072d802a82ecf7d16fbf8882ff8c79287f
Gerrit-PatchSet: 7
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Daniel Kinzler <[email protected]>
Gerrit-Reviewer: Aaron Schulz <[email protected]>
Gerrit-Reviewer: Addshore <[email protected]>
Gerrit-Reviewer: Anomie <[email protected]>
Gerrit-Reviewer: Daniel Kinzler <[email protected]>
Gerrit-Reviewer: Jjanes <[email protected]>
Gerrit-Reviewer: Ladsgroup <[email protected]>
Gerrit-Reviewer: Mattflaschen <[email protected]>
Gerrit-Reviewer: Parent5446 <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to