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 13 Sep 2008 18:04:25 -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.7
diff -u -r1.7 simpletest.test
--- modules/simpletest/simpletest.test 10 Sep 2008 04:13:01 -0000 1.7
+++ modules/simpletest/simpletest.test 13 Sep 2008 18:04:25 -0000
@@ -81,7 +81,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.
@@ -110,7 +110,7 @@
// Generates a warning
$i = 1 / 0;
- // Call an assert function specific to that class.
+ // Call an assert function specific to the class.
$this->assertNothing();
}
@@ -125,17 +125,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()');
$this->test_ids[] = $test_id = $this->getTestIdFromResults();
$this->assertTrue($test_id, t('Found test ID in results.'));
@@ -158,18 +161,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 ($assertion['message'] == $message &&
- $assertion['type'] == $type &&
+ $assertion['group'] == $group &&
$assertion['status'] == $status &&
$assertion['file'] == $file &&
$assertion['function'] == $function) {
@@ -177,7 +180,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)));
}
/**
@@ -187,22 +190,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;
}
}
@@ -211,17 +223,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.38
diff -u -r1.38 drupal_web_test_case.php
--- modules/simpletest/drupal_web_test_case.php 10 Sep 2008 04:13:01 -0000 1.38
+++ modules/simpletest/drupal_web_test_case.php 13 Sep 2008 18:04:25 -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.12
diff -u -r1.12 simpletest.module
--- modules/simpletest/simpletest.module 10 Sep 2008 04:13:01 -0000 1.12
+++ modules/simpletest/simpletest.module 13 Sep 2008 18:04:25 -0000
@@ -26,6 +26,14 @@
'page arguments' => array('simpletest_test_form'),
'description' => 'Run tests against Drupal core and your active modules. These tests help assure that your site code is working as designed.',
'access arguments' => array('administer unit tests'),
+ 'type' => MENU_NORMAL_ITEM,
+ );
+ $items['admin/build/testing/results'] = array(
+ 'title' => 'Results',
+ 'page callback' => 'simpletest_display_results',
+ 'description' => 'View the test results.',
+ 'access arguments' => array('administer unit tests'),
+ 'type' => MENU_CALLBACK,
);
return $items;
}
@@ -50,110 +58,34 @@
'simpletest_result_summary' => array(
'arguments' => array('form' => NULL)
),
+ 'simpletest_results' => array(
+ 'arguments' => array('results' => NULL, 'assertions' => NULL)
+ ),
);
}
/**
- * Menu callback for both running tests and listing possible tests
+ * Menu callback -- List all tests that can be run.
*/
function simpletest_test_form() {
global $db_prefix, $db_prefix_original;
+
$form = array();
+ $form['run_all'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => 0,
+ '#attributes' => array('class' => 'simpletest-master'),
+ );
+
+ // List out all tests in groups for selection.
$uncategorized_tests = simpletest_get_all_tests();
$tests = simpletest_categorize_tests($uncategorized_tests);
- if (isset($_SESSION['test_id'])) {
- // Select all results using the active test ID used to group them.
- $results = db_query("SELECT * FROM {simpletest} WHERE test_id = %d ORDER BY test_class, message_id", $_SESSION['test_id']);
-
- $summary = array(
- '#theme' => 'simpletest_result_summary',
- '#pass' => 0,
- '#fail' => 0,
- '#exception' => 0,
- '#weight' => -10,
- );
- $form['summary'] = $summary;
- $form['results'] = array();
- $group_summary = array();
- $map = array(
- 'pass' => theme('image', 'misc/watchdog-ok.png'),
- 'fail' => theme('image', 'misc/watchdog-error.png'),
- 'exception' => theme('image', 'misc/watchdog-warning.png'),
- );
- $header = array(t('Message'), t('Group'), t('Filename'), t('Line'), t('Function'), array('colspan' => 2, 'data' => t('Status')));
- while ($result = db_fetch_object($results)) {
- $class = $result->test_class;
- $info = $uncategorized_tests[$class]->getInfo();
- $group = $info['group'];
- if (!isset($group_summary[$group])) {
- $group_summary[$group] = $summary;
- }
- $element = &$form['results'][$group][$class];
- if (!isset($element)) {
- $element['summary'] = $summary;
- }
- $status = $result->status;
- // This reporter can only handle pass, fail and exception.
- if (isset($map[$status])) {
- $element['#title'] = $info['name'];
- $status_index = '#'. $status;
- $form['summary'][$status_index]++;
- $group_summary[$group][$status_index]++;
- $element['summary'][$status_index]++;
- $element['result_table']['#rows'][] = array(
- 'data' => array(
- $result->message,
- $result->message_group,
- basename($result->file),
- $result->line,
- $result->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;
- }
foreach ($tests as $group_name => $test_group) {
foreach ($test_group as $test) {
$test_info = $test->getInfo();
$test_class = get_class($test);
+
$form['tests'][$group_name][$test_class] = array(
'#type' => 'checkbox',
'#title' => $test_info['name'],
@@ -163,21 +95,8 @@
}
}
- $form['run'] = array(
- '#type' => 'fieldset',
- '#collapsible' => FALSE,
- '#collapsed' => FALSE,
- '#title' => t('Run tests'),
- );
- $form['run']['running_options'] = array(
- '#type' => 'radios',
- '#default_value' => 'selected_tests',
- '#options' => array(
- 'all_tests' => t('Run all tests (WARNING, this may take a long time)'),
- 'selected_tests' => t('Run selected tests'),
- ),
- );
- $form['run']['op'] = array(
+ // Action buttons.
+ $form['op'] = array(
'#type' => 'submit',
'#value' => t('Run tests'),
);
@@ -186,7 +105,8 @@
'#collapsible' => FALSE,
'#collapsed' => FALSE,
'#title' => t('Clean test environment'),
- '#description' => t('Remove tables with the prefix "simpletest" and temporary directories that are left over from tests that crashed.')
+ '#description' => t('Remove tables with the prefix "simpletest" and temporary directories that are left over from' .
+ 'tests that crashed. This is intended for developers when creating tests.')
);
$form['reset']['op'] = array(
'#type' => 'submit',
@@ -202,13 +122,18 @@
* @ingroup themeable
*/
function theme_simpletest_test_form($form) {
- drupal_add_css(drupal_get_path('module', 'simpletest') .'/simpletest.css', 'module');
- drupal_add_js(drupal_get_path('module', 'simpletest') .'/simpletest.js', 'module');
+ drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css', 'module');
+ drupal_add_js(drupal_get_path('module', 'simpletest') . '/simpletest.js', 'module');
+
+ // Create header for test selection table.
$header = array(
- array('data' => t('Run'), 'class' => 'simpletest_run checkbox'),
+ array('data' => drupal_render($form['run_all']), 'class' => 'simpletest_run checkbox'),
array('data' => t('Test'), 'class' => 'simpletest_test'),
array('data' => t('Description'), 'class' => 'simpletest_description'),
);
+ unset($form['run_all']);
+
+ // Define the images used to expand/collapse the test groups.
$js = array(
'images' => array(
theme('image', 'misc/menu-collapsed.png', 'Expand', 'Expand'),
@@ -216,61 +141,172 @@
),
);
- // Go through each test group and create a row:
+ // Go through each test group and create a row.
$rows = array();
foreach (element_children($form['tests']) as $key) {
$element = &$form['tests'][$key];
- $test_class = strtolower(trim(preg_replace("/[^\w\d]/", "-", $key)));
$row = array();
+
+ // Make the class name safe for output on the pace by replacing all
+ // non-word/decimal characters with a dash (-).
+ $test_class = strtolower(trim(preg_replace("/[^\w\d]/", "-", $key)));
+
+ // Place-holder for checkboxes to select group of tests.
$row[] = array('id' => $test_class, 'class' => 'simpletest-select-all');
+
+ // Expand/collapse image and group title.
$row[] = array(
- 'data' => '
'. $js['images'][0] .'
'. $key .' ',
+ 'data' => '' . $js['images'][0] . '
' .
+ '' . $key . ' ',
'style' => 'font-weight: bold;'
);
+
$row[] = isset($element['#description']) ? $element['#description'] : ' ';
$rows[] = array('data' => $row, 'class' => 'simpletest-group');
- $current_js = array('testClass' => $test_class .'-test', 'testNames' => array(), 'imageDirection' => 0, 'clickActive' => FALSE);
+ // Add individual tests to group.
+ $current_js = array('testClass' => $test_class . '-test', 'testNames' => array(), 'imageDirection' => 0, 'clickActive' => FALSE);
foreach (element_children($element) as $test_name) {
- $current_js['testNames'][] = 'edit-'. $test_name;
$test = $element[$test_name];
- foreach (array('title', 'description') as $key) {
- $$key = $test['#'. $key];
- unset($test['#'. $key]);
- }
- $test['#name'] = $test_name;
- $themed_test = drupal_render($test);
$row = array();
- $row[] = $themed_test;
- $row[] = theme('indentation', 1) .''. $title .' ';
- $row[] = ''. $description .'
';
- $rows[] = array('data' => $row, 'style' => 'display: none;', 'class' => $test_class .'-test');
+
+ $current_js['testNames'][] = 'edit-' . $test_name;
+
+ // Store test title and description so that checkbox won't render them.
+ $title = $test['#title'];
+ $description = $test['#description'];
+
+ unset($test['#title']);
+ unset($test['#description']);
+
+ // Test name is used to determine what tests to run.
+ $test['#name'] = $test_name;
+
+ $row[] = drupal_render($test);
+ $row[] = theme('indentation', 1) . '' . $title . ' ';
+ $row[] = '' . $description . '
';
+ $rows[] = array('data' => $row, 'style' => 'display: none;', 'class' => $test_class . '-test');
}
$js['simpletest-test-group-'. $test_class] = $current_js;
}
+
+ // Remove already rendered tests.
unset($form['tests']);
+
+ // Add js array of settings.
drupal_add_js(array('simpleTest' => $js), 'setting');
- // Output test groups:
- $output = '';
- if (isset($form['results'])) {
- $output .= drupal_render($form['summary']);
- $output .= drupal_render($form['results']);
+
+ $output = '' . t('Select the tests you would like to run, and click Run tests .') . '
';
+ if (empty($rows)) {
+ $output .= '' . t('No tests to display.') . ' ';
}
- if (count($rows)) {
+ else {
$output .= theme('table', $header, $rows, array('id' => 'simpletest-form-table'));
}
- // Output the rest of the form, excluded test groups which have been removed:
+
+ // Output the rest of the form, excluded tests which have been removed.
$output .= drupal_render($form);
return $output;
}
-function theme_simpletest_result_summary($form, $text = NULL) {
- return '' . _simpletest_format_summary_line($form) . '
';
+/**
+ * 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 _simpletest_format_summary_line($summary) {
- return t('@pass, @fail, @exception', array(
+ return t('@pass, @fail, and @exception', array(
'@pass' => format_plural(isset($summary['#pass']) ? $summary['#pass'] : 0, '1 pass', '@count passes'),
'@fail' => format_plural(isset($summary['#fail']) ? $summary['#fail'] : 0, '1 fail', '@count fails'),
'@exception' => format_plural(isset($summary['#exception']) ? $summary['#exception'] : 0, '1 exception', '@count exceptions'),
@@ -281,17 +317,20 @@
* Run selected tests.
*/
function simpletest_test_form_submit($form, &$form_state) {
- $output = '';
- $batch_mode = !preg_match("/^simpletest\d+$/", $_SERVER['HTTP_USER_AGENT']);
- $tests_list = array();
- $run_all_tests = $form_state['values']['running_options'] == 'all_tests';
+ // Ensure that all classes are loaded before we create instances to get test information and run.
simpletest_get_all_tests();
+
+ // Get list of tests.
+ $tests_list = array();
foreach ($form_state['values'] as $class_name => $value) {
- if (class_exists($class_name) && ($value === 1 || $run_all_tests)) {
+ if (class_exists($class_name) && $value === 1) {
$tests_list[] = $class_name;
}
}
- if (count($tests_list) > 0 ) {
+
+ // If there are selected tests then run them.
+ if (!empty($tests_list)) {
+ $batch_mode = !preg_match("/^simpletest\d+$/", $_SERVER['HTTP_USER_AGENT']);
simpletest_run_tests($tests_list, 'drupal', $batch_mode);
}
else {
@@ -315,16 +354,20 @@
$test_id = db_insert('simpletest_test_id')->useDefaults(array('test_id'))->execute();
if ($batch_mode) {
+ $first_test = array_shift($test_list);
+ $first_instance = new $first_test();
+ array_unshift($test_list, $first_test);
+ $info = $first_instance->getInfo();
$batch = array(
'title' => t('Running SimpleTests'),
'operations' => array(
array('_simpletest_batch_operation', array($test_list, $test_id)),
),
'finished' => '_simpletest_batch_finished',
- 'redirect' => 'admin/build/testing',
- 'progress_message' => t('Processing tests.'),
+ 'progress_message' => '',
'css' => array(drupal_get_path('module', 'simpletest') .'/simpletest.css'),
- 'init_message' => t('SimpleTest is initializing...') . ' ' . format_plural(count($test_list), "one test case will run.", "@count test cases will run."),
+ 'js' => array(drupal_get_path('module', 'simpletest') .'/simpletest.js'),
+ 'init_message' => t('Processing test @num of @max - %test.', array('%test' => $info['name'], '@num' => '1', '@max' => count($test_list))),
);
batch_set($batch);
}
@@ -335,6 +378,7 @@
$test->run();
}
$_SESSION['test_id'] = $test_id;
+ drupal_goto('admin/build/testing/results');
}
}
@@ -374,12 +418,11 @@
$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]))) . '
';
+ array_unshift($items, '' . t('@name: @summary', array('@name' => $test_results[$class]['#name'], '@summary' => _simpletest_format_summary_line($test_results[$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?
+ $context['message'] = t('Processed test @num of @max - %test.', array('%test' => $info['name'], '@num' => $max - $size, '@max' => $max));
+ $context['message'] .= 'Overall results: ' . _simpletest_format_summary_line($test_results) . '
';
+ $context['message'] .= theme('item_list', $items);
// Save working values for the next iteration.
$context['sandbox']['tests'] = $test_list;
@@ -392,13 +435,16 @@
}
function _simpletest_batch_finished($success, $results, $operations) {
- $_SESSION['test_id'] = $results['test_id'];
+ if (isset($results['test_id'])) {
+ $_SESSION['test_id'] = $results['test_id'];
+ }
if ($success) {
drupal_set_message(t('The tests have finished running.'));
}
else {
drupal_set_message(t('The tests did not successfully finish.'), 'error');
}
+ drupal_goto('admin/build/testing/results');
}
/**
@@ -465,6 +511,237 @@
}
/**
+ * 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;
+}
+
+/**
+ * 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' => '',
+ '#suffix' => '
',
+ );
+ $form['actions']['filter'] = array(
+ '#type' => 'select',
+ '#title' => t('Filter'),
+ '#options' => array(
+ 'all' => t('All'),
+ 'pass' => t('Pass'),
+ 'fail' => t('Fail'),
+ ),
+ );
+ // If any tests fail select that as the default.
+ if (!empty($classes['fail'])) {
+ $form['actions']['filter']['#default_value'] = 'fail';
+ }
+
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Re-run tests'),
+ );
+ $form['actions']['cancel'] = array(
+ '#markup' => l(t('Return to testing overview page'), 'admin/build/testing'),
+ );
+ return $form;
+}
+
+/**
+ * Re-run selected tests.
+ */
+function simpletest_rerun_form_submit($form, &$form_state) {
+ // Simulate the checkboxes on the test select form by setting each of the
+ // test classes as a value in the form state.
+ $classes = array();
+ switch ($form_state['values']['filter']) {
+ case 'all':
+ $classes = array_merge(explode(',', $form_state['values']['classes_pass']), explode(',', $form_state['values']['classes_fail']));
+ break;
+ case 'pass':
+ $classes = explode(',', $form_state['values']['classes_pass']);
+ break;
+ case 'fail':
+ $classes = explode(',', $form_state['values']['classes_fail']);
+ break;
+ }
+
+ $found_tests = FALSE;
+ foreach ($classes as $class) {
+ if ($class) {
+ $form_state['values'][$class] = 1;
+ $found_tests = TRUE;
+ }
+ }
+
+ if (!$found_tests) {
+ drupal_set_message(t('No test to re-run.'), 'error');
+ drupal_goto('admin/build/testing');
+ }
+
+ // Use the existing submit handler to run the tests.
+ simpletest_test_form_submit($form, $form_state);
+}
+
+/**
+ * Get the appropriate css class based on the assertions status and importance.
+ *
+ * @param $assertion Assertion item aray.
+ * @param $important Importance of assertion.
+ * @return CSS class.
+ */
+function simpletest_get_assertion_css_class($assertion, $important) {
+ switch ($assertion['status']) {
+ case 'pass':
+ $css_class = 'simpletest-pass';
+ break;
+ case 'fail':
+ $css_class = 'simpletest-fail';
+ break;
+ case 'exception':
+ $css_class = 'simpletest-exception';
+ break;
+ }
+ if ($important) {
+ $css_class .= ' simpletest-important';
+ }
+ return $css_class;
+}
+
+/**
+ * Get the status message text
+ *
+ * @param $assertion Assertion item aray.
+ * @return Status message text.
+ */
+function simpletest_get_status_message($assertion) {
+ switch ($assertion['status']) {
+ case 'pass':
+ return t('PASS');
+ case 'fail':
+ return t('FAIL');
+ case 'exception':
+ return t('EXCEPTION');
+ }
+ return t('UNKNOWN');
+}
+
+/**
* Remove all temporary database tables and directories.
*/
function simpletest_clean_environment() {
Index: modules/simpletest/simpletest.js
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.js,v
retrieving revision 1.2
diff -u -r1.2 simpletest.js
--- modules/simpletest/simpletest.js 23 Apr 2008 18:50:49 -0000 1.2
+++ modules/simpletest/simpletest.js 13 Sep 2008 18:04:25 -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 13 Sep 2008 18:04:24 -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.282
diff -u -r1.282 form.inc
--- includes/form.inc 6 Sep 2008 08:36:19 -0000 1.282
+++ includes/form.inc 13 Sep 2008 18:04:24 -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 13 Sep 2008 18:04:24 -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 13 Sep 2008 18:04:24 -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 13 Sep 2008 18:04:24 -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'));
}
}