=== modified file 'modules/simpletest/drupal_web_test_case.php'
--- modules/simpletest/drupal_web_test_case.php	2008-06-06 10:36:43 +0000
+++ modules/simpletest/drupal_web_test_case.php	2008-06-15 09:59:08 +0000
@@ -4,7 +4,7 @@
 /**
  * Test case for typical Drupal tests.
  */
-class DrupalWebTestCase extends UnitTestCase {
+class DrupalWebTestCase {
   protected $_logged_in = FALSE;
   protected $_content;
   protected $plain_text;
@@ -24,14 +24,129 @@ class DrupalWebTestCase extends UnitTest
    *
    * @param string $label Name of the test to be used by the SimpleTest library.
    */
-  function __construct($label = NULL) {
-    if (!$label) {
-      if (method_exists($this, 'getInfo')) {
-        $info  = $this->getInfo();
-        $label = $info['name'];
+  function __construct($test_id = NULL) {
+    if (method_exists($this, 'getInfo')) {
+      $info  = $this->getInfo();
+      $label = $info['name'];
+    }
+    $this->test_id = $test_id;
+  }
+
+  /**
+   * This function stores the assert. Do not call directly.
+   *
+   * @param $status
+   *   Can be 'pass', 'fail', 'exception'. TRUE is a synonym for 'pass', FALSE
+   *   for 'fail'.
+   * @param $message
+   *   The message string.
+   * @param $group
+   *   WHich group this assert belongs to.
+   * @param $custom_caller
+   *   By default, the assert comes from a function which names start with
+   *   'test'. Instead, you can specify where this assert originates from
+   *   by passing in an associative array as $custom_caller. Key 'file' is
+   *   the name of the source file, 'line' is the line number and 'function'
+   *   is the caller function itself.
+   */
+  protected function _assert($status, $message = '', $group = 'Other', $custom_caller = NULL) {
+    global $db_prefix;
+    if (is_bool($status)) {
+      $status = $status ? 'pass' : 'fail';
+    }
+    if (!isset($custom_caller)) {
+      $callers = debug_backtrace();
+      while ((list($index, $caller) = each($callers)) && substr($caller['function'], 0, 4) != 'test');
+      $function = $callers[$index - 1];
+    }
+    else {
+      $function = $custom_caller;
+    }
+    $current_db_prefix = $db_prefix;
+    $db_prefix = $this->db_prefix_original;
+    db_query("INSERT INTO {simpletest} (test_id, test_class, status, message, message_group, caller, line, file) VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s')", $this->test_id, get_class($this), $status, $message, $group, $function['function'], $function['line'], $function['file']);
+    $db_prefix = $current_db_prefix;
+    return $status;
+  }
+
+  protected function assertTrue($value, $message = '', $group = 'Other') {
+    return $this->_assert((bool) $value, $message ? $message : t('%value is TRUE', array('%value' => $value)), $group);
+  }
+
+  protected function assertFalse($value, $message = '', $group = 'Other') {
+    return $this->_assert(!$value, $message ? $message : t('%value is FALSE', array('%value' => $value)), $group);
+  }
+
+  protected function assertNull($value, $message = '', $group = 'Other') {
+    return $this->_assert(!isset($value), $message ? $message : t('%value is NULL', array('%value' => $value)), $group);
+  }
+
+  protected function assertNotNull($value, $message = '', $group = 'Other') {
+    return $this->_assert(isset($value), $message ? $message : t('%value is not NULL', array('%value' => $value)), $group);
+  }
+
+  protected function assertEqual($first, $second, $message = '', $group = 'Other') {
+    return $this->_assert($first == $second, $message ? $message : t('%first is equal to %second', array('%first' => $first, '%second' => $second)), $group);
+  }
+
+  protected function assertNotEqual($first, $second, $message = '', $group = 'Other') {
+    return $this->_assert($first != $second, $message ? $message : t('%first is not equal to %second', array('%first' => $first, '%second' => $second)), $group);
+  }
+
+  protected function assertIdentical($first, $second, $message = '', $group = 'Other') {
+    return $this->_assert($first === $second, $message ? $message : t('%first is identical to %second', array('%first' => $first, '%second' => $second)), $group);
+  }
+
+  protected function assertNotIdentical($first, $second, $message = '', $group = 'Other') {
+    return $this->_assert($first !== $second, $message ? $message : t('%first is not identical to %second', array('%first' => $first, '%second' => $second)), $group);
+  }
+
+  protected function pass($message = NULL, $group = 'Other') {
+    return $this->_assert(TRUE, $message, $group);
+  }
+
+  protected function fail($message = NULL, $group = 'Other') {
+    return $this->_assert(FALSE, $message, $group);
+  }
+
+  protected function error($message = '', $group = 'Other', $custom_caller = NULL) {
+    return $this->_assert('fail', $message, $group, $custom_caller);
+  }
+
+  function run() {
+    set_error_handler(array($this, 'errorHandler'));
+    $this->setUp();
+    $methods = array();
+    foreach (get_class_methods(get_class($this)) as $method) {
+      if (strtolower(substr($method, 0, 4)) == 'test') {
+        $this->$method();
       }
     }
-    parent::__construct($label);
+    $this->tearDown();
+    restore_error_handler();
+  }
+
+  function errorHandler($severity, $message, $file = NULL, $line = NULL) {
+    $severity = $severity & error_reporting();
+    if ($severity) {
+      $error_map = array(
+        E_STRICT => 'Run-time notice',
+        E_WARNING => 'Warning',
+        E_NOTICE => 'Notice',
+        E_CORE_ERROR => 'Core error',
+        E_CORE_WARNING => 'Core warning',
+        E_USER_ERROR => 'User error',
+        E_USER_WARNING => 'User warning',
+        E_USER_NOTICE => 'User notice',
+        E_RECOVERABLE_ERROR => 'Recoverable error',
+      );
+      $this->error($message, $error_map[$severity], array(
+        'function' => '',
+        'line' => $line,
+        'file' => $file,
+      ));
+    }
+    return TRUE;
   }
 
   /**
@@ -296,13 +411,13 @@ class DrupalWebTestCase extends UnitTest
   }
 
   /**
-   * Generates a random database prefix, runs the install scripts on the 
-   * prefixed database and enable the specified modules. After installation 
-   * many caches are flushed and the internal browser is setup so that the 
-   * page requests will run on the new prefix. A temporary files directory 
+   * Generates a random database prefix, runs the install scripts on the
+   * prefixed database and enable the specified modules. After installation
+   * many caches are flushed and the internal browser is setup so that the
+   * page requests will run on the new prefix. A temporary files directory
    * is created with the same name as the database prefix.
    *
-   * @param ... 
+   * @param ...
    *   List of modules to enable for the duration of the test.
    */
   function setUp() {
@@ -341,8 +456,6 @@ class DrupalWebTestCase extends UnitTest
     $this->original_file_directory = file_directory_path();
     variable_set('file_directory_path', file_directory_path() . '/' . $db_prefix);
     file_check_directory(file_directory_path(), TRUE); // Create the files directory.
-
-    parent::setUp();
   }
 
   /**
@@ -395,21 +508,8 @@ class DrupalWebTestCase extends UnitTest
 
       // Close the CURL handler.
       $this->curlClose();
+      restore_error_handler();
     }
-    parent::tearDown();
-  }
-
-  /**
-   * Set necessary reporter info.
-   */
-  function run(&$reporter) {
-    $arr = array('class' => get_class($this));
-    if (method_exists($this, 'getInfo')) {
-      $arr = array_merge($arr, $this->getInfo());
-    }
-    $reporter->test_info_stack[] = $arr;
-    parent::run($reporter);
-    array_pop($reporter->test_info_stack);
   }
 
   /**
@@ -558,7 +658,7 @@ class DrupalWebTestCase extends UnitTest
           }
           $out = $this->curlExec(array(CURLOPT_URL => $action, CURLOPT_POST => TRUE, CURLOPT_POSTFIELDS => $post));
           // Ensure that any changes to variables in the other thread are picked up.
-          $this->refreshVariables(); 
+          $this->refreshVariables();
           return $out;
         }
       }
@@ -576,15 +676,15 @@ class DrupalWebTestCase extends UnitTest
    * exist and attempt to create POST data in the correct manner for the particular
    * field type.
    *
-   * @param array $post 
+   * @param array $post
    *   Reference to array of post values.
-   * @param array $edit 
+   * @param array $edit
    *   Reference to array of edit values to be checked against the form.
-   * @param string $submit 
+   * @param string $submit
    *   Form submit button value.
-   * @param array $form 
+   * @param array $form
    *   Array of form elements.
-   * @return boolean 
+   * @return boolean
    *   Submit value matches a valid submit input in the form.
    */
   protected function handleForm(&$post, &$edit, &$upload, $submit, $form) {

=== modified file 'modules/simpletest/simpletest.install'
--- modules/simpletest/simpletest.install	2008-05-10 06:55:09 +0000
+++ modules/simpletest/simpletest.install	2008-06-15 18:03:11 +0000
@@ -5,6 +5,7 @@
  * Implementation of hook_install().
  */
 function simpletest_install() {
+  drupal_install_schema('simpletest');
   // Check for files directory.
   $path = file_directory_path() . '/simpletest';
   if (file_check_directory($path, FILE_CREATE_DIRECTORY)) {
@@ -95,6 +96,7 @@ function simpletest_uninstall() {
   variable_del('simpletest_httpauth_username');
   variable_del('simpletest_httpauth_pass');
   variable_del('simpletest_devel');
+  drupal_uninstall_schema('simpletest');
 }
 
 /**
@@ -133,3 +135,85 @@ function simpletest_requirements($phase)
 
   return $requirements;
 }
+
+function simpletest_schema() {
+  $schema['simpletest'] = array(
+    'description' => t('Stores simpletest messages'),
+    'fields' => array(
+      'message_id'  => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => t('Primary Key: Unique simpletest message ID.'),
+      ),
+      'test_id' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Test id, messages belonging to the same id are reported together'),
+      ),
+      'test_class' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('The name of the class that created this message.'),
+      ),
+      'status' => array(
+        'type' => 'varchar',
+        'length' => 9,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Message status. Core understands pass, fail, exception.'),
+      ),
+      'message' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('The message itself.'),
+      ),
+      'message_group' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('The message group this message belongs to. For example: warning, browser, user.'),
+      ),
+      'caller' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Name of the caller function or method that created this message.'),
+      ),
+      'line' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Line number of the caller.'),
+      ),
+      'file' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Name of the file where the caller is.'),
+      ),
+    ),
+    'primary key' => array('message_id'),
+    'indexes' => array(
+      'reporter' => array('test_class, message_id'),
+    ),
+  );
+  $schema['simpletest_test_id'] = array(
+    'description' => t('Stores simpletest test IDs.'),
+    'fields' => array(
+      'message_id'  => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => t('Primary Key: Unique simpletest ID.'),
+      ),
+    ),
+    'primary key' => array('message_id'),
+  );
+}
\ No newline at end of file

=== modified file 'modules/simpletest/simpletest.module'
--- modules/simpletest/simpletest.module	2008-05-10 07:46:22 +0000
+++ modules/simpletest/simpletest.module	2008-06-15 17:48:24 +0000
@@ -22,7 +22,8 @@ function simpletest_help($path, $arg) {
 function simpletest_menu() {
   $items['admin/build/testing'] = array(
     'title' => 'Testing',
-    'page callback' => 'simpletest_entrypoint',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('simpletest_test_form'),
     'description' => 'Run tests against Drupal core and your active modules. These tests help assure that your site code is working as designed.',
     'access arguments' => array('administer unit tests'),
   );
@@ -50,104 +51,122 @@ function simpletest_perm() {
  */
 function simpletest_theme() {
   return array(
-    'simpletest_overview_form' => array(
+    'simpletest_test_form' => array(
+      'arguments' => array('form' => NULL)
+    ),
+    'simpletest_result_summary' => array(
       'arguments' => array('form' => NULL)
     ),
   );
 }
 
 /**
- * Try to load the simepletest
- * @return boolean TRUE if the load succeeded
+ * Menu callback for both running tests and listing possible tests
  */
-function simpletest_load() {
-  global $user;
-  static $loaded;
-  if (!$loaded) {
-    $loaded = TRUE;
-    if ($user->uid != 1) {
-      drupal_set_message(t('It is strongly suggested to run the tests with the first user!'));
+function simpletest_test_form() {
+  $form = array();
+  $uncategorized_tests = simpletest_get_all_tests();
+  $tests = simpletest_categorize_tests($uncategorized_tests);
+  if (isset($_SESSION['test_id'])) {
+    $result_set = db_query("SELECT * FROM {simpletest} WHERE test_id = %d ORDER BY test_class, message_id", $_SESSION['test_id']);
+    unset($_SESSION['test_id']);
+    $summary = array(
+      '#theme' => 'simpletest_result_summary',
+      '#pass' => 0,
+      '#fail' => 0,
+      '#exception' => 0,
+      '#weight' => -10,
+    );
+    $form['summary'] = $summary;
+    $group_summary = array();
+    $map = array(
+      'pass' => array('text' => t('Pass'), 'image' => theme('image', 'misc/watchdog-ok.png')),
+      'fail' => array('text' => t('Fail'), 'image' => theme('image', 'misc/watchdog-error.png')),
+      'exception' => array('text' => t('Exception'), 'image' => theme('image', 'misc/watchdog-warning.png')),
+    );
+    $header = array(t('Message'), t('Group'), t('Filename'), t('Line #'), t('Called function'), array('colspan' => 2, 'data' => t('Status')));
+    while ($result = db_fetch_object($result_set)) {
+      $class = $result->test_class;
+      $info = $uncategorized_tests[$class]->getInfo();
+      $group = $info['group'];
+      if (!isset($group_summary[$group])) {
+        $group_summary[$group] = $summary;
+      }
+      $element = &$form['results'][$group][$class];
+      if (!isset($element)) {
+        $element['summary'] = $summary;
+      }
+      $status = $result->status;
+      // This reporter can only handle pass, fail and exception.
+      if (isset($map[$status])) {
+        $element['#title'] = $info['name'];
+        $status_index = '#'. $status;
+        $form['summary'][$status_index]++;
+        $group_summary[$group][$status_index]++;
+        $element['summary'][$status_index]++;
+        $element['result_table']['#rows'][] = array(
+          'data' => array(
+            $result->message,
+            $result->message_group,
+            basename($result->file),
+            $result->line,
+            $result->caller,
+            $map[$status]['text'],
+            $map[$status]['image'],
+          ),
+          'class' => "simpletest-$status",
+        );
+      }
+      unset($element);
     }
-    $path = drupal_get_path('module', 'simpletest') . '/';
-    foreach (array('simpletest.php', 'unit_tester.php', 'reporter.php', 'drupal_reporter.php', 'drupal_web_test_case.php', 'drupal_test_suite.php') as $file) {
-      require_once($path . $file);
+    $all_ok = TRUE;
+    foreach ($form['results'] as $group => &$elements) {
+      $group_ok = TRUE;
+      foreach ($elements as $class => &$element) {
+        $info = $uncategorized_tests[$class]->getInfo();
+        $ok = $element['summary']['#fail'] + $element['summary']['#exception'] == 0;
+        $element += array(
+          '#type' => 'fieldset',
+          '#collapsible' => TRUE,
+          '#collapsed' => $ok,
+          '#description' => $info['description'],
+        );
+        $element['result_table']['#value'] = theme('table', $header, $element['result_table']['#rows']);
+        $element['summary']['#ok'] = $ok;
+        $group_ok = $group_ok && $ok;
+      }
+      $elements += array(
+        '#type' => 'fieldset',
+        '#title' => $group,
+        '#collapsible' => TRUE,
+        '#collapsed' => $group_ok,
+        'summary' => $group_summary[$group],
+      );
+      $elements['summary']['#ok'] = $group_ok;
+      $all_ok = $group_ok && $all_ok;
     }
+    $form['summary']['#ok'] = $all_ok;
   }
-}
-
-/**
- * Menu callback for both running tests and listing possible tests
- */
-function simpletest_entrypoint() {
-  simpletest_load();
-  drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css', 'module');
-  drupal_add_js(drupal_get_path('module', 'simpletest') . '/simpletest.js', 'module');
-  $output = drupal_get_form('simpletest_overview_form');
-
-  if (simpletest_running_output()) {
-    return simpletest_running_output() . $output;
-  }
-  else {
-    return $output;
-  }
-}
-
-function simpletest_running_output($output = NULL) {
-  static $o;
-  if ($output != NULL) {
-    $o = $output;
-  }
-  return $o;
-}
-
-/**
- * Form callback;  make the form to run tests
- */
-function simpletest_overview_form() {
-  $output = array(
-    '#theme' => 'simpletest_overview_form'
-  );
-
-  $total_test = &simpletest_get_total_test();
-
-  $test_instances = $total_test->getTestInstances();
-  uasort($test_instances, 'simpletest_compare_instances');
-
-  foreach ($test_instances as $group_test) {
-    $group = array();
-    $tests = $group_test->getTestInstances();
-    $group_class = str_replace(' ', '-', strtolower($group_test->getLabel()));
-    $group['tests'] = array(
-      '#type' => 'fieldset',
-      '#collapsible' => TRUE,
-      '#collapsed' => TRUE,
-      '#title' => 'Tests',
-      '#attributes' => array('class' => $group_class),
-    );
-    foreach ($tests as $test) {
+  foreach ($tests as $group_name => $test_group) {
+    foreach ($test_group as $test) {
       $test_info = $test->getInfo();
-      $group['tests'][get_class($test)] = array(
+      $test_class = get_class($test);
+      $form['tests'][$group_name][$test_class] = array(
         '#type' => 'checkbox',
         '#title' => $test_info['name'],
         '#default_value' => 0,
         '#description' => $test_info['description'],
       );
     }
-    $output[] = $group + array(
-      '#type' => 'fieldset',
-      '#collapsible' => FALSE,
-      '#title' => $group_test->getLabel(),
-      '#attributes' => array('class' => 'all-tests'),
-    );
   }
 
-  $output['run'] = array(
+  $form['run'] = array(
     '#type' => 'fieldset',
     '#collapsible' => FALSE,
     '#collapsed' => FALSE,
     '#title' => t('Run tests'),
   );
-  $output['run']['running_options'] = array(
+  $form['run']['running_options'] = array(
     '#type' => 'radios',
     '#default_value' => 'selected_tests',
     '#options' => array(
@@ -155,25 +174,23 @@ function simpletest_overview_form() {
       'selected_tests' => t('Run selected tests'),
     ),
   );
-  $output['run']['op'] = array(
+  $form['run']['op'] = array(
     '#type' => 'submit',
     '#value' => t('Run tests'),
-    '#submit' => array('simpletest_run_selected_tests')
   );
-
-  $output['reset'] = array(
+  $form['reset'] = array(
     '#type' => 'fieldset',
     '#collapsible' => FALSE,
     '#collapsed' => FALSE,
     '#title' => t('Clean test environment'),
     '#description' => t('Remove tables with the prefix "simpletest" and temporary directories that are left over from tests that crashed.')
   );
-  $output['reset']['op'] = array(
+  $form['reset']['op'] = array(
     '#type' => 'submit',
     '#value' => t('Clean environment'),
     '#submit' => array('simpletest_clean_environment')
   );
-  return $output;
+  return $form;
 }
 
 /**
@@ -181,7 +198,9 @@ function simpletest_overview_form() {
  *
  * @ingroup themeable
  */
-function theme_simpletest_overview_form($form) {
+function theme_simpletest_test_form($form) {
+  drupal_add_css(drupal_get_path('module', 'simpletest') .'/simpletest.css', 'module');
+  drupal_add_js(drupal_get_path('module', 'simpletest') .'/simpletest.js', 'module');
   $header = array(
     array('data' => t('Run'), 'class' => 'simpletest_run checkbox'),
     array('data' => t('Test'), 'class' => 'simpletest_test'),
@@ -196,73 +215,71 @@ function theme_simpletest_overview_form(
 
   // Go through each test group and create a row:
   $rows = array();
-  foreach (element_children($form) as $gid) {
-    if (isset($form[$gid]['tests'])) {
-      $element = &$form[$gid];
-      $test_class = strtolower(trim(preg_replace("/[^\w\d]/", "-",$element["#title"])));
+  foreach (element_children($form['tests']) as $key) {
+    $element = &$form['tests'][$key];
+    $test_class = strtolower(trim(preg_replace("/[^\w\d]/", "-", $key)));
+    $row = array();
+    $row[] = array('id' => $test_class, 'class' => 'simpletest-select-all');
+    $row[] = array(
+      'data' =>  '<div class="simpletest-image" id="simpletest-test-group-'. $test_class .'">'. $js['images'][0] .'</div>&nbsp;<label for="'. $test_class .'-select-all" class="simpletest-group-label">'. $key .'</label>',
+      'style' => 'font-weight: bold;'
+    );
+    $row[] = isset($element['#description']) ? $element['#description'] : '&nbsp;';
+    $rows[] = array('data' => $row, 'class' => 'simpletest-group');
 
-      $row = array();
-      $row[] = array('id' => $test_class, 'class' => 'simpletest-select-all');
-      $row[] = array(
-        'data' =>  '<div class="simpletest-image" id="simpletest-test-group-' . $test_class . '">' . $js['images'][0] . '</div>&nbsp;<label for="' . $test_class . '-select-all" class="simpletest-group-label">' . $element['#title'] . '</label>',
-      );
-      $row[] = $element['#description'];
-      $rows[] = array('data' => $row, 'class' => 'simpletest-group');
-      $current_js = array('testClass' => $test_class . '-test', 'testNames' => array(), 'imageDirection' => 0, 'clickActive' => FALSE);
-
-      // Go through each test in the group and create table rows setting them to invisible:
-      foreach (element_children($element['tests']) as $test_name) {
-        $current_js['testNames'][] = 'edit-' . $test_name;
-        $test = $element['tests'][$test_name];
-        foreach (array('title', 'description') as $key) {
-          $$key = $test['#' . $key];
-          unset($test['#' . $key]);
-        }
-        $test['#name'] = $test_name;
-        $themed_test = drupal_render($test);
-        $row = array();
-        $row[] = $themed_test;
-        $row[] = theme('indentation', 1) . '<label for="edit-' . $test_name . '">' . $title . '</label>';
-        $row[] = '<div class="description">' . $description . '</div>';
-        $rows[] = array('data' => $row, 'style' => 'display: none;', 'class' => $test_class . '-test');
+    $current_js = array('testClass' => $test_class .'-test', 'testNames' => array(), 'imageDirection' => 0, 'clickActive' => FALSE);
+    foreach (element_children($element) as $test_name) {
+      $current_js['testNames'][] = 'edit-'. $test_name;
+      $test = $element[$test_name];
+      foreach (array('title', 'description') as $key) {
+        $$key = $test['#'. $key];
+        unset($test['#'. $key]);
       }
-      $js['simpletest-test-group-' . $test_class] = $current_js;
-      unset($form[$gid]); // Remove test group from form.
+      $test['#name'] = $test_name;
+      $themed_test = drupal_render($test);
+      $row = array();
+      $row[] = $themed_test;
+      $row[] = theme('indentation', 1) .'<label for="edit-'. $test_name .'">'. $title .'</label>';
+      $row[] = '<div class="description">'. $description .'</div>';
+      $rows[] = array('data' => $row, 'style' => 'display: none;', 'class' => $test_class .'-test');
     }
+    $js['simpletest-test-group-'. $test_class] = $current_js;
   }
+  unset($form['tests']);
   drupal_add_js(array('simpleTest' => $js), 'setting');
-
   // Output test groups:
   $output = '';
+  if (isset($form['results'])) {
+    $output .= drupal_render($form['summary']);
+    $output .= drupal_render($form['results']);
+  }
   if (count($rows)) {
     $output .= theme('table', $header, $rows, array('id' => 'simpletest-form-table'));
   }
-
   // Output the rest of the form, excluded test groups which have been removed:
   $output .= drupal_render($form);
 
   return $output;
 }
 
-/**
- * Compare two test instance objects for use in sorting.
- */
-function simpletest_compare_instances(&$a, &$b) {
-  if (substr_compare($a->_label, $b->_label, 0) > 0) {
-    return 1;
-  }
-  return -1;
+function theme_simpletest_result_summary($form) {
+  $output = '<div class="simpletest-'. ($form['#ok'] ? 'pass' : 'fail') .'">';
+  $output .= format_plural($form['#pass'], '1 pass', '@count passes') . ', ';
+  $output .= format_plural($form['#fail'], '1 fail', '@count fails') . ', ';
+  $output .= format_plural($form['#exception'], '1 exception', '@count exceptions');
+  $output .= '</div>';
+  return $output;
 }
 
 /**
  * Run selected tests.
  */
-function simpletest_run_selected_tests($form, &$form_state) {
-  $form_state['redirect'] = FALSE;
+function simpletest_test_form_submit($form, &$form_state) {
   $output = '';
+  $batch_mode = !preg_match("/^simpletest\d+$/", $_SERVER['HTTP_USER_AGENT']);
   switch ($form_state['values']['running_options']) {
     case 'all_tests':
-      $output = simpletest_run_tests();
+      $output = simpletest_run_tests(NULL, 'drupal', $batch_mode);
       break;
     case 'selected_tests':
       $tests_list = array();
@@ -272,7 +289,7 @@ function simpletest_run_selected_tests($
         }
       }
       if (count($tests_list) > 0 ) {
-        $output = simpletest_run_tests($tests_list);
+        $output = simpletest_run_tests($tests_list, 'drupal', $batch_mode);
         break;
       }
       // Fall through
@@ -280,11 +297,142 @@ function simpletest_run_selected_tests($
       drupal_set_message(t('No test has been selected.'), 'error');
   }
 
-  simpletest_running_output($output);
   return FALSE;
 }
 
 /**
+ * Actually runs tests
+ * @param $test_list
+ *   List of tests to run or NULL to run all tests. Defaults to NULL.
+ * @param $reporter
+ *   Which reporter to use. Allowed values are: text, xml, html and drupal,
+ *   drupal being the default.
+ * @param $batch_mode
+ *   Whether to use the batch API or not.
+ */
+function simpletest_run_tests($test_list = NULL, $reporter = 'drupal', $batch_mode = FALSE) {
+  if (!isset($test_list)) {
+    $test_list = simpletest_get_all_tests();
+  }
+  cache_clear_all();
+  db_query('INSERT INTO {simpletest_test_id} VALUES (default)');
+  $test_id = db_last_insert_id('simpletest_test_id', 'test_id');
+
+  if ($batch_mode) {
+    $batch = array(
+      'title' => t('Running SimpleTests'),
+      'operations' => array(
+        array('_simpletest_batch_operation', array($test_list, $test_id)),
+      ),
+      'finished' => '_simpletest_batch_finished',
+      'redirect' => 'admin/build/testing',
+      'progress_message' => t('Processing tests.'),
+      'init_message' => t('SimpleTest is initializing...') . ' ' . format_plural(count($test_list), "one test case will run.", "@count test cases will run."),
+    );
+    batch_set($batch);
+  }
+  else {
+    foreach ($test_list as $test_class) {
+      $test = new $test_class($test_id);
+      $test->run();
+    }
+  }
+}
+
+/**
+ * Batch operation callback.
+ */
+function _simpletest_batch_operation($test_list_init, $test_id_init, &$context) {
+  // Ensure that all classes are loaded.
+  simpletest_get_all_tests();
+
+  if (!isset($context['sandbox']['max'])) {
+    // First iteration: initialize working values.
+    $test_list = $test_list_init;
+    $test_id = $test_id_init;
+    $context['sandbox']['max'] = count($test_list);
+  }
+  else {
+    // Nth iteration: get the current values where we last stored them.
+    $test_list = $context['sandbox']['tests'];
+    $test_id = $context['results'];
+  }
+  $max = $context['sandbox']['max'];
+
+  $test_class = array_shift($test_list);
+  $test = new $test_class($test_id);
+  $test->run();
+  $size = count($test_list);
+
+  $context['message'] = t('Processed test %test (remaining: @count of @max).', array('%test' => $max - $size, '@count' => $size, '@max' => $max)); // !passes passes, !fails fails, !exceptions exceptions. '!passes' => $reporter->_passes, '!fails' => $reporter->_fails, '!exceptions' => $reporter->_exceptions
+
+  // Put back the tests.
+  $context['sandbox']['tests'] = $test_list;
+  $context['results'] = $test_id;
+
+  // Multistep processing: report progress.
+  $context['finished'] = 1 - $size / $max;
+}
+
+function _simpletest_batch_finished($success, $results, $operations) {
+  $_SESSION['test_id'] = $results;
+  if ($success) {
+    drupal_set_message(t('The tests have finished running.'));
+  }
+  else {
+    drupal_set_message(t('The tests did not successfully finish.'), 'error');
+  }
+}
+
+function simpletest_get_all_tests() {
+  static $formatted_classes;
+  if (!isset($formatted_classes)) {
+    require_once drupal_get_path('module', 'simpletest') . '/drupal_web_test_case.php';
+    $files = array();
+    foreach (array_keys(module_rebuild_cache()) as $module) {
+      $module_path = drupal_get_path('module', $module);
+      $test = $module_path . "/$module.test";
+      if (file_exists($test)) {
+        $files[] = $test;
+      }
+
+      $tests_directory = $module_path . '/tests';
+      if (is_dir($tests_directory)) {
+        foreach (file_scan_directory($tests_directory, '\.test$') as $file) {
+          $files[] = $file->filename;
+        }
+      }
+    }
+
+    $existing_classes = get_declared_classes();
+    foreach ($files as $file) {
+      include_once($file);
+    }
+    $classes = array_values(array_diff(get_declared_classes(), $existing_classes));
+    $formatted_classes = array();
+    foreach ($classes as $key => $class) {
+      if (method_exists($class, 'getInfo')) {
+        $formatted_classes[$class] = new $class;
+      }
+    }
+  }
+  if (count($formatted_classes) == 0) {
+    drupal_set_message('No test cases found.', 'error');
+    return FALSE;
+  }
+  return $formatted_classes;
+}
+
+function simpletest_categorize_tests($tests) {
+  $groups = array();
+  foreach ($tests as $test => $instance) {
+    $info = $instance->getInfo();
+    $groups[$info['group']][] = $instance;
+  }
+  return $groups;
+}
+
+/**
  * Remove all temporary database tables and directories.
  */
 function simpletest_clean_environment() {
@@ -378,65 +526,6 @@ function simpletest_clean_temporary_dire
   rmdir($path);
 }
 
-/**
- * Actually runs tests
- * @param array $test_list list of tests to run or DEFAULT NULL run all tests
- * @param boolean $html_reporter TRUE if you want results in simple html, FALSE for full drupal page
- */
-function simpletest_run_tests($test_list = NULL, $reporter = 'drupal') {
-  static $test_running;
-  if (!$test_running) {
-    $test_running = TRUE;
-    $test = simpletest_get_total_test($test_list);
-    switch ($reporter) {
-      case 'text':
-        $reporter = &new TextReporter();
-        break;
-      case 'xml':
-        $reporter = &new XMLReporter();
-        break;
-      case 'html':
-        $reporter = &new HtmlReporter();
-        break;
-      case 'drupal':
-        $reporter = &new DrupalReporter();
-        break;
-    }
-
-    cache_clear_all();
-    $results = $test->run($reporter);
-    $test_running = FALSE;
-
-    switch (get_class($reporter)) {
-      case 'TextReporter':
-      case 'XMLReporter':
-      case 'HtmlReporter':
-        return $results;
-      case 'DrupalReporter':
-        return $reporter->getOutput();
-    }
-  }
-}
-
-/**
- * This function makes sure no unnecessary copies of the DrupalTests object are instantiated
- * @param  array $classes list of all classes the test should concern or
- *                        DEFAULT NULL
- * @return DrupalTests object
- */
-function &simpletest_get_total_test($classes = NULL) {
-  static $total_test;
-  if (!$total_test) {
-    simpletest_load();
-    $total_test = &new DrupalTests();
-  }
-  if (!is_null($classes)) {
-    $dut = new DrupalTests($classes);
-    return $dut;
-  }
-  return $total_test;
-}
-
 function simpletest_settings() {
   $form = array();
 
@@ -473,5 +562,4 @@ function simpletest_settings() {
   );
 
   return system_settings_form($form);
-
 }

