Index: drupal_web_test_case.php =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/simpletest/Attic/drupal_web_test_case.php,v retrieving revision 1.3.2.6 diff -u -r1.3.2.6 drupal_web_test_case.php --- drupal_web_test_case.php 16 Dec 2009 02:39:11 -0000 1.3.2.6 +++ drupal_web_test_case.php 16 Dec 2009 04:19:16 -0000 @@ -189,12 +189,12 @@ /** * Delete an assertion record by message ID. - * + * * @param $message_id * Message ID of the assertion to delete. * @return * TRUE if the assertion was deleted, FALSE otherwise. - * + * * @see DrupalTestCase::insertAssert() */ public static function deleteAssert($message_id) { @@ -423,7 +423,8 @@ $username = variable_get('simpletest_username', NULL); $password = variable_get('simpletest_password', NULL); if ($username && $password) { - $this->httpauth_credentials = $username . ':' . $password; + $this->httpauth_username = $username; + $this->httpauth_password = $password; } set_error_handler(array($this, 'errorHandler')); @@ -609,6 +610,14 @@ * Test case for typical Drupal tests. */ class DrupalWebTestCase extends DrupalTestCase { + + /** + * Browser instance. + * + * @var object + */ + protected $browser; + /** * The URL currently loaded in the internal browser. * @@ -700,6 +709,7 @@ */ function __construct($test_id = NULL) { parent::__construct($test_id); + $this->skipClasses[__CLASS__] = TRUE; } @@ -1249,136 +1259,63 @@ if ($this->originalLanguageDefault) { $GLOBALS['conf']['language_default'] = $this->originalLanguageDefault; } - - // Close the CURL handler. - $this->curlClose(); } } /** - * Initializes the cURL connection. - * - * If the simpletest_httpauth_credentials variable is set, this function will - * add HTTP authentication headers. This is necessary for testing sites that - * are protected by login credentials from public access. - * See the description of $curl_options for other options. + * Initialize browser. */ - protected function curlInitialize() { - global $base_url, $db_prefix; + protected function browserInitialize() { + global $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_MAXREDIRS => 5, - 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->httpauth_credentials)) { - $curl_options[CURLOPT_USERPWD] = $this->httpauth_credentials; + if (!isset($this->browser)) { + module_load_include('inc', 'browser'); + $this->browser = new Browser(); + $this->browser->setPageListener(array($this, 'browserPageListener')); + + if (isset($this->httpauth_username)) { + $this->browser->setHttpAuthentication($this->httpauth_username, $this->httpauth_password); } - curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options); // By default, the child session name should be the same as the parent. - $this->session_name = session_name(); + $this->session_name = session_name(); // TODO } - // We set the user agent header on each request so as to use the current - // time and a new uniqid. + + // Generate a user agent based on the db prefix. if (preg_match('/simpletest\d+/', $db_prefix, $matches)) { - curl_setopt($this->curlHandle, CURLOPT_USERAGENT, drupal_generate_test_ua($matches[0])); + $this->browser->setUserAgent(drupal_generate_test_ua($matches[0])); } + + $this->browser->setRequestHeaders(array()); + return $this->browser; } /** - * Performs a cURL exec with the specified options after calling curlConnect(). - * - * @param $curl_options - * Custom cURL options. - * @return - * Content returned from the exec. + * Browser page callback. */ - protected function curlExec($curl_options) { - $this->curlInitialize(); - $url = empty($curl_options[CURLOPT_URL]) ? curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL) : $curl_options[CURLOPT_URL]; - if (!empty($curl_options[CURLOPT_POST])) { - // This is a fix for the Curl library to prevent Expect: 100-continue - // headers in POST requests, that may cause unexpected HTTP response - // codes from some webservers (like lighttpd that returns a 417 error - // code). It is done by setting an empty "Expect" header field that is - // not overwritten by Curl. - $curl_options[CURLOPT_HTTPHEADER][] = 'Expect:'; - } - curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options); - - // Reset headers and the session ID. - $this->session_id = NULL; - $this->headers = array(); - - $this->drupalSetContent(curl_exec($this->curlHandle), curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL)); + public function browserPageListener(array $state, Browser $browser) { $message_vars = array( - '!method' => !empty($curl_options[CURLOPT_NOBODY]) ? 'HEAD' : (empty($curl_options[CURLOPT_POSTFIELDS]) ? 'GET' : 'POST'), - '@url' => $url, - '@status' => curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE), - '!length' => format_size(strlen($this->content)) + '!method' => $browser->getMethod(), + '@url' => $state['url'], + '@status' => $state['code'], + '!length' => format_size(strlen($state['content'])), ); $message = t('!method @url returned @status (!length).', $message_vars); - $this->assertTrue($this->content !== FALSE, $message, 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; + $this->assertTrue($this->browser->getContent() !== FALSE, $message, t('Browser')); - // 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]))); + if ($browser->getMethod() == 'GET') { + $this->verbose('GET request to: ' . $this->path . + '
Ending URL: ' . $state['url'] . + '
' . $state['content']); } - - // Save cookies. - if (preg_match('/^Set-Cookie: ([^=]+)=(.+)/', $header, $matches)) { - $name = $matches[1]; - $parts = array_map('trim', explode(';', $matches[2])); - $value = array_shift($parts); - $this->cookies[$name] = array('value' => $value, 'secure' => in_array('secure', $parts)); - if ($name == $this->session_name) { - if ($value != 'deleted') { - $this->session_id = $value; - } - else { - $this->session_id = NULL; - } - } + else { + $this->verbose('POST request to: ' . $this->path . + '
Ending URL: ' . $state['url'] . + '
Fields: ' . highlight_string('browser->getPost(), TRUE), TRUE) . + '
' . $state['content']); } - // 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); - } + $this->path = '[previous]'; } /** @@ -1420,22 +1357,18 @@ * The retrieved HTML string, also available as $this->drupalGetContent() */ protected function drupalGet($path, array $options = array(), array $headers = array()) { + $this->path = $path; + + // The browser requires the URL to be absolute. $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. - - // Replace original page output with new output from redirected page(s). - if (($new = $this->checkForMetaRefresh())) { - $out = $new; - } - $this->verbose('GET request to: ' . $path . - '
Ending URL: ' . $this->getUrl() . - '
' . $out); - return $out; + // Make the GET request. + $state = $this->browserInitialize()->get(url($path, $options)); + + // Ensure that any changes to variables in the other thread are picked up. + $this->refreshVariables(); + + return $state['content']; } /** @@ -1501,81 +1434,28 @@ * "name: value". */ protected function drupalPost($path, $edit, $submit, array $options = array(), array $headers = array()) { - $submit_matches = FALSE; - $ajax = is_array($submit); - 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, $ajax ? NULL : $submit, $form); - $action = isset($form['action']) ? $this->getAbsoluteUrl($form['action']) : $this->getUrl(); - if ($ajax) { - $action = $this->getAbsoluteUrl(!empty($submit['path']) ? $submit['path'] : 'system/ajax'); - // AJAX callbacks verify the triggering element if necessary, so while - // we may eventually want extra code that verifies it in the - // handleForm() function, it's not currently a requirement. - $submit_matches = TRUE; - } + $this->path = $path; - // We post only if we managed to handle every field in edit and the - // submit button matches. - if (!$edit && $submit_matches) { - $post_array = $post; - 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 = drupal_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); - } - if ($ajax && isset($submit['triggering_element'])) { - $post['ajax_triggering_element'] = 'ajax_triggering_element=' . urlencode($submit['triggering_element']); - } - $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; - } - $this->verbose('POST request to: ' . $path . - '
Ending URL: ' . $this->getUrl() . - '
Fields: ' . highlight_string('' . $out); - 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))); - } - if (!$ajax) { - $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))); + // The browser requires the URL to be absolute. + $options['absolute'] = TRUE; + + $this->browserInitialize(); + + foreach ($headers as $header) { + $parts = explode(':', $header); + $this->browser->setRequestHeader(trim($parts[0]), trim($parts[1])); + } + $state = $this->browser->post(url($path, $options), $edit, $submit); + + if ($state) { + // Ensure that any changes to variables in the other thread are picked up. + $this->refreshVariables(); + + return $state['content']; } + + $this->fail(t('Found the requested form fields at [@path]', array('@path' => $path))); + return FALSE; } /** @@ -1593,28 +1473,6 @@ } /** - * 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 (strpos($this->drupalGetContent(), '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; - } - - /** * Retrieves only the headers for a Drupal path or an absolute path. * * @param $path @@ -1628,156 +1486,18 @@ * The retrieved headers, also available as $this->drupalGetContent() */ protected function drupalHead($path, array $options = array(), array $headers = array()) { + $this->path = $path; + + // The browser requires the URL to be absolute. $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. - return $out; - } - /** - * 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