From ee34db2123633d8511e1734664a62dface4e1dcd Mon Sep 17 00:00:00 2001
From: phenaproxima <phenaproxima@205645.no-reply.drupal.org>
Date: Wed, 19 Jan 2022 17:05:13 +0000
Subject: [PATCH] Issue #3258056 by phenaproxima, tedbow: Simplify
 Stage::dispatch exception handling

---
 .../src/Exception/StageException.php          |  2 ++
 .../src/Exception/StageOwnershipException.php |  2 ++
 .../Exception/StageValidationException.php    |  2 ++
 package_manager/src/Stage.php                 | 24 +++++++-----------
 src/Exception/UpdateException.php             |  6 +++++
 tests/src/Functional/UpdaterFormTest.php      | 25 +++++++++----------
 6 files changed, 33 insertions(+), 28 deletions(-)

diff --git a/package_manager/src/Exception/StageException.php b/package_manager/src/Exception/StageException.php
index e8011bac34..9e5e06ebc1 100644
--- a/package_manager/src/Exception/StageException.php
+++ b/package_manager/src/Exception/StageException.php
@@ -4,6 +4,8 @@ namespace Drupal\package_manager\Exception;
 
 /**
  * Base class for all exceptions related to staging operations.
+ *
+ * Should not be thrown by external code.
  */
 class StageException extends \RuntimeException {
 }
diff --git a/package_manager/src/Exception/StageOwnershipException.php b/package_manager/src/Exception/StageOwnershipException.php
index 8a8e868ac9..5e44c8c360 100644
--- a/package_manager/src/Exception/StageOwnershipException.php
+++ b/package_manager/src/Exception/StageOwnershipException.php
@@ -4,6 +4,8 @@ namespace Drupal\package_manager\Exception;
 
 /**
  * Exception thrown if a stage encounters an ownership or locking error.
+ *
+ * Should not be thrown by external code.
  */
 class StageOwnershipException extends StageException {
 }
diff --git a/package_manager/src/Exception/StageValidationException.php b/package_manager/src/Exception/StageValidationException.php
index 1bb0a9c696..2654615bf9 100644
--- a/package_manager/src/Exception/StageValidationException.php
+++ b/package_manager/src/Exception/StageValidationException.php
@@ -4,6 +4,8 @@ namespace Drupal\package_manager\Exception;
 
 /**
  * Exception thrown if a stage has validation errors.
+ *
+ * Should not be thrown by external code.
  */
 class StageValidationException extends StageException {
 
diff --git a/package_manager/src/Stage.php b/package_manager/src/Stage.php
index b1b9483f6d..756a45a23f 100644
--- a/package_manager/src/Stage.php
+++ b/package_manager/src/Stage.php
@@ -333,9 +333,8 @@ class Stage {
    *   The event object.
    *
    * @throws \Drupal\package_manager\Exception\StageValidationException
-   *   If the event collects any validation errors, or a subscriber throws a
-   *   StageValidationException directly.
-   * @throws \RuntimeException
+   *   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): void {
@@ -344,25 +343,20 @@ class Stage {
 
       $results = $event->getResults();
       if ($results) {
-        throw new StageValidationException($results);
+        $error = new StageValidationException($results);
       }
     }
     catch (\Throwable $error) {
-      // @todo Simplify exception handling in https://www.drupal.org/i/3258056.
-      // If we are not going to be able to create the staging area, mark it as
-      // available.
+      $error = new StageException($error->getMessage(), $error->getCode(), $error);
+    }
+
+    if (isset($error)) {
+      // If we won't be able to create the staging area, mark it as available.
       // @see ::create()
       if ($event instanceof PreCreateEvent) {
         $this->markAsAvailable();
       }
-
-      // Wrap the exception to preserve the backtrace, and re-throw it.
-      if ($error instanceof StageValidationException) {
-        throw new StageValidationException($error->getResults(), $error->getMessage(), $error->getCode(), $error);
-      }
-      else {
-        throw new \RuntimeException($error->getMessage(), $error->getCode(), $error);
-      }
+      throw $error;
     }
   }
 
diff --git a/src/Exception/UpdateException.php b/src/Exception/UpdateException.php
index 390e899b2f..8ea6a9f3b0 100644
--- a/src/Exception/UpdateException.php
+++ b/src/Exception/UpdateException.php
@@ -6,6 +6,12 @@ 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 staging operation performed by
+ * Automatic Updates.
+ *
+ * @see \Drupal\automatic_updates\Updater::dispatch()
  */
 class UpdateException extends StageValidationException {
 }
diff --git a/tests/src/Functional/UpdaterFormTest.php b/tests/src/Functional/UpdaterFormTest.php
index f8e289ae21..e98f123d6f 100644
--- a/tests/src/Functional/UpdaterFormTest.php
+++ b/tests/src/Functional/UpdaterFormTest.php
@@ -3,7 +3,6 @@
 namespace Drupal\Tests\automatic_updates\Functional;
 
 use Drupal\automatic_updates\Event\ReadinessCheckEvent;
-use Drupal\automatic_updates\Exception\UpdateException;
 use Drupal\package_manager\Event\PreCreateEvent;
 use Drupal\package_manager\ValidationResult;
 use Drupal\automatic_updates_test\ReadinessChecker\TestChecker1;
@@ -176,29 +175,29 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
     $assert_session->pageTextNotContains((string) $message);
     TestChecker1::setTestResult(NULL, ReadinessCheckEvent::class);
 
-    // Repackage the validation error as an exception, so we can test what
-    // happens if a validator throws once the update has started.
-    $error = new UpdateException($expected_results, 'The update exploded.');
+    // Make the validator throw an exception during pre-create.
+    $error = new \Exception('The update exploded.');
     TestChecker1::setException($error, PreCreateEvent::class);
     $session->reload();
     $assert_session->pageTextNotContains(static::$errorsExplanation);
     $assert_session->pageTextNotContains(static::$warningsExplanation);
     $page->pressButton('Update');
     $this->checkForMetaRefresh();
-
-    // If a validator flags an error, but doesn't throw, the update should still
-    // be halted.
     $this->assertUpdateStagedTimes(0);
     $assert_session->pageTextContainsOnce('An error has occurred.');
     $page->clickLink('the error page');
-    $assert_session->pageTextContainsOnce((string) $expected_results[0]->getMessages()[0]);
-    // Since there's only one error message, we shouldn't see the summary...
+    // We should see the exception message, but not the validation result's
+    // messages or summary, because exceptions thrown directly by event
+    // subscribers are wrapped in simple exceptions and re-thrown.
+    $assert_session->pageTextContainsOnce($error->getMessage());
+    $assert_session->pageTextNotContains((string) $expected_results[0]->getMessages()[0]);
     $assert_session->pageTextNotContains($expected_results[0]->getSummary());
-    // ...but we should see the exception message.
-    $assert_session->pageTextContainsOnce('The update exploded.');
-    // If the error is thrown in PreCreateEvent the update stage will not have
-    // been created.
+    // Since the error occurred during pre-create, there should be no existing
+    // update to delete.
     $assert_session->buttonNotExists('Delete existing update');
+
+    // If a validator flags an error, but doesn't throw, the update should still
+    // be halted.
     TestChecker1::setTestResult($expected_results, PreCreateEvent::class);
     $page->pressButton('Update');
     $this->checkForMetaRefresh();
-- 
GitLab