=== modified file 'modules/simpletest/drupal_web_test_case.php' --- modules/simpletest/drupal_web_test_case.php 2008-06-06 10:36:43 +0000 +++ modules/simpletest/drupal_web_test_case.php 2008-06-15 18:50:20 +0000 @@ -4,7 +4,7 @@ /** * Test case for typical Drupal tests. */ -class DrupalWebTestCase extends UnitTestCase { +class DrupalWebTestCase { protected $_logged_in = FALSE; protected $_content; protected $plain_text; @@ -24,14 +24,129 @@ class DrupalWebTestCase extends UnitTest * * @param string $label Name of the test to be used by the SimpleTest library. */ - function __construct($label = NULL) { - if (!$label) { - if (method_exists($this, 'getInfo')) { - $info = $this->getInfo(); - $label = $info['name']; + function __construct($test_id = NULL) { + if (method_exists($this, 'getInfo')) { + $info = $this->getInfo(); + $label = $info['name']; + } + $this->test_id = $test_id; + } + + /** + * This function stores the assert. Do not call directly. + * + * @param $status + * Can be 'pass', 'fail', 'exception'. TRUE is a synonym for 'pass', FALSE + * for 'fail'. + * @param $message + * The message string. + * @param $group + * WHich group this assert belongs to. + * @param $custom_caller + * By default, the assert comes from a function which names start with + * 'test'. Instead, you can specify where this assert originates from + * by passing in an associative array as $custom_caller. Key 'file' is + * the name of the source file, 'line' is the line number and 'function' + * is the caller function itself. + */ + protected function _assert($status, $message = '', $group = 'Other', $custom_caller = NULL) { + global $db_prefix; + if (is_bool($status)) { + $status = $status ? 'pass' : 'fail'; + } + if (!isset($custom_caller)) { + $callers = debug_backtrace(); + while ((list($index, $caller) = each($callers)) && substr($caller['function'], 0, 4) != 'test'); + $function = $callers[$index - 1]; + } + else { + $function = $custom_caller; + } + $current_db_prefix = $db_prefix; + $db_prefix = $this->db_prefix_original; + db_query("INSERT INTO {simpletest} (test_id, test_class, status, message, message_group, caller, line, file) VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s')", $this->test_id, get_class($this), $status, $message, $group, $function['function'], $function['line'], $function['file']); + $db_prefix = $current_db_prefix; + return $status; + } + + protected function assertTrue($value, $message = '', $group = 'Other') { + return $this->_assert((bool) $value, $message ? $message : t('%value is TRUE', array('%value' => $value)), $group); + } + + protected function assertFalse($value, $message = '', $group = 'Other') { + return $this->_assert(!$value, $message ? $message : t('%value is FALSE', array('%value' => $value)), $group); + } + + protected function assertNull($value, $message = '', $group = 'Other') { + return $this->_assert(!isset($value), $message ? $message : t('%value is NULL', array('%value' => $value)), $group); + } + + protected function assertNotNull($value, $message = '', $group = 'Other') { + return $this->_assert(isset($value), $message ? $message : t('%value is not NULL', array('%value' => $value)), $group); + } + + protected function assertEqual($first, $second, $message = '', $group = 'Other') { + return $this->_assert($first == $second, $message ? $message : t('%first is equal to %second', array('%first' => $first, '%second' => $second)), $group); + } + + protected function assertNotEqual($first, $second, $message = '', $group = 'Other') { + return $this->_assert($first != $second, $message ? $message : t('%first is not equal to %second', array('%first' => $first, '%second' => $second)), $group); + } + + protected function assertIdentical($first, $second, $message = '', $group = 'Other') { + return $this->_assert($first === $second, $message ? $message : t('%first is identical to %second', array('%first' => $first, '%second' => $second)), $group); + } + + protected function assertNotIdentical($first, $second, $message = '', $group = 'Other') { + return $this->_assert($first !== $second, $message ? $message : t('%first is not identical to %second', array('%first' => $first, '%second' => $second)), $group); + } + + protected function pass($message = NULL, $group = 'Other') { + return $this->_assert(TRUE, $message, $group); + } + + protected function fail($message = NULL, $group = 'Other') { + return $this->_assert(FALSE, $message, $group); + } + + protected function error($message = '', $group = 'Other', $custom_caller = NULL) { + return $this->_assert('exception', $message, $group, $custom_caller); + } + + function run() { + set_error_handler(array($this, 'errorHandler')); + $this->setUp(); + $methods = array(); + foreach (get_class_methods(get_class($this)) as $method) { + if (strtolower(substr($method, 0, 4)) == 'test') { + $this->$method(); } } - parent::__construct($label); + $this->tearDown(); + restore_error_handler(); + } + + function errorHandler($severity, $message, $file = NULL, $line = NULL) { + $severity = $severity & error_reporting(); + if ($severity) { + $error_map = array( + E_STRICT => 'Run-time notice', + E_WARNING => 'Warning', + E_NOTICE => 'Notice', + E_CORE_ERROR => 'Core error', + E_CORE_WARNING => 'Core warning', + E_USER_ERROR => 'User error', + E_USER_WARNING => 'User warning', + E_USER_NOTICE => 'User notice', + E_RECOVERABLE_ERROR => 'Recoverable error', + ); + $this->error($message, $error_map[$severity], array( + 'function' => '', + 'line' => $line, + 'file' => $file, + )); + } + return TRUE; } /** @@ -296,13 +411,13 @@ class DrupalWebTestCase extends UnitTest } /** - * Generates a random database prefix, runs the install scripts on the - * prefixed database and enable the specified modules. After installation - * many caches are flushed and the internal browser is setup so that the - * page requests will run on the new prefix. A temporary files directory + * Generates a random database prefix, runs the install scripts on the + * prefixed database and enable the specified modules. After installation + * many caches are flushed and the internal browser is setup so that the + * page requests will run on the new prefix. A temporary files directory * is created with the same name as the database prefix. * - * @param ... + * @param ... * List of modules to enable for the duration of the test. */ function setUp() { @@ -341,8 +456,6 @@ class DrupalWebTestCase extends UnitTest $this->original_file_directory = file_directory_path(); variable_set('file_directory_path', file_directory_path() . '/' . $db_prefix); file_check_directory(file_directory_path(), TRUE); // Create the files directory. - - parent::setUp(); } /** @@ -395,21 +508,8 @@ class DrupalWebTestCase extends UnitTest // Close the CURL handler. $this->curlClose(); + restore_error_handler(); } - parent::tearDown(); - } - - /** - * Set necessary reporter info. - */ - function run(&$reporter) { - $arr = array('class' => get_class($this)); - if (method_exists($this, 'getInfo')) { - $arr = array_merge($arr, $this->getInfo()); - } - $reporter->test_info_stack[] = $arr; - parent::run($reporter); - array_pop($reporter->test_info_stack); } /** @@ -558,7 +658,7 @@ class DrupalWebTestCase extends UnitTest } $out = $this->curlExec(array(CURLOPT_URL => $action, CURLOPT_POST => TRUE, CURLOPT_POSTFIELDS => $post)); // Ensure that any changes to variables in the other thread are picked up. - $this->refreshVariables(); + $this->refreshVariables(); return $out; } } @@ -576,15 +676,15 @@ class DrupalWebTestCase extends UnitTest * exist and attempt to create POST data in the correct manner for the particular * field type. * - * @param array $post + * @param array $post * Reference to array of post values. - * @param array $edit + * @param array $edit * Reference to array of edit values to be checked against the form. - * @param string $submit + * @param string $submit * Form submit button value. - * @param array $form + * @param array $form * Array of form elements. - * @return boolean + * @return boolean * Submit value matches a valid submit input in the form. */ protected function handleForm(&$post, &$edit, &$upload, $submit, $form) { === modified file 'modules/simpletest/simpletest.install' --- modules/simpletest/simpletest.install 2008-05-10 06:55:09 +0000 +++ modules/simpletest/simpletest.install 2008-06-15 18:06:32 +0000 @@ -5,6 +5,7 @@ * Implementation of hook_install(). */ function simpletest_install() { + drupal_install_schema('simpletest'); // Check for files directory. $path = file_directory_path() . '/simpletest'; if (file_check_directory($path, FILE_CREATE_DIRECTORY)) { @@ -95,6 +96,7 @@ function simpletest_uninstall() { variable_del('simpletest_httpauth_username'); variable_del('simpletest_httpauth_pass'); variable_del('simpletest_devel'); + drupal_uninstall_schema('simpletest'); } /** @@ -133,3 +135,85 @@ function simpletest_requirements($phase) return $requirements; } + +function simpletest_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 of the caller.'), + ), + 'file' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + 'description' => t('Name of the file where the caller is.'), + ), + ), + 'primary key' => array('message_id'), + 'indexes' => array( + 'reporter' => array('test_class, message_id'), + ), + ); + $schema['simpletest_test_id'] = array( + 'description' => t('Stores simpletest test IDs.'), + 'fields' => array( + 'message_id' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'description' => t('Primary Key: Unique simpletest ID.'), + ), + ), + 'primary key' => array('message_id'), + ); +} === modified file 'modules/simpletest/simpletest.module' --- modules/simpletest/simpletest.module 2008-05-10 07:46:22 +0000 +++ modules/simpletest/simpletest.module 2008-06-15 19:16:43 +0000 @@ -22,7 +22,8 @@ function simpletest_help($path, $arg) { function simpletest_menu() { $items['admin/build/testing'] = array( 'title' => 'Testing', - 'page callback' => 'simpletest_entrypoint', + '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'), ); @@ -50,104 +51,122 @@ function simpletest_perm() { */ function simpletest_theme() { return array( - 'simpletest_overview_form' => array( + 'simpletest_test_form' => array( + 'arguments' => array('form' => NULL) + ), + 'simpletest_result_summary' => array( 'arguments' => array('form' => NULL) ), ); } /** - * Try to load the simepletest - * @return boolean TRUE if the load succeeded + * Menu callback for both running tests and listing possible tests */ -function simpletest_load() { - global $user; - static $loaded; - if (!$loaded) { - $loaded = TRUE; - if ($user->uid != 1) { - drupal_set_message(t('It is strongly suggested to run the tests with the first user!')); +function simpletest_test_form() { + $form = array(); + $uncategorized_tests = simpletest_get_all_tests(); + $tests = simpletest_categorize_tests($uncategorized_tests); + if (isset($_SESSION['test_id'])) { + $results = db_query("SELECT * FROM {simpletest} WHERE test_id = %d ORDER BY test_class, message_id", $_SESSION['test_id']); + unset($_SESSION['test_id']); + $summary = array( + '#theme' => 'simpletest_result_summary', + '#pass' => 0, + '#fail' => 0, + '#exception' => 0, + '#weight' => -10, + ); + $form['summary'] = $summary; + $group_summary = array(); + $map = array( + 'pass' => array('text' => t('Pass'), 'image' => theme('image', 'misc/watchdog-ok.png')), + 'fail' => array('text' => t('Fail'), 'image' => theme('image', 'misc/watchdog-error.png')), + 'exception' => array('text' => t('Exception'), 'image' => theme('image', 'misc/watchdog-warning.png')), + ); + $header = array(t('Message'), t('Group'), t('Filename'), t('Line #'), t('Called function'), array('colspan' => 2, 'data' => t('Status'))); + while ($result = db_fetch_object($results)) { + $class = $result->test_class; + $info = $uncategorized_tests[$class]->getInfo(); + $group = $info['group']; + if (!isset($group_summary[$group])) { + $group_summary[$group] = $summary; + } + $element = &$form['results'][$group][$class]; + if (!isset($element)) { + $element['summary'] = $summary; + } + $status = $result->status; + // This reporter can only handle pass, fail and exception. + if (isset($map[$status])) { + $element['#title'] = $info['name']; + $status_index = '#'. $status; + $form['summary'][$status_index]++; + $group_summary[$group][$status_index]++; + $element['summary'][$status_index]++; + $element['result_table']['#rows'][] = array( + 'data' => array( + $result->message, + $result->message_group, + basename($result->file), + $result->line, + $result->caller, + $map[$status]['text'], + $map[$status]['image'], + ), + 'class' => "simpletest-$status", + ); + } + unset($element); } - $path = drupal_get_path('module', 'simpletest') . '/'; - foreach (array('simpletest.php', 'unit_tester.php', 'reporter.php', 'drupal_reporter.php', 'drupal_web_test_case.php', 'drupal_test_suite.php') as $file) { - require_once($path . $file); + $all_ok = TRUE; + foreach ($form['results'] as $group => &$elements) { + $group_ok = TRUE; + foreach ($elements as $class => &$element) { + $info = $uncategorized_tests[$class]->getInfo(); + $ok = $element['summary']['#fail'] + $element['summary']['#exception'] == 0; + $element += array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => $ok, + '#description' => $info['description'], + ); + $element['result_table']['#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; } -} - -/** - * Menu callback for both running tests and listing possible tests - */ -function simpletest_entrypoint() { - simpletest_load(); - drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css', 'module'); - drupal_add_js(drupal_get_path('module', 'simpletest') . '/simpletest.js', 'module'); - $output = drupal_get_form('simpletest_overview_form'); - - if (simpletest_running_output()) { - return simpletest_running_output() . $output; - } - else { - return $output; - } -} - -function simpletest_running_output($output = NULL) { - static $o; - if ($output != NULL) { - $o = $output; - } - return $o; -} - -/** - * Form callback; make the form to run tests - */ -function simpletest_overview_form() { - $output = array( - '#theme' => 'simpletest_overview_form' - ); - - $total_test = &simpletest_get_total_test(); - - $test_instances = $total_test->getTestInstances(); - uasort($test_instances, 'simpletest_compare_instances'); - - foreach ($test_instances as $group_test) { - $group = array(); - $tests = $group_test->getTestInstances(); - $group_class = str_replace(' ', '-', strtolower($group_test->getLabel())); - $group['tests'] = array( - '#type' => 'fieldset', - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#title' => 'Tests', - '#attributes' => array('class' => $group_class), - ); - foreach ($tests as $test) { + foreach ($tests as $group_name => $test_group) { + foreach ($test_group as $test) { $test_info = $test->getInfo(); - $group['tests'][get_class($test)] = array( + $test_class = get_class($test); + $form['tests'][$group_name][$test_class] = array( '#type' => 'checkbox', '#title' => $test_info['name'], '#default_value' => 0, '#description' => $test_info['description'], ); } - $output[] = $group + array( - '#type' => 'fieldset', - '#collapsible' => FALSE, - '#title' => $group_test->getLabel(), - '#attributes' => array('class' => 'all-tests'), - ); } - $output['run'] = array( + $form['run'] = array( '#type' => 'fieldset', '#collapsible' => FALSE, '#collapsed' => FALSE, '#title' => t('Run tests'), ); - $output['run']['running_options'] = array( + $form['run']['running_options'] = array( '#type' => 'radios', '#default_value' => 'selected_tests', '#options' => array( @@ -155,25 +174,23 @@ function simpletest_overview_form() { 'selected_tests' => t('Run selected tests'), ), ); - $output['run']['op'] = array( + $form['run']['op'] = array( '#type' => 'submit', '#value' => t('Run tests'), - '#submit' => array('simpletest_run_selected_tests') ); - - $output['reset'] = array( + $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.') ); - $output['reset']['op'] = array( + $form['reset']['op'] = array( '#type' => 'submit', '#value' => t('Clean environment'), '#submit' => array('simpletest_clean_environment') ); - return $output; + return $form; } /** @@ -181,7 +198,9 @@ function simpletest_overview_form() { * * @ingroup themeable */ -function theme_simpletest_overview_form($form) { +function theme_simpletest_test_form($form) { + drupal_add_css(drupal_get_path('module', 'simpletest') .'/simpletest.css', 'module'); + drupal_add_js(drupal_get_path('module', 'simpletest') .'/simpletest.js', 'module'); $header = array( array('data' => t('Run'), 'class' => 'simpletest_run checkbox'), array('data' => t('Test'), 'class' => 'simpletest_test'), @@ -196,73 +215,73 @@ function theme_simpletest_overview_form( // Go through each test group and create a row: $rows = array(); - foreach (element_children($form) as $gid) { - if (isset($form[$gid]['tests'])) { - $element = &$form[$gid]; - $test_class = strtolower(trim(preg_replace("/[^\w\d]/", "-",$element["#title"]))); + foreach (element_children($form['tests']) as $key) { + $element = &$form['tests'][$key]; + $test_class = strtolower(trim(preg_replace("/[^\w\d]/", "-", $key))); + $row = array(); + $row[] = array('id' => $test_class, 'class' => 'simpletest-select-all'); + $row[] = array( + 'data' => '
'. $js['images'][0] .'
 ', + 'style' => 'font-weight: bold;' + ); + $row[] = isset($element['#description']) ? $element['#description'] : ' '; + $rows[] = array('data' => $row, 'class' => 'simpletest-group'); - $row = array(); - $row[] = array('id' => $test_class, 'class' => 'simpletest-select-all'); - $row[] = array( - 'data' => '
' . $js['images'][0] . '
 ', - ); - $row[] = $element['#description']; - $rows[] = array('data' => $row, 'class' => 'simpletest-group'); - $current_js = array('testClass' => $test_class . '-test', 'testNames' => array(), 'imageDirection' => 0, 'clickActive' => FALSE); - - // Go through each test in the group and create table rows setting them to invisible: - foreach (element_children($element['tests']) as $test_name) { - $current_js['testNames'][] = 'edit-' . $test_name; - $test = $element['tests'][$test_name]; - foreach (array('title', 'description') as $key) { - $$key = $test['#' . $key]; - unset($test['#' . $key]); - } - $test['#name'] = $test_name; - $themed_test = drupal_render($test); - $row = array(); - $row[] = $themed_test; - $row[] = theme('indentation', 1) . ''; - $row[] = '
' . $description . '
'; - $rows[] = array('data' => $row, 'style' => 'display: none;', 'class' => $test_class . '-test'); + $current_js = array('testClass' => $test_class .'-test', 'testNames' => array(), 'imageDirection' => 0, 'clickActive' => FALSE); + foreach (element_children($element) as $test_name) { + $current_js['testNames'][] = 'edit-'. $test_name; + $test = $element[$test_name]; + foreach (array('title', 'description') as $key) { + $$key = $test['#'. $key]; + unset($test['#'. $key]); } - $js['simpletest-test-group-' . $test_class] = $current_js; - unset($form[$gid]); // Remove test group from form. + $test['#name'] = $test_name; + $themed_test = drupal_render($test); + $row = array(); + $row[] = $themed_test; + $row[] = theme('indentation', 1) .''; + $row[] = '
'. $description .'
'; + $rows[] = array('data' => $row, 'style' => 'display: none;', 'class' => $test_class .'-test'); } + $js['simpletest-test-group-'. $test_class] = $current_js; } + unset($form['tests']); drupal_add_js(array('simpleTest' => $js), 'setting'); - // Output test groups: $output = ''; + if (isset($form['results'])) { + $output .= drupal_render($form['summary']); + $output .= drupal_render($form['results']); + } if (count($rows)) { $output .= theme('table', $header, $rows, array('id' => 'simpletest-form-table')); } - // Output the rest of the form, excluded test groups which have been removed: $output .= drupal_render($form); return $output; } -/** - * Compare two test instance objects for use in sorting. - */ -function simpletest_compare_instances(&$a, &$b) { - if (substr_compare($a->_label, $b->_label, 0) > 0) { - return 1; - } - return -1; +function theme_simpletest_result_summary($form) { + return '
' . _simpletest_format_summary_line($form) . '
'; +} + +function _simpletest_format_summary_line($summary) { + return + format_plural(isset($summary['#pass']) ? $summary['#pass'] : 0, '1 pass', '@count passes') . ', ' . + format_plural(isset($summary['#fail']) ? $summary['#fail'] : 0, '1 fail', '@count fails') . ', ' . + format_plural(isset($summary['#exception']) ? $summary['#exception'] : 0, '1 exception', '@count exceptions'); } /** * Run selected tests. */ -function simpletest_run_selected_tests($form, &$form_state) { - $form_state['redirect'] = FALSE; +function simpletest_test_form_submit($form, &$form_state) { $output = ''; + $batch_mode = !preg_match("/^simpletest\d+$/", $_SERVER['HTTP_USER_AGENT']); switch ($form_state['values']['running_options']) { case 'all_tests': - $output = simpletest_run_tests(); + $output = simpletest_run_tests(NULL, 'drupal', $batch_mode); break; case 'selected_tests': $tests_list = array(); @@ -272,7 +291,7 @@ function simpletest_run_selected_tests($ } } if (count($tests_list) > 0 ) { - $output = simpletest_run_tests($tests_list); + $output = simpletest_run_tests($tests_list, 'drupal', $batch_mode); break; } // Fall through @@ -280,11 +299,155 @@ function simpletest_run_selected_tests($ drupal_set_message(t('No test has been selected.'), 'error'); } - simpletest_running_output($output); return FALSE; } /** + * Actually runs tests + * @param $test_list + * List of tests to run or NULL to run all tests. Defaults to NULL. + * @param $reporter + * Which reporter to use. Allowed values are: text, xml, html and drupal, + * drupal being the default. + * @param $batch_mode + * Whether to use the batch API or not. + */ +function simpletest_run_tests($test_list = NULL, $reporter = 'drupal', $batch_mode = FALSE) { + if (!isset($test_list)) { + $test_list = simpletest_get_all_tests(); + } + cache_clear_all(); + db_query('INSERT INTO {simpletest_test_id} VALUES (default)'); + $test_id = db_last_insert_id('simpletest_test_id', 'test_id'); + + if ($batch_mode) { + $batch = array( + 'title' => t('Running SimpleTests'), + 'operations' => array( + array('_simpletest_batch_operation', array($test_list, $test_id)), + ), + 'finished' => '_simpletest_batch_finished', + 'redirect' => 'admin/build/testing', + 'progress_message' => t('Processing tests.'), + 'init_message' => t('SimpleTest is initializing...') . ' ' . format_plural(count($test_list), "one test case will run.", "@count test cases will run."), + ); + batch_set($batch); + } + else { + foreach ($test_list as $test_class) { + $test = new $test_class($test_id); + $test->run(); + } + } +} + +/** + * Batch operation callback. + */ +function _simpletest_batch_operation($test_list_init, $test_id_init, &$context) { + // Ensure that all classes are loaded. + simpletest_get_all_tests(); + + if (!isset($context['sandbox']['max'])) { + // First iteration: initialize working values. + $test_list = $test_list_init; + $test_id = $test_id_init; + $context['sandbox']['max'] = count($test_list); + } + else { + // Nth iteration: get the current values where we last stored them. + $test_list = $context['sandbox']['tests']; + $test_id = $context['results']; + } + $max = $context['sandbox']['max']; + + $test_class = array_shift($test_list); + $test = new $test_class($test_id); + $test->run(); + $size = count($test_list); + + $result_array = array(); + $results = db_query('SELECT test_class, status, COUNT(*) as c FROM {simpletest} WHERE test_id = %d GROUP BY test_class, status ORDER BY test_class, message_id', $test_id); + while ($result = db_fetch_object($results)) { + $result_array[$result->test_class]['#'. $result->status] = $result->c; + } + $items = array(); + foreach ($result_array as $class => $summary) { + $items[] = $class . ':' . _simpletest_format_summary_line($summary); + } + + $message = t('Processed test %test (remaining: @count of @max).', array('%test' => $max - $size, '@count' => $size, '@max' => $max)); + $message .= theme('item_list', $items); + $context['message'] = $message; + // TODO: Do we want a summary of all? + + // Put back the tests. + $context['sandbox']['tests'] = $test_list; + $context['results'] = $test_id; + + // Multistep processing: report progress. + $context['finished'] = 1 - $size / $max; +} + +function _simpletest_batch_finished($success, $results, $operations) { + $_SESSION['test_id'] = $results; + if ($success) { + drupal_set_message(t('The tests have finished running.')); + } + else { + drupal_set_message(t('The tests did not successfully finish.'), 'error'); + } +} + +function simpletest_get_all_tests() { + static $formatted_classes; + if (!isset($formatted_classes)) { + 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; + } + + $tests_directory = $module_path . '/tests'; + if (is_dir($tests_directory)) { + foreach (file_scan_directory($tests_directory, '\.test$') as $file) { + $files[] = $file->filename; + } + } + } + + $existing_classes = get_declared_classes(); + foreach ($files as $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')) { + $formatted_classes[$class] = new $class; + } + } + } + if (count($formatted_classes) == 0) { + drupal_set_message('No test cases found.', 'error'); + return FALSE; + } + return $formatted_classes; +} + +function simpletest_categorize_tests($tests) { + $groups = array(); + foreach ($tests as $test => $instance) { + $info = $instance->getInfo(); + $groups[$info['group']][] = $instance; + } + return $groups; +} + +/** * Remove all temporary database tables and directories. */ function simpletest_clean_environment() { @@ -378,65 +541,6 @@ function simpletest_clean_temporary_dire rmdir($path); } -/** - * Actually runs tests - * @param array $test_list list of tests to run or DEFAULT NULL run all tests - * @param boolean $html_reporter TRUE if you want results in simple html, FALSE for full drupal page - */ -function simpletest_run_tests($test_list = NULL, $reporter = 'drupal') { - static $test_running; - if (!$test_running) { - $test_running = TRUE; - $test = simpletest_get_total_test($test_list); - switch ($reporter) { - case 'text': - $reporter = &new TextReporter(); - break; - case 'xml': - $reporter = &new XMLReporter(); - break; - case 'html': - $reporter = &new HtmlReporter(); - break; - case 'drupal': - $reporter = &new DrupalReporter(); - break; - } - - cache_clear_all(); - $results = $test->run($reporter); - $test_running = FALSE; - - switch (get_class($reporter)) { - case 'TextReporter': - case 'XMLReporter': - case 'HtmlReporter': - return $results; - case 'DrupalReporter': - return $reporter->getOutput(); - } - } -} - -/** - * This function makes sure no unnecessary copies of the DrupalTests object are instantiated - * @param array $classes list of all classes the test should concern or - * DEFAULT NULL - * @return DrupalTests object - */ -function &simpletest_get_total_test($classes = NULL) { - static $total_test; - if (!$total_test) { - simpletest_load(); - $total_test = &new DrupalTests(); - } - if (!is_null($classes)) { - $dut = new DrupalTests($classes); - return $dut; - } - return $total_test; -} - function simpletest_settings() { $form = array(); @@ -473,5 +577,4 @@ function simpletest_settings() { ); return system_settings_form($form); - }