5 use Webmozart\PathUtil\Path;
11 class UpdateDBTest extends CommandUnishTestCase
13 use TestModuleHelperTrait;
15 protected $pathPostUpdate;
17 public function testUpdateDBStatus()
19 $this->setUpDrupal(1, true);
20 $this->drush('pm:enable', ['devel']);
21 $this->drush('updatedb:status', [], ['format' => 'json']);
22 $out = $this->getOutputFromJSON();
23 $this->assertNull($out);
25 // Force a pending update.
26 $this->drush('php-script', ['updatedb_script'], ['script-path' => __DIR__ . '/resources']);
28 // Assert that pending hook_update_n appears
29 $this->drush('updatedb:status', [], ['format' => 'json']);
30 $out = $this->getOutputFromJSON('devel_update_8002');
31 $this->assertEquals('Add enforced dependencies to system.menu.devel', trim($out->description));
34 $this->drush('updatedb', []);
36 // Assert that we ran hook_update_n properly
37 $this->drush('updatedb:status', [], ['format' => 'json']);
38 $out = $this->getOutputFromJSON();
39 $this->assertNull($out);
41 // Assure that a pending post-update is reported.
42 $this->pathPostUpdate = $this->getSut() . '/web/modules/unish/devel/devel.post_update.php';
43 copy(__DIR__ . '/resources/devel.post_update.php', $this->pathPostUpdate);
44 $this->drush('updatedb:status', [], ['format' => 'json']);
45 $out = $this->getOutputFromJSON('devel-post-null_op');
46 $this->assertEquals('This is a test of the emergency broadcast system.', trim($out->description));
50 * Tests that updatedb command returns properly a failure.
52 public function testFailedUpdate()
54 $sites = $this->setUpDrupal(1, true);
57 'root' => $this->webroot(),
60 $this->setupModulesForTests(['woot'], Path::join(__DIR__, 'resources/modules/d8'));
61 $this->drush('pm-enable', ['woot'], $options);
63 // Force a pending update.
64 $this->drush('php-script', ['updatedb_script'], ['script-path' => __DIR__ . '/resources']);
66 // Force re-run of woot_update_8101().
67 $this->drush('php:eval', array('drupal_set_installed_schema_version("woot", 8100)'), $options);
69 // Force re-run of the post-update woot_post_update_failing().
70 $this->forcePostUpdate('woot_post_update_failing', $options);
73 $this->drush('updatedb', [], $options, null, null, self::EXIT_ERROR);
75 $expected_output = <<<LOG
76 -------- ----------- --------------- -----------------------
77 Module Update ID Type Description
78 -------- ----------- --------------- -----------------------
79 woot 8101 hook_update_n Good update.
80 woot 8102 hook_update_n Failing update.
81 woot 8103 hook_update_n Another good update.
82 woot failing post-update Failing post-update.
83 -------- ----------- --------------- -----------------------
85 // Do you wish to run the specified pending updates?: yes.
87 $this->assertOutputEquals(preg_replace('# *#', ' ', $this->simplifyOutput($expected_output)));
89 $expected_error_output = <<<LOG
90 [notice] Update started: woot_update_8101
91 [notice] This is the update message from woot_update_8101
92 [ok] Update completed: woot_update_8101
93 [notice] Update started: woot_update_8102
94 [error] This is the exception message thrown in woot_update_8102
95 [error] Update failed: woot_update_8102
96 [error] Update aborted by: woot_update_8102
97 [error] Finished performing updates.
100 $this->assertErrorOutputEquals(preg_replace('# *#', ' ', $this->simplifyOutput($expected_error_output)));
104 * Tests that a failed post-update is handled correctly.
106 public function testFailedPostUpdate()
108 $sites = $this->setUpDrupal(1, true);
111 'root' => $this->webroot(),
112 'uri' => key($sites),
114 $this->setupModulesForTests(['woot'], Path::join(__DIR__, 'resources/modules/d8'));
115 $this->drush('pm-enable', ['woot'], $options);
117 // Force re-run of woot_update_8103().
118 $this->drush('php:eval', array('drupal_set_installed_schema_version("woot", 8102)'), $options);
120 // Force re-run of post-update hooks.
121 $this->forcePostUpdate('woot_post_update_a', $options);
122 $this->forcePostUpdate('woot_post_update_failing', $options);
125 $this->drush('updatedb', [], $options, null, null, self::EXIT_ERROR);
127 $expected_output = <<<LOG
128 -------- ----------- --------------- -------------------------
129 Module Update ID Type Description
130 -------- ----------- --------------- -------------------------
131 woot 8103 hook_update_n Another good update.
132 woot a post-update Successful post-update.
133 woot failing post-update Failing post-update.
134 -------- ----------- --------------- -------------------------
136 // Do you wish to run the specified pending updates?: yes.
138 $this->assertOutputEquals(preg_replace('# *#', ' ', $this->simplifyOutput($expected_output)));
140 $expected_error_output = <<<LOG
141 [notice] Update started: woot_update_8103
142 [notice] This is the update message from woot_update_8103
143 [ok] Update completed: woot_update_8103
144 [notice] Update started: woot_post_update_a
145 [notice] This is the update message from woot_post_update_a
146 [ok] Update completed: woot_post_update_a
147 [notice] Update started: woot_post_update_failing
148 [error] This is the exception message thrown in woot_post_update_failing
149 [error] Update failed: woot_post_update_failing
150 [error] Update aborted by: woot_post_update_failing
151 [error] Finished performing updates.
154 $this->assertErrorOutputEquals(preg_replace('# *#', ' ', $this->simplifyOutput($expected_error_output)));
158 * Tests that the updatedb command works when new services are introduced.
160 * This is a regression test for a bug that prevented the updatedb command
161 * from running when the update introduces a new module, and introduces a
162 * new service in an existing module that has a dependency on the new
165 * @see https://github.com/drush-ops/drush/issues/3193
166 * @see https://www.drupal.org/project/drupal/issues/2863986
168 public function testUpdateModuleWithServiceDependency()
170 $root = $this->webroot();
171 $sites = $this->setUpDrupal(1, true);
175 'uri' => key($sites),
176 'include' => __DIR__,
178 $this->setupModulesForTests(['woot'], Path::join(__DIR__, 'resources/modules/d8'));
179 $this->drush('pm-enable', ['woot'], $options);
181 // Force re-run of the post-update woot_post_update_install_devel().
182 $this->forcePostUpdate('woot_post_update_install_devel', $options);
184 // Force a flush of the dependency injection container, so that we can
185 // test that the container can be correctly rebuilt even if new services
186 // are introduced that depend on modules that are not enabled yet.
187 $this->drush('unit-invalidate-container', [], $options);
189 // Introduce a new service in the Woot module that depends on a service
190 // in the Devel module (which is not yet enabled).
191 $filename = Path::join($root, 'modules/unish/woot/woot.services.yml');
192 $serviceDefinition = <<<YAML_FRAGMENT
193 woot.depending_service:
194 class: Drupal\woot\DependingService
195 arguments: ['@devel.dumper']
197 file_put_contents($filename, $serviceDefinition, FILE_APPEND);
199 $filename = Path::join($root, 'modules/unish/woot/woot.info.yml');
200 $moduleDependency = <<<YAML_FRAGMENT
204 file_put_contents($filename, $moduleDependency, FILE_APPEND);
207 $this->drush('updatedb');
209 // Assert that the updates were run correctly.
210 $this->drush('updatedb:status', [], ['format' => 'json']);
211 $out = $this->getOutputFromJSON();
212 $this->assertNull($out);
216 * Tests that updates and post-updated can be executed successfully.
218 public function testSuccessfulUpdate()
220 $sites = $this->setUpDrupal(1, true);
223 'root' => $this->webroot(),
224 'uri' => key($sites),
226 $this->setupModulesForTests(['woot'], Path::join(__DIR__, 'resources/modules/d8'));
227 $this->drush('pm-enable', ['woot'], $options);
229 // Force re-run of woot_update_8103() which is expected to be completed successfully.
230 $this->drush('php:eval', array('drupal_set_installed_schema_version("woot", 8102)'), $options);
232 // Force re-run of post-update hooks which are expected to be completed successfully.
233 $this->forcePostUpdate('woot_post_update_a', $options);
234 $this->forcePostUpdate('woot_post_update_render', $options);
237 $this->drush('updatedb', [], $options, null, null, self::EXIT_SUCCESS);
239 $expected_output = <<<LOG
240 -------- ----------- --------------- -------------------------
241 Module Update ID Type Description
242 -------- ----------- --------------- -------------------------
243 woot 8103 hook_update_n Another good update.
244 woot a post-update Successful post-update.
245 woot render post-update Renders some content.
246 -------- ----------- --------------- -------------------------
248 // Do you wish to run the specified pending updates?: yes.
250 $this->assertOutputEquals(preg_replace('# *#', ' ', $this->simplifyOutput($expected_output)));
252 $expected_error_output = <<<LOG
253 [notice] Update started: woot_update_8103
254 [notice] This is the update message from woot_update_8103
255 [ok] Update completed: woot_update_8103
256 [notice] Update started: woot_post_update_a
257 [notice] This is the update message from woot_post_update_a
258 [ok] Update completed: woot_post_update_a
259 [notice] Update started: woot_post_update_render
260 [ok] Update completed: woot_post_update_render
261 [success] Finished performing updates.
264 $this->assertErrorOutputEquals(preg_replace('# *#', ' ', $this->simplifyOutput($expected_error_output)));
267 public function tearDown()
269 $this->recursiveDelete($this->pathPostUpdate, true);
274 * Forces a post-update hook to run again on the next database update.
276 * @param string $hook
277 * The name of the hook that needs to be run again.
278 * @param array $options
279 * An associative array containing options for the `sql:query` command.
281 protected function forcePostUpdate($hook, array $options)
283 $this->drush('sql:query', ["SELECT value FROM key_value WHERE collection = 'post_update' AND name = 'existing_updates'"], $options);
284 $functions = unserialize($this->getOutput());
285 unset($functions[array_search($hook, $functions)]);
286 $functions = serialize($functions);
287 $this->drush('sql:query', ["UPDATE key_value SET value = '$functions' WHERE collection = 'post_update' AND name = 'existing_updates'"], $options);