diff --git a/core/config/install/core.extension.yml b/core/config/install/core.extension.yml
index 659446cc64..8f8fe08f60 100644
--- a/core/config/install/core.extension.yml
+++ b/core/config/install/core.extension.yml
@@ -1,3 +1,4 @@
module: {}
theme: {}
profile: ''
+versions: {}
diff --git a/core/config/schema/core.extension.schema.yml b/core/config/schema/core.extension.schema.yml
index 087e2e3b94..c5f9a9177e 100644
--- a/core/config/schema/core.extension.schema.yml
+++ b/core/config/schema/core.extension.schema.yml
@@ -17,3 +17,16 @@ core.extension:
profile:
type: string
label: 'Install profile'
+ versions:
+ type: sequence
+ label: 'Version information'
+ sequence:
+ type: mapping
+ label: 'Extensions'
+ mapping:
+ current:
+ type: string
+ label: 'Currently installed version'
+ schema:
+ type: string
+ label: 'Currently installed schema version'
diff --git a/core/includes/schema.inc b/core/includes/schema.inc
index 443cbf1934..7201fa3c2d 100644
--- a/core/includes/schema.inc
+++ b/core/includes/schema.inc
@@ -104,6 +104,12 @@ function drupal_get_installed_schema_version($module, $reset = FALSE, $array = F
*/
function drupal_set_installed_schema_version($module, $version) {
\Drupal::keyValue('system.schema')->set($module, $version);
+ // Store the information in config so that we can ensure that database updates
+ // are the same between the source and the target site instances.
+ \Drupal::configFactory()
+ ->getEditable('core.extension')
+ ->set("versions.$module.schema", (string) $version)
+ ->save();
// Reset the static cache of module schema versions.
drupal_get_installed_schema_version(NULL, TRUE);
}
diff --git a/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
index ea7d29fedf..dd774d6440 100644
--- a/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
@@ -66,14 +66,33 @@ public function onConfigImporterValidate(ConfigImporterEvent $event) {
}
}
$config_importer = $event->getConfigImporter();
- if ($config_importer->getStorageComparer()->getSourceStorage()->exists('core.extension')) {
+ if ($this->validateCoreExtension($config_importer)) {
$this->validateModules($config_importer);
$this->validateThemes($config_importer);
$this->validateDependencies($config_importer);
}
- else {
+ }
+
+ /**
+ * Validates the core.extension configuration file.
+ *
+ * @param \Drupal\Core\Config\ConfigImporter $config_importer
+ * The configuration importer.
+ *
+ * @return bool
+ * TRUE if the core.extension is valid, FALSE if not.
+ */
+ protected function validateCoreExtension(ConfigImporter $config_importer) {
+ $data = $config_importer->getStorageComparer()->getSourceStorage()->read('core.extension');
+ if ($data === FALSE) {
$config_importer->logError($this->t('The core.extension configuration does not exist.'));
+ return FALSE;
}
+ elseif (empty($data['versions'])) {
+ $config_importer->logError($this->t('The core.extension file is out-of-date. Update the codebase of the source site, run update.php and export the configuration again.'));
+ return FALSE;
+ }
+ return TRUE;
}
/**
@@ -91,6 +110,25 @@ protected function validateModules(ConfigImporter $config_importer) {
$config_importer->logError($this->t('Unable to install the %module module since it does not exist.', ['%module' => $module]));
}
+ // Ensure that schema and version of the existing modules that produced the
+ // source configuration match the site.
+ $existing_modules = array_keys(array_intersect_key($core_extension['module'], $config_importer->getStorageComparer()->getTargetStorage()->read('core.extension')['module']));
+ foreach ($existing_modules as $module) {
+ if ($core_extension['versions'][$module]['current'] !== $module_data[$module]->info['version']) {
+ $config_importer->logError($this->t(
+ 'The version of %module module on the source site (@source_version) does not match this site (@target_version).',
+ ['%module' => $module, '@source_version' => $core_extension['versions'][$module]['current'], '@target_version' => $module_data[$module]->info['version']]
+ ));
+ }
+ elseif ($core_extension['versions'][$module]['schema'] !== (string) drupal_get_installed_schema_version($module)) {
+ $config_importer->logError($this->t(
+ 'The schema version of %module module on the source site (@source_schema) does not match this site (@target_schema).',
+ ['%module' => $module, '@source_schema' => $core_extension['versions'][$module]['schema'], '@target_schema' => drupal_get_installed_schema_version($module)]
+ ));
+
+ }
+ }
+
// Ensure that all modules being installed have their dependencies met.
$installs = $config_importer->getExtensionChangelist('module', 'install');
foreach ($installs as $module) {
@@ -109,6 +147,12 @@ protected function validateModules(ConfigImporter $config_importer) {
);
$config_importer->logError($message);
}
+ if ($core_extension['versions'][$module]['current'] !== $module_data[$module]->info['version']) {
+ $config_importer->logError($this->t(
+ 'Unable to install the %module module since the installed version (@config_version) does not match the code base (@code_version).',
+ ['%module' => $module, '@config_version' => $core_extension['versions'][$module]['current'], '@code_version' => $module_data[$module]->info['version']]
+ ));
+ }
}
// Get the install profile from the site's configuration.
@@ -160,6 +204,18 @@ protected function validateThemes(ConfigImporter $config_importer) {
}
}
+ // Ensure that version of the existing themes that produced the source
+ // configuration match the site.
+ $existing_themes = array_keys(array_intersect_key($core_extension['theme'], $config_importer->getStorageComparer()->getTargetStorage()->read('core.extension')['theme']));
+ foreach ($existing_themes as $theme) {
+ if ($core_extension['versions'][$theme]['current'] !== $theme_data[$theme]->info['version']) {
+ $config_importer->logError($this->t(
+ 'The version of %theme theme on the source site (@source_version) does not match this site (@target_version).',
+ ['%theme' => $theme, '@source_version' => $core_extension['versions'][$theme]['current'], '@target_version' => $theme_data[$theme]->info['version']]
+ ));
+ }
+ }
+
// Ensure that all themes being installed have their dependencies met.
foreach ($installs as $theme) {
foreach (array_keys($theme_data[$theme]->requires) as $required_theme) {
@@ -169,6 +225,12 @@ protected function validateThemes(ConfigImporter $config_importer) {
$config_importer->logError($this->t('Unable to install the %theme theme since it requires the %required_theme theme.', ['%theme' => $theme_name, '%required_theme' => $required_theme_name]));
}
}
+ if ($core_extension['versions'][$theme]['current'] !== $theme_data[$theme]->info['version']) {
+ $config_importer->logError($this->t(
+ 'Unable to install the %theme theme since the installed version (@config_version) does not match the code base (@code_version).',
+ ['%theme' => $theme, '@config_version' => $core_extension['versions'][$theme]['current'], '@code_version' => $theme_data[$theme]->info['version']]
+ ));
+ }
}
// Ensure that all themes being uninstalled are not required by themes that
diff --git a/core/lib/Drupal/Core/Extension/InfoParserDynamic.php b/core/lib/Drupal/Core/Extension/InfoParserDynamic.php
index 3b00cea093..e8e7a49a38 100644
--- a/core/lib/Drupal/Core/Extension/InfoParserDynamic.php
+++ b/core/lib/Drupal/Core/Extension/InfoParserDynamic.php
@@ -31,6 +31,9 @@ public function parse($filename) {
if (isset($parsed_info['version']) && $parsed_info['version'] === 'VERSION') {
$parsed_info['version'] = \Drupal::VERSION;
}
+ if (!isset($parsed_info['version'])) {
+ $parsed_info['version'] = 'None';
+ }
}
return $parsed_info;
}
diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
index 2cf34bd22f..a7ea9db5d0 100644
--- a/core/lib/Drupal/Core/Extension/ModuleInstaller.php
+++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
@@ -310,6 +310,21 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
// If any modules were newly installed, invoke hook_modules_installed().
if (!empty($modules_installed)) {
\Drupal::getContainer()->set('router.route_provider', \Drupal::service('router.route_provider.old'));
+ // There will be a new config factory.
+ $extension_config = \Drupal::configFactory()->getEditable('core.extension');
+ // Update version information in configuration.
+ $list = $this->moduleHandler->getModuleList();
+ $versions = $extension_config->get('versions');
+ foreach ($modules_installed as $module_name) {
+ $info = \Drupal::service('info_parser')->parse($list[$module_name]->getPathname());
+ $versions[$module_name] = [
+ 'current' => $info['version'],
+ 'schema' => (string) drupal_get_installed_schema_version($module_name),
+ ];
+ }
+ ksort($versions);
+ $extension_config->set('versions', $versions)->save();
+
if (!\Drupal::service('router.route_provider.lazy_builder')->hasRebuilt()) {
// Rebuild routes after installing module. This is done here on top of
// \Drupal\Core\Routing\RouteBuilder::destruct to not run into errors on
@@ -436,7 +451,11 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
// Remove the module's entry from the config. Don't check schema when
// uninstalling a module since we are only clearing a key.
- \Drupal::configFactory()->getEditable('core.extension')->clear("module.$module")->save(TRUE);
+ \Drupal::configFactory()
+ ->getEditable('core.extension')
+ ->clear("module.$module")
+ ->clear("versions.$module")
+ ->save(TRUE);
// Update the module handler to remove the module.
// The current ModuleHandler instance is obsolete with the kernel rebuild
diff --git a/core/lib/Drupal/Core/Extension/ThemeInstaller.php b/core/lib/Drupal/Core/Extension/ThemeInstaller.php
index 43a5469e3f..a496372c24 100644
--- a/core/lib/Drupal/Core/Extension/ThemeInstaller.php
+++ b/core/lib/Drupal/Core/Extension/ThemeInstaller.php
@@ -169,8 +169,14 @@ public function install(array $theme_list, $install_dependencies = TRUE) {
// The value is not used; the weight is ignored for themes currently. Do
// not check schema when saving the configuration.
+ $versions = $extension_config->get('versions');
+ $versions[$key] = [
+ 'current' => $theme_data[$key]->info['version'],
+ ];
+ ksort($versions);
$extension_config
->set("theme.$key", 0)
+ ->set('versions', $versions)
->save(TRUE);
// Add the theme to the current list.
@@ -247,7 +253,9 @@ public function uninstall(array $theme_list) {
$current_theme_data = $this->state->get('system.theme.data', []);
foreach ($theme_list as $key) {
// The value is not used; the weight is ignored for themes currently.
- $extension_config->clear("theme.$key");
+ $extension_config
+ ->clear("theme.$key")
+ ->clear("versions.$key");
// Update the current theme data accordingly.
unset($current_theme_data[$key]);
diff --git a/core/modules/config/tests/src/Functional/ConfigImportInstallProfileTest.php b/core/modules/config/tests/src/Functional/ConfigImportInstallProfileTest.php
index 8b0787d6ee..867c47c273 100644
--- a/core/modules/config/tests/src/Functional/ConfigImportInstallProfileTest.php
+++ b/core/modules/config/tests/src/Functional/ConfigImportInstallProfileTest.php
@@ -62,8 +62,13 @@ public function testInstallProfileValidation() {
$core['module']['testing_config_import'] = 0;
unset($core['module']['syslog']);
unset($core['theme']['stark']);
+ unset($core['versions']['syslog']);
+ unset($core['versions']['stark']);
$core['theme']['stable'] = 0;
$core['theme']['classy'] = 0;
+ $core['versions']['stable'] = ['current' => \Drupal::VERSION];
+ $core['versions']['classy'] = ['current' => \Drupal::VERSION];
+ ksort($core['versions']);
$sync->write('core.extension', $core);
$sync->deleteAll('syslog.');
$theme = $sync->read('system.theme');
diff --git a/core/modules/config/tests/src/Functional/ConfigImportUITest.php b/core/modules/config/tests/src/Functional/ConfigImportUITest.php
index fd76990da6..acb51a3b31 100644
--- a/core/modules/config/tests/src/Functional/ConfigImportUITest.php
+++ b/core/modules/config/tests/src/Functional/ConfigImportUITest.php
@@ -79,9 +79,14 @@ public function testImport() {
$core_extension['module']['action'] = 0;
$core_extension['module']['ban'] = 0;
$core_extension['module'] = module_config_sort($core_extension['module']);
+ $core_extension['versions']['action'] = ['current' => \Drupal::VERSION, 'schema' => '8000'];
+ $core_extension['versions']['ban'] = ['current' => \Drupal::VERSION, 'schema' => '8000'];
// Bartik is a subtheme of classy so classy must be enabled.
$core_extension['theme']['classy'] = 0;
$core_extension['theme']['bartik'] = 0;
+ $core_extension['versions']['classy'] = ['current' => \Drupal::VERSION];
+ $core_extension['versions']['bartik'] = ['current' => \Drupal::VERSION];
+ ksort($core_extension['versions']);
$sync->write('core.extension', $core_extension);
// Use the install storage so that we can read configuration from modules
@@ -171,6 +176,11 @@ public function testImport() {
unset($core_extension['module']['options']);
unset($core_extension['module']['text']);
unset($core_extension['theme']['bartik']);
+ unset($core_extension['versions']['action']);
+ unset($core_extension['versions']['ban']);
+ unset($core_extension['versions']['options']);
+ unset($core_extension['versions']['text']);
+ unset($core_extension['versions']['bartik']);
$sync->write('core.extension', $core_extension);
$sync->delete('action.settings');
$sync->delete('text.settings');
@@ -508,6 +518,16 @@ public function testExtensionValidation() {
$core['module']['does_not_exist'] = 0;
// This theme does not exist.
$core['theme']['does_not_exist'] = 0;
+ // This module does exist but the version is wrong.
+ $core['module']['ban'] = 0;
+ $core['versions']['ban'] = ['current' => '8.0.0-rc3', 'schema' => '8000'];
+ // This theme does exist but the version is wrong.
+ $core['theme']['seven'] = 0;
+ $core['versions']['seven'] = ['current' => '8.0.0-rc3'];
+ $core['versions']['bartik']['current'] = '8.0.0-rc3';
+ $core['versions']['text']['current'] = '8.0.0-rc3';
+ $core['versions']['config_test']['schema'] = '8999';
+
$sync->write('core.extension', $core);
$this->drupalPostForm('admin/config/development/configuration', [], t('Import all'));
@@ -516,6 +536,10 @@ public function testExtensionValidation() {
$this->assertText('Unable to uninstall the Classy theme since the Bartik theme is installed.');
$this->assertText('Unable to install the does_not_exist module since it does not exist.');
$this->assertText('Unable to install the does_not_exist theme since it does not exist.');
+ $this->assertText('Unable to install the ban module since the installed version (8.0.0-rc3) does not match the code base (' . \Drupal::VERSION . ').');
+ $this->assertText('Unable to install the seven theme since the installed version (8.0.0-rc3) does not match the code base (' . \Drupal::VERSION . ').');
+ $this->assertText('The schema version of config_test module on the source site (8999) does not match this site (' . drupal_get_installed_schema_version('config_test') . ').');
+ $this->assertText('The version of bartik theme on the source site (8.0.0-rc3) does not match this site (' . \Drupal::VERSION . ').');
}
}
diff --git a/core/modules/simpletest/src/KernelTestBase.php b/core/modules/simpletest/src/KernelTestBase.php
index 255adcc256..cbf8a45f0f 100644
--- a/core/modules/simpletest/src/KernelTestBase.php
+++ b/core/modules/simpletest/src/KernelTestBase.php
@@ -256,7 +256,13 @@ protected function setUp() {
// \Drupal\Core\Config\ConfigInstaller::installDefaultConfig() to work.
// Write directly to active storage to avoid early instantiation of
// the event dispatcher which can prevent modules from registering events.
- \Drupal::service('config.storage')->write('core.extension', ['module' => [], 'theme' => [], 'profile' => '']);
+ \Drupal::service('config.storage')->write('core.extension', [
+ 'module' => [],
+ 'theme' => [],
+ 'versions' => [],
+ 'profile' => '',
+ ]);
+
// Collect and set a fixed module list.
$class = get_class($this);
@@ -550,6 +556,11 @@ protected function enableModules(array $modules) {
$module_handler->addModule($module, $module_list[$module]->getPath());
// Maintain the list of enabled modules in configuration.
$extensions['module'][$module] = 0;
+ $version = \Drupal::service('info_parser')->parse($module_list[$module]->getPathname())['version'];
+ $extensions['versions'][$module] = [
+ 'current' => $version,
+ 'schema' => (string) SCHEMA_UNINSTALLED,
+ ];
}
$active_storage->write('core.extension', $extensions);
diff --git a/core/modules/simpletest/src/Tests/KernelTestBaseTest.php b/core/modules/simpletest/src/Tests/KernelTestBaseTest.php
index 1e9e75da0b..fe1cb1e8df 100644
--- a/core/modules/simpletest/src/Tests/KernelTestBaseTest.php
+++ b/core/modules/simpletest/src/Tests/KernelTestBaseTest.php
@@ -332,6 +332,18 @@ public function testDrupalGetProfile() {
$this->assertNull(\Drupal::installProfile());
}
+ /**
+ * @covers ::enableModules
+ */
+ public function testEnableModules() {
+ $this->assertFalse(\Drupal::moduleHandler()->moduleExists('system'));
+ $this->enableModules(['system', 'user']);
+ $core = \Drupal::config('core.extension');
+ $this->assertEqual(['entity_test' => 0, 'user' => 0, 'system' => 0], $core->get('module'));
+ $this->assertEqual(['current' => \Drupal::VERSION, 'schema' => (string) SCHEMA_UNINSTALLED], $core->get('versions.user'));
+ $this->assertTrue(\Drupal::moduleHandler()->moduleExists('system'));
+ }
+
/**
* {@inheritdoc}
*/
diff --git a/core/modules/system/src/Controller/DbUpdateController.php b/core/modules/system/src/Controller/DbUpdateController.php
index 9e0a374a4a..08f5c21cb6 100644
--- a/core/modules/system/src/Controller/DbUpdateController.php
+++ b/core/modules/system/src/Controller/DbUpdateController.php
@@ -649,6 +649,17 @@ public static function batchFinished($success, $results, $operations) {
// No updates to run, so caches won't get flushed later. Clear them now.
drupal_flush_all_caches();
+ // Update core.extensions to have the correct version information.
+ $core = \Drupal::configFactory()->getEditable('core.extension');
+ foreach (\Drupal::moduleHandler()->getModuleList() as $module => $extension) {
+ $version = \Drupal::service('info_parser')->parse($extension->getPathname())['version'];
+ $core->set("versions.$module.current", $version);
+ }
+ foreach (\Drupal::service('theme_handler')->listInfo() as $theme => $extension) {
+ $version = \Drupal::service('info_parser')->parse($extension->getPathname())['version'];
+ $core->set("versions.$theme.current", $version);
+ }
+
$_SESSION['update_results'] = $results;
$_SESSION['update_success'] = $success;
$_SESSION['updates_remaining'] = $operations;
diff --git a/core/modules/system/src/Tests/Update/VersionInfoUpdateTest.php b/core/modules/system/src/Tests/Update/VersionInfoUpdateTest.php
new file mode 100644
index 0000000000..17132e4924
--- /dev/null
+++ b/core/modules/system/src/Tests/Update/VersionInfoUpdateTest.php
@@ -0,0 +1,60 @@
+databaseDumpFiles = [
+ __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
+ ];
+ }
+
+ /**
+ * Tests that core.extension:versions is updated properly.
+ */
+ public function testSystemUpdate8015() {
+ // Set a partial value to ensure that regardless everything is updated
+ // correctly.
+ $this->config('core.extension')->set('versions.user.current', '8.0.0')->save();
+ $this->runUpdates();
+
+ // Test that a module is added.
+ $this->assertEqual([
+ 'current' => \Drupal::VERSION,
+ 'schema' => drupal_get_installed_schema_version('system'),
+ ], $this->config('core.extension')->get('versions.system'));
+ // Test that a profile is added.
+ $this->assertEqual([
+ 'current' => \Drupal::VERSION,
+ 'schema' => drupal_get_installed_schema_version('standard'),
+ ], $this->config('core.extension')->get('versions.standard'));
+ // Test that a theme is added.
+ $this->assertEqual([
+ 'current' => \Drupal::VERSION,
+ ], $this->config('core.extension')->get('versions.bartik'));
+
+ // Test that any existing version information is merged correctly.
+ $this->assertEqual([
+ 'current' => \Drupal::VERSION,
+ 'schema' => drupal_get_installed_schema_version('user'),
+ ], $this->config('core.extension')->get('versions.user'));
+
+ }
+
+}
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 1838326451..ef38a9aa7d 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -19,6 +19,7 @@
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\DrupalKernel;
use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\Extension\Extension;
use Drupal\Core\Site\Settings;
use Drupal\Core\StreamWrapper\PrivateStream;
use Drupal\Core\StreamWrapper\PublicStream;
@@ -2135,3 +2136,37 @@ function system_update_8501() {
}
}
}
+
+/**
+ * Add the version information to core.extension
+ */
+function system_update_8601() {
+ // Update core.extensions to have the correct version information.
+ $core = \Drupal::configFactory()->getEditable('core.extension');
+ $versions = $core->get('versions') ?: [];
+ $info_parser = \Drupal::service('info_parser');
+
+ // Create a method to populate the version information for an array of
+ // \Drupal\Core\Extension\Extension objects.
+ $map_version_info = function (Extension $extension) use ($info_parser) {
+ // Read from disk to avoid using APIs.
+ $version = $info_parser->parse($extension->getPathname())['version'];
+ $return = [
+ 'current' => $version,
+ ];
+ // Modules and profiles also stored the schema version.
+ if ($extension->getType() === 'module' || $extension->getType() === 'profile') {
+ $return['schema'] = (string) drupal_get_installed_schema_version($extension->getName());
+ }
+ return $return;
+ };
+
+ // It is possible that version information already exists. For example,
+ // another hook_update_N hook could have installed a module. However we can
+ // safely ignore that as this will ensure that all the information is correct
+ // at the time of writing.
+ $versions = array_map($map_version_info, \Drupal::moduleHandler()->getModuleList()) +
+ array_map($map_version_info, \Drupal::service('theme_handler')->listInfo());
+ ksort($versions);
+ $core->set('versions', $versions)->save();
+}
diff --git a/core/modules/system/tests/src/Functional/Module/UninstallTest.php b/core/modules/system/tests/src/Functional/Module/UninstallTest.php
index 2b17b8fcf6..509ffa8070 100644
--- a/core/modules/system/tests/src/Functional/Module/UninstallTest.php
+++ b/core/modules/system/tests/src/Functional/Module/UninstallTest.php
@@ -66,6 +66,10 @@ public function testUninstallPage() {
// Delete the node to allow node to be uninstalled.
$node->delete();
+ // Ensure the module information is present in core.extension before
+ // uninstall.
+ $this->assertNotNull($this->config('core.extension')->get('versions.module_test'), 'The "module_test" version information is present.');
+
// Uninstall module_test.
$edit = [];
$edit['uninstall[module_test]'] = TRUE;
@@ -76,6 +80,10 @@ public function testUninstallPage() {
$this->drupalPostForm(NULL, NULL, t('Uninstall'));
$this->assertText(t('The selected modules have been uninstalled.'), 'Modules status has been updated.');
+ // Ensure the module information has been removed from core.extension after
+ // uninstall.
+ $this->assertNull($this->config('core.extension')->get('versions.module_test'), 'The "module_test" version information has been removed.');
+
// Uninstall node testing that the configuration that will be deleted is
// listed.
$node_dependencies = \Drupal::service('config.manager')->findConfigEntityDependentsAsEntities('module', ['node']);
diff --git a/core/modules/system/tests/src/Functional/Update/UpdateScriptTest.php b/core/modules/system/tests/src/Functional/Update/UpdateScriptTest.php
index 29d4d08e23..4470cf5f01 100644
--- a/core/modules/system/tests/src/Functional/Update/UpdateScriptTest.php
+++ b/core/modules/system/tests/src/Functional/Update/UpdateScriptTest.php
@@ -197,11 +197,13 @@ public function testSuccessfulUpdateFunctionality() {
// Reset the static cache to ensure we have the most current setting.
$schema_version = drupal_get_installed_schema_version('update_script_test', TRUE);
$this->assertEqual($schema_version, 8001, 'update_script_test schema version is 8001 after updating.');
+ $this->assertIdentical('8001', $this->config('core.extension')->get('versions.update_script_test.schema'), 'update_script_test schema version is 8001 in core.extension config after updating.');
// Set the installed schema version to one less than the current update.
drupal_set_installed_schema_version('update_script_test', $schema_version - 1);
$schema_version = drupal_get_installed_schema_version('update_script_test', TRUE);
$this->assertEqual($schema_version, 8000, 'update_script_test schema version overridden to 8000.');
+ $this->assertIdentical('8000', $this->config('core.extension')->get('versions.update_script_test.schema'), 'update_script_test schema version is overridden to 8000 in core.extension config.');
// Click through update.php with 'access administration pages' and
// 'access site reports' permissions.
diff --git a/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterTest.php b/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterTest.php
index 4024b7f9d9..b14d3c24bb 100644
--- a/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterTest.php
@@ -573,6 +573,8 @@ public function testUnmetDependency() {
// Add a module and a theme that depend on uninstalled extensions.
$extensions['module']['book'] = 0;
$extensions['theme']['bartik'] = 0;
+ $extensions['versions']['book'] = ['current' => \Drupal::VERSION];
+ $extensions['versions']['bartik'] = ['current' => \Drupal::VERSION];
$sync->write('core.extension', $extensions);
try {
@@ -643,6 +645,58 @@ public function testMissingCoreExtension() {
}
}
+ /**
+ * Tests missing core.extension:versions during configuration import.
+ *
+ * Configuration exported before the versions information was added should
+ * fail.
+ *
+ * @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
+ */
+ public function testMissingVersionInformation() {
+ $sync = $this->container->get('config.storage.sync');
+ $data = $sync->read('core.extension');
+ unset($data['versions']);
+ $sync->write('core.extension', $data);
+ try {
+ $this->configImporter->reset()->import();
+ $this->fail('ConfigImporterException not thrown, invalid import was not stopped due to missing version information.');
+ }
+ catch (ConfigImporterException $e) {
+ $this->assertEqual($e->getMessage(), 'There were errors validating the config synchronization.');
+ $error_log = $this->configImporter->getErrors();
+ $this->assertEqual(['The core.extension file is out-of-date. Update the codebase of the source site, run update.php and export the configuration again.'], $error_log);
+ }
+ }
+
+ /**
+ * Tests missing core.extension:versions during configuration import.
+ *
+ * Configuration exported before the versions information was added should
+ * fail.
+ *
+ * @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
+ */
+ public function testVersionMismatch() {
+ $sync = $this->container->get('config.storage.sync');
+ $data = $sync->read('core.extension');
+ $data['versions']['system']['current'] = '8.0.0-beta1';
+ $data['versions']['config_test']['schema'] = '8999';
+ $sync->write('core.extension', $data);
+ try {
+ $this->configImporter->reset()->import();
+ $this->fail('ConfigImporterException not thrown, invalid import was not stopped due to missing version information.');
+ }
+ catch (ConfigImporterException $e) {
+ $this->assertEqual($e->getMessage(), 'There were errors validating the config synchronization.');
+ $error_log = $this->configImporter->getErrors();
+ $this->assertEqual([
+ 'The schema version of config_test module on the source site (8999) does not match this site (-1).',
+ 'The version of system module on the source site (8.0.0-beta1) does not match this site (' . \Drupal::VERSION . ').'
+ ], $error_log);
+ }
+ }
+
/**
* Tests install profile validation during configuration import.
*
diff --git a/core/tests/Drupal/KernelTests/Core/Theme/ThemeInstallerTest.php b/core/tests/Drupal/KernelTests/Core/Theme/ThemeInstallerTest.php
index 61f647528d..9f696db58e 100644
--- a/core/tests/Drupal/KernelTests/Core/Theme/ThemeInstallerTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Theme/ThemeInstallerTest.php
@@ -267,6 +267,7 @@ public function testUninstall() {
$this->themeInstaller()->install([$name]);
$this->assertTrue($this->config("$name.settings")->get());
+ $this->assertNotNull($this->config('core.extension')->get('versions.test_basetheme'), 'The "test_basetheme" version information is present after install.');
$this->themeInstaller()->uninstall([$name]);
@@ -274,6 +275,7 @@ public function testUninstall() {
$this->assertFalse(array_keys(system_list('theme')));
$this->assertFalse($this->config("$name.settings")->get());
+ $this->assertNull($this->config('core.extension')->get('versions.test_basetheme'), 'The "test_basetheme" version information is not present after uninstall.');
// Ensure that the uninstalled theme can be installed again.
$this->themeInstaller()->install([$name]);
diff --git a/core/tests/Drupal/KernelTests/KernelTestBase.php b/core/tests/Drupal/KernelTests/KernelTestBase.php
index 0a611d87c2..db968a5ebb 100644
--- a/core/tests/Drupal/KernelTests/KernelTestBase.php
+++ b/core/tests/Drupal/KernelTests/KernelTestBase.php
@@ -834,6 +834,11 @@ protected function enableModules(array $modules) {
$module_handler->addModule($module, $module_list[$module]->getPath());
// Maintain the list of enabled modules in configuration.
$extension_config['module'][$module] = 0;
+ $version = \Drupal::service('info_parser')->parse($module_list[$module]->getPathname())['version'];
+ $extension_config['versions'][$module] = [
+ 'current' => $version,
+ 'schema' => (string) SCHEMA_UNINSTALLED,
+ ];
}
$active_storage->write('core.extension', $extension_config);
diff --git a/core/tests/Drupal/KernelTests/KernelTestBaseTest.php b/core/tests/Drupal/KernelTests/KernelTestBaseTest.php
index 2ba85e92e1..01e9c69faa 100644
--- a/core/tests/Drupal/KernelTests/KernelTestBaseTest.php
+++ b/core/tests/Drupal/KernelTests/KernelTestBaseTest.php
@@ -212,6 +212,7 @@ public function testRenderWithTheme() {
}
/**
+<<<<<<< HEAD
* @covers ::bootKernel
*/
public function testFileDefaultScheme() {
@@ -275,12 +276,23 @@ public function testRequiresModule() {
try {
$stub_test->publicCheckRequirements();
$this->fail('Missing required module throws skipped test exception.');
- }
- catch (\PHPUnit_Framework_SkippedTestError $e) {
+ } catch (\PHPUnit_Framework_SkippedTestError $e) {
$this->assertEqual('Required modules: module_does_not_exist', $e->getMessage());
}
}
+ /**
+ * @covers ::enableModules
+ */
+ public function testEnableModules() {
+ $this->assertFalse(\Drupal::moduleHandler()->moduleExists('system'));
+ $this->enableModules(['system', 'user']);
+ $core = \Drupal::config('core.extension');
+ $this->assertEquals(['user' => 0, 'system' => 0], $core->get('module'));
+ $this->assertEquals(['current' => \Drupal::VERSION, 'schema' => (string) SCHEMA_UNINSTALLED], $core->get('versions.user'));
+ $this->assertTrue(\Drupal::moduleHandler()->moduleExists('system'));
+ }
+
/**
* {@inheritdoc}
*/
diff --git a/core/tests/Drupal/Tests/Core/Extension/DefaultConfigTest.php b/core/tests/Drupal/Tests/Core/Extension/DefaultConfigTest.php
index 29677b2c6e..b719ab4da8 100644
--- a/core/tests/Drupal/Tests/Core/Extension/DefaultConfigTest.php
+++ b/core/tests/Drupal/Tests/Core/Extension/DefaultConfigTest.php
@@ -27,6 +27,7 @@ public function testConfigIsEmpty() {
$expected = [
'module' => [],
'theme' => [],
+ 'versions' => [],
'profile' => '',
];
$this->assertEquals($expected, $config);