Code coverage for /20081101/modules/simpletest/drupal_web_test_case.php

Line #Times calledCode
1
<?php
2
// $Id: drupal_web_test_case.php,v 1.53 2008/11/01 21:21:35 dries Exp $
3
4
/**
5
 * Test case for typical Drupal tests.
6
 */
710
class DrupalWebTestCase {
8
  protected $_logged_in = FALSE;
9
  protected $_content;
10
  protected $_url;
11
  protected $plain_text;
12
  protected $ch;
13
  protected $elements;
14
  // We do not reuse the cookies in further runs, so we do not need a file
15
  // but we still need cookie handling, so we set the jar to NULL
16
  protected $cookie_file = NULL;
17
  // Overwrite this any time to supply cURL options as necessary,
18
  // DrupalTestCase itself never sets this but always obeys whats set.
19
  protected $curl_options = array();
20
  protected $db_prefix_original;
21
  protected $original_file_directory;
22
23
  var $_results = array('#pass' => 0, '#fail' => 0, '#exception' => 0);
24
  var $_assertions = array();
25
26
  /**
27
   * Constructor for DrupalWebTestCase.
28
   *
29
   * @param @test_id
30
   *   Tests with the same id are reported together.
31
   */
32
  function __construct($test_id = NULL) {
3310
    $this->test_id = $test_id;
3410
  }
35
36
  /**
37
   * This function stores the assert. Do not call directly.
38
   *
39
   * @param $status
40
   *   Can be 'pass', 'fail', 'exception'. TRUE is a synonym for 'pass',
FALSE
41
   *   for 'fail'.
42
   * @param $message
43
   *   The message string.
44
   * @param $group
45
   *   WHich group this assert belongs to.
46
   * @param $caller
47
   *   By default, the assert comes from a function which names start with
48
   *   'test'. Instead, you can specify where this assert originates from
49
   *   by passing in an associative array as $caller. Key 'file' is
50
   *   the name of the source file, 'line' is the line number and
'function'
51
   *   is the caller function itself.
52
   */
53
  protected function _assert($status, $message = '', $group = 'Other',
$caller = NULL) {
54135
    global $db_prefix;
55
56
    // Convert boolean status to string status.
57135
    if (is_bool($status)) {
58135
      $status = $status ? 'pass' : 'fail';
59135
    }
60
61
    // Increment summary result counter.
62135
    $this->_results['#' . $status]++;
63
64
    // Get the function information about the call to the assertion method.
65135
    if (!$caller) {
66135
      $caller = $this->getAssertionCall();
67135
    }
68
69
    // Switch to non-testing database to store results in.
70135
    $current_db_prefix = $db_prefix;
71135
    $db_prefix = $this->db_prefix_original;
72
73
    // Creation assertion array that can be displayed while tests are
running.
74135
    $this->_assertions[] = $assertion = array(
75135
      'test_id' => $this->test_id,
76135
      'test_class' => get_class($this),
77135
      'status' => $status,
78135
      'message' => $message,
79135
      'message_group' => $group,
80135
      'function' => $caller['function'],
81135
      'line' => $caller['line'],
82135
      'file' => $caller['file'],
83135
    );
84
85
    // Store assertion for display after the test has completed.
86135
    db_insert('simpletest')->fields($assertion)->execute();
87
88
    // Return to testing prefix.
89135
    $db_prefix = $current_db_prefix;
90135
    return $status;
910
  }
92
93
  /**
94
   * Cycles through backtrace until the first non-assertion method is
found.
95
   *
96
   * @return
97
   *   Array representing the true caller.
98
   */
99
  protected function getAssertionCall() {
100135
    $backtrace = debug_backtrace();
101
102
    // The first element is the call. The second element is the caller.
103
    // We skip calls that occured in one of the methods of
DrupalWebTestCase
104
    // or in an assertion function.
105135
    while (($caller = $backtrace[1]) &&
106135
          ((isset($caller['class']) && $caller['class'] ==
'DrupalWebTestCase') ||
107135
            substr($caller['function'], 0, 6) == 'assert')) {
108
      // We remove that call.
109135
      array_shift($backtrace);
110135
    }
111
112135
    return _drupal_get_last_caller($backtrace);
1130
  }
114
115
  /**
116
   * Check to see if a value is not false (not an empty string, 0, NULL, or
FALSE).
117
   *
118
   * @param $value
119
   *   The value on which the assertion is to be done.
120
   * @param $message
121
   *   The message to display along with the assertion.
122
   * @param $group
123
   *   The type of assertion - examples are "Browser", "PHP".
124
   * @return
125
   *   The status passed in.
126
   */
127
  protected function assertTrue($value, $message = '', $group = 'Other') {
128126
    return $this->_assert((bool) $value, $message ? $message : t('Value is
TRUE'), $group);
1290
  }
130
131
  /**
132
   * Check to see if a value is false (an empty string, 0, NULL, or FALSE).
133
   *
134
   * @param $value
135
   *   The value on which the assertion is to be done.
136
   * @param $message
137
   *   The message to display along with the assertion.
138
   * @param $group
139
   *   The type of assertion - examples are "Browser", "PHP".
140
   * @return
141
   *   The status passed in.
142
   */
143
  protected function assertFalse($value, $message = '', $group = 'Other') {
14428
    return $this->_assert(!$value, $message ? $message : t('Value is
FALSE'), $group);
1450
  }
146
147
  /**
148
   * Check to see if a value is NULL.
149
   *
150
   * @param $value
151
   *   The value on which the assertion is to be done.
152
   * @param $message
153
   *   The message to display along with the assertion.
154
   * @param $group
155
   *   The type of assertion - examples are "Browser", "PHP".
156
   * @return
157
   *   The status passed in.
158
   */
159
  protected function assertNull($value, $message = '', $group = 'Other') {
1602
    return $this->_assert(!isset($value), $message ? $message : t('Value is
NULL'), $group);
1610
  }
162
163
  /**
164
   * Check to see if a value is not NULL.
165
   *
166
   * @param $value
167
   *   The value on which the assertion is to be done.
168
   * @param $message
169
   *   The message to display along with the assertion.
170
   * @param $group
171
   *   The type of assertion - examples are "Browser", "PHP".
172
   * @return
173
   *   The status passed in.
174
   */
175
  protected function assertNotNull($value, $message = '', $group = 'Other')
{
17613
    return $this->_assert(isset($value), $message ? $message : t('Value is
not NULL'), $group);
1770
  }
178
179
  /**
180
   * Check to see if two values are equal.
181
   *
182
   * @param $first
183
   *   The first value to check.
184
   * @param $second
185
   *   The second value to check.
186
   * @param $message
187
   *   The message to display along with the assertion.
188
   * @param $group
189
   *   The type of assertion - examples are "Browser", "PHP".
190
   * @return
191
   *   The status passed in.
192
   */
193
  protected function assertEqual($first, $second, $message = '', $group =
'Other') {
19454
    return $this->_assert($first == $second, $message ? $message : t('First
value is equal to second value'), $group);
1950
  }
196
197
  /**
198
   * Check to see if two values are not equal.
199
   *
200
   * @param $first
201
   *   The first value to check.
202
   * @param $second
203
   *   The second value to check.
204
   * @param $message
205
   *   The message to display along with the assertion.
206
   * @param $group
207
   *   The type of assertion - examples are "Browser", "PHP".
208
   * @return
209
   *   The status passed in.
210
   */
211
  protected function assertNotEqual($first, $second, $message = '', $group
= 'Other') {
2127
    return $this->_assert($first != $second, $message ? $message : t('First
value is not equal to second value'), $group);
2130
  }
214
215
  /**
216
   * Check to see if two values are identical.
217
   *
218
   * @param $first
219
   *   The first value to check.
220
   * @param $second
221
   *   The second value to check.
222
   * @param $message
223
   *   The message to display along with the assertion.
224
   * @param $group
225
   *   The type of assertion - examples are "Browser", "PHP".
226
   * @return
227
   *   The status passed in.
228
   */
229
  protected function assertIdentical($first, $second, $message = '', $group
= 'Other') {
23013
    return $this->_assert($first === $second, $message ? $message :
t('First value is identical to second value'), $group);
2310
  }
232
233
  /**
234
   * Check to see if two values are not identical.
235
   *
236
   * @param $first
237
   *   The first value to check.
238
   * @param $second
239
   *   The second value to check.
240
   * @param $message
241
   *   The message to display along with the assertion.
242
   * @param $group
243
   *   The type of assertion - examples are "Browser", "PHP".
244
   * @return
245
   *   The status passed in.
246
   */
247
  protected function assertNotIdentical($first, $second, $message = '',
$group = 'Other') {
2489
    return $this->_assert($first !== $second, $message ? $message :
t('First value is not identical to second value'), $group);
2490
  }
250
251
  /**
252
   * Fire an assertion that is always positive.
253
   *
254
   * @param $message
255
   *   The message to display along with the assertion.
256
   * @param $group
257
   *   The type of assertion - examples are "Browser", "PHP".
258
   * @return
259
   *   TRUE.
260
   */
261
  protected function pass($message = NULL, $group = 'Other') {
26281
    return $this->_assert(TRUE, $message, $group);
2630
  }
264
265
  /**
266
   * Fire an assertion that is always negative.
267
   *
268
   * @param $message
269
   *   The message to display along with the assertion.
270
   * @param $group
271
   *   The type of assertion - examples are "Browser", "PHP".
272
   * @return
273
   *   FALSE.
274
   */
275
  protected function fail($message = NULL, $group = 'Other') {
2764
    return $this->_assert(FALSE, $message, $group);
2770
  }
278
279
  /**
280
   * Fire an error assertion.
281
   *
282
   * @param $message
283
   *   The message to display along with the assertion.
284
   * @param $group
285
   *   The type of assertion - examples are "Browser", "PHP".
286
   * @param $caller
287
   *   The caller of the error.
288
   */
289
  protected function error($message = '', $group = 'Other', $caller = NULL)
{
2904
    return $this->_assert('exception', $message, $group, $caller);
2910
  }
292
293
  /**
294
   * Run all tests in this class.
295
   */
296
  function run() {
297135
    set_error_handler(array($this, 'errorHandler'));
298135
    $methods = array();
299
    // Iterate through all the methods in this class.
300135
    foreach (get_class_methods(get_class($this)) as $method) {
301
      // If the current method starts with "test", run it - it's a test.
302135
      if (strtolower(substr($method, 0, 4)) == 'test') {
303135
        $this->setUp();
304
        try {
305135
          $this->$method();
306
          // Finish up.
307
        }
308135
        catch (Exception $e) {
3090
          $this->exceptionHandler($e);
310
        }
311135
        $this->tearDown();
312135
      }
313135
    }
314
    // Clear out the error messages and restore error handler.
315135
    drupal_get_messages();
316135
    restore_error_handler();
317135
  }
318
319
  /**
320
   * Handle errors.
321
   *
322
   * @see set_error_handler
323
   */
324
  function errorHandler($severity, $message, $file = NULL, $line = NULL) {
32581
    if ($severity & error_reporting()) {
326
      $error_map = array(
3274
        E_STRICT => 'Run-time notice',
3284
        E_WARNING => 'Warning',
3294
        E_NOTICE => 'Notice',
3304
        E_CORE_ERROR => 'Core error',
3314
        E_CORE_WARNING => 'Core warning',
3324
        E_USER_ERROR => 'User error',
3334
        E_USER_WARNING => 'User warning',
3344
        E_USER_NOTICE => 'User notice',
3354
        E_RECOVERABLE_ERROR => 'Recoverable error',
3364
      );
337
3384
      $backtrace = debug_backtrace();
3394
      $this->error($message, $error_map[$severity],
_drupal_get_last_caller($backtrace));
3404
    }
34181
    return TRUE;
3420
  }
343
344
  /**
345
   * Handle exceptions.
346
   *
347
   * @see set_exception_handler
348
   */
349
  function exceptionHandler($exception) {
3500
    $backtrace = $exception->getTrace();
351
    // Push on top of the backtrace the call that generated the exception.
3520
    array_unshift($backtrace, array(
3530
      'line' => $exception->getLine(),
3540
      'file' => $exception->getFile(),
3550
    ));
3560
    $this->error($exception->getMessage(), 'Uncaught exception',
_drupal_get_last_caller($backtrace));
3570
  }
358
359
  /**
360
   * Creates a node based on default settings.
361
   *
362
   * @param $settings
363
   *   An associative array of settings to change from the defaults, keys
are
364
   *   node properties, for example 'body' => 'Hello, world!'.
365
   * @return object Created node object.
366
   */
367
  function drupalCreateNode($settings = array()) {
368
    // Populate defaults array
369
    $defaults = array(
37017
      'body'      => $this->randomName(32),
37117
      'title'     => $this->randomName(8),
37217
      'comment'   => 2,
37317
      'changed'   => REQUEST_TIME,
37417
      'format'    => FILTER_FORMAT_DEFAULT,
37517
      'moderate'  => 0,
37617
      'promote'   => 0,
37717
      'revision'  => 1,
37817
      'log'       => '',
37917
      'status'    => 1,
38017
      'sticky'    => 0,
38117
      'type'      => 'page',
38217
      'revisions' => NULL,
38317
      'taxonomy'  => NULL,
38417
    );
38517
    $defaults['teaser'] = $defaults['body'];
386
    // If we already have a node, we use the original node's created time,
and this
38717
    if (isset($defaults['created'])) {
3880
      $defaults['date'] = format_date($defaults['created'], 'custom',
'Y-m-d H:i:s O');
3890
    }
39017
    if (empty($settings['uid'])) {
39113
      global $user;
39213
      $defaults['uid'] = $user->uid;
39313
    }
39417
    $node = ($settings + $defaults);
39517
    $node = (object)$node;
396
39717
    node_save($node);
398
399
    // small hack to link revisions to our test user
40017
    db_query('UPDATE {node_revisions} SET uid = %d WHERE vid = %d',
$node->uid, $node->vid);
40117
    return $node;
4020
  }
403
404
  /**
405
   * Creates a custom content type based on default settings.
406
   *
407
   * @param $settings
408
   *   An array of settings to change from the defaults.
409
   *   Example: 'type' => 'foo'.
410
   * @return
411
   *   Created content type.
412
   */
413
  function drupalCreateContentType($settings = array()) {
414
    // find a non-existent random type name.
415
    do {
4160
      $name = strtolower($this->randomName(3, 'type_'));
4170
    } while (node_get_types('type', $name));
418
419
    // Populate defaults array
420
    $defaults = array(
4210
      'type' => $name,
4220
      'name' => $name,
4230
      'description' => '',
4240
      'help' => '',
4250
      'min_word_count' => 0,
4260
      'title_label' => 'Title',
4270
      'body_label' => 'Body',
4280
      'has_title' => 1,
4290
      'has_body' => 1,
4300
    );
431
    // imposed values for a custom type
432
    $forced = array(
4330
      'orig_type' => '',
4340
      'old_type' => '',
4350
      'module' => 'node',
4360
      'custom' => 1,
4370
      'modified' => 1,
4380
      'locked' => 0,
4390
    );
4400
    $type = $forced + $settings + $defaults;
4410
    $type = (object)$type;
442
4430
    $saved_type = node_type_save($type);
4440
    node_types_rebuild();
445
4460
    $this->assertEqual($saved_type, SAVED_NEW, t('Created content type
%type.', array('%type' => $type->type)));
447
448
    // Reset permissions so that permissions for this content type are
available.
4490
    $this->checkPermissions(array(), TRUE);
450
4510
    return $type;
4520
  }
453
454
  /**
455
   * Get a list files that can be used in tests.
456
   *
457
   * @param $type
458
   *   File type, possible values: 'binary', 'html', 'image', 'javascript',
'php', 'sql', 'text'.
459
   * @param $size
460
   *   File size in bytes to match. Please check the tests/files folder.
461
   * @return
462
   *   List of files that match filter.
463
   */
464
  function drupalGetTestFiles($type, $size = NULL) {
4654
    $files = array();
466
467
    // Make sure type is valid.
4684
    if (in_array($type, array('binary', 'html', 'image', 'javascript',
'php', 'sql', 'text'))) {
469
     // Use original file directory instead of one created during setUp().
4704
      $path = $this->original_file_directory . '/simpletest';
4714
      $files = file_scan_directory($path, '/' . $type . '\-.*/');
472
473
      // If size is set then remove any files that are not of that size.
4744
      if ($size !== NULL) {
4751
        foreach ($files as $file) {
4761
          $stats = stat($file->filename);
4771
          if ($stats['size'] != $size) {
4781
            unset($files[$file->filename]);
4791
          }
4801
        }
4811
      }
4824
    }
4834
    usort($files, array($this, 'drupalCompareFiles'));
4844
    return $files;
4850
  }
486
487
  /**
488
   * Compare two files based on size and file name.
489
   */
490
  function drupalCompareFiles($file1, $file2) {
491
    // Determine which file is larger.
4924
    $compare_size = (filesize($file1->filename) >
filesize($file2->filename));
4934
    if (!$compare_size) {
494
      // Both files were the same size, so return whichever one is
alphabetically greater.
4953
      return strnatcmp($file1->name, $file2->name);
4960
    }
497
    else {
498
      // Return TRUE if $file1 is larger than $file2.
4994
      return $compare_size;
500
    }
5010
  }
502
503
  /**
504
   * Generates a random string.
505
   *
506
   * @param $number
507
   *   Number of characters in length to append to the prefix.
508
   * @param $prefix
509
   *   Prefix to use.
510
   * @return
511
   *   Randomly generated string.
512
   */
513
  function randomName($number = 4, $prefix = 'simpletest_') {
51499
    $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_';
51599
    for ($x = 0; $x < $number; $x++) {
51699
      $prefix .= $chars{mt_rand(0, strlen($chars) - 1)};
51799
      if ($x == 0) {
51899
        $chars .= '0123456789';
51999
      }
52099
    }
52199
    return $prefix;
5220
  }
523
524
  /**
525
   * Create a user with a given set of permissions. The permissions
correspond to the
526
   * names given on the privileges page.
527
   *
528
   * @param $permissions
529
   *   Array of permission names to assign to user.
530
   * @return
531
   *   A fully loaded user object with pass_raw property, or FALSE if
account
532
   *   creation fails.
533
   */
534
  function drupalCreateUser($permissions = NULL) {
535
    // Create a role with the given permission set.
53676
    if (!($rid = $this->_drupalCreateRole($permissions))) {
5374
      return FALSE;
5380
    }
539
540
    // Create a user assigned to that role.
54176
    $edit = array();
54276
    $edit['name']   = $this->randomName();
54376
    $edit['mail']   = $edit['name'] . '@example.com';
54476
    $edit['roles']  = array($rid => $rid);
54576
    $edit['pass']   = user_password();
54676
    $edit['status'] = 1;
547
54876
    $account = user_save('', $edit);
549
55076
    $this->assertTrue(!empty($account->uid), t('User created with name
%name and pass %pass', array('%name' => $edit['name'], '%pass' =>
$edit['pass'])), t('User login'));
55176
    if (empty($account->uid)) {
5520
      return FALSE;
5530
    }
554
555
    // Add the raw password so that we can log in as this user.
55676
    $account->pass_raw = $edit['pass'];
55776
    return $account;
5580
  }
559
560
  /**
561
   * Internal helper function; Create a role with specified permissions.
562
   *
563
   * @param $permissions
564
   *   Array of permission names to assign to role.
565
   * @return
566
   *   Role ID of newly created role, or FALSE if role creation failed.
567
   */
568
  private function _drupalCreateRole($permissions = NULL) {
569
    // Generate string version of permissions list.
57076
    if ($permissions === NULL) {
5718
      $permissions = array('access comments', 'access content', 'post
comments', 'post comments without approval');
5728
    }
573
57476
    if (!$this->checkPermissions($permissions)) {
5754
      return FALSE;
5760
    }
577
578
    // Create new role.
57976
    $role_name = $this->randomName();
58076
    db_query("INSERT INTO {role} (name) VALUES ('%s')", $role_name);
58176
    $role = db_fetch_object(db_query("SELECT * FROM {role} WHERE name =
'%s'", $role_name));
58276
    $this->assertTrue($role, t('Created role of name: @role_name, id:
@rid', array('@role_name' => $role_name, '@rid' => (isset($role->rid) ?
$role->rid : t('-n/a-')))), t('Role'));
58376
    if ($role && !empty($role->rid)) {
584
      // Assign permissions to role and mark it for clean-up.
58576
      foreach ($permissions as $permission_string) {
58676
        db_query("INSERT INTO {role_permission} (rid, permission) VALUES
(%d, '%s')", $role->rid, $permission_string);
58776
      }
58876
      $count = db_result(db_query("SELECT COUNT(*) FROM {role_permission}
WHERE rid = %d", $role->rid));
58976
      $this->assertTrue($count == count($permissions), t('Created
permissions: @perms', array('@perms' => implode(', ', $permissions))),
t('Role'));
59076
      return $role->rid;
5910
    }
592
    else {
5930
      return FALSE;
594
    }
5950
  }
596
597
  /**
598
   * Check to make sure that the array of permissions are valid.
599
   *
600
   * @param $permissions
601
   *   Permissions to check.
602
   * @param $reset
603
   *   Reset cached available permissions.
604
   * @return
605
   *   TRUE or FALSE depending on whether the permissions are valid.
606
   */
607
  private function checkPermissions(array $permissions, $reset = FALSE) {
608134
    static $available;
609
610134
    if (!isset($available) || $reset) {
611134
      $available = array_keys(module_invoke_all('perm'));
612134
    }
613
614134
    $valid = TRUE;
615134
    foreach ($permissions as $permission) {
61676
      if (!in_array($permission, $available)) {
6174
        $this->fail(t('Invalid permission %permission.',
array('%permission' => $permission)), t('Role'));
6184
        $valid = FALSE;
6194
      }
62076
    }
621134
    return $valid;
6220
  }
623
624
  /**
625
   * Logs in a user with the internal browser. If already logged in then
logs the current
626
   * user out before logging in the specified user. If no user is specified
then a new
627
   * user will be created and logged in.
628
   *
629
   * @param $user
630
   *   User object representing the user to login.
631
   * @return
632
   *   User that was logged in. Useful if no user was passed in order to
retrieve
633
   *   the created user.
634
   */
635
  function drupalLogin($user = NULL) {
63671
    if ($this->_logged_in) {
63716
      $this->drupalLogout();
63816
    }
639
64071
    if (!isset($user)) {
6410
      $user = $this->_drupalCreateRole();
6420
    }
643
644
    $edit = array(
64571
      'name' => $user->name,
64671
      'pass' => $user->pass_raw
64771
    );
64871
    $this->drupalPost('user', $edit, t('Log in'));
649
65071
    $pass = $this->assertText($user->name, t('Found name: %name',
array('%name' => $user->name)), t('User login'));
65171
    $pass = $pass && $this->assertNoText(t('The username %name has been
blocked.', array('%name' => $user->name)), t('No blocked message at login
page'), t('User login'));
65271
    $pass = $pass && $this->assertNoText(t('The name %name is a reserved
username.', array('%name' => $user->name)), t('No reserved message at login
page'), t('User login'));
653
65471
    $this->_logged_in = $pass;
655
65671
    return $user;
6570
  }
658
659
  /*
660
   * Logs a user out of the internal browser, then check the login page to
confirm logout.
661
   */
662
  function drupalLogout() {
663
    // Make a request to the logout page.
66429
    $this->drupalGet('logout');
665
666
    // Load the user page, the idea being if you were properly logged out
you should be seeing a login screen.
66729
    $this->drupalGet('user');
66829
    $pass = $this->assertField('name', t('Username field found.'),
t('Logout'));
66929
    $pass = $pass && $this->assertField('pass', t('Password field found.'),
t('Logout'));
670
67129
    $this->_logged_in = !$pass;
67229
  }
673
674
  /**
675
   * Generates a random database prefix, runs the install scripts on the
676
   * prefixed database and enable the specified modules. After installation
677
   * many caches are flushed and the internal browser is setup so that the
678
   * page requests will run on the new prefix. A temporary files directory
679
   * is created with the same name as the database prefix.
680
   *
681
   * @param ...
682
   *   List of modules to enable for the duration of the test.
683
   */
684
  function setUp() {
685134
    global $db_prefix;
686
687
    // Store necessary current values before switching to prefixed
database.
688134
    $this->db_prefix_original = $db_prefix;
689134
    $clean_url_original = variable_get('clean_url', 0);
690
691
    // Generate temporary prefixed database to ensure that tests have a
clean starting point.
692134
    $db_prefix =
Database::getActiveConnection()->prefixTables('{simpletest' . mt_rand(1000,
1000000) . '}');
693
694134
    include_once DRUPAL_ROOT . '/includes/install.inc';
695134
    drupal_install_system();
696
697
    // Add the specified modules to the list of modules in the default
profile.
698134
    $args = func_get_args();
699134
    $modules =
array_unique(array_merge(drupal_get_profile_modules('default', 'en'),
$args));
700134
    drupal_install_modules($modules);
701
702
    // Because the schema is static cached, we need to flush
703
    // it between each run.  If we don't, then it will contain
704
    // stale data for the previous run's database prefix and all
705
    // calls to it will fail.
706134
    drupal_get_schema(NULL, TRUE);
707
708
    // Run default profile tasks.
709134
    $task = 'profile';
710134
    default_profile_tasks($task, '');
711
712
    // Rebuild caches.
713134
    menu_rebuild();
714134
    actions_synchronize();
715134
    _drupal_flush_css_js();
716134
    $this->refreshVariables();
717134
    $this->checkPermissions(array(), TRUE);
718
719
    // Restore necessary variables.
720134
    variable_set('install_profile', 'default');
721134
    variable_set('install_task', 'profile-finished');
722134
    variable_set('clean_url', $clean_url_original);
723
724
    // Use temporary files directory with the same prefix as database.
725134
    $this->original_file_directory = file_directory_path();
726134
    variable_set('file_directory_path', file_directory_path() . '/' .
$db_prefix);
727134
    $directory = file_directory_path();
728134
    file_check_directory($directory, FILE_CREATE_DIRECTORY); // Create the
files directory.
729134
  }
730
731
  /**
732
   * Refresh the in-memory set of variables. Useful after a page request is
made
733
   * that changes a variable in a different thread.
734
   *
735
   * In other words calling a settings page with $this->drupalPost() with a
changed
736
   * value would update a variable to reflect that change, but in the
thread that
737
   * made the call (thread running the test) the changed variable would not
be
738
   * picked up.
739
   *
740
   * This method clears the variables cache and loads a fresh copy from the
database
741
   * to ensure that the most up-to-date set of variables is loaded.
742
   */
743
  function refreshVariables() {
744134
    global $conf;
745134
    cache_clear_all('variables', 'cache');
746134
    $conf = variable_init();
747134
  }
748
749
  /**
750
   * Delete created files and temporary files directory, delete the tables
created by setUp(),
751
   * and reset the database prefix.
752
   */
753
  function tearDown() {
754135
    global $db_prefix;
755135
    if (preg_match('/simpletest\d+/', $db_prefix)) {
756
      // Delete temporary files directory and reset files directory path.
757134
      simpletest_clean_temporary_directory(file_directory_path());
758134
      variable_set('file_directory_path', $this->original_file_directory);
759
760
      // Remove all prefixed tables (all the tables in the schema).
761134
      $schema = drupal_get_schema(NULL, TRUE);
762134
      $ret = array();
763134
      foreach ($schema as $name => $table) {
764134
        db_drop_table($ret, $name);
765134
      }
766
767
      // Return the database prefix to the original.
768134
      $db_prefix = $this->db_prefix_original;
769
770
      // Ensure that the internal logged in variable is reset.
771134
      $this->_logged_in = FALSE;
772
773
      // Reload module list to ensure that test module hooks aren't called
after tests.
774134
      module_list(TRUE);
775
776
      // Rebuild caches.
777134
      $this->refreshVariables();
778
779
      // Close the CURL handler.
780134
      $this->curlClose();
781134
    }
782135
  }
783
784
  /**
785
   * Initializes the cURL connection and gets a session cookie.
786
   *
787
   * This function will add authentication headers as specified in
788
   * simpletest_httpauth_username and simpletest_httpauth_pass variables.
789
   * Also, see the description of $curl_options among the properties.
790
   */
791
  protected function curlConnect() {
79278
    global $base_url, $db_prefix;
79378
    if (!isset($this->ch)) {
79478
      $this->ch = curl_init();
79578
      $curl_options = $this->curl_options + array(
79678
        CURLOPT_COOKIEJAR => $this->cookie_file,
79778
        CURLOPT_URL => $base_url,
79878
        CURLOPT_FOLLOWLOCATION => TRUE,
79978
        CURLOPT_RETURNTRANSFER => TRUE,
80078
        CURLOPT_SSL_VERIFYPEER => FALSE,  // Required to make the tests run
on https://
80178
        CURLOPT_SSL_VERIFYHOST => FALSE,  // Required to make the tests run
on https://
80278
      );
80378
      if (preg_match('/simpletest\d+/', $db_prefix, $matches)) {
80478
        $curl_options[CURLOPT_USERAGENT] = $matches[0];
80578
      }
80678
      if (!isset($curl_options[CURLOPT_USERPWD]) && ($auth =
variable_get('simpletest_httpauth_username', ''))) {
8070
        if ($pass = variable_get('simpletest_httpauth_pass', '')) {
8080
          $auth .= ':' . $pass;
8090
        }
8100
        $curl_options[CURLOPT_USERPWD] = $auth;
8110
      }
81278
      return $this->curlExec($curl_options);
8130
    }
81478
  }
815
816
  /**
817
   * Performs a cURL exec with the specified options after calling
curlConnect().
818
   *
819
   * @param
820
   *   $curl_options Custom cURL options.
821
   * @return
822
   *   Content returned from the exec.
823
   */
824
  protected function curlExec($curl_options) {
82578
    $this->curlConnect();
82678
    $url = empty($curl_options[CURLOPT_URL]) ? curl_getinfo($this->ch,
CURLINFO_EFFECTIVE_URL) : $curl_options[CURLOPT_URL];
82778
    curl_setopt_array($this->ch, $this->curl_options + $curl_options);
82878
    $this->drupalSetContent(curl_exec($this->ch), curl_getinfo($this->ch,
CURLINFO_EFFECTIVE_URL));
82978
    $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'));
83078
    return $this->drupalGetContent();
8310
  }
832
833
  /**
834
   * Close the cURL handler and unset the handler.
835
   */
836
  protected function curlClose() {
837134
    if (isset($this->ch)) {
83878
      curl_close($this->ch);
83978
      unset($this->ch);
84078
    }
841134
  }
842
843
  /**
844
   * Parse content returned from curlExec using DOM and SimpleXML.
845
   *
846
   * @return
847
   *   A SimpleXMLElement or FALSE on failure.
848
   */
849
  protected function parse() {
85077
    if (!$this->elements) {
851
      // DOM can load HTML soup. But, HTML soup can throw warnings, supress
852
      // them.
85377
      @$htmlDom = DOMDocument::loadHTML($this->_content);
85477
      if ($htmlDom) {
85577
        $this->pass(t('Valid HTML found on "@path"', array('@path' =>
$this->getUrl())), t('Browser'));
856
        // It's much easier to work with simplexml than DOM, luckily enough
857
        // we can just simply import our DOM tree.
85877
        $this->elements = simplexml_import_dom($htmlDom);
85977
      }
86077
    }
86177
    if (!$this->elements) {
8620
      $this->fail(t('Parsed page successfully.'), t('Browser'));
8630
    }
864
86577
    return $this->elements;
8660
  }
867
868
  /**
869
   * Retrieves a Drupal path or an absolute path.
870
   *
871
   * @param $path
872
   *   Drupal path or URL to load into internal browser
873
   * @param $options
874
   *  Options to be forwarded to url().
875
   * @return
876
   *  The retrieved HTML string, also available as
$this->drupalGetContent()
877
   */
878
  function drupalGet($path, $options = array()) {
87977
    $options['absolute'] = TRUE;
880
881
    // We re-using a CURL connection here.  If that connection still has
certain
882
    // options set, it might change the GET into a POST.  Make sure we
clear out
883
    // previous options.
88477
    $out = $this->curlExec(array(CURLOPT_HTTPGET => TRUE, CURLOPT_URL =>
url($path, $options), CURLOPT_HEADER => FALSE, CURLOPT_NOBODY => FALSE));
88577
    $this->refreshVariables(); // Ensure that any changes to variables in
the other thread are picked up.
886
887
    // Replace original page output with new output from redirected
page(s).
88877
    if (($new = $this->checkForMetaRefresh())) {
8891
      $out = $new;
8901
    }
89177
    return $out;
8920
  }
893
894
  /**
895
   * Execute a POST request on a Drupal page.
896
   * It will be done as usual POST request with SimpleBrowser.
897
   *
898
   * @param $path
899
   *   Location of the post form. Either a Drupal path or an absolute path
or
900
   *   NULL to post to the current page. For multi-stage forms you can set
the
901
   *   path to NULL and have it post to the last received page. Example:
902
   *
903
   *   // First step in form.
904
   *   $edit = array(...);
905
   *   $this->drupalPost('some_url', $edit, t('Save'));
906
   *
907
   *   // Second step in form.
908
   *   $edit = array(...);
909
   *   $this->drupalPost(NULL, $edit, t('Save'));
910
   * @param  $edit
911
   *   Field data in an assocative array. Changes the current input fields
912
   *   (where possible) to the values indicated. A checkbox can be set to
913
   *   TRUE to be checked and FALSE to be unchecked. Note that when a form
914
   *   contains file upload fields, other fields cannot start with the '@'
915
   *   character.
916
   *
917
   *   Multiple select fields can be set using name[] and setting each of
the
918
   *   possible values. Example:
919
   *   $edit = array();
920
   *   $edit['name[]'] = array('value1', 'value2');
921
   * @param $submit
922
   *   Value of the submit button.
923
   * @param $options
924
   *   Options to be forwarded to url().
925
   */
926
  function drupalPost($path, $edit, $submit, $options = array()) {
92772
    $submit_matches = FALSE;
92872
    if (isset($path)) {
92972
      $html = $this->drupalGet($path, $options);
93072
    }
93172
    if ($this->parse()) {
93272
      $edit_save = $edit;
933
      // Let's iterate over all the forms.
93472
      $forms = $this->xpath('//form');
93572
      foreach ($forms as $form) {
936
        // We try to set the fields of this form as specified in $edit.
93772
        $edit = $edit_save;
93872
        $post = array();
93972
        $upload = array();
94072
        $submit_matches = $this->handleForm($post, $edit, $upload, $submit,
$form);
94172
        $action = isset($form['action']) ?
$this->getAbsoluteUrl($form['action']) : $this->getUrl();
942
943
        // We post only if we managed to handle every field in edit and the
944
        // submit button matches.
94572
        if (!$edit && $submit_matches) {
94672
          if ($upload) {
947
            // TODO: cURL handles file uploads for us, but the
implementation
948
            // is broken. This is a less than elegant workaround.
Alternatives
949
            // are being explored at #253506.
9504
            foreach ($upload as $key => $file) {
9514
              $post[$key] = '@' . realpath($file);
9524
            }
9534
          }
954
          else {
95572
            foreach ($post as $key => $value) {
956
              // Encode according to application/x-www-form-urlencoded
957
              // Both names and values needs to be urlencoded, according to
958
              // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
95972
              $post[$key] = urlencode($key) . '=' . urlencode($value);
96072
            }
96172
            $post = implode('&', $post);
962
          }
96372
          $out = $this->curlExec(array(CURLOPT_URL => $action, CURLOPT_POST
=> TRUE, CURLOPT_POSTFIELDS => $post, CURLOPT_HEADER => FALSE));
964
          // Ensure that any changes to variables in the other thread are
picked up.
96572
          $this->refreshVariables();
966
967
          // Replace original page output with new output from redirected
page(s).
96872
          if (($new = $this->checkForMetaRefresh())) {
9691
            $out = $new;
9701
          }
97172
          return $out;
9720
        }
9737
      }
974
      // We have not found a form which contained all fields of $edit.
9750
      foreach ($edit as $name => $value) {
9760
        $this->fail(t('Failed to set field @name to @value', array('@name'
=> $name, '@value' => $value)));
9770
      }
9780
      $this->assertTrue($submit_matches, t('Found the @submit button',
array('@submit' => $submit)));
9790
      $this->fail(t('Found the requested form fields at @path',
array('@path' => $path)));
9800
    }
9810
  }
982
983
  /**
984
   * Check for meta refresh tag and if found call drupalGet() recursively.
This
985
   * function looks for the http-equiv attribute to be set to "Refresh"
986
   * and is case-sensitive.
987
   *
988
   * @return
989
   *   Either the new page content or FALSE.
990
   */
991
  private function checkForMetaRefresh() {
99277
    if ($this->drupalGetContent() != '' && $this->parse()) {
99377
      $refresh = $this->xpath('//meta[@http-equiv="Refresh"]');
99477
      if (!empty($refresh)) {
995
        // Parse the content attribute of the meta tag for the format:
996
        // "[delay]: URL=[page_to_redirect_to]".
9971
        if (preg_match('/\d+;\s*URL=(?P<url>.*)/i', $refresh[0]['content'],
$match)) {
9981
          return
$this->drupalGet($this->getAbsoluteUrl(decode_entities($match['url'])));
9990
        }
10000
      }
100177
    }
100277
    return FALSE;
10030
  }
1004
1005
  /**
1006
   * Retrieves only the headers for a Drupal path or an absolute path.
1007
   *
1008
   * @param $path
1009
   *   Drupal path or URL to load into internal browser
1010
   * @param $options
1011
   *   Options to be forwarded to url().
1012
   * @return
1013
   *   The retrieved headers, also available as $this->drupalGetContent()
1014
   */
1015
  function drupalHead($path, $options = array()) {
10161
    $options['absolute'] = TRUE;
10171
    $out = $this->curlExec(array(CURLOPT_HEADER => TRUE, CURLOPT_NOBODY =>
TRUE, CURLOPT_URL => url($path, $options)));
10181
    $this->refreshVariables(); // Ensure that any changes to variables in
the other thread are picked up.
10191
    return $out;
10200
  }
1021
1022
  /**
1023
   * Handle form input related to drupalPost(). Ensure that the specified
fields
1024
   * exist and attempt to create POST data in the correct manner for the
particular
1025
   * field type.
1026
   *
1027
   * @param $post
1028
   *   Reference to array of post values.
1029
   * @param $edit
1030
   *   Reference to array of edit values to be checked against the form.
1031
   * @param $submit
1032
   *   Form submit button value.
1033
   * @param $form
1034
   *   Array of form elements.
1035
   * @return
1036
   *   Submit value matches a valid submit input in the form.
1037
   */
1038
  protected function handleForm(&$post, &$edit, &$upload, $submit, $form) {
1039
    // Retrieve the form elements.
104072
    $elements = $form->xpath('.//input|.//textarea|.//select');
104172
    $submit_matches = FALSE;
104272
    foreach ($elements as $element) {
1043
      // SimpleXML objects need string casting all the time.
104472
      $name = (string) $element['name'];
1045
      // This can either be the type of <input> or the name of the tag
itself
1046
      // for <select> or <textarea>.
104772
      $type = isset($element['type']) ? (string)$element['type'] :
$element->getName();
104872
      $value = isset($element['value']) ? (string)$element['value'] : '';
104972
      $done = FALSE;
105072
      if (isset($edit[$name])) {
1051
        switch ($type) {
105272
          case 'text':
105372
          case 'textarea':
105472
          case 'password':
105572
            $post[$name] = $edit[$name];
105672
            unset($edit[$name]);
105772
            break;
105843
          case 'radio':
10597
            if ($edit[$name] == $value) {
10607
              $post[$name] = $edit[$name];
10617
              unset($edit[$name]);
10627
            }
10637
            break;
106442
          case 'checkbox':
1065
            // To prevent checkbox from being checked.pass in a FALSE,
1066
            // otherwise the checkbox will be set to its value regardless
1067
            // of $edit.
106821
            if ($edit[$name] === FALSE) {
106910
              unset($edit[$name]);
107010
              continue 2;
10710
            }
1072
            else {
107321
              unset($edit[$name]);
107421
              $post[$name] = $value;
1075
            }
107621
            break;
107734
          case 'select':
107832
            $new_value = $edit[$name];
107932
            $index = 0;
108032
            $key = preg_replace('/\[\]$/', '', $name);
108132
            $options = $this->getAllOptions($element);
108232
            foreach ($options as $option) {
108332
              if (is_array($new_value)) {
10841
                $option_value= (string)$option['value'];
10851
                if (in_array($option_value, $new_value)) {
10861
                  $post[$key . '[' . $index++ . ']'] = $option_value;
10871
                  $done = TRUE;
10881
                  unset($edit[$name]);
10891
                }
10901
              }
109131
              elseif ($new_value == $option['value']) {
109230
                $post[$name] = $new_value;
109330
                unset($edit[$name]);
109430
                $done = TRUE;
109530
              }
109632
            }
109732
            break;
10984
          case 'file':
10994
            $upload[$name] = $edit[$name];
11004
            unset($edit[$name]);
11014
            break;
11020
        }
110372
      }
110472
      if (!isset($post[$name]) && !$done) {
1105
        switch ($type) {
110672
          case 'textarea':
110723
            $post[$name] = (string)$element;
110823
            break;
110972
          case 'select':
111033
            $single = empty($element['multiple']);
111133
            $first = TRUE;
111233
            $index = 0;
111333
            $key = preg_replace('/\[\]$/', '', $name);
111433
            $options = $this->getAllOptions($element);
111533
            foreach ($options as $option) {
1116
              // For single select, we load the first option, if there is a
1117
              // selected option that will overwrite it later.
111833
              if ($option['selected'] || ($first && $single)) {
111932
                $first = FALSE;
112032
                if ($single) {
112132
                  $post[$name] = (string)$option['value'];
112232
                }
1123
                else {
11240
                  $post[$key . '[' . $index++ . ']'] =
(string)$option['value'];
1125
                }
112632
              }
112733
            }
112833
            break;
112972
          case 'file':
11304
            break;
113172
          case 'submit':
113272
          case 'image':
113372
            if ($submit == $value) {
113472
              $post[$name] = $value;
113572
              $submit_matches = TRUE;
113672
            }
113772
            break;
113872
          case 'radio':
113972
          case 'checkbox':
114040
            if (!isset($element['checked'])) {
114129
              break;
11420
            }
1143
            // Deliberate no break.
114472
          default:
114572
            $post[$name] = $value;
114672
        }
114772
      }
114872
    }
114972
    return $submit_matches;
11500
  }
1151
1152
  /**
1153
   * Peform an xpath search on the contents of the internal browser. The
search
1154
   * is relative to the root element (HTML tag normally) of the page.
1155
   *
1156
   * @param $xpath
1157
   *   The xpath string to use in the search.
1158
   * @return
1159
   *   The return value of the xpath search. For details on the xpath
string
1160
   *   format and return values see the SimpleXML documentation.
1161
   *   http://us.php.net/manual/function.simplexml-element-xpath.php
1162
   */
1163
  public function xpath($xpath) {
116477
    if ($this->parse()) {
116577
      return $this->elements->xpath($xpath);
11660
    }
11670
    return FALSE;
11680
  }
1169
1170
  /**
1171
   * Get all option elements, including nested options, in a select.
1172
   *
1173
   * @param $element
1174
   *   The element for which to get the options.
1175
   * @return
1176
   *   Option elements in select.
1177
   */
1178
  private function getAllOptions(SimpleXMLElement $element) {
117942
    $options = array();
1180
    // Add all options items.
118142
    foreach ($element->option as $option) {
118242
      $options[] = $option;
118342
    }
1184
1185
    // Search option group children.
118642
    if (isset($element->optgroup)) {
11872
      foreach ($element->optgroup as $group) {
11882
        $options = array_merge($options, $this->getAllOptions($group));
11892
      }
11902
    }
119142
    return $options;
11920
  }
1193
1194
  /**
1195
   * Pass if a link with the specified label is found, and optional with
the
1196
   * specified index.
1197
   *
1198
   * @param $label
1199
   *   Text between the anchor tags.
1200
   * @param $index
1201
   *   Link position counting from zero.
1202
   * @param $message
1203
   *   Message to display.
1204
   * @param $group
1205
   *   The group this message belongs to, defaults to 'Other'.
1206
   */
1207
  public function assertLink($label, $index = 0, $message = '', $group =
'Other') {
12081
    $links = $this->xpath('//a[text()="' . $label . '"]');
12091
    $message = ($message ?  $message : t('Link with label "!label" found.',
array('!label' => $label)));
12101
    $this->_assert(isset($links[$index]), $message, $group);
12111
  }
1212
1213
  /**
1214
   * Pass if a link with the specified label is not found.
1215
   *
1216
   * @param $label
1217
   *   Text between the anchor tags.
1218
   * @param $index
1219
   *   Link position counting from zero.
1220
   * @param $message
1221
   *   Message to display.
1222
   * @param $group
1223
   *   The group this message belongs to, defaults to 'Other'.
1224
   */
1225
  public function assertNoLink($label, $message = '', $group = 'Other') {
12261
    $links = $this->xpath('//a[text()="' . $label . '"]');
12271
    $message = ($message ?  $message : t('Link with label "!label" not
found.', array('!label' => $label)));
12281
    $this->_assert(empty($links), $message, $group);
12291
  }
1230
1231
  /**
1232
   * Follows a link by name.
1233
   *
1234
   * Will click the first link found with this link text by default, or a
1235
   * later one if an index is given. Match is case insensitive with
1236
   * normalized space. The label is translated label. There is an assert
1237
   * for successful click.
1238
   *
1239
   * @param $label
1240
   *   Text between the anchor tags.
1241
   * @param $index
1242
   *   Link position counting from zero.
1243
   * @return
1244
   *   Page on success, or FALSE on failure.
1245
   */
1246
  function clickLink($label, $index = 0) {
124711
    $url_before = $this->getUrl();
124811
    $urls = $this->xpath('//a[text()="' . $label . '"]');
1249
125011
    if (isset($urls[$index])) {
125111
      $url_target = $this->getAbsoluteUrl($urls[$index]['href']);
125211
    }
1253
125411
    $this->assertTrue(isset($urls[$index]), t('Clicked link "!label"
(!url_target) from !url_before', array('!label' => $label, '!url_target' =>
$url_target, '!url_before' => $url_before)), t('Browser'));
1255
125611
    if (isset($urls[$index])) {
125711
      return $this->drupalGet($url_target);
12580
    }
12590
    return FALSE;
12600
  }
1261
1262
  /**
1263
   * Takes a path and returns an absolute path.
1264
   *
1265
   * @param $path
1266
   *   The path, can be a Drupal path or a site-relative path. It might
have a
1267
   *   query, too. Can even be an absolute path which is just passed
through.
1268
   * @return
1269
   *   An absolute path.
1270
   */
1271
  function getAbsoluteUrl($path) {
127272
    $options = array('absolute' => TRUE);
127372
    $parts = parse_url($path);
1274
    // This is more crude than the menu_is_external but enough here.
127572
    if (empty($parts['host'])) {
127672
      $path = $parts['path'];
127772
      $base_path = base_path();
127872
      $n = strlen($base_path);
127972
      if (substr($path, 0, $n) == $base_path) {
128072
        $path = substr($path, $n);
128172
      }
128272
      if (isset($parts['query'])) {
12838
        $options['query'] = $parts['query'];
12848
      }
128572
      $path = url($path, $options);
128672
    }
128772
    return $path;
12880
  }
1289
1290
  /**
1291
   * Get the current url from the cURL handler.
1292
   *
1293
   * @return
1294
   *   The current url.
1295
   */
1296
  function getUrl() {
129777
    return $this->_url;
12980
  }
1299
1300
  /**
1301
   * Gets the current raw HTML of requested page.
1302
   */
1303
  function drupalGetContent() {
130478
    return $this->_content;
13050
  }
1306
1307
  /**
1308
   * Sets the raw HTML content. This can be useful when a page has been
fetched
1309
   * outside of the internal browser and assertions need to be made on the
1310
   * returned page.
1311
   *
1312
   * A good example would be when testing drupal_http_request(). After
fetching
1313
   * the page the content can be set and page elements can be checked to
ensure
1314
   * that the function worked properly.
1315
   */
1316
  function drupalSetContent($content, $url = 'internal:') {
131778
    $this->_content = $content;
131878
    $this->_url = $url;
131978
    $this->plain_text = FALSE;
132078
    $this->elements = FALSE;
132178
  }
1322
1323
  /**
1324
   * Pass if the raw text IS found on the loaded page, fail otherwise. Raw
text
1325
   * refers to the raw HTML that the page generated.
1326
   *
1327
   * @param $raw
1328
   *  Raw (HTML) string to look for.
1329
   * @param $message
1330
   *   Message to display.
1331
   * @param $group
1332
   *   The group this message belongs to, defaults to 'Other'.
1333
   * @return
1334
   *   TRUE on pass, FALSE on fail.
1335
   */
1336
  function assertRaw($raw, $message = '%s found', $group = 'Other') {
133740
    return $this->_assert(strpos($this->_content, $raw) !== FALSE,
$message, $group);
13380
  }
1339
1340
  /**
1341
   * Pass if the raw text is NOT found on the loaded page, fail otherwise.
Raw text
1342
   * refers to the raw HTML that the page generated.
1343
   *
1344
   * @param $raw
1345
   *   Raw (HTML) string to look for.
1346
   * @param $message
1347
   *   Message to display.
1348
   * @param $group
1349
   *   The group this message belongs to, defaults to 'Other'.
1350
   * @return
1351
   *   TRUE on pass, FALSE on fail.
1352
   */
1353
  function assertNoRaw($raw, $message = '%s found', $group = 'Other') {
13547
    return $this->_assert(strpos($this->_content, $raw) === FALSE,
$message, $group);
13550
  }
1356
1357
  /**
1358
   * Pass if the text IS found on the text version of the page. The text
version
1359
   * is the equivilent of what a user would see when viewing through a web
browser.
1360
   * In other words the HTML has been filtered out of the contents.
1361
   *
1362
   * @param $text
1363
   *  Plain text to look for.
1364
   * @param $message
1365
   *   Message to display.
1366
   * @param $group
1367
   *   The group this message belongs to, defaults to 'Other'.
1368
   * @return
1369
   *   TRUE on pass, FALSE on fail.
1370
   */
1371
  function assertText($text, $message = '', $group = 'Other') {
137276
    return $this->assertTextHelper($text, $message, $group, FALSE);
13730
  }
1374
1375
  /**
1376
   * Pass if the text is NOT found on the text version of the page. The
text version
1377
   * is the equivilent of what a user would see when viewing through a web
browser.
1378
   * In other words the HTML has been filtered out of the contents.
1379
   *
1380
   * @param $text
1381
   *   Plain text to look for.
1382
   * @param $message
1383
   *   Message to display.
1384
   * @param $group
1385
   *   The group this message belongs to, defaults to 'Other'.
1386
   * @return
1387
   *   TRUE on pass, FALSE on fail.
1388
   */
1389
  function assertNoText($text, $message = '', $group = 'Other') {
139072
    return $this->assertTextHelper($text, $message, $group, TRUE);
13910
  }
1392
1393
  /**
1394
   * Helper for assertText and assertNoText.
1395
   *
1396
   * It is not recommended to call this function directly.
1397
   *
1398
   * @param $text
1399
   *   Plain text to look for.
1400
   * @param $message
1401
   *   Message to display.
1402
   * @param $group
1403
   *   The group this message belongs to.
1404
   * @param $not_exists
1405
   *   TRUE if this text should not exist, FALSE if it should.
1406
   * @return
1407
   *   TRUE on pass, FALSE on fail.
1408
   */
1409
  protected function assertTextHelper($text, $message, $group, $not_exists)
{
141076
    if ($this->plain_text === FALSE) {
141176
      $this->plain_text = filter_xss($this->_content, array());
141276
    }
141376
    if (!$message) {
14143
      $message = '"' . $text . '"' . ($not_exists ? ' not found' : '
found');
14153
    }
141676
    return $this->_assert($not_exists == (strpos($this->plain_text, $text)
=== FALSE), $message, $group);
14170
  }
1418
1419
  /**
1420
   * Will trigger a pass if the Perl regex pattern is found in the raw
content.
1421
   *
1422
   * @param $pattern
1423
   *   Perl regex to look for including the regex delimiters.
1424
   * @param $message
1425
   *   Message to display.
1426
   * @param $group
1427
   *   The group this message belongs to.
1428
   * @return
1429
   *   TRUE on pass, FALSE on fail.
1430
   */
1431
  function assertPattern($pattern, $message = 'Pattern %s found', $group =
'Other') {
14322
    return $this->_assert((bool) preg_match($pattern,
$this->drupalGetContent()), $message, $group);
14330
  }
1434
1435
  /**
1436
   * Will trigger a pass if the perl regex pattern is not present in raw
content.
1437
   *
1438
   * @param $pattern
1439
   *   Perl regex to look for including the regex delimiters.
1440
   * @param $message
1441
   *   Message to display.
1442
   * @param $group
1443
   *   The group this message belongs to.
1444
   * @return
1445
   *   TRUE on pass, FALSE on fail.
1446
   */
1447
  function assertNoPattern($pattern, $message = 'Pattern %s not found',
$group = 'Other') {
14481
    return $this->_assert(!preg_match($pattern, $this->drupalGetContent()),
$message, $group);
14490
  }
1450
1451
  /**
1452
   * Pass if the page title is the given string.
1453
   *
1454
   * @param $title
1455
   *  The string the title should be.
1456
   * @param $message
1457
   *   Message to display.
1458
   * @param $group
1459
   *   The group this message belongs to.
1460
   * @return
1461
   *   TRUE on pass, FALSE on fail.
1462
   */
1463
  function assertTitle($title, $message, $group = 'Other') {
14647
    return $this->_assert($this->xpath('//title[text()="' . $title . '"]')
!== FALSE, $message, $group);
14650
  }
1466
1467
  /**
1468
   * Assert that a field exists in the current page by the given XPath.
1469
   *
1470
   * @param $xpath
1471
   *   XPath used to find the field.
1472
   * @param $value
1473
   *   Value of the field to assert.
1474
   * @param $message
1475
   *   Message to display.
1476
   * @param $group
1477
   *   The group this message belongs to.
1478
   * @return
1479
   *   TRUE on pass, FALSE on fail.
1480
   */
1481
  function assertFieldByXPath($xpath, $value, $message, $group = 'Other') {
148236
    $fields = $this->xpath($xpath);
1483
1484
    // If value specified then check array for match.
148536
    $found = TRUE;
148636
    if ($value) {
14874
      $found = FALSE;
14884
      if ($fields) {
14894
        foreach ($fields as $field) {
14904
          if (isset($field['value']) && $field['value'] == $value) {
1491
            // Input element with correct value.
14924
            $found = TRUE;
14934
          }
14943
          elseif (isset($field->option)) {
1495
            // Select element found.
14960
            if ($this->getSelectedItem($field) == $value) {
14970
              $found = TRUE;
14980
            }
1499
            else {
1500
              // No item selected so use first item.
15010
              $items = $this->getAllOptions($field);
15020
              if (!empty($items) && $items[0]['value'] == $value) {
15030
                $found = TRUE;
15040
              }
1505
            }
15060
          }
15073
          elseif (isset($field[0]) && $field[0] == $value) {
1508
            // Text area with correct text.
15092
            $found = TRUE;
15102
          }
15114
        }
15124
      }
15134
    }
151436
    return $this->assertTrue($fields && $found, $message, $group);
15150
  }
1516
1517
  /**
1518
   * Get the selected value from a select field.
1519
   *
1520
   * @param $element
1521
   *   SimpleXMLElement select element.
1522
   * @return
1523
   *   The selected value or FALSE.
1524
   */
1525
  function getSelectedItem(SimpleXMLElement $element) {
15260
    foreach ($element->children() as $item) {
15270
      if (isset($item['selected'])) {
15280
        return $item['value'];
15290
      }
15300
      elseif ($item->getName() == 'optgroup') {
15310
        if ($value = $this->getSelectedItem($item)) {
15320
          return $value;
15330
        }
15340
      }
15350
    }
15360
    return FALSE;
15370
  }
1538
1539
  /**
1540
   * Assert that a field does not exist in the current page by the given
XPath.
1541
   *
1542
   * @param $xpath
1543
   *   XPath used to find the field.
1544
   * @param $value
1545
   *   Value of the field to assert.
1546
   * @param $message
1547
   *   Message to display.
1548
   * @param $group
1549
   *   The group this message belongs to.
1550
   * @return
1551
   *   TRUE on pass, FALSE on fail.
1552
   */
1553
  function assertNoFieldByXPath($xpath, $value, $message, $group = 'Other')
{
15546
    $fields = $this->xpath($xpath);
1555
1556
    // If value specified then check array for match.
15576
    $found = TRUE;
15586
    if ($value) {
15595
      $found = FALSE;
15605
      if ($fields) {
15614
        foreach ($fields as $field) {
15624
          if ($field['value'] == $value) {
15630
            $found = TRUE;
15640
          }
15654
        }
15664
      }
15675
    }
15686
    return $this->assertFalse($fields && $found, $message, $group);
15690
  }
1570
1571
  /**
1572
   * Assert that a field exists in the current page with the given name and
value.
1573
   *
1574
   * @param $name
1575
   *   Name of field to assert.
1576
   * @param $value
1577
   *   Value of the field to assert.
1578
   * @param $message
1579
   *   Message to display.
1580
   * @param $group
1581
   *   The group this message belongs to.
1582
   * @return
1583
   *   TRUE on pass, FALSE on fail.
1584
   */
1585
  function assertFieldByName($name, $value = '', $message = '') {
15864
    return $this->assertFieldByXPath($this->_constructFieldXpath('name',
$name), $value, $message ? $message : t('Found field by name @name',
array('@name' => $name)), t('Browser'));
15870
  }
1588
1589
  /**
1590
   * Assert that a field does not exist with the given name and value.
1591
   *
1592
   * @param $name
1593
   *   Name of field to assert.
1594
   * @param $value
1595
   *   Value of the field to assert.
1596
   * @param $message
1597
   *   Message to display.
1598
   * @param $group
1599
   *   The group this message belongs to.
1600
   * @return
1601
   *   TRUE on pass, FALSE on fail.
1602
   */
1603
  function assertNoFieldByName($name, $value = '', $message = '') {
16046
    return $this->assertNoFieldByXPath($this->_constructFieldXpath('name',
$name), $value, $message ? $message : t('Did not find field by name @name',
array('@name' => $name)), t('Browser'));
16050
  }
1606
1607
  /**
1608
   * Assert that a field exists in the current page with the given id and
value.
1609
   *
1610
   * @param $id
1611
   *  Id of field to assert.
1612
   * @param $value
1613
   *   Value of the field to assert.
1614
   * @param $message
1615
   *   Message to display.
1616
   * @param $group
1617
   *   The group this message belongs to.
1618
   * @return
1619
   *   TRUE on pass, FALSE on fail.
1620
   */
1621
  function assertFieldById($id, $value = '', $message = '') {
16220
    return $this->assertFieldByXPath($this->_constructFieldXpath('id',
$id), $value, $message ? $message : t('Found field by id @id', array('@id'
=> $id)), t('Browser'));
16230
  }
1624
1625
  /**
1626
   * Assert that a field does not exist with the given id and value.
1627
   *
1628
   * @param $id
1629
   *  Id of field to assert.
1630
   * @param $value
1631
   *   Value of the field to assert.
1632
   * @param $message
1633
   *   Message to display.
1634
   * @param $group
1635
   *   The group this message belongs to.
1636
   * @return
1637
   *   TRUE on pass, FALSE on fail.
1638
   */
1639
  function assertNoFieldById($id, $value = '', $message = '') {
16400
    return $this->assertNoFieldByXPath($this->_constructFieldXpath('id',
$id), $value, $message ? $message : t('Did not find field by id @id',
array('@id' => $id)), t('Browser'));
16410
  }
1642
1643
  /**
1644
   * Assert that a field exists with the given name or id.
1645
   *
1646
   * @param $field
1647
   *  Name or id of field to assert.
1648
   * @param $message
1649
   *   Message to display.
1650
   * @param $group
1651
   *   The group this message belongs to.
1652
   * @return
1653
   *   TRUE on pass, FALSE on fail.
1654
   */
1655
  function assertField($field, $message = '', $group = 'Other') {
165633
    return $this->assertFieldByXPath($this->_constructFieldXpath('name',
$field) . '|' . $this->_constructFieldXpath('id', $field), '', $message,
$group);
16570
  }
1658
1659
  /**
1660
   * Assert that a field does not exist with the given name or id.
1661
   *
1662
   * @param $field
1663
   *  Name or id of field to assert.
1664
   * @param $message
1665
   *   Message to display.
1666
   * @param $group
1667
   *   The group this message belongs to.
1668
   * @return
1669
   *   TRUE on pass, FALSE on fail.
1670
   */
1671
  function assertNoField($field, $message = '', $group = 'Other') {
16721
    return $this->assertNoFieldByXPath($this->_constructFieldXpath('name',
$field) . '|' . $this->_constructFieldXpath('id', $field), '', $message,
$group);
16730
  }
1674
1675
  /**
1676
   * Construct an XPath for the given set of attributes and value.
1677
   *
1678
   * @param $attribute
1679
   *  Field attributes.
1680
   * @param $value
1681
   *  Value of field.
1682
   * @return
1683
   *  XPath for specified values.
1684
   */
1685
  function _constructFieldXpath($attribute, $value) {
168637
    return '//textarea[@' . $attribute . '="' . $value . '"]|//input[@' .
$attribute . '="' . $value . '"]|//select[@' . $attribute . '="' . $value .
'"]';
16870
  }
1688
1689
  /**
1690
   * Assert the page responds with the specified response code.
1691
   *
1692
   * @param $code
1693
   *   Reponse code. For example 200 is a successful page request. For a
list
1694
   *   of all codes see
http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html.
1695
   * @param $message
1696
   *   Message to display.
1697
   * @return
1698
   *   Assertion result.
1699
   */
1700
  function assertResponse($code, $message = '') {
170123
    $curl_code = curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
170223
    $match = is_array($code) ? in_array($curl_code, $code) : $curl_code ==
$code;
170323
    return $this->assertTrue($match, $message ? $message : t('HTTP response
expected !code, actual !curl_code', array('!code' => $code, '!curl_code' =>
$curl_code)), t('Browser'));
17040
  }
1705
}
170610