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 {
$active_dir = $this->pathLocator->getProjectRoot();
$stage_dir = $this->getStageDirectory();
$event = new PreApplyEvent($this);
$this->tempStore->set(self::TEMPSTORE_APPLY_TIME_KEY, $this->time->getRequestTime());
// If an error occurs while dispatching the event, ensure that ::destroy()
// If an error occurs while dispatching the events, ensure that ::destroy()
// doesn't think we're in the middle of applying the staged changes to the
// active directory.
$this->dispatch($event, function (): void {
$release_apply = function (): void {
$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->tempStore->delete(self::TEMPSTORE_APPLY_TIME_KEY);
// Rebuild the container and clear all caches, to ensure that new services
// are picked up.
......@@ -340,7 +341,8 @@ class Stage {
// unlikely to call newly added code during the current request.
$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;
use Drupal\Component\Datetime\Time;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\package_manager\Event\PostApplyEvent;
use Drupal\package_manager\Event\PreApplyEvent;
use Drupal\package_manager\Event\StageEvent;
use Drupal\package_manager\Exception\StageException;
/**
......@@ -14,6 +16,24 @@ use Drupal\package_manager\Exception\StageException;
*/
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}
*/
......@@ -22,6 +42,11 @@ class StageTest extends PackageManagerKernelTestBase {
$container->getDefinition('datetime.time')
->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 {
* @covers ::getStagingRoot
*/
public function testGetStageDirectory(): void {
$this->container->get('module_installer')->install(['system']);
// 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()
// Ensure that a site ID was generated in ::setUp().
$site_id = $this->config('system.site')->get('uuid');
$this->assertNotEmpty($site_id);
......@@ -68,16 +88,62 @@ class StageTest extends PackageManagerKernelTestBase {
*/
public function providerDestroyDuringApply(): array {
return [
'force destroy, not stale' => [TRUE, 1, TRUE],
'regular destroy, not stale' => [FALSE, 1, TRUE],
'force destroy, stale' => [TRUE, 7200, FALSE],
'regular destroy, stale' => [FALSE, 7200, FALSE],
'force destroy on pre-apply, fresh' => [
PreApplyEvent::class,
TRUE,
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.
*
* @param string $event_class
* The event class for which to attempt to destroy the stage.
* @param bool $force
* Whether or not the stage should be force destroyed.
* @param int $time_offset
......@@ -88,8 +154,8 @@ class StageTest extends PackageManagerKernelTestBase {
*
* @dataProvider providerDestroyDuringApply
*/
public function testDestroyDuringApply(bool $force, int $time_offset, bool $expect_exception): void {
$listener = function (PreApplyEvent $event) use ($force, $time_offset): void {
public function testDestroyDuringApply(string $event_class, bool $force, int $time_offset, bool $expect_exception): void {
$listener = function (StageEvent $event) use ($force, $time_offset): void {
// Simulate that a certain amount of time has passed since we started
// applying staged changes. After a point, it should be possible to
// destroy the stage even if it hasn't finished.
......@@ -102,7 +168,7 @@ class StageTest extends PackageManagerKernelTestBase {
$event->getStage()->destroy($force);
};
$this->container->get('event_dispatcher')
->addListener(PreApplyEvent::class, $listener);
->addListener($event_class, $listener);
$stage = $this->createStage();
$stage->create();
......
......@@ -55,15 +55,8 @@ abstract class AutomaticUpdatesKernelTestBase extends PackageManagerKernelTestBa
$this->installConfig('update');
// 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
// their updates, we need to do this so that all validators are tested from
// 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);
// have run.
$this->registerPostUpdateFunctions();
// By default, pretend we're running Drupal core 9.8.0 and a non-security
// 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