Index: simpletest.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/simpletest/simpletest.module,v
retrieving revision 1.33.2.4.2.17
diff -u -r1.33.2.4.2.17 simpletest.module
--- simpletest.module	23 Apr 2009 05:39:52 -0000	1.33.2.4.2.17
+++ simpletest.module	5 Sep 2009 11:05:02 -0000
@@ -1,6 +1,7 @@
 <?php
-// $Id: simpletest.module,v 1.33.2.4.2.17 2009/04/23 05:39:52 boombatower Exp $
-// Core: Id: simpletest.module,v 1.39 2009/03/31 01:49:53 webchick Exp
+// $Id$
+// Core: Id: simpletest.module,v 1.71 2009/08/24 00:14:21 webchick Exp
+
 /**
  * @file
  * Backport of Drupal 7 simpletest.module with modifications, see BACKPORT.txt.
@@ -14,11 +15,11 @@
 function simpletest_help($path, $arg) {
   switch ($path) {
     case 'admin/help#simpletest':
-      $output  = '<p>' . t('The SimpleTest module is a framework for running automated unit tests in Drupal. It can be used to verify a working state of Drupal before and after any code changes, or as a means for developers to write and execute tests for their modules.') .'</p>';
-      $output .= '<p>' . t('Visit <a href="@admin-simpletest">Administer >> Site building >> SimpleTest</a> to display a list of available tests. For comprehensive testing, select <em>all</em> tests, or individually select tests for more targeted testing. Note that it might take several minutes for all tests to complete.)', array('@admin-simpletest' => url('admin/build/testing'))) .'</p>';
-      $output .= '<p>' . t('After the tests have run, a message will be displayed next to each test group indicating whether tests within it passed, failed, or had exceptions. A pass means that a test returned the expected results, while fail means that it did not. An exception normally indicates an error outside of the test, such as a PHP warning or notice. If there were fails or exceptions, the results are expanded, and the tests that had issues will be indicated in red or pink rows. Use these results to refine your code and tests until all tests return a pass.') .'</p>';
-      $output .= '<p>' . t('For more information on creating and modifying your own tests, see the <a href="@simpletest-api">SimpleTest API Documentation</a> in the Drupal handbook.', array('@simpletest-api' => 'http://drupal.org/simpletest')) .'</p>';
-      $output .= '<p>' . t('For more information, see the online handbook entry for <a href="@simpletest">SimpleTest module</a>.', array('@simpletest' => 'http://drupal.org/handbook/modules/simpletest')) .'</p>';
+      $output  = '<p>' . t('The SimpleTest module is a framework for running automated unit tests in Drupal. It can be used to verify a working state of Drupal before and after any code changes, or as a means for developers to write and execute tests for their modules.') . '</p>';
+      $output .= '<p>' . t('Visit <a href="@admin-simpletest">Administer >> Structure >> SimpleTest</a> to display a list of available tests. For comprehensive testing, select <em>all</em> tests, or individually select tests for more targeted testing. Note that it might take several minutes for all tests to complete.)', array('@admin-simpletest' => url('admin/config/development/testing'))) . '</p>';
+      $output .= '<p>' . t('After the tests have run, a message will be displayed next to each test group indicating whether tests within it passed, failed, or had exceptions. A pass means that a test returned the expected results, while fail means that it did not. An exception normally indicates an error outside of the test, such as a PHP warning or notice. If there were fails or exceptions, the results are expanded, and the tests that had issues will be indicated in red or pink rows. Use these results to refine your code and tests until all tests return a pass.') . '</p>';
+      $output .= '<p>' . t('For more information on creating and modifying your own tests, see the <a href="@simpletest-api">SimpleTest API Documentation</a> in the Drupal handbook.', array('@simpletest-api' => 'http://drupal.org/simpletest')) . '</p>';
+      $output .= '<p>' . t('For more information, see the online handbook entry for <a href="@simpletest">SimpleTest module</a>.', array('@simpletest' => 'http://drupal.org/handbook/modules/simpletest')) . '</p>';
       return $output;
   }
 }
@@ -27,12 +28,38 @@
  * Implementation of hook_menu().
  */
 function simpletest_menu() {
+//  $items['admin/config/development/testing'] = array(
   $items['admin/build/testing'] = array(
     'title' => 'Testing',
     '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'),
+    'file' => 'simpletest.pages.inc',
+  );
+//  $items['admin/config/development/testing/list'] = array(
+  $items['admin/build/testing/list'] = array(
+    'title' => 'List',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+  );
+//  $items['admin/config/development/testing/settings'] = array(
+  $items['admin/build/testing/settings'] = array(
+    'title' => 'Settings',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('simpletest_settings_form'),
+    'access arguments' => array('administer unit tests'),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'simpletest.pages.inc',
+  );
+//  $items['admin/config/development/testing/results/%'] = array(
+  $items['admin/build/testing/results/%'] = array(
+    'title' => 'Test result',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('simpletest_result_form', 5),
+    'description' => 'View result of tests.',
+    'access arguments' => array('administer unit tests'),
+    'type' => MENU_CALLBACK,
+    'file' => 'simpletest.pages.inc',
   );
   return $items;
 }
@@ -56,294 +83,52 @@
 function simpletest_theme() {
   return array(
     'simpletest_test_table' => array(
-      'arguments' => array('table' => NULL)
+      'arguments' => array('table' => NULL),
+      'file' => 'simpletest.pages.inc',
     ),
     'simpletest_result_summary' => array(
-      'arguments' => array('form' => NULL)
+      'arguments' => array('form' => NULL),
+      'file' => 'simpletest.pages.inc',
     ),
   );
 }
 
-/**
- * Menu callback for both running tests and listing possible tests
- */
-function simpletest_test_form() {
-  $form = array();
-
-  // List out all tests in groups for selection.
-  $uncategorized_tests = simpletest_get_all_tests();
-  $tests = simpletest_categorize_tests($uncategorized_tests);
-  $selected_tests = array();
-
-  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 = call_user_func(array($class, 'getInfo'));
-      $group = $info['group'];
-      $selected_tests[$group][$class] = TRUE;
-      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->function,
-            $map[$status],
-          ),
-          'class' => "simpletest-$status",
-        );
-      }
-      unset($element);
-    }
-
-    // Clear test results.
-    simpletest_clean_results_table($_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 = call_user_func(array($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['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;
-  }
-  $form['tests'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Tests'),
-    '#description' => t('Select the tests you would like to run, and click Run tests.'),
-  );
-  $form['tests']['table'] = array(
-    '#theme' => 'simpletest_test_table'
-    );
-  foreach ($tests as $group_name => $test_group) {
-    $form['tests']['table'][$group_name] = array(
-      '#collapsed' => TRUE,
-    );
-    foreach ($test_group as $class => $info) {
-      $is_selected = isset($selected_tests[$group_name][$class]);
-      $form['tests']['table'][$group_name][$class] = array(
-        '#type' => 'checkbox',
-        '#title' => $info['name'],
-        '#default_value' => $is_selected,
-        '#description' => $info['description'],
-      );
-      if ($is_selected) {
-        $form['tests']['table'][$group_name]['#collapsed'] = FALSE;
-      }
-    }
-  }
-
-  // Action buttons.
-  $form['tests']['op'] = array(
-    '#type' => 'submit',
-    '#value' => t('Run tests'),
-  );
-  $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. This is intended for developers when creating tests.'),
-  );
-  $form['reset']['op'] = array(
-    '#type' => 'submit',
-    '#value' => t('Clean environment'),
-    '#submit' => array('simpletest_clean_environment'),
-  );
-
-  return $form;
-}
-
-function theme_simpletest_test_table($table) {
-  drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css');
-  drupal_add_js(drupal_get_path('module', 'simpletest') . '/simpletest.js');
-
-  // Create header for test selection table.
-  $header = array(
-    theme('table_select_header_cell'),
-    array('data' => t('Test'), 'class' => 'simpletest_test'),
-    array('data' => t('Description'), 'class' => 'simpletest_description'),
-  );
-
-  // Define the images used to expand/collapse the test groups.
-  $js = array(
-    'images' => array(
-      theme('image', 'misc/menu-collapsed.png', 'Expand', 'Expand'),
-      theme('image', 'misc/menu-expanded.png', 'Collapsed', 'Collapsed'),
-    ),
-  );
-
-  // Go through each test group and create a row.
-  $rows = array();
-  foreach (element_children($table) as $key) {
-    $element = &$table[$key];
-    $row = array();
-
-    // Make the class name safe for output on the page by replacing all
-    // non-word/decimal characters with a dash (-).
-    $test_class = strtolower(trim(preg_replace("/[^\w\d]/", "-", $key)));
-
-    // Select the right "expand"/"collapse" image, depending on whether the
-    // category is expanded (at least one test selected) or not.
-    $collapsed = !empty($element['#collapsed']);
-    $image_index = $collapsed ? 0 : 1;
-
-    // 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 . '"></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');
-
-      // Add individual tests to group.
-      $current_js = array(
-        'testClass' => $test_class . '-test',
-        'testNames' => array(),
-        'imageDirection' => $image_index,
-        'clickActive' => FALSE,
-      );
-      foreach (element_children($element) as $test_name) {
-        $test = $element[$test_name];
-        $row = array();
-
-        $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, 'class' => $test_class . '-test' . ($collapsed ? ' js-hide' : ''));
-      }
-      $js['simpletest-test-group-'. $test_class] = $current_js;
-      unset($table[$key]);
-  }
-
-  // Add js array of settings.
-  drupal_add_js(array('simpleTest' => $js), 'setting');
-
-  if (empty($rows)) {
-    return '<strong>' . t('No tests to display.') . '</strong>';
-  }
-  else {
-    return theme('table', $header, $rows, array('id' => 'simpletest-form-table'));
-  }
-}
-
-/**
- * Implementation of hook_js_alter().
- */
-function simpletest_js_alter(&$javascript) {
-  // Since SimpleTest is a special use case for the table select, stick the
-  // SimpleTest JavaScript above the table select.
-  $simpletest = drupal_get_path('module', 'simpletest') . '/simpletest.js';
-  if (array_key_exists($simpletest, $javascript) && array_key_exists('misc/tableselect.js', $javascript)) {
-    $javascript[$simpletest]['weight'] = $javascript['misc/tableselect.js']['weight'] - 1;
-  }
-}
+///**
+// * Implementation of hook_stream_wrappers().
+// */
+//function simpletest_test_stream_wrappers() {
+//  return array(
+//    'simpletest' => array(
+//      'name' => t('Simpletest files'),
+//      'class' => 'DrupalSimpleTestStreamWrapper',
+//      'description' => t('Stream Wrapper for Simpletest files.'),
+//    ),
+//  );
+//}
 
-function theme_simpletest_result_summary($form, $text = NULL) {
-  return '<div class="simpletest-'. ($form['#ok'] ? 'pass' : 'fail') .'">' . _simpletest_format_summary_line($form) . '</div>';
-}
+///**
+// * Implement hook_js_alter().
+// */
+//function simpletest_js_alter(&$javascript) {
+//  // Since SimpleTest is a special use case for the table select, stick the
+//  // SimpleTest JavaScript above the table select.
+//  $simpletest = drupal_get_path('module', 'simpletest') . '/simpletest.js';
+//  if (array_key_exists($simpletest, $javascript) && array_key_exists('misc/tableselect.js', $javascript)) {
+//    $javascript[$simpletest]['weight'] = $javascript['misc/tableselect.js']['weight'] - 1;
+//  }
+//}
 
 function _simpletest_format_summary_line($summary) {
-  return t('@pass, @fail, and @exception', array(
+  $args = 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'),
-  ));
-}
-
-/**
- * Run selected tests.
- */
-function simpletest_test_form_submit($form, &$form_state) {
-  // 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) {
-      $tests_list[] = $class_name;
-    }
-  }
-  if (count($tests_list) > 0 ) {
-    simpletest_run_tests($tests_list, 'drupal');
-  }
-  else {
-    drupal_set_message(t('No test(s) selected.'), 'error');
+  );
+  if (!$summary['#debug']) {
+    return t('@pass, @fail, and @exception', $args);
   }
+  $args['@debug'] = format_plural(isset($summary['#debug']) ? $summary['#debug'] : 0, '1 debug message', '@count debug messages');
+  return t('@pass, @fail, @exception, and @debug', $args);
 }
 
 /**
@@ -357,10 +142,16 @@
  */
 function simpletest_run_tests($test_list, $reporter = 'drupal') {
   cache_clear_all();
-//  $test_id = db_insert('simpletest_test_id')->useDefaults(array('test_id'))->execute();
-  db_query('INSERT INTO {simpletest_test_id} VALUES (default)');
+//  $test_id = db_insert('simpletest_test_id')
+//    ->useDefaults(array('test_id'))
+//    ->execute();
+  db_query('INSERT INTO {simpletest_test_id} (test_id) VALUES (default)');
   $test_id = db_last_insert_id('simpletest_test_id', 'test_id');
 
+  // Clear out the previous verbose files.
+//  file_unmanaged_delete_recursive(file_directory_path() . '/simpletest/verbose');
+  simpletest_clean_temporary_directory(file_directory_path() . '/simpletest/verbose');
+
   // Get the info for the first test being run.
   $first_test = array_shift($test_list);
   $first_instance = new $first_test();
@@ -373,34 +164,34 @@
       array('_simpletest_batch_operation', array($test_list, $test_id)),
     ),
     'finished' => '_simpletest_batch_finished',
-    'redirect' => 'admin/build/testing',
     'progress_message' => '',
     'css' => array(drupal_get_path('module', 'simpletest') . '/simpletest.css'),
     'init_message' => t('Processing test @num of @max - %test.', array('%test' => $info['name'], '@num' => '1', '@max' => count($test_list))),
   );
   batch_set($batch);
+
+  module_invoke_all('test_group_started');
+
   // Normally, the forms portion of the batch API takes care of calling
   // batch_process(), but in the process it saves the whole $form into the
   // database (which is huge for the test selection form).
   // By calling batch_process() directly, we skip that behavior and ensure
   // that we don't exceed the size of data that can be sent to the database
   // (max_allowed_packet on MySQL).
-  batch_process();
+//  batch_process('admin/config/development/testing/results/' . $test_id);
+  batch_process('admin/build/testing/results/' . $test_id);
 }
 
 /**
  * Batch operation callback.
  */
 function _simpletest_batch_operation($test_list_init, $test_id, &$context) {
-  // Ensure that all classes are loaded before we unserialize some instances.
-  simpletest_get_all_tests();
-
   // Get working values.
   if (!isset($context['sandbox']['max'])) {
     // First iteration: initialize working values.
     $test_list = $test_list_init;
     $context['sandbox']['max'] = count($test_list);
-    $test_results = array('#pass' => 0, '#fail' => 0, '#exception' => 0);
+    $test_results = array('#pass' => 0, '#fail' => 0, '#exception' => 0, '#debug' => 0);
   }
   else {
     // Nth iteration: get the current values where we last stored them.
@@ -416,6 +207,8 @@
   $size = count($test_list);
   $info = $test->getInfo();
 
+  module_invoke_all('test_finished', $test->results);
+
   // Gather results and compose the report.
   $test_results[$test_class] = $test->results;
   foreach ($test_results[$test_class] as $key => $value) {
@@ -442,97 +235,213 @@
 
 //function _simpletest_batch_finished($success, $results, $operations, $elapsed) {
 function _simpletest_batch_finished($success, $results, $operations) {
-  if (isset($results['test_id'])) {
-//    drupal_set_session('test_id', $results['test_id']);
-    $_SESSION['test_id'] = $results['test_id'];
-  }
   if ($success) {
-//    drupal_set_message(t('The tests finished in @elapsed.', array('@elapsed' => $elapsed)));
-    drupal_set_message(t('The tests finished.'));
+//    drupal_set_message(t('The test run finished in @elapsed.', array('@elapsed' => $elapsed)));
+    drupal_set_message(t('The test run finished.'));
   }
   else {
-    drupal_set_message(t('The tests did not successfully finish.'), 'error');
+    // Use the test_id passed as a parameter to _simpletest_batch_operation().
+    $test_id = $operations[0][1][1];
+
+    // Retrieve the last database prefix used for testing and the last test
+    // class that was run from. Use the information to read the lgo file
+    // in case any fatal errors caused the test to crash.
+    list($last_prefix, $last_test_class) = simpletest_last_test_get($test_id);
+    simpletest_log_read($test_id, $last_prefix, $last_test_class);
+
+    drupal_set_message(t('The test run did not successfully finish.'), 'error');
+    drupal_set_message(t('Please use the <em>Clean environment</em> button to clean-up temporary files and tables.'), 'warning');
   }
+  module_invoke_all('test_group_finished');
 }
 
-/**
- * Get a list of all of the tests.
+/*
+ * Get information about the last test that ran given a test ID.
  *
+ * @param $test_id
+ *   The test ID to get the last test from.
  * @return
- *   An array of tests, with the class name as the keys and the instantiated
- *   versions of the classes as the values.
+ *   Array containing the last database prefix used and the last test class
+ *   that ran.
  */
-function simpletest_get_all_tests() {
-  static $formatted_classes;
-  if (!isset($formatted_classes)) {
-//    require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'simpletest') . '/drupal_web_test_case.php';
-    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;
-      }
+function simpletest_last_test_get($test_id) {
+//  $last_prefix = db_result(db_query_range('SELECT last_prefix FROM {simpletest_test_id} WHERE test_id = :test_id', array(':test_id' => $test_id), 0, 1));
+  $last_prefix = db_result(db_query_range('SELECT last_prefix FROM {simpletest_test_id} WHERE test_id = ' . $test_id, 0, 1));
+//  $last_test_class = db_result(db_query_range('SELECT test_class FROM {simpletest} WHERE test_id = :test_id ORDER BY message_id DESC', array(':test_id' => $test_id), 0, 1));
+  $last_test_class = db_result(db_query_range('SELECT test_class FROM {simpletest} WHERE test_id = ' . $test_id . ' ORDER BY message_id DESC', 0, 1));
+  return array($last_prefix, $last_test_class);
+}
 
-      $tests_directory = $module_path . '/tests';
-      if (is_dir($tests_directory)) {
-//        foreach (file_scan_directory($tests_directory, '/\.test$/') as $file) {
-        foreach (file_scan_directory($tests_directory, '\.test$') as $file) {
-          // Drupal 6: Ignore 1.x style tests.
-          if (!preg_match('/class\s+.*?\s+extends\s+DrupalTestCase/', file_get_contents($file->filename))) {
-            // Ignore tests using the old format.
-            $files[] = $file->filename;
-          }
-        }
+/**
+ * Read the error log and report any errors as assertion failures.
+ *
+ * The errors in the log should only be fatal errors since any other errors
+ * will have been recorded by the error handler.
+ *
+ * @param $test_id
+ *   The test ID to which the log relates.
+ * @param $prefix
+ *   The database prefix to which the log relates.
+ * @param $test_class
+ *   The test class to which the log relates.
+ * @param $during_test
+ *   Indicates that the current file directory path is a temporary file
+ *   file directory used during testing.
+ * @return
+ *   Found any entries in log.
+ */
+function simpletest_log_read($test_id, $prefix, $test_class, $during_test = FALSE) {
+//  $log = 'public://' . ($during_test ? '' : '/simpletest/' . substr($prefix, 10)) . '/error.log';
+  $log = file_directory_path() . ($during_test ? '' : '/simpletest/' . substr($prefix, 10)) . '/error.log';
+  $found = FALSE;
+  if (file_exists($log)) {
+    foreach (file($log) as $line) {
+      if (preg_match('/\[.*?\] (.*?): (.*?) in (.*) on line (\d+)/', $line, $match)) {
+        // Parse PHP fatal errors for example: PHP Fatal error: Call to
+        // undefined function break_me() in /path/to/file.php on line 17
+        $caller = array(
+          'line' => $match[4],
+          'file' => $match[3],
+        );
+        DrupalTestCase::insertAssert($test_id, $test_class, FALSE, $match[2], $match[1], $caller);
       }
-    }
-
-    $existing_classes = get_declared_classes();
-    foreach ($files as $file) {
-//      include_once DRUPAL_ROOT . '/' . $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')) {
-        unset($classes[$key]);
+      else {
+        // Unkown format, place the entire message in the log.
+        DrupalTestCase::insertAssert($test_id, $test_class, FALSE, $line, 'Fatal error');
       }
+      $found = TRUE;
     }
   }
-  if (count($classes) == 0) {
-    drupal_set_message('No test cases found.', 'error');
-    return FALSE;
-  }
-  return $classes;
+  return $found;
 }
 
 /**
- * Categorize the tests into groups.
+ * Get a list of all of the tests provided by the system.
  *
- * @param $tests
- *   A list of tests from simpletest_get_all_tests.
- * @see simpletest_get_all_tests.
- */
-function simpletest_categorize_tests($tests) {
-  $groups = array();
-  foreach ($tests as $test) {
-    $info = call_user_func(array($test, 'getInfo'));
-    $groups[$info['group']][$test] = $info;
+ * The list of test classes is loaded from the registry where it looks for
+ * files ending in ".test". Once loaded the test list is cached and stored in
+ * a static variable. In order to list tests provided by disabled modules
+ * hook_registry_files_alter() is used to forcefully add them to the registry.
+ *
+ * @return
+ *   An array of tests keyed with the groups specified in each of the tests
+ *   getInfo() method and then keyed by the test class. An example of the array
+ *   structure is provided below.
+ *
+ *   @code
+ *     $groups['Blog'] => array(
+ *       'BlogTestCase' => array(
+ *         'name' => 'Blog functionality',
+ *         'description' => 'Create, view, edit, delete, ...',
+ *         'group' => 'Blog',
+ *       ),
+ *     );
+ *   @endcode
+ * @see simpletest_registry_files_alter()
+ */
+function simpletest_test_get_all() {
+//  $groups = &drupal_static(__FUNCTION__);
+  static $groups;
+
+  if (!$groups) {
+    // Load test information from cache if available, otherwise retrieve the
+    // information from each tests getInfo() method.
+    if ($cache = cache_get('simpletest', 'cache') && FALSE) { // TODO
+      $groups = $cache->data;
+    }
+    else {
+      // Select all clases in files ending with .test.
+//      $classes = db_select('registry')
+//        ->fields('registry', array('name'))
+//        ->condition('type', 'class')
+//        ->condition('filename', '%.test', 'LIKE')
+//        ->execute();
+      $classes = simpletest_test_get_all_classes();
+
+      $groups = array();
+
+      // Check that each class has a getInfo() method and store the information
+      // in an array keyed with the group specified in the test information.
+      foreach ($classes as $class) {
+//        $class = $class->name;
+        if (class_exists($class) && method_exists($class, 'getInfo')) {
+          // Valid test class, retrieve test information.
+          $info = call_user_func(array($class, 'getInfo'));
+
+          // Initialize test groups.
+          if (!isset($groups[$info['group']])) {
+            $groups[$info['group']] = array();
+          }
+          $groups[$info['group']][$class] = $info;
+        }
+      }
+      // Sort the groups and tests within the groups by name.
+      uksort($groups, 'strnatcasecmp');
+      foreach ($groups as $group => &$tests) {
+        uksort($tests, 'strnatcasecmp');
+      }
+
+      cache_set('simpletest', $groups);
+    }
   }
-  uksort($groups, 'strnatcasecmp');
   return $groups;
 }
 
+function simpletest_test_get_all_classes() {
+  // Must load DrupalWebTestCase before loading any other test classes which
+  // will extend it.
+  require_once 'drupal_web_test_case.php';
+
+  $pre = get_declared_classes();
+  $files = module_rebuild_cache();
+  foreach ($files as $file) {
+    $directory = dirname($file->filename);
+    $test_files = file_scan_directory($directory, '\.test$');
+
+    foreach ($test_files as $test_file) {
+      require_once $test_file->filename;
+    }
+  }
+  $post = get_declared_classes();
+  return array_values(array_diff($post, $pre));
+}
+
+///**
+// * Implementation of hook_registry_files_alter().
+// *
+// * Add the test files for disabled modules so that we get a list containing
+// * all the avialable tests.
+// */
+//function simpletest_registry_files_alter(&$files, $modules) {
+//  foreach ($modules as $module) {
+//    // Only add test files for disabled modules, as enabled modules should
+//    // already include any test files they provide.
+//    if (!$module->status) {
+//      $dir = $module->dir;
+//      if (!empty($module->info['files'])) {
+//        foreach ($module->info['files'] as $file) {
+//          if (substr($file, -5) == '.test') {
+//            $files["$dir/$file"] = array('module' => $module->name, 'weight' => $module->weight);
+//          }
+//        }
+//      }
+//    }
+//  }
+//}
+
 /**
  * Remove all temporary database tables and directories.
  */
 function simpletest_clean_environment() {
   simpletest_clean_database();
   simpletest_clean_temporary_directories();
-  $count = simpletest_clean_results_table();
-  drupal_set_message(t('Removed @count test results.', array('@count' => $count)));
+  if (variable_get('simpletest_clear_results', TRUE)) {
+    $count = simpletest_clean_results_table();
+    drupal_set_message(format_plural($count, 'Removed 1 test result.', 'Removed @count test results.'));
+  }
+  else {
+    drupal_set_message(t('Clear results is disabled and the test results table will not be cleared.'), 'warning');
+  }
 }
 
 /**
@@ -552,10 +461,10 @@
   }
 
   if (count($ret) > 0) {
-    drupal_set_message(t('Removed @count left over tables.', array('@count' => count($ret))));
+    drupal_set_message(format_plural(count($ret), 'Removed 1 leftover table.', 'Removed @count leftover tables.'));
   }
   else {
-    drupal_set_message(t('No left over tables to remove.'));
+    drupal_set_message(t('No leftover tables to remove.'));
   }
 }
 
@@ -583,21 +492,24 @@
 }
 
 /**
- * Find all left over temporary directories and remove them.
+ * Find all leftover temporary directories and remove them.
  */
 function simpletest_clean_temporary_directories() {
+//  $files = scandir('public://');
   $files = scandir(file_directory_path());
   $count = 0;
   foreach ($files as $file) {
-    $path = file_directory_path() . '/' . $file;
+//    $path = 'public://' . $file;
+    $path = file_directory_path() . $file;
     if (is_dir($path) && preg_match('/^simpletest\d+/', $file)) {
+//      file_unmanaged_delete_recursive($path);
       simpletest_clean_temporary_directory($path);
       $count++;
     }
   }
 
   if ($count > 0) {
-    drupal_set_message(t('Removed @count temporary directories.', array('@count' => $count)));
+    drupal_set_message(format_plural($count, 'Removed 1 temporary directory.', 'Removed @count temporary directories.'));
   }
   else {
     drupal_set_message(t('No temporary directories to remove.'));
@@ -610,6 +522,11 @@
  * @param string $path Directory path.
  */
 function simpletest_clean_temporary_directory($path) {
+  // Drupal 6.
+  if (!is_dir($path)) {
+    return;
+  }
+
   $files = scandir($path);
   foreach ($files as $file) {
     if ($file != '.' && $file != '..') {
@@ -632,36 +549,37 @@
  * @param $test_id
  *   Test ID to remove results for, or NULL to remove all results.
  * @return
- *   The number of results removed or FALSE.
+ *   The number of results removed.
  */
 function simpletest_clean_results_table($test_id = NULL) {
   if (variable_get('simpletest_clear_results', TRUE)) {
     if ($test_id) {
-//      $count = db_result(db_query('SELECT COUNT(test_id) FROM {simpletest_test_id} WHERE test_id = :test_id', array(':test_id' => $test_id)));
+//      $count = db_query('SELECT COUNT(test_id) FROM {simpletest_test_id} WHERE test_id = :test_id', array(':test_id' => $test_id))->fetchField();
       $count = db_result(db_query('SELECT COUNT(test_id) FROM {simpletest_test_id} WHERE test_id = %d', $test_id));
 
-//      db_delete("simpletest")
+//      db_delete('simpletest')
 //        ->condition('test_id', $test_id)
 //        ->execute();
-//      db_delete("simpletest_test_id")
+//      db_delete('simpletest_test_id')
 //        ->condition('test_id', $test_id)
 //        ->execute();
       db_query("DELETE FROM {simpletest} WHERE test_id = %d", $test_id);
       db_query("DELETE FROM {simpletest_test_id} WHERE test_id = %d", $test_id);
     }
     else {
+//      $count = db_query('SELECT COUNT(test_id) FROM {simpletest_test_id}')->fetchField();
       $count = db_result(db_query('SELECT COUNT(test_id) FROM {simpletest_test_id}'));
 
       // Clear test results.
-//      db_delete("simpletest")->execute();
-//      db_delete("simpletest_test_id")->execute();
+//      db_delete('simpletest')->execute();
+//      db_delete('simpletest_test_id')->execute();
       db_query('DELETE FROM {simpletest}');
       db_query('DELETE FROM {simpletest_test_id}');
     }
 
     return $count;
   }
-  return FALSE;
+  return 0;
 }
 
 /**
Index: drupal_web_test_case.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/simpletest/drupal_web_test_case.php,v
retrieving revision 1.2.2.3.2.38
diff -u -r1.2.2.3.2.38 drupal_web_test_case.php
--- drupal_web_test_case.php	25 Apr 2009 22:14:54 -0000	1.2.2.3.2.38
+++ drupal_web_test_case.php	5 Sep 2009 11:05:01 -0000
@@ -1,6 +1,7 @@
 <?php
-// $Id: drupal_web_test_case.php,v 1.2.2.3.2.38 2009/04/25 22:14:54 boombatower Exp $
-// Core: Id: drupal_web_test_case.php,v 1.96 2009/04/22 09:57:10 dries Exp
+// $Id$
+// Core: Id: drupal_web_test_case.php,v 1.146 2009/08/31 18:30:26 webchick Exp $
+
 /**
  * @file
  * Provide required modifications to Drupal 7 core DrupalWebTestCase in order
@@ -12,10 +13,11 @@
 module_load_include('function.inc', 'simpletest');
 
 /**
- * Test case for typical Drupal tests.
+ * Base class for Drupal tests.
+ *
+ * Do not extend this class, use one of the subclasses in this file.
  */
-class DrupalWebTestCase {
-
+abstract class DrupalTestCase {
   /**
    * The test run ID.
    *
@@ -24,70 +26,6 @@
   protected $testId;
 
   /**
-   * The URL currently loaded in the internal browser.
-   *
-   * @var string
-   */
-  protected $url;
-
-  /**
-   * The handle of the current cURL connection.
-   *
-   * @var resource
-   */
-  protected $curlHandle;
-
-  /**
-   * The headers of the page currently loaded in the internal browser.
-   *
-   * @var Array
-   */
-  protected $headers;
-
-  /**
-   * The content of the page currently loaded in the internal browser.
-   *
-   * @var string
-   */
-  protected $content;
-
-  /**
-   * The content of the page currently loaded in the internal browser (plain text version).
-   *
-   * @var string
-   */
-  protected $plainTextContent;
-
-  /**
-   * The parsed version of the page.
-   *
-   * @var SimpleXMLElement
-   */
-  protected $elements = NULL;
-
-  /**
-   * The current user logged in using the internal browser.
-   *
-   * @var bool
-   */
-  protected $loggedInUser = FALSE;
-
-  /**
-   * The current cookie file used by cURL.
-   *
-   * We do not reuse the cookies in further runs, so we do not need a file
-   * but we still need cookie handling, so we set the jar to NULL.
-   */
-  protected $cookieFile = NULL;
-
-  /**
-   * Additional cURL options.
-   *
-   * DrupalWebTestCase itself never sets this but always obeys what is set.
-   */
-  protected $additionalCurlOptions = array();
-
-  /**
    * The original database prefix, before it was changed for testing purposes.
    *
    * @var string
@@ -102,11 +40,9 @@
   protected $originalFileDirectory = NULL;
 
   /**
-   * The original user, before it was changed to a clean uid = 1 for testing purposes.
-   *
-   * @var object
+   * Time limit for the test.
    */
-  protected $originalUser = NULL;
+  protected $timeLimit = 180;
 
   /**
    * Current results of this test case.
@@ -117,6 +53,7 @@
     '#pass' => 0,
     '#fail' => 0,
     '#exception' => 0,
+    '#debug' => 0,
   );
 
   /**
@@ -127,10 +64,14 @@
   protected $assertions = array();
 
   /**
-   * Time limit for the test.
+   * This class is skipped when looking for the source of an assertion.
+   *
+   * When displaying which function an assert comes from, it's not too useful
+   * to see "drupalWebTestCase->drupalLogin()', we would like to see the test
+   * that called it. So we need to skip the classes defining these helper
+   * methods.
    */
-  protected $timeLimit = 180;
-
+  protected $skipClasses = array(__CLASS__ => TRUE);
 
   /**
    * Constructor for DrupalWebTestCase.
@@ -159,7 +100,7 @@
    *   the name of the source file, 'line' is the line number and 'function'
    *   is the caller function itself.
    */
-  private function assert($status, $message = '', $group = 'Other', array $caller = NULL) {
+  protected function assert($status, $message = '', $group = 'Other', array $caller = NULL) {
     global $db_prefix;
 
     // Convert boolean status to string status.
@@ -192,14 +133,66 @@
     );
 
     // Store assertion for display after the test has completed.
-//    db_insert('simpletest')->fields($assertion)->execute();
+//    db_insert('simpletest')
+//      ->fields($assertion)
+//      ->execute();
     db_query("INSERT INTO {simpletest}
               (test_id, test_class, status, message, message_group, function, line, file)
               VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s')", array_values($assertion));
 
     // Return to testing prefix.
     $db_prefix = $current_db_prefix;
-    return $status == 'pass' ? TRUE : FALSE;
+    // We do not use a ternary operator here to allow a breakpoint on
+    // test failure.
+    if ($status == 'pass') {
+      return TRUE;
+    }
+    else {
+      return FALSE;
+    }
+  }
+
+  /**
+   * Store an assertion from outside the testing context.
+   *
+   * This is useful for inserting assertions that can only be recorded after
+   * the test case has been destroyed, such as PHP fatal errors. The caller
+   * information is not automatically gathered since the caller is most likely
+   * inserting the assertion on behalf of other code. In all other respects
+   * the method behaves just like DrupalTestCase::assert() in terms of storing
+   * the assertion.
+   *
+   * @see DrupalTestCase::assert()
+   */
+  public static function insertAssert($test_id, $test_class, $status, $message = '', $group = 'Other', array $caller = array()) {
+    // Convert boolean status to string status.
+    if (is_bool($status)) {
+      $status = $status ? 'pass' : 'fail';
+    }
+
+    $caller += array(
+      'function' => t('Unknown'),
+      'line' => 0,
+      'file' => t('Unknown'),
+    );
+
+    $assertion = array(
+      'test_id' => $test_id,
+      'test_class' => $test_class,
+      'status' => $status,
+      'message' => $message,
+      'message_group' => $group,
+      'function' => $caller['function'],
+      'line' => $caller['line'],
+      'file' => $caller['file'],
+    );
+
+//    db_insert('simpletest')
+//      ->fields($assertion)
+//      ->execute();
+    db_query("INSERT INTO {simpletest}
+              (test_id, test_class, status, message, message_group, function, line, file)
+              VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s')", array_values($assertion));
   }
 
   /**
@@ -212,11 +205,11 @@
     $backtrace = debug_backtrace();
 
     // The first element is the call. The second element is the caller.
-    // We skip calls that occurred in one of the methods of DrupalWebTestCase
+    // We skip calls that occurred in one of the methods of our base classes
     // or in an assertion function.
-    while (($caller = $backtrace[1]) &&
-          ((isset($caller['class']) && $caller['class'] == 'DrupalWebTestCase') ||
-            substr($caller['function'], 0, 6) == 'assert')) {
+   while (($caller = $backtrace[1]) &&
+         ((isset($caller['class']) && isset($this->skipClasses[$caller['class']])) ||
+           substr($caller['function'], 0, 6) == 'assert')) {
       // We remove that call.
       array_shift($backtrace);
     }
@@ -401,6 +394,12 @@
    *   FALSE.
    */
   protected function error($message = '', $group = 'Other', array $caller = NULL) {
+    if ($group == 'User notice') {
+      // Since 'User notice' is set by trigger_error() which is used for debug
+      // set the message to a status of 'debug'.
+      return $this->assert('debug', $message, 'Debug', $caller);
+    }
+
     return $this->assert('exception', $message, $group, $caller);
   }
 
@@ -408,6 +407,17 @@
    * Run all tests in this class.
    */
   public function run() {
+    // Initialize verbose debugging.
+    simpletest_verbose(NULL, file_directory_path(), get_class($this));
+
+    // HTTP auth settings (<username>:<password>) for the simpletest browser
+    // when sending requests to the test site.
+    $username = variable_get('simpletest_username', NULL);
+    $password = variable_get('simpletest_password', NULL);
+    if ($username && $password) {
+      $this->httpauth_credentials = $username . ':' . $password;
+    }
+
     set_error_handler(array($this, 'errorHandler'));
     $methods = array();
     // Iterate through all the methods in this class.
@@ -473,6 +483,206 @@
   }
 
   /**
+   * Generates a random string of ASCII characters of codes 32 to 126.
+   *
+   * The generated string includes alpha-numeric characters and common misc
+   * characters. Use this method when testing general input where the content
+   * is not restricted.
+   *
+   * @param $length
+   *   Length of random string to generate which will be appended to $db_prefix.
+   * @return
+   *   Randomly generated string.
+   */
+  public static function randomString($length = 8) {
+    global $db_prefix;
+
+    $str = '';
+    for ($i = 0; $i < $length; $i++) {
+      $str .= chr(mt_rand(32, 126));
+    }
+    return str_replace('simpletest', 's', $db_prefix) . $str;
+  }
+
+  /**
+   * Generates a random string containing letters and numbers.
+   *
+   * The letters may be upper or lower case. This method is better for
+   * restricted inputs that do not accept certain characters. For example,
+   * when testing input fields that require machine readable values (ie without
+   * spaces and non-standard characters) this method is best.
+   *
+   * @param $length
+   *   Length of random string to generate which will be appended to $db_prefix.
+   * @return
+   *   Randomly generated string.
+   */
+  public static function randomName($length = 8) {
+    global $db_prefix;
+
+    $values = array_merge(range(65, 90), range(97, 122), range(48, 57));
+    $max = count($values) - 1;
+    $str = '';
+    for ($i = 0; $i < $length; $i++) {
+      $str .= chr($values[mt_rand(0, $max)]);
+    }
+    return str_replace('simpletest', 's', $db_prefix) . $str;
+  }
+
+}
+
+/**
+ * Test case for Drupal unit tests.
+ *
+ * These tests can not access the database nor files. Calling any Drupal
+ * function that needs the database will throw exceptions. These include
+ * watchdog(), function_exists(), module_implements(),
+ * module_invoke_all() etc.
+ */
+class DrupalUnitTestCase extends DrupalTestCase {
+
+  /**
+   * Constructor for DrupalUnitTestCase.
+   */
+  function __construct($test_id = NULL) {
+    parent::__construct($test_id);
+    $this->skipClasses[__CLASS__] = TRUE;
+  }
+
+  function setUp() {
+    global $db_prefix, $conf;
+
+    // Store necessary current values before switching to prefixed database.
+    $this->originalPrefix = $db_prefix;
+    $this->originalFileDirectory = file_directory_path();
+
+    // Generate temporary prefixed database to ensure that tests have a clean starting point.
+    $db_prefix = Database::getConnection()->prefixTables('{simpletest' . mt_rand(1000, 1000000) . '}');
+    $conf['file_public_path'] = $this->originalFileDirectory . '/' . $db_prefix;
+
+    // If locale is enabled then t() will try to access the database and
+    // subsequently will fail as the database is not accessible.
+    $module_list = module_list();
+    if (isset($module_list['locale'])) {
+      $this->originalModuleList = $module_list;
+      unset($module_list['locale']);
+      module_list(TRUE, FALSE, FALSE, $module_list);
+    }
+  }
+
+  function tearDown() {
+    global $db_prefix, $conf;
+    if (preg_match('/simpletest\d+/', $db_prefix)) {
+      $conf['file_public_path'] = $this->originalFileDirectory;
+      // Return the database prefix to the original.
+      $db_prefix = $this->originalPrefix;
+      // Restore modules if necessary.
+      if (isset($this->originalModuleList)) {
+        module_list(TRUE, FALSE, FALSE, $this->originalModuleList);
+      }
+    }
+  }
+}
+
+/**
+ * Test case for typical Drupal tests.
+ */
+class DrupalWebTestCase extends DrupalTestCase {
+  /**
+   * The URL currently loaded in the internal browser.
+   *
+   * @var string
+   */
+  protected $url;
+
+  /**
+   * The handle of the current cURL connection.
+   *
+   * @var resource
+   */
+  protected $curlHandle;
+
+  /**
+   * The headers of the page currently loaded in the internal browser.
+   *
+   * @var Array
+   */
+  protected $headers;
+
+  /**
+   * The content of the page currently loaded in the internal browser.
+   *
+   * @var string
+   */
+  protected $content;
+
+  /**
+   * The content of the page currently loaded in the internal browser (plain text version).
+   *
+   * @var string
+   */
+  protected $plainTextContent;
+
+  /**
+   * The parsed version of the page.
+   *
+   * @var SimpleXMLElement
+   */
+  protected $elements = NULL;
+
+  /**
+   * The current user logged in using the internal browser.
+   *
+   * @var bool
+   */
+  protected $loggedInUser = FALSE;
+
+  /**
+   * The current cookie file used by cURL.
+   *
+   * We do not reuse the cookies in further runs, so we do not need a file
+   * but we still need cookie handling, so we set the jar to NULL.
+   */
+  protected $cookieFile = NULL;
+
+  /**
+   * Additional cURL options.
+   *
+   * DrupalWebTestCase itself never sets this but always obeys what is set.
+   */
+  protected $additionalCurlOptions = array();
+
+  /**
+   * The original user, before it was changed to a clean uid = 1 for testing purposes.
+   *
+   * @var object
+   */
+  protected $originalUser = NULL;
+
+  /**
+   * HTTP authentication credentials (<username>:<password>).
+   */
+  protected $httpauth_credentials = NULL;
+
+  /**
+   * The current session name, if available.
+   */
+  protected $session_name = NULL;
+
+  /**
+   * The current session ID, if available.
+   */
+  protected $session_id = NULL;
+
+  /**
+   * Constructor for DrupalWebTestCase.
+   */
+  function __construct($test_id = NULL) {
+    parent::__construct($test_id);
+    $this->skipClasses[__CLASS__] = TRUE;
+  }
+
+  /**
    * Get a node from the database based on its title.
    *
    * @param title
@@ -494,13 +704,14 @@
    *
    * @param $settings
    *   An associative array of settings to change from the defaults, keys are
-   *   node properties, for example 'body' => 'Hello, world!'.
+   *   node properties, for example 'title' => 'Hello, world!'.
    * @return
    *   Created node object.
    */
   protected function drupalCreateNode($settings = array()) {
-    // Populate defaults array
+    // Populate defaults array.
     $settings += array(
+//      'body'      => array(FIELD_LANGUAGE_NONE => array(array())),
       'body'      => $this->randomName(32),
       'title'     => $this->randomName(8),
       'comment'   => 2,
@@ -523,11 +734,6 @@
       $settings['date'] = format_date($settings['created'], 'custom', 'Y-m-d H:i:s O');
     }
 
-    // Add the default teaser.
-    if (!isset($settings['teaser'])) {
-      $settings['teaser'] = $settings['body'];
-    }
-
     // If the node's user uid is not specified manually, use the currently
     // logged in user if available, or else the user running the test.
     if (!isset($settings['uid'])) {
@@ -540,11 +746,21 @@
       }
     }
 
+//    // Merge body field value and format separately.
+//    $body = array(
+//      'value' => $this->randomName(32),
+//      'format' => FILTER_FORMAT_DEFAULT
+//    );
+//    $settings['body'][FIELD_LANGUAGE_NONE][0] += $body;
+
     $node = (object) $settings;
     node_save($node);
 
-    // small hack to link revisions to our test user
-//    db_query('UPDATE {node_revision} SET uid = %d WHERE vid = %d', $node->uid, $node->vid);
+    // Small hack to link revisions to our test user.
+//    db_update('node_revision')
+//      ->fields(array('uid' => $node->uid))
+//      ->condition('vid', $node->vid)
+//      ->execute();
     db_query('UPDATE {node_revisions} SET uid = %d WHERE vid = %d', $node->uid, $node->vid);
     return $node;
   }
@@ -559,24 +775,26 @@
    *   Created content type.
    */
   protected function drupalCreateContentType($settings = array()) {
-    // find a non-existent random type name.
+    // Find a non-existent random type name.
     do {
+//      $name = strtolower($this->randomName(8));
+//    } while (node_type_get_type($name));
       $name = strtolower($this->randomName(3, 'type_'));
     } while (node_get_types('type', $name));
 
-    // Populate defaults array
+    // Populate defaults array.
     $defaults = array(
       'type' => $name,
       'name' => $name,
       'description' => '',
       'help' => '',
-      'min_word_count' => 0,
+      'min_word_count' => 0, // Drupal 6.
       'title_label' => 'Title',
       'body_label' => 'Body',
       'has_title' => 1,
       'has_body' => 1,
     );
-    // imposed values for a custom type
+    // Imposed values for a custom type.
     $forced = array(
       'orig_type' => '',
       'old_type' => '',
@@ -615,7 +833,7 @@
 
     // Make sure type is valid.
     if (in_array($type, array('binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'))) {
-     // Use original file directory instead of one created during setUp().
+      // Use original file directory instead of one created during setUp().
       $path = $this->originalFileDirectory . '/simpletest';
 //      $files = file_scan_directory($path, '/' . $type . '\-.*/');
       $files = file_scan_directory($path, '' . $type . '\-.*');
@@ -623,10 +841,10 @@
       // If size is set then remove any files that are not of that size.
       if ($size !== NULL) {
         foreach ($files as $file) {
-//          $stats = stat($file->filepath);
+//          $stats = stat($file->uri);
           $stats = stat($file->filename);
           if ($stats['size'] != $size) {
-//            unset($files[$file->filepath]);
+//            unset($files[$file->uri]);
             unset($files[$file->filename]);
           }
         }
@@ -640,7 +858,7 @@
    * Compare two files based on size and file name.
    */
   protected function drupalCompareFiles($file1, $file2) {
-//    $compare_size = filesize($file1->filepath) - filesize($file2->filepath);
+//    $compare_size = filesize($file1->uri) - filesize($file2->uri);
     $compare_size = filesize($file1->filename) - filesize($file2->filename);
     if ($compare_size) {
       // Sort by file size.
@@ -653,27 +871,6 @@
   }
 
   /**
-   * Generates a random string.
-   *
-   * @param $number
-   *   Number of characters in length to append to the prefix.
-   * @param $prefix
-   *   Prefix to use.
-   * @return
-   *   Randomly generated string.
-   */
-  public static function randomName($number = 4, $prefix = 'simpletest_') {
-    $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_';
-    for ($x = 0; $x < $number; $x++) {
-      $prefix .= $chars{mt_rand(0, strlen($chars) - 1)};
-      if ($x == 0) {
-        $chars .= '0123456789';
-      }
-    }
-    return $prefix;
-  }
-
-  /**
    * Create a user with a given set of permissions. The permissions correspond to the
    * names given on the privileges page.
    *
@@ -683,9 +880,9 @@
    *   A fully loaded user object with pass_raw property, or FALSE if account
    *   creation fails.
    */
-  protected function drupalCreateUser($permissions = NULL) {
+  protected function drupalCreateUser($permissions = array('access comments', 'access content', 'post comments', 'post comments without approval')) {
     // Create a role with the given permission set.
-    if (!($rid = $this->_drupalCreateRole($permissions))) {
+    if (!($rid = $this->drupalCreateRole($permissions))) {
       return FALSE;
     }
 
@@ -714,32 +911,34 @@
    *
    * @param $permissions
    *   Array of permission names to assign to role.
+   * @param $name
+   *   (optional) String for the name of the role.  Defaults to a random string.
    * @return
    *   Role ID of newly created role, or FALSE if role creation failed.
    */
-  protected function _drupalCreateRole(array $permissions = NULL) {
-    // Generate string version of permissions list.
-    if ($permissions === NULL) {
-      $permissions = array('access comments', 'access content', 'post comments', 'post comments without approval');
+  protected function drupalCreateRole(array $permissions, $name = NULL) {
+    // Generate random name if it was not passed.
+    if (!$name) {
+      $name = $this->randomName();
     }
 
+    // Check the all the permissions strings are valid.
     if (!$this->checkPermissions($permissions)) {
       return FALSE;
     }
 
     // Create new role.
-    $role_name = $this->randomName();
-    db_query("INSERT INTO {role} (name) VALUES ('%s')", $role_name);
-    $role = db_fetch_object(db_query("SELECT * FROM {role} WHERE name = '%s'", $role_name));
-    $this->assertTrue($role, t('Created role of name: @role_name, id: @rid', array('@role_name' => $role_name, '@rid' => (isset($role->rid) ? $role->rid : t('-n/a-')))), t('Role'));
+//    $role = new stdClass();
+//    $role->name = $name;
+//    user_role_save($role);
+//    user_role_set_permissions($role->name, $permissions);
+    db_query("INSERT INTO {role} (name) VALUES ('%s')", $name);
+    $role = db_fetch_object(db_query("SELECT * FROM {role} WHERE name = '%s'", $name));
+
+    $this->assertTrue(isset($role->rid), t('Created role of name: @name, id: @rid', array('@name' => $name, '@rid' => (isset($role->rid) ? $role->rid : t('-n/a-')))), t('Role'));
     if ($role && !empty($role->rid)) {
-//      // Assign permissions to role and mark it for clean-up.
-//      foreach ($permissions as $permission_string) {
-//        db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", $role->rid, $permission_string);
-//      }
-//      $count = db_result(db_query("SELECT COUNT(*) FROM {role_permission} WHERE rid = %d", $role->rid));
+//      $count = db_query('SELECT COUNT(*) FROM {role_permission} WHERE rid = :rid', array(':rid' => $role->rid))->fetchField();
 //      $this->assertTrue($count == count($permissions), t('Created permissions: @perms', array('@perms' => implode(', ', $permissions))), t('Role'));
-//      return $role->rid;
 
       // Assign permissions to role and mark it for clean-up.
       db_query("INSERT INTO {permission} (rid, perm) VALUES (%d, '%s')", $role->rid, implode(', ', $permissions));
@@ -763,10 +962,11 @@
    *   TRUE or FALSE depending on whether the permissions are valid.
    */
   protected function checkPermissions(array $permissions, $reset = FALSE) {
+//    $available = &drupal_static(__FUNCTION__);
     static $available;
 
     if (!isset($available) || $reset) {
-//      $available = array_keys(module_invoke_all('perm'));
+//      $available = array_keys(module_invoke_all('permission'));
       $available = module_invoke_all('perm');
     }
 
@@ -818,25 +1018,32 @@
     );
     $this->drupalPost('user', $edit, t('Log in'));
 
-    $pass = $this->assertText($user->name, t('Found name: %name', array('%name' => $user->name)), t('User login'));
-    $pass = $pass && $this->assertNoText(t('The username %name has been blocked.', array('%name' => $user->name)), t('No blocked message at login page'), t('User login'));
-    $pass = $pass && $this->assertNoText(t('The name %name is a reserved username.', array('%name' => $user->name)), t('No reserved message at login page'), t('User login'));
+    // If a "log out" link appears on the page, it is almost certainly because
+    // the login was successful.
+    $pass = $this->assertLink(t('Log out'), 0, t('User %name successfully logged in.', array('%name' => $user->name)), t('User login'));
 
     if ($pass) {
       $this->loggedInUser = $user;
     }
   }
 
+  /**
+   * Generate a token for the currently logged in user.
+   */
+  protected function drupalGetToken($value = '') {
+    $private_key = drupal_get_private_key();
+    return md5($this->session_id . $value . $private_key);
+  }
+
   /*
    * Logs a user out of the internal browser, then check the login page to confirm logout.
    */
   protected function drupalLogout() {
-    // Make a request to the logout page.
-//    $this->drupalGet('user/logout');
-    $this->drupalGet('logout');
-
-    // Load the user page, the idea being if you were properly logged out you should be seeing a login screen.
-    $this->drupalGet('user');
+    // Make a request to the logout page, and redirect to the user page, the
+    // idea being if you were properly logged out you should be seeing a login
+    // screen.
+//    $this->drupalGet('user/logout', array('query' => 'destination=user'));
+    $this->drupalGet('logout', array('query' => 'destination=user'));
     $pass = $this->assertField('name', t('Username field found.'), t('Logout'));
     $pass = $pass && $this->assertField('pass', t('Password field found.'), t('Logout'));
 
@@ -856,22 +1063,43 @@
    *   List of modules to enable for the duration of the test.
    */
   protected function setUp() {
-    global $db_prefix, $user, $language; // $language (Drupal 6).
+    global $db_prefix, $user, $language;
 
     // Store necessary current values before switching to prefixed database.
+    $this->originalLanguage = $language;
+    $this->originalLanguageDefault = variable_get('language_default');
     $this->originalPrefix = $db_prefix;
     $this->originalFileDirectory = file_directory_path();
+    $this->originalProfile = drupal_get_profile();
     $clean_url_original = variable_get('clean_url', 0);
 
-    // Must reset locale here, since schema calls t().  (Drupal 6)
+    // Must reset locale here, since schema calls t(). (Drupal 6)
     if (module_exists('locale')) {
       $language = (object) array('language' => 'en', 'name' => 'English', 'native' => 'English', 'direction' => 0, 'enabled' => 1, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => '', 'weight' => 0, 'javascript' => '');
       locale(NULL, NULL, TRUE);
     }
 
     // Generate temporary prefixed database to ensure that tests have a clean starting point.
-//    $db_prefix = Database::getConnection()->prefixTables('{simpletest' . mt_rand(1000, 1000000) . '}');
-    $db_prefix = 'simpletest' . mt_rand(1000, 1000000);
+    $db_prefix_new = Database::getConnection()->prefixTables('{simpletest' . mt_rand(1000, 1000000) . '}');
+    $db_prefix_new = $db_prefix . 'simpletest' . mt_rand(1000, 1000000);
+//    db_update('simpletest_test_id')
+//      ->fields(array('last_prefix' => $db_prefix_new))
+//      ->condition('test_id', $this->testId)
+//      ->execute();
+    db_query("UPDATE {simpletest_test_id}
+              SET last_prefix = '%s'
+              WHERE test_id = %d", $db_prefix_new, $this->testId);
+    $db_prefix = $db_prefix_new;
+
+    // Create test directory ahead of installation so fatal errors and debug
+    // information can be logged during installation process.
+    $directory = $this->originalFileDirectory . '/simpletest/' . substr($db_prefix, 10);
+//    file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+    file_check_directory($directory, FILE_CREATE_DIRECTORY);
+
+    // Log fatal errors.
+    ini_set('log_errors', 1);
+    ini_set('error_log', $directory . '/error.log');
 
 //    include_once DRUPAL_ROOT . '/includes/install.inc';
     include_once './includes/install.inc';
@@ -879,12 +1107,24 @@
 
 //    $this->preloadRegistry();
 
+//    // Include the default profile
+//    variable_set('install_profile', 'default');
+//    $profile_details = install_profile_info('default', 'en');
+
     // Add the specified modules to the list of modules in the default profile.
-    $args = func_get_args();
-//    $modules = array_unique(array_merge(drupal_get_profile_modules('default', 'en'), $args));
-    $modules = array_unique(array_merge(drupal_verify_profile('default', 'en'), $args));
-//    drupal_install_modules($modules, TRUE);
-    drupal_install_modules($modules);
+    // Install the modules specified by the default profile.
+//    drupal_install_modules($profile_details['dependencies'], TRUE);
+    drupal_install_modules(drupal_verify_profile('default', 'en'));
+
+//    node_type_clear();
+
+    // Install additional modules one at a time in order to make sure that the
+    // list of modules is updated between each module's installation.
+    $modules = func_get_args();
+    foreach ($modules as $module) {
+//      drupal_install_modules(array($module), TRUE);
+      drupal_install_modules(array($module));
+    }
 
     // Because the schema is static cached, we need to flush
     // it between each run. If we don't, then it will contain
@@ -893,10 +1133,13 @@
     drupal_get_schema(NULL, TRUE);
 
     // Run default profile tasks.
+//    $install_state = array();
+//    drupal_install_modules(array('default'), TRUE);
     $task = 'profile';
     default_profile_tasks($task, '');
 
     // Rebuild caches.
+//    node_types_rebuild();
     actions_synchronize();
     _drupal_flush_css_js();
     $this->refreshVariables();
@@ -912,28 +1155,46 @@
 
     // Restore necessary variables.
     variable_set('install_profile', 'default');
+//    variable_set('install_task', 'done');
     variable_set('install_task', 'profile-finished');
     variable_set('clean_url', $clean_url_original);
     variable_set('site_mail', 'simpletest@example.com');
-
-    // Use temporary files directory with the same prefix as database.
-    variable_set('file_directory_path', $this->originalFileDirectory . '/' . $db_prefix);
-    $directory = file_directory_path();
-    // Create the files directory.
+//    // Set up English language.
+//    unset($GLOBALS['conf']['language_default']);
+//    $language = language_default();
+
+    // Use the test mail class instead of the default mail handler class.
+    variable_set('mail_sending_system', array('default-system' => 'TestingMailSystem'));
+
+    // Use temporary files directory with the same prefix as the database.
+//    $public_files_directory  = $this->originalFileDirectory . '/' . $db_prefix;
+//    $private_files_directory = $public_files_directory . '/private';
+    $directory = $this->originalFileDirectory . '/' . $db_prefix;
+
+    // Set path variables
+//    variable_set('file_public_path', $public_files_directory);
+//    variable_set('file_private_path', $private_files_directory);
+    variable_set('file_directory_path', $directory);
+
+    // Create the directories
+//    $directory = file_directory_path('public');
+//    file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+//    file_prepare_directory($private_files_directory, FILE_CREATE_DIRECTORY);
     file_check_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
 
+//    drupal_set_time_limit($this->timeLimit);
     set_time_limit($this->timeLimit);
   }
 
-  /**
-   * This method is called by DrupalWebTestCase::setUp, and preloads the
-   * registry from the testing site to cut down on the time it takes to
-   * setup a clean environment for the current test run.
-   */
-  protected function preloadRegistry() {
-    db_query('INSERT INTO {registry} SELECT * FROM ' . $this->originalPrefix . 'registry');
-    db_query('INSERT INTO {registry_file} SELECT * FROM ' . $this->originalPrefix . 'registry_file');
-  }
+//  /**
+//   * This method is called by DrupalWebTestCase::setUp, and preloads the
+//   * registry from the testing site to cut down on the time it takes to
+//   * setup a clean environment for the current test run.
+//   */
+//  protected function preloadRegistry() {
+//    db_query('INSERT INTO {registry} SELECT * FROM ' . $this->originalPrefix . 'registry');
+//    db_query('INSERT INTO {registry_file} SELECT * FROM ' . $this->originalPrefix . 'registry_file');
+//  }
 
   /**
    * Refresh the in-memory set of variables. Useful after a page request is made
@@ -950,7 +1211,8 @@
   protected function refreshVariables() {
     global $conf;
     cache_clear_all('variables', 'cache');
-    $conf = variable_init();
+//    $conf = variable_initialize();
+    $conf = variable_initialize();
   }
 
   /**
@@ -958,12 +1220,25 @@
    * and reset the database prefix.
    */
   protected function tearDown() {
-    global $db_prefix, $user;
+    global $db_prefix, $user, $language;
+
+    // In case a fatal error occured that was not in the test process read the
+    // log to pick up any fatal errors.
+    $db_prefix_temp = $db_prefix;
+    $db_prefix = $this->originalPrefix;
+    simpletest_log_read($this->testId, $db_prefix, get_class($this), TRUE);
+    $db_prefix = $db_prefix_temp;
+
+    $emailCount = count(variable_get('drupal_test_email_collector', array()));
+    if ($emailCount) {
+      $message = format_plural($emailCount, t('!count e-mail was sent during this test.'), t('!count e-mails were sent during this test.'), array('!count' => $emailCount));
+      $this->pass($message, t('E-mail'));
+    }
+
     if (preg_match('/simpletest\d+/', $db_prefix)) {
-      // Delete temporary files directory and reset files directory path.
+      // Delete temporary files directory.
 //      file_unmanaged_delete_recursive(file_directory_path());
       simpletest_clean_temporary_directory(file_directory_path());
-      variable_set('file_directory_path', $this->originalFileDirectory);
 
       // Remove all prefixed tables (all the tables in the schema).
       $schema = drupal_get_schema(NULL, TRUE);
@@ -993,7 +1268,7 @@
       // Reload module list and implementations to ensure that test module hooks
       // aren't called after tests.
       module_list(TRUE);
-//      module_implements(MODULE_IMPLEMENTS_CLEAR_CACHE);
+//      module_implements('', FALSE, TRUE);
       module_implements('', '', TRUE);
 
       // Reset the Field API.
@@ -1002,6 +1277,12 @@
       // Rebuild caches.
       $this->refreshVariables();
 
+      // Reset language.
+      $language = $this->originalLanguage;
+      if ($this->originalLanguageDefault) {
+        $GLOBALS['conf']['language_default'] = $this->originalLanguageDefault;
+      }
+
       // Close the CURL handler.
       $this->curlClose();
     }
@@ -1010,12 +1291,14 @@
   /**
    * Initializes the cURL connection.
    *
-   * This function will add authentication headers as specified in the
-   * simpletest_httpauth_username and simpletest_httpauth_pass variables. Also,
-   * see the description of $curl_options among the properties.
+   * If the simpletest_httpauth_credentials variable is set, this function will
+   * add HTTP authentication headers. This is necessary for testing sites that
+   * are protected by login credentials from public access.
+   * See the description of $curl_options for other options.
    */
   protected function curlInitialize() {
     global $base_url, $db_prefix;
+
     if (!isset($this->curlHandle)) {
       $this->curlHandle = curl_init();
       $curl_options = $this->additionalCurlOptions + array(
@@ -1024,20 +1307,22 @@
         CURLOPT_FOLLOWLOCATION => TRUE,
         CURLOPT_MAXREDIRS => 5,
         CURLOPT_RETURNTRANSFER => TRUE,
-        CURLOPT_SSL_VERIFYPEER => FALSE,  // Required to make the tests run on https://
-        CURLOPT_SSL_VERIFYHOST => FALSE,  // Required to make the tests run on https://
+        CURLOPT_SSL_VERIFYPEER => FALSE, // Required to make the tests run on https.
+        CURLOPT_SSL_VERIFYHOST => FALSE, // Required to make the tests run on https.
         CURLOPT_HEADERFUNCTION => array(&$this, 'curlHeaderCallback'),
       );
-      if (preg_match('/simpletest\d+/', $db_prefix, $matches)) {
-        $curl_options[CURLOPT_USERAGENT] = $matches[0];
-      }
-      if (!isset($curl_options[CURLOPT_USERPWD]) && ($auth = variable_get('simpletest_httpauth_username', ''))) {
-        if ($pass = variable_get('simpletest_httpauth_pass', '')) {
-          $auth .= ':' . $pass;
-        }
-        $curl_options[CURLOPT_USERPWD] = $auth;
+      if (isset($this->httpauth_credentials)) {
+        $curl_options[CURLOPT_USERPWD] = $this->httpauth_credentials;
       }
       curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options);
+
+      // By default, the child session name should be the same as the parent.
+      $this->session_name = session_name();
+    }
+    // We set the user agent header on each request so as to use the current
+    // time and a new uniqid.
+    if (preg_match('/simpletest\d+/', $db_prefix, $matches)) {
+      curl_setopt($this->curlHandle, CURLOPT_USERAGENT, drupal_generate_test_ua($matches[0]));
     }
   }
 
@@ -1061,7 +1346,11 @@
       $curl_options[CURLOPT_HTTPHEADER][] = 'Expect:';
     }
     curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options);
+
+    // Reset headers and the session ID.
+    $this->session_id = NULL;
     $this->headers = array();
+
     $this->drupalSetContent(curl_exec($this->curlHandle), curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL));
     $message_vars = array(
       '!method' => !empty($curl_options[CURLOPT_NOBODY]) ? 'HEAD' : (empty($curl_options[CURLOPT_POSTFIELDS]) ? 'GET' : 'POST'),
@@ -1086,6 +1375,7 @@
    */
   protected function curlHeaderCallback($curlHandler, $header) {
     $this->headers[] = $header;
+
     // Errors are being sent via X-Drupal-Assertion-* headers,
     // generated by _drupal_log_error() in the exact form required
     // by DrupalWebTestCase::error().
@@ -1093,6 +1383,17 @@
       // Call DrupalWebTestCase::error() with the parameters from the header.
       call_user_func_array(array(&$this, 'error'), unserialize(urldecode($matches[1])));
     }
+
+    // Save the session cookie, if set.
+    if (preg_match('/^Set-Cookie: ' . preg_quote($this->session_name) . '=([a-z90-9]+)/', $header, $matches)) {
+      if ($matches[1] != 'deleted') {
+        $this->session_id = $matches[1];
+      }
+      else {
+        $this->session_id = NULL;
+      }
+    }
+
     // This is required by cURL.
     return strlen($header);
   }
@@ -1158,6 +1459,9 @@
     if (($new = $this->checkForMetaRefresh())) {
       $out = $new;
     }
+    $this->verbose('GET request to: ' . $path .
+                   '<hr />Ending URL: ' . $this->getUrl() .
+                   '<hr />' . $out);
     return $out;
   }
 
@@ -1216,12 +1520,13 @@
         // We post only if we managed to handle every field in edit and the
         // submit button matches.
         if (!$edit && $submit_matches) {
+          $post_array = $post;
           if ($upload) {
             // TODO: cURL handles file uploads for us, but the implementation
             // is broken. This is a less than elegant workaround. Alternatives
             // are being explored at #253506.
             foreach ($upload as $key => $file) {
-              $file = realpath($file);
+              $file = drupal_realpath($file);
               if ($file && is_file($file)) {
                 $post[$key] = '@' . $file;
               }
@@ -1244,6 +1549,10 @@
           if (($new = $this->checkForMetaRefresh())) {
             $out = $new;
           }
+          $this->verbose('POST request to: ' . $path .
+                         '<hr />Ending URL: ' . $this->getUrl() .
+                         '<hr />Fields: ' . highlight_string('<?php ' . var_export($post_array, TRUE), TRUE) .
+                         '<hr />' . $out);
           return $out;
         }
       }
@@ -1436,8 +1745,8 @@
    *   The xpath string to use in the search.
    * @return
    *   The return value of the xpath search. For details on the xpath string
-   *   format and return values see the SimpleXML documentation.
-   *   http://us.php.net/manual/function.simplexml-element-xpath.php
+   *   format and return values see the SimpleXML documentation,
+   *   http://us.php.net/manual/function.simplexml-element-xpath.php.
    */
   protected function xpath($xpath) {
     if ($this->parse()) {
@@ -1482,11 +1791,13 @@
    *   Message to display.
    * @param $group
    *   The group this message belongs to, defaults to 'Other'.
+   * @return
+   *   TRUE if the assertion succeeded, FALSE otherwise.
    */
   protected function assertLink($label, $index = 0, $message = '', $group = 'Other') {
     $links = $this->xpath('//a[text()="' . $label . '"]');
     $message = ($message ?  $message : t('Link with label "!label" found.', array('!label' => $label)));
-    $this->assert(isset($links[$index]), $message, $group);
+    return $this->assert(isset($links[$index]), $message, $group);
   }
 
   /**
@@ -1500,11 +1811,13 @@
    *   Message to display.
    * @param $group
    *   The group this message belongs to, defaults to 'Other'.
+   * @return
+   *   TRUE if the assertion succeeded, FALSE otherwise.
    */
   protected function assertNoLink($label, $message = '', $group = 'Other') {
     $links = $this->xpath('//a[text()="' . $label . '"]');
     $message = ($message ?  $message : t('Link with label "!label" not found.', array('!label' => $label)));
-    $this->assert(empty($links), $message, $group);
+    return $this->assert(empty($links), $message, $group);
   }
 
   /**
@@ -1670,6 +1983,30 @@
   }
 
   /**
+   * Gets an array containing all e-mails sent during this test case.
+   *
+   * @param $filter
+   *   An array containing key/value pairs used to filter the e-mails that are returned.
+   * @return
+   *   An array containing e-mail messages captured during the current test.
+   */
+  protected function drupalGetMails($filter = array()) {
+    $captured_emails = variable_get('drupal_test_email_collector', array());
+    $filtered_emails = array();
+
+    foreach ($captured_emails as $message) {
+      foreach ($filter as $key => $value) {
+        if (!isset($message[$key]) || $message[$key] != $value) {
+          continue 2;
+        }
+      }
+      $filtered_emails[] = $message;
+    }
+
+    return $filtered_emails;
+  }
+
+  /**
    * Sets the raw HTML content. This can be useful when a page has been fetched
    * outside of the internal browser and assertions need to be made on the
    * returned page.
@@ -1698,7 +2035,10 @@
    * @return
    *   TRUE on pass, FALSE on fail.
    */
-  protected function assertRaw($raw, $message = '%s found', $group = 'Other') {
+  protected function assertRaw($raw, $message = '', $group = 'Other') {
+    if (!$message) {
+      $message = t('Raw "@raw" found', array('@raw' => check_plain($raw)));
+    }
     return $this->assert(strpos($this->content, $raw) !== FALSE, $message, $group);
   }
 
@@ -1715,7 +2055,10 @@
    * @return
    *   TRUE on pass, FALSE on fail.
    */
-  protected function assertNoRaw($raw, $message = '%s found', $group = 'Other') {
+  protected function assertNoRaw($raw, $message = '', $group = 'Other') {
+    if (!$message) {
+      $message = t('Raw "@raw" not found', array('@raw' => check_plain($raw)));
+    }
     return $this->assert(strpos($this->content, $raw) === FALSE, $message, $group);
   }
 
@@ -1776,7 +2119,7 @@
       $this->plainTextContent = filter_xss($this->content, array());
     }
     if (!$message) {
-      $message = '"' . $text . '"' . ($not_exists ? ' not found' : ' found');
+      $message = !$not_exists ? t('"@text" found', array('@text' => $text)) : t('"@text" not found', array('@text' => $text));
     }
     return $this->assert($not_exists == (strpos($this->plainTextContent, $text) === FALSE), $message, $group);
   }
@@ -1842,7 +2185,7 @@
       $this->plainTextContent = filter_xss($this->content, array());
     }
     if (!$message) {
-      $message = '"' . $text . '"'. ($be_unique ? ' found only once' : ' found more than once');
+      $message = '"' . $text . '"' . ($be_unique ? ' found only once' : ' found more than once');
     }
     $first_occurance = strpos($this->plainTextContent, $text);
     if ($first_occurance === FALSE) {
@@ -1865,7 +2208,10 @@
    * @return
    *   TRUE on pass, FALSE on fail.
    */
-  protected function assertPattern($pattern, $message = 'Pattern %s found', $group = 'Other') {
+  protected function assertPattern($pattern, $message = '', $group = 'Other') {
+    if (!$message) {
+      $message = t('Pattern "@pattern" found', array('@pattern' => $pattern));
+    }
     return $this->assert((bool) preg_match($pattern, $this->drupalGetContent()), $message, $group);
   }
 
@@ -1881,7 +2227,10 @@
    * @return
    *   TRUE on pass, FALSE on fail.
    */
-  protected function assertNoPattern($pattern, $message = 'Pattern %s not found', $group = 'Other') {
+  protected function assertNoPattern($pattern, $message = '', $group = 'Other') {
+    if (!$message) {
+      $message = t('Pattern "@pattern" not found', array('@pattern' => $pattern));
+    }
     return $this->assert(!preg_match($pattern, $this->drupalGetContent()), $message, $group);
   }
 
@@ -2094,6 +2443,36 @@
   }
 
   /**
+   * Assert that a checkbox field in the current page is checked.
+   *
+   * @param $id
+   *   Id of field to assert.
+   * @param $message
+   *   Message to display.
+   * @return
+   *   TRUE on pass, FALSE on fail.
+   */
+  protected function assertFieldChecked($id, $message = '') {
+    $elements = $this->xpath('//input[@id="' . $id . '"]');
+    return $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), $message ? $message : t('Checkbox field @id is checked.', array('@id' => $id)), t('Browser'));
+  }
+
+  /**
+   * Assert that a checkbox field in the current page is not checked.
+   *
+   * @param $id
+   *   Id of field to assert.
+   * @param $message
+   *   Message to display.
+   * @return
+   *   TRUE on pass, FALSE on fail.
+   */
+  protected function assertNoFieldChecked($id, $message = '') {
+    $elements = $this->xpath('//input[@id="' . $id . '"]');
+    return $this->assertTrue(isset($elements[0]) && empty($elements[0]['checked']), $message ? $message : t('Checkbox field @id is not checked.', array('@id' => $id)), t('Browser'));
+  }
+
+  /**
    * Assert that a field exists with the given name or id.
    *
    * @param $field
@@ -2155,4 +2534,83 @@
     $match = is_array($code) ? in_array($curl_code, $code) : $curl_code == $code;
     return $this->assertTrue($match, $message ? $message : t('HTTP response expected !code, actual !curl_code', array('!code' => $code, '!curl_code' => $curl_code)), t('Browser'));
   }
+
+  /**
+   * Assert that the most recently sent e-mail message has a field with the given value.
+   *
+   * @param $name
+   *   Name of field or message property to assert. Examples: subject, body, id, ...
+   * @param $value
+   *   Value of the field to assert.
+   * @param $message
+   *   Message to display.
+   * @return
+   *   TRUE on pass, FALSE on fail.
+   */
+  protected function assertMail($name, $value = '', $message = '') {
+    $captured_emails = variable_get('drupal_test_email_collector', array());
+    $email = end($captured_emails);
+    return $this->assertTrue($email && isset($email[$name]) && $email[$name] == $value, $message, t('E-mail'));
+  }
+
+  /**
+   * Log verbose message in a text file.
+   *
+   * The a link to the vebose message will be placed in the test results via
+   * as a passing assertion with the text '[verbose message]'.
+   *
+   * @param $message
+   *   The verbose message to be stored.
+   * @see simpletest_verbose()
+   */
+  protected function verbose($message) {
+    if ($id = simpletest_verbose($message)) {
+      $this->pass(l(t('Verbose message'), $this->originalFileDirectory . '/simpletest/verbose/' . get_class($this) . '-' . $id . '.html', array('attributes' => array('target' => '_blank'))), 'Debug');
+    }
+  }
+
+}
+
+/**
+ * Log verbose message in a text file.
+ *
+ * If verbose mode is enabled then page requests will be dumped to a file and
+ * presented on the test result screen. The messages will be placed in a file
+ * located in the simpletest directory in the original file system.
+ *
+ * @param $message
+ *   The verbose message to be stored.
+ * @param $original_file_directory
+ *   The original file directory, before it was changed for testing purposes.
+ * @param $test_class
+ *   The active test case class.
+ * @return
+ *   The ID of the message to be placed in related assertion messages.
+ * @see DrupalTestCase->originalFileDirectory
+ * @see DrupalWebTestCase->verbose()
+ */
+function simpletest_verbose($message, $original_file_directory = NULL, $test_class = NULL) {
+  static $file_directory = NULL, $class = NULL, $id = 1;
+//  $verbose = &drupal_static(__FUNCTION__);
+  static $verbose;
+
+  // Will pass first time during setup phase, and when verbose is TRUE.
+  if (!isset($original_file_directory) && !$verbose) {
+    return FALSE;
+  }
+
+  if ($message && $file_directory) {
+    $message = '<hr />ID #' . $id . ' (<a href="' . $class . '-' . ($id - 1) . '.html">Previous</a> | <a href="' . $class . '-' . ($id + 1) . '.html">Next</a>)<hr />' . $message;
+    file_put_contents($file_directory . "/simpletest/verbose/$class-$id.html", $message, FILE_APPEND);
+    return $id++;
+  }
+
+  if ($original_file_directory) {
+    $file_directory = $original_file_directory;
+    $class = $test_class;
+    $verbose = variable_get('simpletest_verbose', FALSE);
+    $directory = $file_directory . '/simpletest/verbose';
+    return file_prepare_directory($directory, FILE_CREATE_DIRECTORY);
+  }
+  return FALSE;
 }
Index: simpletest.css
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/simpletest/simpletest.css,v
retrieving revision 1.1.6.3
diff -u -r1.1.6.3 simpletest.css
--- simpletest.css	8 Feb 2009 02:35:39 -0000	1.1.6.3
+++ simpletest.css	5 Sep 2009 11:05:01 -0000
@@ -1,4 +1,5 @@
-/* $Id: simpletest.css,v 1.1.6.3 2009/02/08 02:35:39 boombatower Exp $ */
+/* $Id$
+/* Core: Id: simpletest.css,v 1.6 2009/08/04 06:47:00 webchick Exp */
 
 /* Addon for the simpletest module */
 #simpletest {
@@ -6,7 +7,7 @@
 
 /* Test Table */
 #simpletest-form-table th.select-all {
-  width: 50px;
+  width: 25px;
 }
 
 th.simpletest_test {
@@ -59,6 +60,14 @@
   background: #f5e742;
 }
 
+tr.simpletest-debug.odd {
+  background: #eeeeee;
+}
+
+tr.simpletest-debug.even {
+  background: #ffffff;
+}
+
 div.simpletest-image {
   display: inline;
   cursor: pointer;
Index: simpletest.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/simpletest/Attic/simpletest.test,v
retrieving revision 1.1.2.11
diff -u -r1.1.2.11 simpletest.test
--- simpletest.test	23 Apr 2009 05:39:51 -0000	1.1.2.11
+++ simpletest.test	5 Sep 2009 11:05:02 -0000
@@ -1,6 +1,7 @@
 <?php
-// $Id: simpletest.test,v 1.1.2.11 2009/04/23 05:39:51 boombatower Exp $
-// Core: Id: simpletest.test,v 1.17 2009/03/31 01:49:53 webchick Exp
+// $Id$
+// Core: Id: simpletest.test,v 1.31 2009/09/01 17:40:27 webchick Exp
+
 /**
  * @file
  * Backport of Drupal 7 simpletest.test with modifications, see BACKPORT.txt.
@@ -22,12 +23,12 @@
 
   public static function getInfo() {
     return array(
-      'name' => t('SimpleTest functionality'),
-      'description' => t('Test SimpleTest\'s web interface: check that the intended tests were
+      'name' => 'SimpleTest functionality',
+      'description' => 'Test SimpleTest\'s web interface: check that the intended tests were
                           run and ensure that test reports display the intended results. Also
                           test SimpleTest\'s internal browser and API\'s both explicitly and
-                          implicitly.'),
-      'group' => t('SimpleTest')
+                          implicitly.',
+      'group' => 'SimpleTest'
     );
   }
 
@@ -52,7 +53,7 @@
     if (!$this->inCURL()) {
       $this->drupalGet('node');
       $this->assertTrue($this->drupalGetHeader('Date'), t('An HTTP header was received.'));
-      $this->assertTitle(variable_get('site_name', 'Drupal'), t('Site title matches.'));
+      $this->assertTitle(t('Welcome to @site-name | @site-name', array('@site-name' => variable_get('site_name', 'Drupal'))), t('Site title matches.'));
       $this->assertNoTitle('Foo', t('Site title does not match.'));
       // Make sure that we are locked out of the installer when prefixing
       // using the user-agent header. This is an important security check.
@@ -91,7 +92,7 @@
       // Run twice so test_ids can be accumulated.
       for ($i = 0; $i < 2; $i++) {
         // Run this test from web interface.
-        $this->drupalGet('admin/build/testing');
+        $this->drupalGet('admin/config/development/testing');
 
         $edit = array();
         $edit['SimpleTestFunctionalTest'] = TRUE;
@@ -128,6 +129,8 @@
 
     // Generates a warning inside a PHP function.
     array_key_exists(NULL, NULL);
+
+    debug('Foo', 'Debug');
   }
 
   /**
@@ -158,6 +161,10 @@
     // the function name 'array_key_exists'.
     $this->assertAssertion('array_key_exists', 'Warning', 'Fail', 'simpletest.test', 'SimpleTestFunctionalTest->stubTest()');
 
+    $this->assertAssertion("Debug: 'Foo'", 'Debug', 'Fail', 'simpletest.test', 'SimpleTestFunctionalTest->stubTest()');
+
+    $this->assertEqual('6 passes, 2 fails, 2 exceptions, and 1 debug message', $this->childTestResults['summary'], 'Stub test summary is correct');
+
     $this->test_ids[] = $test_id = $this->getTestIdFromResults();
     $this->assertTrue($test_id, t('Found test ID in results.'));
   }
@@ -166,7 +173,7 @@
    * Fetch the test id from the test results.
    */
   function getTestIdFromResults() {
-    foreach($this->childTestResults['assertions'] as $assertion) {
+    foreach ($this->childTestResults['assertions'] as $assertion) {
       if (preg_match('@^Test ID is ([0-9]*)\.$@', $assertion['message'], $matches)) {
         return $matches[1];
       }
@@ -206,15 +213,14 @@
    */
   function getTestResults() {
     $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['summary'] = $this->asText($fieldset->div[1]);
+        $results['name'] = $this->asText($fieldset->legend);
 
         $results['assertions'] = array();
-        $tbody = $fieldset->fieldset->table->tbody;
+        $tbody = $fieldset->table->tbody;
         foreach ($tbody->tr as $row) {
           $assertion = array();
           $assertion['message'] = $this->asText($row->td[0]);
@@ -222,7 +228,7 @@
           $assertion['file'] = $this->asText($row->td[2]);
           $assertion['line'] = $this->asText($row->td[3]);
           $assertion['function'] = $this->asText($row->td[4]);
-          $ok_url = (url('misc/watchdog-ok.png') == 'misc/watchdog-ok.png') ? 'misc/watchdog-ok.png' : (base_path() . 'misc/watchdog-ok.png');
+          $ok_url = file_create_url('misc/watchdog-ok.png');
           $assertion['status'] = ($row->td[5]->img['src'] == $ok_url) ? 'Pass' : 'Fail';
           $results['assertions'][] = $assertion;
         }
@@ -240,7 +246,7 @@
     $fieldsets = $this->xpath('//fieldset');
     $info = $this->getInfo();
     foreach ($fieldsets as $fieldset) {
-      if ($fieldset->legend == $info['group']) {
+      if ($this->asText($fieldset->legend) == $info['name']) {
         return $fieldset;
       }
     }
@@ -268,23 +274,82 @@
    * @return The test is being run from inside a CURL request.
    */
   function inCURL() {
-    return preg_match("/^simpletest\d+/", $_SERVER['HTTP_USER_AGENT']);
+    return isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^simpletest\d+/", $_SERVER['HTTP_USER_AGENT']);
   }
+}
 
-  /*
-   * Drupal 6.
+class SimpleTestMailCaptureTestCase extends DrupalWebTestCase {
+  /**
+   * Implement getInfo().
    */
-
-  function testUserAccess()
-  {
-    $user = $this->drupalCreateUser(array('administer unit tests'));
-    $this->assertTrue(user_access('administer unit tests', $user), 'user_access() check successfull.');
+  public static function getInfo() {
+    return array(
+      'name' => 'SimpleTest e-mail capturing',
+      'description' => 'Test the SimpleTest e-mail capturing logic, the assertMail assertion and the drupalGetMails function.',
+      'group' => 'SimpleTest',
+    );
   }
 
-  function testUserAccessCache()
-  {
-    $user = $this->drupalCreateUser(array('administer nodes'));
-    $this->assertTrue(user_access('administer nodes', $user), 'user_access() cache has been cleaned');
+  /**
+   * Test to see if the wrapper function is executed correctly.
+   */
+  function testMailSend() {
+    // Create an e-mail.
+    $subject = $this->randomString(64);
+    $body = $this->randomString(128);
+    $message = array(
+      'id' => 'drupal_mail_test',
+      'headers' => array('Content-type'=> 'text/html'),
+      'subject' => $subject,
+      'to' => 'foobar@example.com',
+      'body' => $body,
+    );
+
+    // Before we send the e-mail, drupalGetMails should return an empty array.
+    $captured_emails = $this->drupalGetMails();
+    $this->assertEqual(count($captured_emails), 0, t('The captured e-mails queue is empty.'), t('E-mail'));
+
+    // Send the e-mail.
+    $response = drupal_mail_sending_system('simpletest', 'drupal_mail_test')->mail($message);
+
+    // Ensure that there is one e-mail in the captured e-mails array.
+    $captured_emails = $this->drupalGetMails();
+    $this->assertEqual(count($captured_emails), 1, t('One e-mail was captured.'), t('E-mail'));
+
+    // Assert that the e-mail was sent by iterating over the message properties
+    // and ensuring that they are captured intact.
+    foreach($message as $field => $value) {
+      $this->assertMail($field, $value, t('The e-mail was sent and the value for property @field is intact.', array('@field' => $field)), t('E-mail'));
+    }
+
+    // Send additional e-mails so more than one e-mail is captured.
+    for ($index = 0; $index < 5; $index++) {
+      $message = array(
+        'id' => 'drupal_mail_test_' . $index,
+        'headers' => array('Content-type'=> 'text/html'),
+        'subject' => $this->randomString(64),
+        'to' => $this->randomName(32) . '@example.com',
+        'body' => $this->randomString(512),
+      );
+      drupal_mail_sending_system('drupal_mail_test', $index)->mail($message);
+    }
 
+    // There should now be 6 e-mails captured.
+    $captured_emails = $this->drupalGetMails();
+    $this->assertEqual(count($captured_emails), 6, t('All e-mails were captured.'), t('E-mail'));
+
+    // Test different ways of getting filtered e-mails via drupalGetMails().
+    $captured_emails = $this->drupalGetMails(array('id' => 'drupal_mail_test'));
+    $this->assertEqual(count($captured_emails), 1, t('Only one e-mail is returned when filtering by id.'), t('E-mail'));
+    $captured_emails = $this->drupalGetMails(array('id' => 'drupal_mail_test', 'subject' => $subject));
+    $this->assertEqual(count($captured_emails), 1, t('Only one e-mail is returned when filtering by id and subject.'), t('E-mail'));
+    $captured_emails = $this->drupalGetMails(array('id' => 'drupal_mail_test', 'subject' => $subject, 'from' => 'this_was_not_used@example.com'));
+    $this->assertEqual(count($captured_emails), 0, t('No e-mails are returned when querying with an unused from address.'), t('E-mail'));
+
+    // Send the last e-mail again, so we can confirm that the drupalGetMails-filter
+    // correctly returns all e-mails with a given property/value.
+    drupal_mail_sending_system('drupal_mail_test', $index)->mail($message);
+    $captured_emails = $this->drupalGetMails(array('id' => 'drupal_mail_test_4'));
+    $this->assertEqual(count($captured_emails), 2, t('All e-mails with the same id are returned when filtering by id.'), t('E-mail'));
   }
 }
Index: simpletest.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/simpletest/simpletest.js,v
retrieving revision 1.2.4.4
diff -u -r1.2.4.4 simpletest.js
--- simpletest.js	8 Feb 2009 02:35:39 -0000	1.2.4.4
+++ simpletest.js	5 Sep 2009 11:05:01 -0000
@@ -1,20 +1,26 @@
-// $Id: simpletest.js,v 1.2.4.4 2009/02/08 02:35:39 boombatower Exp $
+// $Id$
+// Core: Id: simpletest.js,v 1.11 2009/04/27 20:19:37 webchick Exp
+//(function ($) {
 
 /**
  * Add the cool table collapsing on the testing overview page.
  */
 //Drupal.behaviors.simpleTestMenuCollapse = {
-//  attach: function() {
+//  attach: function (context, settings) {
 Drupal.behaviors.simpleTestMenuCollapse = function() {
     var timeout = null;
     // Adds expand-collapse functionality.
-    $('div.simpletest-image').each(function() {
+    $('div.simpletest-image').each(function () {
+//      direction = settings.simpleTest[$(this).attr('id')].imageDirection;
+//      $(this).html(settings.simpleTest.images[direction]);
       direction = Drupal.settings.simpleTest[$(this).attr('id')].imageDirection;
       $(this).html(Drupal.settings.simpleTest.images[direction]);
     });
 
     // Adds group toggling functionality to arrow images.
-    $('div.simpletest-image').click(function() {
+    $('div.simpletest-image').click(function () {
+//      var trs = $(this).parents('tbody').children('.' + settings.simpleTest[this.id].testClass);
+//      var direction = settings.simpleTest[this.id].imageDirection;
       var trs = $(this).parents('tbody').children('.' + Drupal.settings.simpleTest[this.id].testClass);
       var direction = Drupal.settings.simpleTest[this.id].imageDirection;
       var row = direction ? trs.size() - 1 : 0;
@@ -48,6 +54,8 @@
       rowToggle();
 
       // Toggle the arrow image next to the test group title.
+//      $(this).html(settings.simpleTest.images[(direction ? 0 : 1)]);
+//      settings.simpleTest[this.id].imageDirection = !direction;
       $(this).html(Drupal.settings.simpleTest.images[(direction ? 0 : 1)]);
       Drupal.settings.simpleTest[this.id].imageDirection = !direction;
 
@@ -60,37 +68,38 @@
  * selected/deselected.
  */
 //Drupal.behaviors.simpleTestSelectAll = {
-//  attach: function() {
+//  attach: function (context, settings) {
 Drupal.behaviors.simpleTestSelectAll = function() {
-    $('td.simpletest-select-all').each(function() {
+    $('td.simpletest-select-all').each(function () {
+//      var testCheckboxes = settings.simpleTest['simpletest-test-group-' + $(this).attr('id')].testNames;
       var testCheckboxes = Drupal.settings.simpleTest['simpletest-test-group-' + $(this).attr('id')].testNames;
       var groupCheckbox = $('<input type="checkbox" class="form-checkbox" id="' + $(this).attr('id') + '-select-all" />');
 
       // Each time a single-test checkbox is checked or unchecked, make sure
       // that the associated group checkbox gets the right state too.
-      var updateGroupCheckbox = function() {
+      var updateGroupCheckbox = function () {
         var checkedTests = 0;
         for (var i = 0; i < testCheckboxes.length; i++) {
-          $('#' + testCheckboxes[i]).each(function() {
+          $('#' + testCheckboxes[i]).each(function () {
             if (($(this).attr('checked'))) {
               checkedTests++;
             }
           });
         }
         $(groupCheckbox).attr('checked', (checkedTests == testCheckboxes.length));
-      }
+      };
 
       // Have the single-test checkboxes follow the group checkbox.
-      groupCheckbox.change(function() {
+      groupCheckbox.change(function () {
         var checked = !!($(this).attr('checked'));
         for (var i = 0; i < testCheckboxes.length; i++) {
-          $('#'+ testCheckboxes[i]).attr('checked', checked);
+          $('#' + testCheckboxes[i]).attr('checked', checked);
         }
       });
 
       // Have the group checkbox follow the single-test checkboxes.
       for (var i = 0; i < testCheckboxes.length; i++) {
-        $('#' + testCheckboxes[i]).change(function() {
+        $('#' + testCheckboxes[i]).change(function () {
           updateGroupCheckbox();
         });
       }
@@ -101,3 +110,5 @@
     });
 //  }
 };
+
+//})(jQuery);
Index: simpletest.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/simpletest/simpletest.info,v
retrieving revision 1.4.2.2.2.6
diff -u -r1.4.2.2.2.6 simpletest.info
--- simpletest.info	27 May 2009 19:15:21 -0000	1.4.2.2.2.6
+++ simpletest.info	5 Sep 2009 11:05:01 -0000
@@ -1,7 +1,43 @@
-; $Id: simpletest.info,v 1.4.2.2.2.6 2009/05/27 19:15:21 boombatower Exp $
+; $Id$
+; Core: Id: simpletest.info,v 1.10 2009/08/31 18:30:26 webchick Exp
 name = "SimpleTest"
 description = "Provides a framework for unit and functional testing."
+; package = Core
 package = Development
+; version = VERSION
+; core = 7.x
 core = 6.x
+php = 5 ; Drupal 6
 files[] = simpletest.module
+files[] = simpletest.pages.inc
 files[] = simpletest.install
+files[] = simpletest.test
+files[] = drupal_web_test_case.php
+
+; Drupal 6
+files[] = tests/block.test
+
+; Tests in tests directory.
+; files[] = tests/actions.test
+; files[] = tests/batch.test
+; files[] = tests/bootstrap.test
+; files[] = tests/browser.test
+; files[] = tests/cache.test
+; files[] = tests/common.test
+; files[] = tests/database_test.test
+; files[] = tests/error.test
+; files[] = tests/file.test
+; files[] = tests/filetransfer.test
+; files[] = tests/form.test
+; files[] = tests/graph.test
+; files[] = tests/image.test
+; files[] = tests/lock.test
+; files[] = tests/mail.test
+; files[] = tests/menu.test
+; files[] = tests/module.test
+; files[] = tests/registry.test
+; files[] = tests/schema.test
+; files[] = tests/session.test
+; files[] = tests/theme.test
+; files[] = tests/unicode.test
+; files[] = tests/xmlrpc.test
Index: simpletest.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/simpletest/simpletest.install,v
retrieving revision 1.4.2.3.2.16
diff -u -r1.4.2.3.2.16 simpletest.install
--- simpletest.install	23 Apr 2009 05:39:52 -0000	1.4.2.3.2.16
+++ simpletest.install	5 Sep 2009 11:05:01 -0000
@@ -1,6 +1,7 @@
 <?php
-// $Id: simpletest.install,v 1.4.2.3.2.16 2009/04/23 05:39:52 boombatower Exp $
-// Core: Id: simpletest.install,v 1.17 2009/03/29 23:14:53 webchick Exp
+// $Id$
+// Core: Id: simpletest.install,v 1.26 2009/08/17 19:14:41 webchick Exp
+
 /**
  * @file
  * Backport of Drupal 7 simpletest.install with modifications, see BACKPORT.txt.
@@ -14,6 +15,8 @@
 function simpletest_install() {
   drupal_install_schema('simpletest');
   // Check for files directory.
+//  $path = 'public://simpletest';
+//  if (file_prepare_directory($path, FILE_CREATE_DIRECTORY)) {
   $path = file_directory_path() . '/simpletest';
   if (file_check_directory($path, FILE_CREATE_DIRECTORY)) {
     // Generate binary and text test files.
@@ -35,19 +38,24 @@
     }
 
     // Copy other test files for consistency.
-    $files = file_scan_directory($path, '/(html|image|javascript|php|sql)-.*/');
-    if (count($files) == 0) {
-      $original = drupal_get_path('module', 'simpletest') . '/files';
-      $files = file_scan_directory($original, '/(html|image|javascript|php|sql)-.*/');
+    $original = drupal_get_path('module', 'simpletest') . '/files';
+    $files = file_scan_directory($original, '/(html|image|javascript|php|sql)-.*/');
+
+    // If there are more files in SimpleTest's files directory than the site's
+    // files directory, restore all the files. This situation might occur when
+    // an errant test deletes one or more files from the site's files
+    // directory. It serves a convenience to developers so that they can get
+    // the test files back easily.
+    if (count($files) > count(file_scan_directory($path, '/(html|image|javascript|php|sql)-.*/'))) {
       foreach ($files as $file) {
-//        file_unmanaged_copy($file->filepath, $path);
-        file_unmanaged_copy($file->filename, $path . '/' . $file->basename);
+//        file_unmanaged_copy($file->uri, $path, FILE_EXISTS_REPLACE);
+        copy($file->filename, $path . '/' . $file->basename);
       }
       $generated = TRUE;
     }
 
     if ($generated) {
-      drupal_set_message('Extra test files generated.');
+      drupal_set_message('Extra test files generated/copied.');
     }
   }
 }
@@ -74,7 +82,7 @@
         break;
     }
   }
-  $text = wordwrap($text, $width - 1, "\n", TRUE) ."\n"; // Add \n for symetrical file.
+  $text = wordwrap($text, $width - 1, "\n", TRUE) . "\n"; // Add \n for symetrical file.
 
   // Create filename.
   $path = file_directory_path() . '/simpletest/';
@@ -102,11 +110,23 @@
 function simpletest_uninstall() {
   simpletest_clean_environment();
 
-  variable_del('simpletest_httpauth');
-  variable_del('simpletest_httpauth_username');
-  variable_del('simpletest_httpauth_pass');
-  variable_del('simpletest_devel');
+  // Remove settings variables.
+  variable_del('simpletest_username');
+  variable_del('simpletest_password');
+  variable_del('simpletest_clear_results');
+  variable_del('simpletest_verbose');
+
+  // Uninstall schema.
   drupal_uninstall_schema('simpletest');
+
+  // Remove generated files.
+  $path = file_directory_path() . '/simpletest';
+  $files = file_scan_directory($path, '/.*/');
+  foreach ($files as $file) {
+//    file_unmanaged_delete($file->uri);
+    unlink($file->filename);
+  }
+  rmdir($path);
 }
 
 /**
@@ -117,6 +137,7 @@
   $t = get_t();
 
   $has_curl = function_exists('curl_init');
+  $has_hash = function_exists('hash_hmac');
   $has_domdocument = class_exists('DOMDocument');
 
   $requirements['curl'] = array(
@@ -127,6 +148,14 @@
     $requirements['curl']['severity'] = REQUIREMENT_ERROR;
     $requirements['curl']['description'] = $t('Simpletest could not be installed because the PHP <a href="@curl_url">cURL</a> library is not available.', array('@curl_url' => 'http://php.net/manual/en/curl.setup.php'));
   }
+  $requirements['hash'] = array(
+    'title' => $t('hash'),
+    'value' => $has_hash ? $t('Enabled') : $t('Not found'),
+  );
+  if (!$has_hash) {
+    $requirements['hash']['severity'] = REQUIREMENT_ERROR;
+    $requirements['hash']['description'] = $t('Simpletest could not be installed because the PHP <a href="@hash_url">hash</a> extension is disabled.', array('@hash_url' => 'http://php.net/manual/en/book.hash.php'));
+  }
 
   $requirements['php_domdocument'] = array(
     'title' => $t('PHP DOMDocument class'),
@@ -237,6 +266,13 @@
         'description' => 'Primary Key: Unique simpletest ID used to group test results together. Each time a set of tests
                             are run a new test ID is used.',
       ),
+      'last_prefix' => array(
+        'type' => 'varchar',
+        'length' => 60,
+        'not null' => FALSE,
+        'default' => '',
+        'description' => 'The last database prefix used during testing.',
+      ),
     ),
     'primary key' => array('test_id'),
   );
@@ -245,184 +281,25 @@
 
 /**
  * Upgrade simpletest 5.x-1.x and 6.x-1.x to 6.x-2.1 release.
- * 
- * Note: This does not fix the update_7000 bug introduced in 6.x-2.1 release.
+ *
+ * Provides a basic upgrade path for initial switch to 2.x branch. The update
+ * path will not be continued as there is no data that needs to be updated and
+ * any further releases should simply un-install and install just like Drupal
+ * HEAD development.
  */
-function simpletest_update_6000() {
+function simpletest_update_6200() {
   $ret = array();
-  $schema = array();
 
-  // Check for files directory.
-  $path = file_directory_path() . '/simpletest';
-  if (file_check_directory($path, FILE_CREATE_DIRECTORY)) {
-    // Generate binary and text test files.
-    $generated = FALSE;
-    if (simpletest_get_file_count($path, 'binary') == 0) {
-      $lines = array(64, 1024);
-      foreach ($lines as $line) {
-        simpletest_generate_file('binary', 64, $line, 'binary');
-      }
-      $generated = TRUE;
-    }
-
-    if (simpletest_get_file_count($path, 'text') == 0) {
-      $lines = array(16, 256, 1024, 2048, 20480);
-      foreach ($lines as $line) {
-        simpletest_generate_file('text', 64, $line);
-      }
-      $generated = TRUE;
-    }
-
-    // Copy other test files for consistency.
-    $files = file_scan_directory($path, '(html|image|javascript|php|sql)-.*');
-    if (count($files) == 0) {
-      $original = drupal_get_path('module', 'simpletest') . '/files';
-      $files = file_scan_directory($original, '(html|image|javascript|php|sql)-.*');
-      foreach ($files as $file) {
-        file_copy($file->filename, $path . '/' . $file->basename);
-      }
-      $generated = TRUE;
-    }
-
-    if ($generated) {
-      $ret[] = array('success' => TRUE, 'query' => 'Extra test files generated.');
-    }
+  // Drop any existing SimpleTest tables.
+  if (db_table_exists('simpletest')) {
+    db_drop_table($ret, 'simpletest');
   }
-
-  // Install 6.x-2.1 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 on which the function is called.'),
-      ),
-      'file' => array(
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-        'default' => '',
-        'description' => t('Name of the file where the function is called.'),
-      ),
-    ),
-    'primary key' => array('message_id'),
-    'indexes' => array(
-      'reporter' => array('test_class', 'message_id'),
-    ),
-  );
-  $schema['simpletest_test_id'] = array(
-    'description' => t('Stores simpletest test IDs, used to auto-incrament the test ID so that a fresh test ID is used.'),
-    'fields' => array(
-      'test_id'  => array(
-        'type' => 'serial',
-        'not null' => TRUE,
-        'description' => t('Primary Key: Unique simpletest ID used to group test results together. Each time a set of tests
-                            are run a new test ID is used.')
-      )
-    ),
-    'primary key' => array('test_id')
-  );
-
-  // Install non-existent tables.
-  db_create_table($ret, 'simpletest', $schema['simpletest']);
-
-  // Drop table to make sure field 'test_id' and primary key is fixed (6.x-2.1 bug fixed in 6.x-2.2).
   if (db_table_exists('simpletest_test_id')) {
     db_drop_table($ret, 'simpletest_test_id');
   }
-  db_create_table($ret, 'simpletest_test_id', $schema['simpletest_test_id']);
-
-  return $ret;
-}
-
-/**
- * Change message field to type text.
- */
-function simpletest_update_6001() {
-  $ret = array();
-  $schema = array();
-
-  $schema = array(
-    'type' => 'text',
-    'not null' => TRUE
-  );
-
-  // Clear test results to prevent odd results.
-  db_query('DELETE FROM {simpletest}');
-
-  db_drop_field($ret, 'simpletest', 'message');
-  db_add_field($ret, 'simpletest', 'message', $schema);
-
-  return $ret;
-}
-
-/**
- * Change caller field to function.
- */
-function simpletest_update_6002() {
-  $ret = array();
-  $schema = array();
-
-  $schema = array(
-    'type' => 'varchar',
-    'length' => 255,
-    'not null' => TRUE,
-    'default' => ''
-  );
-
-  // Clear test results to prevent odd results.
-  db_query('DELETE FROM {simpletest}');
 
-  db_drop_field($ret, 'simpletest', 'caller');
-  db_add_field($ret, 'simpletest', 'function', $schema);
+  // Install most recent schema and files.
+  simpletest_install();
 
   return $ret;
 }
Index: simpletest.pages.inc
===================================================================
RCS file: simpletest.pages.inc
diff -N simpletest.pages.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ simpletest.pages.inc	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,461 @@
+<?php
+// $Id: simpletest.pages.inc,v 1.15 2009/08/22 14:34:21 webchick Exp $
+
+/**
+ * @file
+ * Page callbacks for simpletest module.
+ */
+
+/**
+ * List tests arranged in groups that can be selected and run.
+ */
+function simpletest_test_form() {
+  $form = array();
+
+  $form['tests'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Tests'),
+    '#description' => t('Select the test(s) or test group(s) you would like to run, and click <em>Run tests</em>.'),
+  );
+
+  $form['tests']['table'] = array(
+    '#theme' => 'simpletest_test_table',
+  );
+
+  // Generate the list of tests arranged by group.
+  $groups = simpletest_test_get_all();
+  foreach ($groups as $group => $tests) {
+    $form['tests']['table'][$group] = array(
+      '#collapsed' => TRUE,
+    );
+
+    foreach ($tests as $class => $info) {
+      $form['tests']['table'][$group][$class] = array(
+        '#type' => 'checkbox',
+        '#title' => $info['name'],
+        '#description' => $info['description'],
+      );
+    }
+  }
+
+ // Operation buttons.
+ $form['tests']['op'] = array(
+   '#type' => 'submit',
+   '#value' => t('Run tests'),
+ );
+ $form['clean'] = 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. This is intended for developers when creating tests.'),
+ );
+ $form['clean']['op'] = array(
+   '#type' => 'submit',
+   '#value' => t('Clean environment'),
+   '#submit' => array('simpletest_clean_environment'),
+ );
+
+  return $form;
+}
+
+/**
+ * Theme the test list generated by simpletest_test_form() into a table.
+ *
+ * @param $table Form array that represent a table.
+ * @return HTML output.
+ */
+function theme_simpletest_test_table($table) {
+  drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css');
+  drupal_add_js(drupal_get_path('module', 'simpletest') . '/simpletest.js');
+
+  // Create header for test selection table.
+  $header = array(
+    theme('table_select_header_cell'),
+//     array('data' => t('Test'), 'class' => array('simpletest_test')),
+    array('data' => t('Test'), 'class' => 'simpletest_test'),
+//     array('data' => t('Description'), 'class' => array('simpletest_description')),
+    array('data' => t('Description'), 'class' => 'simpletest_description'),
+  );
+
+  // Define the images used to expand/collapse the test groups.
+  $js = array(
+    'images' => array(
+      theme('image', 'misc/menu-collapsed.png', 'Expand', 'Expand'),
+      theme('image', 'misc/menu-expanded.png', 'Collapsed', 'Collapsed'),
+    ),
+  );
+
+  // Cycle through each test group and create a row.
+  $rows = array();
+  foreach (element_children($table) as $key) {
+    $element = &$table[$key];
+    $row = array();
+
+    // Make the class name safe for output on the page by replacing all
+    // non-word/decimal characters with a dash (-).
+    $test_class = strtolower(trim(preg_replace("/[^\w\d]/", "-", $key)));
+
+    // Select the right "expand"/"collapse" image, depending on whether the
+    // category is expanded (at least one test selected) or not.
+    $collapsed = !empty($element['#collapsed']);
+    $image_index = $collapsed ? 0 : 1;
+
+    // Place-holder for checkboxes to select group of tests.
+//     $row[] = array('id' => $test_class, 'class' => array('simpletest-select-all'));
+    $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 . '"></div>&nbsp;' .
+                 '<label for="' . $test_class . '-select-all" class="simpletest-group-label">' . $key . '</label>',
+      'style' => 'font-weight: bold;'
+    );
+
+    $row[] = '&nbsp;';
+
+    $rows[] = array('data' => $row, 'class' => array('simpletest-group'));
+
+    // Add individual tests to group.
+    $current_js = array(
+      'testClass' => $test_class . '-test',
+      'testNames' => array(),
+      'imageDirection' => $image_index,
+      'clickActive' => FALSE,
+    );
+
+    // Sorting $element by children's #title attribute instead of by class name.
+    uasort($element, '_simpletest_sort_by_title');
+
+    // Cycle through each test within the current group.
+    foreach (element_children($element) as $test_name) {
+      $test = $element[$test_name];
+      $row = array();
+
+      $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, 'class' => array($test_class . '-test', ($collapsed ? 'js-hide' : '')));
+      $rows[] = array('data' => $row, 'class' => $test_class . '-test ' . ($collapsed ? 'js-hide' : ''));
+    }
+    $js['simpletest-test-group-' . $test_class] = $current_js;
+    unset($table[$key]);
+  }
+
+  // Add js array of settings.
+  drupal_add_js(array('simpleTest' => $js), 'setting');
+
+  if (empty($rows)) {
+    return '<strong>' . t('No tests to display.') . '</strong>';
+  }
+  else {
+    return theme('table', $header, $rows, array('id' => 'simpletest-form-table'));
+  }
+}
+
+/**
+ * Sort element by title instead of by class name.
+ */
+function _simpletest_sort_by_title($a, $b) {
+  // This is for parts of $element that are not an array.
+  if (!isset($a['#title']) || !isset($b['#title'])) {
+    return 1;
+  }
+
+  return strcasecmp($a['#title'], $b['#title']);
+}
+
+/**
+ * Run selected tests.
+ */
+function simpletest_test_form_submit($form, &$form_state) {
+  // Get list of tests.
+  $tests_list = array();
+  foreach ($form_state['values'] as $class_name => $value) {
+    if (class_exists($class_name) && $value === 1) {
+      $tests_list[] = $class_name;
+    }
+  }
+  if (count($tests_list) > 0 ) {
+    simpletest_run_tests($tests_list, 'drupal');
+  }
+  else {
+    drupal_set_message(t('No test(s) selected.'), 'error');
+  }
+}
+
+/**
+ * Test results form for $test_id.
+ */
+function simpletest_result_form(&$form_state, $test_id) {
+  $form = array();
+
+  // Make sure there are test results to display and a re-run is not being performed.
+  $results = array();
+  if (is_numeric($test_id) && !$results = simpletest_result_get($test_id)) {
+    drupal_set_message(t('No test results to display.'), 'error');
+    drupal_goto('admin/config/development/testing');
+    return $form;
+  }
+
+  // Load all classes and include CSS.
+  drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css');
+
+  // Keep track of which test cases passed or failed.
+  $filter = array(
+    'pass' => array(),
+    'fail' => array(),
+  );
+
+  // Summary result fieldset.
+  $form['result'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Results'),
+  );
+  $form['result']['summary'] = $summary = array(
+    '#theme' => 'simpletest_result_summary',
+    '#pass' => 0,
+    '#fail' => 0,
+    '#exception' => 0,
+    '#debug' => 0,
+  );
+
+  // Cycle through each test group.
+  $header = array(t('Message'), t('Group'), t('Filename'), t('Line'), t('Function'), array('colspan' => 2, 'data' => t('Status')));
+  $form['result']['results'] = array();
+  foreach ($results as $group => $assertions) {
+    // Create group fieldset with summary information.
+    $info = call_user_func(array($group, 'getInfo'));
+    $form['result']['results'][$group] = array(
+      '#type' => 'fieldset',
+      '#title' => $info['name'],
+      '#description' => $info['description'],
+      '#collapsible' => TRUE,
+    );
+    $form['result']['results'][$group]['summary'] = $summary;
+    $group_summary = &$form['result']['results'][$group]['summary'];
+
+    // Create table of assertions for the group.
+    $rows = array();
+    foreach ($assertions as $assertion) {
+      $row = array();
+      $row[] = $assertion->message;
+      $row[] = $assertion->message_group;
+      $row[] = basename($assertion->file);
+      $row[] = $assertion->line;
+      $row[] = $assertion->function;
+      $row[] = simpletest_result_status_image($assertion->status);
+
+      $class = 'simpletest-' . $assertion->status;
+      if ($assertion->message_group == 'Debug') {
+        $class = 'simpletest-debug';
+      }
+      $rows[] = array('data' => $row, 'class' => array($class));
+
+      $group_summary['#' . $assertion->status]++;
+      $form['result']['summary']['#' . $assertion->status]++;
+    }
+    $form['result']['results'][$group]['table'] = array(
+      '#theme' => 'table',
+      '#header' => $header,
+      '#rows' => $rows,
+    );
+
+    // Set summary information.
+    $group_summary['#ok'] = $group_summary['#fail'] + $group_summary['#exception'] == 0;
+    $form['result']['results'][$group]['#collapsed'] = $group_summary['#ok'] && !$group_summary['#debug'];
+
+    // Store test group (class) as for use in filter.
+    $filter[$group_summary['#ok'] ? 'pass' : 'fail'][] = $group;
+  }
+
+  // Overal summary status.
+  $form['result']['summary']['#ok'] = $form['result']['summary']['#fail'] + $form['result']['summary']['#exception'] == 0;
+
+  // Actions.
+  $form['#action'] = url('admin/config/development/testing/results/re-run');
+  $form['action'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Actions'),
+    '#attributes' => array('class' => array('container-inline')),
+    '#weight' => -11,
+  );
+
+  $form['action']['filter'] = array(
+    '#type' => 'select',
+    '#title' => 'Filter',
+    '#options' => array(
+      'all' => t('All (@count)', array('@count' => count($filter['pass']) + count($filter['fail']))),
+      'pass' => t('Pass (@count)', array('@count' => count($filter['pass']))),
+      'fail' => t('Fail (@count)', array('@count' => count($filter['fail']))),
+    ),
+  );
+  $form['action']['filter']['#default_value'] = ($filter['fail'] ? 'fail' : 'all');
+
+  // Catagorized test classes for to be used with selected filter value.
+  $form['action']['filter_pass'] = array(
+    '#type' => 'hidden',
+    '#default_value' => implode(',', $filter['pass']),
+  );
+  $form['action']['filter_fail'] = array(
+    '#type' => 'hidden',
+    '#default_value' => implode(',', $filter['fail']),
+  );
+
+  $form['action']['op'] = array(
+    '#type' => 'submit',
+    '#value' => t('Run tests'),
+  );
+
+  $form['action']['return'] = array(
+    '#markup' => l(t('Return to list'), 'admin/config/development/testing'),
+  );
+
+  if (is_numeric($test_id)) {
+    simpletest_clean_results_table($test_id);
+  }
+
+  return $form;
+}
+
+/**
+ * Re-run the tests that match the filter.
+ */
+function simpletest_result_form_submit($form, &$form_state) {
+  $pass = $form_state['values']['filter_pass'] ? explode(',', $form_state['values']['filter_pass']) : array();
+  $fail = $form_state['values']['filter_fail'] ? explode(',', $form_state['values']['filter_fail']) : array();
+
+  if ($form_state['values']['filter'] == 'all') {
+    $classes = array_merge($pass, $fail);
+  }
+  else if ($form_state['values']['filter'] == 'pass') {
+    $classes = $pass;
+  }
+  else {
+    $classes = $fail;
+  }
+
+  if (!$classes) {
+    $form_state['redirect'] = 'admin/config/development/testing';
+    return;
+  }
+
+  $form_state_execute = array('values' => array());
+  foreach ($classes as $class) {
+    $form_state_execute['values'][$class] = 1;
+  }
+
+  simpletest_test_form_submit(array(), $form_state_execute);
+}
+
+/**
+ * Add wrapper div with class based on summary status.
+ *
+ * @return HTML output.
+ */
+function theme_simpletest_result_summary($form) {
+  return '<div class="simpletest-' . ($form['#ok'] ? 'pass' : 'fail') . '">' . _simpletest_format_summary_line($form) . '</div>';
+}
+
+/**
+ * Get test results for $test_id.
+ *
+ * @param $test_id The test_id to retrieve results of.
+ * @return Array of results grouped by test_class.
+ */
+function simpletest_result_get($test_id) {
+  $results = db_select('simpletest')
+    ->fields('simpletest')
+    ->condition('test_id', $test_id)
+    ->orderBy('test_class')
+    ->orderBy('message_id')
+    ->execute();
+
+  $test_results = array();
+  foreach ($results as $result) {
+    if (!isset($test_results[$result->test_class])) {
+      $test_results[$result->test_class] = array();
+    }
+    $test_results[$result->test_class][] = $result;
+  }
+  return $test_results;
+}
+
+/**
+ * Get the appropriate image for the status.
+ *
+ * @param $status Status string, either: pass, fail, exception.
+ * @return HTML image or false.
+ */
+function simpletest_result_status_image($status) {
+  // $map does not use drupal_static() as its value never changes.
+  static $map;
+
+  if (!isset($map)) {
+    $map = array(
+      'pass' => theme('image', 'misc/watchdog-ok.png'),
+      'fail' => theme('image', 'misc/watchdog-error.png'),
+      'exception' => theme('image', 'misc/watchdog-warning.png'),
+      'debug' => theme('image', 'misc/watchdog-warning.png'),
+    );
+  }
+  if (isset($map[$status])) {
+    return $map[$status];
+  }
+  return FALSE;
+}
+
+/**
+ * Provides settings form for SimpleTest variables.
+ */
+function simpletest_settings_form(&$form_state) {
+  $form = array();
+
+  $form['general'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('General'),
+  );
+  $form['general']['simpletest_clear_results'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Clear results after each complete test suite run'),
+    '#description' => t('By default SimpleTest will clear the results after they have been viewed on the results page, but in some cases it may be useful to leave the results in the database. The results can then be viewed at <em>admin/config/development/testing/[test_id]</em>. The test ID can be found in the database, simpletest table, or kept track of when viewing the results the first time. Additionally, some modules may provide more analaysis or features that require this setting to be disabled.'),
+    '#default_value' => variable_get('simpletest_clear_results', TRUE),
+  );
+  $form['general']['simpletest_verbose'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Provide verbose information when running tests'),
+    '#description' => t('The verbose data will be printed along with the standard assertions and is useful for debugging. The verbose data will be erased between each test suite run. The verbose data output is very detailed and should only be used when debugging.'),
+    '#default_value' => variable_get('simpletest_verbose', FALSE),
+  );
+
+  $form['httpauth'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('HTTP authentication credentials'),
+    '#description' => t('HTTP auth settings to be used by the SimpleTest browser during testing. Useful when the site requires basic HTTP authentication.'),
+  );
+  $form['httpauth']['simpletest_username'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Username'),
+    '#default_value' => variable_get('simpletest_username', ''),
+  );
+  $form['httpauth']['simpletest_password'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Password'),
+    '#default_value' => variable_get('simpletest_password', ''),
+  );
+
+  return system_settings_form($form);
+}
