? .svn ? php5powah_tests.patch ? simpletest ? simpletest_orig ? x.php ? tests/.svn ? tests/pager.test ? tests/patch Index: drupal_test_case.php =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/simpletest/drupal_test_case.php,v retrieving revision 1.57 diff -u -p -r1.57 drupal_test_case.php --- drupal_test_case.php 17 Mar 2008 23:57:29 -0000 1.57 +++ drupal_test_case.php 21 Mar 2008 09:27:40 -0000 @@ -3,39 +3,45 @@ /** * Test case for typical Drupal tests. - * Extends WebTestCase for comfortable browser usage - * but also implements all UnitTestCase methods, I wish - * WebTestCase would do this. */ -class DrupalTestCase extends WebTestCase { - var $_logged_in = FALSE; - var $_content; - var $_originalModules = array(); - var $_modules = array(); - var $_cleanupVariables = array(); - var $_cleanupUsers = array(); - var $_cleanupRoles = array(); - var $_cleanupNodes = array(); - var $_cleanupContentTypes = array(); +class DrupalTestCase extends UnitTestCase { + protected $_logged_in = FALSE; + protected $_content; + protected $plain_text; + protected $_originalModules = array(); + protected $_modules = array(); + protected $_cleanupVariables = array(); + protected $_cleanupUsers = array(); + protected $_cleanupRoles = array(); + protected $_cleanupNodes = array(); + protected $_cleanupContentTypes = array(); + protected $ch; + // We do not reuse the cookies in further runs, so we do not need a file + // but we still need cookie handling, so we set the jar to NULL + protected $cookie_file = NULL; + // Overwrite this any time to supply cURL options as necessary, + // DrupalTestCase itself never sets this but always obeys whats set. + protected $curl_options = array(); - - function DrupalTestCase($label = NULL) { - if (! $label) { + function __construct($label = NULL) { + if (!$label) { if (method_exists($this, 'get_info')) { $info = $this->get_info(); $label = $info['name']; } } - $this->WebTestCase($label); + parent::__construct($label); } - + /** * Creates a node based on default settings. * - * @param settings An array of settings to change from the defaults, in the form of 'body' => 'Hello, world!' + * @param settings + * An assocative array of settings to change from the defaults, keys are + * node properties, for example 'body' => 'Hello, world!'. */ function drupalCreateNode($settings = array()) { - + // Populate defaults array $defaults = array( 'body' => $this->randomName(32), @@ -58,16 +64,15 @@ class DrupalTestCase extends WebTestCase if (isset($defaults['created'])) { $defaults['date'] = format_date($defaults['created'], 'custom', 'Y-m-d H:i:s O'); } - if (empty($settings['uid'])) { global $user; $defaults['uid'] = $user->uid; } $node = ($settings + $defaults); $node = (object)$node; - + node_save($node); - + // small hack to link revisions to our test user db_query('UPDATE {node_revisions} SET uid = %d WHERE vid = %d', $node->uid, $node->vid); $this->_cleanupNodes[] = $node->nid; @@ -77,7 +82,9 @@ class DrupalTestCase extends WebTestCase /** * Creates a custom content type based on default settings. * - * @param settings An array of settings to change from the defaults, in the form of 'type' => 'foo' + * @param settings + * An array of settings to change from the defaults. + * Example: 'type' => 'foo'. */ function drupalCreateContentType($settings = array()) { // find a non-existent random type name. @@ -117,143 +124,6 @@ class DrupalTestCase extends WebTestCase } /** - * @abstract Checks to see if we need to send - * a http-auth header to authenticate - * when browsing a site. - * - * @param status Boolean pass true if you want to know if we are using - * HTTP-AUTH - * @return void - */ - function drupalCheckAuth($status = false) { - $check = variable_get('simpletest_httpauth', false); - if( $status ) { - return $check; - } - if( variable_get('simpletest_httpauth', false) ) { - $html = $this->authenticate(variable_get('simpletest_httpauth_username', ''), variable_get('simpletest_httpauth_pass', '')); - } - return $html; - } - - /** - * @abstract Broker for the get function - * adds the authentication headers if necessary - * @author Earnest Berry III - * - * @param $path string Drupal path or url to load into internal browser - * @param array $options Options to be forwarded to url(). - * @return void - */ - function drupalGet($path, $options = array()) { - $url = url($path, array_merge($options, array('absolute' => TRUE))); - $html = $this->_browser->get($url); - - if ($this->drupalCheckAuth(true)) { - $html .= $this->drupalCheckAuth(); - } - - $this->_content = $this->_browser->getContent(); - - return $html; - } - - /** - * @abstract Broker for the post function - * adds the authentication headers if - * necessary - * @author Earnest Berry III - * - * @param url string Url to retch - * @return void - */ - function drupalRawPost($action, $edit = array()) { - $html = $this->_browser->post($action, $edit); - - if( $this->drupalCheckAuth(true) ) { - $html .= $this->drupalCheckAuth(); - } - - $this->_content = $this->_browser->getContent(); - - return $html; - } - - - - /** - * Do a post request on a drupal page. - * It will be done as usual post request with SimpleBrowser - * By $reporting you specify if this request does assertions or not - * Warning: empty ("") returns will cause fails with $reporting - * - * @param string $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 - * 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 - * Untranslated value, id or name of the submit button. - */ - function drupalPost($path, $edit = array(), $submit) { - if (isset($path)) { - $ret = $this->drupalGet($path); - $this->assertTrue($ret, t(' [browser] GET path "@path"', array('@path' => $path))); - } - - foreach ($edit as $field_name => $field_value) { - $ret = $this->_browser->setFieldByName($field_name, $field_value) - || $this->_browser->setFieldById("edit-$field_name", $field_value); - $this->assertTrue($ret, " [browser] Setting $field_name=\"$field_value\""); - } - - $ret = $this->_browser->clickSubmit(t($submit)) || $this->_browser->clickSubmitById($submit) || $this->_browser->clickSubmitByName($submit) || $this->_browser->clickImageByName($submit); - $this->assertTrue($ret, ' [browser] POST by click on ' . t($submit)); - $this->_content = $this->_browser->getContent(); - } - - /** - * Follows a link by name. - * - * Will click the first link found with this link text by default, or a - * later one if an index is given. Match is case insensitive with - * normalized space. The label is translated label. There is an assert - * 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. - * - * @access public - */ - function clickLink($label, $index = 0) { - $url_before = str_replace('%', '%%', $this->getUrl()); - $urls = $this->_browser->_page->getUrlsByLabel($label); - if (count($urls) < $index + 1) { - $url_target = 'URL NOT FOUND!'; - } else { - $url_target = str_replace('%', '%%', $urls[$index]->asString()); - } - - $ret = parent::clickLink(t($label), $index); - - $this->assertTrue($ret, ' [browser] clicked link '. t($label) . " ($url_target) from $url_before"); - - return $ret; - } - - /** - * @TODO: needs documentation - */ - function drupalGetContent() { - return $this->_content; - } - - /** * Generates a random string, to be used as name or whatever * @param integer $number number of characters * @return random string @@ -284,7 +154,7 @@ class DrupalTestCase extends WebTestCase $this->_modules[$name] = $name; $form_state['values'] = array('status' => $this->_modules, 'op' => t('Save configuration')); drupal_execute('system_modules', $form_state); - + //rebuilding all caches drupal_rebuild_theme_registry(); node_types_rebuild(); @@ -310,7 +180,7 @@ class DrupalTestCase extends WebTestCase unset($this->_modules[$key]); $form_state['values'] = array('status' => $this->_modules, 'op' => t('Save configuration')); drupal_execute('system_modules', $form_state); - + //rebuilding all caches drupal_rebuild_theme_registry(); node_types_rebuild(); @@ -332,7 +202,7 @@ class DrupalTestCase extends WebTestCase $this->_modules = $this->_originalModules; } } - + /** * Set a drupal variable and keep track of the changes for tearDown() * @param string $name name of the value @@ -425,10 +295,7 @@ class DrupalTestCase extends WebTestCase $this->drupalGet('logout'); } - $this->drupalGet('user'); - // Going to the page retrieves the cookie, as the browser should save it - - if ($user === NULL) { + if (!isset($user)) { $user = $this->drupalCreateUserRolePerm(); } @@ -452,15 +319,15 @@ class DrupalTestCase extends WebTestCase if ($this->_modules != $this->_originalModules) { $form_state['values'] = array('status' => $this->_originalModules, 'op' => t('Save configuration')); drupal_execute('system_modules', $form_state); - + //rebuilding all caches drupal_rebuild_theme_registry(); node_types_rebuild(); menu_rebuild(); cache_clear_all('schema', 'cache'); module_rebuild_cache(); - - $this->_modules = $this->_originalModules; + + $this->_modules = $this->_originalModules; } foreach ($this->_cleanupVariables as $name => $value) { @@ -471,7 +338,7 @@ class DrupalTestCase extends WebTestCase } } $this->_cleanupVariables = array(); - + //delete nodes foreach ($this->_cleanupNodes as $nid) { node_delete($nid); @@ -526,301 +393,388 @@ class DrupalTestCase extends WebTestCase array_pop($reporter->test_info_stack); } - - /** - * Will trigger a pass if the raw text is found on the loaded page - * Fail otherwise. - * @param string $raw Raw string to look for - * @param string $message Message to display. - * @return boolean True on pass - * @access public - */ - function assertWantedRaw($raw, $message = "%s") { - return $this->assertExpectation( - new TextExpectation($raw), - $this->_browser->getContent(), - $message); + /** + * Initializes the cURL connection and gets a session cookie. + * + * This function will add authentaticon headers as specified in + * simpletest_httpauth_username and simpletest_httpauth_pass variables. + * Also, see the description of $curl_options among the properties. + */ + protected function curlConnect() { + global $base_url; + if (!isset($this->ch)) { + $this->ch = curl_init(); + $curl_options = $this->curl_options + array( + CURLOPT_COOKIEJAR => $this->cookie_file, + CURLOPT_URL => $base_url, + CURLOPT_FOLLOWLOCATION => TRUE, + CURLOPT_RETURNTRANSFER => TRUE, + ); + if (!isset($curl_options[CURLOPT_USERPWD]) && ($auth = variable_get('simpletest_httpauth_username', ''))) { + if ($pass = variable_get('simpletest_httpauth_pass', '')) { + $auth .= ':'. $pass; } + $curl_options[CURLOPT_USERPWD] = $auth; + } + return $this->curlExec($curl_options); + } + } + protected function curlExec($curl_options) { + $this->curlConnect(); + $url = empty($curl_options[CURLOPT_URL]) ? curl_getinfo($this->ch, CURLINFO_EFFECTIVE_URL) : $curl_options[CURLOPT_URL]; + curl_setopt_array($this->ch, $this->curl_options + $curl_options); + $this->_content = curl_exec($this->ch); + $this->plain_text = FALSE; + $this->elements = FALSE; + $this->assertTrue($this->_content, t(' [browser] !method to !url, response is !length bytes.', array('!method' => isset($curl_options[CURLOPT_POSTFIELDS]) ? 'POST' : 'GET', '!url' => $url, '!length' => strlen($this->_content)))); + return $this->_content; + } - /** - * Will trigger a pass if the raw text is NOT found on the loaded page - * Fail otherwise. - * @param string $raw Raw string to look for - * @param string $message Message to display. - * @return boolean True on pass - * @access public - */ - function assertNoUnwantedRaw($raw, $message = "%s") { - return $this->assertExpectation( - new NoTextExpectation($raw), - $this->_browser->getContent(), - $message); - } - /* Taken from UnitTestCase */ - /** - * Will be true if the value is null. - * @param null $value Supposedly null value. - * @param string $message Message to display. - * @return boolean True on pass - * @access public - */ - function assertNull($value, $message = "%s") { - $dumper = &new SimpleDumper(); - $message = sprintf( - $message, - "[" . $dumper->describeValue($value) . "] should be null"); - return $this->assertTrue(! isset($value), $message); - } + protected function parse() { + if (!$this->elements) { + // DOM can load HTML soup. But, HTML soup can throw warnings, supress + // them. + @$htmlDom = DOMDocument::loadHTML($this->_content); + if ($htmlDom) { + $this->assertTrue(TRUE, t(' [browser] Valid HTML found on "@path"', array('@path' => $this->getUrl()))); + // It's much easier to work with simplexml than DOM, luckily enough + // we can just simply import our DOM tree. + $this->elements = simplexml_import_dom($htmlDom); + } + } + return $this->elements; + } - /** - * Will be true if the value is set. - * @param mixed $value Supposedly set value. - * @param string $message Message to display. - * @return boolean True on pass. - * @access public - */ - function assertNotNull($value, $message = "%s") { - $dumper = &new SimpleDumper(); - $message = sprintf( - $message, - "[" . $dumper->describeValue($value) . "] should not be null"); - return $this->assertTrue(isset($value), $message); - } + /** + * 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() + */ + function drupalGet($path, $options = array()) { + $options['absolute'] = TRUE; + return $this->curlExec(array(CURLOPT_URL => url($path, $options))); + } - /** - * Type and class test. Will pass if class - * matches the type name or is a subclass or - * if not an object, but the type is correct. - * @param mixed $object Object to test. - * @param string $type Type name as string. - * @param string $message Message to display. - * @return boolean True on pass. - * @access public - */ - function assertIsA($object, $type, $message = "%s") { - return $this->assertExpectation( - new IsAExpectation($type), - $object, - $message); + /** + * Do a post request on a drupal page. + * It will be done as usual post request with SimpleBrowser + * By $reporting you specify if this request does assertions or not + * Warning: empty ("") returns will cause fails with $reporting + * + * @param string $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 + * 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 + * Untranslated value, id or name of the submit button. + * @param $tamper + * If this is set to TRUE then you can post anything, otherwise hidden and + * nonexistent fields are not posted. + */ + function drupalPost($path, $edit, $submit, $tamper = FALSE) { + if (isset($path)) { + $html = $this->drupalGet($path); + } + if ($this->parse()) { + $edit_save = $edit; + // Let's iterate over all the forms. + $forms = $this->elements->xpath('//form'); + foreach ($forms as $form) { + if ($tamper) { + // @TODO: this will be Drupal specific. One needs to add the build_id + // and the token to $edit then $post that. + } + else { + // We try to set the fields of this form as specified in $edit. + $edit = $edit_save; + $post = array(); + $submit_matches = $this->handleForm($post, $edit, $submit, $form); + } + // We post only if we managed to handle every field in edit and the + // submit button matches; + if (!$edit && $submit_matches) { + $encoded_post = ''; + foreach ($post as $key => $value) { + if (is_array($value)) { + foreach ($value as $v) { + $encoded_post .= $key .'='. rawurlencode($v) .'&'; + } + } + else { + $encoded_post .= $key .'='. rawurlencode($value) .'&'; + } + } + return $this->curlExec(array(CURLOPT_POSTFIELDS => $encoded_post)); } + } + // We have not found a form which contained all fields of $edit. + $this->fail(t('Found the requested form')); + $this->assertTrue($submit_matches, t('Found the @submit button', array('@submit' => $submit))); + foreach ($edit as $name => $value) { + $this->fail(t('Failed to set field @name to @value', array('@name' => $name, '@value' => $value))); + } + } + } - /** - * Type and class mismatch test. Will pass if class - * name or underling type does not match the one - * specified. - * @param mixed $object Object to test. - * @param string $type Type name as string. - * @param string $message Message to display. - * @return boolean True on pass. - * @access public - */ - function assertNotA($object, $type, $message = "%s") { - return $this->assertExpectation( - new NotAExpectation($type), - $object, - $message); + protected function handleForm(&$post, &$edit, $submit, $form) { + // Retrieve the form elements. + $elements = $form->xpath('.//input|.//textarea|.//select'); + $submit_matches = FALSE; + foreach ($elements as $element) { + // SimpleXML objects need string casting all the time. + $name = (string)$element['name']; + // This can either be the type of or the name of the tag itself + // for