? modules/simpletest/drupal_structured_reporter.php
Index: modules/simpletest/drupal_test_suite.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_test_suite.php,v
retrieving revision 1.3
diff -u -r1.3 drupal_test_suite.php
--- modules/simpletest/drupal_test_suite.php	6 May 2008 11:21:10 -0000	1.3
+++ modules/simpletest/drupal_test_suite.php	5 Jun 2008 23:23:54 -0000
@@ -65,9 +65,9 @@
     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);
     }
   }
 
Index: modules/simpletest/simpletest.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.module,v
retrieving revision 1.3
diff -u -r1.3 simpletest.module
--- modules/simpletest/simpletest.module	10 May 2008 07:46:22 -0000	1.3
+++ modules/simpletest/simpletest.module	5 Jun 2008 23:23:55 -0000
@@ -82,24 +82,20 @@
   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;
+  $output = drupal_get_form('simpletest_overview_form');
+  
+  if (isset($_SESSION['simpletest_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
  */
@@ -155,6 +151,11 @@
       'selected_tests' => t('Run selected tests'),
     ),
   );
+  $output['run']['use_batch'] = array(
+    '#type' => 'checkbox',
+    '#default_value' => TRUE,
+    '#title' => t('Display testing progress'),
+  );
   $output['run']['op'] = array(
     '#type' => 'submit',
     '#value' => t('Run tests'),
@@ -260,19 +261,21 @@
 function simpletest_run_selected_tests($form, &$form_state) {
   $form_state['redirect'] = FALSE;
   $output = '';
+  $batch = $form_state['values']['use_batch'];
+  unset($form_state['values']['use_batch']);
   switch ($form_state['values']['running_options']) {
     case 'all_tests':
-      $output = simpletest_run_tests();
+      $output = simpletest_run_tests(NULL, 'drupal', $batch);
       break;
     case 'selected_tests':
       $tests_list = array();
       foreach ($form_state['values'] as $item => $value) {
-        if ($value === 1 && strpos($item, 'selectall') === FALSE) {
+        if ($value === 1) {
            $tests_list[] = $item;
         }
       }
       if (count($tests_list) > 0 ) {
-        $output = simpletest_run_tests($tests_list);
+        $output = simpletest_run_tests($tests_list, 'drupal', $batch);
         break;
       }
       // Fall through
@@ -280,7 +283,6 @@
       drupal_set_message(t('No test has been selected.'), 'error');
   }
 
-  simpletest_running_output($output);
   return FALSE;
 }
 
@@ -383,11 +385,11 @@
  * @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') {
+function simpletest_run_tests($test_list = NULL, $reporter = 'drupal', $batch = 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,21 +406,76 @@
     }
 
     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) {
+      $operations = array();
+      $operations[] = array('_simpletest_batch_operation', array(serialize($tests), serialize($reporter)));
+
+      $batch = array(
+        'title' => t('Running SimpleTests'),
+        'operations' => $operations,
+        '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);
+    }
+    else {
+      $results = $tests->run($reporter);
+      $test_running = FALSE;
+      return $results;
     }
   }
 }
 
 /**
+ * Batch operation callback.
+ */
+function _simpletest_batch_operation($tests, $reporter, &$context) {
+  // Ensure that all classes are loaded.
+  simpletest_get_total_test();
+  $reporter = unserialize( isset($context['sandbox']['reporter']) ? $context['sandbox']['reporter'] : $reporter);
+  $tests = unserialize( isset($context['sandbox']['tests']) ? $context['sandbox']['tests'] : $tests);
+
+  if (!isset($context['sandbox']['progress'])) {
+    $context['sandbox']['progress'] = 0;
+    $context['sandbox']['max'] = $tests->getSize();
+  }
+
+  $test_ran = $tests->runOneByOne($reporter);
+  $context['sandbox']['progress']++;
+
+  $context['message'] = t('Processed test %test (remaining: @count of @max)', array('%test' => $test_ran, '@count' => $tests->getSize(), '@max' => $context['sandbox']['max']));
+
+  // Put back the reporter and tests
+  $context['sandbox']['reporter'] = serialize($reporter);
+  $context['sandbox']['tests'] = serialize($tests);
+
+  // Multistep processing: report progress.
+  if (($size = $tests->getSize()) != 0) {
+    $context['finished'] = 1 - $size / $context['sandbox']['max'];
+  }
+  else {
+    // Finished: save the reporter
+    $context['results'][] = $context['sandbox']['reporter'];
+  }
+}
+
+/**
+ * Batch finished callback.
+ */
+function _simpletest_batch_finished($success, $results, $operations) {
+  $_SESSION['simpletest_reporter'] = array_pop($results);
+  if ($success) {
+    drupal_set_message(t('The tests have finished running.'));
+  }
+  else {
+    drupal_set_message(t('The tests did not successfully finish.'), 'error');
+  }
+}
+
+/**
  * 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
@@ -473,5 +530,4 @@
   );
 
   return system_settings_form($form);
-
 }
Index: modules/simpletest/test_case.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/test_case.php,v
retrieving revision 1.3
diff -u -r1.3 test_case.php
--- modules/simpletest/test_case.php	10 May 2008 06:55:09 -0000	1.3
+++ modules/simpletest/test_case.php	5 Jun 2008 23:23:56 -0000
@@ -390,6 +390,8 @@
 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 @@
   function TestSuite($label = false) {
     $this->_label = $label;
     $this->_test_cases = array();
+    $this->_size = 0;
   }
 
   /**
@@ -421,19 +424,14 @@
    *    @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 @@
   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,51 @@
     return $reporter->getStatus();
   }
 
+  function runOneByOne(&$reporter) {
+    if ($this->getSize() == 0) {
+      // Nothing to do, exit early
+      return;
+    }
+
+    if (!$this->_already_ran) {
+      $this->_already_ran = TRUE;
+      $reporter->paintGroupStart($this->getLabel(), $this->getSize());
+    }
+
+    $test = array_shift($this->_test_cases);
+    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();
+    }
+
+    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;
+    return $this->_size;
   }
 
   /**
