diff --git a/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php b/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php index 32cb051dd6..cde942272c 100644 --- a/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php +++ b/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php @@ -711,4 +711,117 @@ protected function getDatabaseTypes() { return $database_types; } + /** + * Installs Drupal using SQL dump. + */ + protected function installDrupalFromDump() { + + $this->restoreDatabase(); + + $this->initUserSession(); + $this->prepareSettings(); + + $connection_info = Database::getConnectionInfo('default'); + + $databases['default']['default'] = (object) [ + 'value' => $connection_info['default'], + 'required' => TRUE, + ]; + + $settings['databases'] = $databases; + $settings['settings']['hash_salt'] = (object) [ + 'value' => $this->databasePrefix, + 'required' => TRUE, + ]; + + $settings['settings']['hash_salt'] = (object) [ + 'value' => $this->databasePrefix, + 'required' => TRUE, + ]; + + $settings['settings']['file_public_path'] = (object) [ + 'value' => $this->publicFilesDirectory, + 'required' => TRUE, + ]; + $settings['settings']['file_private_path'] = (object) [ + 'value' => $this->privateFilesDirectory, + 'required' => TRUE, + ]; + $settings['settings']['file_temp_path'] = (object) [ + 'value' => $this->tempFilesDirectory, + 'required' => TRUE, + ]; + + $this->writeSettings($settings); + + // During tests, cacheable responses should get the debugging cacheability + // headers by default. + $this->setContainerParameter('http.response.debug_cacheability_headers', TRUE); + + $autoloader = require './autoload.php'; + $this->kernel = new DrupalKernel('prod', $autoloader); + $request = Request::createFromGlobals(); + DrupalKernel::bootEnvironment(); + $site_path = DrupalKernel::findSitePath($request); + $this->kernel->setSitePath($site_path); + Settings::initialize($this->kernel->getAppRoot(), $this->kernel->getSitePath(), $autoloader); + $this->kernel->boot()->preHandle($request); + $this->container = $this->kernel->getContainer(); + + // The value comes with the dump from previous installation. + \Drupal::configFactory()->getEditable('system.file') + ->set('path.temporary', $this->tempFilesDirectory) + ->save(); + \Drupal::service('file_system')->prepareDirectory($this->tempFilesDirectory, FileSystemInterface::MODIFY_PERMISSIONS | FileSystemInterface::CREATE_DIRECTORY); + + // Manually create the private directory. + \Drupal::service('file_system')->prepareDirectory($this->privateFilesDirectory, FileSystemInterface::CREATE_DIRECTORY); + } + + /** + * Dumps database structure and contents of test site. + */ + protected function dumpDatabase() { + $connection_info = Database::getConnectionInfo('default'); + + $user = $connection_info['default']['username']; + $pass = $connection_info['default']['password']; + $db = $connection_info['default']['database']; + $host = $connection_info['default']['host']; + + switch ($connection_info['default']['driver']) { + case 'mysql': + $tables = \Drupal::database() + ->query("SHOW TABLES LIKE '$this->databasePrefix%'") + ->fetchCol(); + $tables_param = implode(' ', $tables); + exec("mysqldump -u$user -p$pass -h $host $db $tables_param | sed 's/$this->databasePrefix/default_db_prefix_/' > {$this->dumpFile}"); + break; + + default: + throw new \LogicException('This database driver is not supported yet.'); + } + } + + /** + * Restores database structure and contents of test site. + */ + protected function restoreDatabase() { + $connection_info = Database::getConnectionInfo('default'); + + $user = $connection_info['default']['username']; + $pass = $connection_info['default']['password']; + $db = $connection_info['default']['database']; + $host = $connection_info['default']['host']; + + switch ($connection_info['default']['driver']) { + case 'mysql': + exec("sed 's/default_db_prefix_/$this->databasePrefix/' $this->dumpFile | mysql -u$user -p$pass -h $host $db"); + break; + + default: + throw new \LogicException('This database driver is not supported yet.'); + } + } + } diff --git a/core/tests/Drupal/Tests/BrowserTestBase.php b/core/tests/Drupal/Tests/BrowserTestBase.php index 4c452d0051..13df0ba1f2 100644 --- a/core/tests/Drupal/Tests/BrowserTestBase.php +++ b/core/tests/Drupal/Tests/BrowserTestBase.php @@ -75,6 +75,13 @@ abstract class BrowserTestBase extends TestCase { use ExpectDeprecationTrait; use ExtensionListTestTrait; + /** + * Path to SQL dump file. + * + * @var string + */ + protected $dumpFile; + /** * Time limit in seconds for the test. * @@ -535,6 +542,44 @@ protected function getOptions($select, Element $container = NULL) { * Installs Drupal into the Simpletest site. */ public function installDrupal() { + if (getenv('BROWSERTEST_CACHE_DB')) { + $this->initDumpFile(); + if (file_exists($this->dumpFile)) { + $this->installDrupalFromDump(); + } + else { + $this->installDrupalFromProfile(); + $this->dumpDatabase(); + } + } + else { + $this->installDrupalFromProfile(); + } + } + + /** + * Determines a proper file name for SQL dump. + */ + protected function initDumpFile() { + $class = get_class($this); + $modules = []; + while ($class) { + if (property_exists($class, 'modules')) { + $modules = array_merge($modules, $class::$modules); + } + $class = get_parent_class($class); + } + sort($modules); + array_unique($modules); + $cache_dir = getenv('BROWSERTEST_CACHE_DIR') ?: sys_get_temp_dir() . '/test_dumps/' . \Drupal::VERSION; + is_dir($cache_dir) || mkdir($cache_dir, 0777, TRUE); + $this->dumpFile = $cache_dir . '/_' . md5(implode('-', $modules)) . '.sql'; + } + + /** + * Installs Drupal using installation profile. + */ + protected function installDrupalFromProfile() { $this->initUserSession(); $this->prepareSettings(); $this->doInstall();