Daniel Kinzler has uploaded a new change for review. ( 
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.

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, 238 insertions(+), 4 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core 
refs/changes/66/391866/1

diff --git a/includes/db/CloneDatabase.php b/includes/db/CloneDatabase.php
index 3d22c03..98275db 100644
--- a/includes/db/CloneDatabase.php
+++ b/includes/db/CloneDatabase.php
@@ -53,12 +53,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 e04566e..6ad5959 100644
--- a/includes/libs/rdbms/database/Database.php
+++ b/includes/libs/rdbms/database/Database.php
@@ -3390,7 +3390,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 === false ) {
+                                               $cmd = $callbackResult;
+                                       }
+                               }
+
+                               if ( $cmd ) {
                                        $res = $this->query( $cmd, $fname );
 
                                        if ( $resultCallback ) {
diff --git a/tests/phpunit/MediaWikiTestCase.php 
b/tests/phpunit/MediaWikiTestCase.php
index f04eec7..10b2a38 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;
 
 /**
@@ -406,6 +408,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,134 @@
        }
 
        /**
+        * Fail with a LogicException if the given database connection is not a 
set up to use
+        * mock tables.
+        */
+       private function ensureMockDatabaseConnection( IDatabase $db ) {
+               if ( $db->tablePrefix() !== self::DB_PREFIX ) {
+                       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 inormation 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 origina 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 +1519,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..bd740bf
--- /dev/null
+++ b/tests/phpunit/tests/MediaWikiTestCaseSchema2Test.php
@@ -0,0 +1,26 @@
+<?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.
+ */
+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->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..bd8a3bd
--- /dev/null
+++ b/tests/phpunit/tests/MediaWikiTestCaseSchemaTest.sql
@@ -0,0 +1,13 @@
+CREATE TABLE /*_*/MediaWikiTestCaseTestTable (
+  id INT AUTO_INCREMENT 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: newchange
Gerrit-Change-Id: I7a4071072d802a82ecf7d16fbf8882ff8c79287f
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Daniel Kinzler <daniel.kinz...@wikimedia.de>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to