diff --git a/automatic_updates_extensions/src/BatchProcessor.php b/automatic_updates_extensions/src/BatchProcessor.php index bf2f48a37a4162ee1b83baf61151796cdcd3edba..9f1742301ad75085b416920d77803a8a19d8a7f9 100644 --- a/automatic_updates_extensions/src/BatchProcessor.php +++ b/automatic_updates_extensions/src/BatchProcessor.php @@ -5,7 +5,6 @@ declare(strict_types = 1); namespace Drupal\automatic_updates_extensions; use Drupal\Core\Url; -use Drupal\package_manager\Exception\StageValidationException; use Symfony\Component\HttpFoundation\RedirectResponse; /** @@ -47,23 +46,7 @@ final class BatchProcessor { * have been recorded. */ protected static function handleException(\Throwable $error, array &$context): void { - $error_messages = [ - $error->getMessage(), - ]; - - if ($error instanceof StageValidationException) { - foreach ($error->getResults() as $result) { - $messages = $result->getMessages(); - if (count($messages) > 1) { - array_unshift($messages, $result->getSummary()); - } - $error_messages = array_merge($error_messages, $messages); - } - } - - foreach ($error_messages as $error_message) { - $context['results']['errors'][] = $error_message; - } + $context['results']['errors'][] = $error->getMessage(); throw $error; } diff --git a/automatic_updates_extensions/src/ExtensionUpdater.php b/automatic_updates_extensions/src/ExtensionUpdater.php index 99031f8b30b316086dfb60ad9e6e44bbf2fa3339..7cae2d5fcd169529e7fb880b57a87bfd41f1f187 100644 --- a/automatic_updates_extensions/src/ExtensionUpdater.php +++ b/automatic_updates_extensions/src/ExtensionUpdater.php @@ -4,12 +4,8 @@ declare(strict_types = 1); namespace Drupal\automatic_updates_extensions; -use Drupal\automatic_updates\Exception\UpdateException; use Drupal\Core\StringTranslation\TranslatableMarkup; -use Drupal\package_manager\Exception\ApplyFailedException; use Drupal\package_manager\LegacyVersionUtility; -use Drupal\package_manager\Event\StageEvent; -use Drupal\package_manager\Exception\StageValidationException; use Drupal\package_manager\Stage; /** @@ -102,30 +98,6 @@ class ExtensionUpdater extends Stage { $this->require($versions['production'], $versions['dev']); } - /** - * {@inheritdoc} - */ - protected function dispatch(StageEvent $event, callable $on_error = NULL): void { - try { - parent::dispatch($event, $on_error); - } - catch (StageValidationException $e) { - throw new UpdateException($e->getResults(), $e->getMessage(), $e->getCode(), $e); - } - } - - /** - * {@inheritdoc} - */ - public function apply(?int $timeout = 600): void { - try { - parent::apply($timeout); - } - catch (ApplyFailedException $exception) { - throw new UpdateException([], 'The update operation failed to apply. The update may have been partially applied. It is recommended that the site be restored from a code backup.', $exception->getCode(), $exception); - } - } - /** * {@inheritdoc} */ diff --git a/automatic_updates_extensions/src/Form/UpdateReady.php b/automatic_updates_extensions/src/Form/UpdateReady.php index 587514e45a884a0aa382f5b6947f90f3f0ff91f7..4503724ab00473144aad25b7f130f03427e0804c 100644 --- a/automatic_updates_extensions/src/Form/UpdateReady.php +++ b/automatic_updates_extensions/src/Form/UpdateReady.php @@ -5,7 +5,7 @@ declare(strict_types = 1); namespace Drupal\automatic_updates_extensions\Form; use Drupal\automatic_updates\Form\UpdateFormBase; -use Drupal\package_manager\Exception\ApplyFailedException; +use Drupal\package_manager\Exception\StageFailureMarkerException; use Drupal\package_manager\ProjectInfo; use Drupal\package_manager\ValidationResult; use Drupal\automatic_updates_extensions\BatchProcessor; @@ -124,7 +124,7 @@ final class UpdateReady extends UpdateFormBase { $this->messenger()->addError($this->t('Cannot continue the update because another Composer operation is currently in progress.')); return $form; } - catch (ApplyFailedException $e) { + catch (StageFailureMarkerException $e) { $this->messenger()->addError($e->getMessage()); return $form; } diff --git a/automatic_updates_extensions/src/Form/UpdaterForm.php b/automatic_updates_extensions/src/Form/UpdaterForm.php index dec4b3fc609b1984eb26b524e6c518015d2233dd..d738728900a8371c0e44f6304f695f0199a5ac69 100644 --- a/automatic_updates_extensions/src/Form/UpdaterForm.php +++ b/automatic_updates_extensions/src/Form/UpdaterForm.php @@ -13,7 +13,7 @@ use Drupal\Core\State\StateInterface; use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Render\RendererInterface; use Drupal\Core\Url; -use Drupal\package_manager\Exception\ApplyFailedException; +use Drupal\package_manager\Exception\StageFailureMarkerException; use Drupal\package_manager\FailureMarker; use Drupal\package_manager\ProjectInfo; use Drupal\package_manager\ValidationResult; @@ -114,7 +114,7 @@ final class UpdaterForm extends UpdateFormBase { try { $this->failureMarker->assertNotExists(); } - catch (ApplyFailedException $e) { + catch (StageFailureMarkerException $e) { $this->messenger()->addError($e->getMessage()); return $form; } diff --git a/automatic_updates_extensions/tests/src/Functional/UpdateErrorTest.php b/automatic_updates_extensions/tests/src/Functional/UpdateErrorTest.php index f72cd4a6ef95db9fe41053c775cd00b402d86956..860f5e0a84746e7ef13a8e80f396331b3eae3833 100644 --- a/automatic_updates_extensions/tests/src/Functional/UpdateErrorTest.php +++ b/automatic_updates_extensions/tests/src/Functional/UpdateErrorTest.php @@ -41,11 +41,11 @@ class UpdateErrorTest extends UpdaterFormTestBase { LoggingCommitter::setException(new \Exception('failed at committer')); $page->pressButton('Continue'); $this->checkForMetaRefresh(); + $failure_message = 'Automatic updates failed to apply, and the site is in an indeterminate state. Consider restoring the code and database from a backup.'; $assert_session->pageTextContainsOnce('An error has occurred.'); - $assert_session->pageTextContains('The update operation failed to apply. The update may have been partially applied. It is recommended that the site be restored from a code backup.'); + $assert_session->pageTextContains($failure_message); $page->clickLink('the error page'); - $failure_message = 'Automatic updates failed to apply, and the site is in an indeterminate state. Consider restoring the code and database from a backup.'; // We should be on the form (i.e., 200 response code), but unable to // continue the update. $assert_session->statusCodeEquals(200); diff --git a/automatic_updates_extensions/tests/src/Kernel/AutomaticUpdatesExtensionsKernelTestBase.php b/automatic_updates_extensions/tests/src/Kernel/AutomaticUpdatesExtensionsKernelTestBase.php index 0e44926fc1f805f7b738b4f4519644269270bb48..24ee03b57533df1c10cb103effede98716eab22c 100644 --- a/automatic_updates_extensions/tests/src/Kernel/AutomaticUpdatesExtensionsKernelTestBase.php +++ b/automatic_updates_extensions/tests/src/Kernel/AutomaticUpdatesExtensionsKernelTestBase.php @@ -4,13 +4,9 @@ declare(strict_types = 1); namespace Drupal\Tests\automatic_updates_extensions\Kernel; -use Drupal\automatic_updates\Exception\UpdateException; -use Drupal\automatic_updates_extensions\ExtensionUpdater; -use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\package_manager\Event\PreOperationStageEvent; +use Drupal\package_manager\Exception\StageEventException; use Drupal\Tests\automatic_updates\Kernel\AutomaticUpdatesKernelTestBase; -use Drupal\Tests\package_manager\Kernel\TestStageTrait; -use Drupal\Tests\package_manager\Kernel\TestStageValidationException; -use PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactory; /** * Base class for kernel tests of the Automatic Updates Extensions module. @@ -51,23 +47,6 @@ abstract class AutomaticUpdatesExtensionsKernelTestBase extends AutomaticUpdates parent::createTestProject($source_dir); } - /** - * {@inheritdoc} - */ - public function register(ContainerBuilder $container) { - parent::register($container); - - // Use the test-only implementations of the regular and cron updaters. - $overrides = [ - 'automatic_updates_extensions.updater' => TestExtensionUpdater::class, - ]; - foreach ($overrides as $service_id => $class) { - if ($container->hasDefinition($service_id)) { - $container->getDefinition($service_id)->setClass($class); - } - } - } - /** * Asserts validation results are returned from a stage life cycle event. * @@ -80,7 +59,7 @@ abstract class AutomaticUpdatesExtensionsKernelTestBase extends AutomaticUpdates * be passed if $expected_results is not empty. */ protected function assertUpdateResults(array $project_versions, array $expected_results, string $event_class = NULL): void { - $updater = $this->createExtensionUpdater(); + $updater = $this->container->get('automatic_updates_extensions.updater'); try { $updater->begin($project_versions); @@ -92,45 +71,13 @@ abstract class AutomaticUpdatesExtensionsKernelTestBase extends AutomaticUpdates // If we did not get an exception, ensure we didn't expect any results. $this->assertEmpty($expected_results); } - catch (TestStageValidationException $e) { + catch (StageEventException $e) { $this->assertNotEmpty($expected_results); - $this->assertValidationResultsEqual($expected_results, $e->getResults()); - // TestStage::dispatch() throws TestUpdateException with event object - // so that we can analyze it. - $this->assertInstanceOf(UpdateException::class, $e->getOriginalException()); - $this->assertNotEmpty($event_class); - $this->assertInstanceOf($event_class, $e->getEvent()); + $exception_event = $e->event; + $this->assertInstanceOf($event_class, $exception_event); + $this->assertInstanceOf(PreOperationStageEvent::class, $exception_event); + $this->assertValidationResultsEqual($expected_results, $e->event->getResults()); } } - /** - * Creates an extension updater object for testing purposes. - * - * @return \Drupal\Tests\automatic_updates_extensions\Kernel\TestExtensionUpdater - * A extension updater object, with test-only modifications. - */ - protected function createExtensionUpdater(): TestExtensionUpdater { - return new TestExtensionUpdater( - $this->container->get('package_manager.path_locator'), - $this->container->get('package_manager.beginner'), - $this->container->get('package_manager.stager'), - $this->container->get('package_manager.committer'), - $this->container->get('file_system'), - $this->container->get('event_dispatcher'), - $this->container->get('tempstore.shared'), - $this->container->get('datetime.time'), - new PathFactory(), - $this->container->get('package_manager.failure_marker') - ); - } - -} - -/** - * A test-only version of the regular extension updater to override internals. - */ -class TestExtensionUpdater extends ExtensionUpdater { - - use TestStageTrait; - } diff --git a/automatic_updates_extensions/tests/src/Kernel/ExtensionUpdaterTest.php b/automatic_updates_extensions/tests/src/Kernel/ExtensionUpdaterTest.php index dd2da2f16515cdd97420ebe269906ba93f1497d0..69d15693e9e38c68c9227f6106a88e6740b34cfb 100644 --- a/automatic_updates_extensions/tests/src/Kernel/ExtensionUpdaterTest.php +++ b/automatic_updates_extensions/tests/src/Kernel/ExtensionUpdaterTest.php @@ -4,13 +4,6 @@ declare(strict_types = 1); namespace Drupal\Tests\automatic_updates_extensions\Kernel; -use Drupal\automatic_updates\Exception\UpdateException; -use Drupal\automatic_updates_test\EventSubscriber\TestSubscriber1; -use Drupal\package_manager\Event\PreApplyEvent; -use Drupal\package_manager\Event\PreCreateEvent; -use Drupal\package_manager\Event\PreRequireEvent; -use Drupal\package_manager\ValidationResult; -use Drupal\Tests\package_manager\Kernel\TestStageValidationException; use Drupal\Tests\user\Traits\UserCreationTrait; /** @@ -152,51 +145,4 @@ class ExtensionUpdaterTest extends AutomaticUpdatesExtensionsKernelTestBase { ]); } - /** - * Tests UpdateException handling. - * - * @param string $event_class - * The stage life cycle event which should raise an error. - * - * @dataProvider providerUpdateException - */ - public function testUpdateException(string $event_class): void { - $extension_updater = $this->container->get('automatic_updates_extensions.updater'); - $results = [ - ValidationResult::createError([t('An error of some sorts.')]), - ]; - TestSubscriber1::setTestResult($results, $event_class); - try { - $extension_updater->begin(['my_module' => '9.8.1']); - $extension_updater->stage(); - $extension_updater->apply(); - $this->fail('Expected an exception, but none was raised.'); - } - catch (TestStageValidationException $e) { - $this->assertStringStartsWith('An error of some sorts.', $e->getMessage()); - $this->assertInstanceOf(UpdateException::class, $e->getOriginalException()); - $this->assertInstanceOf($event_class, $e->getEvent()); - } - } - - /** - * Data provider for testUpdateException(). - * - * @return string[][] - * The test cases. - */ - public function providerUpdateException(): array { - return [ - 'pre-create exception' => [ - PreCreateEvent::class, - ], - 'pre-require exception' => [ - PreRequireEvent::class, - ], - 'pre-apply exception' => [ - PreApplyEvent::class, - ], - ]; - } - } diff --git a/package_manager/package_manager.api.php b/package_manager/package_manager.api.php index 4e58300c4cf3127e321cd7710d3e8c5b6fe1181e..7da700f4d8607be08eb0bc8ab5961e899e5bddc0 100644 --- a/package_manager/package_manager.api.php +++ b/package_manager/package_manager.api.php @@ -146,7 +146,7 @@ * If problems occur during any point of the stage life cycle, a * \Drupal\package_manager\Exception\StageException is thrown. If problems were * detected during one of the "pre" operations, a subclass of that is thrown: - * \Drupal\package_manager\Exception\StageValidationException. This will contain + * \Drupal\package_manager\Exception\StageEventException. This will contain * \Drupal\package_manager\ValidationResult objects. * The Package Manager module does not catch or handle these exceptions: they * provide the foundation for other modules to build user experiences for diff --git a/package_manager/package_manager.install b/package_manager/package_manager.install index fa864d70da858e2007a27ca072d3ad40dd4c9634..aab5d430d70631de81150d5273a154c27450dd40 100644 --- a/package_manager/package_manager.install +++ b/package_manager/package_manager.install @@ -7,7 +7,7 @@ declare(strict_types = 1); -use Drupal\package_manager\Exception\ApplyFailedException; +use Drupal\package_manager\Exception\StageFailureMarkerException; /** * Implements hook_requirements(). @@ -29,9 +29,9 @@ function package_manager_requirements(string $phase) { $service_id = 'package_manager.failure_marker'; if (\Drupal::hasService($service_id)) { try { - \Drupal::service($service_id)->assertNotExists(); + \Drupal::service($service_id)->assertNotExists(NULL); } - catch (ApplyFailedException $exception) { + catch (StageFailureMarkerException $exception) { $requirements['package_manager_failure_marker'] = [ 'title' => t('Failed update detected'), 'description' => $exception->getMessage(), diff --git a/package_manager/src/Exception/ApplyFailedException.php b/package_manager/src/Exception/ApplyFailedException.php index 8445e8dcf8b56db1aadbe5910dbf2418112b8457..7cf59d149a97aa76068b1ab0444e320a6d692bf9 100644 --- a/package_manager/src/Exception/ApplyFailedException.php +++ b/package_manager/src/Exception/ApplyFailedException.php @@ -12,6 +12,10 @@ namespace Drupal\package_manager\Exception; * indeterminate state. Package Manager does not provide a method for recovering * from this state. The site code should be restored from a backup. * + * This exception is different from StageFailureMarkerException in that it is + * thrown if an error happens *during* the apply operation, rather than before + * or after it. + * * Should not be thrown by external code. */ final class ApplyFailedException extends StageException { diff --git a/package_manager/src/Exception/StageEventException.php b/package_manager/src/Exception/StageEventException.php new file mode 100644 index 0000000000000000000000000000000000000000..971faf1e26bd28dd9aa44841a7820dcc55219503 --- /dev/null +++ b/package_manager/src/Exception/StageEventException.php @@ -0,0 +1,56 @@ +<?php + +declare(strict_types = 1); + +namespace Drupal\package_manager\Exception; + +use Drupal\package_manager\Event\PreOperationStageEvent; +use Drupal\package_manager\Event\StageEvent; + +/** + * Exception thrown if an error related to an event occurs. + * + * This exception is thrown when an error strictly associated with an event + * occurs. This is also what makes it different from StageException. + * + * Should not be thrown by external code. + */ +class StageEventException extends StageException { + + /** + * Constructs a StageEventException object. + * + * @param \Drupal\package_manager\Event\StageEvent $event + * The stage event during which this exception is thrown. + * @param string|null $message + * (optional) The exception message. Defaults to a plain text representation + * of the validation results. + * @param mixed ...$arguments + * Additional arguments to pass to the parent constructor. + */ + public function __construct(public readonly StageEvent $event, ?string $message = NULL, ...$arguments) { + parent::__construct($event->stage, $message ?: $this->getResultsAsText(), ...$arguments); + } + + /** + * Formats the validation results, if any, as plain text. + * + * @return string + * The results, formatted as plain text. + */ + protected function getResultsAsText(): string { + $text = ''; + if ($this->event instanceof PreOperationStageEvent) { + foreach ($this->event->getResults() as $result) { + $messages = $result->getMessages(); + $summary = $result->getSummary(); + if ($summary) { + array_unshift($messages, $summary); + } + $text .= implode("\n", $messages) . "\n"; + } + } + return $text; + } + +} diff --git a/package_manager/src/Exception/StageException.php b/package_manager/src/Exception/StageException.php index c9b1632a3e1b77474d027f8d8c840d3573b63afa..2d2bc8a1bafaab55874550dd69cbe02c1f750fe7 100644 --- a/package_manager/src/Exception/StageException.php +++ b/package_manager/src/Exception/StageException.php @@ -4,10 +4,25 @@ declare(strict_types = 1); namespace Drupal\package_manager\Exception; +use Drupal\package_manager\Stage; + /** * Base class for all exceptions related to stage operations. * * Should not be thrown by external code. */ class StageException extends \RuntimeException { + + /** + * Constructs a StageException object. + * + * @param \Drupal\package_manager\Stage $stage + * The stage. + * @param mixed ...$arguments + * Additional arguments to pass to the parent constructor. + */ + public function __construct(public readonly Stage $stage, ...$arguments) { + parent::__construct(...$arguments); + } + } diff --git a/package_manager/src/Exception/StageFailureMarkerException.php b/package_manager/src/Exception/StageFailureMarkerException.php new file mode 100644 index 0000000000000000000000000000000000000000..ede028c14e133add2539407b70515d8c8c056980 --- /dev/null +++ b/package_manager/src/Exception/StageFailureMarkerException.php @@ -0,0 +1,22 @@ +<?php + +declare(strict_types = 1); + +namespace Drupal\package_manager\Exception; + +/** + * Exception thrown if a stage can't be created due to an earlier failed commit. + * + * If this exception is thrown it indicates that an earlier commit operation had + * failed. If this happens the site code is in an indeterminate state. Package + * Manager does not provide a method for recovering from this state. The site + * code should be restored from a backup. + * + * We are extending RuntimeException rather than StageException which makes it + * clearer that it's unrelated to the stage life cycle. + * + * This exception is different from ApplyFailedException as it focuses on + * the failure marker being detected outside the stage lifecycle. + */ +final class StageFailureMarkerException extends \RuntimeException { +} diff --git a/package_manager/src/Exception/StageValidationException.php b/package_manager/src/Exception/StageValidationException.php deleted file mode 100644 index 7286d8adecc4d0ad50c79726ee928378c735cc1b..0000000000000000000000000000000000000000 --- a/package_manager/src/Exception/StageValidationException.php +++ /dev/null @@ -1,67 +0,0 @@ -<?php - -declare(strict_types = 1); - -namespace Drupal\package_manager\Exception; - -/** - * Exception thrown if a stage has validation errors. - * - * Should not be thrown by external code. - */ -class StageValidationException extends StageException { - - /** - * Any relevant validation results. - * - * @var \Drupal\package_manager\ValidationResult[] - */ - protected $results = []; - - /** - * Constructs a StageException object. - * - * @param \Drupal\package_manager\ValidationResult[] $results - * Any relevant validation results. - * @param string $message - * (optional) The exception message. Defaults to a plain text representation - * of the validation results. - * @param mixed ...$arguments - * Additional arguments to pass to the parent constructor. - */ - public function __construct(array $results = [], string $message = '', ...$arguments) { - $this->results = $results; - parent::__construct($message ?: $this->getResultsAsText(), ...$arguments); - } - - /** - * Gets the validation results. - * - * @return \Drupal\package_manager\ValidationResult[] - * The validation results. - */ - public function getResults(): array { - return $this->results; - } - - /** - * Formats the validation results as plain text. - * - * @return string - * The results, formatted as plain text. - */ - protected function getResultsAsText(): string { - $text = ''; - - foreach ($this->getResults() as $result) { - $messages = $result->getMessages(); - $summary = $result->getSummary(); - if ($summary) { - array_unshift($messages, $summary); - } - $text .= implode("\n", $messages) . "\n"; - } - return $text; - } - -} diff --git a/package_manager/src/FailureMarker.php b/package_manager/src/FailureMarker.php index 3ef94e6532c02ee93a89fc823db51edd3e894253..d18e011aae62c16e5cd9e56130c943592cc57bb3 100644 --- a/package_manager/src/FailureMarker.php +++ b/package_manager/src/FailureMarker.php @@ -6,7 +6,7 @@ namespace Drupal\package_manager; use Drupal\Component\Serialization\Json; use Drupal\Core\StringTranslation\TranslatableMarkup; -use Drupal\package_manager\Exception\ApplyFailedException; +use Drupal\package_manager\Exception\StageFailureMarkerException; /** * Handles failure marker file operation. @@ -82,10 +82,10 @@ final class FailureMarker { $data = json_decode($data, TRUE, 512, JSON_THROW_ON_ERROR); } catch (\JsonException $exception) { - throw new ApplyFailedException('Failure marker file exists but cannot be decoded.', $exception->getCode(), $exception); + throw new StageFailureMarkerException('Failure marker file exists but cannot be decoded.', $exception->getCode(), $exception); } - throw new ApplyFailedException($data['message']); + throw new StageFailureMarkerException($data['message']); } } diff --git a/package_manager/src/Stage.php b/package_manager/src/Stage.php index 9d727e615567eb4b3f124b066a90888396f60f69..9a6d5d7f1850b6cad5373b50faa4563916542df7 100644 --- a/package_manager/src/Stage.php +++ b/package_manager/src/Stage.php @@ -24,9 +24,9 @@ use Drupal\package_manager\Event\PreOperationStageEvent; use Drupal\package_manager\Event\PreRequireEvent; use Drupal\package_manager\Event\StageEvent; use Drupal\package_manager\Exception\ApplyFailedException; +use Drupal\package_manager\Exception\StageEventException; use Drupal\package_manager\Exception\StageException; use Drupal\package_manager\Exception\StageOwnershipException; -use Drupal\package_manager\Exception\StageValidationException; use PhpTuf\ComposerStager\Domain\Core\Beginner\BeginnerInterface; use PhpTuf\ComposerStager\Domain\Core\Committer\CommitterInterface; use PhpTuf\ComposerStager\Domain\Core\Stager\StagerInterface; @@ -267,7 +267,7 @@ class Stage implements LoggerAwareInterface { $this->failureMarker->assertNotExists(); if (!$this->isAvailable()) { - throw new StageException('Cannot create a new stage because one already exists.'); + throw new StageException($this, 'Cannot create a new stage because one already exists.'); } // Mark the stage as unavailable as early as possible, before dispatching // the pre-create event. The idea is to prevent a race condition if the @@ -292,7 +292,7 @@ class Stage implements LoggerAwareInterface { $ignored_paths = $this->getIgnoredPaths(); } catch (\Throwable $throwable) { - throw new StageException($throwable->getMessage(), $throwable->getCode(), $throwable); + throw new StageException($this, $throwable->getMessage(), $throwable->getCode(), $throwable); } $event = new PreCreateEvent($this, $ignored_paths); // If an error occurs and we won't be able to create the stage, mark it as @@ -377,7 +377,7 @@ class Stage implements LoggerAwareInterface { $ignored_paths = $this->getIgnoredPaths(); } catch (\Throwable $throwable) { - throw new StageException($throwable->getMessage(), $throwable->getCode(), $throwable); + throw new StageException($this, $throwable->getMessage(), $throwable->getCode(), $throwable); } // If an error occurs while dispatching the events, ensure that ::destroy() @@ -402,7 +402,7 @@ class Stage implements LoggerAwareInterface { // The commit operation has not started yet, so we can clear the failure // marker. $this->failureMarker->clear(); - throw new StageException($e->getMessage(), $e->getCode(), $e); + throw new StageException($this, $e->getMessage(), $e->getCode(), $e); } catch (\Throwable $throwable) { // The commit operation may have failed midway through, and the site code @@ -410,7 +410,7 @@ class Stage implements LoggerAwareInterface { // applying, because in this situation, the site owner should probably // restore everything from a backup. $this->setNotApplying()(); - throw new ApplyFailedException($throwable->getMessage(), $throwable->getCode(), $throwable); + throw new ApplyFailedException($this, (string) $this->getFailureMarkerMessage(), $throwable->getCode(), $throwable); } $this->failureMarker->clear(); $this->setMetadata(self::TEMPSTORE_CHANGES_APPLIED, TRUE); @@ -469,7 +469,7 @@ class Stage implements LoggerAwareInterface { $this->checkOwnership(); } if ($this->isApplying()) { - throw new StageException('Cannot destroy the stage directory while it is being applied to the active directory.'); + throw new StageException($this, 'Cannot destroy the stage directory while it is being applied to the active directory.'); } $this->dispatch(new PreDestroyEvent($this)); @@ -513,24 +513,21 @@ class Stage implements LoggerAwareInterface { * (optional) A callback function to call if an error occurs, before any * exceptions are thrown. * - * @throws \Drupal\package_manager\Exception\StageValidationException + * @throws \Drupal\package_manager\Exception\StageEventException * If the event collects any validation errors. - * @throws \Drupal\package_manager\Exception\StageException - * If any other sort of error occurs. */ protected function dispatch(StageEvent $event, callable $on_error = NULL): void { try { $this->eventDispatcher->dispatch($event); if ($event instanceof PreOperationStageEvent) { - $results = $event->getResults(); - if ($results) { - $error = new StageValidationException($results); + if ($event->getResults()) { + $error = new StageEventException($event); } } } catch (\Throwable $error) { - $error = new StageException($error->getMessage(), $error->getCode(), $error); + $error = new StageEventException($event, $error->getMessage(), $error->getCode(), $error); } if (isset($error)) { @@ -595,7 +592,7 @@ class Stage implements LoggerAwareInterface { if ($this->isAvailable()) { // phpcs:disable DrupalPractice.General.ExceptionT.ExceptionT // @see https://www.drupal.org/project/automatic_updates/issues/3338651 - throw new StageException($this->computeDestroyMessage( + throw new StageException($this, $this->computeDestroyMessage( $unique_id, $this->t('Cannot claim the stage because no stage has been created.') )->render()); @@ -603,7 +600,7 @@ class Stage implements LoggerAwareInterface { $stored_lock = $this->tempStore->getIfOwner(static::TEMPSTORE_LOCK_KEY); if (!$stored_lock) { - throw new StageOwnershipException($this->computeDestroyMessage( + throw new StageOwnershipException($this, $this->computeDestroyMessage( $unique_id, $this->t('Cannot claim the stage because it is not owned by the current user or session.') )->render()); @@ -614,7 +611,7 @@ class Stage implements LoggerAwareInterface { return $this; } - throw new StageOwnershipException($this->computeDestroyMessage( + throw new StageOwnershipException($this, $this->computeDestroyMessage( $unique_id, $this->t('Cannot claim the stage because the current lock does not match the stored lock.') )->render()); @@ -659,7 +656,7 @@ class Stage implements LoggerAwareInterface { $stored_lock = $this->tempStore->getIfOwner(static::TEMPSTORE_LOCK_KEY); if ($stored_lock !== $this->lock) { - throw new StageOwnershipException('Stage is not owned by the current user or session.'); + throw new StageOwnershipException($this, 'Stage is not owned by the current user or session.'); } } diff --git a/package_manager/tests/src/Kernel/ComposerMinimumStabilityValidatorTest.php b/package_manager/tests/src/Kernel/ComposerMinimumStabilityValidatorTest.php index 5cb13665d048f099ad0297b2320a8a7568b8876b..f3ff2e8ce62872cc7ff1e3eb5c440777315483db 100644 --- a/package_manager/tests/src/Kernel/ComposerMinimumStabilityValidatorTest.php +++ b/package_manager/tests/src/Kernel/ComposerMinimumStabilityValidatorTest.php @@ -4,7 +4,7 @@ declare(strict_types = 1); namespace Drupal\Tests\package_manager\Kernel; -use Drupal\package_manager\Exception\StageValidationException; +use Drupal\package_manager\Exception\StageEventException; use Drupal\package_manager\ValidationResult; /** @@ -27,8 +27,8 @@ class ComposerMinimumStabilityValidatorTest extends PackageManagerKernelTestBase $stage->require(['drupal/core:9.8.1-beta1']); $this->fail('Able to require a package even though it did not meet minimum stability.'); } - catch (StageValidationException $exception) { - $this->assertValidationResultsEqual([$result], $exception->getResults()); + catch (StageEventException $exception) { + $this->assertValidationResultsEqual([$result], $exception->event->getResults()); } $stage->destroy(); diff --git a/package_manager/tests/src/Kernel/ComposerPatchesValidatorTest.php b/package_manager/tests/src/Kernel/ComposerPatchesValidatorTest.php index e2781023c4103d02c2322ec9619d42e5c47bc648..d694b8b83164c2ccbbf83b43796047a5d43defed 100644 --- a/package_manager/tests/src/Kernel/ComposerPatchesValidatorTest.php +++ b/package_manager/tests/src/Kernel/ComposerPatchesValidatorTest.php @@ -8,6 +8,7 @@ use Drupal\Core\Url; use Drupal\fixture_manipulator\ActiveFixtureManipulator; use Drupal\package_manager\Event\PreCreateEvent; use Drupal\package_manager\Event\StatusCheckEvent; +use Drupal\package_manager\Exception\StageEventException; use Drupal\package_manager\ValidationResult; use Symfony\Component\Process\Process; @@ -162,12 +163,12 @@ class ComposerPatchesValidatorTest extends PackageManagerKernelTestBase { try { $stage->apply(); - // If we didn't get an exception, ensure we didn't expect any errors + // If we didn't get an exception, ensure we didn't expect any errors. $this->assertSame([], $expected_results); } - catch (TestStageValidationException $e) { + catch (StageEventException $e) { $this->assertNotEmpty($expected_results); - $this->assertValidationResultsEqual($expected_results, $e->getResults(), NULL, $stage_dir); + $this->assertValidationResultsEqual($expected_results, $e->event->getResults(), NULL, $stage_dir); } } diff --git a/package_manager/tests/src/Kernel/DuplicateInfoFileValidatorTest.php b/package_manager/tests/src/Kernel/DuplicateInfoFileValidatorTest.php index d5c4c3e3a282c99598d8d469c15df0e5ee8f3bb5..980eb5cdd8a005502afb257d8238cba45ac9fcc4 100644 --- a/package_manager/tests/src/Kernel/DuplicateInfoFileValidatorTest.php +++ b/package_manager/tests/src/Kernel/DuplicateInfoFileValidatorTest.php @@ -4,7 +4,7 @@ declare(strict_types = 1); namespace Drupal\Tests\package_manager\Kernel; -use Drupal\package_manager\Exception\StageValidationException; +use Drupal\package_manager\Exception\StageEventException; use Drupal\package_manager\ValidationResult; use Symfony\Component\Filesystem\Filesystem; @@ -214,9 +214,9 @@ class DuplicateInfoFileValidatorTest extends PackageManagerKernelTestBase { $stage->apply(); $this->assertEmpty($expected_results); } - catch (StageValidationException $e) { + catch (StageEventException $e) { $this->assertNotEmpty($expected_results); - $this->assertValidationResultsEqual($expected_results, $e->getResults()); + $this->assertValidationResultsEqual($expected_results, $e->event->getResults()); } } diff --git a/package_manager/tests/src/Kernel/FailureMarkerTest.php b/package_manager/tests/src/Kernel/FailureMarkerTest.php index 97708dabb02ca436827a100b8866b070c7b4039d..c9cfbd831b0e80802089578d8e3ab7ba8373149b 100644 --- a/package_manager/tests/src/Kernel/FailureMarkerTest.php +++ b/package_manager/tests/src/Kernel/FailureMarkerTest.php @@ -5,7 +5,7 @@ declare(strict_types = 1); namespace Drupal\Tests\package_manager\Kernel; use Drupal\Core\StringTranslation\StringTranslationTrait; -use Drupal\package_manager\Exception\ApplyFailedException; +use Drupal\package_manager\Exception\StageFailureMarkerException; /** * @coversDefaultClass \Drupal\package_manager\FailureMarker @@ -22,7 +22,7 @@ class FailureMarkerTest extends PackageManagerKernelTestBase { $failure_marker = $this->container->get('package_manager.failure_marker'); $failure_marker->write($this->createStage(), $this->t('Disastrous catastrophe!')); - $this->expectException(ApplyFailedException::class); + $this->expectException(StageFailureMarkerException::class); $this->expectExceptionMessage('Disastrous catastrophe!'); $failure_marker->assertNotExists(); } @@ -37,7 +37,7 @@ class FailureMarkerTest extends PackageManagerKernelTestBase { // Write the failure marker with invalid JSON. file_put_contents($failure_marker->getPath(), '{}}'); - $this->expectException(ApplyFailedException::class); + $this->expectException(StageFailureMarkerException::class); $this->expectExceptionMessage('Failure marker file exists but cannot be decoded.'); $failure_marker->assertNotExists(); } diff --git a/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php b/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php index 95fc412280ac556ccadd48dbc7b1ced9387d5fac..7e9a9730ba2252f76189d32445272df518711d8e 100644 --- a/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php +++ b/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php @@ -10,11 +10,13 @@ use Drupal\Core\Site\Settings; use Drupal\fixture_manipulator\StageFixtureManipulator; use Drupal\KernelTests\KernelTestBase; use Drupal\package_manager\Event\PreApplyEvent; -use Drupal\package_manager\Event\StageEvent; +use Drupal\package_manager\Event\PreCreateEvent; +use Drupal\package_manager\Event\PreOperationStageEvent; +use Drupal\package_manager\Exception\StageEventException; use Drupal\package_manager\StatusCheckTrait; use Drupal\package_manager\Validator\DiskSpaceValidator; -use Drupal\package_manager\Exception\StageValidationException; use Drupal\package_manager\Stage; +use Drupal\system\SystemManager; use Drupal\Tests\package_manager\Traits\AssertPreconditionsTrait; use Drupal\Tests\package_manager\Traits\FixtureManipulatorTrait; use Drupal\Tests\package_manager\Traits\FixtureUtilityTrait; @@ -165,14 +167,9 @@ abstract class PackageManagerKernelTestBase extends KernelTestBase { // If we did not get an exception, ensure we didn't expect any results. $this->assertValidationResultsEqual([], $expected_results); } - catch (TestStageValidationException $e) { - $this->assertValidationResultsEqual($expected_results, $e->getResults()); + catch (StageEventException $e) { $this->assertNotEmpty($expected_results); - // TestStage::dispatch() throws TestStageValidationException with the - // event object so that we can analyze it. - $this->assertNotEmpty($event_class); - $this->assertInstanceOf(StageValidationException::class, $e->getOriginalException()); - $this->assertInstanceOf($event_class, $e->getEvent()); + $this->assertExpectedResultsFromException($expected_results, $e); } return $stage; } @@ -384,77 +381,44 @@ abstract class PackageManagerKernelTestBase extends KernelTestBase { StageFixtureManipulator::handleTearDown(); } -} - -/** - * Test-only class to associate event with StageValidationException. - * - * @todo Remove this class in https://drupal.org/i/3331355 or if that issue is - * closed without adding the ability to associate events with exceptions - * remove this comment. - */ -final class TestStageValidationException extends StageValidationException { - /** - * The stage event. + * Asserts that a StageEventException has a particular set of results. * - * @var \Drupal\package_manager\Event\StageEvent + * @param array $expected_results + * The expected results. + * @param \Drupal\package_manager\Exception\StageEventException $exception + * The exception. */ - private $event; - - /** - * The original exception. - * - * @var \Drupal\package_manager\Exception\StageValidationException - */ - private $originalException; - - public function __construct(StageValidationException $original_exception, StageEvent $event) { - parent::__construct($original_exception->getResults(), $original_exception->getMessage(), $original_exception->getCode(), $original_exception); - $this->originalException = $original_exception; - $this->event = $event; + protected function assertExpectedResultsFromException(array $expected_results, StageEventException $exception): void { + $event = $exception->event; + $this->assertInstanceOf(PreOperationStageEvent::class, $event); + $this->assertValidationResultsEqual($expected_results, $event->getResults()); } /** - * Gets the original exception which is triggered at the event. + * Creates a StageEventException from an array of validation results. * - * @return \Drupal\package_manager\Exception\StageValidationException - * Exception triggered at event. - */ - public function getOriginalException(): StageValidationException { - return $this->originalException; - } - - /** - * Gets the stage event which triggers the exception. + * @param \Drupal\package_manager\ValidationResult[] $expected_results + * The validation results. Note that only errors will be added to the event; + * warnings will be ignored. + * @param string $event_class + * (optional) The event which raised the exception. Defaults to + * PreCreateEvent. + * @param \Drupal\package_manager\Stage $stage + * (optional) The stage which caused the exception. * - * @return \Drupal\package_manager\Event\StageEvent - * Event triggering stage exception. + * @return \Drupal\package_manager\Exception\StageEventException + * An exception with the given validation results. */ - public function getEvent(): StageEvent { - return $this->event; - } - -} - -/** - * Common functions for test stages. - */ -trait TestStageTrait { + protected function createStageEventExceptionFromResults(array $expected_results, string $event_class = PreCreateEvent::class, Stage $stage = NULL): StageEventException { + $event = new $event_class($stage ?? $this->createStage(), []); - /** - * {@inheritdoc} - */ - protected function dispatch(StageEvent $event, callable $on_error = NULL): void { - try { - parent::dispatch($event, $on_error); - } - catch (StageValidationException $e) { - // Throw TestStageValidationException with event object so that test - // code can verify that the exception was thrown when a specific event was - // dispatched. - throw new TestStageValidationException($e, $event); + foreach ($expected_results as $result) { + if ($result->getSeverity() === SystemManager::REQUIREMENT_ERROR) { + $event->addError($result->getMessages(), $result->getSummary()); + } } + return new StageEventException($event); } } @@ -464,8 +428,6 @@ trait TestStageTrait { */ class TestStage extends Stage { - use TestStageTrait; - /** * {@inheritdoc} * diff --git a/package_manager/tests/src/Kernel/PendingUpdatesValidatorTest.php b/package_manager/tests/src/Kernel/PendingUpdatesValidatorTest.php index 4156a56a3bbdcbe214d2068434b60bcc6d95beae..bf4c160057736794504ab674691adba58c7caf78 100644 --- a/package_manager/tests/src/Kernel/PendingUpdatesValidatorTest.php +++ b/package_manager/tests/src/Kernel/PendingUpdatesValidatorTest.php @@ -5,7 +5,7 @@ declare(strict_types = 1); namespace Drupal\Tests\package_manager\Kernel; use Drupal\package_manager\Event\PreCreateEvent; -use Drupal\package_manager\Exception\StageValidationException; +use Drupal\package_manager\Exception\StageEventException; use Drupal\package_manager\ValidationResult; /** @@ -81,8 +81,8 @@ class PendingUpdatesValidatorTest extends PackageManagerKernelTestBase { $stage->apply(); $this->fail('Able to apply update even though there is pending update.'); } - catch (StageValidationException $exception) { - $this->assertValidationResultsEqual([$result], $exception->getResults()); + catch (StageEventException $exception) { + $this->assertExpectedResultsFromException([$result], $exception); } } diff --git a/package_manager/tests/src/Kernel/StageEventsTest.php b/package_manager/tests/src/Kernel/StageEventsTest.php index 425acbe59b81f26c301ffaba2101b45d13248679..a2b34f14b575bc82a4327011987fbeee0b69eceb 100644 --- a/package_manager/tests/src/Kernel/StageEventsTest.php +++ b/package_manager/tests/src/Kernel/StageEventsTest.php @@ -15,6 +15,7 @@ use Drupal\package_manager\Event\PreDestroyEvent; use Drupal\package_manager\Event\PreOperationStageEvent; use Drupal\package_manager\Event\PreRequireEvent; use Drupal\package_manager\Event\StageEvent; +use Drupal\package_manager\Exception\StageEventException; use Drupal\package_manager\ValidationResult; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -182,7 +183,7 @@ class StageEventsTest extends PackageManagerKernelTestBase implements EventSubsc }; $this->addEventTestListener($listener, PreCreateEvent::class); - $this->expectException(TestStageValidationException::class); + $this->expectException(StageEventException::class); $this->expectExceptionMessage('Event propagation stopped without any errors added to the event. This bypasses the package_manager validation system.'); $stage = $this->createStage(); $stage->create(); diff --git a/package_manager/tests/src/Kernel/StageTest.php b/package_manager/tests/src/Kernel/StageTest.php index 09fe3c335cb4ef8e992383227dbff4f56a5f8b6f..3ae2e3b17e85f25d2f4b7cce612523369ee8e166 100644 --- a/package_manager/tests/src/Kernel/StageTest.php +++ b/package_manager/tests/src/Kernel/StageTest.php @@ -16,6 +16,7 @@ use Drupal\package_manager\Event\PreCreateEvent; use Drupal\package_manager\Event\StageEvent; use Drupal\package_manager\Exception\ApplyFailedException; use Drupal\package_manager\Exception\StageException; +use Drupal\package_manager\Exception\StageFailureMarkerException; use Drupal\package_manager_bypass\LoggingCommitter; use PhpTuf\ComposerStager\Domain\Exception\InvalidArgumentException; use PhpTuf\ComposerStager\Domain\Exception\PreconditionException; @@ -332,6 +333,12 @@ class StageTest extends PackageManagerKernelTestBase { $this->fail('Expected an exception.'); } catch (\Throwable $exception) { + // This needs to be done because we always use the message from + // \Drupal\package_manager\Stage::getFailureMarkerMessage() when throwing + // ApplyFailedException. + if ($expected_class == ApplyFailedException::class) { + $thrown_message = 'Staged changes failed to apply, and the site is in an indeterminate state. It is strongly recommended to restore the code and database from a backup.'; + } $this->assertInstanceOf($expected_class, $exception); $this->assertSame($thrown_message, $exception->getMessage()); $this->assertSame(123, $exception->getCode()); @@ -357,7 +364,7 @@ class StageTest extends PackageManagerKernelTestBase { // Make the committer throw an exception, which should cause the failure // marker to be present. - $thrown = new \Exception('Disastrous catastrophe!'); + $thrown = new \Exception('Staged changes failed to apply, and the site is in an indeterminate state. It is strongly recommended to restore the code and database from a backup.'); LoggingCommitter::setException($thrown); try { $stage->apply(); @@ -376,7 +383,7 @@ class StageTest extends PackageManagerKernelTestBase { $stage->create(); $this->fail('Expected an exception.'); } - catch (ApplyFailedException $e) { + catch (StageFailureMarkerException $e) { $this->assertSame('Staged changes failed to apply, and the site is in an indeterminate state. It is strongly recommended to restore the code and database from a backup.', $e->getMessage()); $this->assertFalse($stage->isApplying()); } diff --git a/package_manager/tests/src/Kernel/StageValidationExceptionTest.php b/package_manager/tests/src/Kernel/StageValidationExceptionTest.php deleted file mode 100644 index 7f219c5333fb0f7c67bc49135ae97623ac0e26a6..0000000000000000000000000000000000000000 --- a/package_manager/tests/src/Kernel/StageValidationExceptionTest.php +++ /dev/null @@ -1,84 +0,0 @@ -<?php - -declare(strict_types = 1); - -namespace Drupal\Tests\package_manager\Kernel; - -use Drupal\package_manager\Event\PreCreateEvent; -use Drupal\package_manager\Exception\StageValidationException; -use Drupal\package_manager\ValidationResult; -use Drupal\package_manager_test_validation\EventSubscriber\TestSubscriber; - -/** - * @coversDefaultClass \Drupal\package_manager\Exception\StageValidationException - * @group package_manager - * @internal - */ -class StageValidationExceptionTest extends PackageManagerKernelTestBase { - - /** - * {@inheritdoc} - */ - protected static $modules = [ - 'package_manager_test_validation', - ]; - - /** - * Data provider for testErrors(). - * - * @return string[][] - * The test cases. - */ - public function providerResultsAsText(): array { - $messages = ['Bang!', 'Pow!']; - $translated_messages = [t('Bang!'), t('Pow!')]; - $summary = t('There was sadness.'); - - $result_no_summary = ValidationResult::createError([$translated_messages[0]]); - $result_with_summary = ValidationResult::createError($translated_messages, $summary); - $result_with_summary_message = "{$summary->getUntranslatedString()}\n{$messages[0]}\n{$messages[1]}\n"; - - return [ - '1 result with summary' => [ - [$result_with_summary], - $result_with_summary_message, - ], - '2 results, with summaries' => [ - [$result_with_summary, $result_with_summary], - "$result_with_summary_message$result_with_summary_message", - ], - '1 result without summary' => [ - [$result_no_summary], - $messages[0], - ], - '2 results without summaries' => [ - [$result_no_summary, $result_no_summary], - $messages[0] . "\n" . $messages[0], - ], - '1 result with summary, 1 result without summary' => [ - [$result_with_summary, $result_no_summary], - $result_with_summary_message . $messages[0] . "\n", - ], - ]; - } - - /** - * Tests formatting a set of validation results as plain text. - * - * @param \Drupal\package_manager\ValidationResult[] $validation_results - * The expected validation results which should be logged. - * @param string $expected_message - * The expected exception message. - * - * @dataProvider providerResultsAsText - * - * @covers ::getResultsAsText() - */ - public function testResultsAsText(array $validation_results, string $expected_message): void { - TestSubscriber::setTestResult($validation_results, PreCreateEvent::class); - $this->expectException(StageValidationException::class); - $this->expectExceptionMessage($expected_message); - $this->createStage()->create(); - } - -} diff --git a/src/BatchProcessor.php b/src/BatchProcessor.php index e991f25639d412c25e150889b7d6c77059504557..8bb86b0f59553361844055e6ccf41438772760c5 100644 --- a/src/BatchProcessor.php +++ b/src/BatchProcessor.php @@ -5,7 +5,6 @@ declare(strict_types = 1); namespace Drupal\automatic_updates; use Drupal\Core\Url; -use Drupal\package_manager\Exception\StageValidationException; use Drupal\system\Controller\DbUpdateController; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -56,24 +55,7 @@ final class BatchProcessor { * have been recorded. */ protected static function handleException(\Throwable $error, array &$context): void { - $error_messages = []; - - if ($error instanceof StageValidationException) { - foreach ($error->getResults() as $result) { - $messages = $result->getMessages(); - if (count($messages) > 1) { - array_unshift($messages, $result->getSummary()); - } - $error_messages = array_merge($error_messages, $messages); - } - } - else { - $error_messages[] = $error->getMessage(); - } - - foreach ($error_messages as $error_message) { - $context['results']['errors'][] = $error_message; - } + $context['results']['errors'][] = $error->getMessage(); throw $error; } diff --git a/src/CronUpdater.php b/src/CronUpdater.php index 2612bb841853c11e81ea8def6c6b9b95d956c742..e3624b7bbb0afc6ed605702ba8ae84608c501597 100644 --- a/src/CronUpdater.php +++ b/src/CronUpdater.php @@ -9,7 +9,7 @@ use Drupal\Core\Mail\MailManagerInterface; use Drupal\Core\State\StateInterface; use Drupal\Core\Url; use Drupal\package_manager\Exception\ApplyFailedException; -use Drupal\package_manager\Exception\StageValidationException; +use Drupal\package_manager\Exception\StageEventException; use Drupal\package_manager\ProjectInfo; use Drupal\update\ProjectRelease; use GuzzleHttp\Psr7\Uri as GuzzleUri; @@ -174,7 +174,7 @@ class CronUpdater extends Updater { 'target_version' => $target_version, 'error_message' => $e->getMessage(), ]; - if ($e instanceof ApplyFailedException || $e->getPrevious() instanceof ApplyFailedException) { + if ($e instanceof ApplyFailedException) { $mail_params['urgent'] = TRUE; $key = 'cron_failed_apply'; } @@ -304,7 +304,7 @@ class CronUpdater extends Updater { try { $this->destroy(); } - catch (StageValidationException $e) { + catch (StageEventException $e) { $this->logger->error($e->getMessage()); } diff --git a/src/Exception/UpdateException.php b/src/Exception/UpdateException.php deleted file mode 100644 index 7279d62bb77cc2686bb87b1a032419da602f73c8..0000000000000000000000000000000000000000 --- a/src/Exception/UpdateException.php +++ /dev/null @@ -1,19 +0,0 @@ -<?php - -declare(strict_types = 1); - -namespace Drupal\automatic_updates\Exception; - -use Drupal\package_manager\Exception\StageValidationException; - -/** - * Defines a custom exception for a failure during an update. - * - * Should not be thrown by external code. This is only used to identify - * validation errors that occurred during a stage operation performed by - * Automatic Updates. - * - * @see \Drupal\automatic_updates\Updater::dispatch() - */ -class UpdateException extends StageValidationException { -} diff --git a/src/Form/UpdateReady.php b/src/Form/UpdateReady.php index 37bee5122f0874f53c74d6739121c9208f6ed712..414c2b2bb9cbc8da866d723fb3376c6d65f1c0d2 100644 --- a/src/Form/UpdateReady.php +++ b/src/Form/UpdateReady.php @@ -6,6 +6,7 @@ namespace Drupal\automatic_updates\Form; use Drupal\automatic_updates\BatchProcessor; use Drupal\automatic_updates\Updater; +use Drupal\package_manager\Exception\StageFailureMarkerException; use Drupal\package_manager\ValidationResult; use Drupal\Core\Batch\BatchBuilder; use Drupal\Core\Extension\ModuleExtensionList; @@ -13,7 +14,6 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Render\RendererInterface; use Drupal\Core\State\StateInterface; -use Drupal\package_manager\Exception\ApplyFailedException; use Drupal\package_manager\Exception\StageException; use Drupal\package_manager\Exception\StageOwnershipException; use Drupal\system\SystemManager; @@ -81,7 +81,7 @@ final class UpdateReady extends UpdateFormBase { $this->messenger()->addError($e->getMessage()); return $form; } - catch (ApplyFailedException $e) { + catch (StageFailureMarkerException $e) { $this->messenger()->addError($e->getMessage()); return $form; } diff --git a/src/Form/UpdaterForm.php b/src/Form/UpdaterForm.php index a6212210a00c921ebfafe5d8954b02c5b44608f2..14a637dcef0df7555c747d175eca9e32cf035793 100644 --- a/src/Form/UpdaterForm.php +++ b/src/Form/UpdaterForm.php @@ -6,11 +6,11 @@ namespace Drupal\automatic_updates\Form; use Drupal\automatic_updates\BatchProcessor; use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\package_manager\Exception\StageFailureMarkerException; use Drupal\package_manager\FailureMarker; use Drupal\package_manager\ProjectInfo; use Drupal\automatic_updates\ReleaseChooser; use Drupal\automatic_updates\Updater; -use Drupal\package_manager\Exception\ApplyFailedException; use Drupal\update\ProjectRelease; use Drupal\Core\Batch\BatchBuilder; use Drupal\Core\Extension\ExtensionVersion; @@ -93,7 +93,7 @@ final class UpdaterForm extends UpdateFormBase { try { $this->failureMarker->assertNotExists(); } - catch (ApplyFailedException $e) { + catch (StageFailureMarkerException $e) { $this->messenger()->addError($e->getMessage()); return $form; } diff --git a/src/Updater.php b/src/Updater.php index 7a73c0cae6d13cd3a1b0c9b37af65bee2b05303c..cca31265087e157bd224f3cf1520efbf6087484c 100644 --- a/src/Updater.php +++ b/src/Updater.php @@ -4,11 +4,7 @@ declare(strict_types = 1); namespace Drupal\automatic_updates; -use Drupal\automatic_updates\Exception\UpdateException; use Drupal\Core\StringTranslation\TranslatableMarkup; -use Drupal\package_manager\Event\StageEvent; -use Drupal\package_manager\Exception\ApplyFailedException; -use Drupal\package_manager\Exception\StageValidationException; use Drupal\package_manager\Stage; /** @@ -96,30 +92,6 @@ class Updater extends Stage { $this->require($versions['production'], $versions['dev'], $timeout); } - /** - * {@inheritdoc} - */ - protected function dispatch(StageEvent $event, callable $on_error = NULL): void { - try { - parent::dispatch($event, $on_error); - } - catch (StageValidationException $e) { - throw new UpdateException($e->getResults(), $e->getMessage(), $e->getCode(), $e); - } - } - - /** - * {@inheritdoc} - */ - public function apply(?int $timeout = 600): void { - try { - parent::apply($timeout); - } - catch (ApplyFailedException $exception) { - throw new UpdateException([], "The update operation failed to apply completely. All the files necessary to run Drupal correctly and securely are probably not present. It is strongly recommended to restore your site's code and database from a backup.", $exception->getCode(), $exception); - } - } - /** * {@inheritdoc} */ diff --git a/tests/src/Functional/UpdateFailedTest.php b/tests/src/Functional/UpdateFailedTest.php index 8258256741f13d9da9e7e29fa1a741a7359fecb3..a09fb0483c799a958900afccd4f419d7848fd313 100644 --- a/tests/src/Functional/UpdateFailedTest.php +++ b/tests/src/Functional/UpdateFailedTest.php @@ -34,11 +34,11 @@ class UpdateFailedTest extends UpdaterFormTestBase { LoggingCommitter::setException(new \Exception('failed at committer')); $page->pressButton('Continue'); $this->checkForMetaRefresh(); + $failure_message = 'Automatic updates failed to apply, and the site is in an indeterminate state. Consider restoring the code and database from a backup.'; $assert_session->pageTextContainsOnce('An error has occurred.'); - $assert_session->pageTextContains("The update operation failed to apply completely. All the files necessary to run Drupal correctly and securely are probably not present. It is strongly recommended to restore your site's code and database from a backup."); + $assert_session->pageTextContains($failure_message); $page->clickLink('the error page'); - $failure_message = 'Automatic updates failed to apply, and the site is in an indeterminate state. Consider restoring the code and database from a backup.'; // We should be on the form (i.e., 200 response code), but unable to // continue the update. $assert_session->statusCodeEquals(200); diff --git a/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php b/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php index 44c25ce6d7e1e3c95d40d308aa4ea938b235e84d..5a448f079b748a1966cb009b04007bc8ccceda31 100644 --- a/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php +++ b/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php @@ -10,7 +10,6 @@ use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Url; use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait; use Drupal\Tests\package_manager\Kernel\PackageManagerKernelTestBase; -use Drupal\Tests\package_manager\Kernel\TestStageTrait; /** * Base class for kernel tests of the Automatic Updates module. @@ -91,8 +90,6 @@ abstract class AutomaticUpdatesKernelTestBase extends PackageManagerKernelTestBa */ class TestUpdater extends Updater { - use TestStageTrait; - /** * {@inheritdoc} */ @@ -107,8 +104,6 @@ class TestUpdater extends Updater { */ class TestCronUpdater extends CronUpdater { - use TestStageTrait; - /** * {@inheritdoc} */ diff --git a/tests/src/Kernel/CronUpdaterTest.php b/tests/src/Kernel/CronUpdaterTest.php index d0f80524d64adc9fb3476e4a8d39b82d8b909287..f347398058e4bb28cd6f2c2d7edf2bfa0cd98afd 100644 --- a/tests/src/Kernel/CronUpdaterTest.php +++ b/tests/src/Kernel/CronUpdaterTest.php @@ -17,8 +17,8 @@ use Drupal\package_manager\Event\PreApplyEvent; use Drupal\package_manager\Event\PreDestroyEvent; use Drupal\package_manager\Event\PreRequireEvent; use Drupal\package_manager\Event\PreCreateEvent; +use Drupal\package_manager\Exception\StageEventException; use Drupal\package_manager\Exception\StageOwnershipException; -use Drupal\package_manager\Exception\StageValidationException; use Drupal\package_manager\ValidationResult; use Drupal\package_manager_bypass\LoggingCommitter; use Drupal\Tests\automatic_updates\Traits\EmailNotificationsTestTrait; @@ -223,19 +223,19 @@ class CronUpdaterTest extends AutomaticUpdatesKernelTestBase { // @see \Drupal\package_manager\Stage::dispatch() 'pre-create validation error' => [ PreCreateEvent::class, - StageValidationException::class, + StageEventException::class, ], 'pre-require validation error' => [ PreRequireEvent::class, - StageValidationException::class, + StageEventException::class, ], 'pre-apply validation error' => [ PreApplyEvent::class, - StageValidationException::class, + StageEventException::class, ], 'pre-destroy validation error' => [ PreDestroyEvent::class, - StageValidationException::class, + StageEventException::class, ], ]; } @@ -277,15 +277,18 @@ class CronUpdaterTest extends AutomaticUpdatesKernelTestBase { ->get('cron') ->addLogger($cron_logger); - // When the event specified by $event_class is fired, either throw an - // exception directly from the event subscriber, or set a validation error - // (if the exception class is StageValidationException). - if ($exception_class === StageValidationException::class) { - $results = [ - ValidationResult::createError([t('Destroy the stage!')]), - ]; - TestSubscriber1::setTestResult($results, $event_class); - $exception = new StageValidationException($results); + /** @var \Drupal\automatic_updates\CronUpdater $updater */ + $updater = $this->container->get('automatic_updates.cron_updater'); + + // When the event specified by $event_class is dispatched, either throw an + // exception directly from the event subscriber, or prepare a + // StageEventException which will format the validation errors its own way. + if ($exception_class === StageEventException::class) { + $error = ValidationResult::createError([ + t('Destroy the stage!'), + ]); + $exception = $this->createStageEventExceptionFromResults([$error], $event_class, $updater); + TestSubscriber1::setTestResult($exception->event->getResults(), $event_class); } else { /** @var \Throwable $exception */ @@ -298,8 +301,6 @@ class CronUpdaterTest extends AutomaticUpdatesKernelTestBase { $this->assertEmpty($cron_logger->records); $this->assertEmpty($this->logger->records); - /** @var \Drupal\automatic_updates\CronUpdater $updater */ - $updater = $this->container->get('automatic_updates.cron_updater'); $this->assertTrue($updater->isAvailable()); $this->container->get('cron')->run(); @@ -324,27 +325,16 @@ class CronUpdaterTest extends AutomaticUpdatesKernelTestBase { // won't try to catch it. Instead, it will be caught and logged by the main // cron service. if ($event_class === PreDestroyEvent::class || $event_class === PostDestroyEvent::class) { - if ($exception instanceof StageValidationException) { - $this->assertTrue($logged_by_updater); - $this->assertFalse($logged_by_cron); - } - else { - $this->assertFalse($logged_by_updater); - $this->assertTrue($logged_by_cron); - } // If the pre-destroy event throws an exception or flags a validation // error, the stage won't be destroyed. But, once the post-destroy event // is fired, the stage should be fully destroyed and marked as available. $this->assertSame($event_class === PostDestroyEvent::class, $updater->isAvailable()); } - // For all other events, the error should be caught and logged by the cron - // updater, not the main cron service, and the stage should always be - // destroyed and marked as available. else { - $this->assertTrue($logged_by_updater); - $this->assertFalse($logged_by_cron); $this->assertTrue($updater->isAvailable()); } + $this->assertTrue($logged_by_updater); + $this->assertFalse($logged_by_cron); } /** @@ -407,8 +397,8 @@ class CronUpdaterTest extends AutomaticUpdatesKernelTestBase { $stage->apply(); $this->fail('Expected update to fail'); } - catch (StageValidationException $exception) { - $this->assertValidationResultsEqual([ValidationResult::createError([$stop_error])], $exception->getResults()); + catch (StageEventException $exception) { + $this->assertExpectedResultsFromException([ValidationResult::createError([$stop_error])], $exception); } $this->assertTrue($this->logger->hasRecord("Cron will not perform any updates as an existing staged update is applying. The site is currently on an insecure version of Drupal core but will attempt to update to a secure version next time cron is run. This update may be applied manually at the <a href=\"%url\">update form</a>.", (string) RfcLogLevel::NOTICE)); @@ -507,11 +497,12 @@ END; ->set('cron', CronUpdater::ALL) ->save(); - $results = [ - ValidationResult::createError([t('Error while updating!')]), - ]; - TestSubscriber1::setTestResult($results, $event_class); - $exception = new StageValidationException($results); + $error = ValidationResult::createError([ + t('Error while updating!'), + ]); + $exception = $this->createStageEventExceptionFromResults([$error], $event_class, $this->container->get('automatic_updates.cron_updater')); + TestSubscriber1::setTestResult($exception->event->getResults(), $event_class); + $this->container->get('cron')->run(); $url = Url::fromRoute('update.report_update') @@ -546,11 +537,13 @@ END; if ($event_class !== PreCreateEvent::class) { $this->getStageFixtureManipulator()->setCorePackageVersion('9.8.1'); } - $results = [ - ValidationResult::createError([t('Error while updating!')]), - ]; - TestSubscriber1::setTestResult($results, $event_class); - $exception = new StageValidationException($results); + + $error = ValidationResult::createError([ + t('Error while updating!'), + ]); + TestSubscriber1::setTestResult([$error], $event_class); + $exception = $this->createStageEventExceptionFromResults([$error], $event_class, $this->container->get('automatic_updates.cron_updater')); + $this->container->get('cron')->run(); $url = Url::fromRoute('update.report_update') @@ -583,7 +576,7 @@ END; $expected_body = <<<END Drupal core failed to update automatically from 9.8.0 to 9.8.1. The following error was logged: -The update operation failed to apply completely. All the files necessary to run Drupal correctly and securely are probably not present. It is strongly recommended to restore your site's code and database from a backup. +Automatic updates failed to apply, and the site is in an indeterminate state. Consider restoring the code and database from a backup. This e-mail was sent by the Automatic Updates module. Unattended updates are not yet fully supported. diff --git a/tests/src/Kernel/StatusCheck/CronServerValidatorTest.php b/tests/src/Kernel/StatusCheck/CronServerValidatorTest.php index 8d7870d2e4f79d47c84bfd22cfc1787d0776029a..5383df65fbaf26db9a54a297ea3c267d63623681 100644 --- a/tests/src/Kernel/StatusCheck/CronServerValidatorTest.php +++ b/tests/src/Kernel/StatusCheck/CronServerValidatorTest.php @@ -8,7 +8,6 @@ use Drupal\automatic_updates\CronUpdater; use Drupal\automatic_updates\Validator\CronServerValidator; use Drupal\Core\Logger\RfcLogLevel; use Drupal\Core\Url; -use Drupal\package_manager\Exception\StageValidationException; use Drupal\package_manager\ValidationResult; use Drupal\Tests\automatic_updates\Kernel\AutomaticUpdatesKernelTestBase; use ColinODell\PsrTestLogger\TestLogger; @@ -129,7 +128,7 @@ class CronServerValidatorTest extends AutomaticUpdatesKernelTestBase { // Assert the update was not staged to ensure the error was flagged in // PreCreateEvent and not PreApplyEvent. $this->assertUpdateStagedTimes(0); - $error = new StageValidationException($expected_results); + $error = $this->createStageEventExceptionFromResults($expected_results); $this->assertTrue($logger->hasRecord($error->getMessage(), (string) RfcLogLevel::ERROR)); } else { @@ -190,7 +189,7 @@ class CronServerValidatorTest extends AutomaticUpdatesKernelTestBase { $this->container->get('cron')->run(); if ($expected_results) { $this->assertUpdateStagedTimes(1); - $error = new StageValidationException($expected_results); + $error = $this->createStageEventExceptionFromResults($expected_results); $this->assertTrue($logger->hasRecord($error->getMessage(), (string) RfcLogLevel::ERROR)); } else { diff --git a/tests/src/Kernel/StatusCheck/RequestedUpdateValidatorTest.php b/tests/src/Kernel/StatusCheck/RequestedUpdateValidatorTest.php index 1444ceba81688f9cc394d073e6543b030c87002b..b9fa93ee751622cc5c6ce2ec5e6ccaa28c05e9f6 100644 --- a/tests/src/Kernel/StatusCheck/RequestedUpdateValidatorTest.php +++ b/tests/src/Kernel/StatusCheck/RequestedUpdateValidatorTest.php @@ -4,7 +4,7 @@ declare(strict_types = 1); namespace Drupal\Tests\automatic_updates\Kernel\StatusCheck; -use Drupal\package_manager\Exception\StageValidationException; +use Drupal\package_manager\Exception\StageEventException; use Drupal\package_manager\ValidationResult; use Drupal\Tests\automatic_updates\Kernel\AutomaticUpdatesKernelTestBase; @@ -52,8 +52,8 @@ class RequestedUpdateValidatorTest extends AutomaticUpdatesKernelTestBase { $updater->apply(); $this->fail('Expecting an exception.'); } - catch (StageValidationException $exception) { - $this->assertValidationResultsEqual($expected_results, $exception->getResults()); + catch (StageEventException $exception) { + $this->assertExpectedResultsFromException($expected_results, $exception); } } @@ -76,7 +76,7 @@ class RequestedUpdateValidatorTest extends AutomaticUpdatesKernelTestBase { $updater = $this->container->get('automatic_updates.updater'); $updater->begin(['drupal' => '9.8.1']); $updater->stage(); - $this->expectException(StageValidationException::class); + $this->expectException(StageEventException::class); $this->expectExceptionMessage('No updates detected in the staging area.'); $updater->apply(); } diff --git a/tests/src/Kernel/StatusCheck/ScaffoldFilePermissionsValidatorTest.php b/tests/src/Kernel/StatusCheck/ScaffoldFilePermissionsValidatorTest.php index d2352a17df19ae379829a765639713ccf81b7add..ccb8dd7684f61c6db9e70435f227b903fffa9d32 100644 --- a/tests/src/Kernel/StatusCheck/ScaffoldFilePermissionsValidatorTest.php +++ b/tests/src/Kernel/StatusCheck/ScaffoldFilePermissionsValidatorTest.php @@ -5,7 +5,8 @@ declare(strict_types = 1); namespace Drupal\Tests\automatic_updates\Kernel\StatusCheck; use Drupal\fixture_manipulator\ActiveFixtureManipulator; -use Drupal\package_manager\Exception\StageValidationException; +use Drupal\package_manager\Exception\ApplyFailedException; +use Drupal\package_manager\Exception\StageEventException; use Drupal\package_manager\PathLocator; use Drupal\package_manager\ValidationResult; use Drupal\Tests\automatic_updates\Kernel\AutomaticUpdatesKernelTestBase; @@ -125,8 +126,8 @@ class ScaffoldFilePermissionsValidatorTest extends AutomaticUpdatesKernelTestBas // If no exception was thrown, ensure that we weren't expecting an error. $this->assertEmpty($expected_results); } - catch (StageValidationException $e) { - $this->assertValidationResultsEqual($expected_results, $e->getResults()); + catch (StageEventException $e) { + $this->assertExpectedResultsFromException($expected_results, $e); } } @@ -331,8 +332,13 @@ class ScaffoldFilePermissionsValidatorTest extends AutomaticUpdatesKernelTestBas // If no exception was thrown, ensure that we weren't expecting an error. $this->assertEmpty($expected_results); } - catch (StageValidationException $e) { - $this->assertValidationResultsEqual($expected_results, $e->getResults()); + // If we try to overwrite any write-protected paths, even if they're not + // scaffold files, we'll get an ApplyFailedException. + catch (ApplyFailedException $e) { + $this->assertSame("Automatic updates failed to apply, and the site is in an indeterminate state. Consider restoring the code and database from a backup.", $e->getMessage()); + } + catch (StageEventException $e) { + $this->assertExpectedResultsFromException($expected_results, $e); } } diff --git a/tests/src/Kernel/StatusCheck/StagedProjectsValidatorTest.php b/tests/src/Kernel/StatusCheck/StagedProjectsValidatorTest.php index 26b70642cf1cb35c4c2d0b5fcb922759a003b862..d11b04a1de9a11016aaf971871f0d35d1bea487a 100644 --- a/tests/src/Kernel/StatusCheck/StagedProjectsValidatorTest.php +++ b/tests/src/Kernel/StatusCheck/StagedProjectsValidatorTest.php @@ -6,7 +6,7 @@ namespace Drupal\Tests\automatic_updates\Kernel\StatusCheck; use Drupal\fixture_manipulator\ActiveFixtureManipulator; use Drupal\package_manager\Event\PreApplyEvent; -use Drupal\package_manager\Exception\StageValidationException; +use Drupal\package_manager\Exception\StageEventException; use Drupal\package_manager\ValidationResult; use Drupal\Tests\automatic_updates\Kernel\AutomaticUpdatesKernelTestBase; @@ -62,8 +62,8 @@ class StagedProjectsValidatorTest extends AutomaticUpdatesKernelTestBase { $updater->apply(); $this->fail('Expected an error, but none was raised.'); } - catch (StageValidationException $e) { - $this->assertValidationResultsEqual([$error], $e->getResults()); + catch (StageEventException $e) { + $this->assertExpectedResultsFromException([$error], $e); } } @@ -153,8 +153,8 @@ class StagedProjectsValidatorTest extends AutomaticUpdatesKernelTestBase { $updater->apply(); $this->fail('Expected an error, but none was raised.'); } - catch (StageValidationException $e) { - $this->assertValidationResultsEqual([$error], $e->getResults()); + catch (StageEventException $e) { + $this->assertExpectedResultsFromException([$error], $e); } } @@ -230,8 +230,8 @@ class StagedProjectsValidatorTest extends AutomaticUpdatesKernelTestBase { $updater->apply(); $this->fail('Expected an error, but none was raised.'); } - catch (StageValidationException $e) { - $this->assertValidationResultsEqual([$error], $e->getResults()); + catch (StageEventException $e) { + $this->assertExpectedResultsFromException([$error], $e); } } @@ -293,8 +293,8 @@ class StagedProjectsValidatorTest extends AutomaticUpdatesKernelTestBase { $updater->apply(); $this->fail('Expected an error, but none was raised.'); } - catch (StageValidationException $e) { - $this->assertValidationResultsEqual([$error], $e->getResults()); + catch (StageEventException $e) { + $this->assertExpectedResultsFromException([$error], $e); } } diff --git a/tests/src/Kernel/StatusCheck/VersionPolicyValidatorTest.php b/tests/src/Kernel/StatusCheck/VersionPolicyValidatorTest.php index d02a32db298127f2f9a81a079a4f027118281325..f84181b2e8b7655cd658830ac3f4a48a8808b100 100644 --- a/tests/src/Kernel/StatusCheck/VersionPolicyValidatorTest.php +++ b/tests/src/Kernel/StatusCheck/VersionPolicyValidatorTest.php @@ -6,8 +6,8 @@ namespace Drupal\Tests\automatic_updates\Kernel\StatusCheck; use Drupal\automatic_updates\CronUpdater; use Drupal\package_manager\Event\PreCreateEvent; +use Drupal\package_manager\Exception\StageEventException; use Drupal\package_manager\Exception\StageException; -use Drupal\package_manager\Exception\StageValidationException; use Drupal\package_manager\ValidationResult; use Drupal\Tests\automatic_updates\Kernel\AutomaticUpdatesKernelTestBase; @@ -315,8 +315,8 @@ class VersionPolicyValidatorTest extends AutomaticUpdatesKernelTestBase { // Reset the updater for the next iteration of the loop. $updater->destroy(); } - catch (StageValidationException $e) { - $this->assertValidationResultsEqual($expected_results, $e->getResults()); + catch (StageEventException $e) { + $this->assertExpectedResultsFromException($expected_results, $e); } } diff --git a/tests/src/Kernel/UpdaterTest.php b/tests/src/Kernel/UpdaterTest.php index 5ce66877608e84aa4a7e9f66d470fa110b6b5588..98c8d27fafc00d012f3e793e03e8bc28baf07cc9 100644 --- a/tests/src/Kernel/UpdaterTest.php +++ b/tests/src/Kernel/UpdaterTest.php @@ -4,15 +4,9 @@ declare(strict_types = 1); namespace Drupal\Tests\automatic_updates\Kernel; -use Drupal\automatic_updates\Exception\UpdateException; -use Drupal\automatic_updates_test\EventSubscriber\TestSubscriber1; -use Drupal\package_manager\Event\PreApplyEvent; -use Drupal\package_manager\Event\PreCreateEvent; -use Drupal\package_manager\Event\PreRequireEvent; +use Drupal\package_manager\Exception\ApplyFailedException; use Drupal\package_manager\Exception\StageException; -use Drupal\package_manager\ValidationResult; use Drupal\package_manager_bypass\LoggingCommitter; -use Drupal\Tests\package_manager\Kernel\TestStageValidationException; use Drupal\Tests\user\Traits\UserCreationTrait; use PhpTuf\ComposerStager\Domain\Exception\InvalidArgumentException; @@ -156,7 +150,7 @@ class UpdaterTest extends AutomaticUpdatesKernelTestBase { return [ 'RuntimeException' => [ 'RuntimeException', - UpdateException::class, + ApplyFailedException::class, ], 'InvalidArgumentException' => [ InvalidArgumentException::class, @@ -164,7 +158,7 @@ class UpdaterTest extends AutomaticUpdatesKernelTestBase { ], 'Exception' => [ 'Exception', - UpdateException::class, + ApplyFailedException::class, ], ]; } @@ -190,8 +184,8 @@ class UpdaterTest extends AutomaticUpdatesKernelTestBase { $thrown_message = 'A very bad thing happened'; LoggingCommitter::setException(new $thrown_class($thrown_message, 123)); $this->expectException($expected_class); - $expected_message = $expected_class === UpdateException::class ? - "The update operation failed to apply completely. All the files necessary to run Drupal correctly and securely are probably not present. It is strongly recommended to restore your site's code and database from a backup." + $expected_message = $expected_class === ApplyFailedException::class ? + "Automatic updates failed to apply, and the site is in an indeterminate state. Consider restoring the code and database from a backup." : $thrown_message; $this->expectExceptionMessage($expected_message); $this->expectExceptionCode(123); @@ -206,51 +200,4 @@ class UpdaterTest extends AutomaticUpdatesKernelTestBase { $this->assertSame('setLogger', $updater_method_calls[0][0]); } - /** - * Tests UpdateException handling. - * - * @param string $event_class - * The stage life cycle event which should raise an error. - * - * @dataProvider providerUpdateException - */ - public function testUpdateException(string $event_class) { - $updater = $this->container->get('automatic_updates.updater'); - $results = [ - ValidationResult::createError([t('An error of some sorts.')]), - ]; - TestSubscriber1::setTestResult($results, $event_class); - try { - $updater->begin(['drupal' => '9.8.1']); - $updater->stage(); - $updater->apply(); - $this->fail('Expected an exception, but none was raised.'); - } - catch (TestStageValidationException $e) { - $this->assertStringStartsWith('An error of some sorts.', $e->getMessage()); - $this->assertInstanceOf(UpdateException::class, $e->getOriginalException()); - $this->assertInstanceOf($event_class, $e->getEvent()); - } - } - - /** - * Data provider for testUpdateException(). - * - * @return string[][] - * The test cases. - */ - public function providerUpdateException(): array { - return [ - 'pre-create exception' => [ - PreCreateEvent::class, - ], - 'pre-require exception' => [ - PreRequireEvent::class, - ], - 'pre-apply exception' => [ - PreApplyEvent::class, - ], - ]; - } - }