Index: modules/simpletest/drupal_web_test_case.php =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v retrieving revision 1.76 diff -u -r1.76 drupal_web_test_case.php --- modules/simpletest/drupal_web_test_case.php 20 Dec 2008 18:24:39 -0000 1.76 +++ modules/simpletest/drupal_web_test_case.php 27 Dec 2008 01:01:50 -0000 @@ -20,12 +20,7 @@ */ protected $url; - /** - * The handle of the current cURL connection. - * - * @var resource - */ - protected $curlHandle; + protected $browser; /** * The headers of the page currently loaded in the internal browser. @@ -48,12 +43,7 @@ */ protected $plainTextContent; - /** - * The parsed version of the page. - * - * @var SimpleXMLElement - */ - protected $elements = NULL; + protected $parsed = FALSE; /** * Whether a user is logged in the internal browser. @@ -904,7 +894,7 @@ $this->refreshVariables(); // Close the CURL handler. - $this->curlClose(); + $this->browserReset(); } } @@ -915,80 +905,47 @@ * simpletest_httpauth_username and simpletest_httpauth_pass variables. Also, * see the description of $curl_options among the properties. */ - protected function curlInitialize() { + protected function browserInit() { global $base_url, $db_prefix; - if (!isset($this->curlHandle)) { - $this->curlHandle = curl_init(); - $curl_options = $this->additionalCurlOptions + array( - CURLOPT_COOKIEJAR => $this->cookieFile, - CURLOPT_URL => $base_url, - CURLOPT_FOLLOWLOCATION => TRUE, - CURLOPT_RETURNTRANSFER => TRUE, - CURLOPT_SSL_VERIFYPEER => FALSE, // Required to make the tests run on https:// - CURLOPT_SSL_VERIFYHOST => FALSE, // Required to make the tests run on https:// - CURLOPT_HEADERFUNCTION => array(&$this, 'curlHeaderCallback'), - ); + + if (!isset($this->browser)) { + require_once './includes/browser/browser.inc'; // TODO autoloaded? + $this->browser = Browser::getInstance('curl'); + // TODO Allow setting of cookie file? or some sort of session handling. + if (preg_match('/simpletest\d+/', $db_prefix, $matches)) { - $curl_options[CURLOPT_USERAGENT] = $matches[0]; - } - 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; + $this->browser->setUserAgent($matches[0]); } - curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options); + // TODO http auth +// 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; +// } } } - /** - * Performs a cURL exec with the specified options after calling curlConnect(). - * - * @param $curl_options - * Custom cURL options. - * @return - * Content returned from the exec. - */ - protected function curlExec($curl_options) { - $this->curlInitialize(); - $url = empty($curl_options[CURLOPT_URL]) ? curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL) : $curl_options[CURLOPT_URL]; - curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options); - $this->headers = array(); - $this->drupalSetContent(curl_exec($this->curlHandle), curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL)); - $this->assertTrue($this->content !== FALSE, t('!method to !url, response is !length bytes.', array('!method' => !empty($curl_options[CURLOPT_NOBODY]) ? 'HEAD' : (empty($curl_options[CURLOPT_POSTFIELDS]) ? 'GET' : 'POST'), '!url' => $url, '!length' => strlen($this->content))), t('Browser')); - return $this->drupalGetContent(); - } - - /** - * Reads headers and registers errors received from the tested site. - * - * @see _drupal_log_error(). - * - * @param $curlHandler - * The cURL handler. - * @param $header - * An header. - */ - protected function curlHeaderCallback($curlHandler, $header) { - $this->headers[] = $header; - // Errors are being sent via X-Drupal-Assertion-* headers, - // generated by _drupal_log_error() in the exact form required - // by DrupalWebTestCase::error(). - if (preg_match('/^X-Drupal-Assertion-[0-9]+: (.*)$/', $header, $matches)) { - // Call DrupalWebTestCase::error() with the parameters from the header. - call_user_func_array(array(&$this, 'error'), unserialize(urldecode($matches[1]))); + protected function checkHeaders() { + $headers = $this->browser->getHeaders(); + foreach ($headers as $key => $value) { + // Errors are being sent via X-Drupal-Assertion-* headers, + // generated by _drupal_log_error() in the exact form required + // by DrupalWebTestCase::error(). + if (preg_match('/X-Drupal-Assertion-[0-9]+/', $key)) { + // Call DrupalWebTestCase::error() with the parameters from the header. + call_user_func_array(array(&$this, 'error'), unserialize($value)); + } } - // This is required by cURL. - return strlen($header); } /** * Close the cURL handler and unset the handler. */ - protected function curlClose() { - if (isset($this->curlHandle)) { - curl_close($this->curlHandle); - unset($this->curlHandle); + protected function browserReset() { + if (isset($this->browser)) { + $this->browser->resetConnection(); + unset($this->browser); } } @@ -999,22 +956,16 @@ * A SimpleXMLElement or FALSE on failure. */ 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) { + if (!$this->parsed) { + if ($this->browser->getPage()) { $this->pass(t('Valid HTML found on "@path"', array('@path' => $this->getUrl())), t('Browser')); - // 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); } + else { + $this->fail(t('Parsed page successfully.'), t('Browser')); + } + $this->parsed = TRUE; } - if (!$this->elements) { - $this->fail(t('Parsed page successfully.'), t('Browser')); - } - - return $this->elements; + return $this->browser->getPage(); } /** @@ -1033,17 +984,20 @@ protected function drupalGet($path, array $options = array(), array $headers = array()) { $options['absolute'] = TRUE; - // We re-using a CURL connection here. If that connection still has certain - // options set, it might change the GET into a POST. Make sure we clear out - // previous options. - $out = $this->curlExec(array(CURLOPT_HTTPGET => TRUE, CURLOPT_URL => url($path, $options), CURLOPT_NOBODY => FALSE, CURLOPT_HTTPHEADER => $headers)); - $this->refreshVariables(); // Ensure that any changes to variables in the other thread are picked up. + // TODO change header format. - // Replace original page output with new output from redirected page(s). - if (($new = $this->checkForMetaRefresh())) { - $out = $new; - } - return $out; + $this->browserInit(); +// $this->browser->setRequestHeaders($headers); + $request = $this->browser->get($url = url($path, $options)); + $this->assertTrue($request, t('!method to !url, response is !length bytes.', array( + '!method' => 'GET', + '!url' => $url, + '!length' => strlen($request['content']))), t('Browser')); + $this->checkHeaders(); + + // Ensure that any changes to variables in the other thread are picked up. + $this->refreshVariables(); + return $request['content']; } /** @@ -1082,85 +1036,33 @@ * "name: value". */ protected function drupalPost($path, $edit, $submit, array $options = array(), array $headers = array()) { - $submit_matches = FALSE; - if (isset($path)) { - $html = $this->drupalGet($path, $options); - } - if ($this->parse()) { - $edit_save = $edit; - // Let's iterate over all the forms. - $forms = $this->xpath('//form'); - foreach ($forms as $form) { - // We try to set the fields of this form as specified in $edit. - $edit = $edit_save; - $post = array(); - $upload = array(); - $submit_matches = $this->handleForm($post, $edit, $upload, $submit, $form); - $action = isset($form['action']) ? $this->getAbsoluteUrl($form['action']) : $this->getUrl(); - - // We post only if we managed to handle every field in edit and the - // submit button matches. - if (!$edit && $submit_matches) { - if ($upload) { - // TODO: cURL handles file uploads for us, but the implementation - // is broken. This is a less than elegant workaround. Alternatives - // are being explored at #253506. - foreach ($upload as $key => $file) { - $file = realpath($file); - if ($file && is_file($file)) { - $post[$key] = '@' . $file; - } - } - } - else { - foreach ($post as $key => $value) { - // Encode according to application/x-www-form-urlencoded - // Both names and values needs to be urlencoded, according to - // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1 - $post[$key] = urlencode($key) . '=' . urlencode($value); - } - $post = implode('&', $post); - } - $out = $this->curlExec(array(CURLOPT_URL => $action, CURLOPT_POST => TRUE, CURLOPT_POSTFIELDS => $post, CURLOPT_HTTPHEADER => $headers)); - // Ensure that any changes to variables in the other thread are picked up. - $this->refreshVariables(); - - // Replace original page output with new output from redirected page(s). - if (($new = $this->checkForMetaRefresh())) { - $out = $new; - } - return $out; - } - } - // We have not found a form which contained all fields of $edit. - foreach ($edit as $name => $value) { - $this->fail(t('Failed to set field @name to @value', array('@name' => $name, '@value' => $value))); - } - $this->assertTrue($submit_matches, t('Found the @submit button', array('@submit' => $submit))); - $this->fail(t('Found the requested form fields at @path', array('@path' => $path))); - } - } + // TODO change header format. - /** - * Check for meta refresh tag and if found call drupalGet() recursively. This - * function looks for the http-equiv attribute to be set to "Refresh" - * and is case-sensitive. - * - * @return - * Either the new page content or FALSE. - */ - protected function checkForMetaRefresh() { - if ($this->drupalGetContent() != '' && $this->parse()) { - $refresh = $this->xpath('//meta[@http-equiv="Refresh"]'); - if (!empty($refresh)) { - // Parse the content attribute of the meta tag for the format: - // "[delay]: URL=[page_to_redirect_to]". - if (preg_match('/\d+;\s*URL=(?P.*)/i', $refresh[0]['content'], $match)) { - return $this->drupalGet($this->getAbsoluteUrl(decode_entities($match['url']))); - } - } - } - return FALSE; + $this->browserInit(); + + $options['absolute'] = TRUE; + + $url = (isset($path) ? url($path, $options) : NULL); + $pre_url = ($url ? $url : $this->browser->getUrl()); + +// $this->browser->setRequestHeaders($headers); + $request = $this->browser->post($url, $edit, $submit); + $this->assertTrue($request, t('!method to !url, response is !length bytes.', array( + '!method' => 'POST', + '!url' => $pre_url, + '!length' => strlen($request['content']))), t('Browser')); + $this->checkHeaders(); + + // Ensure that any changes to variables in the other thread are picked up. + $this->refreshVariables(); + + // TODO add error handling in post that lets us know this. +// // We have not found a form which contained all fields of $edit. +// foreach ($edit as $name => $value) { +// $this->fail(t('Failed to set field @name to @value', array('@name' => $name, '@value' => $value))); +// } +// $this->assertTrue($submit_matches, t('Found the @submit button', array('@submit' => $submit))); +// $this->fail(t('Found the requested form fields at @path', array('@path' => $path))); } /** @@ -1177,6 +1079,7 @@ * The retrieved headers, also available as $this->drupalGetContent() */ protected function drupalHead($path, array $options = array(), array $headers = array()) { + // TODO $options['absolute'] = TRUE; $out = $this->curlExec(array(CURLOPT_NOBODY => TRUE, CURLOPT_URL => url($path, $options), CURLOPT_HTTPHEADER => $headers)); $this->refreshVariables(); // Ensure that any changes to variables in the other thread are picked up. @@ -1184,136 +1087,6 @@ } /** - * Handle form input related to drupalPost(). Ensure that the specified fields - * exist and attempt to create POST data in the correct manner for the particular - * field type. - * - * @param $post - * Reference to array of post values. - * @param $edit - * Reference to array of edit values to be checked against the form. - * @param $submit - * Form submit button value. - * @param $form - * Array of form elements. - * @return - * Submit value matches a valid submit input in the form. - */ - protected function handleForm(&$post, &$edit, &$upload, $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