From 264f81b0916151b8a68a0fe5567eef7e782aa3bd Mon Sep 17 00:00:00 2001 From: catch <catch@35733.no-reply.drupal.org> Date: Thu, 19 Dec 2019 14:03:35 +0000 Subject: [PATCH] Issue #3063912 by alexpott, jhedstrom: Move UpdatePathTestBase::runUpdates() to a trait --- ...rupal-8.update-test-postupdate-enabled.php | 39 ---- ...update-test-postupdate-failing-enabled.php | 38 ---- .../Update/BrokenCacheUpdateTest.php | 17 +- .../Update/UpdatePathNewDependencyTest.php | 16 +- .../Update/UpdatePathTestJavaScriptTest.php | 13 +- .../UpdatePathWithBrokenRoutingFilledTest.php | 30 +++- .../UpdatePathWithBrokenRoutingTest.php | 20 +-- .../Update/UpdatePostUpdateFailingTest.php | 44 ++++- .../Update/UpdatePostUpdateTest.php | 46 +++-- .../Update/UpdatePathTestBase.php | 137 +------------- .../Drupal/Tests/UpdatePathTestTrait.php | 169 ++++++++++++++++++ 11 files changed, 303 insertions(+), 266 deletions(-) delete mode 100644 core/modules/system/tests/fixtures/update/drupal-8.update-test-postupdate-enabled.php delete mode 100644 core/modules/system/tests/fixtures/update/drupal-8.update-test-postupdate-failing-enabled.php create mode 100644 core/tests/Drupal/Tests/UpdatePathTestTrait.php diff --git a/core/modules/system/tests/fixtures/update/drupal-8.update-test-postupdate-enabled.php b/core/modules/system/tests/fixtures/update/drupal-8.update-test-postupdate-enabled.php deleted file mode 100644 index a6243a138ca5..000000000000 --- a/core/modules/system/tests/fixtures/update/drupal-8.update-test-postupdate-enabled.php +++ /dev/null @@ -1,39 +0,0 @@ -<?php - -/** - * @file - * Partial database to mimic the installation of the update_test_post_update - * module. - */ - -use Drupal\Core\Database\Database; - -$connection = Database::getConnection(); - -// Set the schema version. -$connection->merge('key_value') - ->condition('collection', 'system.schema') - ->condition('name', 'update_test_postupdate') - ->fields([ - 'collection' => 'system.schema', - 'name' => 'update_test_postupdate', - 'value' => 'i:8000;', - ]) - ->execute(); - -// Update core.extension. -$extensions = $connection->select('config') - ->fields('config', ['data']) - ->condition('collection', '') - ->condition('name', 'core.extension') - ->execute() - ->fetchField(); -$extensions = unserialize($extensions); -$extensions['module']['update_test_postupdate'] = 8000; -$connection->update('config') - ->fields([ - 'data' => serialize($extensions), - ]) - ->condition('collection', '') - ->condition('name', 'core.extension') - ->execute(); diff --git a/core/modules/system/tests/fixtures/update/drupal-8.update-test-postupdate-failing-enabled.php b/core/modules/system/tests/fixtures/update/drupal-8.update-test-postupdate-failing-enabled.php deleted file mode 100644 index ab37f83af706..000000000000 --- a/core/modules/system/tests/fixtures/update/drupal-8.update-test-postupdate-failing-enabled.php +++ /dev/null @@ -1,38 +0,0 @@ -<?php - -/** - * @file - * Partial database to mimic the installation of the update_test_failing module. - */ - -use Drupal\Core\Database\Database; - -$connection = Database::getConnection(); - -// Set the schema version. -$connection->merge('key_value') - ->condition('collection', 'system.schema') - ->condition('name', 'update_test_failing') - ->fields([ - 'collection' => 'system.schema', - 'name' => 'update_test_failing', - 'value' => 'i:8000;', - ]) - ->execute(); - -// Update core.extension. -$extensions = $connection->select('config') - ->fields('config', ['data']) - ->condition('collection', '') - ->condition('name', 'core.extension') - ->execute() - ->fetchField(); -$extensions = unserialize($extensions); -$extensions['module']['update_test_failing'] = 8000; -$connection->update('config') - ->fields([ - 'data' => serialize($extensions), - ]) - ->condition('collection', '') - ->condition('name', 'core.extension') - ->execute(); diff --git a/core/modules/system/tests/src/Functional/Update/BrokenCacheUpdateTest.php b/core/modules/system/tests/src/Functional/Update/BrokenCacheUpdateTest.php index 300676a2a38f..d66c31029c7d 100644 --- a/core/modules/system/tests/src/Functional/Update/BrokenCacheUpdateTest.php +++ b/core/modules/system/tests/src/Functional/Update/BrokenCacheUpdateTest.php @@ -3,15 +3,16 @@ namespace Drupal\Tests\system\Functional\Update; use Drupal\Core\Database\Database; -use Drupal\FunctionalTests\Update\UpdatePathTestBase; +use Drupal\Tests\BrowserTestBase; +use Drupal\Tests\UpdatePathTestTrait; /** * Ensures that a broken or out-of-date element info cache is not used. * * @group Update - * @group legacy */ -class BrokenCacheUpdateTest extends UpdatePathTestBase { +class BrokenCacheUpdateTest extends BrowserTestBase { + use UpdatePathTestTrait; /** * {@inheritdoc} @@ -21,10 +22,9 @@ class BrokenCacheUpdateTest extends UpdatePathTestBase { /** * {@inheritdoc} */ - protected function setDatabaseDumpFiles() { - $this->databaseDumpFiles = [ - __DIR__ . '/../../../../tests/fixtures/update/drupal-8.6.0.bare.testing.php.gz', - ]; + protected function setUp() { + parent::setUp(); + $this->ensureUpdatesToRun(); } /** @@ -40,7 +40,8 @@ public function testUpdate() { ->execute(); // Create broken element info caches entries. - $insert = $connection->insert('cache_discovery'); + $insert = $connection->upsert('cache_discovery'); + $insert->key('cid'); $fields = [ 'cid' => 'element_info', 'data' => 'BROKEN', diff --git a/core/modules/system/tests/src/Functional/Update/UpdatePathNewDependencyTest.php b/core/modules/system/tests/src/Functional/Update/UpdatePathNewDependencyTest.php index 8f10ef76ce71..3a673962d6dc 100644 --- a/core/modules/system/tests/src/Functional/Update/UpdatePathNewDependencyTest.php +++ b/core/modules/system/tests/src/Functional/Update/UpdatePathNewDependencyTest.php @@ -2,31 +2,23 @@ namespace Drupal\Tests\system\Functional\Update; -use Drupal\FunctionalTests\Update\UpdatePathTestBase; +use Drupal\Tests\BrowserTestBase; +use Drupal\Tests\UpdatePathTestTrait; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; /** * Modules can introduce new dependencies and enable them in update hooks. * * @group system - * @group legacy */ -class UpdatePathNewDependencyTest extends UpdatePathTestBase { +class UpdatePathNewDependencyTest extends BrowserTestBase { + use UpdatePathTestTrait; /** * {@inheritdoc} */ protected $defaultTheme = 'stark'; - /** - * {@inheritdoc} - */ - protected function setDatabaseDumpFiles() { - $this->databaseDumpFiles = [ - __DIR__ . '/../../../../tests/fixtures/update/drupal-8.6.0.bare.testing.php.gz', - ]; - } - /** * Test that a module can add services that depend on new modules. */ diff --git a/core/modules/system/tests/src/Functional/Update/UpdatePathTestJavaScriptTest.php b/core/modules/system/tests/src/Functional/Update/UpdatePathTestJavaScriptTest.php index 473c392aa03e..28f727a7e6fb 100644 --- a/core/modules/system/tests/src/Functional/Update/UpdatePathTestJavaScriptTest.php +++ b/core/modules/system/tests/src/Functional/Update/UpdatePathTestJavaScriptTest.php @@ -2,7 +2,8 @@ namespace Drupal\Tests\system\Functional\Update; -use Drupal\FunctionalTests\Update\UpdatePathTestBase; +use Drupal\Tests\BrowserTestBase; +use Drupal\Tests\UpdatePathTestTrait; /** * Tests the presence of JavaScript at update.php. @@ -10,7 +11,8 @@ * @group Update * @group legacy */ -class UpdatePathTestJavaScriptTest extends UpdatePathTestBase { +class UpdatePathTestJavaScriptTest extends BrowserTestBase { + use UpdatePathTestTrait; /** * {@inheritdoc} @@ -20,10 +22,9 @@ class UpdatePathTestJavaScriptTest extends UpdatePathTestBase { /** * {@inheritdoc} */ - protected function setDatabaseDumpFiles() { - $this->databaseDumpFiles = [ - __DIR__ . '/../../../../tests/fixtures/update/drupal-8.bare.standard.php.gz', - ]; + protected function setUp() { + parent::setUp(); + $this->ensureUpdatesToRun(); } /** diff --git a/core/modules/system/tests/src/Functional/Update/UpdatePathWithBrokenRoutingFilledTest.php b/core/modules/system/tests/src/Functional/Update/UpdatePathWithBrokenRoutingFilledTest.php index 6be4f333cb4a..2f3287a3b3ce 100644 --- a/core/modules/system/tests/src/Functional/Update/UpdatePathWithBrokenRoutingFilledTest.php +++ b/core/modules/system/tests/src/Functional/Update/UpdatePathWithBrokenRoutingFilledTest.php @@ -2,13 +2,15 @@ namespace Drupal\Tests\system\Functional\Update; +use Drupal\FunctionalTests\Update\UpdatePathTestBase; + /** * Runs UpdatePathWithBrokenRoutingTest with a dump filled with content. * * @group Update * @group legacy */ -class UpdatePathWithBrokenRoutingFilledTest extends UpdatePathWithBrokenRoutingTest { +class UpdatePathWithBrokenRoutingFilledTest extends UpdatePathTestBase { /** * {@inheritdoc} @@ -19,8 +21,30 @@ class UpdatePathWithBrokenRoutingFilledTest extends UpdatePathWithBrokenRoutingT * {@inheritdoc} */ protected function setDatabaseDumpFiles() { - parent::setDatabaseDumpFiles(); - $this->databaseDumpFiles[0] = __DIR__ . '/../../../../tests/fixtures/update/drupal-8.filled.standard.php.gz'; + $this->databaseDumpFiles = [ + __DIR__ . '/../../../../tests/fixtures/update/drupal-8.8.0.filled.standard.php.gz', + __DIR__ . '/../../../../tests/fixtures/update/drupal-8.broken_routing.php', + ]; + } + + /** + * Tests running update.php with some form of broken routing. + */ + public function testWithBrokenRouting() { + // Simulate a broken router, and make sure the front page is + // inaccessible. + \Drupal::state()->set('update_script_test_broken_inbound', TRUE); + \Drupal::service('cache_tags.invalidator')->invalidateTags(['route_match', 'rendered']); + $this->drupalGet('<front>'); + $this->assertResponse(500); + + $this->runUpdates(); + + // Remove the simulation of the broken router, and make sure we can get to + // the front page again. + \Drupal::state()->set('update_script_test_broken_inbound', FALSE); + $this->drupalGet('<front>'); + $this->assertResponse(200); } } diff --git a/core/modules/system/tests/src/Functional/Update/UpdatePathWithBrokenRoutingTest.php b/core/modules/system/tests/src/Functional/Update/UpdatePathWithBrokenRoutingTest.php index 17a70c0215b3..1faee31db884 100644 --- a/core/modules/system/tests/src/Functional/Update/UpdatePathWithBrokenRoutingTest.php +++ b/core/modules/system/tests/src/Functional/Update/UpdatePathWithBrokenRoutingTest.php @@ -2,15 +2,17 @@ namespace Drupal\Tests\system\Functional\Update; -use Drupal\FunctionalTests\Update\UpdatePathTestBase; +use Drupal\Core\Url; +use Drupal\Tests\BrowserTestBase; +use Drupal\Tests\UpdatePathTestTrait; /** * Tests the update path with a broken router. * * @group Update - * @group legacy */ -class UpdatePathWithBrokenRoutingTest extends UpdatePathTestBase { +class UpdatePathWithBrokenRoutingTest extends BrowserTestBase { + use UpdatePathTestTrait; /** * {@inheritdoc} @@ -20,11 +22,9 @@ class UpdatePathWithBrokenRoutingTest extends UpdatePathTestBase { /** * {@inheritdoc} */ - protected function setDatabaseDumpFiles() { - $this->databaseDumpFiles = [ - __DIR__ . '/../../../../tests/fixtures/update/drupal-8.bare.standard.php.gz', - __DIR__ . '/../../../../tests/fixtures/update/drupal-8.broken_routing.php', - ]; + protected function setUp() { + parent::setUp(); + $this->ensureUpdatesToRun(); } /** @@ -34,11 +34,11 @@ public function testWithBrokenRouting() { // Simulate a broken router, and make sure the front page is // inaccessible. \Drupal::state()->set('update_script_test_broken_inbound', TRUE); - \Drupal::service('cache_tags.invalidator')->invalidateTags(['route_match', 'rendered']); + $this->resetAll(); $this->drupalGet('<front>'); $this->assertResponse(500); - $this->runUpdates(); + $this->runUpdates(Url::fromRoute('system.db_update', [], ['path_processing' => FALSE])); // Remove the simulation of the broken router, and make sure we can get to // the front page again. diff --git a/core/modules/system/tests/src/Functional/Update/UpdatePostUpdateFailingTest.php b/core/modules/system/tests/src/Functional/Update/UpdatePostUpdateFailingTest.php index 5c6b223d6fbb..a645cb457c04 100644 --- a/core/modules/system/tests/src/Functional/Update/UpdatePostUpdateFailingTest.php +++ b/core/modules/system/tests/src/Functional/Update/UpdatePostUpdateFailingTest.php @@ -2,15 +2,17 @@ namespace Drupal\Tests\system\Functional\Update; -use Drupal\FunctionalTests\Update\UpdatePathTestBase; +use Drupal\Core\Database\Database; +use Drupal\Tests\BrowserTestBase; +use Drupal\Tests\UpdatePathTestTrait; /** * Tests hook_post_update() when there are failing update hooks. * * @group Update - * @group legacy */ -class UpdatePostUpdateFailingTest extends UpdatePathTestBase { +class UpdatePostUpdateFailingTest extends BrowserTestBase { + use UpdatePathTestTrait; /** * {@inheritdoc} @@ -20,11 +22,37 @@ class UpdatePostUpdateFailingTest extends UpdatePathTestBase { /** * {@inheritdoc} */ - protected function setDatabaseDumpFiles() { - $this->databaseDumpFiles = [ - __DIR__ . '/../../../../tests/fixtures/update/drupal-8.bare.standard.php.gz', - __DIR__ . '/../../../../tests/fixtures/update/drupal-8.update-test-postupdate-failing-enabled.php', - ]; + protected function setUp() { + parent::setUp(); + $connection = Database::getConnection(); + + // Set the schema version. + $connection->merge('key_value') + ->condition('collection', 'system.schema') + ->condition('name', 'update_test_failing') + ->fields([ + 'collection' => 'system.schema', + 'name' => 'update_test_failing', + 'value' => 'i:8000;', + ]) + ->execute(); + + // Update core.extension. + $extensions = $connection->select('config') + ->fields('config', ['data']) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute() + ->fetchField(); + $extensions = unserialize($extensions); + $extensions['module']['update_test_failing'] = 8000; + $connection->update('config') + ->fields([ + 'data' => serialize($extensions), + ]) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute(); } /** diff --git a/core/modules/system/tests/src/Functional/Update/UpdatePostUpdateTest.php b/core/modules/system/tests/src/Functional/Update/UpdatePostUpdateTest.php index 1015e7a138eb..a1c5690c34e3 100644 --- a/core/modules/system/tests/src/Functional/Update/UpdatePostUpdateTest.php +++ b/core/modules/system/tests/src/Functional/Update/UpdatePostUpdateTest.php @@ -3,15 +3,17 @@ namespace Drupal\Tests\system\Functional\Update; use Drupal\Component\Render\FormattableMarkup; -use Drupal\FunctionalTests\Update\UpdatePathTestBase; +use Drupal\Core\Database\Database; +use Drupal\Tests\BrowserTestBase; +use Drupal\Tests\UpdatePathTestTrait; /** * Tests hook_post_update(). * * @group Update - * @group legacy */ -class UpdatePostUpdateTest extends UpdatePathTestBase { +class UpdatePostUpdateTest extends BrowserTestBase { + use UpdatePathTestTrait; /** * {@inheritdoc} @@ -21,19 +23,43 @@ class UpdatePostUpdateTest extends UpdatePathTestBase { /** * {@inheritdoc} */ - protected function setDatabaseDumpFiles() { - $this->databaseDumpFiles = [ - __DIR__ . '/../../../../tests/fixtures/update/drupal-8.bare.standard.php.gz', - __DIR__ . '/../../../../tests/fixtures/update/drupal-8.update-test-postupdate-enabled.php', - ]; + protected function setUp() { + parent::setUp(); + $connection = Database::getConnection(); + + // Set the schema version. + $connection->merge('key_value') + ->condition('collection', 'system.schema') + ->condition('name', 'update_test_postupdate') + ->fields([ + 'collection' => 'system.schema', + 'name' => 'update_test_postupdate', + 'value' => 'i:8000;', + ]) + ->execute(); + + // Update core.extension. + $extensions = $connection->select('config') + ->fields('config', ['data']) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute() + ->fetchField(); + $extensions = unserialize($extensions); + $extensions['module']['update_test_postupdate'] = 8000; + $connection->update('config') + ->fields([ + 'data' => serialize($extensions), + ]) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute(); } /** * {@inheritdoc} */ protected function doSelectionTest() { - parent::doSelectionTest(); - // Ensure that normal and post_update updates are merged together on the // selection page. $this->assertRaw('<ul><li>8001 - Normal update_N() function. </li><li>First update.</li><li>Second update.</li><li>Test0 update.</li><li>Test1 update.</li><li>Testing batch processing in post updates update.</li></ul>'); diff --git a/core/tests/Drupal/FunctionalTests/Update/UpdatePathTestBase.php b/core/tests/Drupal/FunctionalTests/Update/UpdatePathTestBase.php index d7bad285130c..c043e327b54c 100644 --- a/core/tests/Drupal/FunctionalTests/Update/UpdatePathTestBase.php +++ b/core/tests/Drupal/FunctionalTests/Update/UpdatePathTestBase.php @@ -6,13 +6,12 @@ use Drupal\Core\Site\Settings; use Drupal\Core\Test\TestRunnerKernel; use Drupal\Tests\BrowserTestBase; -use Drupal\Tests\SchemaCheckTestTrait; use Drupal\Core\Database\Database; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Language\Language; use Drupal\Core\Url; -use Drupal\Tests\RequirementsPageTrait; +use Drupal\Tests\UpdatePathTestTrait; use Drupal\user\Entity\User; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpFoundation\Request; @@ -43,9 +42,9 @@ * @see hook_update_N() */ abstract class UpdatePathTestBase extends BrowserTestBase { - - use SchemaCheckTestTrait; - use RequirementsPageTrait; + use UpdatePathTestTrait { + runUpdates as doRunUpdates; + } /** * Modules to enable after the database is loaded. @@ -125,13 +124,6 @@ abstract class UpdatePathTestBase extends BrowserTestBase { */ protected $strictConfigSchema = FALSE; - /** - * Fail the test if there are failed updates. - * - * @var bool - */ - protected $checkFailedUpdates = TRUE; - /** * Constructs an UpdatePathTestCase object. * @@ -298,117 +290,7 @@ protected function runUpdates() { $this->fail('Missing zlib requirement for update tests.'); return FALSE; } - // The site might be broken at the time so logging in using the UI might - // not work, so we use the API itself. - drupal_rewrite_settings([ - 'settings' => [ - 'update_free_access' => (object) [ - 'value' => TRUE, - 'required' => TRUE, - ], - ], - ]); - - $this->drupalGet($this->updateUrl); - $this->updateRequirementsProblem(); - $this->clickLink(t('Continue')); - - $this->doSelectionTest(); - // Run the update hooks. - $this->clickLink(t('Apply pending updates')); - $this->checkForMetaRefresh(); - - // Ensure there are no failed updates. - if ($this->checkFailedUpdates) { - $failure = $this->cssSelect('.failure'); - if ($failure) { - $this->fail('The update failed with the following message: "' . reset($failure)->getText() . '"'); - } - - // Ensure that there are no pending updates. - foreach (['update', 'post_update'] as $update_type) { - switch ($update_type) { - case 'update': - $all_updates = update_get_update_list(); - break; - case 'post_update': - $all_updates = \Drupal::service('update.post_update_registry')->getPendingUpdateInformation(); - break; - } - foreach ($all_updates as $module => $updates) { - if (!empty($updates['pending'])) { - foreach (array_keys($updates['pending']) as $update_name) { - $this->fail("The $update_name() update function from the $module module did not run."); - } - } - } - } - - // Ensure that the container is updated if any modules are installed or - // uninstalled during the update. - /** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */ - $module_handler = $this->container->get('module_handler'); - $config_module_list = $this->config('core.extension')->get('module'); - $module_handler_list = $module_handler->getModuleList(); - $modules_installed = FALSE; - // Modules that are in configuration but not the module handler have been - // installed. - foreach (array_keys(array_diff_key($config_module_list, $module_handler_list)) as $module) { - $module_handler->addModule($module, drupal_get_path('module', $module)); - $modules_installed = TRUE; - } - $modules_uninstalled = FALSE; - $module_handler_list = $module_handler->getModuleList(); - // Modules that are in the module handler but not configuration have been - // uninstalled. - foreach (array_keys(array_diff_key($module_handler_list, $config_module_list)) as $module) { - $modules_uninstalled = TRUE; - unset($module_handler_list[$module]); - } - if ($modules_installed || $modules_uninstalled) { - // Note that resetAll() does not reset the kernel module list so we - // have to do that manually. - $this->kernel->updateModules($module_handler_list, $module_handler_list); - } - - // If we have successfully clicked 'Apply pending updates' then we need to - // clear the caches in the update test runner as this has occurred as part - // of the updates. - $this->resetAll(); - - // The config schema can be incorrect while the update functions are being - // executed. But once the update has been completed, it needs to be valid - // again. Assert the schema of all configuration objects now. - $names = $this->container->get('config.storage')->listAll(); - - // Allow tests to opt out of checking specific configuration. - $exclude = $this->getConfigSchemaExclusions(); - /** @var \Drupal\Core\Config\TypedConfigManagerInterface $typed_config */ - $typed_config = $this->container->get('config.typed'); - foreach ($names as $name) { - if (in_array($name, $exclude, TRUE)) { - // Skip checking schema if the config is listed in the - // $configSchemaCheckerExclusions property. - continue; - } - $config = $this->config($name); - $this->assertConfigSchema($typed_config, $name, $config->get()); - } - - // Ensure that the update hooks updated all entity schema. - $needs_updates = \Drupal::entityDefinitionUpdateManager()->needsUpdates(); - if ($needs_updates) { - foreach (\Drupal::entityDefinitionUpdateManager()->getChangeSummary() as $entity_type_id => $summary) { - $entity_type_label = \Drupal::entityTypeManager()->getDefinition($entity_type_id)->getLabel(); - foreach ($summary as $message) { - $this->fail("$entity_type_label: $message"); - } - } - // The above calls to `fail()` should prevent this from ever being - // called, but it is here in case something goes really wrong. - $this->assertFalse($needs_updates, 'After all updates ran, entity schema is up to date.'); - } - } + $this->doRunUpdates($this->updateUrl); } /** @@ -449,13 +331,4 @@ protected function replaceUser1() { $account->save(); } - /** - * Tests the selection page. - */ - protected function doSelectionTest() { - // No-op. Tests wishing to do test the selection page or the general - // update.php environment before running update.php can override this method - // and implement their required tests. - } - } diff --git a/core/tests/Drupal/Tests/UpdatePathTestTrait.php b/core/tests/Drupal/Tests/UpdatePathTestTrait.php new file mode 100644 index 000000000000..61facab0bd26 --- /dev/null +++ b/core/tests/Drupal/Tests/UpdatePathTestTrait.php @@ -0,0 +1,169 @@ +<?php + +namespace Drupal\Tests; + +use Drupal\Core\Url; + +/** + * Trait UpdatePathTestTrait + * + * For use on \Drupal\Tests\BrowserTestBase tests. + */ +trait UpdatePathTestTrait { + use RequirementsPageTrait; + use SchemaCheckTestTrait; + + /** + * Fail the test if there are failed updates. + * + * @var bool + */ + protected $checkFailedUpdates = TRUE; + + /** + * Helper function to run pending database updates. + * + * @param string|null $update_url + * The update URL. + */ + protected function runUpdates($update_url = NULL) { + if (!$update_url) { + $update_url = Url::fromRoute('system.db_update'); + } + require_once $this->root . '/core/includes/update.inc'; + // The site might be broken at the time so logging in using the UI might + // not work, so we use the API itself. + $this->writeSettings([ + 'settings' => [ + 'update_free_access' => (object) [ + 'value' => TRUE, + 'required' => TRUE, + ], + ], + ]); + + $this->drupalGet($update_url); + $this->updateRequirementsProblem(); + $this->clickLink(t('Continue')); + + $this->doSelectionTest(); + // Run the update hooks. + $this->clickLink(t('Apply pending updates')); + $this->checkForMetaRefresh(); + + // Ensure there are no failed updates. + if ($this->checkFailedUpdates) { + $failure = $this->cssSelect('.failure'); + if ($failure) { + $this->fail('The update failed with the following message: "' . reset($failure)->getText() . '"'); + } + + // Ensure that there are no pending updates. + foreach (['update', 'post_update'] as $update_type) { + switch ($update_type) { + case 'update': + $all_updates = update_get_update_list(); + break; + case 'post_update': + $all_updates = \Drupal::service('update.post_update_registry')->getPendingUpdateInformation(); + break; + } + foreach ($all_updates as $module => $updates) { + if (!empty($updates['pending'])) { + foreach (array_keys($updates['pending']) as $update_name) { + $this->fail("The $update_name() update function from the $module module did not run."); + } + } + } + } + + // Ensure that the container is updated if any modules are installed or + // uninstalled during the update. + /** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */ + $module_handler = $this->container->get('module_handler'); + $config_module_list = $this->config('core.extension')->get('module'); + $module_handler_list = $module_handler->getModuleList(); + $modules_installed = FALSE; + // Modules that are in configuration but not the module handler have been + // installed. + foreach (array_keys(array_diff_key($config_module_list, $module_handler_list)) as $module) { + $module_handler->addModule($module, drupal_get_path('module', $module)); + $modules_installed = TRUE; + } + $modules_uninstalled = FALSE; + $module_handler_list = $module_handler->getModuleList(); + // Modules that are in the module handler but not configuration have been + // uninstalled. + foreach (array_keys(array_diff_key($module_handler_list, $config_module_list)) as $module) { + $modules_uninstalled = TRUE; + unset($module_handler_list[$module]); + } + if ($modules_installed || $modules_uninstalled) { + // Note that resetAll() does not reset the kernel module list so we + // have to do that manually. + $this->kernel->updateModules($module_handler_list, $module_handler_list); + } + + // If we have successfully clicked 'Apply pending updates' then we need to + // clear the caches in the update test runner as this has occurred as part + // of the updates. + $this->resetAll(); + + // The config schema can be incorrect while the update functions are being + // executed. But once the update has been completed, it needs to be valid + // again. Assert the schema of all configuration objects now. + $names = $this->container->get('config.storage')->listAll(); + + // Allow tests to opt out of checking specific configuration. + $exclude = $this->getConfigSchemaExclusions(); + /** @var \Drupal\Core\Config\TypedConfigManagerInterface $typed_config */ + $typed_config = $this->container->get('config.typed'); + foreach ($names as $name) { + if (in_array($name, $exclude, TRUE)) { + // Skip checking schema if the config is listed in the + // $configSchemaCheckerExclusions property. + continue; + } + $config = $this->config($name); + $this->assertConfigSchema($typed_config, $name, $config->get()); + } + + // Ensure that the update hooks updated all entity schema. + $needs_updates = \Drupal::entityDefinitionUpdateManager()->needsUpdates(); + if ($needs_updates) { + foreach (\Drupal::entityDefinitionUpdateManager()->getChangeSummary() as $entity_type_id => $summary) { + $entity_type_label = \Drupal::entityTypeManager()->getDefinition($entity_type_id)->getLabel(); + foreach ($summary as $message) { + $this->fail("$entity_type_label: $message"); + } + } + // The above calls to `fail()` should prevent this from ever being + // called, but it is here in case something goes really wrong. + $this->assertFalse($needs_updates, 'After all updates ran, entity schema is up to date.'); + } + } + } + + /** + * Tests the selection page. + */ + protected function doSelectionTest() { + // No-op. Tests wishing to do test the selection page or the general + // update.php environment before running update.php can override this method + // and implement their required tests. + } + + /** + * Installs the update_script_test module and makes an update available. + */ + protected function ensureUpdatesToRun() { + \Drupal::service('module_installer')->install(['update_script_test']); + // Reset the schema so there is an update to run. + \Drupal::database()->update('key_value') + ->fields(['value' => serialize(8000)]) + ->condition('collection', 'system.schema') + ->condition('name', 'update_script_test') + ->execute(); + } + +} -- GitLab