Merge "Provide PHPUnit 4 and 6 compatibility layer"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 6 Apr 2018 23:04:48 +0000 (23:04 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 6 Apr 2018 23:04:48 +0000 (23:04 +0000)
1  2 
tests/common/TestsAutoLoader.php
tests/phpunit/MediaWikiTestCase.php

@@@ -63,6 -63,7 +63,7 @@@ $wgAutoloadClasses += 
        'TestUserRegistry' => "$testDir/phpunit/includes/TestUserRegistry.php",
        'LessFileCompilationTest' => "$testDir/phpunit/LessFileCompilationTest.php",
        'MediaWikiCoversValidator' => "$testDir/phpunit/MediaWikiCoversValidator.php",
+       'PHPUnit4And6Compat' => "$testDir/phpunit/PHPUnit4And6Compat.php",
  
        # tests/phpunit/includes
        'RevisionDbTestBase' => "$testDir/phpunit/includes/RevisionDbTestBase.php",
@@@ -77,7 -78,6 +78,7 @@@
        'ApiTestCase' => "$testDir/phpunit/includes/api/ApiTestCase.php",
        'ApiTestCaseUpload' => "$testDir/phpunit/includes/api/ApiTestCaseUpload.php",
        'ApiTestContext' => "$testDir/phpunit/includes/api/ApiTestContext.php",
 +      'ApiUploadTestCase' => "$testDir/phpunit/includes/api/ApiUploadTestCase.php",
        'MockApi' => "$testDir/phpunit/includes/api/MockApi.php",
        'MockApiQueryBase' => "$testDir/phpunit/includes/api/MockApiQueryBase.php",
        'UserWrapper' => "$testDir/phpunit/includes/api/UserWrapper.php",
@@@ -96,8 -96,6 +97,8 @@@
        'DummyContentForTesting' => "$testDir/phpunit/mocks/content/DummyContentForTesting.php",
        'DummyNonTextContentHandler' => "$testDir/phpunit/mocks/content/DummyNonTextContentHandler.php",
        'DummyNonTextContent' => "$testDir/phpunit/mocks/content/DummyNonTextContent.php",
 +      'DummySerializeErrorContentHandler' =>
 +              "$testDir/phpunit/mocks/content/DummySerializeErrorContentHandler.php",
        'ContentHandlerTest' => "$testDir/phpunit/includes/content/ContentHandlerTest.php",
        'JavaScriptContentTest' => "$testDir/phpunit/includes/content/JavaScriptContentTest.php",
        'TextContentTest' => "$testDir/phpunit/includes/content/TextContentTest.php",
        'SpecialPageTestBase' => "$testDir/phpunit/includes/specials/SpecialPageTestBase.php",
        'SpecialPageExecutor' => "$testDir/phpunit/includes/specials/SpecialPageExecutor.php",
  
 +      # tests/phpunit/includes/Storage
 +      'MediaWiki\Tests\Storage\RevisionSlotsTest' => "$testDir/phpunit/includes/Storage/RevisionSlotsTest.php",
 +
        # tests/phpunit/languages
        'LanguageClassesTestCase' => "$testDir/phpunit/languages/LanguageClassesTestCase.php",
  
        'MediaWiki\\Session\\DummySessionBackend'
                => "$testDir/phpunit/mocks/session/DummySessionBackend.php",
        'DummySessionProvider' => "$testDir/phpunit/mocks/session/DummySessionProvider.php",
 +      'MockMessageLocalizer' => "$testDir/phpunit/mocks/MockMessageLocalizer.php",
  
        # tests/suites
        'ParserTestFileSuite' => "$testDir/phpunit/suites/ParserTestFileSuite.php",
        'ParserTestTopLevelSuite' => "$testDir/phpunit/suites/ParserTestTopLevelSuite.php",
  ];
  // phpcs:enable
+ /**
+  * Alias any PHPUnit 4 era PHPUnit_... class
+  * to it's PHPUnit 6 replacement. For most classes
+  * this is a direct _ -> \ replacement, but for
+  * some others we might need to maintain a manual
+  * mapping. Once we drop support for PHPUnit 4 this
+  * should be considered deprecated and eventually removed.
+  */
+ spl_autoload_register( function ( $class ) {
+       if ( strpos( $class, 'PHPUnit_' ) !== 0 ) {
+               // Skip if it doesn't start with the old prefix
+               return;
+       }
+       // Classes that don't map 100%
+       $map = [
+               'PHPUnit_Framework_TestSuite_DataProvider' => 'PHPUnit\Framework\DataProviderTestSuite'
+       ];
+       if ( isset( $map[$class] ) ) {
+               $newForm = $map[$class];
+       } else {
+               $newForm = str_replace( '_', '\\', $class );
+       }
+       if ( class_exists( $newForm ) ) {
+               // If the new class name exists, alias
+               // the old name to it.
+               class_alias( $newForm, $class );
+       }
+ } );
@@@ -17,6 -17,7 +17,7 @@@ use Wikimedia\TestingAccessWrapper
  abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
  
        use MediaWikiCoversValidator;
+       use PHPUnit4And6Compat;
  
        /**
         * The service locator created by prepareServices(). This service locator will
                }
        }
  
 +      private static $schemaOverrideDefaults = [
 +              'scripts' => [],
 +              'create' => [],
 +              'drop' => [],
 +              'alter' => [],
 +      ];
 +
        /**
         * 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.
 +       * @param IMaintainableDatabase $db The DB connection to use for the mock schema.
 +       *        May be used to check the current state of the schema, to determine what
 +       *        overrides are needed.
 +       *
 +       * @return array An associative array with the following fields:
 +       *  - 'scripts': any SQL scripts to run. If empty or not present, schema overrides are skipped.
 +       * - 'create': A list of tables created (may or may not exist in the original schema).
 +       * - 'drop': A list of tables dropped (expected to be present in the original schema).
 +       * - 'alter': A list of tables altered (expected to be present in the original schema).
         */
 -      protected function getSchemaOverrides() {
 -              return [ [], [] ];
 +      protected function getSchemaOverrides( IMaintainableDatabase $db ) {
 +              return [];
        }
  
        /**
 -       * Applies any schema changes requested by calling setDbSchema().
 +       * Undoes the dpecified schema overrides..
 +       * Called once per test class, just before addDataOnce().
 +       *
 +       * @param IMaintainableDatabase $db
 +       * @param array $oldOverrides
 +       */
 +      private function undoSchemaOverrides( IMaintainableDatabase $db, $oldOverrides ) {
 +              $this->ensureMockDatabaseConnection( $db );
 +
 +              $oldOverrides = $oldOverrides + self::$schemaOverrideDefaults;
 +              $originalTables = $this->listOriginalTables( $db );
 +
 +              // Drop tables that need to be restored or removed.
 +              $tablesToDrop = array_merge( $oldOverrides['create'], $oldOverrides['alter'] );
 +
 +              // Restore tables that have been dropped or created or altered,
 +              // if they exist in the original schema.
 +              $tablesToRestore = array_merge( $tablesToDrop, $oldOverrides['drop'] );
 +              $tablesToRestore = array_intersect( $originalTables, $tablesToRestore );
 +
 +              if ( $tablesToDrop ) {
 +                      $this->dropMockTables( $db, $tablesToDrop );
 +              }
 +
 +              if ( $tablesToRestore ) {
 +                      $this->recloneMockTables( $db, $tablesToRestore );
 +              }
 +      }
 +
 +      /**
 +       * Applies the schema overrides returned by getSchemaOverrides(),
 +       * after undoing any previously applied schema overrides.
         * Called once per test class, just before addDataOnce().
         */
        private function setUpSchema( IMaintainableDatabase $db ) {
 -              list( $tablesToAlter, $scriptsToRun ) = $this->getSchemaOverrides();
 +              // Undo any active overrides.
 +              $oldOverrides = isset( $db->_schemaOverrides ) ? $db->_schemaOverrides
 +                      : self::$schemaOverrideDefaults;
 +
 +              if ( $oldOverrides['alter'] || $oldOverrides['create'] || $oldOverrides['drop'] ) {
 +                      $this->undoSchemaOverrides( $db, $oldOverrides );
 +              }
 +
 +              // Determine new overrides.
 +              $overrides = $this->getSchemaOverrides( $db ) + self::$schemaOverrideDefaults;
 +
 +              $extraKeys = array_diff(
 +                      array_keys( $overrides ),
 +                      array_keys( self::$schemaOverrideDefaults )
 +              );
  
 -              if ( $tablesToAlter && !$scriptsToRun ) {
 +              if ( $extraKeys ) {
                        throw new InvalidArgumentException(
 -                              'No scripts supplied for applying the database schema.'
 +                              'Schema override contains extra keys: ' . var_export( $extraKeys, true )
                        );
                }
  
 -              if ( !$tablesToAlter && $scriptsToRun ) {
 +              if ( !$overrides['scripts'] ) {
 +                      // no scripts to run
 +                      return;
 +              }
 +
 +              if ( !$overrides['create'] && !$overrides['drop'] && !$overrides['alter'] ) {
                        throw new InvalidArgumentException(
 -                              'No tables declared to be altered by schema scripts.'
 +                              'Schema override scripts given, but no tables are declared to be '
 +                              . 'created, dropped or altered.'
                        );
                }
  
                $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 );
 +              // Drop the tables that will be created by the schema scripts.
 +              $originalTables = $this->listOriginalTables( $db );
 +              $tablesToDrop = array_intersect( $originalTables, $overrides['create'] );
  
                if ( $tablesToDrop ) {
                        $this->dropMockTables( $db, $tablesToDrop );
                }
  
 -              if ( $tablesToRestore ) {
 -                      $this->recloneMockTables( $db, $tablesToRestore );
 -              }
 -
 -              foreach ( $scriptsToRun as $script ) {
 +              // Run schema override scripts.
 +              foreach ( $overrides['scripts'] as $script ) {
                        $db->sourceFile(
                                $script,
                                null,
                        );
                }
  
 -              $db->_alteredMockTables = $tablesToAlter;
 +              $db->_schemaOverrides = $overrides;
        }
  
        private function mungeSchemaUpdateQuery( $cmd ) {
                }
        }
  
 +      /**
 +       * Lists all tables in the live database schema.
 +       *
 +       * @param IMaintainableDatabase $db
 +       * @return array
 +       */
 +      private function listOriginalTables( IMaintainableDatabase $db ) {
 +              if ( !isset( $db->_originalTablePrefix ) ) {
 +                      throw new LogicException( 'No original table prefix know, cannot list tables!' );
 +              }
 +
 +              $originalTables = $db->listTables( $db->_originalTablePrefix, __METHOD__ );
 +              return $originalTables;
 +      }
 +
        /**
         * Re-clones the given mock tables to restore them based on the live database schema.
 +       * The tables listed in $tables are expected to currently not exist, so dropMockTables()
 +       * should be called first.
         *
         * @param IMaintainableDatabase $db
         * @param array $tables
                        throw new LogicException( 'No original table prefix know, cannot restore tables!' );
                }
  
 -              $originalTables = $db->listTables( $db->_originalTablePrefix, __METHOD__ );
 +              $originalTables = $this->listOriginalTables( $db );
                $tables = array_intersect( $tables, $originalTables );
  
                $dbClone = new CloneDatabase( $db, $tables, $db->tablePrefix(), $db->_originalTablePrefix );
         */
        private function resetDB( $db, $tablesUsed ) {
                if ( $db ) {
 -                      $userTables = [ 'user', 'user_groups', 'user_properties' ];
 -                      $pageTables = [ 'page', 'revision', 'ip_changes', 'revision_comment_temp', 'comment' ];
 +                      $userTables = [ 'user', 'user_groups', 'user_properties', 'actor' ];
 +                      $pageTables = [ 'page', 'revision', 'ip_changes', 'revision_comment_temp',
 +                              'revision_actor_temp', 'comment' ];
                        $coreDBDataTables = array_merge( $userTables, $pageTables );
  
                        // If any of the user or page tables were marked as used, we should clear all of them.
                                        continue;
                                }
  
 +                              if ( !$db->tableExists( $tbl ) ) {
 +                                      continue;
 +                              }
 +
                                if ( $truncate ) {
                                        $db->query( 'TRUNCATE TABLE ' . $db->tableName( $tbl ), __METHOD__ );
                                } else {
                                        $db->delete( $tbl, '*', __METHOD__ );
                                }
  
 +                              if ( $db->getType() === 'postgres' ) {
 +                                      // Reset the table's sequence too.
 +                                      $db->resetSequenceForTable( $tbl, __METHOD__ );
 +                              }
 +
                                if ( $tbl === 'page' ) {
                                        // Forget about the pages since they don't
                                        // exist in the DB.
                }
        }
  
-       /**
-        * @since 1.18
-        *
-        * @param string $func
-        * @param array $args
-        *
-        * @return mixed
-        * @throws MWException
-        */
-       public function __call( $func, $args ) {
-               static $compatibility = [
-                       'createMock' => 'createMock2',
-               ];
-               if ( isset( $compatibility[$func] ) ) {
-                       return call_user_func_array( [ $this, $compatibility[$func] ], $args );
-               } else {
-                       throw new MWException( "Called non-existent $func method on " . static::class );
-               }
-       }
-       /**
-        * Return a test double for the specified class.
-        *
-        * @param string $originalClassName
-        * @return PHPUnit_Framework_MockObject_MockObject
-        * @throws Exception
-        */
-       private function createMock2( $originalClassName ) {
-               return $this->getMockBuilder( $originalClassName )
-                       ->disableOriginalConstructor()
-                       ->disableOriginalClone()
-                       ->disableArgumentCloning()
-                       // New in phpunit-mock-objects 3.2 (phpunit 5.4.0)
-                       // ->disallowMockingUnknownTypes()
-                       ->getMock();
-       }
        private static function unprefixTable( &$tableName, $ind, $prefix ) {
                $tableName = substr( $tableName, strlen( $prefix ) );
        }