diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index c14ab53..399fd38 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -387,6 +387,13 @@ function conf_path($require_settings = TRUE, $reset = FALSE) {
     return $conf;
   }
 
+  // Check for a simpletest override.
+  if ($simpletest_conf_path = _drupal_simpletest_conf_path()) {
+    $conf = $simpletest_conf_path;
+    return $conf;
+  }
+
+  // Otherwise, use the normal $conf_path.
   $script_name = $_SERVER['SCRIPT_NAME'];
   if (!$script_name) {
     $script_name = $_SERVER['SCRIPT_FILENAME'];
@@ -397,6 +404,49 @@ 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 (and therefore override variables set by) the primary one used by the
+ * parent site. 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.
+  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 the settings object is available.
+  if (!drupal_valid_test_ua()) {
+    return FALSE;
+  }
+
+  // Otherwise, 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
@@ -474,11 +524,7 @@ function find_conf_path($http_host, $script_name, $require_settings = TRUE) {
 function config_get_config_directory($type = CONFIG_ACTIVE_DIRECTORY) {
   global $config_directories;
 
-  if ($test_prefix = drupal_valid_test_ua()) {
-    // @see Drupal\simpletest\WebTestBase::setUp()
-    $path = conf_path() . '/files/simpletest/' . substr($test_prefix, 10) . '/config_' . $type;
-  }
-  elseif (!empty($config_directories[$type])) {
+  if (!empty($config_directories[$type])) {
     // Allow a configuration directory path to be outside of webroot.
     if (empty($config_directories[$type]['absolute'])) {
       $path = conf_path() . '/files/' . $config_directories[$type]['path'];
@@ -2554,7 +2600,7 @@ function drupal_valid_test_ua($new_prefix = NULL) {
     return $test_prefix;
   }
 
-  if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^(simpletest\d+);(.+);(.+);(.+)$/", $_SERVER['HTTP_USER_AGENT'], $matches)) {
+  if ($drupal_hash_salt && 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
@@ -2566,6 +2612,7 @@ function drupal_valid_test_ua($new_prefix = NULL) {
     // and the HMAC must match.
     if ($time_diff >= 0 && $time_diff <= 5 && $hmac == drupal_hmac_base64($check_string, $key)) {
       $test_prefix = $prefix;
+      _drupal_load_test_overrides($test_prefix);
       return $test_prefix;
     }
   }
@@ -2575,6 +2622,43 @@ function drupal_valid_test_ua($new_prefix = NULL) {
 }
 
 /**
+ * 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 $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] = array('path' => $path_prefix . '/config_' . $type);
+  }
+
+  $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) {
diff --git a/core/includes/errors.inc b/core/includes/errors.inc
index 5fc4b7d..f29def7 100644
--- a/core/includes/errors.inc
+++ b/core/includes/errors.inc
@@ -153,7 +153,7 @@ function _drupal_render_exception_safe($exception) {
  *   TRUE if an error should be displayed.
  */
 function error_displayable($error = NULL) {
-  $error_level = config('system.logging')->get('error_level');
+  $error_level = _drupal_get_error_level();
   $updating = (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update');
   $all_errors_displayed = ($error_level == ERROR_REPORTING_DISPLAY_ALL) ||
     ($error_level == ERROR_REPORTING_DISPLAY_VERBOSE);
@@ -254,7 +254,7 @@ function _drupal_log_error($error, $fatal = FALSE) {
       $message = format_string('%type: !message in %function (line %line of %file).', $error);
 
       // Check if verbose error reporting is on.
-      $error_level = config('system.logging')->get('error_level');
+      $error_level = _drupal_get_error_level();
 
       if ($error_level == ERROR_REPORTING_DISPLAY_VERBOSE) {
         // First trace is the error itself, already contained in the message.
@@ -287,6 +287,29 @@ function _drupal_log_error($error, $fatal = FALSE) {
 }
 
 /**
+ * Returns the current error level.
+ *
+ * This function should only be used to get the current error level pre
+ * DRUPAL_BOOTSTRAP_KERNEL or before Drupal is installed. In all other
+ * situations the following code is preferred:
+ * @code
+ * Drupal::config('system.logging')->get('error_level');
+ * @endcode
+ *
+ * @return string
+ *   The current error level.
+ */
+function _drupal_get_error_level() {
+  try {
+    return Drupal::config('system.logging')->get('error_level');
+  }
+  catch (Exception $e) {
+    // During very early install the cache_config table does not exist.
+    return ERROR_REPORTING_DISPLAY_ALL;
+  }
+}
+
+/**
  * Gets the last caller from a backtrace.
  *
  * @param $backtrace
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index df688ab..a5c5c22 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -1,5 +1,6 @@
 <?php
 
+use Drupal\Core\Config\FileStorage;
 use Drupal\Core\DrupalKernel;
 use Drupal\Core\CoreBundle;
 use Drupal\Core\Database\Database;
@@ -263,13 +264,6 @@ 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.
-  elseif (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], "simpletest") !== FALSE) {
-    header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
-    exit;
-  }
 
   // Initialize conf_path().
   // This primes the site path to be used during installation. By not requiring
@@ -427,7 +421,17 @@ function install_begin_request(&$install_state) {
     $task = NULL;
 
     // Do not install over a configured settings.php.
-    if (!empty($GLOBALS['databases'])) {
+    if (!empty($GLOBALS['databases']) && !drupal_valid_test_ua()) {
+      throw new Exception(install_already_done_error());
+    }
+  }
+
+  // Ensure that the active configuration directory is empty before installation
+  // starts.
+  if ($install_state['config_verified'] && empty($task)) {
+    $config = glob(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY) . '/*.' . FileStorage::getFileExtension());
+    if (!empty($config)) {
+      $task = NULL;
       throw new Exception(install_already_done_error());
     }
   }
@@ -1049,6 +1053,9 @@ 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];
+  if ($test_prefix = drupal_valid_test_ua()) {
+    $database['prefix'] = $test_prefix;
+  }
   $drivers = drupal_get_database_types();
   $reflection = new \ReflectionClass($drivers[$driver]);
   $install_namespace = $reflection->getNamespaceName();
@@ -1057,8 +1064,11 @@ function install_settings_form_validate($form, &$form_state) {
   $database['driver'] = $driver;
 
   // TODO: remove when PIFR will be updated to use 'db_prefix' instead of
-  // 'prefix' in the database settings form.
-  $database['prefix'] = $database['db_prefix'];
+  // 'prefix' in the database settings form with the non-interactive
+  // installer.
+  if (!$test_prefix) {
+    $database['prefix'] = $database['db_prefix'];
+  }
   unset($database['db_prefix']);
 
   $form_state['storage']['database'] = $database;
@@ -1118,14 +1128,34 @@ function install_settings_form_submit($form, &$form_state) {
   global $install_state;
 
   // Update global settings array and save.
-  $settings['databases'] = (object) array(
-    'value'    => array('default' => array('default' => $form_state['storage']['database'])),
-    'required' => TRUE,
-  );
-  $settings['drupal_hash_salt'] = (object) array(
-    'value'    => drupal_hash_base64(drupal_random_bytes(55)),
-    'required' => TRUE,
-  );
+  $settings = array();
+  $database = $form_state['storage']['database'];
+  if ($test_prefix = drupal_valid_test_ua()) {
+    // This is test specific code, however it is very small and contained.
+    foreach ($form_state['storage']['database'] as $k => $v) {
+      if ($k != 'password') {
+        $settings['databases']['default']['default'][$k] = (object) array(
+          'value'    => $v,
+          'required' => TRUE,
+        );
+      }
+    }
+  }
+  else {
+    // Because of the test specific code above, this assignment here must be
+    // kept simple and must not call any functions to avoid creating a tested
+    // and a non-tested code path.
+    $settings['databases']['default']['default'] = (object) array(
+      'value'    => $database,
+      'required' => TRUE,
+    );
+  }
+  if (!$test_prefix) {
+    $settings['drupal_hash_salt'] = (object) array(
+      'value'    => drupal_hash_base64(drupal_random_bytes(55)),
+      'required' => TRUE,
+    );
+  }
 
   drupal_rewrite_settings($settings);
 
@@ -1609,7 +1639,7 @@ function install_already_done_error() {
   global $base_url;
 
   drupal_set_title(st('Drupal already installed'));
-  return st('<ul><li>To start over, you must empty your existing database.</li><li>To install to a different database, edit the appropriate <em>settings.php</em> file in the <em>sites</em> folder.</li><li>To upgrade an existing installation, proceed to the <a href="@base-url/core/update.php">update script</a>.</li><li>View your <a href="@base-url">existing site</a>.</li></ul>', array('@base-url' => $base_url));
+  return st('<ul><li>To start over, you must empty your existing database and delete your active configuration.</li><li>To install to a different database, edit the appropriate <em>settings.php</em> file in the <em>sites</em> folder.</li><li>To locate your active configuration, view the appropriate <em>settings.php</em> file in the <em>sites</em> folder.</em></li></li><li>To upgrade an existing installation, proceed to the <a href="@base-url/core/update.php">update script</a>.</li><li>View your <a href="@base-url">existing site</a>.</li></ul>', array('@base-url' => $base_url));
 }
 
 /**
diff --git a/core/includes/theme.maintenance.inc b/core/includes/theme.maintenance.inc
index 37775e6..cda4109 100644
--- a/core/includes/theme.maintenance.inc
+++ b/core/includes/theme.maintenance.inc
@@ -50,7 +50,17 @@ function _drupal_maintenance_theme() {
     //   Stark otherwise. Since there is no low-level access to configuration
     //   currently, we only consult settings.php and fall back to Bartik
     //   otherwise, as it looks generic enough and way more user-friendly.
-    $custom_theme = variable_get('maintenance_theme', config('system.theme')->get('default')) ?: 'bartik';
+    $custom_theme = variable_get('maintenance_theme');
+    if (!$custom_theme)  {
+      $config = Drupal::config('system.theme');
+      // A broken install might not return an object.
+      if (is_object($config)) {
+        $custom_theme = $config->get('default');
+      }
+    }
+    if (!$custom_theme)  {
+      $custom_theme = 'bartik';
+    }
   }
 
   // Ensure that system.module is loaded.
diff --git a/core/lib/Drupal/Core/SystemListingInfo.php b/core/lib/Drupal/Core/SystemListingInfo.php
index b2e42e1..989b0f2 100644
--- a/core/lib/Drupal/Core/SystemListingInfo.php
+++ b/core/lib/Drupal/Core/SystemListingInfo.php
@@ -28,7 +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).
-    if (drupal_valid_test_ua()) {
+    if (drupal_valid_test_ua() && !drupal_installation_attempted()) {
       $testing_profile = config('simpletest.settings')->get('parent_profile');
       if ($testing_profile && $testing_profile != $profile) {
         $searchdir[] = drupal_get_path('profile', $testing_profile) . '/' . $directory;
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
index 5cca38a..b24bba3 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
@@ -859,6 +859,36 @@ protected function setUp() {
   }
 
   /**
+   * Writes a test-specific settings.php file for the child 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().
+   *
+   * @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;
+    }
+
+    include_once DRUPAL_ROOT . '/core/includes/install.inc';
+    $filename = $this->public_files_directory . '/settings.php';
+    file_put_contents($filename, "<?php\n");
+    drupal_rewrite_settings($settings, $filename);
+
+    // Restore the original globals.
+    foreach ($original_globals as $variable_name => $value) {
+      $GLOBALS[$variable_name] = $value;
+    }
+  }
+
+  /**
    * Reset all data structures after having enabled new modules.
    *
    * This method is called by Drupal\simpletest\WebTestBase::setUp() after enabling
@@ -1328,9 +1358,9 @@ protected function drupalPost($path, $edit, $submit, array $options = array(), a
         $this->fail(t('Failed to set field @name to @value', array('@name' => $name, '@value' => $value)));
       }
       if (!$ajax && isset($submit)) {
-        $this->assertTrue($submit_matches, t('Found the @submit button', array('@submit' => $submit)));
+        $this->assertTrue($submit_matches, format_string('Found the @submit button', array('@submit' => $submit)));
       }
-      $this->fail(t('Found the requested form fields at @path', array('@path' => $path)));
+      $this->fail(format_string('Found the requested form fields at @path', array('@path' => $path)));
     }
   }
 
diff --git a/core/modules/system/lib/Drupal/system/Tests/InstallerTest.php b/core/modules/system/lib/Drupal/system/Tests/InstallerTest.php
new file mode 100644
index 0000000..5afb59e
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/InstallerTest.php
@@ -0,0 +1,138 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Form\SystemConfigFormTest.
+ */
+
+namespace Drupal\system\Tests;
+
+use Drupal\Component\Utility\NestedArray;
+use Drupal\simpletest\WebTestBase;
+
+
+class InstallerTest extends WebTestBase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Installer tests',
+      'description' => 'Tests the interactive installer.',
+      'group' => 'Devilry',
+    );
+  }
+
+  protected function setUp() {
+    global $conf;
+
+    // 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();
+
+    // 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.
+    $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;
+    }
+    $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);
+      }
+    }
+    $GLOBALS['conf']['file_public_path'] = $this->public_files_directory;
+    $settings['conf_path'] = (object) array(
+      'value' => $this->public_files_directory,
+      'required' => TRUE,
+    );
+    $settings['config_directories'] = (object) array(
+      'value' => array(),
+      'required' => TRUE,
+    );
+    $this->writeSettings($settings);
+
+    $this->drupalGet('core/install.php?langcode=en&profile=minimal');
+    $this->drupalPost(NULL, array(), 'Save and continue');
+    // Reload config directories.
+    include $this->public_files_directory . '/settings.php';
+    $prefix = substr($this->public_files_directory, strlen(conf_path() . '/files/'));
+    foreach ($config_directories as $type => $data) {
+      $GLOBALS['config_directories'][$type]['path'] = $prefix . '/files/' . $data['path'];
+    }
+    $this->rebuildContainer();
+
+    foreach ($variable_groups as $config_base => $variables) {
+      $config = config($config_base);
+      foreach ($variables as $name => $value) {
+        $config->set($name, $value);
+      }
+      $config->save();
+    }
+    variable_set('file_public_path', $this->public_files_directory);
+
+    // Use the test mail class instead of the default mail handler class.
+    config('system.mail')->set('interface.default', 'Drupal\Core\Mail\VariableLog')->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.
+    $path = current_path();
+    if (empty($path)) {
+      _current_path('run-tests');
+    }
+    $this->setup = TRUE;
+  }
+
+  protected function refreshVariables() {
+    if (!empty($this->setup)) {
+      parent::refreshVariables();
+    }
+  }
+
+  protected function drupalGet($path, array $options = array(), array $headers = array()) {
+
+    // We re-using a CURL connection here. If that connection still has certain
+    // options set, it might change the GET into a POST. Make sure we clear out
+    // previous options.
+    $out = $this->curlExec(array(CURLOPT_HTTPGET => TRUE, CURLOPT_URL => $this->getAbsoluteUrl($path), CURLOPT_NOBODY => FALSE, CURLOPT_HTTPHEADER => $headers));
+    $this->refreshVariables(); // Ensure that any changes to variables in the other thread are picked up.
+
+    // Replace original page output with new output from redirected page(s).
+    if ($new = $this->checkForMetaRefresh()) {
+      $out = $new;
+    }
+    $this->verbose('GET request to: ' . $path .
+                   '<hr />Ending URL: ' . $this->getUrl() .
+                   '<hr />' . $out);
+    return $out;
+  }
+
+  function testInstaller() {
+    $this->drupalGet('user');
+  }
+}
\ No newline at end of file
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/BareMinimalAnonymousUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/BareMinimalAnonymousUpgradePathTest.php
new file mode 100644
index 0000000..2ac0b68
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/BareMinimalAnonymousUpgradePathTest.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * Contains \Drupal\system\Tests\Upgrade\BareMinimalAnonymousUpgradePathTest.
+ */
+
+namespace Drupal\system\Tests\Upgrade;
+
+/**
+ * Tests the upgrade path without prior creation of config directions.
+ */
+class BareMinimalAnonymousUpgradePathTest extends BareMinimalUpgradePathTest {
+  public static function getInfo() {
+    return array(
+      'name' => 'Basic minimal profile upgrade, free access',
+      'description' => 'Basic upgrade path tests for a minimal profile install with a bare database and update_free_access set to TRUE.',
+      'group' => 'Upgrade path',
+    );
+  }
+
+  /**
+   * Overrides \Drupal\system\Tests\Upgrade\UpgradePathTestBase::setUp().
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $settings['settings']['update_free_access'] = (object) array(
+      'value' => TRUE,
+      'required' => TRUE,
+    );
+    $this->writeSettings($settings);
+  }
+
+  /**
+   * Overrides \Drupal\system\Tests\Upgrade\UpgradePathTestBase::prepareD8Session().
+   */
+  protected function prepareD8Session() {
+    // We are not logged in, nothing to do.
+  }
+
+  /**
+   * Overrides \Drupal\system\Tests\Upgrade\UpgradePathTestBase::assertSessionKept().
+   */
+  protected function assertSessionKept() {
+    // We are not logged in, nothing to do.
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/BareMinimalNoConfigUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/BareMinimalNoConfigUpgradePathTest.php
new file mode 100644
index 0000000..36a3a03
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/BareMinimalNoConfigUpgradePathTest.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * Contains \Drupal\system\Tests\Upgrade\BareMinimalNoConfigUpgradePathTest.
+ */
+
+namespace Drupal\system\Tests\Upgrade;
+
+/**
+ * Tests the database upgrade path without creating config directories.
+ */
+class BareMinimalNoConfigUpgradePathTest extends BareMinimalUpgradePathTest {
+
+  public static function getInfo() {
+    return array(
+      'name'  => 'Basic minimal profile upgrade, no config',
+      'description'  => 'Basic upgrade path tests for a minimal profile install with a bare database and config directory not pre-created.',
+      'group' => 'Upgrade path',
+    );
+  }
+
+  /**
+   * Overrides \Drupal\system\Tests\Upgrade\UpgradePathTestBase::setUp().
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $settings['conf_path'] = (object) array(
+      'value' => $this->public_files_directory,
+      'required' => TRUE,
+    );
+    $settings['config_directories'] = (object) array(
+      'value' => array(),
+      'required' => TRUE,
+    );
+    $this->writeSettings($settings);
+  }
+
+  /**
+   * Overrides \Drupal\system\Tests\Upgrade\UpgradePathTestBase::refreshVariables().
+   */
+  protected function refreshVariables() {
+    // Refresh the variables only if the site was already upgraded.
+    if ($this->upgradedSite) {
+      // update.php puts the new, randomized config directries in this file.
+      include $this->public_files_directory . '/settings.php';
+      $GLOBALS['config_directories'] = array();
+      foreach ($config_directories as $type => $data) {
+        // update.php runs as the child site, so writes the paths relative to
+        // that "$conf_path/files", but here, we're running as the parent site,
+        // so need to make the paths relative to our "conf_path()/files".
+        //
+        // Example:
+        // - Parent site conf_path(): 'sites/default'
+        // - Child site $conf_path: 'sites/default/files/simpletest/123456'
+        // - Child site $data['path']: 'config_xyz'
+        // - Desired result: 'simpletest/123456/files/config_xyz'
+        //
+        // @see config_get_config_directory()
+        $GLOBALS['config_directories'][$type]['path'] = substr($conf_path, strlen(conf_path() . '/files/')) . '/files/' . $data['path'];
+      }
+      parent::refreshVariables();
+    }
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/BareMinimalUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/BareMinimalUpgradePathTest.php
index 9c51bee..ab58790 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/BareMinimalUpgradePathTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/BareMinimalUpgradePathTest.php
@@ -2,7 +2,7 @@
 
 /**
  * @file
- * Definition of Drupal\system\Tests\Upgrade\BareMinimalUpgradePathTest.
+ * Contains \Drupal\system\Tests\Upgrade\BareMinimalUpgradePathTest.
  */
 
 namespace Drupal\system\Tests\Upgrade;
@@ -43,12 +43,7 @@ public function testBasicMinimalUpgrade() {
     $this->assertResponse(200);
 
     // Verify that we are still logged in.
-    $this->drupalGet('user');
-    $this->clickLink(t('Edit'));
-    $this->assertEqual($this->getUrl(), url('user/1/edit', array('absolute' => TRUE)), 'We are still logged in as admin at the end of the upgrade.');
-
-    // Logout and verify that we can login back in with our initial password.
-    $this->drupalLogout();
+    $this->assertSessionKept();
     $this->drupalLogin((object) array(
       'uid' => 1,
       'name' => 'admin',
@@ -95,4 +90,14 @@ public function testBasicMinimalUpgrade() {
     $this->assertEqual(array('default' => 'Drupal\Core\Mail\PhpMail'), config('system.mail')->get('interface'), 'Default mail configuration set.');
   }
 
+  /**
+   * Asserts that the session was kept during update. Also, log out.
+   */
+  protected function assertSessionKept() {
+    $this->drupalGet('user');
+    $this->clickLink(t('Edit'));
+    $this->assertEqual($this->getUrl(), url('user/1/edit', array('absolute' => TRUE)), 'We are still logged in as admin at the end of the upgrade.');
+    $this->drupalLogout();
+  }
+
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
index 74c9923..c809829 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
@@ -73,7 +73,7 @@ protected function checkRequirements() {
    * @see Drupal\simpletest\WebTestBase::changeDatabasePrefix()
    * @see Drupal\simpletest\WebTestBase::prepareEnvironment()
    */
-  protected function setUp() {
+  public function setUp() {
     global $user, $conf;
 
     // Load the Update API.
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index a93a124..445b27c 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -397,7 +397,9 @@ function system_requirements($phase) {
       }
     }
     else {
-      if (file_default_scheme() == 'public') {
+      // This function can be called before the config_cache table has been
+      // created.
+      if ($phase == 'install' || file_default_scheme() == 'public') {
         $requirements['file system']['value'] = $t('Writable (<em>public</em> download method)');
       }
       else {
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index bdf5862..46f8807 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -719,6 +719,12 @@ function user_format_name($account) {
 function user_template_preprocess_default_variables_alter(&$variables) {
   global $user;
 
+  // If this function is called from the installer after Drupal has been
+  // installed then $user will not be set.
+  if (!is_object($user)) {
+    return;
+  }
+
   $variables['user'] = clone $user;
   // Remove password and session IDs, since themes should not need nor see them.
   unset($variables['user']->pass, $variables['user']->sid, $variables['user']->ssid);
