=== modified file 'modules/simpletest/drupal_web_test_case.php' --- modules/simpletest/drupal_web_test_case.php 2008-06-06 10:36:43 +0000 +++ modules/simpletest/drupal_web_test_case.php 2008-06-15 18:50:20 +0000 @@ -4,7 +4,7 @@ /** * Test case for typical Drupal tests. */ -class DrupalWebTestCase extends UnitTestCase { +class DrupalWebTestCase { protected $_logged_in = FALSE; protected $_content; protected $plain_text; @@ -24,14 +24,129 @@ class DrupalWebTestCase extends UnitTest * * @param string $label Name of the test to be used by the SimpleTest library. */ - function __construct($label = NULL) { - if (!$label) { - if (method_exists($this, 'getInfo')) { - $info = $this->getInfo(); - $label = $info['name']; + function __construct($test_id = NULL) { + if (method_exists($this, 'getInfo')) { + $info = $this->getInfo(); + $label = $info['name']; + } + $this->test_id = $test_id; + } + + /** + * This function stores the assert. Do not call directly. + * + * @param $status + * Can be 'pass', 'fail', 'exception'. TRUE is a synonym for 'pass', FALSE + * for 'fail'. + * @param $message + * The message string. + * @param $group + * WHich group this assert belongs to. + * @param $custom_caller + * By default, the assert comes from a function which names start with + * 'test'. Instead, you can specify where this assert originates from + * by passing in an associative array as $custom_caller. Key 'file' is + * the name of the source file, 'line' is the line number and 'function' + * is the caller function itself. + */ + protected function _assert($status, $message = '', $group = 'Other', $custom_caller = NULL) { + global $db_prefix; + if (is_bool($status)) { + $status = $status ? 'pass' : 'fail'; + } + if (!isset($custom_caller)) { + $callers = debug_backtrace(); + while ((list($index, $caller) = each($callers)) && substr($caller['function'], 0, 4) != 'test'); + $function = $callers[$index - 1]; + } + else { + $function = $custom_caller; + } + $current_db_prefix = $db_prefix; + $db_prefix = $this->db_prefix_original; + db_query("INSERT INTO {simpletest} (test_id, test_class, status, message, message_group, caller, line, file) VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s')", $this->test_id, get_class($this), $status, $message, $group, $function['function'], $function['line'], $function['file']); + $db_prefix = $current_db_prefix; + return $status; + } + + protected function assertTrue($value, $message = '', $group = 'Other') { + return $this->_assert((bool) $value, $message ? $message : t('%value is TRUE', array('%value' => $value)), $group); + } + + protected function assertFalse($value, $message = '', $group = 'Other') { + return $this->_assert(!$value, $message ? $message : t('%value is FALSE', array('%value' => $value)), $group); + } + + protected function assertNull($value, $message = '', $group = 'Other') { + return $this->_assert(!isset($value), $message ? $message : t('%value is NULL', array('%value' => $value)), $group); + } + + protected function assertNotNull($value, $message = '', $group = 'Other') { + return $this->_assert(isset($value), $message ? $message : t('%value is not NULL', array('%value' => $value)), $group); + } + + protected function assertEqual($first, $second, $message = '', $group = 'Other') { + return $this->_assert($first == $second, $message ? $message : t('%first is equal to %second', array('%first' => $first, '%second' => $second)), $group); + } + + protected function assertNotEqual($first, $second, $message = '', $group = 'Other') { + return $this->_assert($first != $second, $message ? $message : t('%first is not equal to %second', array('%first' => $first, '%second' => $second)), $group); + } + + protected function assertIdentical($first, $second, $message = '', $group = 'Other') { + return $this->_assert($first === $second, $message ? $message : t('%first is identical to %second', array('%first' => $first, '%second' => $second)), $group); + } + + protected function assertNotIdentical($first, $second, $message = '', $group = 'Other') { + return $this->_assert($first !== $second, $message ? $message : t('%first is not identical to %second', array('%first' => $first, '%second' => $second)), $group); + } + + protected function pass($message = NULL, $group = 'Other') { + return $this->_assert(TRUE, $message, $group); + } + + protected function fail($message = NULL, $group = 'Other') { + return $this->_assert(FALSE, $message, $group); + } + + protected function error($message = '', $group = 'Other', $custom_caller = NULL) { + return $this->_assert('exception', $message, $group, $custom_caller); + } + + function run() { + set_error_handler(array($this, 'errorHandler')); + $this->setUp(); + $methods = array(); + foreach (get_class_methods(get_class($this)) as $method) { + if (strtolower(substr($method, 0, 4)) == 'test') { + $this->$method(); } } - parent::__construct($label); + $this->tearDown(); + restore_error_handler(); + } + + function errorHandler($severity, $message, $file = NULL, $line = NULL) { + $severity = $severity & error_reporting(); + if ($severity) { + $error_map = array( + E_STRICT => 'Run-time notice', + E_WARNING => 'Warning', + E_NOTICE => 'Notice', + E_CORE_ERROR => 'Core error', + E_CORE_WARNING => 'Core warning', + E_USER_ERROR => 'User error', + E_USER_WARNING => 'User warning', + E_USER_NOTICE => 'User notice', + E_RECOVERABLE_ERROR => 'Recoverable error', + ); + $this->error($message, $error_map[$severity], array( + 'function' => '', + 'line' => $line, + 'file' => $file, + )); + } + return TRUE; } /** @@ -296,13 +411,13 @@ class DrupalWebTestCase extends UnitTest } /** - * Generates a random database prefix, runs the install scripts on the - * prefixed database and enable the specified modules. After installation - * many caches are flushed and the internal browser is setup so that the - * page requests will run on the new prefix. A temporary files directory + * Generates a random database prefix, runs the install scripts on the + * prefixed database and enable the specified modules. After installation + * many caches are flushed and the internal browser is setup so that the + * page requests will run on the new prefix. A temporary files directory * is created with the same name as the database prefix. * - * @param ... + * @param ... * List of modules to enable for the duration of the test. */ function setUp() { @@ -341,8 +456,6 @@ class DrupalWebTestCase extends UnitTest $this->original_file_directory = file_directory_path(); variable_set('file_directory_path', file_directory_path() . '/' . $db_prefix); file_check_directory(file_directory_path(), TRUE); // Create the files directory. - - parent::setUp(); } /** @@ -395,21 +508,8 @@ class DrupalWebTestCase extends UnitTest // Close the CURL handler. $this->curlClose(); + restore_error_handler(); } - parent::tearDown(); - } - - /** - * Set necessary reporter info. - */ - function run(&$reporter) { - $arr = array('class' => get_class($this)); - if (method_exists($this, 'getInfo')) { - $arr = array_merge($arr, $this->getInfo()); - } - $reporter->test_info_stack[] = $arr; - parent::run($reporter); - array_pop($reporter->test_info_stack); } /** @@ -558,7 +658,7 @@ class DrupalWebTestCase extends UnitTest } $out = $this->curlExec(array(CURLOPT_URL => $action, CURLOPT_POST => TRUE, CURLOPT_POSTFIELDS => $post)); // Ensure that any changes to variables in the other thread are picked up. - $this->refreshVariables(); + $this->refreshVariables(); return $out; } } @@ -576,15 +676,15 @@ class DrupalWebTestCase extends UnitTest * exist and attempt to create POST data in the correct manner for the particular * field type. * - * @param array $post + * @param array $post * Reference to array of post values. - * @param array $edit + * @param array $edit * Reference to array of edit values to be checked against the form. - * @param string $submit + * @param string $submit * Form submit button value. - * @param array $form + * @param array $form * Array of form elements. - * @return boolean + * @return boolean * Submit value matches a valid submit input in the form. */ protected function handleForm(&$post, &$edit, &$upload, $submit, $form) { === modified file 'modules/simpletest/simpletest.install' --- modules/simpletest/simpletest.install 2008-05-10 06:55:09 +0000 +++ modules/simpletest/simpletest.install 2008-06-15 18:06:32 +0000 @@ -5,6 +5,7 @@ * Implementation of hook_install(). */ function simpletest_install() { + drupal_install_schema('simpletest'); // Check for files directory. $path = file_directory_path() . '/simpletest'; if (file_check_directory($path, FILE_CREATE_DIRECTORY)) { @@ -95,6 +96,7 @@ function simpletest_uninstall() { variable_del('simpletest_httpauth_username'); variable_del('simpletest_httpauth_pass'); variable_del('simpletest_devel'); + drupal_uninstall_schema('simpletest'); } /** @@ -133,3 +135,85 @@ function simpletest_requirements($phase) return $requirements; } + +function simpletest_schema() { + $schema['simpletest'] = array( + 'description' => t('Stores simpletest messages'), + 'fields' => array( + 'message_id' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'description' => t('Primary Key: Unique simpletest message ID.'), + ), + 'test_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => t('Test id, messages belonging to the same id are reported together'), + ), + 'test_class' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + 'description' => t('The name of the class that created this message.'), + ), + 'status' => array( + 'type' => 'varchar', + 'length' => 9, + 'not null' => TRUE, + 'default' => '', + 'description' => t('Message status. Core understands pass, fail, exception.'), + ), + 'message' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + 'description' => t('The message itself.'), + ), + 'message_group' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + 'description' => t('The message group this message belongs to. For example: warning, browser, user.'), + ), + 'caller' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + 'description' => t('Name of the caller function or method that created this message.'), + ), + 'line' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => t('Line number of the caller.'), + ), + 'file' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + 'description' => t('Name of the file where the caller is.'), + ), + ), + 'primary key' => array('message_id'), + 'indexes' => array( + 'reporter' => array('test_class, message_id'), + ), + ); + $schema['simpletest_test_id'] = array( + 'description' => t('Stores simpletest test IDs.'), + 'fields' => array( + 'message_id' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'description' => t('Primary Key: Unique simpletest ID.'), + ), + ), + 'primary key' => array('message_id'), + ); +} === modified file 'modules/simpletest/simpletest.module' --- modules/simpletest/simpletest.module 2008-05-10 07:46:22 +0000 +++ modules/simpletest/simpletest.module 2008-06-15 19:16:43 +0000 @@ -22,7 +22,8 @@ function simpletest_help($path, $arg) { function simpletest_menu() { $items['admin/build/testing'] = array( 'title' => 'Testing', - 'page callback' => 'simpletest_entrypoint', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('simpletest_test_form'), 'description' => 'Run tests against Drupal core and your active modules. These tests help assure that your site code is working as designed.', 'access arguments' => array('administer unit tests'), ); @@ -50,104 +51,122 @@ function simpletest_perm() { */ function simpletest_theme() { return array( - 'simpletest_overview_form' => array( + 'simpletest_test_form' => array( + 'arguments' => array('form' => NULL) + ), + 'simpletest_result_summary' => array( 'arguments' => array('form' => NULL) ), ); } /** - * Try to load the simepletest - * @return boolean TRUE if the load succeeded + * Menu callback for both running tests and listing possible tests */ -function simpletest_load() { - global $user; - static $loaded; - if (!$loaded) { - $loaded = TRUE; - if ($user->uid != 1) { - drupal_set_message(t('It is strongly suggested to run the tests with the first user!')); +function simpletest_test_form() { + $form = array(); + $uncategorized_tests = simpletest_get_all_tests(); + $tests = simpletest_categorize_tests($uncategorized_tests); + if (isset($_SESSION['test_id'])) { + $results = db_query("SELECT * FROM {simpletest} WHERE test_id = %d ORDER BY test_class, message_id", $_SESSION['test_id']); + unset($_SESSION['test_id']); + $summary = array( + '#theme' => 'simpletest_result_summary', + '#pass' => 0, + '#fail' => 0, + '#exception' => 0, + '#weight' => -10, + ); + $form['summary'] = $summary; + $group_summary = array(); + $map = array( + 'pass' => array('text' => t('Pass'), 'image' => theme('image', 'misc/watchdog-ok.png')), + 'fail' => array('text' => t('Fail'), 'image' => theme('image', 'misc/watchdog-error.png')), + 'exception' => array('text' => t('Exception'), 'image' => theme('image', 'misc/watchdog-warning.png')), + ); + $header = array(t('Message'), t('Group'), t('Filename'), t('Line #'), t('Called function'), array('colspan' => 2, 'data' => t('Status'))); + while ($result = db_fetch_object($results)) { + $class = $result->test_class; + $info = $uncategorized_tests[$class]->getInfo(); + $group = $info['group']; + if (!isset($group_summary[$group])) { + $group_summary[$group] = $summary; + } + $element = &$form['results'][$group][$class]; + if (!isset($element)) { + $element['summary'] = $summary; + } + $status = $result->status; + // This reporter can only handle pass, fail and exception. + if (isset($map[$status])) { + $element['#title'] = $info['name']; + $status_index = '#'. $status; + $form['summary'][$status_index]++; + $group_summary[$group][$status_index]++; + $element['summary'][$status_index]++; + $element['result_table']['#rows'][] = array( + 'data' => array( + $result->message, + $result->message_group, + basename($result->file), + $result->line, + $result->caller, + $map[$status]['text'], + $map[$status]['image'], + ), + 'class' => "simpletest-$status", + ); + } + unset($element); } - $path = drupal_get_path('module', 'simpletest') . '/'; - foreach (array('simpletest.php', 'unit_tester.php', 'reporter.php', 'drupal_reporter.php', 'drupal_web_test_case.php', 'drupal_test_suite.php') as $file) { - require_once($path . $file); + $all_ok = TRUE; + foreach ($form['results'] as $group => &$elements) { + $group_ok = TRUE; + foreach ($elements as $class => &$element) { + $info = $uncategorized_tests[$class]->getInfo(); + $ok = $element['summary']['#fail'] + $element['summary']['#exception'] == 0; + $element += array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => $ok, + '#description' => $info['description'], + ); + $element['result_table']['#value'] = theme('table', $header, $element['result_table']['#rows']); + $element['summary']['#ok'] = $ok; + $group_ok = $group_ok && $ok; + } + $elements += array( + '#type' => 'fieldset', + '#title' => $group, + '#collapsible' => TRUE, + '#collapsed' => $group_ok, + 'summary' => $group_summary[$group], + ); + $elements['summary']['#ok'] = $group_ok; + $all_ok = $group_ok && $all_ok; } + $form['summary']['#ok'] = $all_ok; } -} - -/** - * Menu callback for both running tests and listing possible tests - */ -function simpletest_entrypoint() { - simpletest_load(); - drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css', 'module'); - drupal_add_js(drupal_get_path('module', 'simpletest') . '/simpletest.js', 'module'); - $output = drupal_get_form('simpletest_overview_form'); - - if (simpletest_running_output()) { - return simpletest_running_output() . $output; - } - else { - return $output; - } -} - -function simpletest_running_output($output = NULL) { - static $o; - if ($output != NULL) { - $o = $output; - } - return $o; -} - -/** - * Form callback; make the form to run tests - */ -function simpletest_overview_form() { - $output = array( - '#theme' => 'simpletest_overview_form' - ); - - $total_test = &simpletest_get_total_test(); - - $test_instances = $total_test->getTestInstances(); - uasort($test_instances, 'simpletest_compare_instances'); - - foreach ($test_instances as $group_test) { - $group = array(); - $tests = $group_test->getTestInstances(); - $group_class = str_replace(' ', '-', strtolower($group_test->getLabel())); - $group['tests'] = array( - '#type' => 'fieldset', - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#title' => 'Tests', - '#attributes' => array('class' => $group_class), - ); - foreach ($tests as $test) { + foreach ($tests as $group_name => $test_group) { + foreach ($test_group as $test) { $test_info = $test->getInfo(); - $group['tests'][get_class($test)] = array( + $test_class = get_class($test); + $form['tests'][$group_name][$test_class] = array( '#type' => 'checkbox', '#title' => $test_info['name'], '#default_value' => 0, '#description' => $test_info['description'], ); } - $output[] = $group + array( - '#type' => 'fieldset', - '#collapsible' => FALSE, - '#title' => $group_test->getLabel(), - '#attributes' => array('class' => 'all-tests'), - ); } - $output['run'] = array( + $form['run'] = array( '#type' => 'fieldset', '#collapsible' => FALSE, '#collapsed' => FALSE, '#title' => t('Run tests'), ); - $output['run']['running_options'] = array( + $form['run']['running_options'] = array( '#type' => 'radios', '#default_value' => 'selected_tests', '#options' => array( @@ -155,25 +174,23 @@ function simpletest_overview_form() { 'selected_tests' => t('Run selected tests'), ), ); - $output['run']['op'] = array( + $form['run']['op'] = array( '#type' => 'submit', '#value' => t('Run tests'), - '#submit' => array('simpletest_run_selected_tests') ); - - $output['reset'] = array( + $form['reset'] = array( '#type' => 'fieldset', '#collapsible' => FALSE, '#collapsed' => FALSE, '#title' => t('Clean test environment'), '#description' => t('Remove tables with the prefix "simpletest" and temporary directories that are left over from tests that crashed.') ); - $output['reset']['op'] = array( + $form['reset']['op'] = array( '#type' => 'submit', '#value' => t('Clean environment'), '#submit' => array('simpletest_clean_environment') ); - return $output; + return $form; } /** @@ -181,7 +198,9 @@ function simpletest_overview_form() { * * @ingroup themeable */ -function theme_simpletest_overview_form($form) { +function theme_simpletest_test_form($form) { + drupal_add_css(drupal_get_path('module', 'simpletest') .'/simpletest.css', 'module'); + drupal_add_js(drupal_get_path('module', 'simpletest') .'/simpletest.js', 'module'); $header = array( array('data' => t('Run'), 'class' => 'simpletest_run checkbox'), array('data' => t('Test'), 'class' => 'simpletest_test'), @@ -196,73 +215,73 @@ function theme_simpletest_overview_form( // Go through each test group and create a row: $rows = array(); - foreach (element_children($form) as $gid) { - if (isset($form[$gid]['tests'])) { - $element = &$form[$gid]; - $test_class = strtolower(trim(preg_replace("/[^\w\d]/", "-",$element["#title"]))); + foreach (element_children($form['tests']) as $key) { + $element = &$form['tests'][$key]; + $test_class = strtolower(trim(preg_replace("/[^\w\d]/", "-", $key))); + $row = array(); + $row[] = array('id' => $test_class, 'class' => 'simpletest-select-all'); + $row[] = array( + 'data' => '