diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 51f4446..4928acc 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -269,15 +269,15 @@ function timer_stop($name) {
  * @see default.settings.php
  */
 function conf_path($require_settings = TRUE, $reset = FALSE) {
-  $conf_path = &drupal_static(__FUNCTION__, '');
+  static $conf_path;
 
-  if ($conf_path && !$reset) {
+  if (isset($conf_path) && !$reset) {
     return $conf_path;
   }
 
   // Check for a simpletest override.
-  if ($simpletest_conf_path = _drupal_simpletest_conf_path()) {
-    $conf_path = $simpletest_conf_path;
+  if ($test_prefix = drupal_valid_test_ua()) {
+    $conf_path = 'sites/simpletest/' . substr($test_prefix, 10);
     return $conf_path;
   }
 
@@ -292,50 +292,6 @@ function conf_path($require_settings = TRUE, $reset = FALSE) {
 }
 
 /**
- * Determines whether to use an overridden value for conf_path().
- *
- * Simpletest may provide a secondary, test-specific settings.php file to load
- * after the primary one used by the parent site and override its variables.
- * - If the child settings.php does not override $conf_path, then this function
- * returns FALSE and conf_path() returns the directory of the primary
- * settings.php.
- * - If the child settings.php does override $conf_path, then
- * _drupal_load_test_overrides() sets the 'simpletest_conf_path' setting, and
- * this function returns that to conf_path(), causing installations and
- * upgrades to act on that one.
- *
- * @return string|false
- *   The overridden $conf_path, or FALSE if the $conf_path should not currently
- *   be overridden.
- *
- * @see conf_path()
- * @see _drupal_load_test_overrides()
- */
-function _drupal_simpletest_conf_path() {
-  // Ensure that the settings object is available. conf_path() is called once
-  // before the Settings class is included, and at that point it should still
-  // load the primary $conf_path. See drupal_settings_initialize().
-  if (!class_exists('Drupal\Component\Utility\Settings', FALSE)) {
-    return FALSE;
-  }
-
-  // If no $simpletest_conf_path is set, use the normal $conf_path.
-  if (!($simpletest_conf_path = settings()->get('simpletest_conf_path'))) {
-    return FALSE;
-  }
-
-  // Ensure that this is actually a simpletest request. We can't check this
-  // before settings.php is loaded.
-  if (!drupal_valid_test_ua()) {
-    return FALSE;
-  }
-
-  // When the $simpletest_conf_path is set in a valid test request,
-  // return that path.
-  return $simpletest_conf_path;
-}
-
-/**
  * Finds the appropriate configuration directory for a given host and path.
  *
  * Finds a matching configuration directory file by stripping the website's
@@ -563,20 +519,33 @@ function drupal_valid_http_host($host) {
  * Sets the base URL, cookie domain, and session name from configuration.
  */
 function drupal_settings_initialize() {
-  global $base_url, $base_path, $base_root, $script_path;
-
   // Export these settings.php variables to the global namespace.
-  global $databases, $cookie_domain, $conf, $db_prefix, $drupal_hash_salt, $base_secure_url, $base_insecure_url, $config_directories;
+  global $base_url, $databases, $cookie_domain, $conf, $drupal_hash_salt, $config_directories;
   $conf = array();
+  $settings = array();
 
   // Make conf_path() available as local variable in settings.php.
   $conf_path = conf_path();
   if (is_readable(DRUPAL_ROOT . '/' . $conf_path . '/settings.php')) {
-    include_once DRUPAL_ROOT . '/' . $conf_path . '/settings.php';
+    require DRUPAL_ROOT . '/' . $conf_path . '/settings.php';
   }
-  require_once __DIR__ . '../../lib/Drupal/Component/Utility/Settings.php';
 
-  new Settings(isset($settings) ? $settings : array());
+  new Settings($settings);
+}
+
+/**
+ * Initializes global request variables.
+ *
+ * @todo D8: Eliminate this entirely in favor of Request object.
+ */
+function _drupal_request_initialize() {
+  // Provided by settings.php.
+  // @see drupal_settings_initialize()
+  global $base_url, $cookie_domain;
+  // Set and derived from $base_url by this function.
+  global $base_path, $base_root, $script_path;
+  global $base_secure_url, $base_insecure_url;
+
   $is_https = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on';
 
   if (isset($base_url)) {
@@ -1954,9 +1923,21 @@ function _drupal_bootstrap_configuration() {
   drupal_environment_initialize();
   // Initialize the configuration, including variables from settings.php.
   drupal_settings_initialize();
+  _drupal_request_initialize();
 
   // Make sure we are using the test database prefix in child Drupal sites.
-  _drupal_initialize_db_test_prefix();
+  if ($test_prefix = drupal_valid_test_ua()) {
+    // Indicate that code is operating in a test child site.
+    // Only code that interfaces directly with tests should rely on this
+    // constant; e.g., the error/exception handler conditionally adds further
+    // error information into HTTP response headers that are consumed by
+    // Simpletest's internal browser.
+    define('DRUPAL_TEST_IN_CHILD_SITE', TRUE);
+
+    // Log fatal errors to the test site directory.
+    ini_set('log_errors', 1);
+    ini_set('error_log', DRUPAL_ROOT . '/sites/simpletest/' . substr($test_prefix, 10) . '/error.log');
+  }
 
   // Activate the class loader.
   drupal_classloader();
@@ -2046,39 +2027,6 @@ function _drupal_bootstrap_page_cache() {
 }
 
 /**
- * In a test environment, get the test db prefix and set it in $databases.
- */
-function _drupal_initialize_db_test_prefix() {
-  // 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 we
-  // validate we ourselves made the request.
-  if ($test_prefix = drupal_valid_test_ua()) {
-    // Set the test run id for use in other parts of Drupal.
-    $test_info = &$GLOBALS['drupal_test_info'];
-    $test_info['test_run_id'] = $test_prefix;
-    $test_info['in_child_site'] = TRUE;
-
-    foreach ($GLOBALS['databases']['default'] as &$value) {
-      // Extract the current default database prefix.
-      if (!isset($value['prefix'])) {
-        $current_prefix = '';
-      }
-      elseif (is_array($value['prefix'])) {
-        $current_prefix = $value['prefix']['default'];
-      }
-      else {
-        $current_prefix = $value['prefix'];
-      }
-
-      // Remove the current database prefix and replace it by our own.
-      $value['prefix'] = array(
-        'default' => $current_prefix . $test_prefix,
-      );
-    }
-  }
-}
-
-/**
  * Loads system variables and all enabled bootstrap modules.
  */
 function _drupal_bootstrap_variables() {
@@ -2202,10 +2150,9 @@ function module_hook($module, $hook) {
  * Returns the test prefix if this is an internal request from SimpleTest.
  *
  * @param string $new_prefix
- *   Internal use only. A new prefix to be stored. Passed in by tests that use
- *   the test runner from within a test.
+ *   Internal use only. A new prefix to be stored.
  *
- * @return
+ * @return string|FALSE
  *   Either the simpletest prefix (the string "simpletest" followed by any
  *   number of digits) or FALSE if the user agent does not contain a valid
  *   HMAC and timestamp.
@@ -2219,80 +2166,67 @@ function drupal_valid_test_ua($new_prefix = NULL) {
   if (isset($test_prefix)) {
     return $test_prefix;
   }
+  // Unless the below User-Agent and HMAC validation succeeds, we are not in
+  // a test environment.
+  $test_prefix = FALSE;
 
+  // Perform a basic check on the User-Agent HTTP request header first. Any
+  // inbound request that uses the simpletest UA header needs to be validated.
   if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^(simpletest\d+);(.+);(.+);(.+)$/", $_SERVER['HTTP_USER_AGENT'], $matches)) {
     list(, $prefix, $time, $salt, $hmac) = $matches;
     $check_string =  $prefix . ';' . $time . ';' . $salt;
-    // We use the salt from settings.php to make the HMAC key, since
-    // the database is not yet initialized and we can't access any Drupal variables.
+    // Read the hash salt prepared by drupal_generate_test_ua().
+    // This function is called before settings.php is read and Drupal's error
+    // handlers are set up. While Drupal's error handling may be properly
+    // configured on production sites, the server's PHP error_reporting may not.
+    // Ensure that no information leaks on production sites.
+    $key_file = DRUPAL_ROOT . '/sites/simpletest/' . substr($prefix, 10) . '/key.php';
+    if (!is_readable($key_file)) {
+      header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
+      exit;
+    }
+    require $key_file;
     // The file properties add more entropy not easily accessible to others.
-    $key = drupal_get_hash_salt() . filectime(__FILE__) . fileinode(__FILE__);
+    $key = $private_key . filectime(__FILE__) . fileinode(__FILE__);
     $time_diff = REQUEST_TIME - $time;
-    // We can't use Crypt::hmacBase64() yet because this can be called in very
-    // early bootstrap when autoloader has not been initialized yet.
-    $test_hmac = base64_encode(hash_hmac('sha256', $check_string, $key, TRUE));
-    $test_hmac = strtr($test_hmac, array('+' => '-', '/' => '_', '=' => ''));
+    $test_hmac = Crypt::hmacBase64($check_string, $key);
     // Since we are making a local request a 5 second time window is allowed,
     // and the HMAC must match.
-    if ($time_diff >= 0 && $time_diff <= 5 && $hmac == $test_hmac) {
+    if ($time_diff >= 0 && $time_diff <= 5 && $hmac === $test_hmac) {
       $test_prefix = $prefix;
-      _drupal_load_test_overrides($test_prefix);
-      return $test_prefix;
     }
   }
-
-  $test_prefix = FALSE;
   return $test_prefix;
 }
 
 /**
- * Overrides low-level and environment-specific configuration.
- *
- * Very strictly for internal use only.
- *
- * Loads settings.php from the simpletest public files directory. These files
- * can change the global $conf, the global $config_directories, the return
- * value of conf_path(), and settings().
- *
- * @param string $test_prefix
- *   The simpletest prefix.
- */
-function _drupal_load_test_overrides($test_prefix) {
-  global $conf, $config_directories;
-
-  // Do not use the parent site's config directories. Use only the child site's.
-  // @see \Drupal\simpletest\TestBase::prepareConfigDirectories()
-  $path_prefix = 'simpletest/' . substr($test_prefix, 10);
-  $config_directories = array();
-  foreach (array(CONFIG_ACTIVE_DIRECTORY, CONFIG_STAGING_DIRECTORY) as $type) {
-    $config_directories[$type] = conf_path() . '/files/' . $path_prefix . '/config_' . $type;
-  }
-
-  // Check for and load a settings.php file in the simpletest files directory.
-  $filename = conf_path() . '/files/' . $path_prefix . '/settings.php';
-  if (file_exists($filename)) {
-    $settings = settings()->getAll();
-    $conf_path = &drupal_static('conf_path');
-    // This can override $conf, $conf_path, $settings, and $config_directories.
-    include $filename;
-    // Keep the overriden $conf_path alive across drupal_static_reset() calls.
-    // @see conf_path()
-    $settings['simpletest_conf_path'] = $conf_path;
-    new Settings($settings);
-  }
-}
-
-/**
  * Generates a user agent string with a HMAC and timestamp for simpletest.
  */
 function drupal_generate_test_ua($prefix) {
-  static $key;
-
-  if (!isset($key)) {
-    // We use the salt from settings.php to make the HMAC key, since
-    // the database is not yet initialized and we can't access any Drupal variables.
+  static $key, $last_prefix;
+
+  if (!isset($key) || $last_prefix != $prefix) {
+    $last_prefix = $prefix;
+    // When issuing an outbound HTTP client request from within an inbound test
+    // request, then the outbound request has to use the same User-Agent header
+    // as the inbound request. A newly generated private key for the same test
+    // prefix would invalidate all subsequent inbound requests.
+    // @see \Drupal\Core\Http\Plugin\SimpletestHttpRequestSubscriber
+    if (($parent_prefix = drupal_valid_test_ua()) && defined('DRUPAL_TEST_IN_CHILD_SITE')) {
+      if ($parent_prefix != $prefix) {
+        throw new \RuntimeException("Malformed forwarded User-Agent: Expected '$parent_prefix' but got '$prefix'.");
+      }
+      require DRUPAL_ROOT . '/sites/simpletest/' . substr($prefix, 10) . '/key.php';
+    }
+    else {
+      // Generate and save a new hash salt for a test run.
+      // Consumed by drupal_valid_test_ua() before settings.php is loaded.
+      $private_key = Crypt::randomStringHashed(55);
+      $key_file = "<?php\n\$private_key = '$private_key';\n";
+      file_put_contents(DRUPAL_ROOT . '/sites/simpletest/' . substr($prefix, 10) . '/key.php', $key_file);
+    }
     // The file properties add more entropy not easily accessible to others.
-    $key = drupal_get_hash_salt() . filectime(__FILE__) . fileinode(__FILE__);
+    $key = $private_key . filectime(__FILE__) . fileinode(__FILE__);
   }
   // Generate a moderately secure HMAC based on the database credentials.
   $salt = uniqid('', TRUE);
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 0099008..cb4e161 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -3060,14 +3060,6 @@ function _drupal_bootstrap_code() {
   // Make sure all stream wrappers are registered.
   file_get_stream_wrappers();
 
-  // Now that stream wrappers are registered, log fatal errors from a simpletest
-  // child site to a test specific file directory.
-  $test_info = &$GLOBALS['drupal_test_info'];
-  if (!empty($test_info['in_child_site'])) {
-    ini_set('log_errors', 1);
-    ini_set('error_log', 'public://error.log');
-  }
-
   // Set the allowed protocols once we have the config available.
   $allowed_protocols = \Drupal::config('system.filter')->get('protocols');
   if (!isset($allowed_protocols)) {
diff --git a/core/includes/errors.inc b/core/includes/errors.inc
index 4f5c923..84f5a1f 100644
--- a/core/includes/errors.inc
+++ b/core/includes/errors.inc
@@ -132,8 +132,7 @@ function _drupal_log_error($error, $fatal = FALSE) {
 
   // When running inside the testing framework, we relay the errors
   // to the tested site by the way of HTTP headers.
-  $test_info = &$GLOBALS['drupal_test_info'];
-  if (!empty($test_info['in_child_site']) && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
+  if (defined('DRUPAL_TEST_IN_CHILD_SITE') && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
     // $number does not use drupal_static as it should not be reset
     // as it uniquely identifies each PHP error.
     static $number = 0;
diff --git a/core/includes/file.inc b/core/includes/file.inc
index 545fca3..622a6ca 100644
--- a/core/includes/file.inc
+++ b/core/includes/file.inc
@@ -575,12 +575,12 @@ function file_ensure_htaccess() {
  */
 function file_save_htaccess($directory, $private = TRUE) {
   if (file_uri_scheme($directory)) {
-    $directory = file_stream_wrapper_uri_normalize($directory);
+    $htaccess_path = file_stream_wrapper_uri_normalize($directory . '/.htaccess');
   }
   else {
     $directory = rtrim($directory, '/\\');
+    $htaccess_path = $directory . '/.htaccess';
   }
-  $htaccess_path =  $directory . '/.htaccess';
 
   if (file_exists($htaccess_path)) {
     // Short circuit if the .htaccess file already exists.
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 6dfc270..4897ef2 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -287,19 +287,18 @@ function install_begin_request(&$install_state) {
   // which will be used for installing Drupal.
   conf_path(FALSE);
 
-  drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
-
   // If the hash salt leaks, it becomes possible to forge a valid testing user
-  // agent, install a new copy of Drupal, and take over the original site. To
-  // avoid this yet allow for automated testing of the installer, make sure
-  // there is also a special test-specific settings.php overriding conf_path().
-  // _drupal_load_test_overrides() sets the simpletest_conf_path in-memory
-  // setting in this case.
-  if ($install_state['interactive'] && drupal_valid_test_ua() && !settings()->get('simpletest_conf_path')) {
+  // agent, install a new copy of Drupal, and take over the original site.
+  // 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 ($install_state['interactive'] && strpos($request->server->get('HTTP_USER_AGENT'), 'simpletest') !== FALSE && !drupal_valid_test_ua()) {
     header($request->server->get('SERVER_PROTOCOL') . ' 403 Forbidden');
     exit;
   }
 
+  drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
+
   // If we have a language selected and it is not yet saved in the system
   // (eg. pre-database data screens we are unable to persistently store
   // the default language), we should set language_default so the proper
@@ -1067,7 +1066,6 @@ function install_verify_database_settings() {
   global $databases;
   if (!empty($databases)) {
     $database = $databases['default']['default'];
-    drupal_static_reset('conf_path');
     $settings_file = './' . conf_path(FALSE) . '/settings.php';
     $errors = install_database_errors($database, $settings_file);
     if (empty($errors)) {
@@ -1090,7 +1088,6 @@ function install_verify_database_settings() {
 function install_settings_form($form, &$form_state, &$install_state) {
   global $databases;
 
-  drupal_static_reset('conf_path');
   $conf_path = './' . conf_path(FALSE);
   $settings_file = $conf_path . '/settings.php';
   $database = isset($databases['default']['default']) ? $databases['default']['default'] : array();
@@ -1151,13 +1148,13 @@ function install_settings_form($form, &$form_state, &$install_state) {
  */
 function install_settings_form_validate($form, &$form_state) {
   $driver = $form_state['values']['driver'];
-  $database = $form_state['values'][$driver];
-  // When testing the interactive installer, copy the database password and
-  // the test prefix.
-  if ($test_prefix = drupal_valid_test_ua()) {
-    $database['prefix'] = $test_prefix;
-    $database['password'] = $GLOBALS['databases']['default']['default']['password'];
+
+  // @todo Remove when PIFR submits 'prefix' instead of 'db_prefix'.
+  if (!empty($form_state['input'][$driver]['db_prefix'])) {
+    $form_state['values'][$driver]['prefix'] = $form_state['input'][$driver]['db_prefix'];
   }
+  $database = $form_state['values'][$driver];
+
   $drivers = drupal_get_database_types();
   $reflection = new \ReflectionClass($drivers[$driver]);
   $install_namespace = $reflection->getNamespaceName();
@@ -1165,13 +1162,6 @@ function install_settings_form_validate($form, &$form_state) {
   $database['namespace'] = substr($install_namespace, 0, strrpos($install_namespace, '\\'));
   $database['driver'] = $driver;
 
-  // @todo PIFR uses 'db_prefix' instead of 'prefix'. Remove this when it gets
-  //   fixed.
-  if (!$test_prefix) {
-    $database['prefix'] = $database['db_prefix'];
-  }
-  unset($database['db_prefix']);
-
   $form_state['storage']['database'] = $database;
   $errors = install_database_errors($database, $form_state['values']['settings_file']);
   foreach ($errors as $name => $message) {
@@ -1229,35 +1219,15 @@ function install_settings_form_submit($form, &$form_state) {
   global $install_state, $conf;
 
   // Update global settings array and save.
-  $settings = array();
   $database = $form_state['storage']['database'];
-  // Ideally, there is no difference between the code executed by the
-  // automated test browser and an ordinary browser. However, the database
-  // settings need a different format and also need to skip the password
-  // when testing. The hash salt also needs to be skipped because the original
-  // salt is used to verify the validity of the automated test browser.
-  // Because of these, there's a little difference in the code following but
-  // it is small and self-contained.
-  if ($test_prefix = drupal_valid_test_ua()) {
-    foreach ($form_state['storage']['database'] as $k => $v) {
-      if ($k != 'password') {
-        $settings['databases']['default']['default'][$k] = (object) array(
-          'value'    => $v,
-          'required' => TRUE,
-        );
-      }
-    }
-  }
-  else {
-    $settings['databases']['default']['default'] = (object) array(
-      'value'    => $database,
-      'required' => TRUE,
-    );
-    $settings['drupal_hash_salt'] = (object) array(
-      'value'    => Crypt::randomStringHashed(55),
-      'required' => TRUE,
-    );
-  }
+  $settings['databases']['default']['default'] = (object) array(
+    'value'    => $database,
+    'required' => TRUE,
+  );
+  $settings['drupal_hash_salt'] = (object) array(
+    'value'    => Crypt::randomStringHashed(55),
+    'required' => TRUE,
+  );
 
   // Remember the profile which was used.
   $settings['settings'] = array(
@@ -2380,7 +2350,7 @@ function install_check_requirements($install_state) {
   if (!$install_state['settings_verified']) {
     $readable = FALSE;
     $writable = FALSE;
-    $conf_path = './' . conf_path(FALSE, TRUE);
+    $conf_path = './' . conf_path(FALSE);
     $settings_file = $conf_path . '/settings.php';
     $default_settings_file = './sites/default/default.settings.php';
     $file = $conf_path;
diff --git a/core/lib/Drupal/Core/Controller/ExceptionController.php b/core/lib/Drupal/Core/Controller/ExceptionController.php
index 06a89a8..0b10516 100644
--- a/core/lib/Drupal/Core/Controller/ExceptionController.php
+++ b/core/lib/Drupal/Core/Controller/ExceptionController.php
@@ -270,8 +270,7 @@ public function on500Html(FlattenException $exception, Request $request) {
 
     // When running inside the testing framework, we relay the errors
     // to the tested site by the way of HTTP headers.
-    $test_info = &$GLOBALS['drupal_test_info'];
-    if (!empty($test_info['in_child_site']) && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
+    if (defined('DRUPAL_TEST_IN_CHILD_SITE') && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
       // $number does not use drupal_static as it should not be reset
       // as it uniquely identifies each PHP error.
       static $number = 0;
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Install/Tasks.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Install/Tasks.php
index 633edc6..97eab54 100644
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Install/Tasks.php
+++ b/core/lib/Drupal/Core/Database/Driver/sqlite/Install/Tasks.php
@@ -49,7 +49,7 @@ public function getFormOptions(array $database) {
     // Make the text more accurate for SQLite.
     $form['database']['#title'] = t('Database file');
     $form['database']['#description'] = t('The absolute path to the file where @drupal data will be stored. This must be writable by the web server and should exist outside of the web root.', array('@drupal' => drupal_install_profile_distribution_name()));
-    $default_database = conf_path(FALSE, TRUE) . '/files/.ht.sqlite';
+    $default_database = conf_path(FALSE) . '/files/.ht.sqlite';
     $form['database']['#default_value'] = empty($database['database']) ? $default_database : $database['database'];
     return $form;
   }
diff --git a/core/lib/Drupal/Core/Database/Install/Tasks.php b/core/lib/Drupal/Core/Database/Install/Tasks.php
index e80dd86..f950647 100644
--- a/core/lib/Drupal/Core/Database/Install/Tasks.php
+++ b/core/lib/Drupal/Core/Database/Install/Tasks.php
@@ -251,14 +251,14 @@ public function getFormOptions(array $database) {
       '#weight' => 10,
     );
 
-    $profile = drupal_get_profile();
-    $db_prefix = ($profile == 'standard') ? 'drupal_' : $profile . '_';
-    $form['advanced_options']['db_prefix'] = array(
+    $form['advanced_options']['prefix'] = array(
       '#type' => 'textfield',
       '#title' => t('Table name prefix'),
       '#default_value' => '',
       '#size' => 45,
-      '#description' => t('If more than one application will be sharing this database, a unique table name prefix–such as %prefix–will prevent collisions.', array('%prefix' => $db_prefix)),
+      '#description' => t('If more than one application will be sharing this database, a unique table name prefix–such as %prefix–will prevent collisions.', array(
+        '%prefix' => preg_replace('@[^a-z0-9]@', '_', strtolower(drupal_install_profile_distribution_name())) . '_',
+      )),
       '#weight' => 10,
     );
 
diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
index 0160119..e7a18b9 100644
--- a/core/lib/Drupal/Core/DrupalKernel.php
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -334,20 +334,13 @@ public function updateModules(array $module_list, array $module_filenames = arra
   }
 
   /**
-   * Returns the classname based on environment and testing prefix.
+   * Returns the classname based on environment.
    *
    * @return string
    *   The class name.
    */
   protected function getClassName() {
     $parts = array('service_container', $this->environment);
-    // Make sure to use a testing-specific container even in the parent site.
-    if (!empty($GLOBALS['drupal_test_info']['test_run_id'])) {
-      $parts[] = $GLOBALS['drupal_test_info']['test_run_id'];
-    }
-    elseif ($prefix = drupal_valid_test_ua()) {
-      $parts[] = $prefix;
-    }
     return implode('_', $parts);
   }
 
diff --git a/core/lib/Drupal/Core/Http/Plugin/SimpletestHttpRequestSubscriber.php b/core/lib/Drupal/Core/Http/Plugin/SimpletestHttpRequestSubscriber.php
index 6f5bfd1..a8de4a8 100644
--- a/core/lib/Drupal/Core/Http/Plugin/SimpletestHttpRequestSubscriber.php
+++ b/core/lib/Drupal/Core/Http/Plugin/SimpletestHttpRequestSubscriber.php
@@ -34,9 +34,8 @@ public function onBeforeSendRequest(Event $event) {
     // user-agent is used to ensure that multiple testing sessions running at the
     // same time won't interfere with each other as they would if the database
     // prefix were stored statically in a file or database variable.
-    $test_info = &$GLOBALS['drupal_test_info'];
-    if (!empty($test_info['test_run_id'])) {
-      $event['request']->setHeader('User-Agent', drupal_generate_test_ua($test_info['test_run_id']));
+    if ($test_prefix = drupal_valid_test_ua()) {
+      $event['request']->setHeader('User-Agent', drupal_generate_test_ua($test_prefix));
     }
   }
 }
diff --git a/core/lib/Drupal/Core/StreamWrapper/PublicStream.php b/core/lib/Drupal/Core/StreamWrapper/PublicStream.php
index 90f17b3..760a1f8 100644
--- a/core/lib/Drupal/Core/StreamWrapper/PublicStream.php
+++ b/core/lib/Drupal/Core/StreamWrapper/PublicStream.php
@@ -38,13 +38,6 @@ public function getExternalUrl() {
    */
   public static function basePath() {
     $base_path = settings()->get('file_public_path', conf_path() . '/files');
-    if ($test_prefix = drupal_valid_test_ua()) {
-      // Append the testing suffix unless already given.
-      // @see \Drupal\simpletest\WebTestBase::setUp()
-      if (strpos($base_path, '/simpletest/' . substr($test_prefix, 10)) === FALSE) {
-        return $base_path . '/simpletest/' . substr($test_prefix, 10);
-      }
-    }
     return $base_path;
   }
 
diff --git a/core/lib/Drupal/Core/SystemListingInfo.php b/core/lib/Drupal/Core/SystemListingInfo.php
index d302699..3575adc 100644
--- a/core/lib/Drupal/Core/SystemListingInfo.php
+++ b/core/lib/Drupal/Core/SystemListingInfo.php
@@ -28,6 +28,7 @@ protected function profiles($directory) {
     // For SimpleTest to be able to test modules packaged together with a
     // distribution we need to include the profile of the parent site (in
     // which test runs are triggered).
+    // @todo !drupal_installation_attempted() defeats the whole purpose?
     if (drupal_valid_test_ua() && !drupal_installation_attempted()) {
       $testing_profile = \Drupal::config('simpletest.settings')->get('parent_profile');
       if ($testing_profile && $testing_profile != $profile) {
diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldImportCreateTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldImportCreateTest.php
index ff1c584..2461165 100644
--- a/core/modules/field/lib/Drupal/field/Tests/FieldImportCreateTest.php
+++ b/core/modules/field/lib/Drupal/field/Tests/FieldImportCreateTest.php
@@ -95,11 +95,12 @@ function testImportCreate() {
 
     // Add the new files to the staging directory.
     $src_dir = drupal_get_path('module', 'field_test_config') . '/staging';
-    $this->assertTrue(file_unmanaged_copy("$src_dir/$field_config_name.yml", "public://config_staging/$field_config_name.yml"));
-    $this->assertTrue(file_unmanaged_copy("$src_dir/$instance_config_name.yml", "public://config_staging/$instance_config_name.yml"));
-    $this->assertTrue(file_unmanaged_copy("$src_dir/$field_config_name_2.yml", "public://config_staging/$field_config_name_2.yml"));
-    $this->assertTrue(file_unmanaged_copy("$src_dir/$instance_config_name_2a.yml", "public://config_staging/$instance_config_name_2a.yml"));
-    $this->assertTrue(file_unmanaged_copy("$src_dir/$instance_config_name_2b.yml", "public://config_staging/$instance_config_name_2b.yml"));
+    $target_dir = $this->configDirectories[CONFIG_STAGING_DIRECTORY];
+    $this->assertTrue(file_unmanaged_copy("$src_dir/$field_config_name.yml", "$target_dir/$field_config_name.yml"));
+    $this->assertTrue(file_unmanaged_copy("$src_dir/$instance_config_name.yml", "$target_dir/$instance_config_name.yml"));
+    $this->assertTrue(file_unmanaged_copy("$src_dir/$field_config_name_2.yml", "$target_dir/$field_config_name_2.yml"));
+    $this->assertTrue(file_unmanaged_copy("$src_dir/$instance_config_name_2a.yml", "$target_dir/$instance_config_name_2a.yml"));
+    $this->assertTrue(file_unmanaged_copy("$src_dir/$instance_config_name_2b.yml", "$target_dir/$instance_config_name_2b.yml"));
 
     // Import the content of the staging directory.
     $this->configImporter()->import();
diff --git a/core/modules/file/tests/file_test/lib/Drupal/file_test/DummyReadOnlyStreamWrapper.php b/core/modules/file/tests/file_test/lib/Drupal/file_test/DummyReadOnlyStreamWrapper.php
index b427d3f..d230bc5 100644
--- a/core/modules/file/tests/file_test/lib/Drupal/file_test/DummyReadOnlyStreamWrapper.php
+++ b/core/modules/file/tests/file_test/lib/Drupal/file_test/DummyReadOnlyStreamWrapper.php
@@ -16,7 +16,7 @@
  */
 class DummyReadOnlyStreamWrapper extends LocalReadOnlyStream {
   function getDirectoryPath() {
-    return 'sites/default/files';
+    return conf_path() . '/files';
   }
 
   /**
diff --git a/core/modules/file/tests/file_test/lib/Drupal/file_test/DummyStreamWrapper.php b/core/modules/file/tests/file_test/lib/Drupal/file_test/DummyStreamWrapper.php
index 4836f09..cbea40f 100644
--- a/core/modules/file/tests/file_test/lib/Drupal/file_test/DummyStreamWrapper.php
+++ b/core/modules/file/tests/file_test/lib/Drupal/file_test/DummyStreamWrapper.php
@@ -16,7 +16,7 @@
  */
 class DummyStreamWrapper extends LocalStream {
   function getDirectoryPath() {
-    return 'sites/default/files';
+    return conf_path() . '/files';
   }
 
   /**
diff --git a/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportCreateTest.php b/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportCreateTest.php
index c6d4b7a..1d59ca1 100644
--- a/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportCreateTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportCreateTest.php
@@ -71,7 +71,8 @@ public function testImportCreate() {
     $this->copyConfig($active, $staging);
     // Manually add new node type.
     $src_dir = drupal_get_path('module', 'node_test_config') . '/staging';
-    $this->assertTrue(file_unmanaged_copy("$src_dir/$node_type_config_name.yml", "public://config_staging/$node_type_config_name.yml"));
+    $target_dir = $this->configDirectories[CONFIG_STAGING_DIRECTORY];
+    $this->assertTrue(file_unmanaged_copy("$src_dir/$node_type_config_name.yml", "$target_dir/$node_type_config_name.yml"));
 
     // Import the content of the staging directory.
     $this->configImporter()->import();
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
index 560afb1..9654182 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
@@ -36,6 +36,13 @@
   protected $testId;
 
   /**
+   * The site directory of this test run.
+   *
+   * @var string
+   */
+  protected $siteDirectory = NULL;
+
+  /**
    * The database prefix of this test run.
    *
    * @var string
@@ -217,11 +224,6 @@ public static function getInfo() {
   }
 
   /**
-   * Performs setup tasks before each individual test method is run.
-   */
-  abstract protected function setUp();
-
-  /**
    * Checks the matching requirements for Test.
    *
    * @return
@@ -837,7 +839,9 @@ public function run(array $methods = array()) {
    * @see WebTestBase::setUp()
    */
   protected function prepareDatabasePrefix() {
-    $this->databasePrefix = 'simpletest' . mt_rand(1000, 1000000);
+    $suffix = mt_rand(1000, 1000000);
+    $this->siteDirectory = 'sites/simpletest/' . $suffix;
+    $this->databasePrefix = 'simpletest' . $suffix;
 
     // As soon as the database prefix is set, the test might start to execute.
     // All assertions as well as the SimpleTest batch operations are associated
@@ -868,20 +872,10 @@ protected function changeDatabasePrefix() {
     $connection_info = Database::getConnectionInfo('default');
     Database::renameConnection('default', 'simpletest_original_default');
     foreach ($connection_info as $target => $value) {
-      $connection_info[$target]['prefix'] = array(
-        'default' => $value['prefix']['default'] . $this->databasePrefix,
-      );
+      $connection_info[$target]['prefix'] = $value['prefix']['default'] . $this->databasePrefix;
     }
     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;
   }
@@ -946,22 +940,31 @@ protected function prepareEnvironment() {
 
     // Create test directory ahead of installation so fatal errors and debug
     // information can be logged during installation process.
-    // Use temporary files directory with the same prefix as the database.
-    $this->public_files_directory = $this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10);
-    $this->private_files_directory = $this->public_files_directory . '/private';
-    $this->temp_files_directory = $this->private_files_directory . '/temp';
-    $this->translation_files_directory = $this->public_files_directory . '/translations';
-
-    // Create the directories
-    file_prepare_directory($this->public_files_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
-    file_prepare_directory($this->private_files_directory, FILE_CREATE_DIRECTORY);
-    file_prepare_directory($this->temp_files_directory, FILE_CREATE_DIRECTORY);
-    file_prepare_directory($this->translation_files_directory, FILE_CREATE_DIRECTORY);
+    file_prepare_directory($this->siteDirectory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+
+    // Prepare filesystem directory paths.
+    $this->public_files_directory = $this->siteDirectory . '/files';
+    $this->private_files_directory = $this->siteDirectory . '/private';
+    $this->temp_files_directory = $this->siteDirectory . '/temp';
+    $this->translation_files_directory = $this->siteDirectory . '/translations';
+
+    // Create filesystem directories, unless the installer will be executed.
+    // @todo Move into DrupalUnitTestBase::setUp().
+    if (!$this instanceof WebTestBase) {
+      file_prepare_directory($this->public_files_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+      file_prepare_directory($this->private_files_directory, FILE_CREATE_DIRECTORY);
+      file_prepare_directory($this->temp_files_directory, FILE_CREATE_DIRECTORY);
+      file_prepare_directory($this->translation_files_directory, FILE_CREATE_DIRECTORY);
+
+      // Create and set new configuration directories.
+      $this->prepareConfigDirectories();
+    }
+    else {
+      $GLOBALS['config_directories'] = array();
+      $this->configDirectories = array();
+    }
     $this->generatedTestFiles = FALSE;
 
-    // Create and set new configuration directories.
-    $this->prepareConfigDirectories();
-
     // Reset statics before the old container is replaced so that objects with a
     // __destruct() method still have access to it.
     // @todo: Remove once they have been converted to services.
@@ -987,12 +990,7 @@ protected function prepareEnvironment() {
 
     // Log fatal errors.
     ini_set('log_errors', 1);
-    ini_set('error_log', $this->public_files_directory . '/error.log');
-
-    // Set the test information for use in other parts of Drupal.
-    $test_info = &$GLOBALS['drupal_test_info'];
-    $test_info['test_run_id'] = $this->databasePrefix;
-    $test_info['in_child_site'] = FALSE;
+    ini_set('error_log', $this->siteDirectory . '/error.log');
 
     // Indicate the environment was set up correctly.
     $this->setupEnvironment = TRUE;
@@ -1013,7 +1011,7 @@ protected function prepareConfigDirectories() {
     include_once DRUPAL_ROOT . '/core/includes/install.inc';
     foreach (array(CONFIG_ACTIVE_DIRECTORY, CONFIG_STAGING_DIRECTORY) as $type) {
       // Assign the relative path to the global variable.
-      $path = conf_path() . '/files/simpletest/' . substr($this->databasePrefix, 10) . '/config_' . $type;
+      $path = $this->siteDirectory . '/config_' . $type;
       $GLOBALS['config_directories'][$type] = $path;
       // Ensure the directory can be created and is writeable.
       if (!install_ensure_config_directory($type)) {
@@ -1040,11 +1038,11 @@ protected function prepareConfigDirectories() {
    *   tests can invoke this workaround when requiring services from newly
    *   enabled modules to be immediately available in the same request.
    */
-  protected function rebuildContainer() {
+  protected function rebuildContainer($environment = 'testing') {
     // Preserve the request object after the container rebuild.
     $request = \Drupal::request();
 
-    $this->kernel = new DrupalKernel('testing', drupal_classloader(), FALSE);
+    $this->kernel = new DrupalKernel($environment, drupal_classloader(), FALSE);
     $this->kernel->boot();
     // DrupalKernel replaces the container in \Drupal::getContainer() with a
     // different object, so we need to replace the instance on this test class.
@@ -1055,6 +1053,36 @@ protected function rebuildContainer() {
   }
 
   /**
+   * Performs setup tasks before each individual test method is run.
+   */
+  protected function setUp() {
+    // Create the database prefix for this test.
+    $this->prepareDatabasePrefix();
+
+    // Prepare the environment for running tests.
+    $this->prepareEnvironment();
+    if (!$this->setupEnvironment) {
+      throw new \RuntimeException('Failed to set up test environment.');
+    }
+
+    // Change the database prefix.
+    // All static variables need to be reset before the database prefix is
+    // changed, since \Drupal\Core\Utility\CacheArray implementations attempt to
+    // write back to persistent caches when they are destructed.
+    $this->changeDatabasePrefix();
+    if (!$this->setupDatabasePrefix) {
+      throw new \RuntimeException('Failed to set up database prefix.');
+    }
+
+    // After preparing the environment and changing the database prefix, we are
+    // in a valid test environment.
+    drupal_valid_test_ua($this->databasePrefix);
+    conf_path(FALSE, TRUE);
+
+    $this->setup = TRUE;
+  }
+
+  /**
    * Performs cleanup tasks after each individual test method has been run.
    *
    * Deletes created files, database tables, and reverts environment changes.
@@ -1084,30 +1112,28 @@ protected function tearDown() {
       }
     }
 
-    // Ensure that TestBase::changeDatabasePrefix() has run and TestBase::$setup
-    // was not tricked into TRUE, since the following code would delete the
-    // entire parent site otherwise.
-    if ($this->setupDatabasePrefix) {
-      // Remove all prefixed tables.
-      $connection_info = Database::getConnectionInfo('default');
-      $tables = db_find_tables($connection_info['default']['prefix']['default'] . '%');
-      $prefix_length = strlen($connection_info['default']['prefix']['default']);
+    // Remove all prefixed tables.
+    // @todo Connection prefix info is not normalized into an array.
+    $original_connection_info = Database::getConnectionInfo('simpletest_original_default');
+    $original_prefix = is_array($original_connection_info['default']['prefix']) ? $original_connection_info['default']['prefix']['default'] : $original_connection_info['default']['prefix'];
+    $test_connection_info = Database::getConnectionInfo('default');
+    $test_prefix = is_array($test_connection_info['default']['prefix']) ? $test_connection_info['default']['prefix']['default'] : $test_connection_info['default']['prefix'];
+    if ($original_prefix != $test_prefix) {
+      $tables = Database::getConnection()->schema()->findTables($test_prefix . '%');
+      $prefix_length = strlen($test_prefix);
       foreach ($tables as $table) {
-        if (db_drop_table(substr($table, $prefix_length))) {
+        if (Database::getConnection()->schema()->dropTable(substr($table, $prefix_length))) {
           unset($tables[$table]);
         }
       }
-      if (!empty($tables)) {
-        $this->fail('Failed to drop all prefixed tables.');
-      }
     }
 
     // In case a fatal error occurred that was not in the test process read the
     // log to pick up any fatal errors.
     simpletest_log_read($this->testId, $this->databasePrefix, get_class($this), TRUE);
 
-    // Delete temporary files directory.
-    file_unmanaged_delete_recursive($this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10), array($this, 'filePreDeleteCallback'));
+    // Delete test site directory.
+    file_unmanaged_delete_recursive($this->siteDirectory, array($this, 'filePreDeleteCallback'));
 
     // Restore original database connection.
     Database::removeConnection('default');
@@ -1135,9 +1161,14 @@ protected function tearDown() {
     // Restore original statics and globals.
     \Drupal::setContainer($this->originalContainer);
     $GLOBALS['config_directories'] = $this->originalConfigDirectories;
+
     if (isset($this->originalPrefix)) {
       drupal_valid_test_ua($this->originalPrefix);
     }
+    else {
+      drupal_valid_test_ua(FALSE);
+    }
+    conf_path(TRUE, TRUE);
 
     // Restore original shutdown callbacks.
     $callbacks = &drupal_register_shutdown_function();
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/Tests/MissingCheckedRequirementsTest.php b/core/modules/simpletest/lib/Drupal/simpletest/Tests/MissingCheckedRequirementsTest.php
index 61d6401..24b4348 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/Tests/MissingCheckedRequirementsTest.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/Tests/MissingCheckedRequirementsTest.php
@@ -39,7 +39,7 @@ function setUp() {
    * Overrides checkRequirements().
    */
   protected function checkRequirements() {
-    if (drupal_valid_test_ua()) {
+    if ($this->isInChildSite()) {
       return array(
         'Test is not allowed to run.'
       );
@@ -53,7 +53,7 @@ protected function checkRequirements() {
   protected function testCheckRequirements() {
     // If this is the main request, run the web test script and then assert
     // that the child tests did not run.
-    if (!drupal_valid_test_ua()) {
+    if (!$this->isInChildSite()) {
       // Run this test from web interface.
       $edit['Drupal\simpletest\Tests\MissingCheckedRequirementsTest'] = TRUE;
       $this->drupalPostForm('admin/config/development/testing', $edit, t('Run tests'));
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/Tests/SimpleTestTest.php b/core/modules/simpletest/lib/Drupal/simpletest/Tests/SimpleTestTest.php
index 2a19a5c1..7fceebf 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/Tests/SimpleTestTest.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/Tests/SimpleTestTest.php
@@ -39,7 +39,7 @@ public static function getInfo() {
   }
 
   function setUp() {
-    if (!$this->inCURL()) {
+    if (!$this->isInChildSite()) {
       parent::setUp();
       // Create and log in an admin user.
       $this->drupalLogin($this->drupalCreateUser(array('administer unit tests')));
@@ -54,7 +54,7 @@ function setUp() {
    * Test the internal browsers functionality.
    */
   function testInternalBrowser() {
-    if (!$this->inCURL()) {
+    if (!$this->isInChildSite()) {
       // Retrieve the test page and check its title and headers.
       $this->drupalGet('test-page');
       $this->assertTrue($this->drupalGetHeader('Date'), 'An HTTP header was received.');
@@ -94,7 +94,7 @@ function testInternalBrowser() {
       // Remove the Simpletest settings.php so we can test the protection
       // against requests that forge a valid testing user agent to gain access
       // to the installer.
-      drupal_unlink($this->public_files_directory . '/settings.php');
+      drupal_unlink($this->siteDirectory . '/key.php');
       global $base_url;
       $this->drupalGet(url($base_url . '/core/install.php', array('external' => TRUE, 'absolute' => TRUE)));
       $this->assertResponse(403, 'Cannot access install.php.');
@@ -105,7 +105,7 @@ function testInternalBrowser() {
    * Test validation of the User-Agent header we use to perform test requests.
    */
   function testUserAgentValidation() {
-    if (!$this->inCURL()) {
+    if (!$this->isInChildSite()) {
       global $base_url;
       $system_path = $base_url . '/' . drupal_get_path('module', 'system');
       $HTTP_path = $system_path .'/tests/http.php?q=node';
@@ -147,7 +147,7 @@ function testWebTestRunner() {
     $this->valid_permission = 'access content';
     $this->invalid_permission = 'invalid permission';
 
-    if ($this->inCURL()) {
+    if ($this->isInChildSite()) {
       // Only run following code if this test is running itself through a CURL
       // request.
       $this->stubTest();
@@ -335,10 +335,4 @@ function asText(\SimpleXMLElement $element) {
     return trim(html_entity_decode(strip_tags($element->asXML())));
   }
 
-  /**
-   * Check if the test is being run from inside a CURL request.
-   */
-  function inCURL() {
-    return (bool) drupal_valid_test_ua();
-  }
 }
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php
index 5510aa4..733d51f 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php
@@ -43,30 +43,11 @@ function __construct($test_id = NULL) {
   protected function setUp() {
     global $conf;
 
-    // Create the database prefix for this test.
-    $this->prepareDatabasePrefix();
+    parent::setUp();
 
-    // Prepare the environment for running tests.
-    $this->prepareEnvironment();
-    if (!$this->setupEnvironment) {
-      return FALSE;
-    }
-
-    // Reset all statics and variables to perform tests in a clean environment.
+    // Reset all variables to perform tests in a clean environment.
     $conf = array();
-    drupal_static_reset();
 
     $this->settingsSet('file_public_path', $this->public_files_directory);
-
-    // Change the database prefix.
-    // All static variables need to be reset before the database prefix is
-    // changed, since \Drupal\Core\Utility\CacheArray implementations attempt to
-    // write back to persistent caches when they are destructed.
-    $this->changeDatabasePrefix();
-    if (!$this->setupDatabasePrefix) {
-      return FALSE;
-    }
-
-    $this->setup = TRUE;
   }
 }
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
index 265012e..93519a5 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Utility\Crypt;
 use Drupal\Component\Utility\NestedArray;
+use Drupal\Component\Utility\Settings;
 use Drupal\Component\Utility\String;
 use Drupal\Core\DrupalKernel;
 use Drupal\Core\Database\Database;
@@ -47,7 +48,21 @@
   protected $curlHandle;
 
   /**
-   * The headers of the page currently loaded in the internal browser.
+   * The request headers of all requests performed.
+   *
+   * @var array
+   */
+  protected $requestHeaders;
+
+  /**
+   * The response headers of all requests performed.
+   *
+   * @var array
+   */
+  protected $responseHeaders;
+
+  /**
+   * The response headers of the page currently loaded in the internal browser.
    *
    * @var Array
    */
@@ -726,32 +741,10 @@ protected function setUp() {
     // Backup the currently running Simpletest batch.
     $this->originalBatch = batch_get();
 
-    // Create the database prefix for this test.
-    $this->prepareDatabasePrefix();
+    parent::setUp();
 
-    // Prepare the environment for running tests.
-    $this->prepareEnvironment();
-    if (!$this->setupEnvironment) {
-      return FALSE;
-    }
-
-    // Reset all statics and variables to perform tests in a clean environment.
+    // Reset all variables to perform tests in a clean environment.
     $conf = array();
-    drupal_static_reset();
-
-    // Change the database prefix.
-    // All static variables need to be reset before the database prefix is
-    // changed, since \Drupal\Core\Utility\CacheArray implementations attempt to
-    // write back to persistent caches when they are destructed.
-    $this->changeDatabasePrefix();
-    if (!$this->setupDatabasePrefix) {
-      return FALSE;
-    }
-
-    // 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;
 
     // Define information about the user 1 account.
     $this->root_user = new UserSession(array(
@@ -764,56 +757,88 @@ protected function setUp() {
     // Reset the static batch to remove Simpletest's batch operations.
     $batch = &batch_get();
     $batch = array();
-    $variable_groups = array(
-      'system.file' => array(
-        'path.private' =>  $this->private_files_directory,
-        'path.temporary' =>  $this->temp_files_directory,
-      ),
-      'locale.settings' =>  array(
-        'translation.path' => $this->translation_files_directory,
-      ),
+
+    // Get parameters for install_drupal() before removing global variables.
+    $parameters = $this->installParameters();
+
+    // Prepare installer settings that are not install_drupal() parameters.
+    // Copy and prepare an actual settings.php, so as to resemble a regular
+    // installation.
+    copy(DRUPAL_ROOT . '/sites/default/default.settings.php', DRUPAL_ROOT . '/' . $this->siteDirectory . '/settings.php');
+
+    // All file system paths are set up by System module during installation.
+    // @see system_requirements()
+    // @see TestBase::prepareEnvironment()
+    $settings['settings']['file_public_path'] = (object) array(
+      'value' => $this->public_files_directory,
+      'required' => TRUE,
     );
-    foreach ($variable_groups as $config_base => $variables) {
-      foreach ($variables as $name => $value) {
-        NestedArray::setValue($GLOBALS['conf'], array_merge(array($config_base), explode('.', $name)), $value);
-      }
-    }
-    $this->settingsSet('file_public_path', $this->public_files_directory);
+    // @todo While private/temporary filesystem paths can be preset in the
+    //   installer via global configuration overrides, some tests expect the
+    //   values to be configurable through the UI. By setting them in
+    //   settings.php, they are no longer configurable... Why are these not
+    //   settings like the public filesystem path to begin with? (consistency)
+    /*
+    $settings['conf']['system.file']['path']['private'] = (object) array(
+      'value' => $this->private_files_directory,
+      'required' => TRUE,
+    );
+    $settings['conf']['system.file']['path']['temporary'] = (object) array(
+      'value' => $this->temp_files_directory,
+      'required' => TRUE,
+    );
+    */
+    // Use the test mail class instead of the default mail handler class.
+    $settings['conf']['system.mail']['interface']['default'] = (object) array(
+      'value' => 'Drupal\Core\Mail\TestMailCollector',
+      'required' => TRUE,
+    );
+    // Add the parent profile's search path to the child site's search paths.
+    // @see drupal_system_listing()
+    $settings['conf']['simpletest.settings']['parent_profile'] = (object) array(
+      'value' => $this->originalProfile,
+      'required' => TRUE,
+    );
+    $this->writeSettings($settings);
+
+    // Since Drupal is bootstrapped already, install_begin_request() will not
+    // bootstrap into DRUPAL_BOOTSTRAP_CONFIGURATION (again). Hence, we have to
+    // reload the newly written custom settings.php manually.
+    drupal_settings_initialize();
+
     // Execute the non-interactive installer.
     require_once DRUPAL_ROOT . '/core/includes/install.core.inc';
-    $this->settingsSet('cache', array('default' => 'cache.backend.memory'));
-    $parameters = $this->installParameters();
     install_drupal($parameters);
 
-    // Set the install_profile so that web requests to the requests to the child
-    // site have the correct profile.
-    $settings = array(
-      'settings' => array(
-        'install_profile' => (object) array(
-          'value' => $this->profile,
-          'required' => TRUE,
-        ),
-      ),
-    );
-    $this->writeSettings($settings);
-    // Override install profile in Settings to so the correct profile is used by
-    // tests.
-    $this->settingsSet('install_profile', $this->profile);
+    // Import new settings.php written by the installer.
+    drupal_settings_initialize();
+    foreach ($GLOBALS['config_directories'] as $type => $path) {
+      $this->configDirectories[$type] = $path;
+    }
+
+    // After writing settings.php, the installer removes write permissions
+    // from the site directory. To allow drupal_generate_test_ua() to write
+    // a key.php file containing the private key for drupal_valid_test_ua(),
+    // the site directory has to be writable.
+    // Use chmod() without a Drupal wrapper, so potential errors are visible.
+    // WebTestBase::tearDown() will delete the entire test site directory.
+    chmod(DRUPAL_ROOT . '/' . $this->siteDirectory, 0777);
+
+    // Manually provide private and temporary files directories.
+    // (see @todo above)
+    file_prepare_directory($this->private_files_directory, FILE_CREATE_DIRECTORY);
+    file_prepare_directory($this->temp_files_directory, FILE_CREATE_DIRECTORY);
+    \Drupal::config('system.file')
+      ->set('path.private', $this->private_files_directory)
+      ->set('path.temporary', $this->temp_files_directory)
+      ->save();
 
-    $this->settingsSet('cache', array());
     $this->rebuildContainer();
 
     // Restore the original Simpletest batch.
     $batch = &batch_get();
     $batch = $this->originalBatch;
 
-    // Set path variables.
-
-    // Set 'parent_profile' of simpletest to add the parent profile's
-    // search path to the child site's search paths.
-    // @see drupal_system_listing()
-    \Drupal::config('simpletest.settings')->set('parent_profile', $this->originalProfile)->save();
-
     // Collect modules to install.
     $class = get_class($this);
     $modules = array();
@@ -830,22 +855,18 @@ protected function setUp() {
       $this->rebuildContainer();
     }
 
-    // Reset/rebuild all data structures after enabling the modules.
+    // Like DRUPAL_BOOTSTRAP_CONFIGURATION above, any further bootstrap phases
+    // are not re-executed by the installer, as Drupal is bootstrapped already.
+    // Reset/rebuild all data structures after enabling the modules, primarily
+    // to synchronize all data structures and caches between the test runner and
+    // the child site.
+    // Affects e.g. file_get_stream_wrappers().
+    // @see _drupal_bootstrap_code()
+    // @see _drupal_bootstrap_full()
+    // @todo Test-specific setUp() methods may set up further fixtures; find a
+    //   way to execute this after setUp() is done, or to eliminate it entirely.
     $this->resetAll();
 
-    // Now make sure that the file path configurations are saved. This is done
-    // after we install the modules to override default values.
-    foreach ($variable_groups as $config_base => $variables) {
-      $config = \Drupal::config($config_base);
-      foreach ($variables as $name => $value) {
-        $config->set($name, $value);
-      }
-      $config->save();
-    }
-
-    // Use the test mail class instead of the default mail handler class.
-    \Drupal::config('system.mail')->set('interface.default', 'Drupal\Core\Mail\TestMailCollector')->save();
-
     drupal_set_time_limit($this->timeLimit);
     // Temporary fix so that when running from run-tests.sh we don't get an
     // empty current path which would indicate we're on the home page.
@@ -864,6 +885,10 @@ protected function setUp() {
    */
   protected function installParameters() {
     $connection_info = Database::getConnectionInfo();
+    $driver = $connection_info['default']['driver'];
+    unset($connection_info['default']['driver']);
+    unset($connection_info['default']['pdo']);
+    unset($connection_info['default']['init_commands']);
     $parameters = array(
       'interactive' => FALSE,
       'parameters' => array(
@@ -871,7 +896,10 @@ protected function installParameters() {
         'langcode' => 'en',
       ),
       'forms' => array(
-        'install_settings_form' => $connection_info['default'],
+        'install_settings_form' => array(
+          'driver' => $driver,
+          $driver => $connection_info['default'],
+        ),
         'install_configure_form' => array(
           'site_name' => 'Drupal',
           'site_mail' => 'simpletest@example.com',
@@ -896,33 +924,18 @@ protected function installParameters() {
   }
 
   /**
-   * Writes a test-specific settings.php file for the child site.
+   * Rewrites the settings.php file of the test site.
    *
-   * The child site loads this after the parent site's settings.php, so settings
-   * here override those.
-   *
-   * @param $settings An array of settings to write out, in the format expected
-   *   by drupal_rewrite_settings().
+   * @param array $settings
+   *   An array of settings to write out, in the format expected by
+   *   drupal_rewrite_settings().
    *
-   * @see _drupal_load_test_overrides()
    * @see drupal_rewrite_settings()
    */
-  protected function writeSettings($settings) {
-    // drupal_rewrite_settings() sets the in-memory global variables in addition
-    // to writing the file. We'll want to restore the original globals.
-    foreach (array_keys($settings) as $variable_name) {
-      $original_globals[$variable_name] = isset($GLOBALS[$variable_name]) ? $GLOBALS[$variable_name] : NULL;
-    }
-
+  protected function writeSettings(array $settings) {
     include_once DRUPAL_ROOT . '/core/includes/install.inc';
-    $filename = $this->public_files_directory . '/settings.php';
-    file_put_contents($filename, "<?php\n");
+    $filename = $this->siteDirectory . '/settings.php';
     drupal_rewrite_settings($settings, $filename);
-
-    // Restore the original globals.
-    foreach ($original_globals as $variable_name => $value) {
-      $GLOBALS[$variable_name] = $value;
-    }
   }
 
   /**
@@ -966,8 +979,8 @@ protected function writeCustomTranslations() {
   /**
    * Overrides \Drupal\simpletest\TestBase::rebuildContainer().
    */
-  protected function rebuildContainer() {
-    parent::rebuildContainer();
+  protected function rebuildContainer($environment = 'prod') {
+    parent::rebuildContainer($environment);
     // Make sure the url generator has a request object, otherwise calls to
     // $this->drupalGet() will fail.
     $this->prepareRequestForGenerator();
@@ -1005,9 +1018,13 @@ protected function resetAll() {
    * database to ensure that the most up-to-date set of variables is loaded.
    */
   protected function refreshVariables() {
-    global $conf;
     cache('bootstrap')->delete('variables');
-    $conf = variable_initialize();
+    // To retain in-memory global $conf from settings.php, we simply re-invoke
+    // DRUPAL_BOOTSTRAP_VARIABLES.
+    // @todo Should this reload $conf overrides from settings.php, so as to
+    //   eliminate any additional global $conf vars that may have been set?
+    _drupal_bootstrap_variables();
+
     // Clear the tag cache.
     drupal_static_reset('Drupal\Core\Cache\CacheBackendInterface::tagCache');
     \Drupal::service('config.factory')->reset();
@@ -1068,6 +1085,7 @@ protected function curlInitialize() {
         CURLOPT_SSL_VERIFYHOST => FALSE,
         CURLOPT_HEADERFUNCTION => array(&$this, 'curlHeaderCallback'),
         CURLOPT_USERAGENT => $this->databasePrefix,
+        CURLINFO_HEADER_OUT => TRUE,
       );
       if (isset($this->httpauth_credentials)) {
         $curl_options[CURLOPT_HTTPAUTH] = $this->httpauth_method;
@@ -1180,12 +1198,24 @@ protected function curlExec($curl_options, $redirect = FALSE) {
       // Reset headers, the session ID and the redirect counter.
       $this->session_id = NULL;
       $this->headers = array();
+      $this->requestId = 0;
+      $this->requestHeaders = array();
+      $this->responseHeaders = array();
       $this->redirect_count = 0;
     }
 
     $content = curl_exec($this->curlHandle);
     $status = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE);
 
+    $this->requestHeaders[$this->requestId] = curl_getinfo($this->curlHandle, CURLINFO_HEADER_OUT);
+
+    // Sort response headers alphabetically, but keep the HTTP status first.
+    $response_headers = array_shift($this->responseHeaders[$this->requestId]);
+    sort($this->responseHeaders[$this->requestId]);
+    $this->responseHeaders[$this->requestId] = $response_headers . implode('', $this->responseHeaders[$this->requestId]);
+
+    $this->requestId++;
+
     // cURL incorrectly handles URLs with fragments, so instead of
     // letting cURL handle redirects we take of them ourselves to
     // to prevent fragments being sent to the web server as part
@@ -1230,9 +1260,11 @@ protected function curlHeaderCallback($curlHandler, $header) {
     if ($header[0] == ' ' || $header[0] == "\t") {
       // Normalize whitespace between chucks.
       $this->headers[] = array_pop($this->headers) . ' ' . trim($header);
+      $this->responseHeaders[$this->requestId][] = array_pop($this->responseHeaders[$this->requestId]) . ' ' . trim($header);
     }
     else {
       $this->headers[] = $header;
+      $this->responseHeaders[$this->requestId][] = $header;
     }
 
     // Errors are being sent via X-Drupal-Assertion-* headers,
@@ -1275,6 +1307,21 @@ protected function curlClose() {
   }
 
   /**
+   * Returns whether the test is being executed from within a test site.
+   *
+   * Mainly used by recursive tests (i.e. to test the testing framework).
+   *
+   * @return bool
+   *   TRUE if this test was instantiated in a request within the test site,
+   *   FALSE otherwise.
+   *
+   * @see _drupal_bootstrap_configuration()
+   */
+  protected function isInChildSite() {
+    return defined('DRUPAL_TEST_IN_CHILD_SITE');
+  }
+
+  /**
    * Parse content returned from curlExec using DOM and SimpleXML.
    *
    * @return
@@ -1333,7 +1380,10 @@ protected function drupalGet($path, array $options = array(), array $headers = a
     $verbose = 'GET request to: ' . $path .
                '<hr />Ending URL: ' . $this->getUrl();
     if ($this->dumpHeaders) {
-      $verbose .= '<hr />Headers: <pre>' . check_plain(var_export(array_map('trim', $this->headers), TRUE)) . '</pre>';
+      for ($i = 0; $i < $this->requestId; $i++) {
+        $verbose .= '<hr />Request headers: <pre>' . check_plain($this->requestHeaders[$i]) . '</pre>';
+        $verbose .= '<hr />Response headers: <pre>' . check_plain($this->responseHeaders[$i]) . '</pre>';
+      }
     }
     $verbose .= '<hr />' . $out;
 
@@ -1523,7 +1573,10 @@ protected function drupalPostForm($path, $edit, $submit, array $options = array(
           $verbose = 'POST request to: ' . $path;
           $verbose .= '<hr />Ending URL: ' . $this->getUrl();
           if ($this->dumpHeaders) {
-            $verbose .= '<hr />Headers: <pre>' . check_plain(var_export(array_map('trim', $this->headers), TRUE)) . '</pre>';
+            for ($i = 0; $i < $this->requestId; $i++) {
+              $verbose .= '<hr />Request headers: <pre>' . check_plain($this->requestHeaders[$i]) . '</pre>';
+              $verbose .= '<hr />Response headers: <pre>' . check_plain($this->responseHeaders[$i]) . '</pre>';
+            }
           }
           $verbose .= '<hr />Fields: ' . highlight_string('<?php ' . var_export($post_array, TRUE), TRUE);
           $verbose .= '<hr />' . $out;
@@ -1887,14 +1940,16 @@ protected function drupalHead($path, array $options = array(), array $headers =
     $options['absolute'] = TRUE;
     $url = $this->container->get('url_generator')->generateFromPath($path, $options);
     $out = $this->curlExec(array(CURLOPT_NOBODY => TRUE, CURLOPT_URL => $url, CURLOPT_HTTPHEADER => $headers));
-    // Ensure that any changes to variables in the other thread are picked up.
-    $this->refreshVariables();
 
+    $verbose = 'HEAD request to: ' . $path;
+    $verbose .= '<hr />Ending URL: ' . $this->getUrl();
     if ($this->dumpHeaders) {
-      $this->verbose('GET request to: ' . $path .
-                     '<hr />Ending URL: ' . $this->getUrl() .
-                     '<hr />Headers: <pre>' . check_plain(var_export(array_map('trim', $this->headers), TRUE)) . '</pre>');
+      for ($i = 0; $i < $this->requestId; $i++) {
+        $verbose .= '<hr />Request headers: <pre>' . check_plain($this->requestHeaders[$i]) . '</pre>';
+        $verbose .= '<hr />Response headers: <pre>' . check_plain($this->responseHeaders[$i]) . '</pre>';
+      }
     }
+    $this->verbose($verbose);
 
     return $out;
   }
diff --git a/core/modules/simpletest/simpletest.api.php b/core/modules/simpletest/simpletest.api.php
index e11c651..dad9a39 100644
--- a/core/modules/simpletest/simpletest.api.php
+++ b/core/modules/simpletest/simpletest.api.php
@@ -6,18 +6,6 @@
  */
 
 /**
- * Global variable that holds information about the tests being run.
- *
- * An array, with the following keys:
- *  - 'test_run_id': the ID of the test being run, in the form 'simpletest_%"
- *  - 'in_child_site': TRUE if the current request is a cURL request from
- *     the parent site.
- *
- * @var array
- */
-global $drupal_test_info;
-
-/**
  * @addtogroup hooks
  * @{
  */
diff --git a/core/modules/simpletest/simpletest.install b/core/modules/simpletest/simpletest.install
index 4b940ed..eb73920 100644
--- a/core/modules/simpletest/simpletest.install
+++ b/core/modules/simpletest/simpletest.install
@@ -58,6 +58,20 @@ function simpletest_requirements($phase) {
     $requirements['php_memory_limit']['description'] = t('The testing framework requires the PHP memory limit to be at least %memory_minimum_limit. The current value is %memory_limit. <a href="@url">Follow these steps to continue</a>.', array('%memory_limit' => $memory_limit, '%memory_minimum_limit' => SIMPLETEST_MINIMUM_PHP_MEMORY_LIMIT, '@url' => 'http://drupal.org/node/207036'));
   }
 
+  if ($phase == 'runtime') {
+    $site_directory = 'sites/simpletest';
+    if (!drupal_verify_install_file(DRUPAL_ROOT . '/' . $site_directory, FILE_EXIST|FILE_WRITABLE, 'dir')) {
+      $requirements['simpletest_site_directory'] = array(
+        'title' => t('Simpletest site directory'),
+        'value' => is_dir(DRUPAL_ROOT . '/' . $site_directory) ? t('Not writable') : t('Missing'),
+        'severity' => REQUIREMENT_ERROR,
+        'description' => t('The testing framework requires the !test-sites-directory to exist and be writable in order to run tests.', array(
+          '!test-sites-directory' => '<code>./' . $site_directory . '</code>',
+        )),
+      );
+    }
+  }
+
   return $requirements;
 }
 
@@ -154,6 +168,20 @@ function simpletest_schema() {
 }
 
 /**
+ * Implements hook_install().
+ */
+function simpletest_install() {
+  // Attempt to automatically create the sites directory for tests.
+  // @see simpletest_requirements()
+  $site_directory = 'sites/simpletest';
+  if (!drupal_install_mkdir(DRUPAL_ROOT . '/' . $site_directory, FILE_READABLE|FILE_WRITABLE|FILE_EXECUTABLE)) {
+    drupal_set_message(t('The test sites directory !directory could not be created automatically. Create it manually and make it writable for the web server.', array(
+      '!directory' => '<code>./' . $site_directory . '</code>',
+    )), 'warning');
+  }
+}
+
+/**
  * Implements hook_uninstall().
  */
 function simpletest_uninstall() {
diff --git a/core/modules/simpletest/simpletest.module b/core/modules/simpletest/simpletest.module
index bd50676..ba3e9d5 100644
--- a/core/modules/simpletest/simpletest.module
+++ b/core/modules/simpletest/simpletest.module
@@ -367,7 +367,7 @@ function simpletest_last_test_get($test_id) {
  *
  * @param $test_id
  *   The test ID to which the log relates.
- * @param $prefix
+ * @param $database_prefix
  *   The database prefix to which the log relates.
  * @param $test_class
  *   The test class to which the log relates.
@@ -377,8 +377,8 @@ function simpletest_last_test_get($test_id) {
  * @return
  *   Found any entries in log.
  */
-function simpletest_log_read($test_id, $prefix, $test_class, $during_test = FALSE) {
-  $log = 'public://' . ($during_test ? '' : '/simpletest/' . substr($prefix, 10)) . '/error.log';
+function simpletest_log_read($test_id, $database_prefix, $test_class, $during_test = FALSE) {
+  $log = 'sites/simpletest/' . substr($database_prefix, 10) . '/error.log';
   $found = FALSE;
   if (file_exists($log)) {
     foreach (file($log) as $line) {
@@ -632,14 +632,12 @@ function simpletest_clean_database() {
  */
 function simpletest_clean_temporary_directories() {
   $count = 0;
-  if (is_dir('public://simpletest')) {
-    $files = scandir('public://simpletest');
+  if (is_dir('sites/simpletest')) {
+    $files = scandir('sites/simpletest');
     foreach ($files as $file) {
-      $path = 'public://simpletest/' . $file;
-      if (is_dir($path) && (is_numeric($file) || strpos($file, 'config_simpletest') !== FALSE)) {
-        file_unmanaged_delete_recursive($path, array('Drupal\simpletest\TestBase', 'filePreDeleteCallback'));
-        $count++;
-      }
+      $path = 'sites/simpletest/' . $file;
+      file_unmanaged_delete_recursive($path, array('Drupal\simpletest\TestBase', 'filePreDeleteCallback'));
+      $count++;
     }
   }
 
diff --git a/core/modules/system/lib/Drupal/system/Tests/File/FileTestBase.php b/core/modules/system/lib/Drupal/system/Tests/File/FileTestBase.php
index 540c19a..70fa934 100644
--- a/core/modules/system/lib/Drupal/system/Tests/File/FileTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/File/FileTestBase.php
@@ -16,16 +16,6 @@
  */
 abstract class FileTestBase extends WebTestBase {
 
-  function setUp() {
-    parent::setUp();
-    // Make sure that custom stream wrappers are registered.
-    // @todo This has the potential to be a major bug deeply buried in File API;
-    //   file_unmanaged_*() API functions and test functions are invoking native
-    //   PHP functions directly, whereas Drupal's custom stream wrappers are not
-    //   registered yet.
-    file_get_stream_wrappers();
-  }
-
   /**
    * Check that two files have the same values for all fields other than the
    * timestamp.
diff --git a/core/modules/system/lib/Drupal/system/Tests/File/ReadOnlyStreamWrapperTest.php b/core/modules/system/lib/Drupal/system/Tests/File/ReadOnlyStreamWrapperTest.php
index ef84796..5f3ea59 100644
--- a/core/modules/system/lib/Drupal/system/Tests/File/ReadOnlyStreamWrapperTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/File/ReadOnlyStreamWrapperTest.php
@@ -30,14 +30,9 @@ public static function getInfo() {
     );
   }
 
-  function setUp() {
-    parent::setUp();
-    drupal_static_reset('file_get_stream_wrappers');
-  }
-
   function tearDown() {
-    parent::tearDown();
     stream_wrapper_unregister($this->scheme);
+    parent::tearDown();
   }
 
   /**
diff --git a/core/modules/system/lib/Drupal/system/Tests/File/StreamWrapperTest.php b/core/modules/system/lib/Drupal/system/Tests/File/StreamWrapperTest.php
index 16bc592..6ce6568 100644
--- a/core/modules/system/lib/Drupal/system/Tests/File/StreamWrapperTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/File/StreamWrapperTest.php
@@ -33,14 +33,9 @@ public static function getInfo() {
     );
   }
 
-  function setUp() {
-    parent::setUp();
-    drupal_static_reset('file_get_stream_wrappers');
-  }
-
   function tearDown() {
-    parent::tearDown();
     stream_wrapper_unregister($this->scheme);
+    parent::tearDown();
   }
 
   /**
diff --git a/core/modules/system/lib/Drupal/system/Tests/Installer/InstallerTranslationTest.php b/core/modules/system/lib/Drupal/system/Tests/Installer/InstallerTranslationTest.php
index 69b3352..afda79d 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Installer/InstallerTranslationTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Installer/InstallerTranslationTest.php
@@ -33,23 +33,17 @@ protected function setUp() {
     // Back up the currently running SimpleTest batch.
     $this->originalBatch = batch_get();
 
-    // Add the translations directory so we can retrieve German translations.
-    $conf['locale.settings']['translation.path'] = drupal_get_path('module', 'simpletest') . '/files/translations';
-    $conf['language_default']['name'] = 'German';
-    $conf['language_default']['id'] = 'de';
-
     // Create the database prefix for this test.
     $this->prepareDatabasePrefix();
 
     // Prepare the environment for running tests.
     $this->prepareEnvironment();
     if (!$this->setupEnvironment) {
-      return FALSE;
+      throw new \RuntimeException('Failed to set up test environment.');
     }
 
     // Reset all statics and variables to perform tests in a clean environment.
     $conf = array();
-    drupal_static_reset();
 
     // Change the database prefix.
     // All static variables need to be reset before the database prefix is
@@ -57,34 +51,18 @@ protected function setUp() {
     // write back to persistent caches when they are destructed.
     $this->changeDatabasePrefix();
     if (!$this->setupDatabasePrefix) {
-      return FALSE;
-    }
-    $variable_groups = array(
-      'system.file' => array(
-        'path.private' =>  $this->private_files_directory,
-        'path.temporary' => $this->temp_files_directory,
-      ),
-      'locale.settings' => array(
-        'translation.path' => $this->translation_files_directory,
-      ),
-    );
-    foreach ($variable_groups as $config_base => $variables) {
-      foreach ($variables as $name => $value) {
-        NestedArray::setValue($GLOBALS['conf'], array_merge(array($config_base), explode('.', $name)), $value);
-      }
+      throw new \RuntimeException('Failed to set up database prefix.');
     }
-    $settings['conf_path'] = (object) array(
-      'value' => $this->public_files_directory,
-      'required' => TRUE,
-    );
-    $settings['config_directories'] = (object) array(
-      'value' => array(),
-      'required' => TRUE,
-    );
-    $this->writeSettings($settings);
+
+    // After preparing the environment and changing the database prefix, we are
+    // in a valid test environment.
+    drupal_valid_test_ua($this->databasePrefix);
 
     // Submit the installer with German language.
-    $this->drupalPostForm($GLOBALS['base_url'] . '/core/install.php', array('langcode' => 'de'), 'Save and continue');
+    $edit = array(
+      'langcode' => 'de',
+    );
+    $this->drupalPostForm($GLOBALS['base_url'] . '/core/install.php', $edit, 'Save and continue');
 
     // On the following page where installation profile is being selected the
     // interface should be already translated, so there is no "Set up database"
@@ -99,28 +77,17 @@ protected function setUp() {
     // Get the "Save and continue" submit button translated value from the
     // translated interface.
     $submit_value = (string) current($this->xpath('//input[@type="submit"]/@value'));
+    $this->assertNotEqual($submit_value, 'Save and continue');
 
-    // Submit the standard profile installation.
-    $this->drupalPostForm(NULL, array('profile' => 'standard'), $submit_value);
+    // Submit the Minimal profile installation.
+    $edit = array(
+      'profile' => 'minimal',
+    );
+    $this->drupalPostForm(NULL, $edit, $submit_value);
 
     // Submit the next step.
     $this->drupalPostForm(NULL, array(), $submit_value);
 
-    // Reload config directories.
-    include $this->public_files_directory . '/settings.php';
-    foreach ($config_directories as $type => $path) {
-      $GLOBALS['config_directories'][$type] = $path;
-    }
-    $this->rebuildContainer();
-
-    foreach ($variable_groups as $config_base => $variables) {
-      $config = \Drupal::config($config_base);
-      foreach ($variables as $name => $value) {
-        $config->set($name, $value);
-      }
-      $config->save();
-    }
-
     // Submit site configuration form.
     $this->drupalPostForm(NULL, array(
       'site_mail' => 'admin@test.de',
diff --git a/core/modules/system/lib/Drupal/system/Tests/InstallerTest.php b/core/modules/system/lib/Drupal/system/Tests/InstallerTest.php
index 5bbaab8..923424f 100644
--- a/core/modules/system/lib/Drupal/system/Tests/InstallerTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/InstallerTest.php
@@ -39,7 +39,7 @@ protected function setUp() {
     // Prepare the environment for running tests.
     $this->prepareEnvironment();
     if (!$this->setupEnvironment) {
-      return FALSE;
+      throw new \RuntimeException('Failed to set up test environment.');
     }
 
     // Reset all statics and variables to perform tests in a clean environment.
@@ -52,34 +52,35 @@ protected function setUp() {
     // write back to persistent caches when they are destructed.
     $this->changeDatabasePrefix();
     if (!$this->setupDatabasePrefix) {
-      return FALSE;
+      throw new \RuntimeException('Failed to set up database prefix.');
     }
-    $variable_groups = array(
-      'system.file' => array(
-        'path.private' =>  $this->private_files_directory,
-        'path.temporary' =>  $this->temp_files_directory,
-      ),
-      'locale.settings' =>  array(
-        'translation.path' => $this->translation_files_directory,
+
+    // After preparing the environment and changing the database prefix, we are
+    // in a valid test environment.
+    drupal_valid_test_ua($this->databasePrefix);
+
+    $settings['conf']['system.file'] = (object) array(
+      'value' => array(
+        'path' => array(
+          'private' => $this->private_files_directory,
+          'temporary' => $this->temp_files_directory,
+        ),
       ),
-    );
-    foreach ($variable_groups as $config_base => $variables) {
-      foreach ($variables as $name => $value) {
-        NestedArray::setValue($GLOBALS['conf'], array_merge(array($config_base), explode('.', $name)), $value);
-      }
-    }
-    $settings['conf_path'] = (object) array(
-      'value' => $this->public_files_directory,
       'required' => TRUE,
     );
-    $settings['config_directories'] = (object) array(
-      'value' => array(),
+    $settings['conf']['locale.settings'] = (object) array(
+      'value' => array(
+        'translation' => array(
+          'path' => $this->translation_files_directory,
+        ),
+      ),
       'required' => TRUE,
     );
     $this->writeSettings($settings);
 
     $this->drupalGet($GLOBALS['base_url'] . '/core/install.php?langcode=en&profile=minimal');
     $this->drupalPostForm(NULL, array(), 'Save and continue');
+
     // Reload config directories.
     include $this->public_files_directory . '/settings.php';
     foreach ($config_directories as $type => $path) {
@@ -87,14 +88,6 @@ protected function setUp() {
     }
     $this->rebuildContainer();
 
-    foreach ($variable_groups as $config_base => $variables) {
-      $config = \Drupal::config($config_base);
-      foreach ($variables as $name => $value) {
-        $config->set($name, $value);
-      }
-      $config->save();
-    }
-
     // Use the test mail class instead of the default mail handler class.
     \Drupal::config('system.mail')->set('interface.default', 'Drupal\Core\Mail\TestMailCollector')->save();
 
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 4fd03af..ab6fd81 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -327,15 +327,14 @@ function system_requirements($phase) {
     }
     else {
       // If we are installing Drupal, the settings.php file might not exist yet
-      // in the intended conf_path() directory, so don't require it. The
-      // conf_path() cache must also be reset in this case.
-      $directories[] = conf_path(FALSE, TRUE) . '/files';
+      // in the intended site directory, so don't require it.
+      $directories[] = conf_path(FALSE) . '/files';
     }
-    if (!empty($conf['system.file']['path.private'])) {
-      $directories[] = $conf['system.file']['path.private'];
+    if (!empty($conf['system.file']['path']['private'])) {
+      $directories[] = $conf['system.file']['path']['private'];
     }
-    if (!empty($conf['system.file']['path.temporary'])) {
-      $directories[] = $conf['system.file']['path.temporary'];
+    if (!empty($conf['system.file']['path']['temporary'])) {
+      $directories[] = $conf['system.file']['path']['temporary'];
     }
     else {
       // If the temporary directory is not overridden use an appropriate
diff --git a/core/rebuild.php b/core/rebuild.php
index 1b94601..b408c43 100644
--- a/core/rebuild.php
+++ b/core/rebuild.php
@@ -15,6 +15,7 @@
 // Change the directory to the Drupal root.
 chdir('..');
 
+require_once __DIR__ . '/vendor/autoload.php';
 require_once dirname(__DIR__) . '/core/includes/bootstrap.inc';
 require_once dirname(__DIR__) . '/core/includes/utility.inc';
 
diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh
index e9f0ffe..dee2ada 100755
--- a/core/scripts/run-tests.sh
+++ b/core/scripts/run-tests.sh
@@ -12,6 +12,9 @@
 const SIMPLETEST_SCRIPT_COLOR_FAIL = 31; // Red.
 const SIMPLETEST_SCRIPT_COLOR_EXCEPTION = 33; // Brown.
 
+set_error_handler('simpletest_script_error_handler');
+set_exception_handler('simpletest_script_exception_handler');
+
 // Set defaults and get overrides.
 list($args, $count) = simpletest_script_parse_args();
 
@@ -34,6 +37,10 @@
 // Bootstrap to perform initial validation or other operations.
 drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE);
 
+// DRUPAL_BOOTSTRAP_CONFIGURATION sets new handlers, restore ours.
+restore_error_handler();
+restore_exception_handler();
+
 if (!\Drupal::moduleHandler()->moduleExists('simpletest')) {
   simpletest_script_print_error("The Testing (simpletest) module must be installed before this script can run.");
   exit;
@@ -489,6 +496,11 @@ function simpletest_script_run_one_test($test_id, $test_class) {
   try {
     // Bootstrap Drupal.
     drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE);
+
+    // DRUPAL_BOOTSTRAP_CONFIGURATION sets new handlers, restore ours.
+    restore_error_handler();
+    restore_exception_handler();
+
     simpletest_classloader_register();
     // We have to add a Request.
     $request = \Symfony\Component\HttpFoundation\Request::createFromGlobals();
@@ -585,10 +597,9 @@ function simpletest_script_cleanup($test_id, $test_class, $exitcode) {
   // Read the log file in case any fatal errors caused the test to crash.
   simpletest_log_read($test_id, $db_prefix, $test_class);
 
-  // Check whether a test file directory was setup already.
-  // @see prepareEnvironment()
-  $public_files = PublicStream::basePath();
-  $test_directory = $public_files . '/simpletest/' . substr($db_prefix, 10);
+  // Check whether a test site directory was setup already.
+  // @see \Drupal\simpletest\TestBase::prepareEnvironment()
+  $test_directory = DRUPAL_ROOT . '/sites/simpletest/' . substr($db_prefix, 10);
   if (is_dir($test_directory)) {
     // Output the error_log.
     if (is_file($test_directory . '/error.log')) {
@@ -597,8 +608,7 @@ function simpletest_script_cleanup($test_id, $test_class, $exitcode) {
         $messages[] = $errors;
       }
     }
-
-    // Delete the test files directory.
+    // Delete the test site directory.
     // simpletest_clean_temporary_directories() cannot be used here, since it
     // would also delete file directories of other tests that are potentially
     // running concurrently.
@@ -864,6 +874,56 @@ function simpletest_script_format_result($result) {
 }
 
 /**
+ * Custom PHP error handler for early run-tests.sh execution.
+ *
+ * @param $error_level
+ *   The level of the error raised.
+ * @param $message
+ *   The error message.
+ * @param $filename
+ *   The filename that the error was raised in.
+ * @param $line
+ *   The line number the error was raised at.
+ * @param $context
+ *   An array that points to the active symbol table at the point the error
+ *   occurred.
+ */
+function simpletest_script_error_handler($error_level, $message, $filename, $line, $context) {
+  $map = array(
+    E_ERROR => 'Error',
+    E_WARNING => 'Warning',
+    E_PARSE => 'Parse error',
+    E_NOTICE => 'Notice',
+    E_CORE_ERROR => 'Core error',
+    E_CORE_WARNING => 'Core warning',
+    E_COMPILE_ERROR => 'Compile error',
+    E_COMPILE_WARNING => 'Compile warning',
+    E_USER_ERROR => 'User error',
+    E_USER_WARNING => 'User warning',
+    E_USER_NOTICE => 'User notice',
+    E_STRICT => 'Strict warning',
+    E_RECOVERABLE_ERROR => 'Recoverable fatal error',
+    E_DEPRECATED => 'Deprecated function',
+    E_USER_DEPRECATED => 'User deprecated function',
+  );
+  simpletest_script_print_error($map[$error_level] . ': ' . $message);
+  echo "in $filename: $line\n";
+  debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
+  echo "\n";
+}
+
+/**
+ * Custom PHP exception handler for early run-tests.sh execution.
+ *
+ * @param \Exception|int $exception
+ *   The exception object that was thrown.
+ */
+function simpletest_script_exception_handler($e) {
+  echo "<pre>"; var_dump(func_get_args()); echo "</pre>\n";
+  simpletest_script_print_error((string) $e);
+}
+
+/**
  * Print error message prefixed with "  ERROR: " and displayed in fail color
  * if color output is enabled.
  *
