Index: modules/simpletest/simpletest.css
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.css,v
retrieving revision 1.2
diff -u -r1.2 simpletest.css
--- modules/simpletest/simpletest.css	24 Jun 2008 21:51:02 -0000	1.2
+++ modules/simpletest/simpletest.css	6 Sep 2008 21:24:19 -0000
@@ -1,11 +1,9 @@
 /* $Id: simpletest.css,v 1.2 2008/06/24 21:51:02 dries Exp $ */
 
-/* Addon for the simpletest module */
-#simpletest {
-}
 /* Test Table */
 th.simpletest_run {
   width: 50px;
+  padding-right: 0.5em;
 }
 th.simpletest_test {
   width: 160px;
@@ -17,46 +15,55 @@
 }
 
 table#simpletest-form-table tr td {
-  background-color: white !important;
+  background-color: white;
 }
 
 table#simpletest-form-table tr.simpletest-group td {
-  background-color: #EDF5FA !important;
+  background-color: #EDF5FA;
 }
 
-div.simpletest-pass {
-  color: #33a333;
+table#simpletest-form-table thead .form-item {
+  margin-bottom:0pt;
+  margin-top:0pt;
+  white-space:nowrap;
 }
 
-div.simpletest-fail {
-  color: #a30000;
+div.simpletest-image {
+  display: inline;
 }
 
-tr.simpletest-pass.odd {
-  background: #b6ffb6;
+div.simpletest-image, label.simpletest-group-label {
+  cursor: pointer;
 }
 
-tr.simpletest-pass.even {
-  background: #9bff9b;
+.simpletest-pass {
+  color: #316d31;
 }
 
-tr.simpletest-fail.odd {
-  background: #ffc9c9;
+.simpletest-fail {
+  color: #981010;
 }
 
-tr.simpletest-fail.even {
-  background: #ffacac;
+.simpletest-exception {
+  color: #bda400;
 }
 
-tr.simpletest-exception.odd {
-  background: #f4ea71;
+div.message > div.item-list {
+  font-weight: normal;
 }
 
-tr.simpletest-exception.even {
-  background: #f5e742;
+li.simpletest-important {
+  font-weight: bold;
 }
 
-div.simpletest-image {
-  display: inline;
+ul.simpletest-results li div.item-list ul {
+  padding-left: 20px;
+}
+
+ul.simpletest-results li span.simpletest-overview {
   cursor: pointer;
 }
+
+ul.simpletest-results li span.simpletest-overview:hover {
+  text-decoration: underline;
+}
\ No newline at end of file
Index: modules/simpletest/simpletest.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.test,v
retrieving revision 1.6
diff -u -r1.6 simpletest.test
--- modules/simpletest/simpletest.test	22 Aug 2008 12:35:55 -0000	1.6
+++ modules/simpletest/simpletest.test	6 Sep 2008 21:24:20 -0000
@@ -112,14 +112,13 @@
    * Confirm that the stub test produced the desired results.
    */
   function confirmStubTestResults() {
-    $this->assertAssertion($this->pass, 'Other', 'Pass');
-    $this->assertAssertion($this->fail, 'Other', 'Fail');
-
-    $this->assertAssertion(t('Created permissions: @perms', array('@perms' => $this->valid_permission)), 'Role', 'Pass');
-    $this->assertAssertion(t('Invalid permission %permission.', array('%permission' => $this->invalid_permission)), 'Role', 'Fail');
+    $info = $this->getInfo();
+    $name = $info['name'];
+    $this->assertAssertion($this->pass, $name);
+    $this->assertAssertion($this->fail, $name);
 
-    $this->test_ids[] = $test_id = $this->getTestIdFromResults();
-    $this->assertTrue($test_id, t('Found test ID in results.'));
+    $this->assertAssertion(t('Created permissions: @perms', array('@perms' => $this->valid_permission)), 'Role');
+    $this->assertAssertion(t('Invalid permission %permission.', array('%permission' => $this->invalid_permission)), 'Role');
   }
 
   /**
@@ -143,18 +142,17 @@
    * @param string $status Assertion status.
    * @return Assertion result.
    */
-  function assertAssertion($message, $type, $status) {
+  function assertAssertion($message, $type) {
     $message = trim(strip_tags($message));
     $found = FALSE;
-    foreach ($this->results['assertions'] as $assertion) {
+    foreach ($this->results as $assertion) {
       if ($assertion['message'] == $message &&
-          $assertion['type'] == $type &&
-          $assertion['status'] == $status) {
+          $assertion['type'] == $type) {
         $found = TRUE;
         break;
       }
     }
-    return $this->assertTrue($found, t('Found assertion {"@message", "@type", "@status"}.', array('@message' => $message, '@type' => $type, '@status' => $status)));
+    return $this->assertTrue($found, t('Found assertion {"@message", "@type"}.', array('@message' => $message, '@type' => $type)));
   }
 
   /**
@@ -164,20 +162,13 @@
     $results = array();
 
     if ($this->parse()) {
-      if ($fieldset = $this->getResultFieldSet()) {
-        // Code assumes this is the only test in group.
-        $results['summary'] = $this->asText($fieldset->div);
-        $results['name'] = $this->asText($fieldset->fieldset->legend);
-
-        $results['assertions'] = array();
-        $tbody = $fieldset->fieldset->table->tbody;
-        foreach ($tbody->tr as $row) {
+      if ($list = $this->getResultList()) {
+        $results = array();
+        foreach ($list->li as $li) {
           $assertion = array();
-          $assertion['message'] = $this->asText($row->td[0]);
-          $assertion['type'] = $this->asText($row->td[1]);
-          $ok_url = (url('misc/watchdog-ok.png') == 'misc/watchdog-ok.png') ? 'misc/watchdog-ok.png' : (base_path() . 'misc/watchdog-ok.png');
-          $assertion['status'] = ($row->td[5]->img['src'] == $ok_url) ? 'Pass' : 'Fail';
-          $results['assertions'][] = $assertion;
+          $assertion['message'] = $this->asText($li->span);
+          $assertion['type'] = str_replace('Group: ', '', $this->asText($li->div->ul->li[0]));
+          $results[] = $assertion;
         }
       }
     }
@@ -190,11 +181,10 @@
    * @return fieldset containing the results for group this test is in.
    */
   function getResultFieldSet() {
-    $fieldsets = $this->xpath('//fieldset');
-    $info = $this->getInfo();
-    foreach ($fieldsets as $fieldset) {
-      if ($fieldset->legend == $info['group']) {
-        return $fieldset;
+    $list = $this->elements->xpath('//li');
+    foreach ($list as $li) {
+      if (strpos($this->asText($li->span), t('SimpleTest: ')) === 0) {
+        return $li->div->ul->li->div->ul;
       }
     }
     return FALSE;
Index: modules/simpletest/simpletest.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.install,v
retrieving revision 1.7
diff -u -r1.7 simpletest.install
--- modules/simpletest/simpletest.install	16 Aug 2008 20:57:14 -0000	1.7
+++ modules/simpletest/simpletest.install	6 Sep 2008 21:24:19 -0000
@@ -177,25 +177,25 @@
         'default' => '',
         'description' => t('The message group this message belongs to. For example: warning, browser, user.'),
       ),
-      'caller' => array(
+      'function' => array(
         'type' => 'varchar',
         'length' => 255,
         'not null' => TRUE,
         'default' => '',
-        'description' => t('Name of the caller function or method that created this message.'),
+        'description' => t('Name of the assertion function or method that created this message.'),
       ),
       'line' => array(
         'type' => 'int',
         'not null' => TRUE,
         'default' => 0,
-        'description' => t('Line number of the caller.'),
+        'description' => t('Line number on which the function is called.'),
       ),
       'file' => array(
         'type' => 'varchar',
         'length' => 255,
         'not null' => TRUE,
         'default' => '',
-        'description' => t('Name of the file where the caller is.'),
+        'description' => t('Name of the file where the function is called.'),
       ),
     ),
     'primary key' => array('message_id'),
Index: modules/simpletest/drupal_web_test_case.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v
retrieving revision 1.37
diff -u -r1.37 drupal_web_test_case.php
--- modules/simpletest/drupal_web_test_case.php	6 Sep 2008 08:36:20 -0000	1.37
+++ modules/simpletest/drupal_web_test_case.php	6 Sep 2008 21:24:19 -0000
@@ -43,56 +43,73 @@
    *   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) {
+  protected function _assert($status, $message = '', $group = 'Other') {
     global $db_prefix;
+
+    // Convert boolean status to string status.
     if (is_bool($status)) {
       $status = $status ? 'pass' : 'fail';
     }
+
+    // Incrament summary result counter.
     $this->_results['#' . $status]++;
-    if (!isset($custom_caller)) {
-      $callers = debug_backtrace();
-      array_shift($callers);
-      foreach ($callers as $function) {
-        if (substr($function['function'], 0, 6) != 'assert' && $function['function'] != 'pass' && $function['function'] != 'fail') {
-          break;
-        }
-      }
-    }
-    else {
-      $function = $custom_caller;
-    }
+
+    // Get the function information about the call to the assertion method.
+    $function = $this->getAssertionCall();
+
+    // Switch to non-testing database to store results in.
     $current_db_prefix = $db_prefix;
     $db_prefix = $this->db_prefix_original;
-    db_insert('simpletest')->fields(array(
+
+    // Creation assertion array to be displayed white tests are running.
+    $this->_assertions[] = $assertion = array(
       'test_id' => $this->test_id,
-    'test_class' => get_class($this), 
-    'status' => $status, 
-    'message' => substr($message, 0, 255),  // Some messages are too long for the database.
-    'message_group' => $group, 
-    'caller' => $function['function'], 
-    'line' => $function['line'], 
-    'file' => $function['file'],
-    ))->execute();
-    $this->_assertions[] = array(
+      'test_class' => get_class($this),
       'status' => $status,
-      'message' => $message,
+      'message' => substr($message, 0, 255),  // Some messages are too long for the database.
       'group' => $group,
       'function' => $function['function'],
       'line' => $function['line'],
       'file' => $function['file'],
     );
+
+    // Store assertion for display after the test has completed.
+    // Change the 'group' attribute to 'message_group' to avoid reserved word
+    // conflict when inserted into database.
+    $assertion['message_group'] = $assertion['group'];
+    unset($assertion['group']);
+    db_insert('simpletest')->fields($assertion)->execute();
+
+    // Return to testing prefix.
     $db_prefix = $current_db_prefix;
     return $status;
   }
 
   /**
+   * Cycle through backtrace until the first non-assertion method is found.
+   * Return the previous function call to the assertion method.
+   *
+   * @return
+   *   Array representing a backtrace element.
+   */
+  protected function getAssertionCall() {
+    $backtrace = debug_backtrace();
+    array_shift($backtrace); // Remove call to getCaller().
+    array_shift($backtrace); // Remove call to _assert().
+
+    foreach ($backtrace as $trace) {
+      if (substr($trace['function'], 0, 6) != 'assert' &&
+          $trace['function'] != 'pass' &&
+          $trace['function'] != 'fail' &&
+          $trace['function'] != 'error') {
+        return $function;
+      }
+      $function = $trace; // Keep track of previous trace element.
+    }
+  }
+
+  /**
    * Check to see if a value is not false (not an empty string, 0, NULL, or FALSE).
    *
    * @param $value
@@ -263,11 +280,9 @@
    *   The message to display along with the assertion.
    * @param $group
    *   The type of assertion - examples are "Browser", "PHP".
-   * @param $custom_caller
-   *   The caller of the error.
    */
-  protected function error($message = '', $group = 'Other', $custom_caller = NULL) {
-    return $this->_assert('exception', $message, $group, $custom_caller);
+  protected function error($message = '', $group = 'Other') {
+    return $this->_assert('exception', $message, $group);
   }
 
   /**
@@ -732,7 +747,6 @@
 
       // Close the CURL handler.
       $this->curlClose();
-      restore_error_handler();
     }
   }
 
@@ -807,7 +821,7 @@
       // them.
       @$htmlDom = DOMDocument::loadHTML($this->_content);
       if ($htmlDom) {
-        $this->assertTrue(TRUE, t('Valid HTML found on "@path"', array('@path' => $this->getUrl())), t('Browser'));
+        $this->pass(t('Valid HTML found on "@path"', array('@path' => $this->getUrl())), t('Browser'));
         // It's much easier to work with simplexml than DOM, luckily enough
         // we can just simply import our DOM tree.
         $this->elements = simplexml_import_dom($htmlDom);
@@ -1290,7 +1304,7 @@
    *   TRUE on pass, FALSE on fail.
    */
   function assertText($text, $message = '', $group = 'Other') {
-    return $this->assertTextHelper($text, $message, $group = 'Other', FALSE);
+    return $this->assertTextHelper($text, $message, $group, FALSE);
   }
 
   /**
Index: modules/simpletest/simpletest.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.module,v
retrieving revision 1.11
diff -u -r1.11 simpletest.module
--- modules/simpletest/simpletest.module	21 Aug 2008 19:36:38 -0000	1.11
+++ modules/simpletest/simpletest.module	6 Sep 2008 21:24:20 -0000
@@ -26,6 +26,14 @@
     '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'),
+    'type' => MENU_NORMAL_ITEM,
+  );
+  $items['admin/build/testing/results'] = array(
+    'title' => 'Results',
+    'page callback' => 'simpletest_display_results',
+    'description' => 'View the test results.',
+    'access arguments' => array('administer unit tests'),
+    'type' => MENU_CALLBACK,
   );
   return $items;
 }
@@ -50,110 +58,34 @@
     'simpletest_result_summary' => array(
       'arguments' => array('form' => NULL)
     ),
+    'simpletest_results' => array(
+      'arguments' => array('results' => NULL, 'assertions' => NULL),
+    ),
   );
 }
 
 /**
- * Menu callback for both running tests and listing possible tests
+ * Menu callback -- List all tests that can be run.
  */
 function simpletest_test_form() {
   global $db_prefix, $db_prefix_original;
+
   $form = array();
+  $form['run_all'] = array(
+    '#type' => 'checkbox',
+    '#default_value' => 0,
+    '#attributes' => array('class' => 'simpletest-master'),
+  );
+
+  // List out all tests in groups for selection.
   $uncategorized_tests = simpletest_get_all_tests();
   $tests = simpletest_categorize_tests($uncategorized_tests);
-  if (isset($_SESSION['test_id'])) {
-    // Select all results using the active test ID used to group them.
-    $results = db_query("SELECT * FROM {simpletest} WHERE test_id = %d ORDER BY test_class, message_id", $_SESSION['test_id']);
-
-    $summary = array(
-      '#theme' => 'simpletest_result_summary',
-      '#pass' => 0,
-      '#fail' => 0,
-      '#exception' => 0,
-      '#weight' => -10,
-    );
-    $form['summary'] = $summary;
-    $form['results'] = array();
-    $group_summary = array();
-    $map = array(
-      'pass' => theme('image', 'misc/watchdog-ok.png'),
-      'fail' => theme('image', 'misc/watchdog-error.png'),
-      'exception' => theme('image', 'misc/watchdog-warning.png'),
-    );
-    $header = array(t('Message'), t('Group'), t('Filename'), t('Line'), t('Function'), array('colspan' => 2, 'data' => t('Status')));
-    while ($result = db_fetch_object($results)) {
-      $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],
-          ),
-          'class' => "simpletest-$status",
-        );
-      }
-      unset($element);
-    }
 
-    // Clear test results.
-    if (variable_get('simpletest_clear_results', TRUE)) {
-      db_query('DELETE FROM {simpletest} WHERE test_id = %d', $_SESSION['test_id']);
-      db_query('DELETE FROM {simpletest_test_id} WHERE test_id = %d', $_SESSION['test_id']);
-    }
-    unset($_SESSION['test_id']);
-
-    $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']['#markup'] = 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;
-  }
   foreach ($tests as $group_name => $test_group) {
     foreach ($test_group as $test) {
       $test_info = $test->getInfo();
       $test_class = get_class($test);
+
       $form['tests'][$group_name][$test_class] = array(
         '#type' => 'checkbox',
         '#title' => $test_info['name'],
@@ -163,21 +95,8 @@
     }
   }
 
-  $form['run'] = array(
-    '#type' => 'fieldset',
-    '#collapsible' => FALSE,
-    '#collapsed' => FALSE,
-    '#title' => t('Run tests'),
-  );
-  $form['run']['running_options'] = array(
-    '#type' => 'radios',
-    '#default_value' => 'selected_tests',
-    '#options' => array(
-      'all_tests' => t('Run all tests (WARNING, this may take a long time)'),
-      'selected_tests' => t('Run selected tests'),
-    ),
-  );
-  $form['run']['op'] = array(
+  // Action buttons.
+  $form['op'] = array(
     '#type' => 'submit',
     '#value' => t('Run tests'),
   );
@@ -186,7 +105,8 @@
     '#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.')
+    '#description' => t('Remove tables with the prefix "simpletest" and temporary directories that are left over from' .
+                        'tests that crashed. This is intended for developers when creating tests.')
   );
   $form['reset']['op'] = array(
     '#type' => 'submit',
@@ -202,13 +122,18 @@
  * @ingroup themeable
  */
 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');
+  drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css', 'module');
+  drupal_add_js(drupal_get_path('module', 'simpletest') . '/simpletest.js', 'module');
+
+  // Create header for test selection table.
   $header = array(
-    array('data' => t('Run'), 'class' => 'simpletest_run checkbox'),
+    array('data' => drupal_render($form['run_all']), 'class' => 'simpletest_run checkbox'),
     array('data' => t('Test'), 'class' => 'simpletest_test'),
     array('data' => t('Description'), 'class' => 'simpletest_description'),
   );
+  unset($form['run_all']);
+
+  // Define the images used to expand/collapse the test groups.
   $js = array(
     'images' => array(
       theme('image', 'misc/menu-collapsed.png', 'Expand', 'Expand'),
@@ -216,61 +141,146 @@
     ),
   );
 
-  // Go through each test group and create a row:
+  // Go through each test group and create a row.
   $rows = array();
   foreach (element_children($form['tests']) as $key) {
     $element = &$form['tests'][$key];
-    $test_class = strtolower(trim(preg_replace("/[^\w\d]/", "-", $key)));
     $row = array();
+
+    // Make the class name safe for output on the pace by replacing all
+    // non-word/decimal characters with a dash (-).
+    $test_class = strtolower(trim(preg_replace("/[^\w\d]/", "-", $key)));
+
+    // Place-holder for checkboxes to select group of tests.
     $row[] = array('id' => $test_class, 'class' => 'simpletest-select-all');
+
+    // Expand/collapse image and group title.
     $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>',
+      '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');
 
-    $current_js = array('testClass' => $test_class .'-test', 'testNames' => array(), 'imageDirection' => 0, 'clickActive' => FALSE);
+    // Add individual tests to group.
+    $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]);
-      }
-      $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['testNames'][] = 'edit-' . $test_name;
+
+      // Store test title and description so that checkbox won't render them.
+      $title = $test['#title'];
+      $description = $test['#description'];
+
+      unset($test['#title']);
+      unset($test['#description']);
+
+      // Test name is used to determine what tests to run.
+      $test['#name'] = $test_name;
+
+      $row[] = drupal_render($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;
   }
+
+  // Remove already rendered tests.
   unset($form['tests']);
+
+  // Add js array of settings.
   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']);
+
+  $output = '<p>' . t('Select the tests you would like to run, and click <em>Run tests</em>.') . '</p>';
+  if (empty($rows)) {
+    $output .= '<strong>' . t('No tests to display.') . '</strong>';
   }
-  if (count($rows)) {
+  else {
     $output .= theme('table', $header, $rows, array('id' => 'simpletest-form-table'));
   }
-  // Output the rest of the form, excluded test groups which have been removed:
+
+  // Output the rest of the form, excluded tests which have been removed.
   $output .= drupal_render($form);
 
   return $output;
 }
 
-function theme_simpletest_result_summary($form, $text = NULL) {
-  return '<div class="simpletest-'. ($form['#ok'] ? 'pass' : 'fail') .'">' . _simpletest_format_summary_line($form) . '</div>';
+function simpletest_display_results() {
+  if (!isset($_SESSION['test_id'])) {
+    drupal_set_message(t('No results to display.'));
+    drupal_goto('admin/build/testing');
+  }
+
+  // Ensure that all classes are loaded before we create instances to get test information.
+  simpletest_get_all_tests();
+
+  drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css', 'module'); // TODO
+  drupal_add_js(drupal_get_path('module', 'simpletest') . '/simpletest.js', 'module');
+
+  $result = db_query("SELECT * FROM {simpletest} WHERE test_id = %d ORDER BY test_class, message_id", $_SESSION['test_id']);
+
+  $results = array();
+  $assertions = array();
+  $classes = array();
+  while ($assertion = db_fetch_object($result)) {
+    $class = $assertion->test_class;
+
+    // Get the summary results, only once per test class.
+    if (empty($results[$class])) {
+      // Instantiate class and get class information.
+      $test = new $class();
+      $info = $test->getInfo();
+
+      // Init summary results counters.
+      $results[$class] = array('#fail' => 0, '#pass' => 0, '#exception' => 0);
+
+      // Store class info for use when themeing.
+      foreach ($info as $key => $value) {
+        $results[$class]['#' . $key] = $value;
+      }
+    }
+
+    // Store test class for use in re-run form.
+    if (!in_array($assertion->test_class, $classes)) {
+      $classes[] = $assertion->test_class;
+    }
+
+    // Update summary counters.
+    $results[$class]['#' . $assertion->status]++;
+
+    // Store assertion in nested array after converting 'message_group' to
+    // 'group' to avoid database field conflict with reserved word.
+    $assertion = (array) $assertion;
+    $assertion['group'] = $assertion['message_group'];
+    unset($assertion['message_group']);
+
+    $assertions[$class][] = $assertion;
+
+    // Update the state of the results for class, if any non-passes then test
+    // results are not "ok" and should be expanded by default.
+    $results[$class]['#ok'] = !($results[$class]['#fail'] + $results[$class]['#exception']);
+  }
+
+  // Clear test results.
+  if (variable_get('simpletest_clear_results', TRUE)) {
+    db_query('DELETE FROM {simpletest} WHERE test_id = %d', $_SESSION['test_id']);
+    db_query('DELETE FROM {simpletest_test_id} WHERE test_id = %d', $_SESSION['test_id']);
+  }
+  unset($_SESSION['test_id']);
+
+  return '<p>' . t('The assertions listed in <strong>bold text</strong> are those made by the tests themselves, rather than the implicit framework tests.') . '</p>' .
+         drupal_get_form('simpletest_filter_form') .
+         theme('simpletest_results', $results, $assertions) .
+         drupal_get_form('simpletest_rerun_form', $classes);
 }
 
 function _simpletest_format_summary_line($summary) {
-  return t('@pass, @fail, @exception', array(
+  return t('@pass, @fail, and @exception', array(
     '@pass' => format_plural(isset($summary['#pass']) ? $summary['#pass'] : 0, '1 pass', '@count passes'),
     '@fail' => format_plural(isset($summary['#fail']) ? $summary['#fail'] : 0, '1 fail', '@count fails'),
     '@exception' => format_plural(isset($summary['#exception']) ? $summary['#exception'] : 0, '1 exception', '@count exceptions'),
@@ -281,17 +291,20 @@
  * Run selected tests.
  */
 function simpletest_test_form_submit($form, &$form_state) {
-  $output = '';
-  $batch_mode = !preg_match("/^simpletest\d+$/", $_SERVER['HTTP_USER_AGENT']);
-  $tests_list = array();
-  $run_all_tests = $form_state['values']['running_options'] == 'all_tests';
+  // Ensure that all classes are loaded before we create instances to get test information and run.
   simpletest_get_all_tests();
+
+  // Get list of tests.
+  $tests_list = array();
   foreach ($form_state['values'] as $class_name => $value) {
-    if (class_exists($class_name) && ($value === 1 || $run_all_tests)) {
+    if (class_exists($class_name) && $value === 1) {
       $tests_list[] = $class_name;
     }
   }
-  if (count($tests_list) > 0 ) {
+
+  // If there are selected tests then run them.
+  if (!empty($tests_list)) {
+    $batch_mode = !preg_match("/^simpletest\d+$/", $_SERVER['HTTP_USER_AGENT']);
     simpletest_run_tests($tests_list, 'drupal', $batch_mode);
   }
   else {
@@ -315,16 +328,20 @@
   $test_id = db_insert('simpletest_test_id')->useDefaults(array('test_id'))->execute();
 
   if ($batch_mode) {
+    $first_test = array_shift($test_list);
+    $first_instance = new $first_test();
+    array_unshift($test_list, $first_test);
+    $info = $first_instance->getInfo();
     $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.'),
+      'progress_message' => '',
       'css' => array(drupal_get_path('module', 'simpletest') .'/simpletest.css'),
-      'init_message' => t('SimpleTest is initializing...') . ' ' . format_plural(count($test_list), "one test case will run.", "@count test cases will run."),
+      'js' => array(drupal_get_path('module', 'simpletest') .'/simpletest.js'),
+      'init_message' => t('Processing test @num of @max - %test.', array('%test' => $info['name'], '@num' => '1', '@max' => count($test_list))),
     );
     batch_set($batch);
   }
@@ -356,6 +373,7 @@
     // Nth iteration: get the current values where we last stored them.
     $test_list = $context['sandbox']['tests'];
     $test_results = $context['sandbox']['test_results'];
+    $test_assertions = $context['sandbox']['test_assertions'];
   }
   $max = $context['sandbox']['max'];
 
@@ -371,19 +389,27 @@
   foreach ($test_results[$test_class] as $key => $value) {
     $test_results[$key] += $value;
   }
-  $test_results[$test_class]['#name'] = $info['name'];
-  $items = array();
-  foreach (element_children($test_results) as $class) {
-    $items[] = '<div class="simpletest-' . ($test_results[$class]['#fail'] + $test_results[$class]['#exception'] ? 'fail' : 'pass') . '">' . t('@name: @summary', array('@name' => $test_results[$class]['#name'], '@summary' => _simpletest_format_summary_line($test_results[$class]))) . '</div>';
+  $test_assertions[$test_class] = $test->_assertions;
+  foreach ($info as $key => $value) {
+    $test_results[$test_class]['#' . $key] = $value;
+  }
+  $test_results[$test_class]['#ok'] = !($test_results['#fail'] + $test_results['#exception']);
+  if (!empty($test_list)) {
+    $current_test_class = array_shift($test_list);
+    $instance = new $current_test_class();
+    $current_info = $instance->getInfo();
+    $context['message'] = t('Processing test @num of @max - %test.', array('%test' => $current_info['name'], '@num' => $max - count($test_list), '@max' => $max));
+    array_unshift($test_list, $current_test_class);
+  }
+  else {
+    $context['message'] = t('Finished processing all tests.');
   }
-  $message = t('Processed test @num of @max - %test.', array('%test' => $info['name'], '@num' => $max - $size, '@max' => $max));
-  $message .= theme('item_list', $items);
-  $context['message'] = $message;
-  // TODO: Do we want a summary of all?
+  $context['message'] .= theme_simpletest_results($test_results, $test_assertions);
 
   // Save working values for the next iteration.
   $context['sandbox']['tests'] = $test_list;
   $context['sandbox']['test_results'] = $test_results;
+  $context['sandbox']['test_assertions'] = $test_assertions;
   // The test_id is the only thing we need to save for the report page.
   $context['results']['test_id'] = $test_id;
 
@@ -392,13 +418,16 @@
 }
 
 function _simpletest_batch_finished($success, $results, $operations) {
-  $_SESSION['test_id'] = $results['test_id'];
+  if (isset($results['test_id'])) {
+    $_SESSION['test_id'] = $results['test_id'];
+  }
   if ($success) {
     drupal_set_message(t('The tests have finished running.'));
   }
   else {
     drupal_set_message(t('The tests did not successfully finish.'), 'error');
   }
+  drupal_goto('admin/build/testing/results');
 }
 
 /**
@@ -465,6 +494,196 @@
 }
 
 /**
+ * Theme simpletest results.
+ *
+ * @param $results
+ *   A nested array of simpletest results.
+ * @param $assertions
+ *   An array of assertions for each test.
+ * @return
+ *   A nested list of simpletest items.
+ */
+function theme_simpletest_results($results, $assertions) {
+  $items = array();
+  $group_data = array();
+  $totals = array(
+    '#fail' => 0,
+    '#pass' => 0,
+    '#exception' => 0,
+  );
+  foreach (element_children($results) as $class) {
+    $totals['#fail'] += $results[$class]['#fail'];
+    $totals['#pass'] += $results[$class]['#pass'];
+    $totals['#exception'] += $results[$class]['#exception'];
+    $item = array();
+    foreach ($results[$class] as $key => $value) {
+      if ($key == '#ok') {
+        $group_data[$results[$class]['#group']][$key] = isset($group_data[$results[$class]['#group']][$key]) ? $group_data[$results[$class]['#group']][$key] && $value : $value;
+      }
+      else {
+        $group_data[$results[$class]['#group']][$key] = isset($group_data[$results[$class]['#group']][$key]) ? $group_data[$results[$class]['#group']][$key] + $value : $value;
+      }
+    }
+    $item['class'] = 'simpletest-container ' . ($results[$class]['#fail'] ? 'simpletest-fail expanded' : ($results[$class]['#exception'] ? 'simpletest-exception expanded' : 'simpletest-pass collapsed'));
+    $item['data'] = '<span class="simpletest-overview">' . t('@name: @summary', array('@name' => $results[$class]['#name'], '@summary' => _simpletest_format_summary_line($results[$class]))) . '</span>';
+    $assertion_items = array();
+    foreach ($assertions[$class] as $assertion) {
+      // The idea of importancy rests upon the fact that the testing framework
+      // makes many assertions in its normal process of making GET and POST
+      // requests, as well as many other normal procedures. We will treat
+      // assertions actually initiated by the test itself as far more likely to
+      // be important, and thus highlight it in bold text.
+      $important = FALSE;
+      if ($assertion['group'] == 'Other') {
+        $assertion['group'] = $results[$class]['#name'];
+        $important = TRUE;
+      }
+
+      $css_class = simpletest_get_assertion_css_class($assertion, $important);
+      $assertion_items[] = array(
+        'data' => t('[@status] <span class="simpletest-overview">!message</span>',
+                    array('@status' => simpletest_get_status_message($assertion),
+                          '!message' => $assertion['message'])),
+        'class' => 'collapsed ' . $css_class,
+        'children' => array(
+          array(
+            'data' => t('Group: @group', array('@group' => $assertion['group'])),
+            'class' => 'leaf ' . $css_class,
+          ),
+          array(
+            'data' => t('File: @file', array('@file' => basename($assertion['file']))),
+            'class' => 'leaf ' . $css_class,
+          ),
+          array(
+            'data' => t('Function: @function', array('@function' => $assertion['function'])),
+            'class' => 'leaf ' . $css_class,
+          ),
+          array(
+            'data' => t('Line: @line', array('@line' => $assertion['line'])),
+            'class' => 'leaf ' . $css_class,
+          ),
+        ),
+      );
+    }
+    $item['children'] = $assertion_items;
+    $items[$results[$class]['#group']]['children'][] = $item;
+    $items[$results[$class]['#group']]['data'] = '<span class="simpletest-overview">' . t('@name: @summary', array('@name' => $results[$class]['#group'], '@summary' => _simpletest_format_summary_line($group_data[$results[$class]['#group']]))) . '</span>';
+    $items[$results[$class]['#group']]['class'] = 'simpletest-container ' . ($group_data[$results[$class]['#group']]['#fail'] ? 'simpletest-fail expanded' : ($group_data[$results[$class]['#group']]['#exception'] ? 'simpletest-exception expanded' : 'simpletest-pass collapsed'));
+  }
+  $output = '';
+  $output .= '<p class="' . ($totals['#fail'] ? 'simpletest-fail' : ($totals['#exception'] ? 'simpletest-exception' : 'simpletest-pass')) . '">';
+  $output .= t('Overall results: @summary', array('@summary' => _simpletest_format_summary_line($totals)));
+  $output .= '</p>';
+  $output .= theme('item_list', $items, NULL, 'ul', array('class' => 'simpletest-results'));
+  return $output;
+}
+
+/**
+ * Create a filter form used on the results page.
+ */
+function simpletest_filter_form() {
+  $form = array();
+  $form['outcome'] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('Filter by outcome'),
+    '#options' => array(),
+    '#attributes' => array('class' => 'container-inline'),
+    '#default_value' => array(),
+  );
+  $categories = array('pass' => t('Passes'), 'fail' => t('Fails'), 'exception' => t('Exceptions'));
+  foreach ($categories as $outcome => $readable) {
+    $form['outcome'][$outcome] = array(
+      '#type' => 'checkbox',
+      '#title' => $readable,
+      '#attributes' => array('class' => 'simpletest-filter'),
+      '#default_value' => TRUE,
+    );
+  }
+  return $form;
+}
+
+/**
+ * Create a form containing the last run test classes so that they can bee run
+ * again.
+ */
+function simpletest_rerun_form($form_state, array $test_classes) {
+  $form = array();
+  $form['test_classes'] = array(
+    '#type' => 'hidden',
+    '#default_value' => implode(',', $test_classes),
+  );
+  $form['actions'] = array(
+    '#prefix' => '<div class="container-inline">',
+    '#suffix' => '</div>',
+  );
+  $form['actions']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Re-run tests'),
+  );
+  $form['actions']['cancel'] = array(
+    '#markup' => l(t('Return to testing overview page'), 'admin/build/testing'),
+  );
+  return $form;
+}
+
+/**
+ * Re-run selected tests.
+ */
+function simpletest_rerun_form_submit($form, &$form_state) {
+  // Simulate the checkboxes on the test select form by setting each of the
+  // test classes as a value in the form state.
+  foreach (explode(',', $form_state['values']['test_classes']) as $test_class) {
+    $form_state['values'][$test_class] = 1;
+  }
+
+  // Use the existing submit handler to run the tests.
+  simpletest_test_form_submit($form, $form_state);
+}
+
+/**
+ * Get the appropriate css class based on the assertions status and importance.
+ *
+ * @param $assertion Assertion item aray.
+ * @param $important Importance of assertion.
+ * @return CSS class.
+ */
+function simpletest_get_assertion_css_class($assertion, $important) {
+  switch ($assertion['status']) {
+    case 'pass':
+      $css_class = 'simpletest-pass';
+      break;
+    case 'fail':
+      $css_class = 'simpletest-fail';
+      break;
+    case 'exception':
+      $css_class = 'simpletest-exception';
+      break;
+  }
+  if ($important) {
+    $css_class .= ' simpletest-important';
+  }
+  return $css_class;
+}
+
+/**
+ * Get the status message text
+ *
+ * @param $assertion Assertion item aray.
+ * @return Status message text.
+ */
+function simpletest_get_status_message($assertion) {
+  switch ($assertion['status']) {
+    case 'pass':
+      return t('PASS');
+    case 'fail':
+      return t('FAIL');
+    case 'exception':
+      return t('EXCEPTION');
+  }
+  return t('UNKNOWN');
+}
+
+/**
  * Remove all temporary database tables and directories.
  */
 function simpletest_clean_environment() {
Index: modules/simpletest/simpletest.js
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.js,v
retrieving revision 1.2
diff -u -r1.2 simpletest.js
--- modules/simpletest/simpletest.js	23 Apr 2008 18:50:49 -0000	1.2
+++ modules/simpletest/simpletest.js	6 Sep 2008 21:24:19 -0000
@@ -1,12 +1,15 @@
 // $Id: simpletest.js,v 1.2 2008/04/23 18:50:49 dries Exp $
 
+/**
+ * Add the cool table collapsing on the testing overview page.
+ */
 Drupal.behaviors.simpleTestMenuCollapse = function() {
   // Adds expand-collapse functionality.
   $('div.simpletest-image').click(function() {
     // Toggle all of the trs.
     if (!Drupal.settings.simpleTest[$(this).attr('id')].clickActive) {
       Drupal.settings.simpleTest[$(this).attr('id')].clickActive = true;
-      var trs = $(this).parents('tbody').children().filter('.'+ Drupal.settings.simpleTest[$(this).attr('id')].testClass), trs_formatted = [], direction = Drupal.settings.simpleTest[$(this).attr('id')].imageDirection, self = $(this);
+      var trs = $(this).parents('tbody').children().filter('.' + Drupal.settings.simpleTest[$(this).attr('id')].testClass), trs_formatted = [], direction = Drupal.settings.simpleTest[$(this).attr('id')].imageDirection, self = $(this);
       for (var i = 0; i < trs.length; i++) {
         trs_formatted.push(trs[i]);
       }
@@ -21,12 +24,21 @@
           Drupal.settings.simpleTest[self.attr('id')].clickActive = false;
         }
       }
-      toggleTrs(trs_formatted, (direction? 'pop' : 'shift'), (direction? 'fadeOut' : 'fadeIn'));
+      toggleTrs(trs_formatted, (direction ? 'pop' : 'shift'), (direction ? 'fadeOut' : 'fadeIn'));
       Drupal.settings.simpleTest[$(this).attr('id')].imageDirection = !direction;
       $(this).html(Drupal.settings.simpleTest.images[(direction? 0 : 1)]);
     }
   });
+  $('label.simpletest-group-label').click(function() {
+    $(this).siblings('div.simpletest-image').click();
+    return false;
+  });
 }
+
+/**
+ * Select/deselect all the inner checkboxes when the outer checkboxes are
+ * selected/deselected.
+ */
 Drupal.behaviors.simpleTestSelectAll = function() {
   $('td.simpletest-select-all').each(function() {
     var checkboxes = Drupal.settings.simpleTest['simpletest-test-group-'+ $(this).attr('id')].testNames, totalCheckboxes = 0,
@@ -35,11 +47,11 @@
       for (var i = 0; i < checkboxes.length; i++) {
         $('#'+ checkboxes[i]).attr('checked', checked);
       }
-      self.data('simpletest-checked-tests', (checked? checkboxes.length : 0));
+      self.data('simpletest-checked-tests', (checked ? checkboxes.length : 0));
     }).data('simpletest-checked-tests', 0);
     var self = $(this);
     for (var i = 0; i < checkboxes.length; i++) {
-      if ($('#'+ checkboxes[i]).change(function() {
+      if ($('#' + checkboxes[i]).change(function() {
         if (checkbox.attr('checked') == 'checked') {
           checkbox.attr('checked', '');
         }
@@ -49,7 +61,7 @@
           checkbox.attr('checked', 'checked');
         }
         else {
-          checkbox.attr('checked', '');
+          checkbox.removeAttr('checked');
         }
       }).attr('checked') == 'checked') {
         totalCheckboxes++;
@@ -60,4 +72,69 @@
     }
     $(this).append(checkbox);
   });
+  $('input.simpletest-master:checkbox').click(function() {
+    if ($(this).is(':checked')) {
+      $('#simpletest-form-table input:checkbox,input.simpletest-master:checkbox').attr('checked', 'checked');
+    }
+    else {
+      $('#simpletest-form-table input:checkbox,input.simpletest-master:checkbox').removeAttr('checked');
+    }
+    $('#simpletest-form-table input:checkbox:not(.simpletest-master)').change(function() {
+      if ($('#simpletest-form-table input:checkbox:not(:checked):not(.simpletest-master)').length) {
+        $('input.simpletest-master:checkbox').removeAttr('checked');
+      }
+      else if ($('#simpletest-form-table input:checkbox:not(.simpletest-master)').length == $('#simpletest-form-table input:checkbox:checked:not(.simpletest-master)').length) {
+        $('input.simpletest-master:checkbox').attr('checked', 'checked');
+      }
+    });
+  });
+};
+
+/**
+ * Filter the results based on their outcome.
+ */
+Drupal.behaviors.simpleTestFilterForm = function() {
+  var updateResults = function(type, checked) {
+    $('li.simpletest-' + type + ':not(.simpletest-container)')[checked ? 'show' : 'hide']();
+    $('li.simpletest-container:not(:has(li.simpletest-container))').each(function() {
+      var children = $(this).find('li:visible:not(.simpletest-container)').is('.simpletest-pass,.simpletest-fail,.simpletest-exception');
+      $(this)[children ? 'show' : 'hide']();
+    });
+  }
+  $('#edit-outcome-pass').change(function() {
+    updateResults('pass', $(this).is(':checked'));
+  });
+  $('#edit-outcome-fail').change(function() {
+    updateResults('fail', $(this).is(':checked'));
+  });
+  $('#edit-outcome-exception').change(function() {
+    updateResults('exception', $(this).is(':checked'));
+  });
+  updateResults('pass', $('#edit-outcome-pass').is(':checked'));
+  updateResults('fail', $('#edit-outcome-fail').is(':checked'));
+  updateResults('exception', $('#edit-outcome-exception').is(':checked'));
+};
+
+/**
+ * The results should collapse when you click on them, of course.
+ */
+Drupal.behaviors.simpleTestResultsCollapse = function() {
+  $('li > span.simpletest-overview').click(function() {
+    $(this).siblings().children('ul').toggle().toggleClass('simpletest-hidden').parent().parent().toggleClass('collapsed').toggleClass('expanded');
+  });
+  $('li.collapsed > span.simpletest-overview').each(function() {
+    $(this).siblings().children('ul').hide().addClass('simpletest-hidden');
+  });
+};
+
+/**
+ * Batch update function - re-attaches javascript behaviors to the new elements
+ * now injected into the HTML.
+ */
+Drupal.batchUpdate.simpleTestResultsCollapse = function() {
+  $('li > span.simpletest-overview').click(function() {
+    $(this).siblings().children('ul').toggle().toggleClass('simpletest-hidden').parent().parent().toggleClass('collapsed').toggleClass('expanded');
+  }).each(function() {
+    $(this).siblings().children('ul').hide().addClass('simpletest-hidden').parent().parent().removeClass('expanded').addClass('collapsed');
+  });
 };
\ No newline at end of file
Index: includes/batch.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/batch.inc,v
retrieving revision 1.20
diff -u -r1.20 batch.inc
--- includes/batch.inc	24 Jun 2008 21:51:02 -0000	1.20
+++ includes/batch.inc	6 Sep 2008 21:24:17 -0000
@@ -22,11 +22,14 @@
   // Register database update for end of processing.
   register_shutdown_function('_batch_shutdown');
 
-  // Add batch-specific css.
+  // Add batch-specific css and javascript.
   foreach ($batch['sets'] as $batch_set) {
     foreach ($batch_set['css'] as $css) {
       drupal_add_css($css);
     }
+    foreach ($batch_set['js'] as $js) {
+      drupal_add_js($js);
+    }
   }
 
   $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : '';
@@ -251,8 +254,11 @@
       '@total'      => $total,
       '@current'    => floor($current),
       '@percentage' => $percentage,
-      );
-    $message = strtr($progress_message, $values) . '<br/>';
+    );
+    $message = strtr($progress_message, $values);
+    if (!empty($message)) {
+      $message .= '<br/>';
+    }
     $message .= $task_message ? $task_message : '&nbsp';
 
     return array($percentage, $message);
Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.282
diff -u -r1.282 form.inc
--- includes/form.inc	6 Sep 2008 08:36:19 -0000	1.282
+++ includes/form.inc	6 Sep 2008 21:24:18 -0000
@@ -2421,6 +2421,7 @@
       'progress_message' => $t('Remaining @remaining of @total.'),
       'error_message' => $t('An error has occurred.'),
       'css' => array(),
+      'js' => array(),
     );
     $batch_set = $init + $batch_definition + $defaults;
 
Index: misc/drupal.js
===================================================================
RCS file: /cvs/drupal/drupal/misc/drupal.js,v
retrieving revision 1.45
diff -u -r1.45 drupal.js
--- misc/drupal.js	25 Jun 2008 07:45:03 -0000	1.45
+++ misc/drupal.js	6 Sep 2008 21:24:19 -0000
@@ -1,6 +1,6 @@
 // $Id: drupal.js,v 1.45 2008/06/25 07:45:03 dries Exp $
 
-var Drupal = Drupal || { 'settings': {}, 'behaviors': {}, 'themes': {}, 'locale': {} };
+var Drupal = Drupal || { 'settings': {}, 'behaviors': {}, 'themes': {}, 'locale': {}, 'batchUpdate': {} };
 
 /**
  * Set the variable that indicates if JavaScript behaviors should be applied
Index: misc/batch.js
===================================================================
RCS file: /cvs/drupal/drupal/misc/batch.js,v
retrieving revision 1.4
diff -u -r1.4 batch.js
--- misc/batch.js	21 Oct 2007 18:59:01 -0000	1.4
+++ misc/batch.js	6 Sep 2008 21:24:18 -0000
@@ -16,6 +16,9 @@
 
     // Success: redirect to the summary.
     var updateCallback = function (progress, status, pb) {
+      for (behavior in Drupal.batchUpdate) {
+        Drupal.batchUpdate[behavior](progress, status, pb);
+      }
       if (progress == 100) {
         pb.stopMonitoring();
         window.location = uri+'&op=finished';
Index: modules/contact/contact.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/contact/contact.test,v
retrieving revision 1.8
diff -u -r1.8 contact.test
--- modules/contact/contact.test	2 Aug 2008 05:16:47 -0000	1.8
+++ modules/contact/contact.test	6 Sep 2008 21:24:19 -0000
@@ -219,7 +219,7 @@
     // Get role id (rid) for specified role.
     $rid = db_result(db_query("SELECT rid FROM {role} WHERE name = '%s'", array($role)));
     if ($rid === FALSE) {
-      $this->fail(t(' [permission] Role "' . $role . '" not found.'));
+      $this->fail(t('Role "@role" not found.', array('@role' => $role)), t('Permission'));
     }
 
     // Create edit array from permission.
@@ -229,7 +229,7 @@
     }
 
     $this->drupalPost('admin/user/permissions', $edit, t('Save permissions'));
-    $this->assertText(t('The changes have been saved.'), t(' [permission] Saved changes.'));
+    $this->assertText(t('The changes have been saved.'), t('Saved changes.'), t('Permission'));
   }
 }
 
