diff --git a/core/includes/batch.inc b/core/includes/batch.inc
index 0b07d8e..3bb90ab 100644
--- a/core/includes/batch.inc
+++ b/core/includes/batch.inc
@@ -480,18 +480,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 7daf311..2933eca 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -2562,7 +2562,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 73bb05f..554fe95 100644
--- a/core/includes/cache.inc
+++ b/core/includes/cache.inc
@@ -24,13 +24,12 @@
  * @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_backends = cache_get_backends();
     $class = isset($cache_backends[$bin]) ? $cache_backends[$bin] : $cache_backends['cache'];
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 0481570..03d9824 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -95,19 +95,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);
     }
   }
 }
@@ -225,7 +233,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'])) {
@@ -241,11 +251,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/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
index cccd011..17393ba 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
@@ -662,6 +662,14 @@ protected function changeDatabasePrefix() {
     }
     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;
   }
@@ -693,7 +701,10 @@ protected function prepareEnvironment() {
     // 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
@@ -766,6 +777,10 @@ protected function tearDown() {
     // 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'];
 
     // Reset all static variables.
     drupal_static_reset();
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
index eda2365..70dc0d6 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
@@ -581,6 +581,13 @@ protected function setUp() {
     global $user, $conf;
     $language_interface = language_manager(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();
 
@@ -603,18 +610,75 @@ protected function setUp() {
       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();
 
-    // Perform the actual Drupal installation.
-    include_once DRUPAL_ROOT . '/core/includes/install.inc';
-    drupal_install_system();
+    // Execute the non-interactive installer.
+    require_once DRUPAL_ROOT . '/core/includes/install.core.inc';
+    install_drupal($settings);
 
-    $this->preloadRegistry();
+    // 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);
@@ -624,16 +688,8 @@ protected function setUp() {
     // 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()
-    // @todo This may need to be primed like 'install_profile' above.
     variable_set('simpletest_parent_profile', $this->originalProfile);
 
-    // 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);
-
     // Install modules needed for this test. This could have been passed in as
     // either a single array argument or a variable number of string arguments.
     // @todo Remove this after fully converting to static $modules property.
@@ -654,36 +710,9 @@ protected function setUp() {
       $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);
-    }
-
     // 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'));
 
@@ -692,42 +721,6 @@ protected function setUp() {
   }
 
   /**
-   * Preload the registry from the testing site.
-   *
-   * This method is called by Drupal\simpletest\WebTestBase::setUp(), and preloads
-   * the registry from the testing site to cut down on the time it takes to
-   * set up a clean environment for the current test run.
-   */
-  protected function preloadRegistry() {
-    // Use two separate queries, each with their own connections: copy the
-    // {registry} and {registry_file} tables over from the parent installation
-    // to the child installation.
-    $original_connection = Database::getConnection('default', 'simpletest_original_default');
-    $test_connection = Database::getConnection();
-
-    foreach (array('registry', 'registry_file') as $table) {
-      // Find the records from the parent database.
-      $source_query = $original_connection
-        ->select($table, array(), array('fetch' => PDO::FETCH_ASSOC))
-        ->fields($table);
-
-      $dest_query = $test_connection->insert($table);
-
-      $first = TRUE;
-      foreach ($source_query->execute() as $row) {
-        if ($first) {
-          $dest_query->fields(array_keys($row));
-          $first = FALSE;
-        }
-        // Insert the records into the child database.
-        $dest_query->values($row);
-      }
-
-      $dest_query->execute();
-    }
-  }
-
-  /**
    * Reset all data structures after having enabled new modules.
    *
    * This method is called by Drupal\simpletest\WebTestBase::setUp() after enabling
