Index: modules/simpletest/simpletest.css =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.css,v retrieving revision 1.2 diff -u -r1.2 simpletest.css --- modules/simpletest/simpletest.css 24 Jun 2008 21:51:02 -0000 1.2 +++ modules/simpletest/simpletest.css 28 Aug 2008 22:31:36 -0000 @@ -1,11 +1,9 @@ /* $Id: simpletest.css,v 1.2 2008/06/24 21:51:02 dries Exp $ */ -/* Addon for the simpletest module */ -#simpletest { -} /* Test Table */ th.simpletest_run { width: 50px; + padding-right: 0.5em; } th.simpletest_test { width: 160px; @@ -17,46 +15,55 @@ } table#simpletest-form-table tr td { - background-color: white !important; + background-color: white; } table#simpletest-form-table tr.simpletest-group td { - background-color: #EDF5FA !important; + background-color: #EDF5FA; } -div.simpletest-pass { - color: #33a333; +table#simpletest-form-table thead .form-item { + margin-bottom:0pt; + margin-top:0pt; + white-space:nowrap; } -div.simpletest-fail { - color: #a30000; +div.simpletest-image { + display: inline; } -tr.simpletest-pass.odd { - background: #b6ffb6; +div.simpletest-image, label.simpletest-group-label { + cursor: pointer; } -tr.simpletest-pass.even { - background: #9bff9b; +.simpletest-pass { + color: #316d31; } -tr.simpletest-fail.odd { - background: #ffc9c9; +.simpletest-fail { + color: #981010; } -tr.simpletest-fail.even { - background: #ffacac; +.simpletest-exception { + color: #bda400; } -tr.simpletest-exception.odd { - background: #f4ea71; +div.message > div.item-list { + font-weight: normal; } -tr.simpletest-exception.even { - background: #f5e742; +li.simpletest-important { + font-weight: bold; } -div.simpletest-image { - display: inline; +ul.simpletest-results li div.item-list ul { + padding-left: 20px; +} + +ul.simpletest-results li span.simpletest-overview { cursor: pointer; } + +ul.simpletest-results li span.simpletest-overview:hover { + text-decoration: underline; +} \ No newline at end of file Index: modules/simpletest/simpletest.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.test,v retrieving revision 1.6 diff -u -r1.6 simpletest.test --- modules/simpletest/simpletest.test 22 Aug 2008 12:35:55 -0000 1.6 +++ modules/simpletest/simpletest.test 28 Aug 2008 22:31:36 -0000 @@ -112,14 +112,13 @@ * Confirm that the stub test produced the desired results. */ function confirmStubTestResults() { - $this->assertAssertion($this->pass, 'Other', 'Pass'); - $this->assertAssertion($this->fail, 'Other', 'Fail'); - - $this->assertAssertion(t('Created permissions: @perms', array('@perms' => $this->valid_permission)), 'Role', 'Pass'); - $this->assertAssertion(t('Invalid permission %permission.', array('%permission' => $this->invalid_permission)), 'Role', 'Fail'); + $info = $this->getInfo(); + $name = $info['name']; + $this->assertAssertion($this->pass, $name); + $this->assertAssertion($this->fail, $name); - $this->test_ids[] = $test_id = $this->getTestIdFromResults(); - $this->assertTrue($test_id, t('Found test ID in results.')); + $this->assertAssertion(t('Created permissions: @perms', array('@perms' => $this->valid_permission)), 'Role'); + $this->assertAssertion(t('Invalid permission %permission.', array('%permission' => $this->invalid_permission)), 'Role'); } /** @@ -143,18 +142,17 @@ * @param string $status Assertion status. * @return Assertion result. */ - function assertAssertion($message, $type, $status) { + function assertAssertion($message, $type) { $message = trim(strip_tags($message)); $found = FALSE; - foreach ($this->results['assertions'] as $assertion) { + foreach ($this->results as $assertion) { if ($assertion['message'] == $message && - $assertion['type'] == $type && - $assertion['status'] == $status) { + $assertion['type'] == $type) { $found = TRUE; break; } } - return $this->assertTrue($found, t('Found assertion {"@message", "@type", "@status"}.', array('@message' => $message, '@type' => $type, '@status' => $status))); + return $this->assertTrue($found, t('Found assertion {"@message", "@type"}.', array('@message' => $message, '@type' => $type))); } /** @@ -164,20 +162,13 @@ $results = array(); if ($this->parse()) { - if ($fieldset = $this->getResultFieldSet()) { - // Code assumes this is the only test in group. - $results['summary'] = $this->asText($fieldset->div); - $results['name'] = $this->asText($fieldset->fieldset->legend); - - $results['assertions'] = array(); - $tbody = $fieldset->fieldset->table->tbody; - foreach ($tbody->tr as $row) { + if ($list = $this->getResultList()) { + $results = array(); + foreach ($list->li as $li) { $assertion = array(); - $assertion['message'] = $this->asText($row->td[0]); - $assertion['type'] = $this->asText($row->td[1]); - $ok_url = (url('misc/watchdog-ok.png') == 'misc/watchdog-ok.png') ? 'misc/watchdog-ok.png' : (base_path() . 'misc/watchdog-ok.png'); - $assertion['status'] = ($row->td[5]->img['src'] == $ok_url) ? 'Pass' : 'Fail'; - $results['assertions'][] = $assertion; + $assertion['message'] = $this->asText($li->span); + $assertion['type'] = str_replace('Group: ', '', $this->asText($li->div->ul->li[0])); + $results[] = $assertion; } } } @@ -190,11 +181,10 @@ * @return fieldset containing the results for group this test is in. */ function getResultFieldSet() { - $fieldsets = $this->xpath('//fieldset'); - $info = $this->getInfo(); - foreach ($fieldsets as $fieldset) { - if ($fieldset->legend == $info['group']) { - return $fieldset; + $list = $this->elements->xpath('//li'); + foreach ($list as $li) { + if (strpos($this->asText($li->span), t('SimpleTest: ')) === 0) { + return $li->div->ul->li->div->ul; } } return FALSE; Index: modules/simpletest/simpletest.install =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.install,v retrieving revision 1.7 diff -u -r1.7 simpletest.install --- modules/simpletest/simpletest.install 16 Aug 2008 20:57:14 -0000 1.7 +++ modules/simpletest/simpletest.install 28 Aug 2008 22:31:36 -0000 @@ -177,25 +177,25 @@ 'default' => '', 'description' => t('The message group this message belongs to. For example: warning, browser, user.'), ), - 'caller' => array( + 'function' => array( 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '', - 'description' => t('Name of the caller function or method that created this message.'), + 'description' => t('Name of the assertion function or method that created this message.'), ), 'line' => array( 'type' => 'int', 'not null' => TRUE, 'default' => 0, - 'description' => t('Line number of the caller.'), + 'description' => t('Line number on which the function is called.'), ), 'file' => array( 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '', - 'description' => t('Name of the file where the caller is.'), + 'description' => t('Name of the file where the function is called.'), ), ), 'primary key' => array('message_id'), Index: modules/simpletest/drupal_web_test_case.php =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v retrieving revision 1.34 diff -u -r1.34 drupal_web_test_case.php --- modules/simpletest/drupal_web_test_case.php 23 Aug 2008 07:42:54 -0000 1.34 +++ modules/simpletest/drupal_web_test_case.php 28 Aug 2008 22:31:36 -0000 @@ -43,56 +43,72 @@ * The message string. * @param $group * WHich group this assert belongs to. - * @param $custom_caller - * By default, the assert comes from a function which names start with - * 'test'. Instead, you can specify where this assert originates from - * by passing in an associative array as $custom_caller. Key 'file' is - * the name of the source file, 'line' is the line number and 'function' - * is the caller function itself. */ - protected function _assert($status, $message = '', $group = 'Other', $custom_caller = NULL) { + protected function _assert($status, $message = '', $group = 'Other') { global $db_prefix; + + // Convert boolean status to string status. if (is_bool($status)) { $status = $status ? 'pass' : 'fail'; } + + // Incrament summary result counter. $this->_results['#' . $status]++; - if (!isset($custom_caller)) { - $callers = debug_backtrace(); - array_shift($callers); - foreach ($callers as $function) { - if (substr($function['function'], 0, 6) != 'assert' && $function['function'] != 'pass' && $function['function'] != 'fail') { - break; - } - } - } - else { - $function = $custom_caller; - } + + // Get the function information about the call to the assertion method. + $function = $this->getAssertionCall(); + + // Switch to non-testing database to store results in. $current_db_prefix = $db_prefix; $db_prefix = $this->db_prefix_original; - db_insert('simpletest')->fields(array( + + // Creation assertion array to be displayed white tests are running. + $this->_assertions[] = $assertion = array( 'test_id' => $this->test_id, - 'test_class' => get_class($this), - 'status' => $status, - 'message' => substr($message, 0, 255), // Some messages are too long for the database. - 'message_group' => $group, - 'caller' => $function['function'], - 'line' => $function['line'], - 'file' => $function['file'], - ))->execute(); - $this->_assertions[] = array( + 'test_class' => get_class($this), 'status' => $status, - 'message' => $message, + 'message' => substr($message, 0, 255), // Some messages are too long for the database. 'group' => $group, 'function' => $function['function'], 'line' => $function['line'], 'file' => $function['file'], ); + + // Store assertion for display after the test has completed. + // Change the 'group' attribute to 'message_group' to avoid reserved word + // conflict when inserted into database. + $assertion['message_group'] = $assertion['group']; + unset($assertion['group']); + db_insert('simpletest')->fields($assertion)->execute(); + + // Return to testing prefix. $db_prefix = $current_db_prefix; return $status; } /** + * Cycle through backtrace until the first non-assertion method is found. + * Return the previous function call to the assertion method. + * + * @return + * Array representing a backtrace element. + */ + protected function getAssertionCall() { + $backtrace = debug_backtrace(); + array_shift($backtrace); // Remove call to getCaller(). + array_shift($backtrace); // Remove call to _assert(). + + foreach ($backtrace as $trace) { + if (substr($trace['function'], 0, 6) != 'assert' && + $trace['function'] != 'pass' && + $trace['function'] != 'fail') { + return $function; + } + $function = $trace; // Keep track of previous trace element. + } + } + + /** * Check to see if a value is not false (not an empty string, 0, NULL, or FALSE). * * @param $value @@ -263,11 +279,9 @@ * The message to display along with the assertion. * @param $group * The type of assertion - examples are "Browser", "PHP". - * @param $custom_caller - * The caller of the error. */ - protected function error($message = '', $group = 'Other', $custom_caller = NULL) { - return $this->_assert('exception', $message, $group, $custom_caller); + protected function error($message = '', $group = 'Other') { + return $this->_assert('exception', $message, $group); } /** @@ -732,7 +746,6 @@ // Close the CURL handler. $this->curlClose(); - restore_error_handler(); } } @@ -807,7 +820,7 @@ // them. @$htmlDom = DOMDocument::loadHTML($this->_content); if ($htmlDom) { - $this->assertTrue(TRUE, t('Valid HTML found on "@path"', array('@path' => $this->getUrl())), t('Browser')); + $this->pass(t('Valid HTML found on "@path"', array('@path' => $this->getUrl())), t('Browser')); // It's much easier to work with simplexml than DOM, luckily enough // we can just simply import our DOM tree. $this->elements = simplexml_import_dom($htmlDom); @@ -1288,7 +1301,7 @@ * TRUE on pass, FALSE on fail. */ function assertText($text, $message = '', $group = 'Other') { - return $this->assertTextHelper($text, $message, $group = 'Other', FALSE); + return $this->assertTextHelper($text, $message, $group, FALSE); } /** Index: modules/simpletest/simpletest.module =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.module,v retrieving revision 1.11 diff -u -r1.11 simpletest.module --- modules/simpletest/simpletest.module 21 Aug 2008 19:36:38 -0000 1.11 +++ modules/simpletest/simpletest.module 28 Aug 2008 22:31:36 -0000 @@ -50,11 +50,14 @@ 'simpletest_result_summary' => array( 'arguments' => array('form' => NULL) ), + 'simpletest_results' => array( + 'arguments' => array('results' => NULL, 'assertions' => NULL), + ), ); } /** - * Menu callback for both running tests and listing possible tests + * Menu callback -- for both running tests and listing possible tests */ function simpletest_test_form() { global $db_prefix, $db_prefix_original; @@ -65,55 +68,29 @@ // 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'), - ); + $form['#test_id'] = $_SESSION['test_id']; $header = array(t('Message'), t('Group'), t('Filename'), t('Line'), t('Function'), array('colspan' => 2, 'data' => t('Status'))); + $test_results = array(); + $test_assertions = array(); while ($result = db_fetch_object($results)) { - $class = $result->test_class; - $info = $uncategorized_tests[$class]->getInfo(); - $group = $info['group']; - if (!isset($group_summary[$group])) { - $group_summary[$group] = $summary; - } - $element = &$form['results'][$group][$class]; - if (!isset($element)) { - $element['summary'] = $summary; - } - $status = $result->status; - // This reporter can only handle pass, fail and exception. - if (isset($map[$status])) { - $element['#title'] = $info['name']; - $status_index = '#'. $status; - $form['summary'][$status_index]++; - $group_summary[$group][$status_index]++; - $element['summary'][$status_index]++; - $element['result_table']['#rows'][] = array( - 'data' => array( - $result->message, - $result->message_group, - basename($result->file), - $result->line, - $result->caller, - $map[$status], - ), - 'class' => "simpletest-$status", - ); + if (empty($test_results[$result->test_class])) { + $class = $result->test_class; + $test = new $class(); + $info = $test->getInfo(); + $test_results[$class] = array('#fail' => 0, '#pass' => 0, '#exception' => 0); + foreach ($info as $key => $value) { + $test_results[$class]['#' . $key] = $value; + } } - unset($element); + $test_results[$result->test_class]['#' . $result->status]++; + $assertion = (array)$result; + $assertion['group'] = $assertion['message_group']; + unset($assertion['message_group']); + $test_assertions[$result->test_class][] = $assertion; + $test_results[$class]['#ok'] = !($test_results[$class]['#fail'] + $test_results[$class]['#exception']); } + $form['#results'] = $test_results; + $form['#assertions'] = $test_assertions; // Clear test results. if (variable_get('simpletest_clear_results', TRUE)) { @@ -121,35 +98,13 @@ db_query('DELETE FROM {simpletest_test_id} WHERE test_id = %d', $_SESSION['test_id']); } unset($_SESSION['test_id']); - - $all_ok = TRUE; - foreach ($form['results'] as $group => &$elements) { - $group_ok = TRUE; - foreach ($elements as $class => &$element) { - $info = $uncategorized_tests[$class]->getInfo(); - $ok = $element['summary']['#fail'] + $element['summary']['#exception'] == 0; - $element += array( - '#type' => 'fieldset', - '#collapsible' => TRUE, - '#collapsed' => $ok, - '#description' => $info['description'], - ); - $element['result_table']['#markup'] = theme('table', $header, $element['result_table']['#rows']); - $element['summary']['#ok'] = $ok; - $group_ok = $group_ok && $ok; - } - $elements += array( - '#type' => 'fieldset', - '#title' => $group, - '#collapsible' => TRUE, - '#collapsed' => $group_ok, - 'summary' => $group_summary[$group], - ); - $elements['summary']['#ok'] = $group_ok; - $all_ok = $group_ok && $all_ok; - } - $form['summary']['#ok'] = $all_ok; } + $form['run_all_top'] = array( + '#type' => 'checkbox', + '#default_value' => 0, + '#attributes' => array('class' => 'simpletest-master'), + ); + foreach ($tests as $group_name => $test_group) { foreach ($test_group as $test) { $test_info = $test->getInfo(); @@ -163,32 +118,15 @@ } } - $form['run'] = array( - '#type' => 'fieldset', - '#collapsible' => FALSE, - '#collapsed' => FALSE, - '#title' => t('Run tests'), - ); - $form['run']['running_options'] = array( - '#type' => 'radios', - '#default_value' => 'selected_tests', - '#options' => array( - 'all_tests' => t('Run all tests (WARNING, this may take a long time)'), - 'selected_tests' => t('Run selected tests'), - ), + $form['submit'] = array( + '#prefix' => '
', + '#suffix' => '
', ); - $form['run']['op'] = array( + $form['submit']['run'] = 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.') - ); - $form['reset']['op'] = array( + $form['submit']['reset'] = array( '#type' => 'submit', '#value' => t('Clean environment'), '#submit' => array('simpletest_clean_environment') @@ -204,11 +142,13 @@ 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' => drupal_render($form['run_all_top']), 'class' => 'simpletest_run checkbox'), array('data' => t('Test'), 'class' => 'simpletest_test'), array('data' => t('Description'), 'class' => 'simpletest_description'), ); + unset($form['run_all_top']); $js = array( 'images' => array( theme('image', 'misc/menu-collapsed.png', 'Expand', 'Expand'), @@ -249,12 +189,21 @@ $js['simpletest-test-group-'. $test_class] = $current_js; } unset($form['tests']); + unset($form['run_all_bottom']); 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 (isset($form['#results'])) { + $output .= '

' . t('The assertions listed in bold text are those made by the test itself, rather than the implicit framework tests.') .'

'; + $output .= drupal_get_form('simpletest_filter_form'); + $output .= theme('simpletest_results', $form['#results'], $form['#assertions']); + $output .= drupal_get_form('simpletest_rerun_form', $form['#test_id']); + return $output; + } + else { + // This will forward us to the batch api if necessary. + drupal_get_form('simpletest_rerun_form', NULL); + $output .= '

' . t('Select the "Clean environment" button below to remove any database tables or files that might be left over from interrupted tests. Otherwise, select the tests you would like to run, and click "Run tests".') . '

'; } if (count($rows)) { $output .= theme('table', $header, $rows, array('id' => 'simpletest-form-table')); @@ -265,12 +214,8 @@ return $output; } -function theme_simpletest_result_summary($form, $text = NULL) { - return '
' . _simpletest_format_summary_line($form) . '
'; -} - function _simpletest_format_summary_line($summary) { - return t('@pass, @fail, @exception', array( + return t('@pass, @fail, and @exception', array( '@pass' => format_plural(isset($summary['#pass']) ? $summary['#pass'] : 0, '1 pass', '@count passes'), '@fail' => format_plural(isset($summary['#fail']) ? $summary['#fail'] : 0, '1 fail', '@count fails'), '@exception' => format_plural(isset($summary['#exception']) ? $summary['#exception'] : 0, '1 exception', '@count exceptions'), @@ -284,10 +229,9 @@ $output = ''; $batch_mode = !preg_match("/^simpletest\d+$/", $_SERVER['HTTP_USER_AGENT']); $tests_list = array(); - $run_all_tests = $form_state['values']['running_options'] == 'all_tests'; simpletest_get_all_tests(); foreach ($form_state['values'] as $class_name => $value) { - if (class_exists($class_name) && ($value === 1 || $run_all_tests)) { + if (class_exists($class_name) && $value === 1) { $tests_list[] = $class_name; } } @@ -315,6 +259,10 @@ $test_id = db_insert('simpletest_test_id')->useDefaults(array('test_id'))->execute(); if ($batch_mode) { + $first_test = array_shift($test_list); + $first_instance = new $first_test(); + array_unshift($test_list, $first_test); + $info = $first_instance->getInfo(); $batch = array( 'title' => t('Running SimpleTests'), 'operations' => array( @@ -322,9 +270,10 @@ ), 'finished' => '_simpletest_batch_finished', 'redirect' => 'admin/build/testing', - 'progress_message' => t('Processing tests.'), + 'progress_message' => '', 'css' => array(drupal_get_path('module', 'simpletest') .'/simpletest.css'), - 'init_message' => t('SimpleTest is initializing...') . ' ' . format_plural(count($test_list), "one test case will run.", "@count test cases will run."), + 'js' => array(drupal_get_path('module', 'simpletest') .'/simpletest.js'), + 'init_message' => t('Processing test @num of @max - %test.', array('%test' => $info['name'], '@num' => '1', '@max' => count($test_list))), ); batch_set($batch); } @@ -356,6 +305,7 @@ // Nth iteration: get the current values where we last stored them. $test_list = $context['sandbox']['tests']; $test_results = $context['sandbox']['test_results']; + $test_assertions = $context['sandbox']['test_assertions']; } $max = $context['sandbox']['max']; @@ -371,19 +321,27 @@ foreach ($test_results[$test_class] as $key => $value) { $test_results[$key] += $value; } - $test_results[$test_class]['#name'] = $info['name']; - $items = array(); - foreach (element_children($test_results) as $class) { - $items[] = '
' . t('@name: @summary', array('@name' => $test_results[$class]['#name'], '@summary' => _simpletest_format_summary_line($test_results[$class]))) . '
'; + $test_assertions[$test_class] = $test->_assertions; + foreach ($info as $key => $value) { + $test_results[$test_class]['#' . $key] = $value; + } + $test_results[$test_class]['#ok'] = !($test_results['#fail'] + $test_results['#exception']); + if (!empty($test_list)) { + $current_test_class = array_shift($test_list); + $instance = new $current_test_class(); + $current_info = $instance->getInfo(); + $context['message'] = t('Processing test @num of @max - %test.', array('%test' => $current_info['name'], '@num' => $max - count($test_list), '@max' => $max)); + array_unshift($test_list, $current_test_class); } - $message = t('Processed test @num of @max - %test.', array('%test' => $info['name'], '@num' => $max - $size, '@max' => $max)); - $message .= theme('item_list', $items); - $context['message'] = $message; - // TODO: Do we want a summary of all? + else { + $context['message'] = t('Finished processing all tests.'); + } + $context['message'] .= theme_simpletest_results($test_results, $test_assertions); // Save working values for the next iteration. $context['sandbox']['tests'] = $test_list; $context['sandbox']['test_results'] = $test_results; + $context['sandbox']['test_assertions'] = $test_assertions; // The test_id is the only thing we need to save for the report page. $context['results']['test_id'] = $test_id; @@ -392,7 +350,9 @@ } function _simpletest_batch_finished($success, $results, $operations) { - $_SESSION['test_id'] = $results['test_id']; + if (isset($results['test_id'])) { + $_SESSION['test_id'] = $results['test_id']; + } if ($success) { drupal_set_message(t('The tests have finished running.')); } @@ -465,6 +425,192 @@ } /** + * Theme simpletest results. + * + * @param $results + * A nested array of simpletest results. + * @param $assertions + * An array of assertions for each test. + * @return + * A nested list of simpletest items. + */ +function theme_simpletest_results($results, $assertions) { + $items = array(); + $group_data = array(); + $totals = array( + '#fail' => 0, + '#pass' => 0, + '#exception' => 0, + ); + foreach (element_children($results) as $class) { + $totals['#fail'] += $results[$class]['#fail']; + $totals['#pass'] += $results[$class]['#pass']; + $totals['#exception'] += $results[$class]['#exception']; + $item = array(); + foreach ($results[$class] as $key => $value) { + if ($key == '#ok') { + $group_data[$results[$class]['#group']][$key] = isset($group_data[$results[$class]['#group']][$key]) ? $group_data[$results[$class]['#group']][$key] && $value : $value; + } + else { + $group_data[$results[$class]['#group']][$key] = isset($group_data[$results[$class]['#group']][$key]) ? $group_data[$results[$class]['#group']][$key] + $value : $value; + } + } + $item['class'] = 'simpletest-container ' . ($results[$class]['#fail'] ? 'simpletest-fail expanded' : ($results[$class]['#exception'] ? 'simpletest-exception expanded' : 'simpletest-pass collapsed')); + $item['data'] = '' . t('@name: @summary', array('@name' => $results[$class]['#name'], '@summary' => _simpletest_format_summary_line($results[$class]))) . ''; + $assertion_items = array(); + foreach ($assertions[$class] as $assertion) { + // The idea of importancy rests upon the fact that the testing framework + // makes many assertions in its normal process of making GET and POST + // requests, as well as many other normal procedures. We will treat + // assertions actually initiated by the test itself as far more likely to + // be important, and thus highlight it in bold text. + $important = FALSE; + if ($assertion['group'] == 'Other') { + $assertion['group'] = $results[$class]['#name']; + $important = TRUE; + } + + $css_class = simpletest_get_assertion_css_class($assertion, $important); + $assertion_items[] = array( + 'data' => t('[@status] !message', + array('@status' => simpletest_get_status_message($assertion), + '!message' => $assertion['message'])), + 'class' => 'collapsed ' . $css_class, + 'children' => array( + array( + 'data' => t('Group: @group', array('@group' => $assertion['group'])), + 'class' => 'leaf ' . $css_class, + ), + array( + 'data' => t('File: @file', array('@file' => basename($assertion['file']))), + 'class' => 'leaf ' . $css_class, + ), + array( + 'data' => t('Function: @function', array('@function' => $assertion['function'])), + 'class' => 'leaf ' . $css_class, + ), + array( + 'data' => t('Line: @line', array('@line' => $assertion['line'])), + 'class' => 'leaf ' . $css_class, + ), + ), + ); + } + $item['children'] = $assertion_items; + $items[$results[$class]['#group']]['children'][] = $item; + $items[$results[$class]['#group']]['data'] = '' . t('@name: @summary', array('@name' => $results[$class]['#group'], '@summary' => _simpletest_format_summary_line($group_data[$results[$class]['#group']]))) . ''; + $items[$results[$class]['#group']]['class'] = 'simpletest-container ' . ($group_data[$results[$class]['#group']]['#fail'] ? 'simpletest-fail expanded' : ($group_data[$results[$class]['#group']]['#exception'] ? 'simpletest-exception expanded' : 'simpletest-pass collapsed')); + } + $output = ''; + $output .= '

'; + $output .= t('Overall results: @summary', array('@summary' => _simpletest_format_summary_line($totals))); + $output .= '

'; + $output .= theme('item_list', $items, NULL, 'ul', array('class' => 'simpletest-results')); + return $output; +} + +/** + * FAPI callback for the filtering form. + */ +function simpletest_filter_form() { + $form = array(); + $form['outcome'] = array( + '#type' => 'checkboxes', + '#title' => t('Filter by outcome'), + '#options' => array(), + '#attributes' => array('class' => 'container-inline'), + '#default_value' => array(), + ); + $categories = array('pass' => t('Passes'), 'fail' => t('Fails'), 'exception' => t('Exceptions')); + foreach ($categories as $outcome => $readable) { + $form['outcome'][$outcome] = array( + '#type' => 'checkbox', + '#title' => $readable, + '#attributes' => array('class' => 'simpletest-filter'), + '#default_value' => TRUE, + ); + } + return $form; +} + +/** + * FAPI callback for the re-run form. + */ +function simpletest_rerun_form($form_state, $test_id) { + $form = array(); + $form['test_id'] = array( + '#type' => 'hidden', + '#default_value' => $test_id, + ); + $form['actions'] = array( + '#prefix' => '
', + '#suffix' => '
', + ); + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Re-run tests'), + ); + $form['actions']['cancel'] = array( + '#markup' => l(t('Return to testing overview page'), 'admin/build/testing'), + ); + return $form; +} + +/** + * FAPI submit handler. + */ +function simpletest_rerun_form_submit($form, &$form_state) { + $result = db_query('SELECT DISTINCT(test_class) FROM {simpletest} WHERE test_id = %d', $form_state['values']['test_id']); + while ($class = db_result($result)) { + $form_state['values'][$class] = 1; + } + simpletest_test_form_submit($form, $form_state); +} + +/** + * Get the appropriate css class based on the assertions status and importance. + * + * @param $assertion Assertion item aray. + * @param $important Importance of assertion. + * @return CSS class. + */ +function simpletest_get_assertion_css_class($assertion, $important) { + switch ($assertion['status']) { + case 'pass': + $css_class = 'simpletest-pass'; + break; + case 'fail': + $css_class = 'simpletest-fail'; + break; + case 'exception': + $css_class = 'simpletest-exception'; + break; + } + if ($important) { + $css_class .= ' simpletest-important'; + } + return $css_class; +} + +/** + * Get the status message text + * + * @param $assertion Assertion item aray. + * @return Status message text. + */ +function simpletest_get_status_message($assertion) { + switch ($assertion['status']) { + case 'pass': + return t('PASS'); + case 'fail': + return t('FAIL'); + case 'exception': + return t('EXCEPTION'); + } + return t('UNKNOWN'); +} + +/** * Remove all temporary database tables and directories. */ function simpletest_clean_environment() { Index: modules/simpletest/simpletest.js =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.js,v retrieving revision 1.2 diff -u -r1.2 simpletest.js --- modules/simpletest/simpletest.js 23 Apr 2008 18:50:49 -0000 1.2 +++ modules/simpletest/simpletest.js 28 Aug 2008 22:31:36 -0000 @@ -1,12 +1,15 @@ // $Id: simpletest.js,v 1.2 2008/04/23 18:50:49 dries Exp $ +/** + * Add the cool table collapsing on the testing overview page. + */ Drupal.behaviors.simpleTestMenuCollapse = function() { // Adds expand-collapse functionality. $('div.simpletest-image').click(function() { // Toggle all of the trs. if (!Drupal.settings.simpleTest[$(this).attr('id')].clickActive) { Drupal.settings.simpleTest[$(this).attr('id')].clickActive = true; - var trs = $(this).parents('tbody').children().filter('.'+ Drupal.settings.simpleTest[$(this).attr('id')].testClass), trs_formatted = [], direction = Drupal.settings.simpleTest[$(this).attr('id')].imageDirection, self = $(this); + var trs = $(this).parents('tbody').children().filter('.' + Drupal.settings.simpleTest[$(this).attr('id')].testClass), trs_formatted = [], direction = Drupal.settings.simpleTest[$(this).attr('id')].imageDirection, self = $(this); for (var i = 0; i < trs.length; i++) { trs_formatted.push(trs[i]); } @@ -21,12 +24,21 @@ Drupal.settings.simpleTest[self.attr('id')].clickActive = false; } } - toggleTrs(trs_formatted, (direction? 'pop' : 'shift'), (direction? 'fadeOut' : 'fadeIn')); + toggleTrs(trs_formatted, (direction ? 'pop' : 'shift'), (direction ? 'fadeOut' : 'fadeIn')); Drupal.settings.simpleTest[$(this).attr('id')].imageDirection = !direction; $(this).html(Drupal.settings.simpleTest.images[(direction? 0 : 1)]); } }); + $('label.simpletest-group-label').click(function() { + $(this).siblings('div.simpletest-image').click(); + return false; + }); } + +/** + * Select/deselect all the inner checkboxes when the outer checkboxes are + * selected/deselected. + */ Drupal.behaviors.simpleTestSelectAll = function() { $('td.simpletest-select-all').each(function() { var checkboxes = Drupal.settings.simpleTest['simpletest-test-group-'+ $(this).attr('id')].testNames, totalCheckboxes = 0, @@ -35,11 +47,11 @@ for (var i = 0; i < checkboxes.length; i++) { $('#'+ checkboxes[i]).attr('checked', checked); } - self.data('simpletest-checked-tests', (checked? checkboxes.length : 0)); + self.data('simpletest-checked-tests', (checked ? checkboxes.length : 0)); }).data('simpletest-checked-tests', 0); var self = $(this); for (var i = 0; i < checkboxes.length; i++) { - if ($('#'+ checkboxes[i]).change(function() { + if ($('#' + checkboxes[i]).change(function() { if (checkbox.attr('checked') == 'checked') { checkbox.attr('checked', ''); } @@ -49,7 +61,7 @@ checkbox.attr('checked', 'checked'); } else { - checkbox.attr('checked', ''); + checkbox.removeAttr('checked'); } }).attr('checked') == 'checked') { totalCheckboxes++; @@ -60,4 +72,69 @@ } $(this).append(checkbox); }); + $('input.simpletest-master:checkbox').click(function() { + if ($(this).is(':checked')) { + $('#simpletest-form-table input:checkbox,input.simpletest-master:checkbox').attr('checked', 'checked'); + } + else { + $('#simpletest-form-table input:checkbox,input.simpletest-master:checkbox').removeAttr('checked'); + } + $('#simpletest-form-table input:checkbox:not(.simpletest-master)').change(function() { + if ($('#simpletest-form-table input:checkbox:not(:checked):not(.simpletest-master)').length) { + $('input.simpletest-master:checkbox').removeAttr('checked'); + } + else if ($('#simpletest-form-table input:checkbox:not(.simpletest-master)').length == $('#simpletest-form-table input:checkbox:checked:not(.simpletest-master)').length) { + $('input.simpletest-master:checkbox').attr('checked', 'checked'); + } + }); + }); +}; + +/** + * Filter the results based on their outcome. + */ +Drupal.behaviors.simpleTestFilterForm = function() { + var updateResults = function(type, checked) { + $('li.simpletest-' + type + ':not(.simpletest-container)')[checked ? 'show' : 'hide'](); + $('li.simpletest-container:not(:has(li.simpletest-container))').each(function() { + var children = $(this).find('li:visible:not(.simpletest-container)').is('.simpletest-pass,.simpletest-fail,.simpletest-exception'); + $(this)[children ? 'show' : 'hide'](); + }); + } + $('#edit-outcome-pass').change(function() { + updateResults('pass', $(this).is(':checked')); + }); + $('#edit-outcome-fail').change(function() { + updateResults('fail', $(this).is(':checked')); + }); + $('#edit-outcome-exception').change(function() { + updateResults('exception', $(this).is(':checked')); + }); + updateResults('pass', $('#edit-outcome-pass').is(':checked')); + updateResults('fail', $('#edit-outcome-fail').is(':checked')); + updateResults('exception', $('#edit-outcome-exception').is(':checked')); +}; + +/** + * The results should collapse when you click on them, of course. + */ +Drupal.behaviors.simpleTestResultsCollapse = function() { + $('li > span.simpletest-overview').click(function() { + $(this).siblings().children('ul').toggle().toggleClass('simpletest-hidden').parent().parent().toggleClass('collapsed').toggleClass('expanded'); + }); + $('li.collapsed > span.simpletest-overview').each(function() { + $(this).siblings().children('ul').hide().addClass('simpletest-hidden'); + }); +}; + +/** + * Batch update function - re-attaches javascript behaviors to the new elements + * now injected into the HTML. + */ +Drupal.batchUpdate.simpleTestResultsCollapse = function() { + $('li > span.simpletest-overview').click(function() { + $(this).siblings().children('ul').toggle().toggleClass('simpletest-hidden').parent().parent().toggleClass('collapsed').toggleClass('expanded'); + }).each(function() { + $(this).siblings().children('ul').hide().addClass('simpletest-hidden').parent().parent().removeClass('expanded').addClass('collapsed'); + }); }; \ No newline at end of file Index: includes/batch.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/batch.inc,v retrieving revision 1.20 diff -u -r1.20 batch.inc --- includes/batch.inc 24 Jun 2008 21:51:02 -0000 1.20 +++ includes/batch.inc 28 Aug 2008 22:31:34 -0000 @@ -22,11 +22,14 @@ // Register database update for end of processing. register_shutdown_function('_batch_shutdown'); - // Add batch-specific css. + // Add batch-specific css and javascript. foreach ($batch['sets'] as $batch_set) { foreach ($batch_set['css'] as $css) { drupal_add_css($css); } + foreach ($batch_set['js'] as $js) { + drupal_add_js($js); + } } $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : ''; @@ -251,8 +254,11 @@ '@total' => $total, '@current' => floor($current), '@percentage' => $percentage, - ); - $message = strtr($progress_message, $values) . '
'; + ); + $message = strtr($progress_message, $values); + if (!empty($message)) { + $message .= '
'; + } $message .= $task_message ? $task_message : ' '; return array($percentage, $message); Index: includes/form.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/form.inc,v retrieving revision 1.281 diff -u -r1.281 form.inc --- includes/form.inc 17 Aug 2008 11:08:23 -0000 1.281 +++ includes/form.inc 28 Aug 2008 22:31:35 -0000 @@ -2421,6 +2421,7 @@ 'progress_message' => $t('Remaining @remaining of @total.'), 'error_message' => $t('An error has occurred.'), 'css' => array(), + 'js' => array(), ); $batch_set = $init + $batch_definition + $defaults; Index: misc/drupal.js =================================================================== RCS file: /cvs/drupal/drupal/misc/drupal.js,v retrieving revision 1.45 diff -u -r1.45 drupal.js --- misc/drupal.js 25 Jun 2008 07:45:03 -0000 1.45 +++ misc/drupal.js 28 Aug 2008 22:31:35 -0000 @@ -1,6 +1,6 @@ // $Id: drupal.js,v 1.45 2008/06/25 07:45:03 dries Exp $ -var Drupal = Drupal || { 'settings': {}, 'behaviors': {}, 'themes': {}, 'locale': {} }; +var Drupal = Drupal || { 'settings': {}, 'behaviors': {}, 'themes': {}, 'locale': {}, 'batchUpdate': {} }; /** * Set the variable that indicates if JavaScript behaviors should be applied Index: misc/batch.js =================================================================== RCS file: /cvs/drupal/drupal/misc/batch.js,v retrieving revision 1.4 diff -u -r1.4 batch.js --- misc/batch.js 21 Oct 2007 18:59:01 -0000 1.4 +++ misc/batch.js 28 Aug 2008 22:31:35 -0000 @@ -16,6 +16,9 @@ // Success: redirect to the summary. var updateCallback = function (progress, status, pb) { + for (behavior in Drupal.batchUpdate) { + Drupal.batchUpdate[behavior](progress, status, pb); + } if (progress == 100) { pb.stopMonitoring(); window.location = uri+'&op=finished'; Index: modules/contact/contact.test =================================================================== RCS file: /cvs/drupal/drupal/modules/contact/contact.test,v retrieving revision 1.8 diff -u -r1.8 contact.test --- modules/contact/contact.test 2 Aug 2008 05:16:47 -0000 1.8 +++ modules/contact/contact.test 28 Aug 2008 22:31:35 -0000 @@ -219,7 +219,7 @@ // Get role id (rid) for specified role. $rid = db_result(db_query("SELECT rid FROM {role} WHERE name = '%s'", array($role))); if ($rid === FALSE) { - $this->fail(t(' [permission] Role "' . $role . '" not found.')); + $this->fail(t('Role "@role" not found.', array('@role' => $role)), t('Permission')); } // Create edit array from permission. @@ -229,7 +229,7 @@ } $this->drupalPost('admin/user/permissions', $edit, t('Save permissions')); - $this->assertText(t('The changes have been saved.'), t(' [permission] Saved changes.')); + $this->assertText(t('The changes have been saved.'), t('Saved changes.'), t('Permission')); } }