? .DS_Store ? sequences.patch ? simpletest_batchapi_243773-77.patch ? simpletest_batchapi_243773-77.patch.1 ? simpletest_batchapi_882768_80.patch ? simpletest_batchapi_882768_82.patch ? system_modules_cleanup_1.patch ? taxonomy_dx_kitchen_sink.patch ? includes/.DS_Store ? modules/.DS_Store ? modules/aggregator/.DS_Store ? modules/simpletest/.DS_Store ? modules/system/.DS_Store ? modules/user/.DS_Store ? profiles/default/.DS_Store ? sites/all/modules ? sites/default/files ? sites/default/settings.php Index: modules/simpletest/drupal_web_test_case.php =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v retrieving revision 1.18 diff -u -p -r1.18 drupal_web_test_case.php --- modules/simpletest/drupal_web_test_case.php 10 Jun 2008 19:39:29 -0000 1.18 +++ modules/simpletest/drupal_web_test_case.php 16 Jun 2008 03:42:51 -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,21 +24,287 @@ 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(); + // Get rid of this function. + array_shift($callers); + foreach ($callers as $caller) { + if (substr($caller['function'], 0, 4) != 'assert') { + break; + } } + $function = $caller; } - parent::__construct($label); + 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; + } + + /** + * Check to see if a value is not false (not an empty string, 0, NULL, or FALSE). + * + * @param $value + * The value on which the assertion is to be done. + * @param $message + * The message to display along with the assertion. + * @param $group + * The type of assertion - examples are "Browser", "PHP". + * @return + * The status passed in. + */ + protected function assertTrue($value, $message = '', $group = 'Other') { + return $this->_assert((bool) $value, $message ? $message : t('%value is TRUE', array('%value' => $value)), $group); + } + + /** + * Check to see if a value is false (an empty string, 0, NULL, or FALSE). + * + * @param $value + * The value on which the assertion is to be done. + * @param $message + * The message to display along with the assertion. + * @param $group + * The type of assertion - examples are "Browser", "PHP". + * @return + * The status passed in. + */ + protected function assertFalse($value, $message = '', $group = 'Other') { + return $this->_assert(!$value, $message ? $message : t('%value is FALSE', array('%value' => $value)), $group); + } + + /** + * Check to see if a value is NULL. + * + * @param $value + * The value on which the assertion is to be done. + * @param $message + * The message to display along with the assertion. + * @param $group + * The type of assertion - examples are "Browser", "PHP". + * @return + * The status passed in. + */ + protected function assertNull($value, $message = '', $group = 'Other') { + return $this->_assert(!isset($value), $message ? $message : t('%value is NULL', array('%value' => $value)), $group); + } + + /** + * Check to see if a value is not NULL. + * + * @param $value + * The value on which the assertion is to be done. + * @param $message + * The message to display along with the assertion. + * @param $group + * The type of assertion - examples are "Browser", "PHP". + * @return + * The status passed in. + */ + protected function assertNotNull($value, $message = '', $group = 'Other') { + return $this->_assert(isset($value), $message ? $message : t('%value is not NULL', array('%value' => $value)), $group); + } + + /** + * Check to see if two values are equal. + * + * @param $first + * The first value to check. + * @param $second + * The second value to check. + * @param $message + * The message to display along with the assertion. + * @param $group + * The type of assertion - examples are "Browser", "PHP". + * @return + * The status passed in. + */ + 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); + } + + /** + * Check to see if two values are not equal. + * + * @param $first + * The first value to check. + * @param $second + * The second value to check. + * @param $message + * The message to display along with the assertion. + * @param $group + * The type of assertion - examples are "Browser", "PHP". + * @return + * The status passed in. + */ + 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); + } + + /** + * Check to see if two values are identical. + * + * @param $first + * The first value to check. + * @param $second + * The second value to check. + * @param $message + * The message to display along with the assertion. + * @param $group + * The type of assertion - examples are "Browser", "PHP". + * @return + * The status passed in. + */ + 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); + } + + /** + * Check to see if two values are not identical. + * + * @param $first + * The first value to check. + * @param $second + * The second value to check. + * @param $message + * The message to display along with the assertion. + * @param $group + * The type of assertion - examples are "Browser", "PHP". + * @return + * The status passed in. + */ + 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); + } + + /** + * Fire an assertion that is always positive. + * + * @param $message + * The message to display along with the assertion. + * @param $group + * The type of assertion - examples are "Browser", "PHP". + * @return + * TRUE. + */ + protected function pass($message = NULL, $group = 'Other') { + return $this->_assert(TRUE, $message, $group); + } + + /** + * Fire an assertion that is always negative. + * + * @param $message + * The message to display along with the assertion. + * @param $group + * The type of assertion - examples are "Browser", "PHP". + * @return + * FALSE. + */ + protected function fail($message = NULL, $group = 'Other') { + return $this->_assert(FALSE, $message, $group); + } + + /** + * Fire an error assertion. + * + * @param $message + * 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); + } + + /** + * Run all tests in this class. + */ + function run() { + set_error_handler(array($this, 'errorHandler')); + $this->setUp(); + $methods = array(); + // Iterate through all the methods in this class. + foreach (get_class_methods(get_class($this)) as $method) { + // If the current method starts with "test", run it - it's a test. + if (strtolower(substr($method, 0, 4)) == 'test') { + $this->$method(); + } + } + // Finish up. + $this->tearDown(); + restore_error_handler(); + } + + /** + * Handle errors. + * + * @see set_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; } /** * Creates a node based on default settings. * * @param settings - * An assocative array of settings to change from the defaults, keys are + * An associative array of settings to change from the defaults, keys are * node properties, for example 'body' => 'Hello, world!'. * @return object Created node object. */ @@ -82,10 +348,11 @@ class DrupalWebTestCase extends UnitTest /** * Creates a custom content type based on default settings. * - * @param settings + * @param $settings * An array of settings to change from the defaults. * Example: 'type' => 'foo'. - * @return object Created content type. + * @return + * Created content type. */ function drupalCreateContentType($settings = array()) { // find a non-existent random type name. @@ -126,9 +393,12 @@ class DrupalWebTestCase extends UnitTest /** * Get a list files that can be used in tests. * - * @param string $type File type, possible values: 'binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'. - * @param integer $size File size in bytes to match. Please check the tests/files folder. - * @return array List of files that match filter. + * @param $type + * File type, possible values: 'binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'. + * @param $size + * File size in bytes to match. Please check the tests/files folder. + * @return + * List of files that match filter. */ function drupalGetTestFiles($type, $size = NULL) { $files = array(); @@ -166,9 +436,12 @@ class DrupalWebTestCase extends UnitTest /** * Generates a random string. * - * @param integer $number Number of characters in length to append to the prefix. - * @param string $prefix Prefix to use. - * @return string Randomly generated string. + * @param $number + * Number of characters in length to append to the prefix. + * @param $prefix + * Prefix to use. + * @return + * Randomly generated string. */ function randomName($number = 4, $prefix = 'simpletest_') { $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'; @@ -185,8 +458,10 @@ class DrupalWebTestCase extends UnitTest * Create a user with a given set of permissions. The permissions correspond to the * names given on the privileges page. * - * @param array $permissions Array of permission names to assign to user. - * @return A fully loaded user object with pass_raw property, or FALSE if account + * @param $permissions + * Array of permission names to assign to user. + * @return + * A fully loaded user object with pass_raw property, or FALSE if account * creation fails. */ function drupalCreateUser($permissions = NULL) { @@ -218,8 +493,10 @@ class DrupalWebTestCase extends UnitTest /** * Internal helper function; Create a role with specified permissions. * - * @param array $permissions Array of permission names to assign to role. - * @return integer Role ID of newly created role, or FALSE if role creation failed. + * @param $permissions + * Array of permission names to assign to role. + * @return + * Role ID of newly created role, or FALSE if role creation failed. */ private function _drupalCreateRole($permissions = NULL) { // Generate string version of permissions list. @@ -253,9 +530,12 @@ class DrupalWebTestCase extends UnitTest /** * Check to make sure that the array of permissions are valid. * - * @param array $permissions Permissions to check. - * @param boolean $reset Reset cached available permissions. - * @return boolean Valid. + * @param $permissions + * Permissions to check. + * @param $reset + * Reset cached available permissions. + * @return + * TRUE or FALSE depending on whether the permissions are valid. */ private function checkPermissions(array $permissions, $reset = FALSE) { static $available; @@ -279,9 +559,11 @@ class DrupalWebTestCase extends UnitTest * user out before logging in the specified user. If no user is specified then a new * user will be created and logged in. * - * @param object $user User object representing the user to login. - * @return object User that was logged in. Useful if no user was passed in order - * to retreive the created user. + * @param $user + * User object representing the user to login. + * @return + * User that was logged in. Useful if no user was passed in order to retrieve + * the created user. */ function drupalLogin($user = NULL) { if ($this->_logged_in) { @@ -369,8 +651,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(); } /** @@ -423,27 +703,14 @@ 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); } /** * Initializes the cURL connection and gets a session cookie. * - * This function will add authentaticon headers as specified in + * This function will add authentication headers as specified in * simpletest_httpauth_username and simpletest_httpauth_pass variables. * Also, see the description of $curl_options among the properties. */ @@ -471,10 +738,12 @@ class DrupalWebTestCase extends UnitTest } /** - * Peforms a cURL exec with the specified options after calling curlConnect(). + * Performs a cURL exec with the specified options after calling curlConnect(). * - * @param array $curl_options Custom cURL options. - * @return string Content returned from the exec. + * @param + * $curl_options Custom cURL options. + * @return + * Content returned from the exec. */ protected function curlExec($curl_options) { $this->curlConnect(); @@ -498,9 +767,10 @@ class DrupalWebTestCase extends UnitTest } /** - * Parse content returned from curlExec using DOM and simplexml. + * Parse content returned from curlExec using DOM and SimpleXML. * - * @return SimpleXMLElement A SimpleXMLElement or FALSE on failure. + * @return + * A SimpleXMLElement or FALSE on failure. */ protected function parse() { if (!$this->elements) { @@ -523,9 +793,12 @@ class DrupalWebTestCase extends UnitTest /** * Retrieves a Drupal path or an absolute path. * - * @param $path string Drupal path or url to load into internal browser - * @param array $options Options to be forwarded to url(). - * @return The retrieved HTML string, also available as $this->drupalGetContent() + * @param $path + * Drupal path or url to load into internal browser + * @param $options + * Options to be forwarded to url(). + * @return + * The retrieved HTML string, also available as $this->drupalGetContent() */ function drupalGet($path, $options = array()) { $options['absolute'] = TRUE; @@ -542,14 +815,14 @@ class DrupalWebTestCase extends UnitTest * Execute a POST request on a Drupal page. * It will be done as usual POST request with SimpleBrowser. * - * @param string $path + * @param $path * Location of the post form. Either a Drupal path or an absolute path or * NULL to post to the current page. - * @param array $edit + * @param $edit * Field data in an assocative array. Changes the current input fields * (where possible) to the values indicated. A checkbox can be set to * TRUE to be checked and FALSE to be unchecked. - * @param string $submit + * @param $submit * Value of the submit button. * @param $tamper * If this is set to TRUE then you can post anything, otherwise hidden and @@ -604,15 +877,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 $post * Reference to array of post values. - * @param array $edit + * @param $edit * Reference to array of edit values to be checked against the form. - * @param string $submit + * @param $submit * Form submit button value. - * @param array $form + * @param $form * Array of form elements. - * @return boolean + * @return * Submit value matches a valid submit input in the form. */ protected function handleForm(&$post, &$edit, &$upload, $submit, $form) { @@ -733,8 +1006,10 @@ class DrupalWebTestCase extends UnitTest /** * Get all option elements, including nested options, in a select. * - * @param SimpleXMLElement $element - * @return array Option elements in select. + * @param $element + * The element for which to get the options. + * @return + * Option elements in select. */ private function getAllOptions(SimpleXMLElement $element) { $options = array(); @@ -759,10 +1034,12 @@ class DrupalWebTestCase extends UnitTest * for successful click. * WARNING: Assertion fails on empty ("") output from the clicked link. * - * @param string $label Text between the anchor tags. - * @param integer $index Link position counting from zero. - * @param boolean $reporting Assertions or not. - * @return boolean/string Page on success. + * @param $label + * Text between the anchor tags. + * @param $index + * Link position counting from zero. + * @return + * Page on success, or FALSE on failure. */ function clickLink($label, $index = 0) { $url_before = $this->getUrl(); @@ -782,7 +1059,7 @@ class DrupalWebTestCase extends UnitTest /** * Takes a path and returns an absolute path. * - * @param @path + * @param $path * The path, can be a Drupal path or a site-relative path. It might have a * query, too. Can even be an absolute path which is just passed through. * @return @@ -810,7 +1087,8 @@ class DrupalWebTestCase extends UnitTest /** * Get the current url from the cURL handler. * - * @return string current url. + * @return + * The current url. */ function getUrl() { return curl_getinfo($this->ch, CURLINFO_EFFECTIVE_URL); Index: modules/simpletest/simpletest.install =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.install,v retrieving revision 1.3 diff -u -p -r1.3 simpletest.install --- modules/simpletest/simpletest.install 10 May 2008 06:55:09 -0000 1.3 +++ modules/simpletest/simpletest.install 16 Jun 2008 03:42:52 -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,122 @@ function simpletest_requirements($phase) return $requirements; } + +/** + * Implementation of hook_schema(). + */ +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( + 'test_id' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'description' => t('Primary Key: Unique simpletest ID.'), + ), + ), + 'primary key' => array('test_id'), + ); + return $schema; +} + +/** + * Update function to add SimpleTest tables. + */ +function simpletest_update_7000() { + $schema['simpletest'] = array( + 'description' => t('Stores simpletest messages'), + 'fields' => array( + 'message_id' => array('type' => 'serial', 'not null' => TRUE), + 'test_id' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), + 'test_class' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''), + 'status' => array('type' => 'varchar', 'length' => 9, 'not null' => TRUE, 'default' => ''), + 'message' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''), + 'message_group' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '',), + 'caller' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''), + 'line' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), + 'file' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''), + ), + 'primary key' => array('message_id'), + 'indexes' => array('reporter' => array('test_class', 'message_id')), + ); + $schema['simpletest_test_id'] = array( + 'fields' => array( + 'test_id' => array('type' => 'serial', 'not null' => TRUE), + ), + 'primary key' => array('test_id'), + ); + + $ret = array(); + db_create_table($ret, 'simpletest', $schema['simpletest']); + db_create_table($ret, 'simpletest_test_id', $schema['simpletest_test_id']); + return $ret; +} Index: modules/simpletest/simpletest.module =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.module,v retrieving revision 1.3 diff -u -p -r1.3 simpletest.module --- modules/simpletest/simpletest.module 10 May 2008 07:46:22 -0000 1.3 +++ modules/simpletest/simpletest.module 16 Jun 2008 03:42:52 -0000 @@ -1,4 +1,5 @@ '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 +52,42 @@ 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 - */ -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!')); - } - $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); - } - } -} - -/** * 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) { +function simpletest_test_form() { + $form = array(); + $uncategorized_tests = simpletest_get_all_tests(); + $tests = simpletest_categorize_tests($uncategorized_tests); + 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 +95,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 +119,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 +136,73 @@ function theme_simpletest_overview_form( // Go through each test group and create a row: $rows = array(); - foreach (element_children($form) as $gid) { - if (isset($form[$gid]['tests'])) { - $element = &$form[$gid]; - $test_class = strtolower(trim(preg_replace("/[^\w\d]/", "-",$element["#title"]))); + foreach (element_children($form['tests']) as $key) { + $element = &$form['tests'][$key]; + $test_class = strtolower(trim(preg_replace("/[^\w\d]/", "-", $key))); + $row = array(); + $row[] = array('id' => $test_class, 'class' => 'simpletest-select-all'); + $row[] = array( + 'data' => '
'. $js['images'][0] .'
 ', + 'style' => 'font-weight: bold;' + ); + $row[] = isset($element['#description']) ? $element['#description'] : ' '; + $rows[] = array('data' => $row, 'class' => 'simpletest-group'); - $row = array(); - $row[] = array('id' => $test_class, 'class' => 'simpletest-select-all'); - $row[] = array( - 'data' => '
' . $js['images'][0] . '
 ', - ); - $row[] = $element['#description']; - $rows[] = array('data' => $row, 'class' => 'simpletest-group'); - $current_js = array('testClass' => $test_class . '-test', 'testNames' => array(), 'imageDirection' => 0, 'clickActive' => FALSE); - - // Go through each test in the group and create table rows setting them to invisible: - foreach (element_children($element['tests']) as $test_name) { - $current_js['testNames'][] = 'edit-' . $test_name; - $test = $element['tests'][$test_name]; - foreach (array('title', 'description') as $key) { - $$key = $test['#' . $key]; - unset($test['#' . $key]); - } - $test['#name'] = $test_name; - $themed_test = drupal_render($test); - $row = array(); - $row[] = $themed_test; - $row[] = theme('indentation', 1) . ''; - $row[] = '
' . $description . '
'; - $rows[] = array('data' => $row, 'style' => 'display: none;', 'class' => $test_class . '-test'); + $current_js = array('testClass' => $test_class .'-test', 'testNames' => array(), 'imageDirection' => 0, 'clickActive' => FALSE); + foreach (element_children($element) as $test_name) { + $current_js['testNames'][] = 'edit-'. $test_name; + $test = $element[$test_name]; + foreach (array('title', 'description') as $key) { + $$key = $test['#'. $key]; + unset($test['#'. $key]); } - $js['simpletest-test-group-' . $test_class] = $current_js; - unset($form[$gid]); // Remove test group from form. + $test['#name'] = $test_name; + $themed_test = drupal_render($test); + $row = array(); + $row[] = $themed_test; + $row[] = theme('indentation', 1) .''; + $row[] = '
'. $description .'
'; + $rows[] = array('data' => $row, 'style' => 'display: none;', 'class' => $test_class .'-test'); } + $js['simpletest-test-group-'. $test_class] = $current_js; } + unset($form['tests']); drupal_add_js(array('simpleTest' => $js), 'setting'); - // Output test groups: $output = ''; + if (isset($form['results'])) { + $output .= drupal_render($form['summary']); + $output .= drupal_render($form['results']); + } if (count($rows)) { $output .= theme('table', $header, $rows, array('id' => 'simpletest-form-table')); } - // Output the rest of the form, excluded test groups which have been removed: $output .= drupal_render($form); return $output; } -/** - * Compare two test instance objects for use in sorting. - */ -function simpletest_compare_instances(&$a, &$b) { - if (substr_compare($a->_label, $b->_label, 0) > 0) { - return 1; - } - return -1; +function theme_simpletest_result_summary($form) { + return '
' . _simpletest_format_summary_line($form) . '
'; +} + +function _simpletest_format_summary_line($summary) { + return + format_plural(isset($summary['#pass']) ? $summary['#pass'] : 0, '1 pass', '@count passes') . ', ' . + format_plural(isset($summary['#fail']) ? $summary['#fail'] : 0, '1 fail', '@count fails') . ', ' . + format_plural(isset($summary['#exception']) ? $summary['#exception'] : 0, '1 exception', '@count exceptions'); } /** * Run selected tests. */ -function simpletest_run_selected_tests($form, &$form_state) { - $form_state['redirect'] = FALSE; +function simpletest_test_form_submit($form, &$form_state) { $output = ''; + $batch_mode = !preg_match("/^simpletest\d+$/", $_SERVER['HTTP_USER_AGENT']); switch ($form_state['values']['running_options']) { case 'all_tests': - $output = simpletest_run_tests(); + $output = simpletest_run_tests(array_keys(simpletest_get_all_tests()), 'drupal', $batch_mode); break; case 'selected_tests': $tests_list = array(); @@ -271,8 +211,8 @@ function simpletest_run_selected_tests($ $tests_list[] = $item; } } - if (count($tests_list) > 0 ) { - $output = simpletest_run_tests($tests_list); + if (count($tests_list) > 0) { + $output = simpletest_run_tests($tests_list, 'drupal', $batch_mode); break; } // Fall through @@ -280,11 +220,167 @@ function simpletest_run_selected_tests($ drupal_set_message(t('No test has been selected.'), 'error'); } - simpletest_running_output($output); return FALSE; } /** + * Actually runs tests + * @param $reporter + * Which reporter to use. Allowed values are: text, xml, html and drupal, + * drupal being the default. + * @param $batch_mode + * Whether to use the batch API or not. + */ +function simpletest_run_tests($test_list, $reporter = 'drupal', $batch_mode = FALSE) { + cache_clear_all(); + db_query('INSERT INTO {simpletest_test_id} VALUES (default)'); + $test_id = db_last_insert_id('simpletest_test_id', 'test_id'); + + if ($batch_mode) { + $batch = array( + 'title' => t('Running SimpleTests'), + 'operations' => array( + array('_simpletest_batch_operation', array($test_list, $test_id)), + ), + 'finished' => '_simpletest_batch_finished', + 'redirect' => FALSE, + 'progress_message' => t('Processing tests.'), + 'init_message' => t('Initializing...'), + ); + batch_set($batch); + } + else { + foreach ($test_list as $test_class) { + $test = new $test_class($test_id); + $test->run(); + } + } +} + +/** + * Batch operation callback. + */ +function _simpletest_batch_operation($test_list_init, $test_id_init, &$context) { + // Ensure that all classes are loaded. + $tests = simpletest_get_all_tests(); + + if (!isset($context['sandbox']['max'])) { + // First iteration: initialize working values. + $test_list = $test_list_init; + $test_id = $test_id_init; + $context['sandbox']['max'] = count($test_list); + } + else { + // Nth iteration: get the current values where we last stored them. + $test_list = $context['sandbox']['tests']; + $test_id = $context['results']; + } + $max = $context['sandbox']['max']; + + $test_class = array_shift($test_list); + $test = new $test_class($test_id); + $test->run(); + $size = count($test_list); + + $message = t('Processed test %test (remaining: @count of @max).', array('%test' => $max - $size, '@count' => $size, '@max' => $max)); + + $results = db_query("SELECT * FROM {simpletest} WHERE test_id = %d ORDER BY test_class, message_id", $test_id); + $complete_summary = array( + '#pass' => 0, + '#fail' => 0, + '#exception' => 0, + '#ok' => TRUE, + ); + $all_ok = TRUE; + while ($result = db_fetch_object($results)) { + $complete_summary['#'. $result->status]++; + if ($result->status != 'pass') { + $complete_summary['#ok'] = $all_ok; + } + } + $context['message'] = $message . theme('simpletest_result_summary', $complete_summary); + + // Put back the tests. + $context['sandbox']['tests'] = $test_list; + $context['results'] = $test_id; + + // Multistep processing: report progress. + $context['finished'] = 1 - $size / $max; +} + +function _simpletest_batch_finished($success, $results, $operations) { + $_SESSION['test_id'] = $results; + if ($success) { + drupal_set_message(t('The tests have finished running.')); + } + else { + drupal_set_message(t('The tests did not successfully finish.'), 'error'); + } +} + +/** + * Get a list of all of the tests. + * + * @return + * An array of tests, with the class name as the keys and the instantiated + * versions of the classes as the values. + */ +function simpletest_get_all_tests() { + static $formatted_classes; + if (!isset($formatted_classes)) { + require_once drupal_get_path('module', 'simpletest') . '/drupal_web_test_case.php'; + $files = array(); + foreach (array_keys(module_rebuild_cache()) as $module) { + $module_path = drupal_get_path('module', $module); + $test = $module_path . "/$module.test"; + if (file_exists($test)) { + $files[] = $test; + } + + $tests_directory = $module_path . '/tests'; + if (is_dir($tests_directory)) { + foreach (file_scan_directory($tests_directory, '\.test$') as $file) { + $files[] = $file->filename; + } + } + } + + $existing_classes = get_declared_classes(); + foreach ($files as $file) { + include_once($file); + } + $classes = array_values(array_diff(get_declared_classes(), $existing_classes)); + $formatted_classes = array(); + foreach ($classes as $key => $class) { + if (method_exists($class, 'getInfo')) { + $formatted_classes[$class] = new $class; + } + } + } + if (count($formatted_classes) == 0) { + drupal_set_message('No test cases found.', 'error'); + return FALSE; + } + return $formatted_classes; +} + +/** + * Categorize the tests into groups. + * + * @param $tests + * A list of tests from simpletest_get_all_tests. + * @see simpletest_get_all_tests. + */ +function simpletest_categorize_tests($tests) { + $groups = array(); + foreach ($tests as $test => $instance) { + $info = $instance->getInfo(); + $groups[$info['group']][] = $instance; + } + return $groups; +} + +/** * Remove all temporary database tables and directories. */ function simpletest_clean_environment() { @@ -378,65 +474,6 @@ function simpletest_clean_temporary_dire rmdir($path); } -/** - * Actually runs tests - * @param array $test_list list of tests to run or DEFAULT NULL run all tests - * @param boolean $html_reporter TRUE if you want results in simple html, FALSE for full drupal page - */ -function simpletest_run_tests($test_list = NULL, $reporter = 'drupal') { - static $test_running; - if (!$test_running) { - $test_running = TRUE; - $test = simpletest_get_total_test($test_list); - switch ($reporter) { - case 'text': - $reporter = &new TextReporter(); - break; - case 'xml': - $reporter = &new XMLReporter(); - break; - case 'html': - $reporter = &new HtmlReporter(); - break; - case 'drupal': - $reporter = &new DrupalReporter(); - break; - } - - cache_clear_all(); - $results = $test->run($reporter); - $test_running = FALSE; - - switch (get_class($reporter)) { - case 'TextReporter': - case 'XMLReporter': - case 'HtmlReporter': - return $results; - case 'DrupalReporter': - return $reporter->getOutput(); - } - } -} - -/** - * This function makes sure no unnecessary copies of the DrupalTests object are instantiated - * @param array $classes list of all classes the test should concern or - * DEFAULT NULL - * @return DrupalTests object - */ -function &simpletest_get_total_test($classes = NULL) { - static $total_test; - if (!$total_test) { - simpletest_load(); - $total_test = &new DrupalTests(); - } - if (!is_null($classes)) { - $dut = new DrupalTests($classes); - return $dut; - } - return $total_test; -} - function simpletest_settings() { $form = array(); @@ -473,5 +510,4 @@ function simpletest_settings() { ); return system_settings_form($form); - }