From f2743e8a2aeb0b54e08b07a4171ae34e3fffc45e Mon Sep 17 00:00:00 2001
From: Dries <dries@buytaert.net>
Date: Wed, 6 Mar 2013 21:48:50 -0500
Subject: [PATCH] Issue #1901670 by msonnabaum, kim.pepper, effulgentsia: Start
 using PHPUnit for unit tests.

---
 .../Tests/BreakpointMediaQueryTest.php        |   8 +-
 core/modules/simpletest/simpletest.module     | 190 +++++++++++++++++-
 core/modules/simpletest/simpletest.pages.inc  |  11 +-
 core/phpunit.xml.dist                         |  16 ++
 core/scripts/run-tests.sh                     | 128 ++++++++++--
 .../BackendChainImplementationUnitTest.php    |  88 ++++----
 .../Tests/Core}/Cache/NullBackendTest.php     |  10 +-
 .../Tests/Core}/NestedArrayUnitTest.php       |  34 ++--
 core/tests/Drupal/Tests/UnitTestCase.php      |  61 ++++++
 core/tests/bootstrap.php                      |  13 ++
 10 files changed, 476 insertions(+), 83 deletions(-)
 rename core/modules/breakpoint/{lib => tests}/Drupal/breakpoint/Tests/BreakpointMediaQueryTest.php (94%)
 create mode 100644 core/phpunit.xml.dist
 rename core/{modules/system/lib/Drupal/system/Tests => tests/Drupal/Tests/Core}/Cache/BackendChainImplementationUnitTest.php (72%)
 rename core/{modules/system/lib/Drupal/system/Tests => tests/Drupal/Tests/Core}/Cache/NullBackendTest.php (77%)
 rename core/{modules/system/lib/Drupal/system/Tests/Common => tests/Drupal/Tests/Core}/NestedArrayUnitTest.php (70%)
 create mode 100644 core/tests/Drupal/Tests/UnitTestCase.php
 create mode 100644 core/tests/bootstrap.php

diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointMediaQueryTest.php b/core/modules/breakpoint/tests/Drupal/breakpoint/Tests/BreakpointMediaQueryTest.php
similarity index 94%
rename from core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointMediaQueryTest.php
rename to core/modules/breakpoint/tests/Drupal/breakpoint/Tests/BreakpointMediaQueryTest.php
index d0ee5fba155d..1e2524f51973 100644
--- a/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointMediaQueryTest.php
+++ b/core/modules/breakpoint/tests/Drupal/breakpoint/Tests/BreakpointMediaQueryTest.php
@@ -6,14 +6,16 @@
 
 namespace Drupal\breakpoint\Tests;
 
-use Drupal\simpletest\UnitTestBase;
+use Drupal\Tests\UnitTestCase;
 use Drupal\breakpoint\Plugin\Core\Entity\Breakpoint;
 use Drupal\breakpoint\InvalidBreakpointMediaQueryException;
 
 /**
  * Tests for media queries in a breakpoint.
+ *
+ * @group Breakpoint
  */
-class BreakpointMediaQueryTest extends UnitTestBase {
+class BreakpointMediaQueryTest extends UnitTestCase {
 
   public static function getInfo() {
     return array(
@@ -116,7 +118,7 @@ public function testInvalidMediaQueries() {
         $this->assertFalse(Breakpoint::isValidMediaQuery($media_query), $media_query . ' is not valid.');
       }
       catch (InvalidBreakpointMediaQueryException $e) {
-        $this->assertTrue(TRUE, format_string('%media_query is not valid.', array('%media_query' => $media_query)));
+        $this->assertTrue(TRUE, sprintf('%s is not valid.', $media_query));
       }
     }
   }
diff --git a/core/modules/simpletest/simpletest.module b/core/modules/simpletest/simpletest.module
index 760947f4716e..1ecc40f06586 100644
--- a/core/modules/simpletest/simpletest.module
+++ b/core/modules/simpletest/simpletest.module
@@ -131,6 +131,18 @@ function simpletest_run_tests($test_list, $reporter = 'drupal') {
     ->useDefaults(array('test_id'))
     ->execute();
 
+  $phpunit_tests = isset($test_list['UnitTest']) ? $test_list['UnitTest'] : array();
+  $phpunit_results = simpletest_run_phpunit_tests($test_id, $phpunit_tests);
+  simpletest_process_phpunit_results($phpunit_results);
+
+  if (!array_key_exists('WebTest', $test_list) || empty($test_list['WebTest'])) {
+    // Early return if there are no WebTests to run.
+    return $test_id;
+  }
+
+  // Contine with SimpleTests only.
+  $test_list = $test_list['WebTest'];
+
   // Clear out the previous verbose files.
   file_unmanaged_delete_recursive('public://simpletest/verbose');
 
@@ -157,6 +169,104 @@ function simpletest_run_tests($test_list, $reporter = 'drupal') {
   return $test_id;
 }
 
+/**
+ * Executes phpunit tests and returns the results of the run.
+ *
+ * @param $test_id
+ *   The current test ID.
+ * @param $unescaped_test_classnames
+ *   An array of test class names, including full namespaces, to be passed as
+ *   a regular expression to phpunit's --filter option.
+ *
+ * @return array
+ *   The parsed results of phpunit's junit xml output, in the format of the
+ *   simpletest table's schema.
+ */
+function simpletest_run_phpunit_tests($test_id, array $unescaped_test_classnames) {
+  $phpunit_file = simpletest_phpunit_xml_filepath($test_id);
+  simpletest_phpunit_run_command($unescaped_test_classnames, $phpunit_file);
+  return simpletest_phpunit_xml_to_rows($test_id, $phpunit_file);
+}
+
+/**
+ * Inserts the parsed phpunit results into the simpletest table.
+ *
+ * @param array $phpunit_results
+ *   An array of test results returned from simpletest_phpunit_xml_to_rows.
+ */
+function simpletest_process_phpunit_results($phpunit_results) {
+  // Insert the results of the phpunit test run into the db so the results are
+  // displayed along with simpletest's results.
+  if (!empty($phpunit_results)) {
+    $query = db_insert('simpletest')->fields(array_keys($phpunit_results[0]));
+    foreach ($phpunit_results as $result) {
+      $query->values($result);
+    }
+    $query->execute();
+  }
+}
+
+/**
+ * Returns the path to use for phpunit's --log-junit option.
+ *
+ * @param $test_id
+ *   The current test ID.
+ * @return string
+ *   Path to the phpunit xml file to use for the current test_id.
+ */
+function simpletest_phpunit_xml_filepath($test_id) {
+  return drupal_realpath('public://simpletest') . '/phpunit-' . $test_id . '.xml';
+}
+
+/**
+ * Returns the path to core's phpunit.xml.dist configuration file.
+ *
+ * @return string
+ *   Path to core's phpunit.xml.dist configuration file.
+ */
+function simpletest_phpunit_configuration_filepath() {
+  return DRUPAL_ROOT . '/core/phpunit.xml.dist';
+}
+
+/**
+ * Executes the phpunit command.
+ *
+ * @param array $unescaped_test_classnames
+ *   An array of test class names, including full namespaces, to be passed as
+ *   a regular expression to phpunit's --filter option.
+ * @param string $phpunit_file
+ *   A filepath to use for phpunit's --log-junit option.
+ */
+function simpletest_phpunit_run_command(array $unescaped_test_classnames, $phpunit_file) {
+  $phpunit_bin = DRUPAL_ROOT . "/core/vendor/bin/phpunit";
+
+  // Double escape namespaces so they'll work in a regexp.
+  $escaped_test_classnames = array_map(function($class) {
+    return addslashes($class);
+  }, $unescaped_test_classnames);
+
+  $filter_string = implode("|", $escaped_test_classnames);
+
+  $command = array(
+    $phpunit_bin,
+    '--filter',
+    escapeshellarg($filter_string),
+    '--log-junit',
+    escapeshellarg($phpunit_file),
+  );
+
+  // Need to change directories before running the command so that we can use
+  // relative paths in the configuration file's exclusions.
+  $old_cwd = getcwd();
+  chdir(DRUPAL_ROOT . "/core");
+
+  // exec in a subshell so that the environment is isolated when running tests
+  // via the simpletest UI.
+  $ret = exec(join($command, " "));
+  chdir($old_cwd);
+  return $ret;
+}
+
 /**
  * Batch operation callback.
  */
@@ -398,10 +508,23 @@ function simpletest_classloader_register() {
     $matches = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.' . $info['extension'] . '$/', $info['dir']);
     foreach ($matches as $name => $file) {
       drupal_classloader_register($name, dirname($file->uri));
+      drupal_classloader()->registerNamespace('Drupal\\' . $name . '\\Tests', DRUPAL_ROOT . '/' . dirname($file->uri) . '/tests');
       // While being there, prime drupal_get_filename().
       drupal_get_filename($type, $name, $file->uri);
     }
   }
+
+  // Register the core test directory so we can find Drupal\UnitTestCase.
+  drupal_classloader()->registerNamespace('Drupal\\Tests', DRUPAL_ROOT . '/core/tests');
+
+  // Manually register phpunit prefixes because they use a classmap instead of a
+  // prefix. This can be safely removed if we move to using composer's
+  // autoloader with a classmap.
+  drupal_classloader()->registerPrefixes(array(
+    'PHPUnit' => DRUPAL_ROOT . '/core/vendor/phpunit/phpunit',
+    'File_Iterator' => DRUPAL_ROOT . '/core/vendor/phpunit/php-file-iterator/',
+    'PHP_Timer' => DRUPAL_ROOT . '/core/vendor/phpunit/php-timer/',
+  ));
 }
 
 /**
@@ -452,7 +575,8 @@ function simpletest_clean_environment() {
 }
 
 /**
- * Removed prefixed tables from the database that are left over from crashed tests.
+ * Removed prefixed tables from the database that are left over from crashed
+ * tests.
  */
 function simpletest_clean_database() {
   $tables = db_find_tables(Database::getConnection()->prefixTables('{simpletest}') . '%');
@@ -569,3 +693,67 @@ function simpletest_library_info() {
 
   return $libraries;
 }
+
+/**
+ * Get PHPUnit Classes
+ *
+ * @param bool $name_only
+ *  If TRUE, returns a flat array of class names only.
+ */
+function simpletest_phpunit_get_available_tests() {
+  // Load the PHPUnit configuration file, which tells us where to find the
+  // tests.
+  $phpunit_config = simpletest_phpunit_configuration_filepath();
+  $configuration = PHPUnit_Util_Configuration::getInstance($phpunit_config);
+  // Find all the tests and get a list of unique class names.
+  $test_suite = $configuration->getTestSuiteConfiguration(NULL);
+  $test_classes = array();
+  foreach ($test_suite AS $test) {
+    $name = get_class($test);
+    if (!array_key_exists($name, $test_classes)) {
+      $test_classes[$name] =  $test->getInfo();
+    }
+  }
+
+  return $test_classes;
+}
+
+/**
+ * Converts phpunit's junit xml output to an array.
+ *
+ * The returned array of rows is in a format that can be inserted into the
+ * simpletest results table.
+ *
+ * @param $test_id
+ *   The current test ID.
+ * @param $phpunit_xml_file
+ *   Path to the phpunit xml file.
+ */
+function simpletest_phpunit_xml_to_rows($test_id, $phpunit_xml_file) {
+  $contents = file_get_contents($phpunit_xml_file);
+  $xml = new SimpleXMLElement($contents);
+  $records = array();
+  foreach ($xml->testsuite as $testsuite) {
+    foreach ($testsuite as $suite) {
+      foreach ($suite as $testcase) {
+        $message = '';
+        if ($testcase->failure) {
+          $lines = explode("\n", $testcase->failure);
+          $message = $lines[2];
+        }
+        $attributes = $testcase->attributes();
+        $records[] = array(
+          'test_id' => $test_id,
+          'test_class' => (string)$attributes->class,
+          'status' => empty($testcase->failure) ? 'pass' : 'fail',
+          'message' => $message,
+          'message_group' => 'Other', // TODO: Check on the proper values for this.
+          'function' => $attributes->class . '->' . $attributes->name . '()',
+          'line' => (string)$attributes->line,
+          'file' => (string)$attributes->file,
+        );
+      }
+    }
+  }
+  return $records;
+}
diff --git a/core/modules/simpletest/simpletest.pages.inc b/core/modules/simpletest/simpletest.pages.inc
index 7fe64d769dfa..61a4f82da6dd 100644
--- a/core/modules/simpletest/simpletest.pages.inc
+++ b/core/modules/simpletest/simpletest.pages.inc
@@ -8,7 +8,7 @@
 /**
  * List tests arranged in groups that can be selected and run.
  */
-function simpletest_test_form($form) {
+function simpletest_test_form($form, &$form_state) {
   $form['tests'] = array(
     '#type' => 'details',
     '#title' => t('Tests'),
@@ -21,6 +21,9 @@ function simpletest_test_form($form) {
 
   // Generate the list of tests arranged by group.
   $groups = simpletest_test_get_all();
+  $groups['PHPUnit'] = simpletest_phpunit_get_available_tests();
+  $form_state['storage']['PHPUnit'] = $groups['PHPUnit'];
+
   foreach ($groups as $group => $tests) {
     $form['tests']['table'][$group] = array(
       '#collapsed' => TRUE,
@@ -180,11 +183,15 @@ function simpletest_test_form_submit($form, &$form_state) {
   // Get list of tests.
   $tests_list = array();
   simpletest_classloader_register();
+
+  $phpunit_all = array_keys($form_state['storage']['PHPUnit']);
+
   foreach ($form_state['values'] as $class_name => $value) {
     // Since class_exists() will likely trigger an autoload lookup,
     // we do the fast check first.
     if ($value === 1 && class_exists($class_name)) {
-      $tests_list[] = $class_name;
+      $test_type = in_array($class_name, $phpunit_all) ? 'UnitTest' : 'WebTest';
+      $tests_list[$test_type][] = $class_name;
     }
   }
   if (count($tests_list) > 0 ) {
diff --git a/core/phpunit.xml.dist b/core/phpunit.xml.dist
new file mode 100644
index 000000000000..6ca7e89f8031
--- /dev/null
+++ b/core/phpunit.xml.dist
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit bootstrap="tests/bootstrap.php" colors="true">
+  <testsuites>
+    <testsuite name="Drupal Unit Test Suite">
+      <directory>./tests/*</directory>
+      <directory>./modules/*/tests/*</directory>
+      <directory>../modules/*/tests/*</directory>
+      <directory>../sites/*/modules/*/tests/*</directory>
+      <!-- Exclude files that end in Test.php that aren't actually phpunit tests. -->
+      <exclude>./modules/config/tests/config_test/lib/Drupal/config_test</exclude>
+      <exclude>./modules/views/tests/views_test_data/lib/Drupal/views_test_data</exclude>
+    </testsuite>
+  </testsuites>
+</phpunit>
+
diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh
index 3609bd746013..30087efc0975 100755
--- a/core/scripts/run-tests.sh
+++ b/core/scripts/run-tests.sh
@@ -51,7 +51,7 @@
   // Display all available tests.
   echo "\nAvailable test groups & classes\n";
   echo   "-------------------------------\n\n";
-  $groups = simpletest_test_get_all();
+  $groups = simpletest_script_get_all_tests();
   foreach ($groups as $group => $tests) {
     echo $group . "\n";
     foreach ($tests as $class => $info) {
@@ -315,23 +315,68 @@ function simpletest_script_init($server_software) {
   require_once DRUPAL_ROOT . '/core/includes/bootstrap.inc';
 }
 
+/**
+ * Get all available tests from simpletest and PHPUnit.
+ *
+ * @return
+ *   An array of tests keyed with the groups specified in each of the tests
+ *   getInfo() method and then keyed by the test class. An example of the array
+ *   structure is provided below.
+ *
+ *   @code
+ *     $groups['Block'] => array(
+ *       'BlockTestCase' => array(
+ *         'name' => 'Block functionality',
+ *         'description' => 'Add, edit and delete custom block...',
+ *         'group' => 'Block',
+ *       ),
+ *     );
+ *   @endcode
+ */
+function simpletest_script_get_all_tests() {
+  $tests = simpletest_test_get_all();
+  $tests['PHPUnit'] = simpletest_phpunit_get_available_tests();
+  return $tests;
+}
+
 /**
  * Execute a batch of tests.
  */
-function simpletest_script_execute_batch($test_classes) {
+function simpletest_script_execute_batch($test_groups) {
   global $args, $test_ids;
 
+  // Separate PHPUnit tests from simpletest.
+  if (isset($test_groups['PHPUnit'])) {
+    $phpunit_tests = $test_groups['PHPUnit'];
+    unset($test_groups['PHPUnit']);
+  }
+
+  // Flatten the simpletest tests into an array of classnames.
+  $test_classes = array();
+  foreach ($test_groups as $group) {
+    $test_classes = array_merge($test_classes, array_values($group));
+  }
+
   // Multi-process execution.
   $children = array();
-  while (!empty($test_classes) || !empty($children)) {
+  while (!empty($test_classes) || !empty($children) || isset($phpunit_tests)) {
     while (count($children) < $args['concurrency']) {
-      if (empty($test_classes)) {
+      if (empty($test_classes) && !isset($phpunit_tests)) {
         break;
       }
 
-      // Fork a child process.
       $test_id = db_insert('simpletest_test_id')->useDefaults(array('test_id'))->execute();
       $test_ids[] = $test_id;
+
+      // Process phpunit tests immediately since they are fast and we don't need
+      // to fork for them.
+      if (isset($phpunit_tests)) {
+        simpletest_script_run_phpunit($test_id, $phpunit_tests);
+        unset($phpunit_tests);
+        continue;
+      }
+
+      // Fork a child process.
       $test_class = array_shift($test_classes);
       $command = simpletest_script_command($test_id, $test_class);
       $process = proc_open($command, array(), $pipes, NULL, NULL, array('bypass_shell' => TRUE));
@@ -383,6 +428,51 @@ function simpletest_script_execute_batch($test_classes) {
   }
 }
 
+/**
+ * Run a group of phpunit tests.
+ */
+function simpletest_script_run_phpunit($test_id, $phpunit_tests) {
+  $results = simpletest_run_phpunit_tests($test_id, $phpunit_tests);
+  simpletest_process_phpunit_results($results);
+
+  // Map phpunit results to a data structure we can pass to
+  // _simpletest_format_summary_line.
+  $summaries = array();
+  foreach ($results as $result) {
+    if (!isset($summaries[$result['test_class']])) {
+      $summaries[$result['test_class']] = array(
+        '#pass' => 0,
+        '#fail' => 0,
+        '#exception' => 0,
+        '#debug' => 0,
+      );
+    }
+
+    switch ($result['status']) {
+      case 'pass':
+        $summaries[$result['test_class']]['#pass']++;
+        break;
+      case 'fail':
+        $summaries[$result['test_class']]['#fail']++;
+        break;
+      case 'exception':
+        $summaries[$result['test_class']]['#exception']++;
+        break;
+      case 'debug':
+        $summary['#exception']++;
+        break;
+    }
+  }
+
+  foreach ($summaries as $class => $summary) {
+    $had_fails = $summary['#fail'] > 0;
+    $had_exceptions = $summary['#exception'] > 0;
+    $status = ($had_fails || $had_exceptions ? 'fail' : 'pass');
+    $info = call_user_func(array($class, 'getInfo'));
+    simpletest_script_print($info['name'] . ' ' . _simpletest_format_summary_line($summary) . "\n", simpletest_script_color_code($status));
+  }
+}
+
 /**
  * Bootstrap Drupal and run a single test.
  */
@@ -535,10 +625,10 @@ function simpletest_script_get_test_list() {
 
   $test_list = array();
   if ($args['all']) {
-    $groups = simpletest_test_get_all();
+    $groups = simpletest_script_get_all_tests();
     $all_tests = array();
     foreach ($groups as $group => $tests) {
-      $all_tests = array_merge($all_tests, array_keys($tests));
+      $all_tests[$group] = array_keys($tests);
     }
     $test_list = $all_tests;
   }
@@ -578,20 +668,28 @@ function simpletest_script_get_test_list() {
         // Extract all class names.
         // Abstract classes are excluded on purpose.
         preg_match_all('@^class ([^ ]+)@m', $content, $matches);
+
+        require $file;
         if (!$namespace) {
-          $test_list = array_merge($test_list, $matches[1]);
+          $info = $matches[1][0]::getInfo();
+          $test_list[$info[$group]] = $matches[1][0];
         }
         else {
+          $class = '\\' . $namespace . '\\' . $matches[1][0];
+          $info = $class::getInfo();
           foreach ($matches[1] as $class_name) {
-            $test_list[] = $namespace . '\\' . $class_name;
+            if (!isset($test_list[$info['group']])) {
+              $test_list[$info['group']] = array();
+            }
+            $test_list[$info['group']][] = $namespace . '\\' . $class_name;
           }
         }
       }
     }
     else {
-      $groups = simpletest_test_get_all();
+      $groups = simpletest_script_get_all_tests();
       foreach ($args['test_names'] as $group_name) {
-        $test_list = array_merge($test_list, array_keys($groups[$group_name]));
+        $test_list[$group_name] = array_keys($groups[$group_name]);
       }
     }
   }
@@ -626,9 +724,11 @@ function simpletest_script_reporter_init() {
   }
   else {
     echo "Tests to be run:\n";
-    foreach ($test_list as $class_name) {
-      $info = call_user_func(array($class_name, 'getInfo'));
-      echo " - " . $info['name'] . ' (' . $class_name . ')' . "\n";
+    foreach ($test_list as $group) {
+      foreach ($group as $class_name) {
+        $info = call_user_func(array($class_name, 'getInfo'));
+        echo " - " . $info['name'] . ' (' . $class_name . ')' . "\n";
+      }
     }
     echo "\n";
   }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Cache/BackendChainImplementationUnitTest.php b/core/tests/Drupal/Tests/Core/Cache/BackendChainImplementationUnitTest.php
similarity index 72%
rename from core/modules/system/lib/Drupal/system/Tests/Cache/BackendChainImplementationUnitTest.php
rename to core/tests/Drupal/Tests/Core/Cache/BackendChainImplementationUnitTest.php
index 9c528ce0cda9..ed1f24abb9f7 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Cache/BackendChainImplementationUnitTest.php
+++ b/core/tests/Drupal/Tests/Core/Cache/BackendChainImplementationUnitTest.php
@@ -2,20 +2,22 @@
 
 /**
  * @file
- * Definition of Drupal\system\Tests\Cache\BackendChainImplementationUnitTest.
+ * Definition of Drupal\Tests\Core\Cache\BackendChainImplementationUnitTest.
  */
 
-namespace Drupal\system\Tests\Cache;
+namespace Drupal\Tests\Core\Cache;
 
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Cache\BackendChain;
 use Drupal\Core\Cache\MemoryBackend;
-use Drupal\simpletest\UnitTestBase;
+use Drupal\Tests\UnitTestCase;
 
 /**
  * Tests implementation-specific functionality of the BackendChain backend.
+ *
+ * @group Cache
  */
-class BackendChainImplementationUnitTest extends UnitTestBase {
+class BackendChainImplementationUnitTest extends UnitTestCase {
 
   public static function getInfo() {
     return array(
@@ -95,16 +97,16 @@ public function setUp() {
    */
   public function testGet() {
     $cached = $this->chain->get('t123');
-    $this->assertNotIdentical(FALSE, $cached, 'Got key that is on all backends');
-    $this->assertIdentical(1231, $cached->data, 'Got the key from the backend 1');
+    $this->assertNotSame(FALSE, $cached, 'Got key that is on all backends');
+    $this->assertSame(1231, $cached->data, 'Got the key from the backend 1');
 
     $cached = $this->chain->get('t23');
-    $this->assertNotIdentical(FALSE, $cached, 'Got key that is on backends 2 and 3');
-    $this->assertIdentical(232, $cached->data, 'Got the key from the backend 2');
+    $this->assertNotSame(FALSE, $cached, 'Got key that is on backends 2 and 3');
+    $this->assertSame(232, $cached->data, 'Got the key from the backend 2');
 
     $cached = $this->chain->get('t3');
-    $this->assertNotIdentical(FALSE, $cached, 'Got key that is on the backend 3');
-    $this->assertIdentical(33, $cached->data, 'Got the key from the backend 3');
+    $this->assertNotSame(FALSE, $cached, 'Got key that is on the backend 3');
+    $this->assertSame(33, $cached->data, 'Got the key from the backend 3');
   }
 
   /**
@@ -114,15 +116,15 @@ public function testGetMultiple() {
     $cids = array('t123', 't23', 't3', 't4');
 
     $ret = $this->chain->getMultiple($cids);
-    $this->assertIdentical($ret['t123']->data, 1231, 'Got key 123 and value is from the first backend');
-    $this->assertIdentical($ret['t23']->data, 232, 'Got key 23 and value is from the second backend');
-    $this->assertIdentical($ret['t3']->data, 33, 'Got key 3 and value is from the third backend');
+    $this->assertSame($ret['t123']->data, 1231, 'Got key 123 and value is from the first backend');
+    $this->assertSame($ret['t23']->data, 232, 'Got key 23 and value is from the second backend');
+    $this->assertSame($ret['t3']->data, 33, 'Got key 3 and value is from the third backend');
     $this->assertFalse(array_key_exists('t4', $ret), "Didn't get the nonexistent key");
 
     $this->assertFalse(in_array('t123', $cids), "Existing key 123 has been removed from &\$cids");
     $this->assertFalse(in_array('t23', $cids), "Existing key 23 has been removed from &\$cids");
     $this->assertFalse(in_array('t3', $cids), "Existing key 3 has been removed from &\$cids");
-    $this->assert(in_array('t4', $cids), "Non existing key 4 is still in &\$cids");
+    $this->assertTrue(in_array('t4', $cids), "Non existing key 4 is still in &\$cids");
   }
 
   /**
@@ -132,16 +134,16 @@ public function testSet() {
     $this->chain->set('test', 123);
 
     $cached = $this->firstBackend->get('test');
-    $this->assertNotIdentical(FALSE, $cached, 'Test key is in the first backend');
-    $this->assertIdentical(123, $cached->data, 'Test key has the right value');
+    $this->assertNotSame(FALSE, $cached, 'Test key is in the first backend');
+    $this->assertSame(123, $cached->data, 'Test key has the right value');
 
     $cached = $this->secondBackend->get('test');
-    $this->assertNotIdentical(FALSE, $cached, 'Test key is in the second backend');
-    $this->assertIdentical(123, $cached->data, 'Test key has the right value');
+    $this->assertNotSame(FALSE, $cached, 'Test key is in the second backend');
+    $this->assertSame(123, $cached->data, 'Test key has the right value');
 
     $cached = $this->thirdBackend->get('test');
-    $this->assertNotIdentical(FALSE, $cached, 'Test key is in the third backend');
-    $this->assertIdentical(123, $cached->data, 'Test key has the right value');
+    $this->assertNotSame(FALSE, $cached, 'Test key is in the third backend');
+    $this->assertSame(123, $cached->data, 'Test key has the right value');
   }
 
   /**
@@ -151,20 +153,20 @@ public function testDelete() {
     $this->chain->set('test', 5);
 
     $cached = $this->firstBackend->get('test');
-    $this->assertNotIdentical(FALSE, $cached, 'Test key has been added to the first backend');
+    $this->assertNotSame(FALSE, $cached, 'Test key has been added to the first backend');
     $cached = $this->secondBackend->get('test');
-    $this->assertNotIdentical(FALSE, $cached, 'Test key has been added to the first backend');
+    $this->assertNotSame(FALSE, $cached, 'Test key has been added to the first backend');
     $cached = $this->thirdBackend->get('test');
-    $this->assertNotIdentical(FALSE, $cached, 'Test key has been added to the first backend');
+    $this->assertNotSame(FALSE, $cached, 'Test key has been added to the first backend');
 
     $this->chain->delete('test');
 
     $cached = $this->firstBackend->get('test');
-    $this->assertIdentical(FALSE, $cached, 'Test key is removed from the first backend');
+    $this->assertSame(FALSE, $cached, 'Test key is removed from the first backend');
     $cached = $this->secondBackend->get('test');
-    $this->assertIdentical(FALSE, $cached, 'Test key is removed from the second backend');
+    $this->assertSame(FALSE, $cached, 'Test key is removed from the second backend');
     $cached = $this->thirdBackend->get('test');
-    $this->assertIdentical(FALSE, $cached, 'Test key is removed from the third backend');
+    $this->assertSame(FALSE, $cached, 'Test key is removed from the third backend');
   }
 
   /**
@@ -173,13 +175,13 @@ public function testDelete() {
   public function testGetHasPropagated() {
     $this->chain->get('t23');
     $cached = $this->firstBackend->get('t23');
-    $this->assertNotIdentical(FALSE, $cached, 'Test 2 has been propagated to the first backend');
+    $this->assertNotSame(FALSE, $cached, 'Test 2 has been propagated to the first backend');
 
     $this->chain->get('t3');
     $cached = $this->firstBackend->get('t3');
-    $this->assertNotIdentical(FALSE, $cached, 'Test 3 has been propagated to the first backend');
+    $this->assertNotSame(FALSE, $cached, 'Test 3 has been propagated to the first backend');
     $cached = $this->secondBackend->get('t3');
-    $this->assertNotIdentical(FALSE, $cached, 'Test 3 has been propagated to the second backend');
+    $this->assertNotSame(FALSE, $cached, 'Test 3 has been propagated to the second backend');
   }
 
   /**
@@ -190,15 +192,15 @@ public function testGetMultipleHasPropagated() {
     $this->chain->getMultiple($cids);
 
     $cached = $this->firstBackend->get('t3');
-    $this->assertNotIdentical(FALSE, $cached, 'Test 3 has been propagated to the first backend');
-    $this->assertIdentical(33, $cached->data, 'And value has been kept');
+    $this->assertNotSame(FALSE, $cached, 'Test 3 has been propagated to the first backend');
+    $this->assertSame(33, $cached->data, 'And value has been kept');
     $cached = $this->secondBackend->get('t3');
-    $this->assertNotIdentical(FALSE, $cached, 'Test 3 has been propagated to the second backend');
-    $this->assertIdentical(33, $cached->data, 'And value has been kept');
+    $this->assertNotSame(FALSE, $cached, 'Test 3 has been propagated to the second backend');
+    $this->assertSame(33, $cached->data, 'And value has been kept');
 
     $cached = $this->firstBackend->get('t23');
-    $this->assertNotIdentical(FALSE, $cached, 'Test 2 has been propagated to the first backend');
-    $this->assertIdentical(232, $cached->data, 'And value has been kept');
+    $this->assertNotSame(FALSE, $cached, 'Test 2 has been propagated to the first backend');
+    $this->assertSame(232, $cached->data, 'And value has been kept');
   }
 
   /**
@@ -209,7 +211,7 @@ public function testNotEmptyIfOneBackendHasTheKey() {
 
     // This is the only test that needs to start with an empty chain.
     $this->chain->deleteAll();
-    $this->assert($this->chain->isEmpty(), 'Chain have been emptied by the deleteAll() call');
+    $this->assertTrue($this->chain->isEmpty(), 'Chain have been emptied by the deleteAll() call');
 
     $this->secondBackend->set('test', 5);
     $this->assertFalse($this->chain->isEmpty(), 'Chain is not empty anymore now that the second backend has something');
@@ -237,7 +239,7 @@ public function testDeleteTagsPropagation() {
     // Create two cache entries with the same tag and tag value.
     $this->chain->set('test_cid_clear1', 'foo', CacheBackendInterface::CACHE_PERMANENT, array('test_tag' => 2));
     $this->chain->set('test_cid_clear2', 'foo', CacheBackendInterface::CACHE_PERMANENT, array('test_tag' => 2));
-    $this->assertNotIdentical(FALSE, $this->firstBackend->get('test_cid_clear1')
+    $this->assertNotSame(FALSE, $this->firstBackend->get('test_cid_clear1')
       && $this->firstBackend->get('test_cid_clear2')
       && $this->secondBackend->get('test_cid_clear1')
       && $this->secondBackend->get('test_cid_clear2')
@@ -247,7 +249,7 @@ public function testDeleteTagsPropagation() {
 
     // Invalidate test_tag of value 1. This should invalidate both entries.
     $this->chain->deleteTags(array('test_tag' => 2));
-    $this->assertIdentical(FALSE, $this->firstBackend->get('test_cid_clear1')
+    $this->assertSame(FALSE, $this->firstBackend->get('test_cid_clear1')
       && $this->firstBackend->get('test_cid_clear2')
       && $this->secondBackend->get('test_cid_clear1')
       && $this->secondBackend->get('test_cid_clear2')
@@ -258,7 +260,7 @@ public function testDeleteTagsPropagation() {
     // Create two cache entries with the same tag and an array tag value.
     $this->chain->set('test_cid_clear1', 'foo', CacheBackendInterface::CACHE_PERMANENT, array('test_tag' => array(1)));
     $this->chain->set('test_cid_clear2', 'foo', CacheBackendInterface::CACHE_PERMANENT, array('test_tag' => array(1)));
-    $this->assertNotIdentical(FALSE, $this->firstBackend->get('test_cid_clear1')
+    $this->assertNotSame(FALSE, $this->firstBackend->get('test_cid_clear1')
       && $this->firstBackend->get('test_cid_clear2')
       && $this->secondBackend->get('test_cid_clear1')
       && $this->secondBackend->get('test_cid_clear2')
@@ -268,7 +270,7 @@ public function testDeleteTagsPropagation() {
 
     // Invalidate test_tag of value 1. This should invalidate both entries.
     $this->chain->deleteTags(array('test_tag' => array(1)));
-    $this->assertIdentical(FALSE, $this->firstBackend->get('test_cid_clear1')
+    $this->assertSame(FALSE, $this->firstBackend->get('test_cid_clear1')
       && $this->firstBackend->get('test_cid_clear2')
       && $this->secondBackend->get('test_cid_clear1')
       && $this->secondBackend->get('test_cid_clear2')
@@ -280,7 +282,7 @@ public function testDeleteTagsPropagation() {
     $this->chain->set('test_cid_clear1', 'foo', CacheBackendInterface::CACHE_PERMANENT, array('test_tag' => array(1)));
     $this->chain->set('test_cid_clear2', 'foo', CacheBackendInterface::CACHE_PERMANENT, array('test_tag' => array(2)));
     $this->chain->set('test_cid_clear3', 'foo', CacheBackendInterface::CACHE_PERMANENT, array('test_tag_foo' => array(3)));
-    $this->assertNotIdentical(FALSE, $this->firstBackend->get('test_cid_clear1')
+    $this->assertNotSame(FALSE, $this->firstBackend->get('test_cid_clear1')
       && $this->firstBackend->get('test_cid_clear2')
       && $this->firstBackend->get('test_cid_clear3')
       && $this->secondBackend->get('test_cid_clear1')
@@ -292,7 +294,7 @@ public function testDeleteTagsPropagation() {
       'Three cached items were created in all backends.');
 
     $this->chain->deleteTags(array('test_tag_foo' => array(3)));
-    $this->assertNotIdentical(FALSE, $this->firstBackend->get('test_cid_clear1')
+    $this->assertNotSame(FALSE, $this->firstBackend->get('test_cid_clear1')
       && $this->firstBackend->get('test_cid_clear2')
       && $this->secondBackend->get('test_cid_clear1')
       && $this->secondBackend->get('test_cid_clear2')
@@ -300,7 +302,7 @@ public function testDeleteTagsPropagation() {
       && $this->thirdBackend->get('test_cid_clear2'),
       'Cached items not matching the tag were not cleared from any of the backends.');
 
-    $this->assertIdentical(FALSE, $this->firstBackend->get('test_cid_clear3')
+    $this->assertSame(FALSE, $this->firstBackend->get('test_cid_clear3')
       && $this->secondBackend->get('test_cid_clear3')
       && $this->thirdBackend->get('test_cid_clear3'),
       'Cached item matching the tag was removed from all backends.');
diff --git a/core/modules/system/lib/Drupal/system/Tests/Cache/NullBackendTest.php b/core/tests/Drupal/Tests/Core/Cache/NullBackendTest.php
similarity index 77%
rename from core/modules/system/lib/Drupal/system/Tests/Cache/NullBackendTest.php
rename to core/tests/Drupal/Tests/Core/Cache/NullBackendTest.php
index 3882401729f4..fc3a7000430b 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Cache/NullBackendTest.php
+++ b/core/tests/Drupal/Tests/Core/Cache/NullBackendTest.php
@@ -2,18 +2,20 @@
 
 /**
  * @file
- * Definition of Drupal\system\Tests\Cache\NullBackendTest.
+ * Definition of Drupal\Tests\Core\Cache\NullBackendTest.
  */
 
-namespace Drupal\system\Tests\Cache;
+namespace Drupal\Tests\Core\Cache;
 
 use Drupal\Core\Cache\NullBackend;
-use Drupal\simpletest\UnitTestBase;
+use Drupal\Tests\UnitTestCase;
 
 /**
  * Tests the cache NullBackend.
+ *
+ * @group Cache
  */
-class NullBackendTest extends UnitTestBase {
+class NullBackendTest extends UnitTestCase {
 
   public static function getInfo() {
     return array(
diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/NestedArrayUnitTest.php b/core/tests/Drupal/Tests/Core/NestedArrayUnitTest.php
similarity index 70%
rename from core/modules/system/lib/Drupal/system/Tests/Common/NestedArrayUnitTest.php
rename to core/tests/Drupal/Tests/Core/NestedArrayUnitTest.php
index ff61ae6c378f..1da86af6e011 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Common/NestedArrayUnitTest.php
+++ b/core/tests/Drupal/Tests/Core/NestedArrayUnitTest.php
@@ -2,18 +2,20 @@
 
 /**
  * @file
- * Contains \Drupal\system\Tests\Common\NestedArrayUnitTest.
+ * Contains \Drupal\Core\NestedArrayUnitTest.
  */
 
-namespace Drupal\system\Tests\Common;
+namespace Drupal\Tests\Core;
 
 use Drupal\Component\Utility\NestedArray;
-use Drupal\simpletest\UnitTestBase;
+use Drupal\Tests\UnitTestCase;
 
 /**
  * Tests the NestedArray helper class.
+ *
+ * @group System
  */
-class NestedArrayUnitTest extends UnitTestBase {
+class NestedArrayUnitTest extends UnitTestCase {
 
   /**
    * Form array to check.
@@ -51,26 +53,26 @@ function setUp() {
   function testGetValue() {
     // Verify getting a value of a nested element.
     $value = NestedArray::getValue($this->form, $this->parents);
-    $this->assertEqual($value['#value'], 'Nested element', 'Nested element value found.');
+    $this->assertEquals($value['#value'], 'Nested element', 'Nested element value found.');
 
     // Verify changing a value of a nested element by reference.
     $value = &NestedArray::getValue($this->form, $this->parents);
     $value['#value'] = 'New value';
     $value = NestedArray::getValue($this->form, $this->parents);
-    $this->assertEqual($value['#value'], 'New value', 'Nested element value was changed by reference.');
-    $this->assertEqual($this->form['details']['element']['#value'], 'New value', 'Nested element value was changed by reference.');
+    $this->assertEquals($value['#value'], 'New value', 'Nested element value was changed by reference.');
+    $this->assertEquals($this->form['details']['element']['#value'], 'New value', 'Nested element value was changed by reference.');
 
     // Verify that an existing key is reported back.
     $key_exists = NULL;
     NestedArray::getValue($this->form, $this->parents, $key_exists);
-    $this->assertIdentical($key_exists, TRUE, 'Existing key found.');
+    $this->assertSame($key_exists, TRUE, 'Existing key found.');
 
     // Verify that a non-existing key is reported back and throws no errors.
     $key_exists = NULL;
     $parents = $this->parents;
     $parents[] = 'foo';
     NestedArray::getValue($this->form, $parents, $key_exists);
-    $this->assertIdentical($key_exists, FALSE, 'Non-existing key not found.');
+    $this->assertSame($key_exists, FALSE, 'Non-existing key not found.');
   }
 
   /**
@@ -84,8 +86,8 @@ function testSetValue() {
 
     // Verify setting the value of a nested element.
     NestedArray::setValue($this->form, $this->parents, $new_value);
-    $this->assertEqual($this->form['details']['element']['#value'], 'New value', 'Changed nested element value found.');
-    $this->assertIdentical($this->form['details']['element']['#required'], TRUE, 'New nested element value found.');
+    $this->assertEquals($this->form['details']['element']['#value'], 'New value', 'Changed nested element value found.');
+    $this->assertSame($this->form['details']['element']['#required'], TRUE, 'New nested element value found.');
   }
 
   /**
@@ -99,13 +101,13 @@ function testUnsetValue() {
     $parents[] = 'foo';
     NestedArray::unsetValue($this->form, $parents, $key_existed);
     $this->assertTrue(isset($this->form['details']['element']['#value']), 'Outermost nested element key still exists.');
-    $this->assertIdentical($key_existed, FALSE, 'Non-existing key not found.');
+    $this->assertSame($key_existed, FALSE, 'Non-existing key not found.');
 
     // Verify unsetting a nested element.
     $key_existed = NULL;
     NestedArray::unsetValue($this->form, $this->parents, $key_existed);
     $this->assertFalse(isset($this->form['details']['element']), 'Removed nested element not found.');
-    $this->assertIdentical($key_existed, TRUE, 'Existing key was found.');
+    $this->assertSame($key_existed, TRUE, 'Existing key was found.');
   }
 
   /**
@@ -113,12 +115,12 @@ function testUnsetValue() {
    */
   function testKeyExists() {
     // Verify that existing key is found.
-    $this->assertIdentical(NestedArray::keyExists($this->form, $this->parents), TRUE, 'Nested key found.');
+    $this->assertSame(NestedArray::keyExists($this->form, $this->parents), TRUE, 'Nested key found.');
 
     // Verify that non-existing keys are not found.
     $parents = $this->parents;
     $parents[] = 'foo';
-    $this->assertIdentical(NestedArray::keyExists($this->form, $parents), FALSE, 'Non-existing nested key not found.');
+    $this->assertSame(NestedArray::keyExists($this->form, $parents), FALSE, 'Non-existing nested key not found.');
   }
 
   /**
@@ -141,6 +143,6 @@ function testMergeDeepArray() {
       'language' => 'en',
       'html' => TRUE,
     );
-    $this->assertIdentical(NestedArray::mergeDeepArray(array($link_options_1, $link_options_2)), $expected, 'NestedArray::mergeDeepArray() returned a properly merged array.');
+    $this->assertSame(NestedArray::mergeDeepArray(array($link_options_1, $link_options_2)), $expected, 'NestedArray::mergeDeepArray() returned a properly merged array.');
   }
 }
diff --git a/core/tests/Drupal/Tests/UnitTestCase.php b/core/tests/Drupal/Tests/UnitTestCase.php
new file mode 100644
index 000000000000..b9a7b504a780
--- /dev/null
+++ b/core/tests/Drupal/Tests/UnitTestCase.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\UnitTestCase.
+ */
+
+namespace Drupal\Tests;
+
+class UnitTestCase extends \PHPUnit_Framework_TestCase {
+
+  /**
+   * This method exists to support the simpletest UI runner.
+   *
+   * It should eventually be replaced with something native to phpunit.
+   *
+   * Also, this method is empty because you can't have an abstract static
+   * method. Sub-classes should always override it.
+   *
+   * @return array
+   *   An array describing the test like so:
+   *   array(
+   *     'name' => 'Something Test',
+   *     'description' => 'Tests Something',
+   *     'group' => 'Something',
+   *   )
+   */
+  public static function getInfo() {
+    throw new \RuntimeException("Sub-class must implement the getInfo method!");
+  }
+
+  /**
+   * Generates a random string containing letters and numbers.
+   *
+   * The string will always start with a letter. The letters may be upper or
+   * lower case. This method is better for restricted inputs that do not accept
+   * certain characters. For example, when testing input fields that require
+   * machine readable values (i.e. without spaces and non-standard characters)
+   * this method is best.
+   *
+   * 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 string.
+   *
+   * @see Drupal\simpletest\TestBase::randomString()
+   */
+  public static function randomName($length = 8) {
+    $values = array_merge(range(65, 90), range(97, 122), range(48, 57));
+    $max = count($values) - 1;
+    $str = chr(mt_rand(97, 122));
+    for ($i = 1; $i < $length; $i++) {
+      $str .= chr($values[mt_rand(0, $max)]);
+    }
+    return $str;
+  }
+}
diff --git a/core/tests/bootstrap.php b/core/tests/bootstrap.php
new file mode 100644
index 000000000000..6c947dfc88ab
--- /dev/null
+++ b/core/tests/bootstrap.php
@@ -0,0 +1,13 @@
+<?php
+
+// Register the namespaces we'll need to autoload from.
+$loader = require __DIR__ . "/../vendor/autoload.php";
+$loader->add('Drupal\\', __DIR__);
+$loader->add('Drupal\Core', __DIR__ . "/../../core/lib");
+$loader->add('Drupal\Component', __DIR__ . "/../../core/lib");
+
+foreach (scandir(__DIR__ . "/../modules") as $module) {
+  $loader->add('Drupal\\' . $module, __DIR__ . "/../modules/" . $module . "/lib");
+}
+// Look into removing this later.
+define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);
-- 
GitLab