diff --git a/core/core.services.yml b/core/core.services.yml
index f3c6cd9..bf55eb5 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -25,6 +25,10 @@ services:
   cache.backend.database:
     class: Drupal\Core\Cache\DatabaseBackendFactory
     arguments: ['@database']
+  cache.backend.memory:
+    class: Drupal\Core\Cache\MemoryBackendFactory
+  cache.backend.null:
+    class: Drupal\Core\Cache\NullBackendFactory
   cache.bootstrap:
     class: Drupal\Core\Cache\CacheBackendInterface
     tags:
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 1f72423..9ab82f7 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -3643,7 +3643,8 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) {
   // Collect all #post_render_cache callbacks associated with this element when:
   // - about to store this element in the render cache, or when;
   // - about to apply #post_render_cache callbacks.
-  if (isset($elements['#cache']) || !$is_recursive_call) {
+  // drupal_render_collect_post_render_cache() recurses into children itself.
+  if (!$is_recursive_call && isset($elements['#cache'])) {
     $post_render_cache = drupal_render_collect_post_render_cache($elements);
     if ($post_render_cache) {
       $elements['#post_render_cache'] = $post_render_cache;
@@ -3755,12 +3756,12 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) {
 
   // Collect all cache tags. This allows the caller of drupal_render() to also
   // access the complete list of cache tags.
-  if (!$is_recursive_call || isset($elements['#cache'])) {
-    $elements['#cache']['tags'] = drupal_render_collect_cache_tags($elements);
-  }
-
-  // Cache the processed element if #cache is set.
   if (isset($elements['#cache'])) {
+    // drupal_render_collect_cache_tags() recurses into children on its own.
+    if (!$is_recursive_call) {
+      $elements['#cache']['tags'] = drupal_render_collect_cache_tags($elements);
+    }
+    // Cache the processed element if #cache is set.
     drupal_render_cache_set($elements['#markup'], $elements);
   }
 
diff --git a/core/includes/form.inc b/core/includes/form.inc
index 54feee0..9c9b850 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -2902,7 +2902,12 @@ function template_preprocess_form_element(&$variables) {
   // Add label_display and label variables to template.
   $variables['label_display'] = $element['#title_display'];
   $variables['label'] = array('#theme' => 'form_element_label');
-  $variables['label'] += array_intersect_key($element, array_flip(array('#id', '#required', '#title', '#title_display')));
+  $variables['label'] += array_intersect_key($element, array(
+    '#id' => 1,
+    '#required' => 1,
+    '#title' => 1,
+    '#title_display' => 1,
+  ));
 
   $variables['children'] = $element['#children'];
 }
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 52ce316..999082a 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -514,11 +514,13 @@ function _theme($hook, $variables = array()) {
   // function may call _theme('node', ...), but a module can add
   // 'node__article' as a suggestion via hook_theme_suggestions_HOOK_alter(),
   // enabling a theme to have an alternate template file for article nodes.
-  foreach (array_reverse($suggestions) as $suggestion) {
+  end($suggestions);
+  while ($suggestion = current($suggestions)) {
     if ($theme_registry->has($suggestion)) {
       $info = $theme_registry->get($suggestion);
       break;
     }
+    prev($suggestions);
   }
 
   // Include a file if the theme function or variable preprocessor is held
diff --git a/core/lib/Drupal/Core/Cache/NullBackendFactory.php b/core/lib/Drupal/Core/Cache/NullBackendFactory.php
new file mode 100644
index 0000000..0101a8e
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/NullBackendFactory.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\NullBackendFactory.
+ */
+
+namespace Drupal\Core\Cache;
+
+class NullBackendFactory implements CacheFactoryInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  function get($bin) {
+    return new NullBackend($bin);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index aa8369a..6a285f1 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -1332,8 +1332,8 @@ public function doBuildForm($form_id, &$element, &$form_state) {
     // Allow for elements to expand to multiple elements, e.g., radios,
     // checkboxes and files.
     if (isset($element['#process']) && !$element['#processed']) {
-      foreach ($element['#process'] as $process) {
-        $element = call_user_func_array($process, array(&$element, &$form_state, &$form_state['complete_form']));
+      foreach ($element['#process'] as $callable) {
+        $element = $callable($element, $form_state, $form_state['complete_form']);
       }
       $element['#processed'] = TRUE;
     }
@@ -1397,7 +1397,7 @@ public function doBuildForm($form_id, &$element, &$form_state) {
     // after normal input parsing has been completed.
     if (isset($element['#after_build']) && !isset($element['#after_build_done'])) {
       foreach ($element['#after_build'] as $callable) {
-        $element = call_user_func_array($callable, array($element, &$form_state));
+        $element = $callable($element, $form_state);
       }
       $element['#after_build_done'] = TRUE;
     }
@@ -1541,7 +1541,7 @@ protected function handleInputElement($form_id, &$element, &$form_state) {
         // property, optionally filtered through $value_callback.
         if ($input_exists) {
           if (is_callable($value_callable)) {
-            $element['#value'] = call_user_func_array($value_callable, array(&$element, $input, &$form_state));
+            $element['#value'] = $value_callable($element, $input, $form_state);
           }
           if (!isset($element['#value']) && isset($input)) {
             $element['#value'] = $input;
@@ -1557,7 +1557,7 @@ protected function handleInputElement($form_id, &$element, &$form_state) {
         // Call #type_value without a second argument to request default_value
         // handling.
         if (is_callable($value_callable)) {
-          $element['#value'] = call_user_func_array($value_callable, array(&$element, FALSE, &$form_state));
+          $element['#value'] = $value_callable($element, FALSE, $form_state);
         }
         // Final catch. If we haven't set a value yet, use the explicit default
         // value. Avoid image buttons (which come with garbage value), so we
diff --git a/core/lib/Drupal/Core/Utility/ThemeRegistry.php b/core/lib/Drupal/Core/Utility/ThemeRegistry.php
index dbd44e9..fdddccd 100644
--- a/core/lib/Drupal/Core/Utility/ThemeRegistry.php
+++ b/core/lib/Drupal/Core/Utility/ThemeRegistry.php
@@ -102,7 +102,7 @@ public function has($key) {
     // are not registered, just check the existence of the key in the registry.
     // Use array_key_exists() here since a NULL value indicates that the theme
     // hook exists but has not yet been requested.
-    return array_key_exists($key, $this->storage);
+    return isset($this->storage[$key]) || array_key_exists($key, $this->storage);
   }
 
   /**
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigLanguageOverride.php b/core/modules/config/lib/Drupal/config/Tests/ConfigLanguageOverrideTest.php
similarity index 100%
rename from core/modules/config/lib/Drupal/config/Tests/ConfigLanguageOverride.php
rename to core/modules/config/lib/Drupal/config/Tests/ConfigLanguageOverrideTest.php
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestDiscovery.php b/core/modules/simpletest/lib/Drupal/simpletest/TestDiscovery.php
new file mode 100644
index 0000000..3b1ff0c
--- /dev/null
+++ b/core/modules/simpletest/lib/Drupal/simpletest/TestDiscovery.php
@@ -0,0 +1,176 @@
+<?php
+
+/**
+ * @file
+ * Test discovery.
+ */
+
+namespace Drupal\simpletest;
+
+use Drupal\Core\Extension\ExtensionDiscovery;
+
+class TestDiscovery {
+
+  /**
+   * Cached class map of all test classes discovered in extensions.
+   *
+   * @var array
+   */
+  protected static $classmap;
+
+  /**
+   * Cached list of all discovered tests.
+   *
+   * @var array
+   */
+  protected $allTests;
+
+  /**
+   * Discovers all available tests in all extensions.
+   *
+   * @return array
+   *   An array of tests keyed with the groups specified in each of the test's
+   *   getInfo() methods and then keyed by the test classes. For example:
+   *   @code
+   *     $groups['Block'] => array(
+   *       'BlockTestCase' => array(
+   *         'name' => 'Block functionality',
+   *         'description' => 'Add, edit and delete custom block...',
+   *         'group' => 'Block',
+   *       ),
+   *     );
+   *   @endcode
+   */
+  public function find() {
+    if (isset($this->allTests)) {
+      return $this->allTests;
+    }
+    // Register test namespaces and test classes of all available extensions.
+    // (including disabled)
+    $classes = $this->registerTestClasses();
+
+    // Check that each class has a getInfo() method and store the information
+    // in an array keyed with the group specified in the test information.
+    $groups = array();
+    foreach ($classes as $class => $pathname) {
+      // Test classes need to implement getInfo() to be valid.
+      if (method_exists($class, 'getInfo')) {
+        $reflectionClass = new \ReflectionClass($class);
+        // Skip abstract classes and interfaces.
+        if (!$reflectionClass->isInstantiable()) {
+          continue;
+        }
+        $reflectionMethod = new \ReflectionMethod($class, 'getInfo');
+        $declaringClass = $reflectionMethod->getDeclaringClass()->getName();
+        // Avoid testing intermediate classes which do not implement the
+        // method.
+        if ($class != $declaringClass) {
+          continue;
+        }
+        $info = $class::getInfo();
+
+        // If this test class requires a non-existing module, skip it.
+        if (!empty($info['dependencies'])) {
+          foreach ($info['dependencies'] as $dependency) {
+            if (!isset($dependency_data[$dependency])) {
+              continue 2;
+            }
+          }
+        }
+
+        $groups[$info['group']][$class] = $info;
+      }
+    }
+
+    // Sort the groups and tests within the groups by name.
+    uksort($groups, 'strnatcasecmp');
+    foreach ($groups as &$tests) {
+      uksort($tests, 'strnatcasecmp');
+    }
+
+    // Allow modules extending core tests to disable originals.
+    \Drupal::moduleHandler()->alter('simpletest', $groups);
+
+    $this->allTests = $groups;
+    return $groups;
+  }
+
+  /**
+   * Registers test classes for all available extensions.
+   */
+  public function registerTestClasses() {
+    if (isset(static::$classmap)) {
+      return static::$classmap;
+    }
+    static::$classmap = array();
+    $loader = drupal_classloader();
+    $existing = $loader->getPrefixes();
+
+    // Register the core tests directory.
+    $loader->add('Drupal\\Tests', DRUPAL_ROOT . '/core/tests');
+
+    $flags = \FilesystemIterator::UNIX_PATHS;
+    $flags |= \FilesystemIterator::SKIP_DOTS;
+    $flags |= \FilesystemIterator::FOLLOW_SYMLINKS;
+    $flags |= \FilesystemIterator::CURRENT_AS_SELF;
+    $extensions = $this->getExtensions();
+    foreach ($extensions as $name => $extension) {
+      $base_namespace = 'Drupal\\' . $name;
+      $base_path = DRUPAL_ROOT . '/' . $extension->getPath();
+      // Register the namespace of disabled/uninstalled extensions.
+      if (!isset($existing[$base_namespace])) {
+        $loader->add($base_namespace, $base_path . '/lib');
+      }
+      // While being there, prime drupal_get_filename().
+      drupal_get_filename($extension->getType(), $name, $extension->getPathname());
+
+      // Register PHPUnit test namespace.
+      $loader->add($base_namespace . '\\Tests', $base_path . '/tests');
+
+      $test_path = $base_path . '/lib/Drupal/' . $name . '/Tests';
+      if (!is_dir($test_path)) {
+        continue;
+      }
+      $test_namespace = $base_namespace . '\\Tests';
+
+      // Discover all test classes and add them to the classmap.
+      $iterator = new \RecursiveDirectoryIterator($test_path, $flags);
+      $filter = new \RecursiveCallbackFilterIterator($iterator, function ($current, $key, $iterator) {
+        if ($iterator->hasChildren()) {
+          return TRUE;
+        }
+        return $current->isFile() && $current->getExtension() === 'php';
+      });
+      $files = new \RecursiveIteratorIterator($filter);
+
+      foreach ($files as $fileinfo) {
+        $class = $test_namespace;
+        if ('' !== $subpath = $fileinfo->getSubPath()) {
+          $class .= '\\' . strtr($subpath, '/', '\\');
+        }
+        $class .= '\\' . $fileinfo->getBasename('.php');
+        static::$classmap[$class] = $fileinfo->getPathname();
+      }
+    }
+    $loader->addClassMap(static::$classmap);
+
+    return static::$classmap;
+  }
+
+  /**
+   * Returns all available extensions.
+   *
+   * @return \Drupal\Core\Extension\Extension[]
+   *   An array of Extension objects, keyed by extension name.
+   */
+  protected function getExtensions() {
+    $listing = new ExtensionDiscovery();
+    // Ensure that tests in all profiles are discovered.
+    $listing->setProfileDirectories(array());
+    $extensions = $listing->scan('module', TRUE);
+    $extensions += $listing->scan('profile', TRUE);
+    $extensions += $listing->scan('theme', TRUE);
+    return $extensions;
+  }
+
+}
diff --git a/core/modules/simpletest/simpletest.module b/core/modules/simpletest/simpletest.module
index adc5bda..ff02404 100644
--- a/core/modules/simpletest/simpletest.module
+++ b/core/modules/simpletest/simpletest.module
@@ -434,149 +434,14 @@ function simpletest_log_read($test_id, $database_prefix, $test_class) {
  *   @endcode
  */
 function simpletest_test_get_all($module = NULL) {
-  static $all_groups = array();
-  $cid = "simpletest:$module";
-
-  if (!isset($all_groups[$cid])) {
-    $all_groups[$cid] = array();
-    $groups = &$all_groups[$cid];
-    // Register namespaces (extensions are not necessarily enabled).
-    simpletest_classloader_register();
-
-    // Load test information from cache if available, otherwise retrieve the
-    // information from each tests getInfo() method.
-    if ($cache = \Drupal::cache()->get($cid)) {
-      $groups = $cache->data;
-    }
-    else {
-      // Select all PSR-0 classes in the Tests namespace of all modules.
-      $listing = new ExtensionDiscovery();
-      // Ensure that tests in all profiles are discovered.
-      $listing->setProfileDirectories(array());
-      $all_data = $listing->scan('module', TRUE);
-      // If module is set then we keep only that one module.
-      if (isset($module)) {
-        $all_data = array(
-          $module => $all_data[$module],
-        );
-      }
-      else {
-        $all_data += $listing->scan('profile', TRUE);
-        $all_data += $listing->scan('theme', TRUE);
-      }
-      $classes = array();
-      foreach ($all_data as $name => $data) {
-        // Build directory in which the test files would reside.
-        $tests_dir = DRUPAL_ROOT . '/' . $data->getPath() . '/lib/Drupal/' . $name . '/Tests';
-        // Scan it for test files if it exists.
-        if (is_dir($tests_dir)) {
-          $files = file_scan_directory($tests_dir, '/\.php$/');
-          if (!empty($files)) {
-            $basedir = DRUPAL_ROOT . '/' . $data->getPath() . '/lib/';
-            foreach ($files as $file) {
-              // Convert the file name into the namespaced class name.
-              $replacements = array(
-                '/' => '\\',
-                $basedir => '',
-                '.php' => '',
-              );
-              $classes[] = strtr($file->uri, $replacements);
-            }
-          }
-        }
-      }
-
-      // Check that each class has a getInfo() method and store the information
-      // in an array keyed with the group specified in the test information.
-      $groups = array();
-      foreach ($classes as $class) {
-        // Test classes need to implement getInfo() to be valid.
-        if (class_exists($class) && method_exists($class, 'getInfo')) {
-          $reflectionClass = new ReflectionClass($class);
-          // Skip abstract classes and interfaces.
-          if ($reflectionClass->isInstantiable()) {
-            $reflectionMethod = new ReflectionMethod($class, 'getInfo');
-            $declaringClass = $reflectionMethod->getDeclaringClass()->getName();
-            // Avoid testing intermediate classes which do not implement the
-            // method.
-            if ($class == $declaringClass) {
-              $info = call_user_func(array($class, 'getInfo'));
-            }
-            else {
-              continue;
-            }
-          }
-          else {
-            continue;
-          }
-          // If this test class requires a non-existing module, skip it.
-          if (!empty($info['dependencies'])) {
-            foreach ($info['dependencies'] as $dependency) {
-              if (!isset($dependency_data[$dependency])) {
-                continue 2;
-              }
-            }
-          }
-
-          $groups[$info['group']][$class] = $info;
-        }
-      }
-
-      // Sort the groups and tests within the groups by name.
-      uksort($groups, 'strnatcasecmp');
-      foreach ($groups as &$tests) {
-        uksort($tests, 'strnatcasecmp');
-      }
-
-      // Allow modules extending core tests to disable originals.
-      \Drupal::moduleHandler()->alter('simpletest', $groups);
-      \Drupal::cache()->set($cid, $groups);
-    }
-  }
-  return $all_groups[$cid];
+  return \Drupal::service('test_discovery')->find();
 }
 
 /**
  * Registers namespaces for disabled modules.
  */
 function simpletest_classloader_register() {
-  // Use the same cache prefix as simpletest_test_get_all().
-  $cid = "simpletest::all";
-  $types = array(
-    'theme_engine',
-    'module',
-    'theme',
-    'profile',
-  );
-
-  if ($cache = \Drupal::cache()->get($cid)) {
-    $extensions = $cache->data;
-  }
-  else {
-    $listing = new ExtensionDiscovery();
-    // Ensure that tests in all profiles are discovered.
-    $listing->setProfileDirectories(array());
-    $extensions = array();
-    foreach ($types as $type) {
-      foreach ($listing->scan($type, TRUE) as $name => $file) {
-        $extensions[$type][$name] = $file->getPathname();
-      }
-    }
-    \Drupal::cache()->set($cid, $extensions);
-  }
-
-  $classloader = drupal_classloader();
-  foreach ($types as $type) {
-    foreach ($extensions[$type] as $name => $uri) {
-      drupal_classloader_register($name, dirname($uri));
-      $classloader->add('Drupal\\' . $name . '\\Tests', DRUPAL_ROOT . '/' . dirname($uri) . '/tests');
-      // While being there, prime drupal_get_filename().
-      drupal_get_filename($type, $name, $uri);
-    }
-  }
-
-  // Register the core test directory so we can find \Drupal\UnitTestCase.
-  $classloader->add('Drupal\\Tests', DRUPAL_ROOT . '/core/tests');
+  \Drupal::service('test_discovery')->registerTestNamespaces();
 }
 
 /**
diff --git a/core/modules/simpletest/simpletest.services.yml b/core/modules/simpletest/simpletest.services.yml
new file mode 100644
index 0000000..b6c75e2
--- /dev/null
+++ b/core/modules/simpletest/simpletest.services.yml
@@ -0,0 +1,3 @@
+services:
+  test_discovery:
+    class: Drupal\simpletest\TestDiscovery
