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' ) );
+ }
+
+}