diff --git a/core/includes/batch.inc b/core/includes/batch.inc
index 5fc76a8..ff0b4cb 100644
--- a/core/includes/batch.inc
+++ b/core/includes/batch.inc
@@ -439,18 +439,17 @@ function _batch_finished() {
         $queue->deleteQueue();
       }
     }
+    // Clean-up the session. Not needed for CLI updates.
+    if (isset($_SESSION)) {
+      unset($_SESSION['batches'][$batch['id']]);
+      if (empty($_SESSION['batches'])) {
+        unset($_SESSION['batches']);
+      }
+    }
   }
   $_batch = $batch;
   $batch = NULL;
 
-  // Clean-up the session. Not needed for CLI updates.
-  if (isset($_SESSION)) {
-    unset($_SESSION['batches'][$batch['id']]);
-    if (empty($_SESSION['batches'])) {
-      unset($_SESSION['batches']);
-    }
-  }
-
   // Redirect if needed.
   if ($_batch['progressive']) {
     // Revert the 'destination' that was saved in batch_process().
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 59d80d4..8df7065 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -1448,6 +1448,27 @@ function bootstrap_hooks() {
  */
 function t($string, array $args = array(), array $options = array()) {
   static $custom_strings;
+  $locale_exists = &drupal_static(__FUNCTION__);
+
+  // Check whether locale() is available and operational.
+  if (!isset($locale_exits)) {
+    // The availability of locale() requires more explanation:
+    // - Whether the module system has been loaded.
+    //   In early bootstrap, in the installer, update.php, and also in early
+    //   error/exception conditions, the module system might not be loaded yet.
+    // - Whether Locale module is enabled.
+    //   The mere existence of locale() does not imply that Locale module is
+    //   actually enabled and its database tables are installed. Since PHP code
+    //   cannot be unloaded, this is typically the case in the environment that
+    //   is executing a test.
+    // - Whether Locale module actually has been loaded and locale() exists.
+    //   The Locale module may be enabled, but the actual .module might not be
+    //   loaded. This is typically the case in update.php, which needs to
+    //   populate the full module list to invoke hook_requirements(), but must
+    //   not load any actual modules and APIs, since they are not guaranteed to
+    //   be operational.
+    $locale_exists = function_exists('locale') && function_exists('module_exists') && module_exists('locale');
+  }
 
   // Merge in default.
   if (empty($options['langcode'])) {
@@ -1469,7 +1490,7 @@ function t($string, array $args = array(), array $options = array()) {
     $string = $custom_strings[$options['langcode']][$options['context']][$string];
   }
   // Translate with locale module if enabled.
-  elseif ($options['langcode'] != LANGUAGE_SYSTEM && ($options['langcode'] != 'en' || variable_get('locale_translate_english', FALSE)) && function_exists('locale')) {
+  elseif ($options['langcode'] != LANGUAGE_SYSTEM && ($options['langcode'] != 'en' || variable_get('locale_translate_english', FALSE)) && $locale_exists) {
     $string = locale($string, $options['context'], $options['langcode']);
   }
   if (empty($args)) {
@@ -2584,7 +2605,12 @@ function drupal_fast_404() {
  * Returns TRUE if a Drupal installation is currently being attempted.
  */
 function drupal_installation_attempted() {
-  return defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'install';
+  // This cannot rely on the MAINTENANCE_MODE constant, since that would prevent
+  // tests from using the non-interactive installer, in which case Drupal is
+  // only happens to be installed within the same request, but subsequently
+  // executed code does not involve the installer at all.
+  // @see install_drupal()
+  return isset($GLOBALS['install_state']) && empty($GLOBALS['install_state']['installation_finished']);
 }
 
 /**
diff --git a/core/includes/cache.inc b/core/includes/cache.inc
index 06b0ab1..72339b0 100644
--- a/core/includes/cache.inc
+++ b/core/includes/cache.inc
@@ -26,13 +26,12 @@ use Drupal\Core\Cache\CacheFactory;
  * @see Drupal\Core\Cache\CacheBackendInterface
  */
 function cache($bin = 'cache') {
+  $cache_objects = &drupal_static(__FUNCTION__, array());
+
   // Temporary backwards compatibiltiy layer, allow old style prefixed cache
   // bin names to be passed as arguments.
   $bin = str_replace('cache_', '', $bin);
 
-  // We do not use drupal_static() here because we do not want to change the
-  // storage of a cache bin mid-request.
-  static $cache_objects;
   if (!isset($cache_objects[$bin])) {
     $cache_objects[$bin] = CacheFactory::get($bin);
   }
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 3b69ce3..ecdf25c 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -94,19 +94,27 @@ function install_drupal($settings = array()) {
       throw $e;
     }
   }
+  // After execution, all tasks might be complete, in which case
+  // $install_state['installation_finished'] is TRUE. In case the last task
+  // has been processed, remove the global $install_state, so other code can
+  // reliably check whether it is running during the installer.
+  // @see drupal_installation_attempted()
+  $state = $install_state;
+  unset($install_state);
+
   // All available tasks for this page request are now complete. Interactive
   // installations can send output to the browser or redirect the user to the
   // next page.
-  if ($install_state['interactive']) {
-    if ($install_state['parameters_changed']) {
+  if ($state['interactive']) {
+    if ($state['parameters_changed']) {
       // Redirect to the correct page if the URL parameters have changed.
-      install_goto(install_redirect_url($install_state));
+      install_goto(install_redirect_url($state));
     }
     elseif (isset($output)) {
       // Display a page only if some output is available. Otherwise it is
       // possible that we are printing a JSON page and theme output should
       // not be shown.
-      install_display_output($output, $install_state);
+      install_display_output($output, $state);
     }
   }
 }
@@ -224,7 +232,9 @@ function install_state_defaults() {
  */
 function install_begin_request(&$install_state) {
   // Add any installation parameters passed in via the URL.
-  $install_state['parameters'] += $_GET;
+  if ($install_state['interactive']) {
+    $install_state['parameters'] += $_GET;
+  }
 
   // Validate certain core settings that are used throughout the installation.
   if (!empty($install_state['parameters']['profile'])) {
@@ -240,11 +250,10 @@ function install_begin_request(&$install_state) {
   if (!$install_state['interactive']) {
     drupal_override_server_variables($install_state['server']);
   }
-
   // The user agent header is used to pass a database prefix in the request when
   // running tests. However, for security reasons, it is imperative that no
   // installation be permitted using such a prefix.
-  if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], "simpletest") !== FALSE) {
+  elseif (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], "simpletest") !== FALSE) {
     header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
     exit;
   }
diff --git a/core/includes/module.inc b/core/includes/module.inc
index cbdbeaf..83d06c4 100644
--- a/core/includes/module.inc
+++ b/core/includes/module.inc
@@ -10,26 +10,30 @@ use Drupal\Component\Graph\Graph;
 /**
  * Loads all the modules that have been enabled in the system table.
  *
- * @param $bootstrap
+ * @param bool $bootstrap
  *   Whether to load only the reduced set of modules loaded in "bootstrap mode"
- *   for cached pages. See bootstrap.inc.
+ *   for cached pages. See bootstrap.inc. Pass NULL to only check the current
+ *   status without loading of modules.
+ * @param bool $reset
+ *   (optional) Internal use only. Whether to reset the internal statically
+ *   cached flag of whether modules have been loaded. If TRUE, all modules are
+ *   (re)loaded in the same call. Used by the testing framework to override and
+ *   persist a limited module list for the duration of a unit test (in which no
+ *   module system exists).
  *
- * @return
- *   If $bootstrap is NULL, return a boolean indicating whether all modules
- *   have been loaded.
+ * @return bool
+ *   A Boolean indicating whether all modules have been loaded. This means all
+ *   modules; the load status of bootstrap modules cannot be checked.
  */
-function module_load_all($bootstrap = FALSE) {
-  // Already loaded code cannot be unloaded, but new modules may be added within
-  // a request, which should be loaded as well.
-  // Use the advanced drupal_static() pattern, since this is called very often.
-  // @see theme()
-  static $drupal_static_fast;
-  if (!isset($drupal_static_fast)) {
-    $drupal_static_fast['has_run'] = &drupal_static(__FUNCTION__, FALSE);
+function module_load_all($bootstrap = FALSE, $reset = FALSE) {
+  static $has_run = FALSE;
+
+  if ($reset) {
+    $has_run = FALSE;
   }
-  $has_run = &$drupal_static_fast['has_run'];
 
-  if (isset($bootstrap)) {
+  // Unless $boostrap is NULL, load the requested set of modules.
+  if (isset($bootstrap) && !$has_run) {
     $type = $bootstrap ? 'bootstrap' : 'module_enabled';
     foreach (module_list($type) as $module) {
       drupal_load('module', $module);
@@ -60,14 +64,29 @@ function module_load_all($bootstrap = FALSE) {
  *   list will persist until the next call with a new $fixed_list passed in.
  *   Primarily intended for internal use (e.g., in install.php and update.php).
  *   Use module_list_reset() to undo the $fixed_list override.
+ * @param bool $reset
+ *   (optional) Whether to reset/remove the $fixed_list.
  *
  * @return array
  *   An associative array whose keys and values are the names of the modules in
  *   the list.
+ *
+ * @see module_list_reset()
  */
-function module_list($type = 'module_enabled', array $fixed_list = NULL) {
-  // This static is only used for $fixed_list.
-  $module_list = &drupal_static(__FUNCTION__);
+function module_list($type = 'module_enabled', array $fixed_list = NULL, $reset = FALSE) {
+  // This static is only used for $fixed_list. It must not be a drupal_static(),
+  // since any call to drupal_static_reset() in unit tests would cause an
+  // attempt to retrieve the list of modules from the database (which does not
+  // exist).
+  static $module_list;
+
+  if ($reset) {
+    $module_list = NULL;
+    // Do nothing if no $type and no $fixed_list have been passed.
+    if (!isset($type) && !isset($fixed_list)) {
+      return;
+    }
+  }
 
   // The list that will be be returned. Separate from $module_list in order
   // to not duplicate the static cache of system_list().
@@ -93,7 +112,7 @@ function module_list($type = 'module_enabled', array $fixed_list = NULL) {
  * Subsequent calls to module_list() will no longer use a fixed list.
  */
 function module_list_reset() {
-  drupal_static_reset('module_list');
+  module_list(NULL, NULL, TRUE);
 }
 
 /**
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
index 6f17f57..ee06fe2 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
@@ -665,6 +665,14 @@ abstract class TestBase {
     }
     Database::addConnectionInfo('default', 'default', $connection_info['default']);
 
+    // Additionally override global $databases, since the installer does not use
+    // the Database connection info.
+    // @see install_verify_database_settings()
+    // @see install_database_errors()
+    // @todo Fix installer to use Database connection info.
+    global $databases;
+    $databases['default']['default'] = $connection_info['default'];
+
     // Indicate the database prefix was set up correctly.
     $this->setupDatabasePrefix = TRUE;
   }
@@ -698,7 +706,10 @@ abstract class TestBase {
     // Save further contextual information.
     $this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files');
     $this->originalProfile = drupal_get_profile();
-    $this->originalUser = $user;
+    $this->originalUser = clone $user;
+
+    // Ensure that the current session is not changed by the new environment.
+    drupal_save_session(FALSE);
 
     // Save and clean the shutdown callbacks array because it is static cached
     // and will be changed by the test run. Otherwise it will contain callbacks
@@ -793,6 +804,10 @@ abstract class TestBase {
     // Restore original database connection.
     Database::removeConnection('default');
     Database::renameConnection('simpletest_original_default', 'default');
+    // @see TestBase::changeDatabasePrefix()
+    global $databases;
+    $connection_info = Database::getConnectionInfo('default');
+    $databases['default']['default'] = $connection_info['default'];
 
     // Restore original globals.
     $GLOBALS['theme_key'] = $this->originalThemeKey;
@@ -801,9 +816,9 @@ abstract class TestBase {
     // Reset all static variables.
     drupal_static_reset();
 
-    // Restore has_run state.
-    $has_run = &drupal_static('module_load_all');
-    $has_run = TRUE;
+    // Reset module list and module load status.
+    module_list_reset();
+    module_load_all(FALSE, TRUE);
 
     // Restore original in-memory configuration.
     $conf = $this->originalConf;
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
index 10501d0..6c3b41f 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
@@ -595,6 +595,13 @@ abstract class WebTestBase extends TestBase {
     global $user, $conf;
     $language_interface = language(LANGUAGE_TYPE_INTERFACE);
 
+    // When running tests through the Simpletest UI (vs. on the command line),
+    // Simpletest's batch conflicts with the installer's batch. Batch API does
+    // not support the concept of nested batches (in which the nested is not
+    // progressive), so we need to temporarily pretend there was no batch.
+    // Backup the currently running Simpletest batch.
+    $this->originalBatch = batch_get();
+
     // Create the database prefix for this test.
     $this->prepareDatabasePrefix();
 
@@ -617,16 +624,75 @@ abstract class WebTestBase extends TestBase {
       return FALSE;
     }
 
-    // Preset the 'install_profile' system variable, so the first call into
-    // system_rebuild_module_data() (in drupal_install_system()) will register
-    // the test's profile as a module. Without this, the installation profile of
-    // the parent site (executing the test) is registered, and the test
-    // profile's hook_install() and other hook implementations are never invoked.
-    $conf['install_profile'] = $this->profile;
+    // Set the 'simpletest_parent_profile' variable to add the parent profile's
+    // search path to the child site's search paths.
+    // @see drupal_system_listing()
+    $conf['simpletest_parent_profile'] = $this->originalProfile;
+
+    // Set installer parameters.
+    // @see install.php, install.core.inc
+    $connection_info = Database::getConnectionInfo('default');
+    $this->root_user = (object) array(
+      'name' => 'admin',
+      'mail' => 'admin@example.com',
+      'pass_raw' => $this->randomName(),
+    );
+    $settings = array(
+      'interactive' => FALSE,
+      'parameters' => array(
+        'profile' => $this->profile,
+        'langcode' => 'en',
+      ),
+      'forms' => array(
+        'install_settings_form' => array(
+          'driver' => $connection_info['default']['driver'],
+          'username' => $connection_info['default']['username'],
+          'host' => $connection_info['default']['host'],
+          'port' => $connection_info['default']['port'],
+          'password' => $connection_info['default']['password'],
+          'database' => $connection_info['default']['database'],
+          'prefix' => $connection_info['default']['prefix'],
+        ),
+        'install_configure_form' => array(
+          'site_name' => 'Drupal',
+          'site_mail' => 'simpletest@example.com',
+          'account' => array(
+            'name' => $this->root_user->name,
+            'mail' => $this->root_user->mail,
+            'pass' => array(
+              'pass1' => $this->root_user->pass_raw,
+              'pass2' => $this->root_user->pass_raw,
+            ),
+          ),
+          // form_type_checkboxes_value() requires NULL instead of FALSE values
+          // for programmatic form submissions to disable a checkbox.
+          'update_status_module' => array(
+            1 => NULL,
+            2 => NULL,
+          ),
+        ),
+      ),
+    );
+
+    // Replace the global $user session with an anonymous user to resemble a
+    // regular installation.
+    $user = drupal_anonymous_user();
+
+    // Reset the static batch to remove Simpletest's batch operations.
+    $batch = &batch_get();
+    $batch = array();
+
+    // Execute the non-interactive installer.
+    require_once DRUPAL_ROOT . '/core/includes/install.core.inc';
+    install_drupal($settings);
 
-    // Perform the actual Drupal installation.
-    include_once DRUPAL_ROOT . '/core/includes/install.inc';
-    drupal_install_system();
+    // Restore the original Simpletest batch.
+    $batch = &batch_get();
+    $batch = $this->originalBatch;
+
+    // Revert install_begin_request() cache and lock service overrides.
+    unset($conf['cache_classes']);
+    unset($conf['lock_backend']);
 
     // Set path variables.
     variable_set('file_public_path', $this->public_files_directory);
@@ -636,16 +702,8 @@ abstract class WebTestBase extends TestBase {
     // Set 'parent_profile' of simpletest to add the parent profile's
     // search path to the child site's search paths.
     // @see drupal_system_listing()
-    // @todo This may need to be primed like 'install_profile' above.
     config('simpletest.settings')->set('parent_profile', $this->originalProfile)->save();
 
-    // Include the testing profile.
-    variable_set('install_profile', $this->profile);
-    $profile_details = install_profile_info($this->profile, 'en');
-
-    // Install the modules specified by the testing profile.
-    module_enable($profile_details['dependencies'], FALSE);
-
     // Collect modules to install.
     $class = get_class($this);
     $modules = array();
@@ -660,14 +718,6 @@ abstract class WebTestBase extends TestBase {
       $this->assertTrue($success, t('Enabled modules: %modules', array('%modules' => implode(', ', $modules))));
     }
 
-    // Run the profile tasks.
-    $install_profile_module_exists = db_query("SELECT 1 FROM {system} WHERE type = 'module' AND name = :name", array(
-      ':name' => $this->profile,
-    ))->fetchField();
-    if ($install_profile_module_exists) {
-      module_enable(array($this->profile), FALSE);
-    }
-
     // Create a new DrupalKernel for testing purposes, now that all required
     // modules have been enabled. This also stores a new dependency injection
     // container in drupal_container(). Drupal\simpletest\TestBase::tearDown()
@@ -687,25 +737,6 @@ abstract class WebTestBase extends TestBase {
     // Reset/rebuild all data structures after enabling the modules.
     $this->resetAll();
 
-    // Run cron once in that environment, as install.php does at the end of
-    // the installation process.
-    drupal_cron_run();
-
-    // Ensure that the session is not written to the new environment and replace
-    // the global $user session with uid 1 from the new test site.
-    drupal_save_session(FALSE);
-    // Login as uid 1.
-    $user = user_load(1);
-
-    // Restore necessary variables.
-    variable_set('install_task', 'done');
-    config('system.site')->set('mail', 'simpletest@example.com')->save();
-    variable_set('date_default_timezone', date_default_timezone_get());
-
-    // Set up English language.
-    unset($conf['language_default']);
-    $language_interface = language_default();
-
     // Use the test mail class instead of the default mail handler class.
     variable_set('mail_system', array('default-system' => 'Drupal\Core\Mail\VariableLog'));
 
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
index a0f6c59..86456e5 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
@@ -191,13 +191,13 @@ abstract class UpgradePathTestBase extends WebTestBase {
     $update_url = $GLOBALS['base_url'] . '/core/update.php';
     $this->drupalGet($update_url, array('external' => TRUE));
     if (!$this->assertResponse(200)) {
-      return FALSE;
+      throw new Exception('Initial GET to update.php did not return HTTP 200 status.');
     }
 
     // Continue.
     $this->drupalPost(NULL, array(), t('Continue'));
     if (!$this->assertResponse(200)) {
-      return FALSE;
+      throw new Exception('POST to continue update.php did not return HTTP 200 status.');
     }
 
     // The test should pass if there are no pending updates.
@@ -211,7 +211,7 @@ abstract class UpgradePathTestBase extends WebTestBase {
     // Go!
     $this->drupalPost(NULL, array(), t('Apply pending updates'));
     if (!$this->assertResponse(200)) {
-      return FALSE;
+      throw new Exception('POST to update.php to apply pending updates did not return HTTP 200 status.');
     }
 
     // Check for errors during the update process.
@@ -222,36 +222,30 @@ abstract class UpgradePathTestBase extends WebTestBase {
         $this->fail($message);
       }
     }
-
     if (!empty($this->upgradeErrors)) {
       // Upgrade failed, the installation might be in an inconsistent state,
       // don't process.
-      return FALSE;
+      throw new Exception('Errors during update process.');
     }
 
     // Check if there still are pending updates.
     $this->drupalGet($update_url, array('external' => TRUE));
     $this->drupalPost(NULL, array(), t('Continue'));
     if (!$this->assertText(t('No pending updates.'), t('No pending updates at the end of the update process.'))) {
-      return FALSE;
+      throw new Exception('update.php still shows pending updates after execution.');
     }
 
     // Upgrade succeed, rebuild the environment so that we can call the API
     // of the child site directly from this request.
     $this->upgradedSite = TRUE;
 
-    // Reload module list. For modules that are enabled in the test database,
-    // but not on the test client, we need to load the code here.
+    // Reload module list for modules that are enabled in the test database
+    // but not on the test client.
     system_list_reset();
-    foreach (module_list() as $module) {
-      drupal_load('module', $module);
-    }
-
-    // Reload hook implementations
     module_implements_reset();
+    module_load_all(FALSE, TRUE);
 
     // Rebuild caches.
-    drupal_static_reset();
     drupal_flush_all_caches();
 
     // Reload global $conf array and permissions.
