Skip to content
Snippets Groups Projects
Commit 5e5c7173 authored by Kunal Sachdev's avatar Kunal Sachdev Committed by Adam G-H
Browse files

Issue #3268868 by phenaproxima, kunal.sachdev: Do not allow a stage to be...

Issue #3268868 by phenaproxima, kunal.sachdev: Do not allow a stage to be destroyed until the post-apply event is complete
parent da859f5d
No related branches found
No related tags found
No related merge requests found
...@@ -320,17 +320,18 @@ class Stage { ...@@ -320,17 +320,18 @@ class Stage {
$active_dir = $this->pathLocator->getProjectRoot(); $active_dir = $this->pathLocator->getProjectRoot();
$stage_dir = $this->getStageDirectory(); $stage_dir = $this->getStageDirectory();
$event = new PreApplyEvent($this); // If an error occurs while dispatching the events, ensure that ::destroy()
$this->tempStore->set(self::TEMPSTORE_APPLY_TIME_KEY, $this->time->getRequestTime());
// If an error occurs while dispatching the event, ensure that ::destroy()
// doesn't think we're in the middle of applying the staged changes to the // doesn't think we're in the middle of applying the staged changes to the
// active directory. // active directory.
$this->dispatch($event, function (): void { $release_apply = function (): void {
$this->tempStore->delete(self::TEMPSTORE_APPLY_TIME_KEY); $this->tempStore->delete(self::TEMPSTORE_APPLY_TIME_KEY);
}); };
$event = new PreApplyEvent($this);
$this->tempStore->set(self::TEMPSTORE_APPLY_TIME_KEY, $this->time->getRequestTime());
$this->dispatch($event, $release_apply);
$this->committer->commit($stage_dir, $active_dir, $event->getExcludedPaths()); $this->committer->commit($stage_dir, $active_dir, $event->getExcludedPaths());
$this->tempStore->delete(self::TEMPSTORE_APPLY_TIME_KEY);
// Rebuild the container and clear all caches, to ensure that new services // Rebuild the container and clear all caches, to ensure that new services
// are picked up. // are picked up.
...@@ -340,7 +341,8 @@ class Stage { ...@@ -340,7 +341,8 @@ class Stage {
// unlikely to call newly added code during the current request. // unlikely to call newly added code during the current request.
$this->eventDispatcher = \Drupal::service('event_dispatcher'); $this->eventDispatcher = \Drupal::service('event_dispatcher');
$this->dispatch(new PostApplyEvent($this)); $this->dispatch(new PostApplyEvent($this), $release_apply);
$release_apply();
} }
/** /**
......
...@@ -4,7 +4,9 @@ namespace Drupal\Tests\package_manager\Kernel; ...@@ -4,7 +4,9 @@ namespace Drupal\Tests\package_manager\Kernel;
use Drupal\Component\Datetime\Time; use Drupal\Component\Datetime\Time;
use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\package_manager\Event\PostApplyEvent;
use Drupal\package_manager\Event\PreApplyEvent; use Drupal\package_manager\Event\PreApplyEvent;
use Drupal\package_manager\Event\StageEvent;
use Drupal\package_manager\Exception\StageException; use Drupal\package_manager\Exception\StageException;
/** /**
...@@ -14,6 +16,24 @@ use Drupal\package_manager\Exception\StageException; ...@@ -14,6 +16,24 @@ use Drupal\package_manager\Exception\StageException;
*/ */
class StageTest extends PackageManagerKernelTestBase { class StageTest extends PackageManagerKernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['system'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installConfig('system');
$this->config('system.site')->set('uuid', $this->randomMachineName())->save();
// Ensure that the core update system thinks that System's post-update
// functions have run.
$this->registerPostUpdateFunctions();
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
...@@ -22,6 +42,11 @@ class StageTest extends PackageManagerKernelTestBase { ...@@ -22,6 +42,11 @@ class StageTest extends PackageManagerKernelTestBase {
$container->getDefinition('datetime.time') $container->getDefinition('datetime.time')
->setClass(TestTime::class); ->setClass(TestTime::class);
// Since this test adds arbitrary event listeners that aren't services, we
// need to ensure they will persist even if the container is rebuilt when
// staged changes are applied.
$container->getDefinition('event_dispatcher')->addTag('persist');
} }
/** /**
...@@ -29,12 +54,7 @@ class StageTest extends PackageManagerKernelTestBase { ...@@ -29,12 +54,7 @@ class StageTest extends PackageManagerKernelTestBase {
* @covers ::getStagingRoot * @covers ::getStagingRoot
*/ */
public function testGetStageDirectory(): void { public function testGetStageDirectory(): void {
$this->container->get('module_installer')->install(['system']); // Ensure that a site ID was generated in ::setUp().
// Ensure we have an up-to-date-container.
$this->container = $this->container->get('kernel')->getContainer();
// Ensure that a site ID was generated.
// @see system_install()
$site_id = $this->config('system.site')->get('uuid'); $site_id = $this->config('system.site')->get('uuid');
$this->assertNotEmpty($site_id); $this->assertNotEmpty($site_id);
...@@ -68,16 +88,62 @@ class StageTest extends PackageManagerKernelTestBase { ...@@ -68,16 +88,62 @@ class StageTest extends PackageManagerKernelTestBase {
*/ */
public function providerDestroyDuringApply(): array { public function providerDestroyDuringApply(): array {
return [ return [
'force destroy, not stale' => [TRUE, 1, TRUE], 'force destroy on pre-apply, fresh' => [
'regular destroy, not stale' => [FALSE, 1, TRUE], PreApplyEvent::class,
'force destroy, stale' => [TRUE, 7200, FALSE], TRUE,
'regular destroy, stale' => [FALSE, 7200, FALSE], 1,
TRUE,
],
'destroy on pre-apply, fresh' => [
PreApplyEvent::class,
FALSE,
1,
TRUE,
],
'force destroy on pre-apply, stale' => [
PreApplyEvent::class,
TRUE,
7200,
FALSE,
],
'destroy on pre-apply, stale' => [
PreApplyEvent::class,
FALSE,
7200,
FALSE,
],
'force destroy on post-apply, fresh' => [
PostApplyEvent::class,
TRUE,
1,
TRUE,
],
'destroy on post-apply, fresh' => [
PostApplyEvent::class,
FALSE,
1,
TRUE,
],
'force destroy on post-apply, stale' => [
PostApplyEvent::class,
TRUE,
7200,
FALSE,
],
'destroy on post-apply, stale' => [
PostApplyEvent::class,
FALSE,
7200,
FALSE,
],
]; ];
} }
/** /**
* Tests destroying a stage while applying it. * Tests destroying a stage while applying it.
* *
* @param string $event_class
* The event class for which to attempt to destroy the stage.
* @param bool $force * @param bool $force
* Whether or not the stage should be force destroyed. * Whether or not the stage should be force destroyed.
* @param int $time_offset * @param int $time_offset
...@@ -88,8 +154,8 @@ class StageTest extends PackageManagerKernelTestBase { ...@@ -88,8 +154,8 @@ class StageTest extends PackageManagerKernelTestBase {
* *
* @dataProvider providerDestroyDuringApply * @dataProvider providerDestroyDuringApply
*/ */
public function testDestroyDuringApply(bool $force, int $time_offset, bool $expect_exception): void { public function testDestroyDuringApply(string $event_class, bool $force, int $time_offset, bool $expect_exception): void {
$listener = function (PreApplyEvent $event) use ($force, $time_offset): void { $listener = function (StageEvent $event) use ($force, $time_offset): void {
// Simulate that a certain amount of time has passed since we started // Simulate that a certain amount of time has passed since we started
// applying staged changes. After a point, it should be possible to // applying staged changes. After a point, it should be possible to
// destroy the stage even if it hasn't finished. // destroy the stage even if it hasn't finished.
...@@ -102,7 +168,7 @@ class StageTest extends PackageManagerKernelTestBase { ...@@ -102,7 +168,7 @@ class StageTest extends PackageManagerKernelTestBase {
$event->getStage()->destroy($force); $event->getStage()->destroy($force);
}; };
$this->container->get('event_dispatcher') $this->container->get('event_dispatcher')
->addListener(PreApplyEvent::class, $listener); ->addListener($event_class, $listener);
$stage = $this->createStage(); $stage = $this->createStage();
$stage->create(); $stage->create();
......
...@@ -55,15 +55,8 @@ abstract class AutomaticUpdatesKernelTestBase extends PackageManagerKernelTestBa ...@@ -55,15 +55,8 @@ abstract class AutomaticUpdatesKernelTestBase extends PackageManagerKernelTestBa
$this->installConfig('update'); $this->installConfig('update');
// Make the update system think that all of System's post-update functions // Make the update system think that all of System's post-update functions
// have run. Since kernel tests don't normally install modules and register // have run.
// their updates, we need to do this so that all validators are tested from $this->registerPostUpdateFunctions();
// a clean, fully up-to-date state.
$updates = $this->container->get('update.post_update_registry')
->getPendingUpdateFunctions();
$this->container->get('keyvalue')
->get('post_update')
->set('existing_updates', $updates);
// By default, pretend we're running Drupal core 9.8.0 and a non-security // By default, pretend we're running Drupal core 9.8.0 and a non-security
// update to 9.8.1 is available. // update to 9.8.1 is available.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment