diff --git a/core/includes/install.inc b/core/includes/install.inc
index 77e0b70..8321048 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -979,11 +979,32 @@ function drupal_check_module($module) {
     // Print any error messages
     foreach ($requirements as $requirement) {
       if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) {
-        $message = $requirement['description'];
+        $message = [];
+
+        // If the requirement description is already a render array, add it
+        // to the message array.
+        if (is_array($requirement['description'])) {
+          $message['description'] = $requirement['description'];
+        }
+        // Otherwise, add a #markup element for the description string.
+        else {
+          $message['description'] = ['#markup' => $requirement['description']];
+        }
+        // Set the description weight to 0. If the description was previously
+        // a render array, we overwrite the existing weight here.
+        $message['description']['#weight'] = 0;
+
+        // If a required value was provided for the item, add it to the error
+        // message. See the hook_requirements() documentation.
         if (isset($requirement['value']) && $requirement['value']) {
-          $message = t('@requirements_message (Currently using @item version @version)', array('@requirements_message' => $requirement['description'], '@item' => $requirement['title'], '@version' => $requirement['value']));
+          $message['version'] = [
+            '#prefix' => ' (',
+            '#markup' => t('Currently using @item version @version', array('@item' => $requirement['title'], '@version' => $requirement['value'])),
+            '#suffix' => ')',
+            '#weight' => 1,
+          ];
         }
-        drupal_set_message($message, 'error');
+        drupal_set_message(\Drupal::service('renderer')->renderPlain($message), 'error');
       }
     }
     return FALSE;
diff --git a/core/lib/Drupal/Core/Extension/module.api.php b/core/lib/Drupal/Core/Extension/module.api.php
index e30fd87..2f7af03 100644
--- a/core/lib/Drupal/Core/Extension/module.api.php
+++ b/core/lib/Drupal/Core/Extension/module.api.php
@@ -823,7 +823,8 @@ function hook_updater_info_alter(&$updaters) {
  *   - value: The current value (e.g., version, time, level, etc). During
  *     install phase, this should only be used for version numbers, do not set
  *     it if not applicable.
- *   - description: The description of the requirement/status.
+ *   - description: The description of the requirement/status. May be either a
+ *     string or a render array.
  *   - severity: The requirement's result/severity level, one of:
  *     - REQUIREMENT_INFO: For info only.
  *     - REQUIREMENT_OK: The requirement is satisfied.
diff --git a/core/modules/aggregator/aggregator.install b/core/modules/aggregator/aggregator.install
index 2b755e1..f6d69ea 100644
--- a/core/modules/aggregator/aggregator.install
+++ b/core/modules/aggregator/aggregator.install
@@ -20,5 +20,23 @@ function aggregator_requirements($phase) {
     $requirements['curl']['severity'] = REQUIREMENT_ERROR;
     $requirements['curl']['description'] = t('The Aggregator module could not be installed because the PHP <a href="@curl_url">cURL</a> library is not available.', array('@curl_url' => 'http://php.net/manual/curl.setup.php'));
   }
+  if ($phase == 'update') {
+    $requirements['other']['severity'] = REQUIREMENT_ERROR;
+    // $requirements['other']['description'] = t('The Aggregator module doesnt want to be updated.');
+    $requirements['other']['description'] = [
+      '#theme' => 'item_list',
+      '#title' => t('Requirements 1 render array with <em>markup!</em>'),
+      '#items' => ['Requirements 1 first item', 'Requirements 1 second item'],
+      '#weight' => 500,
+    ];
+  }
+
   return $requirements;
 }
+
+/**
+ * An update function to trick it into thinking there is an update to run.
+ */
+function aggregator_update_8446() {
+  return 'This should not show. Cause it should not run.';
+}
diff --git a/core/modules/system/src/Tests/Module/DependencyTest.php b/core/modules/system/src/Tests/Module/DependencyTest.php
index e9162a8..05f17cd 100644
--- a/core/modules/system/src/Tests/Module/DependencyTest.php
+++ b/core/modules/system/src/Tests/Module/DependencyTest.php
@@ -104,6 +104,10 @@ function testEnableRequirementsFailureDependency() {
     $this->assertModules(array('requirements1_test'), FALSE);
     $this->assertModules(array('requirements2_test'), FALSE);
 
+    // Configure requirements1_test to fail installation.
+    \Drupal::state()->set('requirements1_test.phase', 'install');
+    \Drupal::state()->set('requirements1_test.severity', REQUIREMENT_ERROR);
+
     // Attempt to install both modules at the same time.
     $edit = array();
     $edit['modules[Testing][requirements1_test][enable]'] = 'requirements1_test';
@@ -111,7 +115,7 @@ function testEnableRequirementsFailureDependency() {
     $this->drupalPostForm('admin/modules', $edit, t('Install'));
 
     // Makes sure the modules were NOT installed.
-    $this->assertText(t('Requirements 1 Test failed requirements'), 'Modules status has been updated.');
+    $this->assertText('Requirements 1 string');
     $this->assertModules(array('requirements1_test'), FALSE);
     $this->assertModules(array('requirements2_test'), FALSE);
 
diff --git a/core/modules/system/src/Tests/Module/HookRequirementsTest.php b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
index 83fdce9..b756a05 100644
--- a/core/modules/system/src/Tests/Module/HookRequirementsTest.php
+++ b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
@@ -8,24 +8,283 @@
 namespace Drupal\system\Tests\Module;
 
 /**
- * Attempts enabling a module that fails hook_requirements('install').
+ * Tests hook_requirements().
  *
  * @group Module
  */
 class HookRequirementsTest extends ModuleTestBase {
+
+  /**
+   * The requirement phase.
+   *
+   * @var string
+   */
+  protected $phase;
+
+  /**
+   * The requirement severity.
+   *
+   * @var int
+   */
+  protected $severity;
+
+  /**
+   * Whether to use a render array for the requirement description text.
+   *
+   * @var bool
+   */
+  protected $descriptionArray;
+
   /**
-   * Assert that a module cannot be installed if it fails hook_requirements().
+   * Whether to use a requirement value.
+   *
+   * @var bool
    */
-  function testHookRequirementsFailure() {
-    $this->assertModules(array('requirements1_test'), FALSE);
+  protected $useValue;
+
+  /**
+   * Tests the result hook_requirements() during install and runtime phases.
+   *
+   * @see requirements1_test_requirements()
+   */
+  function testHookRequirements() {
+    // Test installing the module with all possible combinations of values in
+    // the hook_requirements() hook.
+
+    // Test all allowed severities.
+    foreach ([REQUIREMENT_ERROR, REQUIREMENT_WARNING, REQUIREMENT_OK, REQUIREMENT_INFO] as $this->severity) {
+      // Test all allowed phases.
+      foreach (['install', 'update', 'runtime'] as $this->phase) {
+        // Test with a render array description and a string description.
+        foreach ([TRUE, FALSE] as $this->descriptionArray) {
+          // Test with and without a 'value' key.
+          foreach ([TRUE, FALSE] as $this->useValue) {
+            $this->doInstallTest();
+          }
+        }
+      }
+    }
 
-    // Attempt to install the requirements1_test module.
-    $edit = array();
+    // Now, configure the requirements hook to allow installation, and install
+    // the module.
+    \Drupal::state()->set('requirements1_test.severity', REQUIREMENT_INFO);
+    $this->container->get('module_installer')->install(['requirements1_test']);
+
+    // Test the status report with all possible combinations of values in the
+    // hook_requirements() hook.
+    // Test all allowed severities.
+    foreach ([REQUIREMENT_ERROR, REQUIREMENT_WARNING, REQUIREMENT_OK, REQUIREMENT_INFO] as $this->severity) {
+      // Test all allowed phases.
+      foreach (['install', 'update', 'runtime'] as $this->phase) {
+        // Test with a render array description and a string description.
+        foreach ([TRUE, FALSE] as $this->descriptionArray) {
+          // Test with and without a 'value' key.
+          foreach ([TRUE, FALSE] as $this->useValue) {
+            // Test the runtime messages.
+            $this->doRuntimeTest();
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Tests hook_requirements() message lists "value" after the description.
+   *
+   * @see requirements1_test_requirements()
+   * @see hook_requirements()
+   */
+  public function testVersionRequirements() {
+    // Make sure the test module is not installed.
+    $this->assertModules(['requirements1_test'], FALSE);
+
+    // Configure the module's hook_requirements(). Testing with render array on
+    // and use value on.
+    \Drupal::state()->set('requirements1_test.phase', "install");
+    \Drupal::state()->set('requirements1_test.severity', REQUIREMENT_ERROR);
+    \Drupal::state()->set('requirements1_test.description_array', TRUE);
+    \Drupal::state()->set('requirements1_test.use_value', TRUE);
+
+    // Attempt to install the module.
+    $edit = [];
     $edit['modules[Testing][requirements1_test][enable]'] = 'requirements1_test';
     $this->drupalPostForm('admin/modules', $edit, t('Install'));
 
-    // Makes sure the module was NOT installed.
-    $this->assertText(t('Requirements 1 Test failed requirements'), 'Modules status has been updated.');
-    $this->assertModules(array('requirements1_test'), FALSE);
+    // Verify the module did not install.
+    $this->assertModules(['requirements1_test'], FALSE);
+
+    // Check that the rendered description (from a render array) is followed by
+    // the value string.
+    parent::assertText('second item (Currently using');
+
+    // Re-configure the module's hook_requirements(). Testing with render array
+    // off (using a string) and use value on.
+    \Drupal::state()->set('requirements1_test.phase', "install");
+    \Drupal::state()->set('requirements1_test.severity', REQUIREMENT_ERROR);
+    \Drupal::state()->set('requirements1_test.description_array', FALSE);
+    \Drupal::state()->set('requirements1_test.use_value', TRUE);
+
+    // Attempt to install the module.
+    $this->drupalPostForm('admin/modules', $edit, t('Install'));
+
+    // Check that the rendered description (from a render array) is followed by
+    // the value string.
+    parent::assertText('markup!. (Currently using');
+
+    // Re-configure the module's hook_requirements(). Testing with render array
+    // off and use value off.
+    \Drupal::state()->set('requirements1_test.phase', "install");
+    \Drupal::state()->set('requirements1_test.severity', REQUIREMENT_ERROR);
+    \Drupal::state()->set('requirements1_test.description_array', FALSE);
+    \Drupal::state()->set('requirements1_test.use_value', FALSE);
+
+    // Attempt to install the module.
+    $this->drupalPostForm('admin/modules', $edit, t('Install'));
+
+    parent::assertNoText('second item (Currently using');
+    parent::assertNoText('markup!. (Currently using');
   }
+
+  /**
+   * Tests installing the module with the current hook_requirements() values.
+   *
+   * @see requirements1_test_requirements()
+   */
+  protected function doInstallTest() {
+    // Only install phase requirement errors should fail installation.
+    if (($this->phase == 'install') && ($this->severity == REQUIREMENT_ERROR)) {
+      $success = FALSE;
+    }
+    else {
+      $success = TRUE;
+    }
+
+    // Ensure that the module is not currently installed.
+    $this->assertModules(['requirements1_test'], FALSE);
+
+    // Configure the module's hook_requirements().
+    \Drupal::state()->set('requirements1_test.phase', $this->phase);
+    \Drupal::state()->set('requirements1_test.severity', $this->severity);
+    \Drupal::state()->set('requirements1_test.description_array', $this->descriptionArray);
+    \Drupal::state()->set('requirements1_test.use_value', $this->useValue);
+
+    // Attempt to install the module.
+    $edit = [];
+    $edit['modules[Testing][requirements1_test][enable]'] = 'requirements1_test';
+    $this->drupalPostForm('admin/modules', $edit, t('Install'));
+
+    // Assert the expected success or failure.
+    $this->assertModules(['requirements1_test'], $success);
+
+    // If the installation was successful, no requirements message should be
+    // displayed. A message is only displayed during the install phase when
+    // installation fails. See the hook_requirements() documentation.
+    if ($success) {
+      $this->assertNoRequirementsMessages();
+
+      // Uninstall the module to prepare for the next test.
+      $this->container->get('module_installer')->uninstall(['requirements1_test']);
+    }
+
+    // Otherwise, if the installation was not successful, the requirements
+    // error should be displayed.
+    else {
+      $this->assertExpectedRequirementsMessages();
+    }
+  }
+
+  /**
+   * Tests the status report with the current hook_requirements() values.
+   *
+   * @see requirements1_test_requirements()
+   */
+  protected function doRuntimeTest() {
+    // Configure the module's hook_requirements().
+    \Drupal::state()->set('requirements1_test.phase', $this->phase);
+    \Drupal::state()->set('requirements1_test.severity', $this->severity);
+    \Drupal::state()->set('requirements1_test.description_array', $this->descriptionArray);
+    \Drupal::state()->set('requirements1_test.use_value', $this->useValue);
+
+    // Visit the reports page.
+    $this->drupalGet('admin/reports/status');
+
+    // If the runtime phase was used, we can expect messages for any severity.
+    if ($this->phase == 'runtime') {
+      $this->assertExpectedRequirementsMessages();
+    }
+
+    // For other phase values, none of the messages should be displayed.
+    else {
+      $this->assertNoRequirementsMessages();
+    }
+  }
+
+  /**
+   * Asserts the expected hook messages with the current seettings.
+   *
+   * @see requirements1_test_requirements()
+   */
+  protected function assertExpectedRequirementsMessages() {
+    // @todo Currently, the 'title' is only displayed during the install phase
+    //   if the 'value' is also defined. This may not be intentional. Fix in
+    //   https://www.drupal.org/node/2549803.
+    if (($this->phase == 'install') && !$this->useValue) {
+      $this->assertNoText('Requirements 1 title');
+    }
+    else {
+      // Normally, the requirement title should always be displayed.
+      $this->assertRaw('Requirements 1 title with <em>markup!</em>');
+    }
+
+    // If the requirements description was a render array, check that it was
+    // rendered correctly.
+    if ($this->descriptionArray) {
+      $this->assertRaw('Requirements 1 render array with <em>markup!</em>');
+      $this->assertText('Requirements 1 first item');
+      $this->assertText('Requirements 1 second item');
+    }
+    // Otherwise, test for the string message.
+    else {
+      $this->assertRaw('Requirements 1 string with <em>markup!</em>');
+    }
+
+    // The value should be displayed if it was set.
+    if ($this->useValue) {
+      $this->assertRaw('Requirements 1 value text with <em>markup!</em>');
+    }
+    else {
+      $this->assertNoText('Requirements 1 value text');
+    }
+  }
+
+  /**
+   * Asserts that none of the possible requirements messages are displayed.
+   *
+   * @see requirements1_test_requirements()
+   */
+  protected function assertNoRequirementsMessages() {
+    $this->assertNoText('Requirements 1 title');
+    $this->assertNoText('Requirements 1 string');
+    $this->assertNoText('Requirements 1 render array');
+    $this->assertNoText('Requirements 1 first item');
+    $this->assertNoText('Requirements 1 second item');
+    $this->assertNoText('Requirements 1 value text');
+  }
+
+  /**
+   * {@inheritoc}
+   */
+  protected function assertText($text, $message = '', $group = 'Other') {
+    $message = "Phase {$this->phase}, severity {$this->severity}, value {$this->useValue}: $text found.";
+    parent::assertText($text, $message, $group);
+  }
+
+  /**
+   * {@inheritoc}
+   */
+  protected function assertNoText($text, $message = '', $group = 'Other') {
+    $message = "Phase {$this->phase}, severity {$this->severity}, value {$this->useValue}: $text not found.";
+    parent::assertNoText($text, $message, $group);
+  }
+
 }
diff --git a/core/modules/system/src/Tests/Module/ModuleTestBase.php b/core/modules/system/src/Tests/Module/ModuleTestBase.php
index 1f225bb..c2d457f 100644
--- a/core/modules/system/src/Tests/Module/ModuleTestBase.php
+++ b/core/modules/system/src/Tests/Module/ModuleTestBase.php
@@ -30,7 +30,7 @@
   protected function setUp() {
     parent::setUp();
 
-    $this->adminUser = $this->drupalCreateUser(array('access administration pages', 'administer modules'));
+    $this->adminUser = $this->drupalCreateUser(array('access administration pages', 'administer modules', 'administer site configuration'));
     $this->drupalLogin($this->adminUser);
   }
 
diff --git a/core/modules/system/tests/modules/requirements1_test/requirements1_test.install b/core/modules/system/tests/modules/requirements1_test/requirements1_test.install
index 80220b9..943dcb0 100644
--- a/core/modules/system/tests/modules/requirements1_test/requirements1_test.install
+++ b/core/modules/system/tests/modules/requirements1_test/requirements1_test.install
@@ -2,18 +2,53 @@
 
 /**
  * Implements hook_requirements().
+ *
+ * Test modules may use four state variables to control this hook:
+ * - requirements1_test.phase: Whether to add the message for 'install',
+ *   'update', or 'runtime' phase.
+ * - requirements1_test.severity: The value for the 'severity' of the
+ *   requirement, e.g. REQUIREMENT_ERROR
+ * - requirements1_test.description_array: Boolean indicating whether to use a
+ *   render array for the description message (TRUE) or a plain string (FALSE).
+ * - requirements1_test.use_value: Whether to use the fantastic 'value' key,
+ *   which has different meanings in different contexts!
+ *
+ * @see hook_requirements()
+ * @see \Drupal\system\Tests\Module\HookRequirementsTest
  */
 function requirements1_test_requirements($phase) {
   $requirements = array();
 
-  // Always fails requirements.
-  if ('install' == $phase) {
-    $requirements['requirements1_test'] = array(
-      'title' => t('Requirements 1 Test'),
-      'severity' => REQUIREMENT_ERROR,
-      'description' => t('Requirements 1 Test failed requirements.'),
-    );
+  // Add the requirement only for the phase set in the test.
+  if ($phase != \Drupal::state()->get('requirements1_test.phase')) {
+    return $requirements;
   }
 
+  // Prepare the requirement message.
+  $requirement = [];
+  $requirement['title'] = t('Requirements 1 title with <em>markup!</em>');
+
+  // Use the severity level set in the test.
+  $requirement['severity'] = \Drupal::state()->get('requirements1_test.severity');
+
+  // Add a value to the requirement if indicated in the test.
+  if (\Drupal::state()->get('requirements1_test.use_value')) {
+    $requirement['value'] = t('Requirements 1 value text with <em>markup!</em>');
+  }
+
+  // Use a render array or a string for the description based on the value set
+  // in the test.
+  if (\Drupal::state()->get('requirements1_test.description_array')) {
+    $requirement['description'] = [
+      '#theme' => 'item_list',
+      '#title' => t('Requirements 1 render array with <em>markup!</em>'),
+      '#items' => ['Requirements 1 first item', 'Requirements 1 second item'],
+    ];
+  }
+  else {
+    $requirement['description'] = t('Requirements 1 string with <em>markup!</em>.');
+  }
+
+  $requirements['requirements1_test'] = $requirement;
   return $requirements;
 }
