=== modified file 'includes/batch.inc'
--- includes/batch.inc	2008-12-27 19:12:09 +0000
+++ includes/batch.inc	2009-01-05 04:59:04 +0000
@@ -411,6 +411,9 @@ function _batch_finished() {
  */
 function _batch_shutdown() {
   if ($batch = batch_get()) {
+    if (!empty($batch['continue_on_error'])) {
+      drupal_json(array('status' => TRUE, 'percentage' => $percentage, 'message' => $message));
+    }
     db_update('batch')
       ->fields(array('batch' => serialize($batch)))
       ->condition('bid', $batch['id'])

=== modified file 'modules/simpletest/drupal_web_test_case.php'
--- modules/simpletest/drupal_web_test_case.php	2008-12-30 16:43:14 +0000
+++ modules/simpletest/drupal_web_test_case.php	2009-01-04 12:29:34 +0000
@@ -117,6 +117,12 @@ class DrupalWebTestCase {
   protected $assertions = array();
 
   /**
+   * Time limit for the test.
+   */
+  protected $timeLimit = 180;
+
+
+  /**
    * Constructor for DrupalWebTestCase.
    *
    * @param $test_id
@@ -144,8 +150,6 @@ class DrupalWebTestCase {
    *   is the caller function itself.
    */
   private function assert($status, $message = '', $group = 'Other', array $caller = NULL) {
-    global $db_prefix;
-
     // Convert boolean status to string status.
     if (is_bool($status)) {
       $status = $status ? 'pass' : 'fail';
@@ -159,14 +163,20 @@ class DrupalWebTestCase {
       $caller = $this->getAssertionCall();
     }
 
+    // Creation assertion array that can be displayed while tests are running.
+    $this->assertions[] = $this->writeAssert($this->originalPrefix, $this->testId, get_class($this), $status, $message, $group, $caller);
+    return $status == 'pass' ? TRUE : FALSE;
+  }
+
+  function writeAssert($original_prefix, $test_id, $class, $status, $message, $group = 'Other', array $caller = array('function' => '', 'line' => '', 'file' => '')) {
+    global $db_prefix;
     // Switch to non-testing database to store results in.
     $current_db_prefix = $db_prefix;
-    $db_prefix = $this->originalPrefix;
+    $db_prefix = $original_prefix;
 
-    // Creation assertion array that can be displayed while tests are running.
-    $this->assertions[] = $assertion = array(
-      'test_id' => $this->testId,
-      'test_class' => get_class($this),
+    $assertion = array(
+      'test_id' => $test_id,
+      'test_class' => $class,
       'status' => $status,
       'message' => $message,
       'message_group' => $group,
@@ -177,10 +187,9 @@ class DrupalWebTestCase {
 
     // Store assertion for display after the test has completed.
     db_insert('simpletest')->fields($assertion)->execute();
-
     // Return to testing prefix.
     $db_prefix = $current_db_prefix;
-    return $status == 'pass' ? TRUE : FALSE;
+    return $assertion;
   }
 
   /**
@@ -837,6 +846,7 @@ class DrupalWebTestCase {
     variable_set('file_directory_path', file_directory_path() . '/' . $db_prefix);
     $directory = file_directory_path();
     file_check_directory($directory, FILE_CREATE_DIRECTORY); // Create the files directory.
+    set_time_limit($this->timeLimit);
   }
 
   /**

=== modified file 'modules/simpletest/simpletest.module'
--- modules/simpletest/simpletest.module	2008-12-30 16:43:14 +0000
+++ modules/simpletest/simpletest.module	2009-01-05 06:49:34 +0000
@@ -387,8 +387,10 @@ function simpletest_run_tests($test_list
  * Batch operation callback.
  */
 function _simpletest_batch_operation($test_list_init, $test_id, &$context) {
+  global $db_prefix, $simpletest_timeout;
   // Ensure that all classes are loaded before we unserialize some instances.
   simpletest_get_all_tests();
+  $test_list = &$context['sandbox']['tests'];
 
   // Get working values.
   if (!isset($context['sandbox']['max'])) {
@@ -399,15 +401,18 @@ function _simpletest_batch_operation($te
   }
   else {
     // Nth iteration: get the current values where we last stored them.
-    $test_list = $context['sandbox']['tests'];
     $test_results = $context['sandbox']['test_results'];
   }
   $max = $context['sandbox']['max'];
 
-  // Perform the next test.
+  // Perform the next test. This also removes the test from the batch so in
+  // case of a time out, it won't run again.
   $test_class = array_shift($test_list);
   $test = new $test_class($test_id);
+  $simpletest_timeout = TRUE;
+  register_shutdown_function('_simpletest_shutdown', $db_prefix, $test_id, $test_class);
   $test->run();
+  $simpletest_timeout = FALSE;
   $size = count($test_list);
   $info = $test->getInfo();
 
@@ -426,7 +431,6 @@ function _simpletest_batch_operation($te
   $context['message'] .= theme('item_list', $items);
 
   // Save working values for the next iteration.
-  $context['sandbox']['tests'] = $test_list;
   $context['sandbox']['test_results'] = $test_results;
   // The test_id is the only thing we need to save for the report page.
   $context['results']['test_id'] = $test_id;
@@ -435,6 +439,13 @@ function _simpletest_batch_operation($te
   $context['finished'] = 1 - $size / $max;
 }
 
+function _simpletest_shutdown($original_prefix, $test_id, $test_class) {
+  global $simpletest_timeout;
+  if ($simpletest_timeout) {
+    DrupalWebTestCase::writeAssert($original_prefix, $test_id, $test_class, 'fail', t('!class timed out', array('!class' => $test_class)));
+  }
+}
+
 function _simpletest_batch_finished($success, $results, $operations) {
   if (isset($results['test_id'])) {
     $_SESSION['test_id'] = $results['test_id'];

=== added file 'modules/simpletest/tests/timeout.test'
--- modules/simpletest/tests/timeout.test	1970-01-01 00:00:00 +0000
+++ modules/simpletest/tests/timeout.test	2009-01-04 12:39:46 +0000
@@ -0,0 +1,19 @@
+<?php
+
+class TimeoutTestCase extends DrupalWebTestCase {
+
+  function getInfo() {
+    return array(
+      'name' => t('timeouttest'),
+      'description' => '',
+      'group' => t('Timeout')
+    );
+  }
+
+  function setUp() {
+    $this->timeLimit = 10;
+    parent::setUp();
+  }
+
+  function testtimeout() { while (true); }
+}

