diff --git a/core/includes/install.inc b/core/includes/install.inc
index 77e0b70..f021373 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -210,7 +210,7 @@ function drupal_rewrite_settings($settings = array(), $settings_file = NULL) {
     }
     $variable_names['$'. $setting] = $setting;
   }
-  $contents = file_get_contents(DRUPAL_ROOT . '/' . $settings_file);
+  $contents = file_get_contents($settings_file);
   if ($contents !== FALSE) {
     // Initialize the contents for the settings.php file if it is empty.
     if (trim($contents) === '') {
@@ -315,7 +315,7 @@ function drupal_rewrite_settings($settings = array(), $settings_file = NULL) {
     }
 
     // Write the new settings file.
-    if (file_put_contents(DRUPAL_ROOT . '/' . $settings_file, $buffer) === FALSE) {
+    if (file_put_contents($settings_file, $buffer) === FALSE) {
       throw new Exception(t('Failed to modify %settings. Verify the file permissions.', array('%settings' => $settings_file)));
     }
     else {
diff --git a/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php
index 0f5b45a..aaac182 100644
--- a/core/lib/Drupal/Core/Config/FileStorage.php
+++ b/core/lib/Drupal/Core/Config/FileStorage.php
@@ -193,19 +193,23 @@ public function decode($raw) {
    * Implements Drupal\Core\Config\StorageInterface::listAll().
    */
   public function listAll($prefix = '') {
-    // glob() silently ignores the error of a non-existing search directory,
-    // even with the GLOB_ERR flag.
     $dir = $this->getCollectionDirectory();
-    if (!file_exists($dir)) {
+    if (!is_dir($dir)) {
       return array();
     }
     $extension = '.' . static::getFileExtension();
-    // \GlobIterator on Windows requires an absolute path.
-    $files = new \GlobIterator(realpath($dir) . '/' . $prefix . '*' . $extension);
+
+    // glob() directly calls into libc glob(), which is not aware of PHP stream
+    // wrappers. Same for \GlobIterator (which additionally requires an absolute
+    // realpath() on Windows).
+    // @see https://github.com/mikey179/vfsStream/issues/2
+    $files = scandir($dir);
 
     $names = array();
     foreach ($files as $file) {
-      $names[] = $file->getBasename($extension);
+      if ($file[0] !== '.' && fnmatch($prefix . '*' . $extension, $file)) {
+        $names[] = basename($file, $extension);
+      }
     }
 
     return $names;
@@ -299,13 +303,15 @@ protected function getAllCollectionNamesHelper($directory) {
             $collections[] = $collection . '.' . $sub_collection;
           }
         }
-        // Check that the collection is valid by searching if for configuration
+        // Check that the collection is valid by searching it for configuration
         // objects. A directory without any configuration objects is not a valid
         // collection.
-        // \GlobIterator on Windows requires an absolute path.
-        $files = new \GlobIterator(realpath($directory . '/' . $collection) . '/*.' . $this->getFileExtension());
-        if (count($files)) {
-          $collections[] = $collection;
+        // @see \Drupal\Core\Config\FileStorage::listAll()
+        foreach (scandir($directory . '/' . $collection) as $file) {
+          if ($file[0] !== '.' && fnmatch('*.' . $this->getFileExtension(), $file)) {
+            $collections[] = $collection;
+            break;
+          }
         }
       }
     }
diff --git a/core/lib/Drupal/Core/Config/InstallStorage.php b/core/lib/Drupal/Core/Config/InstallStorage.php
index 5c1d65a..0843250 100644
--- a/core/lib/Drupal/Core/Config/InstallStorage.php
+++ b/core/lib/Drupal/Core/Config/InstallStorage.php
@@ -195,10 +195,17 @@ public function getComponentNames(array $list) {
       // We don't have to use ExtensionDiscovery here because our list of
       // extensions was already obtained through an ExtensionDiscovery scan.
       $directory = $this->getComponentFolder($extension_object);
-      if (file_exists($directory)) {
-        $files = new \GlobIterator(\Drupal::root() . '/' . $directory . '/*' . $extension);
+      if (is_dir($directory)) {
+        // glob() directly calls into libc glob(), which is not aware of PHP
+        // stream wrappers. Same for \GlobIterator (which additionally requires
+        // an absolute realpath() on Windows).
+        // @see https://github.com/mikey179/vfsStream/issues/2
+        $files = scandir($directory);
+
         foreach ($files as $file) {
-          $folders[$file->getBasename($extension)] = $directory;
+          if ($file[0] !== '.' && fnmatch('*' . $extension, $file)) {
+            $folders[basename($file, $extension)] = $directory;
+          }
         }
       }
     }
@@ -215,10 +222,17 @@ public function getCoreNames() {
     $extension = '.' . $this->getFileExtension();
     $folders = array();
     $directory = $this->getCoreFolder();
-    if (file_exists($directory)) {
-      $files = new \GlobIterator(\Drupal::root() . '/' . $directory . '/*' . $extension);
+    if (is_dir($directory)) {
+      // glob() directly calls into libc glob(), which is not aware of PHP
+      // stream wrappers. Same for \GlobIterator (which additionally requires an
+      // absolute realpath() on Windows).
+      // @see https://github.com/mikey179/vfsStream/issues/2
+      $files = scandir($directory);
+
       foreach ($files as $file) {
-        $folders[$file->getBasename($extension)] = $directory;
+        if ($file[0] !== '.' && fnmatch('*' . $extension, $file)) {
+          $folders[basename($file, $extension)] = $directory;
+        }
       }
     }
     return $folders;
diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
index 4747440..dda8637 100644
--- a/core/lib/Drupal/Core/DrupalKernel.php
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -15,6 +15,7 @@
 use Drupal\Core\Config\NullStorage;
 use Drupal\Core\Database\Database;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\DependencyInjection\ServiceModifierInterface;
 use Drupal\Core\DependencyInjection\ServiceProviderInterface;
 use Drupal\Core\DependencyInjection\YamlFileLoader;
 use Drupal\Core\Extension\ExtensionDiscovery;
@@ -151,13 +152,16 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
   protected $serviceYamls;
 
   /**
-   * List of discovered service provider class names.
+   * List of discovered service provider class names or objects.
    *
    * This is a nested array whose top-level keys are 'app' and 'site', denoting
    * the origin of a service provider. Site-specific providers have to be
    * collected separately, because they need to be processed last, so as to be
    * able to override services from application service providers.
    *
+   * Allowing objects is for example used to allow
+   * \Drupal\Tests\KernelTestBase to register itself as service provider.
+   *
    * @var array
    */
   protected $serviceProviderClasses;
@@ -430,6 +434,21 @@ public function getContainer() {
   /**
    * {@inheritdoc}
    */
+  public function setContainer(ContainerInterface $container = NULL) {
+    if (isset($this->container)) {
+      throw new \Exception('The container should not override an existing container.');
+    }
+    if ($this->booted) {
+      throw new \Exception('The container cannot be set after a booted kernel.');
+    }
+
+    $this->container = $container;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function loadLegacyIncludes() {
     require_once $this->root . '/core/includes/common.inc';
     require_once $this->root . '/core/includes/database.inc';
@@ -514,7 +533,7 @@ public function discoverServiceProviders() {
     // Add site-specific service providers.
     if (!empty($GLOBALS['conf']['container_service_providers'])) {
       foreach ($GLOBALS['conf']['container_service_providers'] as $class) {
-        if (class_exists($class)) {
+        if ((is_string($class) && class_exists($class)) || (is_object($class) && ($class instanceof ServiceProviderInterface || $class instanceof ServiceModifierInterface))) {
           $this->serviceProviderClasses['site'][] = $class;
         }
       }
@@ -745,6 +764,13 @@ protected function initializeContainer() {
       }
     }
 
+    // If we haven't booted yet but there is a container, then we're asked to
+    // boot the container injected via setContainer().
+    // @see \Drupal\Tests\KernelTestBase::setUp()
+    if (isset($this->container) && !$this->booted) {
+     $container = $this->container;
+    }
+
     // If the module list hasn't already been set in updateModules and we are
     // not forcing a rebuild, then try and load the container from the disk.
     if (empty($this->moduleList) && !$this->containerNeedsRebuild) {
@@ -760,6 +786,7 @@ protected function initializeContainer() {
       }
     }
 
+    // If there is still no container, build a new one from scratch.
     if (!isset($container)) {
       $container = $this->compileContainer();
     }
@@ -1149,7 +1176,12 @@ protected function initializeServiceProviders() {
     );
     foreach ($this->serviceProviderClasses as $origin => $classes) {
       foreach ($classes as $name => $class) {
-        $this->serviceProviders[$origin][$name] = new $class;
+        if (!is_object($class)) {
+          $this->serviceProviders[$origin][$name] = new $class;
+        }
+        else {
+          $this->serviceProviders[$origin][$name] = $class;
+        }
       }
     }
   }
diff --git a/core/lib/Drupal/Core/DrupalKernelInterface.php b/core/lib/Drupal/Core/DrupalKernelInterface.php
index 892952a..12c32f6 100644
--- a/core/lib/Drupal/Core/DrupalKernelInterface.php
+++ b/core/lib/Drupal/Core/DrupalKernelInterface.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core;
 
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
 use Symfony\Component\HttpKernel\HttpKernelInterface;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -16,7 +17,7 @@
  * This interface extends Symfony's KernelInterface and adds methods for
  * responding to modules being enabled or disabled during its lifetime.
  */
-interface DrupalKernelInterface extends HttpKernelInterface {
+interface DrupalKernelInterface extends HttpKernelInterface, ContainerAwareInterface {
 
   /**
    * Boots the current kernel.
diff --git a/core/lib/Drupal/Core/PhpStorage/PhpStorageFactory.php b/core/lib/Drupal/Core/PhpStorage/PhpStorageFactory.php
index 69598e1..5ca0971 100644
--- a/core/lib/Drupal/Core/PhpStorage/PhpStorageFactory.php
+++ b/core/lib/Drupal/Core/PhpStorage/PhpStorageFactory.php
@@ -52,7 +52,7 @@ static function get($name) {
       $configuration['bin'] = $name;
     }
     if (!isset($configuration['directory'])) {
-      $configuration['directory'] = DRUPAL_ROOT . '/' . PublicStream::basePath() . '/php';
+      $configuration['directory'] = PublicStream::basePath() . '/php';
     }
     return new $class($configuration);
   }
diff --git a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
index 5c60942..021c541 100644
--- a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
+++ b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
@@ -127,6 +127,15 @@ protected function getLocalPath($uri = NULL) {
       $uri = $this->uri;
     }
     $path = $this->getDirectoryPath() . '/' . $this->getTarget($uri);
+
+    // In PHPUnit tests, the base path for local streams may be a virtual
+    // filesystem stream wrapper URI, in which case this local stream acts like
+    // a proxy. realpath() is not supported by vfsStream, because a virtual
+    // file system does not have a real filepath.
+    if (strpos($path, 'vfs://') === 0) {
+      return $path;
+    }
+
     $realpath = realpath($path);
     if (!$realpath) {
       // This file does not yet exist.
diff --git a/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php b/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php
index d5bd0b9..9319cdd 100644
--- a/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php
+++ b/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php
@@ -331,10 +331,12 @@ public function getLangcodes() {
     if (empty($langcodes)) {
       $langcodes = array();
       // Collect languages included with CKEditor based on file listing.
-      $ckeditor_languages = new \GlobIterator(\Drupal::root() . '/core/assets/vendor/ckeditor/lang/*.js');
-      foreach ($ckeditor_languages as $language_file) {
-        $langcode = $language_file->getBasename('.js');
-        $langcodes[$langcode] = $langcode;
+      $files = scandir('core/assets/vendor/ckeditor/lang');
+      foreach ($files as $file) {
+        if ($file[0] !== '.' && fnmatch('*.js', $file)) {
+          $langcode = basename($file, '.js');
+          $langcodes[$langcode] = $langcode;
+        }
       }
       \Drupal::cache()->set('ckeditor.langcodes', $langcodes);
     }
diff --git a/core/modules/simpletest/src/KernelTestBase.php b/core/modules/simpletest/src/KernelTestBase.php
index ba8622b..0961e9c 100644
--- a/core/modules/simpletest/src/KernelTestBase.php
+++ b/core/modules/simpletest/src/KernelTestBase.php
@@ -33,6 +33,9 @@
  * Additional modules needed in a test may be loaded and added to the fixed
  * module list.
  *
+ * @deprecated in Drupal 8.0.x, will be removed before Drupal 8.2.x. Use
+ *   \Drupal\Tests\KernelTestBase instead.
+ *
  * @see \Drupal\simpletest\KernelTestBase::$modules
  * @see \Drupal\simpletest\KernelTestBase::enableModules()
  *
diff --git a/core/modules/simpletest/src/RandomGeneratorTrait.php b/core/modules/simpletest/src/RandomGeneratorTrait.php
new file mode 100644
index 0000000..70087ce
--- /dev/null
+++ b/core/modules/simpletest/src/RandomGeneratorTrait.php
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\simpletest\RandomGeneratorTrait.
+ */
+
+namespace Drupal\simpletest;
+
+use Drupal\Component\Utility\Random;
+
+/**
+ * Provides random generator utility methods.
+ */
+trait RandomGeneratorTrait {
+
+  /**
+   * The random generator.
+   *
+   * @var \Drupal\Component\Utility\Random
+   */
+  protected $randomGenerator;
+
+  /**
+   * Generates a pseudo-random string of ASCII characters of codes 32 to 126.
+   *
+   * Do not use this method when special characters are not possible (e.g., in
+   * machine or file names that have already been validated); instead, use
+   * \Drupal\simpletest\TestBase::randomMachineName(). If $length is greater
+   * than 3 the random string will include at least one ampersand ('&') and
+   * at least one greater than ('>') character to ensure coverage for special
+   * characters and avoid the introduction of random test failures.
+   *
+   * @param int $length
+   *   Length of random string to generate.
+   *
+   * @return string
+   *   Pseudo-randomly generated unique string including special characters.
+   *
+   * @see \Drupal\Component\Utility\Random::string()
+   */
+  public function randomString($length = 8) {
+    if ($length < 4) {
+      return $this->getRandomGenerator()->string($length, TRUE, array($this, 'randomStringValidate'));
+    }
+
+    // To prevent the introduction of random test failures, ensure that the
+    // returned string contains a character that needs to be escaped in HTML by
+    // injecting an ampersand into it.
+    $replacement_pos = floor($length / 2);
+    // Remove 2 from the length to account for the ampersand and greater than
+    // characters.
+    $string = $this->getRandomGenerator()->string($length - 2, TRUE, array($this, 'randomStringValidate'));
+    return substr_replace($string, '>&', $replacement_pos, 0);
+  }
+
+  /**
+   * Callback for random string validation.
+   *
+   * @see \Drupal\Component\Utility\Random::string()
+   *
+   * @param string $string
+   *   The random string to validate.
+   *
+   * @return bool
+   *   TRUE if the random string is valid, FALSE if not.
+   */
+  public function randomStringValidate($string) {
+    // Consecutive spaces causes issues for
+    // \Drupal\simpletest\WebTestBase::assertLink().
+    if (preg_match('/\s{2,}/', $string)) {
+      return FALSE;
+    }
+
+    // Starting or ending with a space means that length might not be what is
+    // expected.
+    if (preg_match('/^\s|\s$/', $string)) {
+      return FALSE;
+    }
+
+    return TRUE;
+  }
+
+  /**
+   * Generates a unique random string containing letters and numbers.
+   *
+   * Do not use this method when testing unvalidated user input. Instead, use
+   * \Drupal\simpletest\TestBase::randomString().
+   *
+   * @param int $length
+   *   Length of random string to generate.
+   *
+   * @return string
+   *   Randomly generated unique string.
+   *
+   * @see \Drupal\Component\Utility\Random::name()
+   */
+  protected function randomMachineName($length = 8) {
+    return $this->getRandomGenerator()->name($length, TRUE);
+  }
+
+  /**
+   * Generates a random PHP object.
+   *
+   * @param int $size
+   *   The number of random keys to add to the object.
+   *
+   * @return \stdClass
+   *   The generated object, with the specified number of random keys. Each key
+   *   has a random string value.
+   *
+   * @see \Drupal\Component\Utility\Random::object()
+   */
+  public function randomObject($size = 4) {
+    return $this->getRandomGenerator()->object($size);
+  }
+
+  /**
+   * Gets the random generator for the utility methods.
+   *
+   * @return \Drupal\Component\Utility\Random
+   *   The random generator.
+   */
+  protected function getRandomGenerator() {
+    if (!is_object($this->randomGenerator)) {
+      $this->randomGenerator = new Random();
+    }
+    return $this->randomGenerator;
+  }
+
+}
diff --git a/core/modules/simpletest/src/TestBase.php b/core/modules/simpletest/src/TestBase.php
index aa5ed49..5e76459 100644
--- a/core/modules/simpletest/src/TestBase.php
+++ b/core/modules/simpletest/src/TestBase.php
@@ -28,6 +28,7 @@
 abstract class TestBase {
 
   use SessionTestTrait;
+  use RandomGeneratorTrait;
 
   /**
    * The test run ID.
@@ -284,13 +285,6 @@
   protected $configImporter;
 
   /**
-   * The random generator.
-   *
-   * @var \Drupal\Component\Utility\Random
-   */
-  protected $randomGenerator;
-
-  /**
    * Set to TRUE to strict check all configuration saved.
    *
    * @see \Drupal\Core\Config\Testing\ConfigSchemaChecker
@@ -1418,113 +1412,6 @@ protected function settingsSet($name, $value) {
   }
 
   /**
-   * Generates a pseudo-random string of ASCII characters of codes 32 to 126.
-   *
-   * Do not use this method when special characters are not possible (e.g., in
-   * machine or file names that have already been validated); instead, use
-   * \Drupal\simpletest\TestBase::randomMachineName(). If $length is greater
-   * than 3 the random string will include at least one ampersand ('&') and
-   * at least one greater than ('>') character to ensure coverage for special
-   * characters and avoid the introduction of random test failures.
-   *
-   * @param int $length
-   *   Length of random string to generate.
-   *
-   * @return string
-   *   Pseudo-randomly generated unique string including special characters.
-   *
-   * @see \Drupal\Component\Utility\Random::string()
-   */
-  public function randomString($length = 8) {
-    if ($length < 4) {
-      return $this->getRandomGenerator()->string($length, TRUE, array($this, 'randomStringValidate'));
-    }
-
-    // To prevent the introduction of random test failures, ensure that the
-    // returned string contains a character that needs to be escaped in HTML by
-    // injecting an ampersand into it.
-    $replacement_pos = floor($length / 2);
-    // Remove 2 from the length to account for the ampersand and greater than
-    // characters.
-    $string = $this->getRandomGenerator()->string($length - 2, TRUE, array($this, 'randomStringValidate'));
-    return substr_replace($string, '>&', $replacement_pos, 0);
-  }
-
-  /**
-   * Callback for random string validation.
-   *
-   * @see \Drupal\Component\Utility\Random::string()
-   *
-   * @param string $string
-   *   The random string to validate.
-   *
-   * @return bool
-   *   TRUE if the random string is valid, FALSE if not.
-   */
-  public function randomStringValidate($string) {
-    // Consecutive spaces causes issues for
-    // Drupal\simpletest\WebTestBase::assertLink().
-    if (preg_match('/\s{2,}/', $string)) {
-      return FALSE;
-    }
-
-    // Starting or ending with a space means that length might not be what is
-    // expected.
-    if (preg_match('/^\s|\s$/', $string)) {
-      return FALSE;
-    }
-
-    return TRUE;
-  }
-
-  /**
-   * Generates a unique random string containing letters and numbers.
-   *
-   * Do not use this method when testing unvalidated user input. Instead, use
-   * \Drupal\simpletest\TestBase::randomString().
-   *
-   * @param int $length
-   *   Length of random string to generate.
-   *
-   * @return string
-   *   Randomly generated unique string.
-   *
-   * @see \Drupal\Component\Utility\Random::name()
-   */
-  public function randomMachineName($length = 8) {
-    return $this->getRandomGenerator()->name($length, TRUE);
-  }
-
-  /**
-   * Generates a random PHP object.
-   *
-   * @param int $size
-   *   The number of random keys to add to the object.
-   *
-   * @return \stdClass
-   *   The generated object, with the specified number of random keys. Each key
-   *   has a random string value.
-   *
-   * @see \Drupal\Component\Utility\Random::object()
-   */
-  public function randomObject($size = 4) {
-    return $this->getRandomGenerator()->object($size);
-  }
-
-  /**
-   * Gets the random generator for the utility methods.
-   *
-   * @return \Drupal\Component\Utility\Random
-   *   The random generator
-   */
-  protected function getRandomGenerator() {
-    if (!is_object($this->randomGenerator)) {
-      $this->randomGenerator = new Random();
-    }
-    return $this->randomGenerator;
-  }
-
-  /**
    * Converts a list of possible parameters into a stack of permutations.
    *
    * Takes a list of parameters containing possible values, and converts all of
diff --git a/core/modules/simpletest/src/TestDiscovery.php b/core/modules/simpletest/src/TestDiscovery.php
index f71027d..630cca7 100644
--- a/core/modules/simpletest/src/TestDiscovery.php
+++ b/core/modules/simpletest/src/TestDiscovery.php
@@ -81,6 +81,7 @@ public function registerTestNamespaces() {
 
     // Add PHPUnit test namespaces of Drupal core.
     $this->testNamespaces['Drupal\\Tests\\'] = [DRUPAL_ROOT . '/core/tests/Drupal/Tests'];
+    $this->testNamespaces['Drupal\\KernelTests\\'] = [DRUPAL_ROOT . '/core/tests/Drupal/KernelTests'];
     $this->testNamespaces['Drupal\\FunctionalTests\\'] = [DRUPAL_ROOT . '/core/tests/Drupal/FunctionalTests'];
 
     $this->availableExtensions = array();
@@ -98,6 +99,7 @@ public function registerTestNamespaces() {
 
       // Add PHPUnit test namespaces.
       $this->testNamespaces["Drupal\\Tests\\$name\\Unit\\"][] = "$base_path/tests/src/Unit";
+      $this->testNamespaces["Drupal\\Tests\\$name\\Kernel\\"][] = "$base_path/tests/src/Kernel";
       $this->testNamespaces["Drupal\\Tests\\$name\\Functional\\"][] = "$base_path/tests/src/Functional";
     }
 
diff --git a/core/modules/simpletest/tests/src/Unit/TestBaseTest.php b/core/modules/simpletest/tests/src/Unit/TestBaseTest.php
index 1b2cb6b..bc2f2fe 100644
--- a/core/modules/simpletest/tests/src/Unit/TestBaseTest.php
+++ b/core/modules/simpletest/tests/src/Unit/TestBaseTest.php
@@ -117,7 +117,7 @@ public function testRandomString($length) {
     $this->assertEquals($length, strlen($string));
     // randomString() should always include an ampersand ('&')  and a
     // greater than ('>') if $length is greater than 3.
-    if ($length > 3) {
+    if ($length > 4) {
       $this->assertContains('&', $string);
       $this->assertContains('>', $string);
     }
diff --git a/core/modules/system/src/Tests/Extension/ModuleHandlerTest.php b/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php
similarity index 91%
rename from core/modules/system/src/Tests/Extension/ModuleHandlerTest.php
rename to core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php
index 7435949..c7e0729 100644
--- a/core/modules/system/src/Tests/Extension/ModuleHandlerTest.php
+++ b/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php
@@ -5,11 +5,11 @@
  * Contains \Drupal\system\Tests\Extension\ModuleHandlerTest.
  */
 
-namespace Drupal\system\Tests\Extension;
+namespace Drupal\Tests\system\Kernel\Extension;
 
 use Drupal\Core\DependencyInjection\ContainerBuilder;
-use Drupal\simpletest\KernelTestBase;
 use \Drupal\Core\Extension\ModuleUninstallValidatorException;
+use Drupal\Tests\KernelTestBase;
 
 /**
  * Tests ModuleHandler functionality.
@@ -21,10 +21,13 @@ class ModuleHandlerTest extends KernelTestBase {
   /**
    * {@inheritdoc}
    */
-  public static $modules = array('system');
-
-  public function setUp() {
+  protected function setUp() {
     parent::setUp();
+
+    // @todo ModuleInstaller calls system_rebuild_module_data which is part of
+    //   system.module, see https://www.drupal.org/node/2208429.
+    include_once $this->root . '/core/modules/system/system.module';
+
     // Set up the state values so we know where to find the files when running
     // drupal_get_filename().
     // @todo Remove as part of https://www.drupal.org/node/2186491
@@ -34,8 +37,8 @@ public function setUp() {
   /**
    * {@inheritdoc}
    */
-  public function containerBuild(ContainerBuilder $container) {
-    parent::containerBuild($container);
+  public function register(ContainerBuilder $container) {
+    parent::register($container);
     // Put a fake route bumper on the container to be called during uninstall.
     $container
       ->register('router.dumper', 'Drupal\Core\Routing\NullMatcherDumper');
@@ -45,24 +48,9 @@ public function containerBuild(ContainerBuilder $container) {
    * The basic functionality of retrieving enabled modules.
    */
   function testModuleList() {
-    // Prime the drupal_get_filename() static cache with the location of the
-    // testing profile as it is not the currently active profile and we don't
-    // yet have any cached way to retrieve its location.
-    // @todo Remove as part of https://www.drupal.org/node/2186491
-    drupal_get_filename('profile', 'testing', 'core/profiles/testing/testing.info.yml');
-    // Build a list of modules, sorted alphabetically.
-    $profile_info = install_profile_info('testing', 'en');
-    $module_list = $profile_info['dependencies'];
+    $module_list = array();
 
-    // Installation profile is a module that is expected to be loaded.
-    $module_list[] = 'testing';
-
-    sort($module_list);
-    // Compare this list to the one returned by the module handler. We expect
-    // them to match, since all default profile modules have a weight equal to 0
-    // (except for block.module, which has a lower weight but comes first in
-    // the alphabet anyway).
-    $this->assertModuleList($module_list, 'Testing profile');
+    $this->assertModuleList($module_list, 'Initial');
 
     // Try to install a new module.
     $this->moduleInstaller()->install(array('ban'));
@@ -98,7 +86,6 @@ function testModuleList() {
   protected function assertModuleList(Array $expected_values, $condition) {
     $expected_values = array_values(array_unique($expected_values));
     $enabled_modules = array_keys($this->container->get('module_handler')->getModuleList());
-    $enabled_modules = sort($enabled_modules);
     $this->assertEqual($expected_values, $enabled_modules, format_string('@condition: extension handler returns correct results', array('@condition' => $condition)));
   }
 
@@ -196,7 +183,7 @@ function testDependencyResolution() {
   function testUninstallProfileDependency() {
     $profile = 'minimal';
     $dependency = 'dblog';
-    $this->settingsSet('install_profile', $profile);
+    $this->setSetting('install_profile', $profile);
     // Prime the drupal_get_filename() static cache with the location of the
     // minimal profile as it is not the currently active profile and we don't
     // yet have any cached way to retrieve its location.
diff --git a/core/modules/system/src/Tests/PhpStorage/PhpStorageFactoryTest.php b/core/modules/system/tests/src/Kernel/PhpStorage/PhpStorageFactoryTest.php
similarity index 86%
rename from core/modules/system/src/Tests/PhpStorage/PhpStorageFactoryTest.php
rename to core/modules/system/tests/src/Kernel/PhpStorage/PhpStorageFactoryTest.php
index 580bfa1..5f866c0 100644
--- a/core/modules/system/src/Tests/PhpStorage/PhpStorageFactoryTest.php
+++ b/core/modules/system/tests/src/Kernel/PhpStorage/PhpStorageFactoryTest.php
@@ -5,14 +5,14 @@
  * Contains \Drupal\system\Tests\PhpStorage\PhpStorageFactoryTest.
  */
 
-namespace Drupal\system\Tests\PhpStorage;
+namespace Drupal\Tests\system\Kernel\PhpStorage;
 
 use Drupal\Component\PhpStorage\MTimeProtectedFileStorage;
 use Drupal\Core\PhpStorage\PhpStorageFactory;
 use Drupal\Core\Site\Settings;
 use Drupal\Core\StreamWrapper\PublicStream;
-use Drupal\simpletest\KernelTestBase;
 use Drupal\system\PhpStorage\MockPhpStorage;
+use Drupal\Tests\KernelTestBase;
 
 /**
  * Tests the PHP storage factory.
@@ -23,6 +23,18 @@
 class PhpStorageFactoryTest extends KernelTestBase {
 
   /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // Empty the PHP storage settings, as KernelTestBase sets it by default.
+    $settings = Settings::getAll();
+    unset($settings['php_storage']);
+    new Settings($settings);
+  }
+
+  /**
    * Tests the get() method with no settings.
    */
   public function testGetNoSettings() {
@@ -59,7 +71,7 @@ public function testGetOverride() {
     $this->setSettings('test', array('directory' => NULL));
     $php = PhpStorageFactory::get('test');
     $this->assertTrue($php instanceof MockPhpStorage, 'An MockPhpStorage instance was returned from overridden settings.');
-    $this->assertIdentical(\Drupal::root() . '/' . PublicStream::basePath() . '/php', $php->getConfigurationValue('directory'), 'Default file directory was used.');
+    $this->assertIdentical(PublicStream::basePath() . '/php', $php->getConfigurationValue('directory'), 'Default file directory was used.');
 
     // Test that a default storage class is set if it's empty.
     $this->setSettings('test', array('class' => NULL));
diff --git a/core/phpunit.xml.dist b/core/phpunit.xml.dist
index 743bf08..15b6316 100644
--- a/core/phpunit.xml.dist
+++ b/core/phpunit.xml.dist
@@ -22,6 +22,16 @@
       <!-- Exclude Drush tests. -->
       <exclude>./drush/tests</exclude>
     </testsuite>
+    <testsuite name="kernel">
+      <directory>./tests/Drupal/KernelTests</directory>
+      <directory>./modules/*/tests/src/Kernel</directory>
+      <directory>../modules/*/tests/src/Kernel</directory>
+      <directory>../sites/*/modules/*/tests/src/Kernel</directory>
+      <!-- Exclude Composer's vendor directory so we don't run tests there. -->
+      <exclude>./vendor</exclude>
+      <!-- Exclude Drush tests. -->
+      <exclude>./drush/tests</exclude>
+    </testsuite>
     <testsuite name="functional">
       <directory>./tests/Drupal/FunctionalTests</directory>
       <directory>./modules/*/tests/src/Functional</directory>
diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh
index 0ccdc35..0755cf2 100755
--- a/core/scripts/run-tests.sh
+++ b/core/scripts/run-tests.sh
@@ -709,7 +709,7 @@ function simpletest_script_command($test_id, $test_class) {
  * @see simpletest_script_run_one_test()
  */
 function simpletest_script_cleanup($test_id, $test_class, $exitcode) {
-  if (strpos($test_class, 'Drupal\\Tests\\') === 0) {
+  if (is_subclass_of($test_class, '\PHPUnit_Framework_TestCase')) {
     // PHPUnit test, move on.
     return;
   }
diff --git a/core/tests/Drupal/KernelTests/KernelTestBaseTest.php b/core/tests/Drupal/KernelTests/KernelTestBaseTest.php
new file mode 100644
index 0000000..998cd1e
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/KernelTestBaseTest.php
@@ -0,0 +1,216 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\KernelTestBaseTest.
+ */
+
+namespace Drupal\KernelTests;
+
+use Drupal\Tests\KernelTestBase;
+use org\bovigo\vfs\vfsStream;
+use org\bovigo\vfs\visitor\vfsStreamStructureVisitor;
+
+/**
+ * @coversDefaultClass \Drupal\Tests\KernelTestBase
+ * @group PHPUnit
+ */
+class KernelTestBaseTest extends KernelTestBase {
+
+  /**
+   * @covers ::setUpBeforeClass
+   */
+  public function testSetUpBeforeClass() {
+    // Note: PHPUnit automatically restores the original working directory.
+    $this->assertSame(realpath(__DIR__ . '/../../../../'), getcwd());
+  }
+
+  /**
+   * @covers ::bootEnvironment
+   */
+  public function testBootEnvironment() {
+    $this->assertRegExp('/^simpletest\d{6}$/', $this->databasePrefix);
+    $this->assertStringStartsWith('vfs://root/sites/simpletest/', $this->siteDirectory);
+    $this->assertEquals(array(
+      'root' => array(
+        'sites' => array(
+          'simpletest' => array(
+            substr($this->databasePrefix, 10) => array(
+              'files' => array(
+                'config' => array(
+                  'active' => array(),
+                  'staging' => array(),
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    ), vfsStream::inspect(new vfsStreamStructureVisitor())->getStructure());
+  }
+
+  /**
+   * @covers ::getDatabaseConnectionInfo
+   */
+  public function testGetDatabaseConnectionInfoWithOutManualSetDbUrl() {
+    $this->setUp();
+
+    $options = $this->container->get('database')->getConnectionOptions();
+    $this->assertSame($this->databasePrefix, $options['prefix']['default']);
+  }
+
+  /**
+   * @covers ::getDatabaseConnectionInfo
+   */
+  public function testGetDatabaseConnectionInfoWithManualSetDbUrl() {
+    if (!file_exists('/tmp')) {
+      $this->markTestSkipped();
+    }
+    putenv('SIMPLETEST_DB=sqlite://localhost//tmp/test2.sqlite');
+    $this->setUp();
+
+    $options = $this->container->get('database')->getConnectionOptions();
+    $this->assertNotEqual('', $options['prefix']['default']);
+  }
+
+  /**
+   * @covers ::setUp
+   */
+  public function testSetUp() {
+    $this->assertTrue($this->container->has('request_stack'));
+    $this->assertTrue($this->container->initialized('request_stack'));
+    $request = $this->container->get('request_stack')->getCurrentRequest();
+    $this->assertNotEmpty($request);
+    $this->assertEquals('/', $request->getPathInfo());
+
+    $this->assertSame($request, \Drupal::request());
+
+    $this->assertEquals($this, $GLOBALS['conf']['container_service_providers']['test']);
+
+    $GLOBALS['destroy-me'] = TRUE;
+    $this->assertArrayHasKey('destroy-me', $GLOBALS);
+
+    $schema = $this->container->get('database')->schema();
+    $schema->createTable('foo', array(
+      'fields' => array(
+        'number' => array(
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+        ),
+      ),
+    ));
+    $this->assertTrue($schema->tableExists('foo'));
+  }
+
+  /**
+   * @covers ::setUp
+   * @depends testSetUp
+   */
+  public function testSetUpDoesNotLeak() {
+    $this->assertArrayNotHasKey('destroy-me', $GLOBALS);
+
+    // Ensure that we have a different database prefix.
+    $schema = $this->container->get('database')->schema();
+    $this->assertFalse($schema->tableExists('foo'));
+  }
+
+  /**
+   * @covers ::register
+   */
+  public function testRegister() {
+    // Verify that this container is identical to the actual container.
+    $this->assertInstanceOf('Symfony\Component\DependencyInjection\ContainerInterface', $this->container);
+    $this->assertSame($this->container, \Drupal::getContainer());
+
+    // The request service should never exist.
+    $this->assertFalse($this->container->has('request'));
+
+    // Verify that there is a request stack.
+    $request = $this->container->get('request_stack')->getCurrentRequest();
+    $this->assertInstanceOf('Symfony\Component\HttpFoundation\Request', $request);
+    $this->assertSame($request, \Drupal::request());
+
+    // Trigger a container rebuild.
+    $this->enableModules(array('system'));
+
+    // Verify that this container is identical to the actual container.
+    $this->assertInstanceOf('Symfony\Component\DependencyInjection\ContainerInterface', $this->container);
+    $this->assertSame($this->container, \Drupal::getContainer());
+
+    // The request service should never exist.
+    $this->assertFalse($this->container->has('request'));
+
+    // Verify that there is a request stack (and that it persisted).
+    $new_request = $this->container->get('request_stack')->getCurrentRequest();
+    $this->assertInstanceOf('Symfony\Component\HttpFoundation\Request', $new_request);
+    $this->assertSame($new_request, \Drupal::request());
+    $this->assertSame($request, $new_request);
+  }
+
+  /**
+   * @covers ::getCompiledContainerBuilder
+   */
+  public function testCompiledContainer() {
+    $this->enableModules(['system', 'user']);
+    $this->installConfig('user');
+  }
+
+  /**
+   * @covers ::getCompiledContainerBuilder
+   * @depends testCompiledContainer
+   */
+  public function testCompiledContainerIsDestructed() {
+    $this->enableModules(['system', 'user']);
+    $this->installConfig('user');
+  }
+
+  /**
+   * @covers ::render
+   */
+  public function testRender() {
+    $type = 'processed_text';
+    $element_info = $this->container->get('element_info');
+    $this->assertSame(['#defaults_loaded' => TRUE], $element_info->getInfo($type));
+
+    $this->enableModules(array('filter'));
+
+    $this->assertNotSame($element_info, $this->container->get('element_info'));
+    $this->assertNotEmpty($this->container->get('element_info')->getInfo($type));
+
+    $build = array(
+      '#type' => 'html_tag',
+      '#tag' => 'h3',
+      '#value' => 'Inner',
+    );
+    $expected = "<h3>Inner</h3>\n";
+
+    $this->assertEquals('core', \Drupal::theme()->getActiveTheme()->getName());
+    $output = \Drupal::service('renderer')->renderRoot($build);
+    $this->assertEquals('core', \Drupal::theme()->getActiveTheme()->getName());
+
+    $this->assertEquals($expected, $build['#children']);
+    $this->assertEquals($expected, $output);
+  }
+
+  /**
+   * @covers ::render
+   */
+  public function testRenderWithTheme() {
+    $this->enableModules(array('system'));
+
+    $build = array(
+      '#type' => 'textfield',
+      '#name' => 'test',
+    );
+    $expected = '/' . preg_quote('<input type="text" name="test"', '/') . '/';
+
+    $this->assertArrayNotHasKey('theme', $GLOBALS);
+    $output = \Drupal::service('renderer')->renderRoot($build);
+    $this->assertEquals('core', \Drupal::theme()->getActiveTheme()->getName());
+
+    $this->assertRegExp($expected, (string) $build['#children']);
+    $this->assertRegExp($expected, (string) $output);
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/AssertLegacyTrait.php b/core/tests/Drupal/Tests/AssertLegacyTrait.php
new file mode 100644
index 0000000..03e25e7
--- /dev/null
+++ b/core/tests/Drupal/Tests/AssertLegacyTrait.php
@@ -0,0 +1,129 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\AssertLegacyTrait.
+ */
+
+namespace Drupal\Tests;
+
+/**
+ * Translates Simpletest assertion methods to PHPUnit.
+ *
+ * Protected methods are custom. Public static methods override methods of
+ * \PHPUnit_Framework_Assert.
+ *
+ * @deprecated Scheduled for removal in Drupal 9.0.0. Use PHPUnit's native
+ *   assert methods instead.
+ */
+trait AssertLegacyTrait {
+
+  /**
+   * @see \Drupal\simpletest\TestBase::assert()
+   *
+   * @deprecated Scheduled for removal in Drupal 9.0.0. Use self::assertTrue()
+   *   instead.
+   */
+  protected function assert($actual, $message = '') {
+    parent::assertTrue((bool) $actual, $message);
+  }
+
+  /**
+   * @see \Drupal\simpletest\TestBase::assertTrue()
+   */
+  public static function assertTrue($actual, $message = '') {
+    if (is_bool($actual)) {
+      parent::assertTrue($actual, $message);
+    }
+    else {
+      parent::assertNotEmpty($actual, $message);
+    }
+  }
+
+  /**
+   * @see \Drupal\simpletest\TestBase::assertFalse()
+   */
+  public static function assertFalse($actual, $message = '') {
+    if (is_bool($actual)) {
+      parent::assertFalse($actual, $message);
+    }
+    else {
+      parent::assertEmpty($actual, $message);
+    }
+  }
+
+  /**
+   * @see \Drupal\simpletest\TestBase::assertEqual()
+   *
+   * @deprecated Scheduled for removal in Drupal 9.0.0. Use self::assertEquals()
+   *   instead.
+   */
+  protected function assertEqual($actual, $expected, $message = '') {
+    $this->assertEquals($expected, $actual, $message);
+  }
+
+  /**
+   * @see \Drupal\simpletest\TestBase::assertNotEqual()
+   *
+   * @deprecated Scheduled for removal in Drupal 9.0.0. Use
+   *   self::assertNotEquals() instead.
+   */
+  protected function assertNotEqual($actual, $expected, $message = '') {
+    $this->assertNotEquals($expected, $actual, $message);
+  }
+
+  /**
+   * @see \Drupal\simpletest\TestBase::assertIdentical()
+   *
+   * @deprecated Scheduled for removal in Drupal 9.0.0. Use self::assertSame()
+   *   instead.
+   */
+  protected function assertIdentical($actual, $expected, $message = '') {
+    $this->assertSame($expected, $actual, $message);
+  }
+
+  /**
+   * @see \Drupal\simpletest\TestBase::assertNotIdentical()
+   *
+   * @deprecated Scheduled for removal in Drupal 9.0.0. Use
+   *   self::assertNotSame() instead.
+   */
+  protected function assertNotIdentical($actual, $expected, $message = '') {
+    $this->assertNotSame($expected, $actual, $message);
+  }
+
+  /**
+   * @see \Drupal\simpletest\TestBase::assertIdenticalObject()
+   *
+   * @deprecated Scheduled for removal in Drupal 9.0.0. Use self::assertEquals()
+   *   instead.
+   */
+  protected function assertIdenticalObject($actual, $expected, $message = '') {
+    // Note: ::assertSame checks whether its the same object. ::assertEquals
+    // though compares
+
+    $this->assertEquals($expected, $actual, $message);
+  }
+
+  /**
+   * @see \Drupal\simpletest\TestBase::pass()
+   *
+   * @deprecated Scheduled for removal in Drupal 9.0.0. Use self::assertTrue()
+   *   instead.
+   */
+  protected function pass($message) {
+    $this->assertTrue(TRUE, $message);
+  }
+
+  /**
+   * @see \Drupal\simpletest\TestBase::verbose()
+   */
+  protected function verbose($message) {
+    if (in_array('--debug', $_SERVER['argv'], TRUE)) {
+      // Write directly to STDOUT to not produce unexpected test output.
+      // The STDOUT stream does not obey output buffering.
+      fwrite(STDOUT, $message . "\n");
+    }
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/KernelTestBase.php b/core/tests/Drupal/Tests/KernelTestBase.php
new file mode 100644
index 0000000..8d5f56d
--- /dev/null
+++ b/core/tests/Drupal/Tests/KernelTestBase.php
@@ -0,0 +1,1051 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\KernelTestBase.
+ */
+
+namespace Drupal\Tests;
+
+use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Config\ConfigImporter;
+use Drupal\Core\Config\StorageComparer;
+use Drupal\Core\Config\StorageInterface;
+use Drupal\Core\Database\Database;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\DependencyInjection\ServiceProviderInterface;
+use Drupal\Core\DrupalKernel;
+use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
+use Drupal\Core\Extension\ExtensionDiscovery;
+use Drupal\Core\Site\Settings;
+use Drupal\simpletest\AssertContentTrait;
+use Drupal\simpletest\RandomGeneratorTrait;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\HttpFoundation\Request;
+use org\bovigo\vfs\vfsStream;
+use org\bovigo\vfs\visitor\vfsStreamPrintVisitor;
+
+/**
+ * Base class for functional integration tests.
+ *
+ * Tests extending this base class can access files and the database, but the
+ * entire environment is initially empty. Drupal runs in a minimal mocked
+ * environment, comparable to the one in the early installer.
+ *
+ * Unlike \Drupal\Tests\UnitTestCase, modules specified in the $modules
+ * property are automatically added to the service container for each test.
+ * The module/hook system is functional and operates on a fixed module list.
+ * Additional modules needed in a test may be loaded and added to the fixed
+ * module list.
+ *
+ * Unlike \Drupal\simpletest\WebTestBase, the modules are only loaded, but not
+ * installed. Modules have to be installed manually, if needed.
+ *
+ * @see \Drupal\Tests\KernelTestBase::$modules
+ * @see \Drupal\Tests\KernelTestBase::enableModules()
+ *
+ * @todo Extend ::setRequirementsFromAnnotation() and ::checkRequirements() to
+ *   account for '@requires module'.
+ */
+abstract class KernelTestBase extends \PHPUnit_Framework_TestCase implements ServiceProviderInterface {
+
+  use AssertLegacyTrait;
+  use AssertContentTrait;
+  use RandomGeneratorTrait;
+
+  /**
+   * {@inheritdoc}
+   *
+   * Back up and restore any global variables that may be changed by tests.
+   *
+   * @see self::runTestInSeparateProcess
+   */
+  protected $backupGlobals = TRUE;
+
+  /**
+   * {@inheritdoc}
+   *
+   * Kernel tests are run in separate processes to prevent collisions between
+   * code that may be loaded by tests.
+   */
+  protected $runTestInSeparateProcess = TRUE;
+
+  /**
+   * {@inheritdoc}
+   *
+   * Back up and restore static class properties that may be changed by tests.
+   *
+   * @see self::runTestInSeparateProcess
+   */
+  protected $backupStaticAttributes = TRUE;
+
+  /**
+   * {@inheritdoc}
+   *
+   * Contains a few static class properties for performance.
+   */
+  protected $backupStaticAttributesBlacklist = [
+    // Ignore static discovery/parser caches to speed up tests.
+    'Drupal\Component\Discovery\YamlDiscovery' => ['parsedFiles'],
+    'Drupal\Core\DependencyInjection\YamlFileLoader' => ['yaml'],
+    'Drupal\Core\Extension\ExtensionDiscovery' => ['files'],
+    'Drupal\Core\Extension\InfoParser' => ['parsedInfos'],
+    // Drupal::$container cannot be serialized.
+    'Drupal' => ['container'],
+    // Settings cannot be serialized.
+    'Drupal\Core\Site\Settings' => ['instance'],
+  ];
+
+  /**
+   * {@inheritdoc}
+   *
+   * Do not forward any global state from the parent process to the processes
+   * that run the actual tests.
+   *
+   * @see self::runTestInSeparateProcess
+   */
+  protected $preserveGlobalState = FALSE;
+
+  /**
+   * @var \Composer\Autoload\Classloader
+   */
+  protected $classLoader;
+
+  /**
+   * @var string
+   */
+  protected $siteDirectory;
+
+  /**
+   * @var string
+   */
+  protected $databasePrefix;
+
+  /**
+   * @var \Drupal\Core\DependencyInjection\ContainerBuilder
+   */
+  protected $container;
+
+  /**
+   * @var \Drupal\Core\DependencyInjection\ContainerBuilder
+   */
+  private static $initialContainerBuilder;
+
+  /**
+   * Modules to enable.
+   *
+   * Test classes extending this class, and any classes in the hierarchy up to
+   * this class, may specify individual lists of modules to enable by setting
+   * this property. The values of all properties in all classes in the class
+   * hierarchy are merged.
+   *
+   * @see \Drupal\Tests\KernelTestBase::enableModules()
+   * @see \Drupal\Tests\KernelTestBase::bootKernel()
+   *
+   * @var array
+   */
+  public static $modules = array();
+
+  /**
+   * The virtual filesystem root directory.
+   *
+   * @var \org\bovigo\vfs\vfsStreamDirectory
+   */
+  protected $vfsRoot;
+
+  /**
+   * @var int
+   */
+  protected $expectedLogSeverity;
+
+  /**
+   * @var string
+   */
+  protected $expectedLogMessage;
+
+  /**
+   * @todo Move into Config test base class.
+   * @var \Drupal\Core\Config\ConfigImporter
+   */
+  protected $configImporter;
+
+  /**
+   * The app root.
+   *
+   * @var string
+   */
+  protected $root;
+
+  /**
+   * Set to TRUE to strict check all configuration saved.
+   *
+   * @see \Drupal\Core\Config\Testing\ConfigSchemaChecker
+   *
+   * @var bool
+   */
+  protected $strictConfigSchema = TRUE;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function setUpBeforeClass() {
+    parent::setUpBeforeClass();
+
+    // Change the current dir to DRUPAL_ROOT.
+    chdir(static::getDrupalRoot());
+  }
+
+  /**
+   * Returns the drupal root directory.
+   *
+   * @return string
+   */
+  protected static function getDrupalRoot() {
+    return dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__))));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->root = static::getDrupalRoot();
+    $this->bootEnvironment();
+    $this->bootKernel();
+  }
+
+  /**
+   * Bootstraps a basic test environment.
+   *
+   * Should not be called by tests. Only visible for DrupalKernel integration
+   * tests.
+   *
+   * @see \Drupal\system\Tests\DrupalKernel\DrupalKernelTest
+   * @internal
+   */
+  protected function bootEnvironment() {
+    $this->streamWrappers = array();
+    \Drupal::unsetContainer();
+
+    // @see /core/tests/bootstrap.php
+    $this->classLoader = $GLOBALS['loader'];
+
+    require_once $this->root . '/core/includes/bootstrap.inc';
+
+    // Set up virtual filesystem.
+    // Ensure that the generated test site directory does not exist already,
+    // which may happen with a large amount of concurrent threads and
+    // long-running tests.
+    do {
+      $suffix = mt_rand(100000, 999999);
+      $this->siteDirectory = 'sites/simpletest/' . $suffix;
+      $this->databasePrefix = 'simpletest' . $suffix;
+    } while (is_dir($this->root . '/' . $this->siteDirectory));
+
+    $this->vfsRoot = vfsStream::setup('root', NULL, array(
+      'sites' => array(
+        'simpletest' => array(
+          $suffix => array(),
+        ),
+      ),
+    ));
+    $this->siteDirectory = vfsStream::url('root/sites/simpletest/' . $suffix);
+
+    mkdir($this->siteDirectory . '/files', 0775);
+    mkdir($this->siteDirectory . '/files/config/' . CONFIG_ACTIVE_DIRECTORY, 0775, TRUE);
+    mkdir($this->siteDirectory . '/files/config/' . CONFIG_STAGING_DIRECTORY, 0775, TRUE);
+
+    // Ensure that all code that relies on drupal_valid_test_ua() can still be
+    // safely executed. This primarily affects the (test) site directory
+    // resolution (used by e.g. LocalStream and PhpStorage).
+    $this->databasePrefix = 'simpletest' . $suffix;
+    drupal_valid_test_ua($this->databasePrefix);
+
+    $settings = array(
+      'hash_salt' => get_class($this),
+      'file_public_path' => $this->siteDirectory . '/files',
+      // Disable Twig template caching/dumping.
+      'twig_cache' => FALSE,
+      // @see \Drupal\Tests\KernelTestBase::register()
+    );
+    new Settings($settings);
+
+    $GLOBALS['config_directories'] = array(
+      CONFIG_ACTIVE_DIRECTORY => $this->siteDirectory . '/files/config/active',
+      CONFIG_STAGING_DIRECTORY => $this->siteDirectory . '/files/config/staging',
+    );
+
+    foreach (Database::getAllConnectionInfo() as $key => $targets) {
+      Database::removeConnection($key);
+    }
+    Database::addConnectionInfo('default', 'default', $this->getDatabaseConnectionInfo()['default']);
+  }
+
+  /**
+   * Bootstraps a kernel for a test.
+   */
+  private function bootKernel() {
+    $this->setSetting('container_yamls', []);
+    // Allow for test-specific overrides.
+    $settings_services_file = $this->root . '/sites/default' . '/testing.services.yml';
+    if (file_exists($settings_services_file)) {
+      // Copy the testing-specific service overrides in place.
+      $testing_services_file = $this->root . '/' . $this->siteDirectory . '/services.yml';
+      copy($settings_services_file, $testing_services_file);
+      $this->setSetting('container_yamls', [$testing_services_file]);
+    }
+
+    // Allow for global test environment overrides.
+    if (file_exists($test_env = $this->root . '/sites/default/testing.services.yml')) {
+      $GLOBALS['conf']['container_yamls']['testing'] = $test_env;
+    }
+    // Add this test class as a service provider.
+    $GLOBALS['conf']['container_service_providers']['test'] = $this;
+
+    $modules = self::getModulesToEnable(get_class($this));
+
+    // Prepare a precompiled container for all tests of this class.
+    // Substantially improves performance, since ContainerBuilder::compile()
+    // is very expensive. Encourages testing best practices (small tests).
+    // Normally a setUpBeforeClass() operation, but object scope is required to
+    // inject $this test class instance as a service provider (see above).
+    $rc = new \ReflectionClass(get_class($this));
+    $test_method_count = count(array_filter($rc->getMethods(), function ($method) {
+      // PHPUnit's @test annotations are intentionally ignored/not supported.
+      return strpos($method->getName(), 'test') === 0;
+    }));
+    if ($test_method_count > 1 && !$this->isTestInIsolation()) {
+      // Clone a precompiled, empty ContainerBuilder instance for each test.
+      $container = $this->getCompiledContainerBuilder($modules);
+    }
+
+    // Bootstrap the kernel. Do not use createFromRequest() to retain Settings.
+    $kernel = new DrupalKernel('testing', $this->classLoader, FALSE);
+    $kernel->setSitePath($this->siteDirectory);
+    // Boot the precompiled container. The kernel will enhance it with synthetic
+    // services.
+    if (isset($container)) {
+      $kernel->setContainer($container);
+      unset($container);
+    }
+    // Boot a new one-time container from scratch. Ensure to set the module list
+    // upfront to avoid a subsequent rebuild.
+    elseif ($modules && $extensions = $this->getExtensionsForModules($modules)) {
+      $kernel->updateModules($extensions, $extensions);
+    }
+    // DrupalKernel::boot() is not sufficient as it does not invoke preHandle(),
+    // which is required to initialize legacy global variables.
+    $request = Request::create('/');
+    $kernel->prepareLegacyRequest($request);
+
+    // register() is only called if a new container was built/compiled.
+    $this->container = $kernel->getContainer();
+
+    if ($modules) {
+      $this->container->get('module_handler')->loadAll();
+    }
+
+    // Write the core.extension configuration.
+    // Required for ConfigInstaller::installDefaultConfig() to work.
+    $this->container->get('config.storage')->write('core.extension', array(
+      'module' => array_fill_keys($modules, 0),
+      'theme' => array(),
+    ));
+
+    $settings = Settings::getAll();
+    $settings['php_storage']['default'] = [
+      'class' => '\Drupal\Component\PhpStorage\FileStorage',
+    ];
+    new Settings($settings);
+  }
+
+  /**
+   * Configuration accessor for tests. Returns non-overridden configuration.
+   *
+   * @param string $name
+   *   The configuration name.
+   *
+   * @return \Drupal\Core\Config\Config
+   *   The configuration object with original configuration data.
+   */
+  protected function config($name) {
+    return $this->container->get('config.factory')->getEditable($name);
+  }
+
+  /**
+   * Returns the Database connection info to be used for this test.
+   *
+   * This method only exists for tests of the Database component itself, because
+   * they require multiple database connections. Each SQLite :memory: connection
+   * creates a new/separate database in memory. A shared-memory SQLite file URI
+   * triggers PHP open_basedir/allow_url_fopen/allow_url_include restrictions.
+   * Due to that, Database tests are running against a SQLite database that is
+   * located in an actual file in the system's temporary directory.
+   *
+   * Other tests should not override this method.
+   *
+   * @return array
+   *   A Database connection info array.
+   *
+   * @internal
+   */
+  protected function getDatabaseConnectionInfo() {
+    // If the test is run with argument dburl then use it.
+    $db_url = getenv('SIMPLETEST_DB');
+    if (!empty($db_url)) {
+      $database = Database::convertDbUrlToConnectionInfo($db_url, $this->root);
+      Database::addConnectionInfo('default', 'default', $database);
+    }
+
+    // Clone the current connection and replace the current prefix.
+    $connection_info = Database::getConnectionInfo('default');
+    if (is_null($connection_info)) {
+      throw new \InvalidArgumentException('There is no database connection so no tests can be run. You must provide a SIMPLETEST_DB environment variable, like "sqlite://localhost//tmp/test.sqlite", to run PHPUnit based functional tests outside of run-tests.sh.');
+    }
+    else {
+      Database::renameConnection('default', 'simpletest_original_default');
+      foreach ($connection_info as $target => $value) {
+        // Replace the full table prefix definition to ensure that no table
+        // prefixes of the test runner leak into the test.
+        $connection_info[$target]['prefix'] = array(
+          'default' => $value['prefix']['default'] . $this->databasePrefix,
+        );
+      }
+    }
+    return $connection_info;
+  }
+
+  /**
+   * Prepares a precompiled ContainerBuilder for all tests of this class.
+   *
+   * Avoids repetitive calls to ContainerBuilder::compile(), which is very slow.
+   *
+   * Based on the (always identical) list of $modules to enable, an initial
+   * container is compiled, all instantiated services are reset/removed, and
+   * this precompiled container is stored in a static class property. (Static,
+   * because PHPUnit instantiates a new class instance for each test *method*.)
+   *
+   * This method is not invoked if there is only a single test method. It is
+   * also not invoked for tests running in process isolation (since each test
+   * method runs in a separate process).
+   *
+   * The ContainerBuilder is not dumped into the filesystem (which would yield
+   * an actually compiled Container class), because
+   *
+   * 1. PHP code cannot be unloaded, so e.g. 900 tests would load 900 different,
+   *    full Container classes into memory, quickly exceeding any sensible
+   *    memory consumption (GigaBytes).
+   * 2. Dumping a Container class requires to actually write to the system's
+   *    temporary directory. This is not really easy with vfs, because vfs
+   *    doesn't support yet "include 'vfs://container.php'.". Maybe we could fix
+   *    that upstream.
+   * 3. PhpDumper is very slow on its own.
+   *
+   * @param string[] $modules
+   *   The list of modules to enable.
+   *
+   * @return \Drupal\Core\DependencyInjection\ContainerBuilder
+   *   A clone of the precompiled, empty service container.
+   */
+  private function getCompiledContainerBuilder(array $modules) {
+    if (!isset(self::$initialContainerBuilder)) {
+      $kernel = new DrupalKernel('testing', $this->classLoader, FALSE);
+      $kernel->setSitePath($this->siteDirectory);
+      if ($modules && $extensions = $this->getExtensionsForModules($modules)) {
+        $kernel->updateModules($extensions, $extensions);
+      }
+      $kernel->boot();
+      self::$initialContainerBuilder = $kernel->getContainer();
+
+      // Remove all instantiated services, so the container is safe for cloning.
+      // Technically, ContainerBuilder::set($id, NULL) removes each definition,
+      // but the container is compiled/frozen already.
+      foreach (self::$initialContainerBuilder->getServiceIds() as $id) {
+        self::$initialContainerBuilder->set($id, NULL);
+      }
+
+      // Destruct and trigger garbage collection.
+      \Drupal::unsetContainer();
+      $kernel->shutdown();
+      $kernel = NULL;
+      // @see register()
+      $this->container = NULL;
+    }
+
+    $container = clone self::$initialContainerBuilder;
+
+    return $container;
+  }
+
+  /**
+   * Returns Extension objects for $modules to enable.
+   *
+   * @param string[] $modules
+   *   The list of modules to enable.
+   *
+   * @return \Drupal\Core\Extension\Extension[]
+   *   Extension objects for $modules, keyed by module name.
+   *
+   * @throws \PHPUnit_Framework_Exception
+   *   If a module is not available.
+   *
+   * @see \Drupal\Tests\KernelTestBase::enableModules()
+   * @see \Drupal\Core\Extension\ModuleHandler::add()
+   */
+  private function getExtensionsForModules(array $modules) {
+    $extensions = array();
+    $discovery = new ExtensionDiscovery($this->root);
+    $discovery->setProfileDirectories(array());
+    $list = $discovery->scan('module');
+    foreach ($modules as $name) {
+      if (!isset($list[$name])) {
+        throw new \PHPUnit_Framework_Exception("Unavailable module: '$name'. If this module needs to be downloaded separately, annotate the test class with '@requires module $name'.");
+      }
+      $extensions[$name] = $list[$name];
+    }
+    return $extensions;
+  }
+
+  /**
+   * Registers test-specific services.
+   *
+   * Extend this method in your test to register additional services. This
+   * method is called whenever the kernel is rebuilt.
+   *
+   * @param \Drupal\Core\DependencyInjection\ContainerBuilder $container
+   *   The service container to enhance.
+   *
+   * @see \Drupal\Tests\KernelTestBase::bootKernel()
+   */
+  public function register(ContainerBuilder $container) {
+    // Keep the container object around for tests.
+    $this->container = $container;
+
+    $container
+      ->register('flood', 'Drupal\Core\Flood\MemoryBackend')
+      ->addArgument(new Reference('request_stack'));
+    $container
+      ->register('lock', 'Drupal\Core\Lock\NullLockBackend');
+    $container
+      ->register('cache_factory', 'Drupal\Core\Cache\MemoryBackendFactory');
+    $container
+      ->register('keyvalue.memory', 'Drupal\Core\KeyValueStore\KeyValueMemoryFactory')
+      // Must persist container rebuilds, or all data would vanish otherwise.
+      ->addTag('persist');
+    $container
+      ->setAlias('keyvalue', 'keyvalue.memory');
+
+    if ($this->strictConfigSchema) {
+      $container
+        ->register('simpletest.config_schema_checker', 'Drupal\Core\Config\Testing\ConfigSchemaChecker')
+        ->addArgument(new Reference('config.typed'))
+        ->addTag('event_subscriber');
+    }
+
+    if ($container->hasDefinition('path_processor_alias')) {
+      // Prevent the alias-based path processor, which requires a url_alias db
+      // table, from being registered to the path processor manager. We do this
+      // by removing the tags that the compiler pass looks for. This means the
+      // url generator can safely be used within tests.
+      $container->getDefinition('path_processor_alias')
+        ->clearTag('path_processor_inbound')
+        ->clearTag('path_processor_outbound');
+    }
+
+    if ($container->hasDefinition('password')) {
+      $container->getDefinition('password')
+        ->setArguments(array(1));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function assertPostConditions() {
+    // Execute registered Drupal shutdown functions prior to tearing down.
+    // @see _drupal_shutdown_function()
+    $callbacks = &drupal_register_shutdown_function();
+    while ($callback = array_shift($callbacks)) {
+      call_user_func_array($callback['callback'], $callback['arguments']);
+    }
+
+    // Shut down the kernel (if bootKernel() was called).
+    // @see \Drupal\system\Tests\DrupalKernel\DrupalKernelTest
+    if ($this->container) {
+      $this->container->get('kernel')->shutdown();
+    }
+
+    // Fail in case any (new) shutdown functions exist.
+    $this->assertCount(0, drupal_register_shutdown_function(), 'Unexpected Drupal shutdown callbacks exist after running shutdown functions.');
+
+    parent::assertPostConditions();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function tearDown() {
+    // Destroy the testing kernel.
+    if (isset($this->kernel)) {
+      $this->kernel->shutdown();
+    }
+
+    // Free up memory: Own properties.
+    $this->classLoader = NULL;
+    $this->vfsRoot = NULL;
+    $this->configImporter = NULL;
+
+    // Free up memory: Custom test class properties.
+    // Note: Private properties cannot be cleaned up.
+    $rc = new \ReflectionClass(__CLASS__);
+    $blacklist = array();
+    foreach ($rc->getProperties() as $property) {
+      $blacklist[$property->name] = $property->getDeclaringClass()->name;
+    }
+    $rc = new \ReflectionClass($this);
+    foreach ($rc->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED) as $property) {
+      if (!$property->isStatic() && !isset($blacklist[$property->name])) {
+        $this->{$property->name} = NULL;
+      }
+    }
+
+    // Clean up statics, container, and settings.
+    if (function_exists('drupal_static_reset')) {
+      drupal_static_reset();
+    }
+    \Drupal::unsetContainer();
+    $this->container = NULL;
+    new Settings(array());
+
+    // Destroy the database connection, which for example removes the memory
+    // from sqlite in memory.
+    foreach (Database::getAllConnectionInfo() as $key => $targets) {
+      Database::removeConnection($key);
+    }
+
+    parent::tearDown();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function tearDownAfterClass() {
+    // Free up memory: Precompiled container.
+    self::$initialContainerBuilder = NULL;
+    parent::tearDownAfterClass();
+  }
+
+  /**
+   * Installs default configuration for a given list of modules.
+   *
+   * @param string|string[] $modules
+   *   A list of modules for which to install default configuration.
+   *
+   * @throws \LogicException
+   *   If any module in $modules is not enabled.
+   */
+  protected function installConfig($modules) {
+    foreach ((array) $modules as $module) {
+      if (!$this->container->get('module_handler')->moduleExists($module)) {
+        throw new \LogicException("$module module is not enabled.");
+      }
+      $this->container->get('config.installer')->installDefaultConfig('module', $module);
+    }
+  }
+
+  /**
+   * Installs database tables from a module schema definition.
+   *
+   * @param string $module
+   *   The name of the module that defines the table's schema.
+   * @param string|array $tables
+   *   The name or an array of the names of the tables to install.
+   *
+   * @throws \LogicException
+   *   If $module is not enabled or the table schema cannot be found.
+   */
+  protected function installSchema($module, $tables) {
+    // drupal_get_module_schema() is technically able to install a schema
+    // of a non-enabled module, but its ability to load the module's .install
+    // file depends on many other factors. To prevent differences in test
+    // behavior and non-reproducible test failures, we only allow the schema of
+    // explicitly loaded/enabled modules to be installed.
+    if (!$this->container->get('module_handler')->moduleExists($module)) {
+      throw new \LogicException("$module module is not enabled.");
+    }
+    $tables = (array) $tables;
+    foreach ($tables as $table) {
+      $schema = drupal_get_module_schema($module, $table);
+      if (empty($schema)) {
+        throw new \LogicException("$module module does not define a schema for table '$table'.");
+      }
+      $this->container->get('database')->schema()->createTable($table, $schema);
+    }
+  }
+
+  /**
+   * Installs the storage schema for a specific entity type.
+   *
+   * @param string $entity_type_id
+   *   The ID of the entity type.
+   */
+  protected function installEntitySchema($entity_type_id) {
+    /** @var \Drupal\Core\Entity\EntityManagerInterface $entity_manager */
+    $entity_manager = $this->container->get('entity.manager');
+    $entity_type = $entity_manager->getDefinition($entity_type_id);
+    $entity_manager->onEntityTypeCreate($entity_type);
+
+    // For test runs, the most common storage backend is a SQL database. For
+    // this case, ensure the tables got created.
+    $storage = $entity_manager->getStorage($entity_type_id);
+    if ($storage instanceof SqlEntityStorageInterface) {
+      $tables = $storage->getTableMapping()->getTableNames();
+      $db_schema = $this->container->get('database')->schema();
+      $all_tables_exist = TRUE;
+      foreach ($tables as $table) {
+        if (!$db_schema->tableExists($table)) {
+          $this->fail(SafeMarkup::format('Installed entity type table for the %entity_type entity type: %table', array(
+            '%entity_type' => $entity_type_id,
+            '%table' => $table,
+          )));
+          $all_tables_exist = FALSE;
+        }
+      }
+      if ($all_tables_exist) {
+        $this->pass(SafeMarkup::format('Installed entity type tables for the %entity_type entity type: %tables', array(
+          '%entity_type' => $entity_type_id,
+          '%tables' => '{' . implode('}, {', $tables) . '}',
+        )));
+      }
+    }
+  }
+
+  /**
+   * Enables modules for this test.
+   *
+   * @param string[] $modules
+   *   A list of modules to enable. Dependencies are not resolved; i.e.,
+   *   multiple modules have to be specified individually. The modules are only
+   *   added to the active module list and loaded; i.e., their database schema
+   *   is not installed. hook_install() is not invoked. A custom module weight
+   *   is not applied.
+   *
+   * @throws \LogicException
+   *   If any module in $modules is already enabled.
+   * @throws \RuntimeException
+   *   If a module is not enabled after enabling it.
+   */
+  protected function enableModules(array $modules) {
+    $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
+    if ($trace[1]['function'] === 'setUp') {
+      trigger_error('KernelTestBase::enableModules() should not be called from setUp(). Use the $modules property instead.', E_DEPRECATED);
+    }
+    unset($trace);
+
+    // Perform an ExtensionDiscovery scan as this function may receive a
+    // profile that is not the current profile, and we don't yet have a cached
+    // way to receive inactive profile information.
+    // @todo Remove as part of https://www.drupal.org/node/2186491
+    $listing = new ExtensionDiscovery(\Drupal::root());
+    $module_list = $listing->scan('module');
+    // In ModuleHandlerTest we pass in a profile as if it were a module.
+    $module_list += $listing->scan('profile');
+
+    // Set the list of modules in the extension handler.
+    $module_handler = $this->container->get('module_handler');
+
+    // Write directly to active storage to avoid early instantiation of
+    // the event dispatcher which can prevent modules from registering events.
+    $active_storage = $this->container->get('config.storage');
+    $extension_config = $active_storage->read('core.extension');
+
+    foreach ($modules as $module) {
+      if ($module_handler->moduleExists($module)) {
+        throw new \LogicException("$module module is already enabled.");
+      }
+      $module_handler->addModule($module, $module_list[$module]->getPath());
+      // Maintain the list of enabled modules in configuration.
+      $extension_config['module'][$module] = 0;
+    }
+    $active_storage->write('core.extension', $extension_config);
+
+    // Update the kernel to make their services available.
+    $extensions = $module_handler->getModuleList();
+    $this->container->get('kernel')->updateModules($extensions, $extensions);
+
+    // Ensure isLoaded() is TRUE in order to make
+    // \Drupal\Core\Theme\ThemeManagerInterface::render() work.
+    // Note that the kernel has rebuilt the container; this $module_handler is
+    // no longer the $module_handler instance from above.
+    $module_handler = $this->container->get('module_handler');
+    $module_handler->reload();
+    foreach ($modules as $module) {
+      if (!$module_handler->moduleExists($module)) {
+        throw new \RuntimeException("$module module is not enabled after enabling it.");
+      }
+    }
+  }
+
+  /**
+   * Disables modules for this test.
+   *
+   * @param string[] $modules
+   *   A list of modules to disable. Dependencies are not resolved; i.e.,
+   *   multiple modules have to be specified with dependent modules first.
+   *   Code of previously enabled modules is still loaded. The modules are only
+   *   removed from the active module list.
+   *
+   * @throws \LogicException
+   *   If any module in $modules is already disabled.
+   * @throws \RuntimeException
+   *   If a module is not disabled after disabling it.
+   */
+  protected function disableModules(array $modules) {
+    // Unset the list of modules in the extension handler.
+    $module_handler = $this->container->get('module_handler');
+    $module_filenames = $module_handler->getModuleList();
+    $extension_config = $this->config('core.extension');
+    foreach ($modules as $module) {
+      if (!$module_handler->moduleExists($module)) {
+        throw new \LogicException("$module module cannot be disabled because it is not enabled.");
+      }
+      unset($module_filenames[$module]);
+      $extension_config->clear('module.' . $module);
+    }
+    $extension_config->save();
+    $module_handler->setModuleList($module_filenames);
+    $module_handler->resetImplementations();
+    // Update the kernel to remove their services.
+    $this->container->get('kernel')->updateModules($module_filenames, $module_filenames);
+
+    // Ensure isLoaded() is TRUE in order to make _theme() work.
+    // Note that the kernel has rebuilt the container; this $module_handler is
+    // no longer the $module_handler instance from above.
+    $module_handler = $this->container->get('module_handler');
+    $module_handler->reload();
+    foreach ($modules as $module) {
+      if ($module_handler->moduleExists($module)) {
+        throw new \RuntimeException("$module module is not disabled after disabling it.");
+      }
+    }
+  }
+
+  /**
+   * Renders a render array.
+   *
+   * @param array $elements
+   *   The elements to render.
+   *
+   * @return string
+   *   The rendered string output (typically HTML).
+   */
+  protected function render(array &$elements) {
+    $content = $this->container->get('renderer')->render($elements);
+    drupal_process_attached($elements);
+    $this->setRawContent($content);
+    $this->verbose('<pre style="white-space: pre-wrap">' . SafeMarkup::checkPlain($content));
+    return $content;
+  }
+
+  /**
+   * Sets an in-memory Settings variable.
+   *
+   * @param string $name
+   *   The name of the setting to set.
+   * @param bool|string|int|array|null $value
+   *   The value to set. Note that array values are replaced entirely; use
+   *   \Drupal\Core\Site\Settings::get() to perform custom merges.
+   */
+  protected function setSetting($name, $value) {
+    $settings = Settings::getAll();
+    $settings[$name] = $value;
+    new Settings($settings);
+  }
+
+  /**
+   * Returns a ConfigImporter object to import test configuration.
+   *
+   * @return \Drupal\Core\Config\ConfigImporter
+   *
+   * @todo Move into Config-specific test base class.
+   */
+  protected function configImporter() {
+    if (!$this->configImporter) {
+      // Set up the ConfigImporter object for testing.
+      $storage_comparer = new StorageComparer(
+        $this->container->get('config.storage.staging'),
+        $this->container->get('config.storage'),
+        $this->container->get('config.manager')
+      );
+      $this->configImporter = new ConfigImporter(
+        $storage_comparer,
+        $this->container->get('event_dispatcher'),
+        $this->container->get('config.manager'),
+        $this->container->get('lock'),
+        $this->container->get('config.typed'),
+        $this->container->get('module_handler'),
+        $this->container->get('module_installer'),
+        $this->container->get('theme_handler'),
+        $this->container->get('string_translation')
+      );
+    }
+    // Always recalculate the changelist when called.
+    return $this->configImporter->reset();
+  }
+
+  /**
+   * Copies configuration objects from a source storage to a target storage.
+   *
+   * @param \Drupal\Core\Config\StorageInterface $source_storage
+   *   The source config storage.
+   * @param \Drupal\Core\Config\StorageInterface $target_storage
+   *   The target config storage.
+   *
+   * @todo Move into Config-specific test base class.
+   */
+  protected function copyConfig(StorageInterface $source_storage, StorageInterface $target_storage) {
+    $target_storage->deleteAll();
+    foreach ($source_storage->listAll() as $name) {
+      $target_storage->write($name, $source_storage->read($name));
+    }
+  }
+
+  /**
+   * Stops test execution.
+   */
+  protected function stop() {
+    $this->getTestResultObject()->stop();
+  }
+
+  /**
+   * Dumps the current state of the virtual filesystem to STDOUT.
+   */
+  protected function vfsDump() {
+    vfsStream::inspect(new vfsStreamPrintVisitor());
+  }
+
+  /**
+   * Returns the modules to enable for this test.
+   *
+   * @param string $class
+   *   The fully-qualified class name of this test.
+   *
+   * @return array
+   */
+  private static function getModulesToEnable($class) {
+    $modules = array();
+    while ($class) {
+      if (property_exists($class, 'modules')) {
+        // Only add the modules, if the $modules property was not inherited.
+        $rp = new \ReflectionProperty($class, 'modules');
+        if ($rp->class == $class) {
+          $modules[$class] = $class::$modules;
+        }
+      }
+      $class = get_parent_class($class);
+    }
+    // Modules have been collected in reverse class hierarchy order; modules
+    // defined by base classes should be sorted first. Then, merge the results
+    // together.
+    $modules = array_reverse($modules);
+    return call_user_func_array('array_merge_recursive', $modules);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function prepareTemplate(\Text_Template $template) {
+    $bootstrap_globals = '';
+
+    // Fix missing bootstrap.php when $preserveGlobalState is FALSE.
+    // @see https://github.com/sebastianbergmann/phpunit/pull/797
+    $bootstrap_globals .= '$__PHPUNIT_BOOTSTRAP = ' . var_export($GLOBALS['__PHPUNIT_BOOTSTRAP'], TRUE) . ";\n";
+
+    // Avoid repetitive test namespace discoveries to improve performance.
+    // @see /core/tests/bootstrap.php
+    $bootstrap_globals .= '$namespaces = ' . var_export($GLOBALS['namespaces'], TRUE) . ";\n";
+
+    $template->setVar(array(
+      'constants' => '',
+      'included_files' => '',
+      'globals' => $bootstrap_globals,
+    ));
+  }
+
+  /**
+   * Returns whether the current test runs in isolation.
+   *
+   * @return bool
+   *
+   * @see https://github.com/sebastianbergmann/phpunit/pull/1350
+   */
+  protected function isTestInIsolation() {
+    return function_exists('__phpunit_run_isolated_test');
+  }
+
+  /**
+   * BC: Automatically resolve former KernelTestBase class properties.
+   *
+   * Test authors should follow the provided instructions and adjust their tests
+   * accordingly.
+   *
+   * @deprecated in Drupal 8.0.x, will be removed before Drupal 8.2.0.
+   */
+  public function __get($name) {
+    if (in_array($name, array(
+      'public_files_directory',
+      'private_files_directory',
+      'temp_files_directory',
+      'translation_files_directory',
+    ))) {
+      // @comment it in again.
+      trigger_error(sprintf("KernelTestBase::\$%s no longer exists. Use the regular API method to retrieve it instead (e.g., Settings).", $name), E_USER_DEPRECATED);
+      switch ($name) {
+        case 'public_files_directory':
+          return Settings::get('file_public_path', conf_path() . '/files');
+
+        case 'private_files_directory':
+          return $this->container->get('config.factory')->get('system.file')->get('path.private');
+
+        case 'temp_files_directory':
+          return file_directory_temp();
+
+        case 'translation_files_directory':
+          return Settings::get('file_public_path', conf_path() . '/translations');
+      }
+    }
+
+    if ($name === 'configDirectories') {
+      trigger_error(sprintf("KernelTestBase::\$%s no longer exists. Use config_get_config_directory() directly instead.", $name), E_DEPRECATED);
+      return array(
+        CONFIG_ACTIVE_DIRECTORY => config_get_config_directory(CONFIG_ACTIVE_DIRECTORY),
+        CONFIG_STAGING_DIRECTORY => config_get_config_directory(CONFIG_STAGING_DIRECTORY),
+      );
+    }
+
+    $denied = array(
+      // @see \Drupal\simpletest\TestBase
+      'testId',
+      'timeLimit',
+      'results',
+      'assertions',
+      'skipClasses',
+      'verbose',
+      'verboseId',
+      'verboseClassName',
+      'verboseDirectory',
+      'verboseDirectoryUrl',
+      'dieOnFail',
+      'kernel',
+      // @see \Drupal\simpletest\TestBase::prepareEnvironment()
+      'generatedTestFiles',
+      // @see \Drupal\simpletest\KernelTestBase::containerBuild()
+      'keyValueFactory',
+    );
+    if (in_array($name, $denied) || strpos($name, 'original') === 0) {
+      throw new \RuntimeException(sprintf('TestBase::$%s property no longer exists', $name));
+    }
+  }
+
+}
diff --git a/core/tests/bootstrap.php b/core/tests/bootstrap.php
index ac8afbd..e837a6f 100644
--- a/core/tests/bootstrap.php
+++ b/core/tests/bootstrap.php
@@ -20,9 +20,10 @@ function drupal_phpunit_find_extension_directories($scan_directory) {
   $extensions = array();
   $dirs = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($scan_directory, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS));
   foreach ($dirs as $dir) {
-    if (strpos($dir->getPathname(), 'info.yml') !== FALSE) {
+    if (strpos($dir->getPathname(), '.info.yml') !== FALSE) {
       // Cut off ".info.yml" from the filename for use as the extension name.
-      $extensions[substr($dir->getFilename(), 0, -9)] = $dir->getPathInfo()->getRealPath();
+      $extensions[substr($dir->getFilename(), 0, -9)] = $dir->getPathInfo()
+        ->getRealPath();
     }
   }
   return $extensions;
@@ -35,10 +36,19 @@ function drupal_phpunit_find_extension_directories($scan_directory) {
  *   An array of directories under which contributed extensions may exist.
  */
 function drupal_phpunit_contrib_extension_directory_roots() {
-  $sites_path = __DIR__ . '/../../sites';
-  $paths = array();
+  $root = dirname(dirname(__DIR__));
+  $paths = array(
+    $root . '/core/modules',
+    $root . '/core/profiles',
+    $root . '/modules',
+    $root . '/profiles',
+  );
+  $sites_path = $root . '/sites';
   // Note this also checks sites/../modules and sites/../profiles.
   foreach (scandir($sites_path) as $site) {
+    if ($site[0] === '.' || $site === 'simpletest') {
+      continue;
+    }
     $path = "$sites_path/$site";
     $paths[] = is_dir("$path/modules") ? realpath("$path/modules") : NULL;
     $paths[] = is_dir("$path/profiles") ? realpath("$path/profiles") : NULL;
@@ -49,37 +59,42 @@ function drupal_phpunit_contrib_extension_directory_roots() {
 /**
  * Registers the namespace for each extension directory with the autoloader.
  *
- * @param Composer\Autoload\ClassLoader $loader
- *   The supplied autoloader.
  * @param array $dirs
  *   An associative array of extension directories, keyed by extension name.
+ *
+ * @return array
+ *   An associative array of extension directories, keyed by their namespace.
  */
-function drupal_phpunit_register_extension_dirs(Composer\Autoload\ClassLoader $loader, $dirs) {
+function drupal_phpunit_get_extension_namespaces($dirs) {
+  $namespaces = array();
   foreach ($dirs as $extension => $dir) {
     if (is_dir($dir . '/src')) {
       // Register the PSR-4 directory for module-provided classes.
-      $loader->addPsr4('Drupal\\' . $extension . '\\', $dir . '/src');
+      $namespaces['Drupal\\' . $extension . '\\'][] = $dir . '/src';
     }
     if (is_dir($dir . '/tests/src')) {
       // Register the PSR-4 directory for PHPUnit test classes.
-      $loader->addPsr4('Drupal\\Tests\\' . $extension . '\\', $dir . '/tests/src');
+      $namespaces['Drupal\\Tests\\' . $extension . '\\'][] = $dir . '/tests/src';
     }
   }
+  return $namespaces;
 }
 
 // Start with classes in known locations.
 $loader = require __DIR__ . '/../../autoload.php';
 $loader->add('Drupal\\Tests', __DIR__);
 
-// Scan for arbitrary extension namespaces from core and contrib.
-$extension_roots = array_merge(array(
-  __DIR__ . '/../modules',
-  __DIR__ . '/../profiles',
-), drupal_phpunit_contrib_extension_directory_roots());
+if (!isset($GLOBALS['namespaces'])) {
+  // Scan for arbitrary extension namespaces from core and contrib.
+  $extension_roots = drupal_phpunit_contrib_extension_directory_roots();
 
-$dirs = array_map('drupal_phpunit_find_extension_directories', $extension_roots);
-$dirs = array_reduce($dirs, 'array_merge', array());
-drupal_phpunit_register_extension_dirs($loader, $dirs);
+  $dirs = array_map('drupal_phpunit_find_extension_directories', $extension_roots);
+  $dirs = array_reduce($dirs, 'array_merge', array());
+  $GLOBALS['namespaces'] = drupal_phpunit_get_extension_namespaces($dirs);
+}
+foreach ($GLOBALS['namespaces'] as $prefix => $paths) {
+  $loader->addPsr4($prefix, $paths);
+}
 
 // Set sane locale settings, to ensure consistent string, dates, times and
 // numbers handling.
