Matthias Mullie <git@mullie.eu>
Matthias Mullie <git@mullie.eu> <mmullie@wikimedia.org>
Matěj Grabovský <mgrabovsky@yahoo.com> <mgrabovsky@users.mediawiki.org>
+Matěj Suchánek <matejsuchanek97@gmail.com>
Max Semenik <maxsem.wiki@gmail.com>
Max Semenik <maxsem.wiki@gmail.com> <maxsem@users.mediawiki.org>
Max Semenik <maxsem.wiki@gmail.com> <semenik@gmail.com>
Sam Smith <git@samsmith.io>
Santhosh Thottingal <santhosh.thottingal@gmail.com>
Santhosh Thottingal <santhosh.thottingal@gmail.com> <santhosh@users.mediawiki.org>
-Schnark <listenleser@gmail.com>
+Schnark (Michael M.) <listenleser@gmail.com>
Scimonster <tehalmightyscimonster@gmail.com>
Sean Colombo <sean.colombo@gmail.com> <sean_colombo@users.mediawiki.org>
Sean Pringle <springle@wikimedia.org>
* Massaf
* Matěj Grabovský
* Matěj Suchánek
-* matejsuchanek
* Mathias Ertl
* mati
* Matt Fitzpatrick
* Michael Dale
* Michael De La Rue
* Michael Holloway
-* Michael M.
* Michael Newton
* Michael Walsh
* Michał Łazowik
* Sam Wilson
* Santhosh Thottingal
* saptaks
-* Schnark
+* Schnark (Michael M.)
* Scimonster
* scnd
* Scott Colcord
* Victor Barbu
* Victor Porton
* Victor Vasiliev
-* victorbarbu
* Ville Stadista
* vishnu
* Vitaliy Filippov
* @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;
}
$fname = __METHOD__,
callable $inputCallback = null
) {
+ $delimiterReset = new ScopedCallback(
+ function ( $delimiter ) {
+ $this->delimiter = $delimiter;
+ },
+ [ $this->delimiter ]
+ );
$cmd = '';
while ( !feof( $fp ) ) {
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 ) {
}
}
+ ScopedCallback::consume( $delimiterReset );
return true;
}
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;
/**
// 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();
}
$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 );
return false;
}
+ /**
+ * @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
*
}
private static function isNotUnittest( $table ) {
- return strpos( $table, 'unittest_' ) !== 0;
+ return strpos( $table, self::DB_PREFIX ) !== 0;
}
/**
--- /dev/null
+<?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 );
+ }
+
+}
--- /dev/null
+<?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' ) );
+ }
+
+}
--- /dev/null
+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*/;
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 {