Index: modules/simpletest/simpletest.css =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.css,v retrieving revision 1.3 diff -u -r1.3 simpletest.css --- modules/simpletest/simpletest.css 18 Sep 2008 14:57:42 -0000 1.3 +++ modules/simpletest/simpletest.css 18 Sep 2008 19:36:34 -0000 @@ -3,57 +3,68 @@ /* Addon for the simpletest module */ #simpletest { } + /* Test Table */ -#simpletest-form-table th.select-all { - width: 50px; +#simpletest-form-table th.select-all, .simpletest-select-all { + width: 30px; } + th.simpletest_test { width: 160px; } table#simpletest-form-table tr td { - background-color: white !important; + background-color: white; color: #494949; } table#simpletest-form-table tr.simpletest-group td { - background-color: #EDF5FA !important; + background-color: #EDF5FA; color: #494949; } -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; +.simpletest-pass { + color: #316d31; } -tr.simpletest-pass.odd { - background: #b6ffb6; +.simpletest-fail { + color: #981010; } -tr.simpletest-pass.even { - background: #9bff9b; +.simpletest-exception { + color: #bda400; } -tr.simpletest-fail.odd { - background: #ffc9c9; +div.message > div.item-list { + font-weight: normal; } -tr.simpletest-fail.even { - background: #ffacac; +li.simpletest-important { + font-weight: bold; } -tr.simpletest-exception.odd { - background: #f4ea71; +ul.simpletest-results li div.item-list ul { + padding-left: 20px; } -tr.simpletest-exception.even { - background: #f5e742; +ul.simpletest-results li span.simpletest-overview { + cursor: pointer; +} + +ul.simpletest-results li span.simpletest-overview:hover { + text-decoration: underline; } div.simpletest-image { display: inline; +} + +div.simpletest-image, label.simpletest-group-label { cursor: pointer; } Index: modules/simpletest/simpletest.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.test,v retrieving revision 1.9 diff -u -r1.9 simpletest.test --- modules/simpletest/simpletest.test 15 Sep 2008 20:48:09 -0000 1.9 +++ modules/simpletest/simpletest.test 18 Sep 2008 19:36:35 -0000 @@ -82,7 +82,7 @@ $this->drupalGet('admin/build/testing'); $edit = array(); - $edit['SimpleTestTestCase'] = TRUE; + $edit[get_class($this)] = TRUE; $this->drupalPost(NULL, $edit, t('Run tests')); // Parse results and confirm that they are correct. @@ -129,17 +129,20 @@ * Confirm that the stub test produced the desired results. */ function confirmStubTestResults() { - $this->assertAssertion($this->pass, 'Other', 'Pass', 'simpletest.test', 'SimpleTestTestCase->stubTest()'); - $this->assertAssertion($this->fail, 'Other', 'Fail', 'simpletest.test', 'SimpleTestTestCase->stubTest()'); + $info = $this->getInfo(); + $name = $info['name']; + + $this->assertAssertion($this->pass, $name, 'pass', 'simpletest.test', 'SimpleTestTestCase->stubTest()'); + $this->assertAssertion($this->fail, $name, 'fail', 'simpletest.test', 'SimpleTestTestCase->stubTest()'); - $this->assertAssertion(t('Created permissions: @perms', array('@perms' => $this->valid_permission)), 'Role', 'Pass', 'simpletest.test', 'SimpleTestTestCase->stubTest()'); - $this->assertAssertion(t('Invalid permission %permission.', array('%permission' => $this->invalid_permission)), 'Role', 'Fail', 'simpletest.test', 'SimpleTestTestCase->stubTest()'); + $this->assertAssertion(t('Created permissions: @perms', array('@perms' => $this->valid_permission)), 'Role', 'pass', 'simpletest.test', 'SimpleTestTestCase->stubTest()'); + $this->assertAssertion(t('Invalid permission %permission.', array('%permission' => $this->invalid_permission)), 'Role', 'fail', 'simpletest.test', 'SimpleTestTestCase->stubTest()'); // Check that a warning is catched by simpletest. - $this->assertAssertion('Division by zero', 'Warning', 'Fail', 'simpletest.test', 'SimpleTestTestCase->stubTest()'); + $this->assertAssertion('Division by zero', 'Warning', 'exception', 'simpletest.test', 'SimpleTestTestCase->stubTest()'); // Check that the backtracing code works for specific assert function. - $this->assertAssertion('This is nothing.', 'Other', 'Pass', 'simpletest.test', 'SimpleTestTestCase->stubTest()'); + $this->assertAssertion('This is nothing.', $name, 'pass', 'simpletest.test', 'SimpleTestTestCase->stubTest()'); // Check that errors that occur inside PHP internal functions are correctly reported. $this->assertAssertion('The second argument should be either an array or an object', 'Warning', 'Fail', 'simpletest.test', 'SimpleTestTestCase->stubTest()'); @@ -165,18 +168,18 @@ * in the test results. * * @param string $message Assertion message. - * @param string $type Assertion type. + * @param string $group Assertion group. * @param string $status Assertion status. * @param string $file File where the assertion originated. * @param string $functuion Function where the assertion originated. * @return Assertion result. */ - function assertAssertion($message, $type, $status, $file, $function) { + function assertAssertion($message, $group, $status, $file, $function) { $message = trim(strip_tags($message)); $found = FALSE; foreach ($this->results['assertions'] as $assertion) { if ((strpos($assertion['message'], $message) !== FALSE) && - $assertion['type'] == $type && + $assertion['group'] == $group && $assertion['status'] == $status && $assertion['file'] == $file && $assertion['function'] == $function) { @@ -184,7 +187,7 @@ break; } } - return $this->assertTrue($found, t('Found assertion {"@message", "@type", "@status", "@file", "@function"}.', array('@message' => $message, '@type' => $type, '@status' => $status, "@file" => $file, "@function" => $function))); + return $this->assertTrue($found, t('Found assertion {"@message", "@group", "@status", "@file", "@function"}.', array('@message' => $message, '@group' => $group, '@status' => $status, "@file" => $file, "@function" => $function))); } /** @@ -194,22 +197,31 @@ $results = array(); if ($this->parse()) { - if ($fieldset = $this->getResultFieldSet()) { + if ($container = $this->getResultContainer()) { // Code assumes this is the only test in group. - $results['summary'] = $this->asText($fieldset->div); - $results['name'] = $this->asText($fieldset->fieldset->legend); + preg_match('/^(.*?): (.*?)$/', $this->asText($container->span), $matches); + $results['name'] = $matches[1]; + $results['summary'] = $matches[2]; $results['assertions'] = array(); - $tbody = $fieldset->fieldset->table->tbody; - foreach ($tbody->tr as $row) { + $list = $container->div->ul->li; + foreach ($list as $item) { $assertion = array(); - $assertion['message'] = $this->asText($row->td[0]); - $assertion['type'] = $this->asText($row->td[1]); - $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'); - $assertion['status'] = ($row->td[5]->img['src'] == $ok_url) ? 'Pass' : 'Fail'; + + // Get status from class name. + preg_match('/simpletest-(\w+)/', $item['class'], $matches); + $assertion['status'] = $matches[1]; + + // Get message text. + $assertion['message'] = $this->asText($item->span); + + // Parse details, including: group, file, function, and line. + foreach ($item->div->ul->li as $detail) { + if (preg_match('/^(\w+): (.*?)$/', $this->asText($detail), $matches)) { + $assertion[strtolower($matches[1])] = $matches[2]; + } + } + $results['assertions'][] = $assertion; } } @@ -218,17 +230,13 @@ } /** - * Get the fieldset containing the results for group this test is in. + * Get the UL containing the results for the group this test is in. * - * @return fieldset containing the results for group this test is in. + * @return UL containing the results. */ - function getResultFieldSet() { - $fieldsets = $this->xpath('//fieldset'); - $info = $this->getInfo(); - foreach ($fieldsets as $fieldset) { - if ($fieldset->legend == $info['group']) { - return $fieldset; - } + function getResultContainer() { + if ($result = $this->xpath('//ul[@class="simpletest-results"]')) { + return $result[0]->li->div->ul->li; } return FALSE; } Index: modules/simpletest/drupal_web_test_case.php =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v retrieving revision 1.41 diff -u -r1.41 drupal_web_test_case.php --- modules/simpletest/drupal_web_test_case.php 17 Sep 2008 07:11:58 -0000 1.41 +++ modules/simpletest/drupal_web_test_case.php 18 Sep 2008 19:36:34 -0000 @@ -76,15 +76,19 @@ 'test_class' => get_class($this), 'status' => $status, 'message' => $message, - 'message_group' => $group, + 'group' => $group, 'function' => $caller['function'], 'line' => $caller['line'], 'file' => $caller['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; Index: modules/simpletest/simpletest.module =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.module,v retrieving revision 1.13 diff -u -r1.13 simpletest.module --- modules/simpletest/simpletest.module 18 Sep 2008 14:57:42 -0000 1.13 +++ modules/simpletest/simpletest.module 18 Sep 2008 19:36:35 -0000 @@ -26,6 +26,14 @@ 'page arguments' => array('simpletest_test_form'), 'description' => 'Run tests against Drupal core and your active modules. These tests help assure that your site code is working as designed.', 'access arguments' => array('administer unit tests'), + 'type' => MENU_NORMAL_ITEM, + ); + $items['admin/build/testing/results'] = array( + 'title' => 'Results', + 'page callback' => 'simpletest_display_results', + 'description' => 'View the test results.', + 'access arguments' => array('administer unit tests'), + 'type' => MENU_CALLBACK, ); return $items; } @@ -50,11 +58,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 -- List all tests that can be run. */ function simpletest_test_form() { global $db_prefix, $db_prefix_original; @@ -64,95 +75,6 @@ // List out all tests in groups for selection. $uncategorized_tests = simpletest_get_all_tests(); $tests = simpletest_categorize_tests($uncategorized_tests); - if (isset($_SESSION['test_id'])) { - // Select all results using the active test ID used to group them. - $results = db_query("SELECT * FROM {simpletest} WHERE test_id = %d ORDER BY test_class, message_id", $_SESSION['test_id']); - - $summary = array( - '#theme' => 'simpletest_result_summary', - '#pass' => 0, - '#fail' => 0, - '#exception' => 0, - '#weight' => -10, - ); - $form['summary'] = $summary; - $form['results'] = array(); - $group_summary = array(); - $map = array( - 'pass' => theme('image', 'misc/watchdog-ok.png'), - 'fail' => theme('image', 'misc/watchdog-error.png'), - 'exception' => theme('image', 'misc/watchdog-warning.png'), - ); - $header = array(t('Message'), t('Group'), t('Filename'), t('Line'), t('Function'), array('colspan' => 2, 'data' => t('Status'))); - while ($result = db_fetch_object($results)) { - $class = $result->test_class; - $info = $uncategorized_tests[$class]->getInfo(); - $group = $info['group']; - if (!isset($group_summary[$group])) { - $group_summary[$group] = $summary; - } - $element = &$form['results'][$group][$class]; - if (!isset($element)) { - $element['summary'] = $summary; - } - $status = $result->status; - // This reporter can only handle pass, fail and exception. - if (isset($map[$status])) { - $element['#title'] = $info['name']; - $status_index = '#'. $status; - $form['summary'][$status_index]++; - $group_summary[$group][$status_index]++; - $element['summary'][$status_index]++; - $element['result_table']['#rows'][] = array( - 'data' => array( - $result->message, - $result->message_group, - basename($result->file), - $result->line, - $result->function, - $map[$status], - ), - 'class' => "simpletest-$status", - ); - } - unset($element); - } - - // Clear test results. - if (variable_get('simpletest_clear_results', TRUE)) { - db_query('DELETE FROM {simpletest} WHERE test_id = %d', $_SESSION['test_id']); - db_query('DELETE FROM {simpletest_test_id} WHERE test_id = %d', $_SESSION['test_id']); - } - unset($_SESSION['test_id']); - - $all_ok = TRUE; - foreach ($form['results'] as $group => &$elements) { - $group_ok = TRUE; - foreach ($elements as $class => &$element) { - $info = $uncategorized_tests[$class]->getInfo(); - $ok = $element['summary']['#fail'] + $element['summary']['#exception'] == 0; - $element += array( - '#type' => 'fieldset', - '#collapsible' => TRUE, - '#collapsed' => $ok, - '#description' => $info['description'], - ); - $element['result_table']['#markup'] = theme('table', $header, $element['result_table']['#rows']); - $element['summary']['#ok'] = $ok; - $group_ok = $group_ok && $ok; - } - $elements += array( - '#type' => 'fieldset', - '#title' => $group, - '#collapsible' => TRUE, - '#collapsed' => $group_ok, - 'summary' => $group_summary[$group], - ); - $elements['summary']['#ok'] = $group_ok; - $all_ok = $group_ok && $all_ok; - } - $form['summary']['#ok'] = $all_ok; - } $form['tests'] = array( '#type' => 'fieldset', '#title' => t('Tests'), @@ -160,7 +82,7 @@ ); $form['tests']['table'] = array( '#theme' => 'simpletest_test_table' - ); + ); foreach ($tests as $group_name => $test_group) { foreach ($test_group as $test) { $test_info = $test->getInfo(); @@ -191,7 +113,7 @@ '#value' => t('Clean environment'), '#submit' => array('simpletest_clean_environment'), ); - + return $form; } @@ -201,17 +123,17 @@ // 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'), + 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'), - ), + 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. @@ -275,12 +197,107 @@ } } +/** + * Display the results of the last run set of tests. + */ +function simpletest_display_results() { + if (!isset($_SESSION['test_id'])) { + if (arg(4) == 're-run') { + // Create form so that submit handler is called. + drupal_get_form('simpletest_rerun_form', array('pass' => array(), 'fail' => array())); + return; + } + else { + drupal_set_message(t('No results to display.')); + drupal_goto('admin/build/testing'); + } + } + + // Ensure that all classes are loaded before we create instances to get test information. + simpletest_get_all_tests(); + + // Add JavasSript and CSS neccessary to use the dynamic results display. + drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css', 'module'); + drupal_add_js(drupal_get_path('module', 'simpletest') . '/simpletest.js', 'module'); + + $result = db_query('SELECT * FROM {simpletest} WHERE test_id = %d ORDER BY test_class, message_id', $_SESSION['test_id']); + + $results = array(); + $assertions = array(); + $classes = array(); + while ($assertion = db_fetch_object($result)) { + $class = $assertion->test_class; + + // Get the summary results, only once per test class. + if (empty($results[$class])) { + // Instantiate class and get class information. + $test = new $class(); + $info = $test->getInfo(); + + // Init summary results counters. + $results[$class] = array('#fail' => 0, '#pass' => 0, '#exception' => 0); + + // Store class info for use when themeing. + foreach ($info as $key => $value) { + $results[$class]['#' . $key] = $value; + } + } + + // Update summary counters. + $results[$class]['#' . $assertion->status]++; + + // Store assertion in nested array after converting 'message_group' to + // 'group' to avoid database field conflict with reserved word. + $assertion = (array) $assertion; + $assertion['group'] = $assertion['message_group']; + unset($assertion['message_group']); + + $assertions[$class][] = $assertion; + + // Update the state of the results for class, if any non-passes then test + // results are not "ok" and should be expanded by default. + $results[$class]['#ok'] = !($results[$class]['#fail'] + $results[$class]['#exception']); + + // Store test class for use in re-run form. + if (!array_key_exists($assertion['test_class'], $classes)) { + $classes[$assertion['test_class']] = $results[$class]['#ok']; + } + else { + // If any fails then overall fail. + $classes[$assertion['test_class']] = $classes[$assertion['test_class']] && $results[$class]['#ok']; + } + } + + // Separate lists in pass and fail. + $test_classes = array('pass' => array(), 'fail' => array()); + foreach ($classes as $class => $status) { + if ($status) { + $test_classes['pass'][] = $class; + } + else { + $test_classes['fail'][] = $class; + } + } + + // Clear test results. + if (variable_get('simpletest_clear_results', TRUE)) { + db_query('DELETE FROM {simpletest} WHERE test_id = %d', $_SESSION['test_id']); + db_query('DELETE FROM {simpletest_test_id} WHERE test_id = %d', $_SESSION['test_id']); + } + unset($_SESSION['test_id']); + + return '
' . t('The assertions listed in bold text are those made by the tests themselves, rather than the implicit framework tests.') . '
' . + drupal_get_form('simpletest_filter_form') . + theme('simpletest_results', $results, $assertions) . + drupal_get_form('simpletest_rerun_form', $test_classes); +} + function theme_simpletest_result_summary($form, $text = NULL) { return ''; + $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; +} + +/** + * Create a filter form used on the results page. + */ +function simpletest_filter_form() { + $form = array(); + $form['outcome'] = array( + '#type' => 'checkboxes', + '#title' => t('Filter by outcome'), + '#options' => array(), + '#attributes' => array('class' => 'container-inline'), + '#default_value' => array(), + ); + $categories = array('pass' => t('Passes'), 'fail' => t('Fails'), 'exception' => t('Exceptions')); + foreach ($categories as $outcome => $readable) { + $form['outcome'][$outcome] = array( + '#type' => 'checkbox', + '#title' => $readable, + '#attributes' => array('class' => 'simpletest-filter'), + '#default_value' => TRUE, + ); + } + return $form; +} + +/** + * Create a form containing the last run test classes so that they can bee run + * again. + */ +function simpletest_rerun_form($form_state, array $classes) { + $form = array(); + $form['#action'] = url('admin/build/testing/results/re-run'); + $form['classes_pass'] = array( + '#type' => 'hidden', + '#default_value' => implode(',', $classes['pass']) + ); + $form['classes_fail'] = array( + '#type' => 'hidden', + '#default_value' => implode(',', $classes['fail']) + ); + $form['actions'] = array( + '#prefix' => '