diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..8254b03
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,18 @@
+language: php
+
+php:
+  - 5.4
+
+before_script:
+  #- composer self-update
+  # Enable APC extension on PHP 5.4.
+  - echo -e "extension = apc.so\napc.enable_cli = 1\napc.shm_size = 128M\napc.num_files_hint = 7000" > apc.ini
+  - '[ `expr "$TRAVIS_PHP_VERSION" "<" "5.5"` -eq 1 ] && phpenv config-add apc.ini'
+
+script:
+  - phpunit -v -c core --testsuite Unit
+  # Disable XDebug after running unit tests.
+  - phpenv config-rm xdebug.ini
+  - php --version
+  # @todo Update to PHPUnit 4.3 (once released).
+  - ./core/vendor/phpunit/phpunit/phpunit -v -c core --testsuite Kernel
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index fcf4d44..5a5c7c2 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -106,7 +106,9 @@
  * @see http://php.net/manual/reserved.variables.server.php
  * @see http://php.net/manual/function.time.php
  */
-define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);
+if (!defined('REQUEST_TIME')) {
+  define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);
+}
 
 /**
  * Regular expression to match PHP function names.
@@ -134,7 +136,9 @@
  *
  * This strips two levels of directories off the current directory.
  */
-define('DRUPAL_ROOT', dirname(dirname(__DIR__)));
+if (!defined('DRUPAL_ROOT')) {
+  define('DRUPAL_ROOT', dirname(dirname(__DIR__)));
+}
 
 /**
  * Returns the appropriate configuration directory.
diff --git a/core/includes/install.inc b/core/includes/install.inc
index e247378..c8e7f74 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -211,7 +211,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) === '') {
@@ -316,7 +316,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/Component/PhpStorage/MTimeProtectedFastFileStorage.php b/core/lib/Drupal/Component/PhpStorage/MTimeProtectedFastFileStorage.php
index 73486a8..b048007 100644
--- a/core/lib/Drupal/Component/PhpStorage/MTimeProtectedFastFileStorage.php
+++ b/core/lib/Drupal/Component/PhpStorage/MTimeProtectedFastFileStorage.php
@@ -70,7 +70,7 @@ public function save($name, $data) {
 
     // Write the file out to a temporary location. Prepend with a '.' to keep it
     // hidden from listings and web servers.
-    $temporary_path = tempnam($this->directory, '.');
+    $temporary_path = $this->directory . '/.' . substr(str_shuffle(md5(time())),0, 10);;
     if (!$temporary_path || !@file_put_contents($temporary_path, $data)) {
       return FALSE;
     }
@@ -103,7 +103,7 @@ public function save($name, $data) {
       // iteration.
       if ($i > 0) {
         $this->unlink($temporary_path);
-        $temporary_path = tempnam($this->directory, '.');
+        $temporary_path = $this->directory . '/.' . substr(str_shuffle(md5(time())),0, 10);
         rename($full_path, $temporary_path);
         // Make sure to not loop infinitely on a hopelessly slow filesystem.
         if ($i > 10) {
diff --git a/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php
index f404480..47d7216 100644
--- a/core/lib/Drupal/Core/Config/FileStorage.php
+++ b/core/lib/Drupal/Core/Config/FileStorage.php
@@ -200,19 +200,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;
@@ -306,13 +310,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/DependencyInjection/DependencySerializationTrait.php b/core/lib/Drupal/Core/DependencyInjection/DependencySerializationTrait.php
index cb6a27d..9de7e71 100644
--- a/core/lib/Drupal/Core/DependencyInjection/DependencySerializationTrait.php
+++ b/core/lib/Drupal/Core/DependencyInjection/DependencySerializationTrait.php
@@ -49,9 +49,10 @@ public function __sleep() {
    * {@inheritdoc}
    */
   public function __wakeup() {
-    $container = \Drupal::getContainer();
-    foreach ($this->_serviceIds as $key => $service_id) {
-      $this->$key = $container->get($service_id);
+    if ($container = \Drupal::getContainer()) {
+      foreach ($this->_serviceIds as $key => $service_id) {
+        $this->$key = $container->get($service_id);
+      }
     }
     $this->_serviceIds = array();
   }
diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
index b25a0d9..7064928 100644
--- a/core/lib/Drupal/Core/DrupalKernel.php
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -432,6 +432,14 @@ public function getContainer() {
   /**
    * {@inheritdoc}
    */
+  public function setContainer(ContainerInterface $container = NULL) {
+    $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';
@@ -523,9 +531,13 @@ 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)) {
-          $this->serviceProviderClasses['site'][] = $class;
+      foreach ($GLOBALS['conf']['container_service_providers'] as $key => $class) {
+        if (is_object($class)) {
+          $this->serviceProviderClasses['site'][$key] = get_class($class);
+          $this->serviceProviders['site'][$key] = $class;
+        }
+        elseif (class_exists($class)) {
+          $this->serviceProviderClasses['site'][$key] = $class;
         }
       }
     }
@@ -695,6 +707,13 @@ protected function initializeContainer($rebuild = FALSE) {
       }
     }
 
+    // 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) && !$rebuild) {
@@ -710,6 +729,7 @@ protected function initializeContainer($rebuild = FALSE) {
       }
     }
 
+    // If there is still no container, build a new one from scratch.
     if (!isset($container)) {
       $container = $this->compileContainer();
     }
@@ -1053,13 +1073,23 @@ protected function compileContainer() {
    */
   protected function initializeServiceProviders() {
     $this->discoverServiceProviders();
-    $this->serviceProviders = array(
+    if (!isset($this->serviceProviders)) {
+      $this->serviceProviders = array();
+    }
+    // Reset app service providers, so that uninstalled modules aren't reflected
+    // anymore.
+    $this->serviceProviders['app'] = [];
+    // Site specific service providers (and so tests) are kept.
+
+    $this->serviceProviders += array(
       'app' => array(),
       'site' => array(),
     );
     foreach ($this->serviceProviderClasses as $origin => $classes) {
       foreach ($classes as $name => $class) {
-        $this->serviceProviders[$origin][$name] = new $class;
+        if (!isset($this->serviceProviders[$origin][$name])) {
+          $this->serviceProviders[$origin][$name] = new $class;
+        }
       }
     }
   }
diff --git a/core/lib/Drupal/Core/DrupalKernelInterface.php b/core/lib/Drupal/Core/DrupalKernelInterface.php
index ab17b77..83d00bd 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\ContainerInterface;
 use Symfony\Component\HttpKernel\HttpKernelInterface;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -58,6 +59,18 @@ public function getServiceProviders($origin);
   public function getContainer();
 
   /**
+   * Sets the current container.
+   *
+   * Must be called before boot() in order to bootstrap the given container.
+   *
+   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+   *   The container to set.
+   *
+   * @return $this
+   */
+  public function setContainer(ContainerInterface $container = NULL);
+
+  /**
    * Set the current site path.
    *
    * @param $path
diff --git a/core/lib/Drupal/Core/Extension/InfoParser.php b/core/lib/Drupal/Core/Extension/InfoParser.php
index 38ac624..0334402 100644
--- a/core/lib/Drupal/Core/Extension/InfoParser.php
+++ b/core/lib/Drupal/Core/Extension/InfoParser.php
@@ -19,6 +19,8 @@ class InfoParser implements InfoParserInterface {
   /**
    * Array of all info keyed by filename.
    *
+   * Especially during kernel tests, YAML files are re-parsed often.
+   *
    * @var array
    */
   protected static $parsedInfos = array();
diff --git a/core/lib/Drupal/Core/PhpStorage/PhpStorageFactory.php b/core/lib/Drupal/Core/PhpStorage/PhpStorageFactory.php
index 3386bba..72d5f31 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/Queue/QueueMemoryFactory.php b/core/lib/Drupal/Core/Queue/QueueMemoryFactory.php
new file mode 100644
index 0000000..97f9db5
--- /dev/null
+++ b/core/lib/Drupal/Core/Queue/QueueMemoryFactory.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Queue\QueueMemoryFactory.
+ */
+
+namespace Drupal\Core\Queue;
+
+/**
+ * Defines the queue factory for the memory backend.
+ */
+class QueueMemoryFactory {
+
+  /**
+   * Constructs a new queue object for a given name.
+   *
+   * @param string $name
+   *   The name of the collection holding key and value pairs.
+   *
+   * @return \Drupal\Core\Queue\Memory
+   *   A queue implementation for the given $collection.
+   */
+  public function get($name) {
+    return new Memory($name);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/ElementInfoManager.php b/core/lib/Drupal/Core/Render/ElementInfoManager.php
index 1054b85..52249ca 100644
--- a/core/lib/Drupal/Core/Render/ElementInfoManager.php
+++ b/core/lib/Drupal/Core/Render/ElementInfoManager.php
@@ -57,8 +57,11 @@ public function getInfo($type) {
     if (!isset($this->elementInfo)) {
       $this->elementInfo = $this->buildInfo();
     }
-    $info = isset($this->elementInfo[$type]) ? $this->elementInfo[$type] : array();
-    $info['#defaults_loaded'] = TRUE;
+    $info = [];
+    if (isset($this->elementInfo[$type])) {
+      $info = $this->elementInfo[$type];
+      $info['#defaults_loaded'] = TRUE;
+    }
     return $info;
   }
 
diff --git a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
index 4612937..765eb48 100644
--- a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
+++ b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
@@ -127,6 +127,14 @@ 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 (obviously) not supported by vfsStream.
+    if (strpos($path, 'vfs://') === 0) {
+      return $path;
+    }
+
     $realpath = realpath($path);
     if (!$realpath) {
       // This file does not yet exist.
diff --git a/core/modules/simpletest/src/RandomGeneratorTrait.php b/core/modules/simpletest/src/RandomGeneratorTrait.php
new file mode 100644
index 0000000..4219ec3
--- /dev/null
+++ b/core/modules/simpletest/src/RandomGeneratorTrait.php
@@ -0,0 +1,120 @@
+<?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 unique 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
+   * randomMachineName().
+   *
+   * @param int $length
+   *   Length of random string to generate.
+   *
+   * @return string
+   *   Randomly generated unique string.
+   *
+   * @see \Drupal\Component\Utility\Random::string()
+   */
+  protected function randomString($length = 8) {
+    return $this->getRandomGenerator()->string($length, TRUE, array($this, 'randomStringValidate'));
+  }
+
+  /**
+   * 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.
+   */
+  protected function randomStringValidate($string) {
+    // Consecutive spaces causes issues for
+    // Drupal\simpletest\WebTestBase::assertLink().
+    if (preg_match('/\s{2,}/', $string)) {
+      return FALSE;
+    }
+    // Starting with a space means that length might not be what is expected.
+    // Starting with an @ sign causes CURL to fail if used in conjunction with a
+    // file upload, see https://drupal.org/node/2174997.
+    if (preg_match('/^(\s|@)/', $string)) {
+      return FALSE;
+    }
+    // Ending with a space means that length might not be what is expected.
+    if (preg_match('/\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()
+   */
+  protected 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 abf09fd..ec7dfea 100644
--- a/core/modules/simpletest/src/TestBase.php
+++ b/core/modules/simpletest/src/TestBase.php
@@ -9,8 +9,8 @@
 
 use Drupal\Component\Utility\Crypt;
 use Drupal\Component\Utility\Random;
-use Drupal\Component\Utility\String;
 use Drupal\Core\Database\Database;
+use Drupal\Component\Utility\String;
 use Drupal\Core\Config\ConfigImporter;
 use Drupal\Core\Config\StorageComparer;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
@@ -33,6 +33,9 @@
  * \Drupal\simpletest\WebTestBase or \Drupal\simpletest\KernelTestBase.
  */
 abstract class TestBase {
+
+  use RandomGeneratorTrait;
+
   /**
    * The test run ID.
    *
@@ -288,13 +291,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
diff --git a/core/modules/simpletest/src/Tests/KernelTestBaseTest.php b/core/modules/simpletest/src/Tests/KernelTestBaseTest.php
index 218d0b9..eca7ffc 100644
--- a/core/modules/simpletest/src/Tests/KernelTestBaseTest.php
+++ b/core/modules/simpletest/src/Tests/KernelTestBaseTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\simpletest\Tests;
 
 use Drupal\simpletest\KernelTestBase;
+use org\bovigo\vfs\vfsStream;
 
 /**
  * Tests KernelTestBase functionality.
diff --git a/core/modules/system/src/Tests/Extension/ModuleHandlerTest.php b/core/modules/system/src/Tests/Extension/ModuleHandlerTest.php
index 130ffd2..0b70cae 100644
--- a/core/modules/system/src/Tests/Extension/ModuleHandlerTest.php
+++ b/core/modules/system/src/Tests/Extension/ModuleHandlerTest.php
@@ -8,8 +8,8 @@
 namespace Drupal\system\Tests\Extension;
 
 use Drupal\Core\DependencyInjection\ContainerBuilder;
-use Drupal\simpletest\KernelTestBase;
 use \Drupal\Core\Extension\ModuleUninstallValidatorException;
+use Drupal\Tests\KernelTestBase;
 
 /**
  * Tests ModuleHandler functionality.
@@ -21,11 +21,6 @@ class ModuleHandlerTest extends KernelTestBase {
   /**
    * {@inheritdoc}
    */
-  public static $modules = array('system');
-
-  /**
-   * {@inheritdoc}
-   */
   public function containerBuild(ContainerBuilder $container) {
     parent::containerBuild($container);
     // Put a fake route bumper on the container to be called during uninstall.
@@ -37,19 +32,12 @@ public function containerBuild(ContainerBuilder $container) {
    * The basic functionality of retrieving enabled modules.
    */
   function testModuleList() {
-    // 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';
+    $this->assertModuleList($module_list, 'Initial');
 
-    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');
+    // @fixme ModuleInstaller calls system_rebuild_module_data which is part of system.module.
+    include_once $this->root . '/core/modules/system/system.module';
 
     // Try to install a new module.
     $this->moduleInstaller()->install(array('ban'));
@@ -85,8 +73,7 @@ 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)));
+    $this->assertIdentical($enabled_modules, $expected_values, format_string('@condition: extension handler returns correct results', array('@condition' => $condition)));
   }
 
   /**
@@ -183,7 +170,7 @@ function testDependencyResolution() {
   function testUninstallProfileDependency() {
     $profile = 'minimal';
     $dependency = 'dblog';
-    $this->settingsSet('install_profile', $profile);
+    $this->setSetting('install_profile', $profile);
     $this->enableModules(array('module_test', $profile));
 
     drupal_static_reset('system_rebuild_module_data');
diff --git a/core/modules/system/src/Tests/PhpStorage/PhpStorageFactoryTest.php b/core/modules/system/src/Tests/PhpStorage/PhpStorageFactoryTest.php
index 248d203..4d67f17 100644
--- a/core/modules/system/src/Tests/PhpStorage/PhpStorageFactoryTest.php
+++ b/core/modules/system/src/Tests/PhpStorage/PhpStorageFactoryTest.php
@@ -11,8 +11,8 @@
 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,19 @@
 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 +72,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/modules/views/src/Plugin/views/query/Sql.php b/core/modules/views/src/Plugin/views/query/Sql.php
index 0bc5134..17cb94f 100644
--- a/core/modules/views/src/Plugin/views/query/Sql.php
+++ b/core/modules/views/src/Plugin/views/query/Sql.php
@@ -1772,7 +1772,18 @@ public function getDateFormat($field, $format) {
           'A' => '',
         );
         $format = strtr($format, $replace);
-        return "strftime('$format', $field, 'unixepoch')";
+        $expression = "strftime('$format', $field, 'unixepoch')";
+        // The expression yields a string, but the comparison value is an
+        // integer in case the comparison value is a float, integer, or numeric.
+        // All of the above SQLite format tokens only produce integers. However,
+        // the given $format may contain 'Y-m-d', which results in a string.
+        // @see \Drupal\Core\Database\Driver\sqlite\Connection::expandArguments()
+        // @see http://www.sqlite.org/lang_datefunc.html
+        // @see http://www.sqlite.org/lang_expr.html#castexpr
+        if (preg_match('/^(?:%\w)+$/', $format)) {
+          $expression = "CAST($expression AS NUMERIC)";
+        }
+        return $expression;
     }
   }
 
diff --git a/core/phpunit.xml.dist b/core/phpunit.xml.dist
index bd0edc9..052dd53 100644
--- a/core/phpunit.xml.dist
+++ b/core/phpunit.xml.dist
@@ -1,6 +1,10 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
-<phpunit bootstrap="tests/bootstrap.php" colors="true">
+<phpunit
+  bootstrap="tests/bootstrap.php"
+  colors="true"
+  verbose="true"
+>
   <php>
     <!-- Set error reporting to E_ALL. -->
     <ini name="error_reporting" value="32767"/>
@@ -8,17 +12,23 @@
     <ini name="memory_limit" value="-1"/>
   </php>
   <testsuites>
-    <testsuite name="Drupal Unit Test Suite">
-      <directory>./tests</directory>
-      <directory>./modules/*/tests</directory>
-      <directory>../modules</directory>
-      <directory>../sites/*/modules</directory>
-      <!-- Exclude Composer's vendor directory so we don't run tests there. -->
-      <exclude>./vendor</exclude>
-      <!-- Exclude Drush tests. -->
-      <exclude>./drush/tests</exclude>
-      <!-- Exclude special-case files from config's test modules. -->
-      <exclude>./modules/config/tests/config_test/src</exclude>
+    <!-- @todo Verify glob patterns -->
+    <!-- @todo Use TestSuite.php -->
+    <testsuite name="Unit">
+      <directory>./tests/Drupal/Tests</directory>
+      <directory>./modules/**/tests/src</directory>
+      <directory>../modules/**/tests/src</directory>
+      <directory>../sites/*/modules/**/tests/src</directory>
+      <exclude>./tests/Drupal/Tests/KernelTestBaseTest.php</exclude>
+      <exclude>**/Kernel</exclude>
+      <exclude>**/vendor</exclude>
+    </testsuite>
+    <testsuite name="Kernel">
+      <!-- @todo Move to /tests/src/Kernel/* -->
+      <directory>./modules/**/src/Tests</directory>
+      <directory>../modules/**/src/Tests</directory>
+      <directory>../sites/*/modules/**/src/Tests</directory>
+      <exclude>**/vendor</exclude>
     </testsuite>
   </testsuites>
   <listeners>
@@ -30,9 +40,14 @@
     <whitelist>
       <directory>./includes</directory>
       <directory>./lib</directory>
-      <directory>./modules</directory>
-      <directory>../modules</directory>
-      <directory>../sites</directory>
-     </whitelist>
+      <directory>./modules/**/src</directory>
+      <directory>../modules/**/src</directory>
+      <directory>../sites/*/modules/**/src</directory>
+    </whitelist>
+    <blacklist>
+      <directory>**/tests</directory>
+      <directory>../sites/simpletest</directory>
+      <directory>**/vendor</directory>
+    </blacklist>
   </filter>
 </phpunit>
diff --git a/core/tests/Drupal/Tests/AssertLegacyTrait.php b/core/tests/Drupal/Tests/AssertLegacyTrait.php
new file mode 100644
index 0000000..2140895
--- /dev/null
+++ b/core/tests/Drupal/Tests/AssertLegacyTrait.php
@@ -0,0 +1,105 @@
+<?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.
+ */
+trait AssertLegacyTrait {
+
+  /**
+   * @see \Drupal\simpletest\TestBase::assert()
+   */
+  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()
+   */
+  protected function assertEqual($actual, $expected, $message = '') {
+    $this->assertEquals($expected, $actual, $message);
+  }
+
+  /**
+   * @see \Drupal\simpletest\TestBase::assertNotEqual()
+   */
+  protected function assertNotEqual($actual, $expected, $message = '') {
+    $this->assertNotEquals($expected, $actual, $message);
+  }
+
+  /**
+   * @see \Drupal\simpletest\TestBase::assertIdentical()
+   */
+  protected function assertIdentical($actual, $expected, $message = '') {
+    $this->assertSame($expected, $actual, $message);
+  }
+
+  /**
+   * @see \Drupal\simpletest\TestBase::assertNotIdentical()
+   */
+  protected function assertNotIdentical($actual, $expected, $message = '') {
+    $this->assertNotSame($expected, $actual, $message);
+  }
+
+  /**
+   * @see \Drupal\simpletest\TestBase::assertIdenticalObject()
+   */
+  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()
+   */
+  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..aa2769a
--- /dev/null
+++ b/core/tests/Drupal/Tests/KernelTestBase.php
@@ -0,0 +1,1311 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\KernelTestBase.
+ */
+
+namespace Drupal\Tests;
+
+use Drupal\Component\Utility\String;
+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\Schema\EntitySchemaProviderInterface;
+use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
+use Drupal\Core\Extension\ExtensionDiscovery;
+use Drupal\Core\Logger\RfcLogLevel;
+use Drupal\Core\Site\Settings;
+use Drupal\Core\StreamWrapper\StreamWrapperInterface;
+use Drupal\simpletest\AssertContentTrait;
+use Drupal\simpletest\RandomGeneratorTrait;
+use Psr\Log\LoggerInterface;
+use Psr\Log\LoggerTrait;
+use Psr\Log\LogLevel;
+use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
+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 need 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, LoggerInterface {
+
+  use AssertLegacyTrait;
+  use AssertContentTrait;
+  use LoggerTrait;
+  use RandomGeneratorTrait;
+
+  /**
+   * Reset any manipulations to global variables between tests.
+   *
+   * TRUE by default, but MUST be TRUE for kernel tests.
+   *
+   * @var bool
+   */
+  protected $backupGlobals = TRUE;
+
+  /**
+   * Reset all static class properties between tests.
+   *
+   * @var bool
+   */
+  protected $backupStaticAttributes = TRUE;
+
+  /**
+   * Exclude a few static class properties for performance.
+   *
+   * @var array
+   */
+  protected $backupStaticAttributesBlacklist = array(
+    // Ignore static discovery/parser caches to speed up tests.
+    'Drupal\Component\Discovery\YamlDiscovery' => array('parsedFiles'),
+    'Drupal\Core\DependencyInjection\YamlFileLoader' => array('yaml'),
+    'Drupal\Core\Extension\ExtensionDiscovery' => array('files'),
+    'Drupal\Core\Extension\InfoParser' => array('parsedInfos'),
+    // Drupal::$container cannot be serialized.
+    'Drupal' => array('container'),
+    // Settings cannot be serialized.
+    'Drupal\Core\Site\Settings' => array('instance'),
+  );
+
+  /**
+   * If a test runs in a separate process, do not forward any state.
+   *
+   * @var bool
+   */
+  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 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;
+
+  /**
+   * List of custom stream wrappers registered by a test.
+   *
+   * @see \Drupal\Tests\KernelTestBase::registerStreamWrapper()
+   *
+   * @var array
+   */
+  private $streamWrappers = array();
+
+  /**
+   * @var int
+   */
+  private $expectedLogSeverity;
+
+  /**
+   * @var string
+   */
+  private $expectedLogMessage;
+
+  /**
+   * @todo Move into Config test base class.
+   * @var \Drupal\Core\Config\ConfigImporter
+   */
+  protected $configImporter;
+
+  /**
+   * The app root.
+   *
+   * @var string
+   */
+  protected $root;
+
+  /**
+   * The name of the session cookie.
+   */
+  protected $strictConfigSchema = TRUE;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function setUpBeforeClass() {
+    parent::setUpBeforeClass();
+    chdir(__DIR__ . '/../../../../');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->root = dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__))));
+    $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 DRUPAL_ROOT . '/core/includes/bootstrap.inc';
+
+    // Set up virtual filesystem.
+    // Uses a random ID, since created test files may be processed by file
+    // discovery/parser services that are using a static cache to avoid parsing
+    // the identical files multiple times.
+    $suffix = mt_rand(100000, 999999);
+    $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,
+      // @todo Remove this; fix Queue factories + consuming code.
+      // @see \Drupal\Tests\KernelTestBase::register()
+      'queue_default' => 'queue.memory',
+    );
+    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::setMultipleConnectionInfo($this->getDatabaseConnectionInfo());
+  }
+
+  protected function parseDbUrl($db_url) {
+    $info = parse_url($db_url);
+    if (!isset($info['scheme'], $info['host'], $info['path'])) {
+      throw new \InvalidArgumentException('Invalid dburl, . Minimum requirement: driver://host/database');
+    }
+    $info += [
+      'user' => '',
+      'pass' => '',
+      'fragment' => '',
+    ];
+    if ($info['path'][0] === '/') {
+      $info['path'] = substr($info['path'], 1);
+    }
+    if ($info['scheme'] === 'sqlite' && $info['path'][0] !== '/') {
+      $info['path'] = DRUPAL_ROOT . '/' . $info['path'];
+    }
+    $db_info = [
+      'driver' => $info['scheme'],
+      'username' => $info['user'],
+      'password' => $info['pass'],
+      'host' => $info['host'],
+      'database' => $info['path'],
+      'prefix' => [
+        'default' => $info['fragment'],
+      ],
+    ];
+    if (isset($info['port'])) {
+      $db_info['port'] = $info['port'];
+    }
+    return $db_info;
+  }
+
+  /**
+   * Bootstraps a kernel for a test.
+   */
+  private function bootKernel() {
+    $this->setSetting('container_yamls', []);
+    // Allow for test-specific overrides.
+    $settings_services_file = DRUPAL_ROOT . '/sites/default' . '/testing.services.yml';
+    if (file_exists($settings_services_file)) {
+      // Copy the testing-specific service overrides in place.
+      $testing_services_file = DRUPAL_ROOT . '/' . $this->siteDirectory . '/services.yml';
+      copy($settings_services_file, $testing_services_file);
+      $this->settingsSet('container_yamls', [$testing_services_file]);
+    }
+
+    // Allow for global test environment overrides.
+    if (file_exists($test_env = DRUPAL_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();
+    }
+
+    $this->container->set('test.logger', $this);
+
+    // 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(),
+    ));
+
+    // Register basic stream wrappers to avoid dependencies on System module.
+    // The public stream wrapper only depends on 'file_public_path'.
+    // @todo Move StreamWrapper management into DrupalKernel.
+    // @see https://drupal.org/node/2028109
+    $this->streamWrappers = [];
+    $this->registerStreamWrapper('public', 'Drupal\Core\StreamWrapper\PublicStream');
+    // The temporary stream wrapper is able to operate both with and without
+    // configuration.
+    $this->registerStreamWrapper('temporary', 'Drupal\Core\StreamWrapper\TemporaryStream');
+//    $this->registerStreamWrapper('stream_wrapper.vfs', 'vfs', 'Drupal\Tests\VfsStreamWrapperWrapper');
+
+    $settings = Settings::getAll();
+    $settings['php_storage']['default'] = [
+      'class' => '\Drupal\Component\PhpStorage\FileStorage',
+    ];
+    new Settings($settings);
+  }
+
+  /**
+   * Configuration accessor for tests. Returns non-overriden configuration.
+   *
+   * @param $name
+   *   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() {
+    $databases['default']['default'] = array(
+      'driver' => 'sqlite',
+      'namespace' => 'Drupal\\Core\\Database\\Driver\\sqlite',
+      'host' => '',
+      'database' => ':memory:',
+      'username' => '',
+      'password' => '',
+      'prefix' => array(
+        'default' => '',
+      ),
+    );
+
+    // Allow to specify a custom db_url.
+    if ($db_url = getenv('PHPUNIT_DBURL')) {
+      $databases['default']['default'] = $this->parseDbUrl($db_url);
+
+      // In the case we don't use sqlite directly we need to generate a DB prefix.
+      $suffix = mt_rand(100000, 999999);
+      $this->databasePrefix = 'simpletest' . $suffix;
+      $databases['default']['default']['prefix'] = ['default' => $this->databasePrefix];
+    }
+    return $databases;
+  }
+
+  /**
+   * 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 (unless vfsStream supports secret PHP stream wrapper
+   *    flags [cf. Patchwork] to allow for `include 'vfs://container.php';`).
+   * 3. PhpDumper is very slow on its own.
+   *
+   * @param array $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;
+    // @todo Remove this after updating symfony/dependency-injection.
+    // @see https://github.com/symfony/symfony/pull/11422
+    $container->set('service_container', $container);
+
+    return $container;
+  }
+
+  /**
+   * Returns Extension objects for $modules to enable.
+   *
+   * @param array $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) {
+    $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');
+    }
+
+    // @todo Missing QueueMemoryFactory + QueueFactory type hints + no interface.
+    //   Temporarily tampering with Settings instead.
+    // @see \Drupal\Tests\KernelTestBase::bootEnvironment()
+    $container
+      ->register('queue.memory', 'Drupal\Core\Queue\QueueMemoryFactory');
+    //$container
+    //  ->setAlias('queue', 'queue.memory');
+
+    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));
+    }
+
+    // @todo Remove this BC layer.
+    $this->containerBuild($container);
+
+    $container
+      ->register('test.logger', __CLASS__)
+      ->setSynthetic(TRUE)
+      ->addTag('logger');
+  }
+
+  /**
+   * BC alias for register().
+   *
+   * @deprecated 8.0.x:8.0.0
+   * @see \Drupal\Tests\KernelTestBase::register()
+   */
+  public function containerBuild(ContainerBuilder $container) {
+  }
+
+  /**
+   * {@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.');
+
+    // Verify that any expected log message was logged.
+    if (isset($this->expectedLogSeverity)) {
+      $this->fail(vsprintf("Failed to assert expected log message:\n%s: %s", array(
+        $this->logSeverityToString($this->expectedLogSeverity),
+        $this->expectedLogMessage ?: '(no message)',
+      )));
+    }
+
+    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 in-memory database.
+    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|array $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_schema_unprocessed() 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_schema_unprocessed($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);
+    }
+
+    // Refresh the schema cache to make drupal_get_schema() aware of the newly
+    // installed schema.
+    // @todo Refactor Schema API to make this obsolete.
+    drupal_get_schema(NULL, TRUE);
+  }
+
+  /**
+   * 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(String::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(String::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 array $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') {
+      $this->triggerDeprecated('KernelTestBase::enableModules() should not be called from setUp(). Use the $modules property instead.');
+    }
+    unset($trace);
+
+    // 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, drupal_get_path('module', $module));
+      // 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 _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 enabled after enabling it.");
+      }
+    }
+  }
+
+  /**
+   * Disables modules for this test.
+   *
+   * @param array $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.");
+      }
+    }
+  }
+
+  /**
+   * Registers a stream wrapper for this test.
+   *
+   * @param string $scheme
+   *   The scheme to register.
+   * @param string $class
+   *   The fully qualified class name to register.
+   * @param int $type
+   *   The Drupal Stream Wrapper API type. Defaults to
+   *   StreamWrapperInterface::NORMAL.
+   */
+  protected function registerStreamWrapper($scheme, $class, $type = StreamWrapperInterface::NORMAL) {
+    $this->container->get('stream_wrapper_manager')->registerWrapper($scheme, $class, $type);
+  }
+
+  /**
+   * 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 = drupal_render($elements);
+    drupal_process_attached($elements);
+    $this->setRawContent($content);
+    $this->verbose('<pre style="white-space: pre-wrap">' . String::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);
+  }
+
+  /**
+   * BC alias for setSetting().
+   *
+   * @deprecated 8.0.x:8.0.0
+   * @see \Drupal\Tests\KernelTestBase::setSetting()
+   */
+  protected function settingsSet($name, $value) {
+    $this->triggerDeprecated(sprintf("KernelTestBase::%s() is deprecated. Use setSetting() instead.", __FUNCTION__));
+    $this->setSetting($name, $value);
+  }
+
+  /**
+   * Converts a list of possible parameters into a stack of permutations.
+   *
+   * Takes a list of parameters containing possible values, and converts all of
+   * them into a list of items containing every possible permutation.
+   *
+   * Example:
+   * @code
+   * $parameters = array(
+   *   'one' => array(0, 1),
+   *   'two' => array(2, 3),
+   * );
+   * $permutations = KernelTestBase::generatePermutations($parameters);
+   * // Result:
+   * $permutations == array(
+   *   array('one' => 0, 'two' => 2),
+   *   array('one' => 1, 'two' => 2),
+   *   array('one' => 0, 'two' => 3),
+   *   array('one' => 1, 'two' => 3),
+   * )
+   * @endcode
+   *
+   * @param array $parameters
+   *   An associative array of parameters, keyed by parameter name, and whose
+   *   values are arrays of parameter values.
+   *
+   * @return array
+   *   A list of permutations, which is an array of arrays. Each inner array
+   *   contains the full list of parameters that have been passed, but with a
+   *   single value only.
+   */
+  public static function generatePermutations(array $parameters) {
+    $all_permutations = array(array());
+    foreach ($parameters as $parameter => $values) {
+      $new_permutations = array();
+      // Iterate over all values of the parameter.
+      foreach ($values as $value) {
+        // Iterate over all existing permutations.
+        foreach ($all_permutations as $permutation) {
+          // Add the new parameter value to existing permutations.
+          $new_permutations[] = $permutation + array($parameter => $value);
+        }
+      }
+      // Replace the old permutations with the new permutations.
+      $all_permutations = $new_permutations;
+    }
+    return $all_permutations;
+  }
+
+  /**
+   * 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));
+    }
+  }
+
+  /**
+   * Sets an expected severe log message.
+   *
+   * @param int $severity
+   *   The expected log level severity constant (WATCHDOG_*).
+   * @param string $message
+   *   (optional) The expected log message to assert via
+   *   \PHPUnit_Framework_Assert::assertStringMatchesFormat().
+   *
+   * @see \Drupal\Tests\KernelTestBase::log()
+   *
+   * @todo Add support for `@expectedLogMessage*` test method annotations.
+   */
+  protected function setExpectedLogMessage($severity, $message = '') {
+    // Provide mapping from PSR log level to RFC log level.
+    $mapping = [
+      LogLevel::EMERGENCY => RfcLogLevel::EMERGENCY,
+      LogLevel::ALERT => RfcLogLevel::ALERT,
+      LogLevel::CRITICAL => RfcLogLevel::CRITICAL,
+      LogLevel::ERROR => RfcLogLevel::ERROR,
+      LogLevel::WARNING => RfcLogLevel::WARNING,
+      LogLevel::NOTICE => RfcLogLevel::NOTICE,
+      LogLevel::INFO => RfcLogLevel::INFO,
+      LogLevel::DEBUG => RfcLogLevel::DEBUG,
+    ];
+    if (isset($mapping[$severity])) {
+      $severity = $mapping[$severity];
+    }
+
+    $this->expectedLogSeverity = $severity;
+    $this->expectedLogMessage = $message;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @see \Drupal\Tests\KernelTestBase::setExpectedLogMessage()
+   * @see \Drupal\Tests\LogException
+   */
+  public function log($level, $message, array $context = array()) {
+    if (in_array($level, [RfcLogLevel::INFO, RfcLogLevel::NOTICE, RfcLogLevel::DEBUG, RfcLogLevel::ERROR, RfcLogLevel::ALERT])) {
+      $placeholders = $this->container->get('logger.log_message_parser')
+        ->parseMessagePlaceholders($message, $context);
+      if (!empty($placeholders)) {
+        $message = strtr($message, $placeholders);
+      }
+
+      // Assert an expected log message.
+      if (isset($this->expectedLogSeverity)) {
+        if (!empty($this->expectedLogMessage)) {
+          $this->expectedLogMessage = $this->logSeverityToString($this->expectedLogSeverity) . ': ' . $this->expectedLogMessage;
+          $message = $this->logSeverityToString($level) . ': ' . $message;
+          $this->assertStringMatchesFormat($this->expectedLogMessage, $message);
+        }
+        else {
+          $this->assertSame($this->expectedLogSeverity, $level, vsprintf("Expected log message severity '%s' does not match actual severity '%s'.", array(
+            $this->logSeverityToString($this->expectedLogSeverity),
+            $this->logSeverityToString($level),
+          )));
+        }
+        // If the test did not fail (and thus stop), reset the properties.
+        $this->expectedLogSeverity = NULL;
+        $this->expectedLogMessage = NULL;
+      }
+    }
+  }
+
+  /**
+   * Returns a string presentation of a log severity constant.
+   *
+   * @param int $severity
+   *   The WATCHDOG_* log severity constant to translate.
+   *
+   * @return string
+   *
+   * @throws \LogicException
+   *   If $severity is unknown.
+   */
+  private function logSeverityToString($severity) {
+    $names = ['EMERGENCY', 'ALERT', 'CRITICAL', 'ERROR', 'WARNING', 'NOTICE', 'INFO', 'DEBUG'];
+    if (isset($names[$severity])) {
+      return 'WATCHDOG_' . $names[$severity];
+    }
+    throw new \LogicException("Unknown log message severity '$severity'");
+  }
+
+  /**
+   * 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 8.0.x:8.0.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.
+      // $this->triggerDeprecated(sprintf("KernelTestBase::\$%s no longer exists. Use the regular API method to retrieve it instead (e.g., Settings).", $name));
+      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') {
+      $this->triggerDeprecated(sprintf("KernelTestBase::\$%s no longer exists. Use config_get_config_directory() directly instead.", $name));
+      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',
+      'databasePrefix', // @todo 
+      '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));
+    }
+  }
+
+  /**
+   * BC: Trigger warnings/exceptions for former KernelTestBase class properties.
+   *
+   * This is just an extra safety net. Legacy tests are only reading the former
+   * KernelTestBase properties. Drupal core is not aware of any tests that are
+   * trying to set or change them.
+   *
+   * Test authors should follow the provided instructions and adjust their tests
+   * accordingly.
+   *
+   * @deprecated 8.0.x:8.0.0
+   */
+  public function __set($name, $value) {
+    $this->__get($name);
+  }
+
+  /**
+   * Triggers a test framework feature deprecation warning.
+   *
+   * Does not halt test execution.
+   *
+   * @param string $message
+   *   The plain-text message to output (on CLI).
+   */
+  private function triggerDeprecated($message) {
+    // PHPUnit_Util_DeprecatedFeature no longer exists and is replaced by simply
+    // triggering E_USER_DEPRECATED errors in upcoming PHPUnit versions.
+    // @todo Remove this entire method after upgrading.
+    if (class_exists('PHPUnit_Util_DeprecatedFeature')) {
+      $message = get_class($this) . '::' . $this->getName() . "\n" . $message;
+      $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
+      $this->getTestResultObject()->addDeprecatedFeature(
+        new \PHPUnit_Util_DeprecatedFeature($message, $trace[1])
+      );
+    }
+    else {
+      trigger_error($message, E_USER_DEPRECATED);
+    }
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/KernelTestBaseTest.php b/core/tests/Drupal/Tests/KernelTestBaseTest.php
new file mode 100644
index 0000000..a0deb43
--- /dev/null
+++ b/core/tests/Drupal/Tests/KernelTestBaseTest.php
@@ -0,0 +1,245 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\KernelTestBaseTest.
+ */
+
+namespace Drupal\Tests;
+
+use org\bovigo\vfs\vfsStream;
+use org\bovigo\vfs\visitor\vfsStreamStructureVisitor;
+
+/**
+ * @coversDefaultClass \Drupal\Tests\KernelTestBase
+ * @group PHPUnit
+ */
+class KernelTestBaseTest extends KernelTestBase {
+
+  public static $modules = array('user');
+
+  /**
+   * @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('', $options['prefix']['default']);
+  }
+
+  /**
+   * @covers ::getDatabaseConnectionInfo
+   */
+  public function testGetDatabaseConnectionInfoWithManualSetDbUrl() {
+    putenv('PHPUNIT_DBURL=sqlite://localhost/:memory:');
+    $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(
+        'name' => array(
+          'type' => 'varchar',
+        ),
+      ),
+    ));
+    $this->assertTrue($schema->tableExists('foo'));
+  }
+
+  /**
+   * @covers ::setUp
+   * @depends testSetUp
+   */
+  public function testSetUpDoesNotLeak() {
+    $this->assertArrayNotHasKey('destroy-me', $GLOBALS);
+
+    $expected = [
+      'config' => 'config',
+      'cachetags' => 'cachetags',
+    ];
+    $schema = $this->container->get('database')->schema();
+    $this->assertEquals($expected, $schema->findTables('%'));
+  }
+
+  /**
+   * @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->installConfig('user');
+  }
+
+  /**
+   * @covers ::getCompiledContainerBuilder
+   * @depends testCompiledContainer
+   */
+  public function testCompiledContainerIsDestructed() {
+    $this->installConfig('user');
+  }
+
+  /**
+   * @covers ::render
+   */
+  public function testRender() {
+    $type = 'processed_text';
+    $element_info = $this->container->get('element_info');
+    $this->assertEmpty($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')->render($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_render($build);
+    $this->assertEquals('core', \Drupal::theme()->getActiveTheme()->getName());
+
+    $this->assertRegExp($expected, $build['#children']);
+    $this->assertRegExp($expected, $output);
+  }
+
+  /**
+   * @covers ::log
+   * @expectedException \ErrorException
+   * @expectedExceptionCode WATCHDOG_WARNING
+   * @expectedExceptionMessage Some problem.
+   */
+  public function testLog() {
+    $this->container->get('logger.factory')->get('system')->notice('Not a problem.');
+    $this->container->get('logger.factory')->get('system')->notice('Some problem.');
+  }
+
+  /**
+   * @covers ::__get
+   * @covers ::__set
+   * @expectedException \RuntimeException
+   * @dataProvider provider__get
+   */
+  public function test__get($property) {
+    $this->$property;
+  }
+
+  public function provider__get() {
+    return [
+      ['originalWhatever'],
+      ['public_files_directory'],
+      ['private_files_directory'],
+      ['temp_files_directory'],
+      ['translation_files_directory'],
+      ['generatedTestFiles'],
+    ];
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/LogException.php b/core/tests/Drupal/Tests/LogException.php
new file mode 100644
index 0000000..f84139a
--- /dev/null
+++ b/core/tests/Drupal/Tests/LogException.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\LogException.
+ */
+
+namespace Drupal\Tests;
+
+/**
+ * Exception thrown in case of unexpected severe log messages.
+ */
+class LogException extends \PHPUnit_Framework_Exception implements \PHPUnit_Framework_SelfDescribing {
+
+  /**
+   * @var int
+   */
+  protected $severity;
+
+  /**
+   * Constructs a new LogException.
+   *
+   * @param string $message
+   *   The log message.
+   * @param int $severity
+   *   The log message severity.
+   * @param \Exception $previous
+   *   (optional) A previously thrown exception.
+   */
+  public function __construct($message = '', $severity = 0, \Exception $previous = NULL) {
+    parent::__construct($message, $severity, $previous);
+    $this->severity = $severity;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function toString() {
+    $names = ['EMERGENCY', 'ALERT', 'CRITICAL', 'ERROR', 'WARNING', 'NOTICE', 'INFO', 'DEBUG'];
+    if (isset($names[$this->severity])) {
+      $name = 'WATCHDOG_' . $names[$this->severity];
+    }
+    else {
+      $name = 'UNKNOWN';
+    }
+    return $name . ': ' . $this->getMessage();
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/VfsStreamWrapperWrapper.php b/core/tests/Drupal/Tests/VfsStreamWrapperWrapper.php
new file mode 100644
index 0000000..cf0dda8
--- /dev/null
+++ b/core/tests/Drupal/Tests/VfsStreamWrapperWrapper.php
@@ -0,0 +1,151 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Tests\VfsStreamWrapperWrapper.
+ */
+
+namespace Drupal\Tests;
+
+use Drupal\Core\StreamWrapper\StreamWrapperInterface;
+use org\bovigo\vfs\vfsStreamWrapper;
+
+class VfsStreamWrapperWrapper implements StreamWrapperInterface {
+
+  protected $uri;
+
+  public function __construct() {
+    $this->streamWrapper = new VfsStreamWrapper;
+  }
+
+  public function dir_closedir() {
+    return $this->streamWrapper->dir_closedir();
+  }
+
+  public function dir_opendir($path, $options) {
+    return $this->streamWrapper->dir_opendir($path, $options);
+  }
+
+  public function dir_readdir() {
+    return $this->streamWrapper->dir_readdir();
+  }
+
+  public function dir_rewinddir() {
+    return $this->streamWrapper->dir_rewinddir();
+  }
+
+  public function mkdir($path, $mode, $options) {
+    return $this->streamWrapper->mkdir($path, $mode, $options);
+  }
+
+  public function rename($path_from, $path_to) {
+    return $this->streamWrapper->rename($path_from, $path_to);
+  }
+
+  public function rmdir($path, $options) {
+    return $this->streamWrapper->rmdir($path, $options);
+  }
+
+  public function stream_cast($cast_as) {
+    return $this->streamWrapper->stream_cast($cast_as);
+  }
+
+  public function stream_close() {
+    $this->streamWrapper->stream_close();
+  }
+
+  public function stream_eof() {
+    return $this->streamWrapper->stream_eof();
+  }
+
+  public function stream_flush() {
+    return $this->streamWrapper->stream_flush();
+  }
+
+  public function stream_lock($operation) {
+    return $this->streamWrapper->stream_lock($operation);
+  }
+
+  public function stream_metadata($path, $option, $value) {
+    return $this->streamWrapper->stream_metadata($path, $option, $value);
+  }
+
+  public function stream_open($path, $mode, $options, &$opened_path) {
+    return $this->streamWrapper->stream_open($path, $mode, $options, $opened_path);
+  }
+
+  public function stream_read($count) {
+    return $this->streamWrapper->stream_read($count);
+  }
+
+  public function stream_seek($offset, $whence = SEEK_SET) {
+    return $this->streamWrapper->stream_seek($offset, $whence = SEEK_SET);
+  }
+
+  public function stream_set_option($option, $arg1, $arg2) {
+    return $this->streamWrapper->stream_set_option($option, $arg1, $arg2);
+  }
+
+  public function stream_stat() {
+    return $this->streamWrapper->stream_stat();
+  }
+
+  public function stream_tell() {
+    return $this->streamWrapper->stream_tell();
+  }
+
+  public function stream_truncate($new_size) {
+    return $this->streamWrapper->stream_truncate($new_size);
+  }
+
+  public function stream_write($data) {
+    return $this->streamWrapper->stream_write($data);
+  }
+
+  public function unlink($path) {
+    return $this->streamWrapper->unlink($path);
+  }
+
+  public function url_stat($path, $flags) {
+    return $this->streamWrapper->url_stat($path, $flags);
+  }
+
+  public static function getType() {
+    return StreamWrapperInterface::ALL;
+  }
+
+  public function getName() {
+    return 'vfs';
+  }
+
+  public function getDescription() {
+    return 'virtual file system.';
+  }
+
+  public function setUri($uri) {
+    $this->uri = $uri;
+  }
+
+  public function getUri() {
+    return $this->uri;
+  }
+
+  public function getExternalUrl() {
+    return NULL;
+  }
+
+  public function realpath() {
+    return NULL;
+  }
+
+  public function dirname($uri = NULL) {
+    $list = explode('/', $uri);
+    array_pop($list);
+    return implode('/', $list);
+  }
+
+  public function getDirectoryPath() {
+    return 'root';
+  }
+
+}
diff --git a/core/tests/bootstrap.php b/core/tests/bootstrap.php
index 76f745c..b030926 100644
--- a/core/tests/bootstrap.php
+++ b/core/tests/bootstrap.php
@@ -20,7 +20,7 @@ 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();
     }
@@ -35,54 +35,76 @@ 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();
-  // Note this also checks sites/../modules and sites/../profiles.
+  $root = dirname(dirname(__DIR__));
+  $paths = array(
+    $root . '/core/modules',
+    $root . '/core/profiles',
+    $root . '/modules',
+    $root . '/profiles',
+  );
+  $sites_path = $root . '/sites';
   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;
+    if (is_dir("$path/modules")) {
+      $paths[] = "$path/modules";
+    }
+    if (is_dir("$path/profiles")) {
+      $paths[] = "$path/profiles";
+    }
   }
-  return array_filter($paths);
+  return $paths;
 }
 
 /**
  * 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.
  */
-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\\' . $extension . '\Tests\\'][] = $dir . '/tests/src';
     }
   }
+  return $namespaces;
 }
 
 // Start with classes in known locations.
 $loader = require __DIR__ . '/../vendor/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);
+}
 
 // Look into removing these later.
-define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);
+// PHPUnit process isolation template re-defines constants and reloads included
+// files (bootstrap.inc) before including this file (bootstrap.php).
+// @todo Fix this upstream and/or use a custom child process template.
+if (!defined('REQUEST_TIME')) {
+  define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);
+}
+if (!defined('DRUPAL_ROOT')) {
+  define('DRUPAL_ROOT', realpath(__DIR__ . '/../../'));
+}
 
 // Set sane locale settings, to ensure consistent string, dates, times and
 // numbers handling.
@@ -92,3 +114,6 @@ function drupal_phpunit_register_extension_dirs(Composer\Autoload\ClassLoader $l
 // Set the default timezone. While this doesn't cause any tests to fail, PHP
 // complains if 'date.timezone' is not set in php.ini.
 date_default_timezone_set('UTC');
+
+// Clean up.
+unset($extension_roots, $dirs);
