=== modified file 'modules/simpletest/drupal_test_suite.php'
--- modules/simpletest/drupal_test_suite.php	2008-05-06 11:21:10 +0000
+++ modules/simpletest/drupal_test_suite.php	2008-06-07 05:18:59 +0000
@@ -65,9 +65,9 @@ class DrupalTests extends DrupalTestSuit
     foreach ($groups as $group_name => $group) {
       $group_test = &new DrupalTestSuite($group_name);
       foreach ($group as $key => $v) {
-        $group_test->addTestCase($group[$key]);
+        $group_test->add($group[$key]);
       }
-      $this->addTestCase($group_test);
+      $this->add($group_test);
     }
   }
 

=== modified file 'modules/simpletest/simpletest.module'
--- modules/simpletest/simpletest.module	2008-05-10 07:46:22 +0000
+++ modules/simpletest/simpletest.module	2008-06-07 15:50:03 +0000
@@ -82,24 +82,23 @@ 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;
+  if (isset($_SESSION['simpletest_reporter'])) {
+    $reporter = $_SESSION['simpletest_reporter'];
+    if (is_string($reporter)) {
+      $reporter = unserialize($_SESSION['simpletest_reporter']);
+    }
+    $output = $reporter->getOutput() . drupal_get_form('simpletest_overview_form');
+    unset($_SESSION['simpletest_reporter']);
+    return $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
  */
@@ -260,9 +259,10 @@ function simpletest_compare_instances(&$
 function simpletest_run_selected_tests($form, &$form_state) {
   $form_state['redirect'] = FALSE;
   $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 +272,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,7 +280,6 @@ function simpletest_run_selected_tests($
       drupal_set_message(t('No test has been selected.'), 'error');
   }
 
-  simpletest_running_output($output);
   return FALSE;
 }
 
@@ -380,14 +379,19 @@ function simpletest_clean_temporary_dire
 
 /**
  * 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
+ * @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') {
+function simpletest_run_tests($test_list = NULL, $reporter = 'drupal', $batch_mode = FALSE) {
   static $test_running;
   if (!$test_running) {
     $test_running = TRUE;
-    $test = simpletest_get_total_test($test_list);
+    $tests = simpletest_get_total_test($test_list);
     switch ($reporter) {
       case 'text':
         $reporter = &new TextReporter();
@@ -404,17 +408,72 @@ function simpletest_run_tests($test_list
     }
 
     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();
+    if ($batch_mode) {
+      $batch = array(
+        'title' => t('Running SimpleTests'),
+        'operations' => array(
+          array('_simpletest_batch_operation', array(serialize($tests), serialize($reporter))),
+        ),
+        'finished' => '_simpletest_batch_finished',
+        'redirect' => 'admin/build/testing',
+        'progress_message' => t('Processing tests.'),
+        'init_message' => t('SimpleTest is initializing...') . ' ' . format_plural($tests->getSize(), "one test case will run.", "@count test cases will run."),
+      );
+      batch_set($batch);
+      return;
     }
+    else {
+      $results = $tests->run($reporter);
+      $test_running = FALSE;
+      if (get_class($reporter) == 'DrupalReporter') {
+        $_SESSION['simpletest_reporter'] = $reporter;
+      }
+      return $results;
+    }
+  }
+}
+
+/**
+ * Batch operation callback.
+ */
+function _simpletest_batch_operation($tests_init, $reporter_init, &$context) {
+  // Ensure that all classes are loaded before we unserialize some instances.
+  simpletest_get_total_test();
+
+  if (!isset($context['sandbox']['max'])) {
+    // First iteration: initialize working values.
+    $tests = unserialize($tests_init);
+    $reporter = unserialize($reporter_init);
+    $context['sandbox']['max'] = $tests->getSize();
+  }
+  else {
+    // Nth iteration: get the current values where we last stored them.
+    $tests = unserialize($context['sandbox']['tests']);
+    $reporter = unserialize($context['results']);
+  }
+
+  $test_ran = $tests->runOneByOne($reporter);
+  $size = $tests->getSize();
+
+  $context['message'] = t('Processed test %test (remaining: @count of @max). !passes passes, !fails fails, !exceptions exceptions.', array('%test' => $test_ran, '@count' => $size, '@max' => $context['sandbox']['max'], '!passes' => $reporter->_passes, '!fails' => $reporter->_fails, '!exceptions' => $reporter->_exceptions));
+
+  // Put back the tests and reporter. The reporter is stored in 'results'
+  // so that it is available to the 'finished' callback.
+  $context['sandbox']['tests'] = serialize($tests);
+  $context['results'] = serialize($reporter);
+
+  // Multistep processing: report progress.
+  $context['finished'] = 1 - $size / $context['sandbox']['max'];
+}
+
+function _simpletest_batch_finished($success, $results, $operations) {
+  $_SESSION['simpletest_reporter'] = $results;
+  if ($success) {
+    drupal_set_message(t('The tests have finished running.'));
+  }
+  else {
+    drupal_set_message(t('The tests did not successfully finish.'), 'error');
   }
 }
 
@@ -473,5 +532,4 @@ function simpletest_settings() {
   );
 
   return system_settings_form($form);
-
 }

=== modified file 'modules/simpletest/test_case.php'
--- modules/simpletest/test_case.php	2008-05-10 06:55:09 +0000
+++ modules/simpletest/test_case.php	2008-06-07 05:36:26 +0000
@@ -390,6 +390,8 @@ class SimpleFileLoader {
 class TestSuite {
   var $_label;
   var $_test_cases;
+  var $_size = 0;
+  var $_already_ran = FALSE;
 
   /**
    *    Sets the name of the test suite.
@@ -400,6 +402,7 @@ class TestSuite {
   function TestSuite($label = false) {
     $this->_label = $label;
     $this->_test_cases = array();
+    $this->_size = 0;
   }
 
   /**
@@ -421,19 +424,14 @@ class TestSuite {
    *    @deprecated
    */
   function addTestCase(&$test_case) {
-    $this->_test_cases[] = &$test_case;
+    $this->add($test_case);
   }
 
   /**
    *    @deprecated
    */
   function addTestClass($class) {
-    if (TestSuite::getBaseTestCase($class) == 'testsuite') {
-      $this->_test_cases[] = &new $class();
-    }
-    else {
-      $this->_test_cases[] = $class;
-    }
+    $this->add($class);
   }
 
   /**
@@ -447,12 +445,11 @@ class TestSuite {
   function add(&$test_case) {
     if (!is_string($test_case)) {
       $this->_test_cases[] = &$test_case;
-    }
-    elseif (TestSuite::getBaseTestCase($class) == 'testsuite') {
-      $this->_test_cases[] = &new $class();
+      $this->_size += $test_case->getSize();
     }
     else {
       $this->_test_cases[] = $class;
+      $this->_size += $test_case->getSize();
     }
   }
 
@@ -509,22 +506,55 @@ class TestSuite {
     return $reporter->getStatus();
   }
 
+  function runOneByOne(&$reporter) {
+    if ($this->getSize() == 0) {
+      // Nothing to do, exit early
+      return;
+    }
+
+    // This is a new group.
+    if (!$this->_already_ran) {
+      $this->_already_ran = TRUE;
+      $reporter->paintGroupStart($this->getLabel(), $this->getSize());
+    }
+
+    $test = array_shift($this->_test_cases);
+    // This is a test class.
+    if (is_string($test)) {
+      $test = &new $test();
+    }
+
+    if (is_a($test, 'TestSuite')) {
+      // This is a test suite, run one test at a time
+      $this->_size -= $test->getSize();
+      $test_ran = $test->runOneByOne($reporter);
+      if ($test->getSize() > 0) {
+        // Requeue this test suite
+        array_unshift($this->_test_cases, $test);
+        $this->_size += $test->getSize();
+      }
+    }
+    else {
+      $this->_size -= 1;
+      $test->run($reporter);
+      $test_ran = $test->getLabel();
+    }
+
+    // Finished with this group.
+    if ($this->getSize() == 0) {
+      $reporter->paintGroupEnd($this->getLabel());
+    }
+    return $test_ran;
+  }
+
   /**
    *    Number of contained test cases.
    *    @return integer     Total count of cases in the group.
    *    @access public
    */
   function getSize() {
-    $count = 0;
-    foreach ($this->_test_cases as $case) {
-      if (is_string($case)) {
-        $count++;
-      }
-      else {
-        $count += $case->getSize();
-      }
-    }
-    return $count;
+    // add and runOneByOne methods set this property so no need to calculate.
+    return $this->_size;
   }
 
   /**

