/[drupal]/drupal/modules/simpletest/drupal_web_test_case.php
ViewVC logotype

Contents of /drupal/modules/simpletest/drupal_web_test_case.php

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.168 - (show annotations) (download) (as text)
Mon Nov 2 03:12:05 2009 UTC (3 weeks, 2 days ago) by webchick
Branch: MAIN
CVS Tags: DRUPAL-7-0-UNSTABLE-10
Changes since 1.167: +2 -2 lines
File MIME type: text/x-php
#43462 by c960657 and andypost: Fixed issues with certain configurations of compressed pages.
1 <?php
2 // $Id: drupal_web_test_case.php,v 1.167 2009/11/02 01:49:02 webchick Exp $
3
4 /**
5 * Base class for Drupal tests.
6 *
7 * Do not extend this class, use one of the subclasses in this file.
8 */
9 abstract class DrupalTestCase {
10 /**
11 * The test run ID.
12 *
13 * @var string
14 */
15 protected $testId;
16
17 /**
18 * The original database prefix, before it was changed for testing purposes.
19 *
20 * @var string
21 */
22 protected $originalPrefix = NULL;
23
24 /**
25 * The original file directory, before it was changed for testing purposes.
26 *
27 * @var string
28 */
29 protected $originalFileDirectory = NULL;
30
31 /**
32 * Time limit for the test.
33 */
34 protected $timeLimit = 500;
35
36 /**
37 * Current results of this test case.
38 *
39 * @var Array
40 */
41 public $results = array(
42 '#pass' => 0,
43 '#fail' => 0,
44 '#exception' => 0,
45 '#debug' => 0,
46 );
47
48 /**
49 * Assertions thrown in that test case.
50 *
51 * @var Array
52 */
53 protected $assertions = array();
54
55 /**
56 * This class is skipped when looking for the source of an assertion.
57 *
58 * When displaying which function an assert comes from, it's not too useful
59 * to see "drupalWebTestCase->drupalLogin()', we would like to see the test
60 * that called it. So we need to skip the classes defining these helper
61 * methods.
62 */
63 protected $skipClasses = array(__CLASS__ => TRUE);
64
65 /**
66 * Constructor for DrupalWebTestCase.
67 *
68 * @param $test_id
69 * Tests with the same id are reported together.
70 */
71 public function __construct($test_id = NULL) {
72 $this->testId = $test_id;
73 }
74
75 /**
76 * Internal helper: stores the assert.
77 *
78 * @param $status
79 * Can be 'pass', 'fail', 'exception'.
80 * TRUE is a synonym for 'pass', FALSE for 'fail'.
81 * @param $message
82 * The message string.
83 * @param $group
84 * Which group this assert belongs to.
85 * @param $caller
86 * By default, the assert comes from a function whose name starts with
87 * 'test'. Instead, you can specify where this assert originates from
88 * by passing in an associative array as $caller. Key 'file' is
89 * the name of the source file, 'line' is the line number and 'function'
90 * is the caller function itself.
91 */
92 protected function assert($status, $message = '', $group = 'Other', array $caller = NULL) {
93 global $db_prefix;
94
95 // Convert boolean status to string status.
96 if (is_bool($status)) {
97 $status = $status ? 'pass' : 'fail';
98 }
99
100 // Increment summary result counter.
101 $this->results['#' . $status]++;
102
103 // Get the function information about the call to the assertion method.
104 if (!$caller) {
105 $caller = $this->getAssertionCall();
106 }
107
108 // Switch to non-testing database to store results in.
109 $current_db_prefix = $db_prefix;
110 $db_prefix = $this->originalPrefix;
111
112 // Creation assertion array that can be displayed while tests are running.
113 $this->assertions[] = $assertion = array(
114 'test_id' => $this->testId,
115 'test_class' => get_class($this),
116 'status' => $status,
117 'message' => $message,
118 'message_group' => $group,
119 'function' => $caller['function'],
120 'line' => $caller['line'],
121 'file' => $caller['file'],
122 );
123
124 // Store assertion for display after the test has completed.
125 db_insert('simpletest')
126 ->fields($assertion)
127 ->execute();
128
129 // Return to testing prefix.
130 $db_prefix = $current_db_prefix;
131 // We do not use a ternary operator here to allow a breakpoint on
132 // test failure.
133 if ($status == 'pass') {
134 return TRUE;
135 }
136 else {
137 return FALSE;
138 }
139 }
140
141 /**
142 * Store an assertion from outside the testing context.
143 *
144 * This is useful for inserting assertions that can only be recorded after
145 * the test case has been destroyed, such as PHP fatal errors. The caller
146 * information is not automatically gathered since the caller is most likely
147 * inserting the assertion on behalf of other code. In all other respects
148 * the method behaves just like DrupalTestCase::assert() in terms of storing
149 * the assertion.
150 *
151 * @see DrupalTestCase::assert()
152 */
153 public static function insertAssert($test_id, $test_class, $status, $message = '', $group = 'Other', array $caller = array()) {
154 // Convert boolean status to string status.
155 if (is_bool($status)) {
156 $status = $status ? 'pass' : 'fail';
157 }
158
159 $caller += array(
160 'function' => t('Unknown'),
161 'line' => 0,
162 'file' => t('Unknown'),
163 );
164
165 $assertion = array(
166 'test_id' => $test_id,
167 'test_class' => $test_class,
168 'status' => $status,
169 'message' => $message,
170 'message_group' => $group,
171 'function' => $caller['function'],
172 'line' => $caller['line'],
173 'file' => $caller['file'],
174 );
175
176 db_insert('simpletest')
177 ->fields($assertion)
178 ->execute();
179 }
180
181 /**
182 * Cycles through backtrace until the first non-assertion method is found.
183 *
184 * @return
185 * Array representing the true caller.
186 */
187 protected function getAssertionCall() {
188 $backtrace = debug_backtrace();
189
190 // The first element is the call. The second element is the caller.
191 // We skip calls that occurred in one of the methods of our base classes
192 // or in an assertion function.
193 while (($caller = $backtrace[1]) &&
194 ((isset($caller['class']) && isset($this->skipClasses[$caller['class']])) ||
195 substr($caller['function'], 0, 6) == 'assert')) {
196 // We remove that call.
197 array_shift($backtrace);
198 }
199
200 return _drupal_get_last_caller($backtrace);
201 }
202
203 /**
204 * Check to see if a value is not false (not an empty string, 0, NULL, or FALSE).
205 *
206 * @param $value
207 * The value on which the assertion is to be done.
208 * @param $message
209 * The message to display along with the assertion.
210 * @param $group
211 * The type of assertion - examples are "Browser", "PHP".
212 * @return
213 * TRUE if the assertion succeeded, FALSE otherwise.
214 */
215 protected function assertTrue($value, $message = '', $group = 'Other') {
216 return $this->assert((bool) $value, $message ? $message : t('Value @value is TRUE.', array('@value' => var_export($value, TRUE))), $group);
217 }
218
219 /**
220 * Check to see if a value is false (an empty string, 0, NULL, or FALSE).
221 *
222 * @param $value
223 * The value on which the assertion is to be done.
224 * @param $message
225 * The message to display along with the assertion.
226 * @param $group
227 * The type of assertion - examples are "Browser", "PHP".
228 * @return
229 * TRUE if the assertion succeeded, FALSE otherwise.
230 */
231 protected function assertFalse($value, $message = '', $group = 'Other') {
232 return $this->assert(!$value, $message ? $message : t('Value @value is FALSE.', array('@value' => var_export($value, TRUE))), $group);
233 }
234
235 /**
236 * Check to see if a value is NULL.
237 *
238 * @param $value
239 * The value on which the assertion is to be done.
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 * TRUE if the assertion succeeded, FALSE otherwise.
246 */
247 protected function assertNull($value, $message = '', $group = 'Other') {
248 return $this->assert(!isset($value), $message ? $message : t('Value @value is NULL.', array('@value' => var_export($value, TRUE))), $group);
249 }
250
251 /**
252 * Check to see if a value is not NULL.
253 *
254 * @param $value
255 * The value on which the assertion is to be done.
256 * @param $message
257 * The message to display along with the assertion.
258 * @param $group
259 * The type of assertion - examples are "Browser", "PHP".
260 * @return
261 * TRUE if the assertion succeeded, FALSE otherwise.
262 */
263 protected function assertNotNull($value, $message = '', $group = 'Other') {
264 return $this->assert(isset($value), $message ? $message : t('Value @value is not NULL.', array('@value' => var_export($value, TRUE))), $group);
265 }
266
267 /**
268 * Check to see if two values are equal.
269 *
270 * @param $first
271 * The first value to check.
272 * @param $second
273 * The second value to check.
274 * @param $message
275 * The message to display along with the assertion.
276 * @param $group
277 * The type of assertion - examples are "Browser", "PHP".
278 * @return
279 * TRUE if the assertion succeeded, FALSE otherwise.
280 */
281 protected function assertEqual($first, $second, $message = '', $group = 'Other') {
282 return $this->assert($first == $second, $message ? $message : t('Value @first is equal to value @second.', array('@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE))), $group);
283 }
284
285 /**
286 * Check to see if two values are not equal.
287 *
288 * @param $first
289 * The first value to check.
290 * @param $second
291 * The second value to check.
292 * @param $message
293 * The message to display along with the assertion.
294 * @param $group
295 * The type of assertion - examples are "Browser", "PHP".
296 * @return
297 * TRUE if the assertion succeeded, FALSE otherwise.
298 */
299 protected function assertNotEqual($first, $second, $message = '', $group = 'Other') {
300 return $this->assert($first != $second, $message ? $message : t('Value @first is not equal to value @second.', array('@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE))), $group);
301 }
302
303 /**
304 * Check to see if two values are identical.
305 *
306 * @param $first
307 * The first value to check.
308 * @param $second
309 * The second value to check.
310 * @param $message
311 * The message to display along with the assertion.
312 * @param $group
313 * The type of assertion - examples are "Browser", "PHP".
314 * @return
315 * TRUE if the assertion succeeded, FALSE otherwise.
316 */
317 protected function assertIdentical($first, $second, $message = '', $group = 'Other') {
318 return $this->assert($first === $second, $message ? $message : t('Value @first is identical to value @second.', array('@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE))), $group);
319 }
320
321 /**
322 * Check to see if two values are not identical.
323 *
324 * @param $first
325 * The first value to check.
326 * @param $second
327 * The second value to check.
328 * @param $message
329 * The message to display along with the assertion.
330 * @param $group
331 * The type of assertion - examples are "Browser", "PHP".
332 * @return
333 * TRUE if the assertion succeeded, FALSE otherwise.
334 */
335 protected function assertNotIdentical($first, $second, $message = '', $group = 'Other') {
336 return $this->assert($first !== $second, $message ? $message : t('Value @first is not identical to value @second.', array('@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE))), $group);
337 }
338
339 /**
340 * Fire an assertion that is always positive.
341 *
342 * @param $message
343 * The message to display along with the assertion.
344 * @param $group
345 * The type of assertion - examples are "Browser", "PHP".
346 * @return
347 * TRUE.
348 */
349 protected function pass($message = NULL, $group = 'Other') {
350 return $this->assert(TRUE, $message, $group);
351 }
352
353 /**
354 * Fire an assertion that is always negative.
355 *
356 * @param $message
357 * The message to display along with the assertion.
358 * @param $group
359 * The type of assertion - examples are "Browser", "PHP".
360 * @return
361 * FALSE.
362 */
363 protected function fail($message = NULL, $group = 'Other') {
364 return $this->assert(FALSE, $message, $group);
365 }
366
367 /**
368 * Fire an error assertion.
369 *
370 * @param $message
371 * The message to display along with the assertion.
372 * @param $group
373 * The type of assertion - examples are "Browser", "PHP".
374 * @param $caller
375 * The caller of the error.
376 * @return
377 * FALSE.
378 */
379 protected function error($message = '', $group = 'Other', array $caller = NULL) {
380 if ($group == 'User notice') {
381 // Since 'User notice' is set by trigger_error() which is used for debug
382 // set the message to a status of 'debug'.
383 return $this->assert('debug', $message, 'Debug', $caller);
384 }
385
386 return $this->assert('exception', $message, $group, $caller);
387 }
388
389 /**
390 * Run all tests in this class.
391 */
392 public function run() {
393 // Initialize verbose debugging.
394 simpletest_verbose(NULL, file_directory_path(), get_class($this));
395
396 // HTTP auth settings (<username>:<password>) for the simpletest browser
397 // when sending requests to the test site.
398 $username = variable_get('simpletest_username', NULL);
399 $password = variable_get('simpletest_password', NULL);
400 if ($username && $password) {
401 $this->httpauth_credentials = $username . ':' . $password;
402 }
403
404 set_error_handler(array($this, 'errorHandler'));
405 $methods = array();
406 // Iterate through all the methods in this class.
407 foreach (get_class_methods(get_class($this)) as $method) {
408 // If the current method starts with "test", run it - it's a test.
409 if (strtolower(substr($method, 0, 4)) == 'test') {
410 $this->setUp();
411 try {
412 $this->$method();
413 // Finish up.
414 }
415 catch (Exception $e) {
416 $this->exceptionHandler($e);
417 }
418 $this->tearDown();
419 }
420 }
421 // Clear out the error messages and restore error handler.
422 drupal_get_messages();
423 restore_error_handler();
424 }
425
426 /**
427 * Handle errors.
428 *
429 * Because this is registered in set_error_handler(), it has to be public.
430 * @see set_error_handler
431 *
432 */
433 public function errorHandler($severity, $message, $file = NULL, $line = NULL) {
434 if ($severity & error_reporting()) {
435 $error_map = array(
436 E_STRICT => 'Run-time notice',
437 E_WARNING => 'Warning',
438 E_NOTICE => 'Notice',
439 E_CORE_ERROR => 'Core error',
440 E_CORE_WARNING => 'Core warning',
441 E_USER_ERROR => 'User error',
442 E_USER_WARNING => 'User warning',
443 E_USER_NOTICE => 'User notice',
444 E_RECOVERABLE_ERROR => 'Recoverable error',
445 );
446
447 $backtrace = debug_backtrace();
448 $this->error($message, $error_map[$severity], _drupal_get_last_caller($backtrace));
449 }
450 return TRUE;
451 }
452
453 /**
454 * Handle exceptions.
455 *
456 * @see set_exception_handler
457 */
458 protected function exceptionHandler($exception) {
459 $backtrace = $exception->getTrace();
460 // Push on top of the backtrace the call that generated the exception.
461 array_unshift($backtrace, array(
462 'line' => $exception->getLine(),
463 'file' => $exception->getFile(),
464 ));
465 $this->error($exception->getMessage(), 'Uncaught exception', _drupal_get_last_caller($backtrace));
466 }
467
468 /**
469 * Generates a random string of ASCII characters of codes 32 to 126.
470 *
471 * The generated string includes alpha-numeric characters and common misc
472 * characters. Use this method when testing general input where the content
473 * is not restricted.
474 *
475 * @param $length
476 * Length of random string to generate which will be appended to $db_prefix.
477 * @return
478 * Randomly generated string.
479 */
480 public static function randomString($length = 8) {
481 global $db_prefix;
482
483 $str = '';
484 for ($i = 0; $i < $length; $i++) {
485 $str .= chr(mt_rand(32, 126));
486 }
487 return str_replace('simpletest', 's', $db_prefix) . $str;
488 }
489
490 /**
491 * Generates a random string containing letters and numbers.
492 *
493 * The letters may be upper or lower case. This method is better for
494 * restricted inputs that do not accept certain characters. For example,
495 * when testing input fields that require machine readable values (ie without
496 * spaces and non-standard characters) this method is best.
497 *
498 * @param $length
499 * Length of random string to generate which will be appended to $db_prefix.
500 * @return
501 * Randomly generated string.
502 */
503 public static function randomName($length = 8) {
504 global $db_prefix;
505
506 $values = array_merge(range(65, 90), range(97, 122), range(48, 57));
507 $max = count($values) - 1;
508 $str = '';
509 for ($i = 0; $i < $length; $i++) {
510 $str .= chr($values[mt_rand(0, $max)]);
511 }
512 return str_replace('simpletest', 's', $db_prefix) . $str;
513 }
514
515 }
516
517 /**
518 * Test case for Drupal unit tests.
519 *
520 * These tests can not access the database nor files. Calling any Drupal
521 * function that needs the database will throw exceptions. These include
522 * watchdog(), function_exists(), module_implements(),
523 * module_invoke_all() etc.
524 */
525 class DrupalUnitTestCase extends DrupalTestCase {
526
527 /**
528 * Constructor for DrupalUnitTestCase.
529 */
530 function __construct($test_id = NULL) {
531 parent::__construct($test_id);
532 $this->skipClasses[__CLASS__] = TRUE;
533 }
534
535 function setUp() {
536 global $db_prefix, $conf;
537
538 // Store necessary current values before switching to prefixed database.
539 $this->originalPrefix = $db_prefix;
540 $this->originalFileDirectory = file_directory_path();
541
542 // Generate temporary prefixed database to ensure that tests have a clean starting point.
543 $db_prefix = Database::getConnection()->prefixTables('{simpletest' . mt_rand(1000, 1000000) . '}');
544 $conf['file_public_path'] = $this->originalFileDirectory . '/' . $db_prefix;
545
546 // If locale is enabled then t() will try to access the database and
547 // subsequently will fail as the database is not accessible.
548 $module_list = module_list();
549 if (isset($module_list['locale'])) {
550 $this->originalModuleList = $module_list;
551 unset($module_list['locale']);
552 module_list(TRUE, FALSE, FALSE, $module_list);
553 }
554 }
555
556 function tearDown() {
557 global $db_prefix, $conf;
558 if (preg_match('/simpletest\d+/', $db_prefix)) {
559 $conf['file_public_path'] = $this->originalFileDirectory;
560 // Return the database prefix to the original.
561 $db_prefix = $this->originalPrefix;
562 // Restore modules if necessary.
563 if (isset($this->originalModuleList)) {
564 module_list(TRUE, FALSE, FALSE, $this->originalModuleList);
565 }
566 }
567 }
568 }
569
570 /**
571 * Test case for typical Drupal tests.
572 */
573 class DrupalWebTestCase extends DrupalTestCase {
574 /**
575 * The URL currently loaded in the internal browser.
576 *
577 * @var string
578 */
579 protected $url;
580
581 /**
582 * The handle of the current cURL connection.
583 *
584 * @var resource
585 */
586 protected $curlHandle;
587
588 /**
589 * The headers of the page currently loaded in the internal browser.
590 *
591 * @var Array
592 */
593 protected $headers;
594
595 /**
596 * The content of the page currently loaded in the internal browser.
597 *
598 * @var string
599 */
600 protected $content;
601
602 /**
603 * The content of the page currently loaded in the internal browser (plain text version).
604 *
605 * @var string
606 */
607 protected $plainTextContent;
608
609 /**
610 * The parsed version of the page.
611 *
612 * @var SimpleXMLElement
613 */
614 protected $elements = NULL;
615
616 /**
617 * The current user logged in using the internal browser.
618 *
619 * @var bool
620 */
621 protected $loggedInUser = FALSE;
622
623 /**
624 * The current cookie file used by cURL.
625 *
626 * We do not reuse the cookies in further runs, so we do not need a file
627 * but we still need cookie handling, so we set the jar to NULL.
628 */
629 protected $cookieFile = NULL;
630
631 /**
632 * Additional cURL options.
633 *
634 * DrupalWebTestCase itself never sets this but always obeys what is set.
635 */
636 protected $additionalCurlOptions = array();
637
638 /**
639 * The original user, before it was changed to a clean uid = 1 for testing purposes.
640 *
641 * @var object
642 */
643 protected $originalUser = NULL;
644
645 /**
646 * HTTP authentication credentials (<username>:<password>).
647 */
648 protected $httpauth_credentials = NULL;
649
650 /**
651 * The current session name, if available.
652 */
653 protected $session_name = NULL;
654
655 /**
656 * The current session ID, if available.
657 */
658 protected $session_id = NULL;
659
660 /**
661 * Constructor for DrupalWebTestCase.
662 */
663 function __construct($test_id = NULL) {
664 parent::__construct($test_id);
665 $this->skipClasses[__CLASS__] = TRUE;
666 }
667
668 /**
669 * Get a node from the database based on its title.
670 *
671 * @param title
672 * A node title, usually generated by $this->randomName().
673 *
674 * @return
675 * A node object matching $title.
676 */
677 function drupalGetNodeByTitle($title) {
678 $nodes = node_load_multiple(array(), array('title' => $title));
679 // Load the first node returned from the database.
680 $returned_node = reset($nodes);
681 return $returned_node;
682 }
683
684 /**
685 * Creates a node based on default settings.
686 *
687 * @param $settings
688 * An associative array of settings to change from the defaults, keys are
689 * node properties, for example 'title' => 'Hello, world!'.
690 * @return
691 * Created node object.
692 */
693 protected function drupalCreateNode($settings = array()) {
694 // Populate defaults array.
695 $settings += array(
696 'body' => array(FIELD_LANGUAGE_NONE => array(array())),
697 'title' => array(FIELD_LANGUAGE_NONE => array(array('value' => $this->randomName(8)))),
698 'comment' => 2,
699 'changed' => REQUEST_TIME,
700 'moderate' => 0,
701 'promote' => 0,
702 'revision' => 1,
703 'log' => '',
704 'status' => 1,
705 'sticky' => 0,
706 'type' => 'page',
707 'revisions' => NULL,
708 'taxonomy' => NULL,
709 );
710
711 // Use the original node's created time for existing nodes.
712 if (isset($settings['created']) && !isset($settings['date'])) {
713 $settings['date'] = format_date($settings['created'], 'custom', 'Y-m-d H:i:s O');
714 }
715
716 // If the node's user uid is not specified manually, use the currently
717 // logged in user if available, or else the user running the test.
718 if (!isset($settings['uid'])) {
719 if ($this->loggedInUser) {
720 $settings['uid'] = $this->loggedInUser->uid;
721 }
722 else {
723 global $user;
724 $settings['uid'] = $user->uid;
725 }
726 }
727
728 // Merge body field value and format separately.
729 $body = array(
730 'value' => $this->randomName(32),
731 'format' => filter_default_format(),
732 );
733 $langcode = !empty($settings['language']) ? $settings['language'] : FIELD_LANGUAGE_NONE;
734 $settings['body'][$langcode][0] += $body;
735
736 $node = (object) $settings;
737 node_save($node);
738
739 // Small hack to link revisions to our test user.
740 db_update('node_revision')
741 ->fields(array('uid' => $node->uid))
742 ->condition('vid', $node->vid)
743 ->execute();
744 return $node;
745 }
746
747 /**
748 * Creates a custom content type based on default settings.
749 *
750 * @param $settings
751 * An array of settings to change from the defaults.
752 * Example: 'type' => 'foo'.
753 * @return
754 * Created content type.
755 */
756 protected function drupalCreateContentType($settings = array()) {
757 // Find a non-existent random type name.
758 do {
759 $name = strtolower($this->randomName(8));
760 } while (node_type_get_type($name));
761
762 // Populate defaults array.
763 $defaults = array(
764 'type' => $name,
765 'name' => $name,
766 'base' => 'node_content',
767 'description' => '',
768 'help' => '',
769 'title_label' => 'Title',
770 'body_label' => 'Body',
771 'has_title' => 1,
772 'has_body' => 1,
773 );
774 // Imposed values for a custom type.
775 $forced = array(
776 'orig_type' => '',
777 'old_type' => '',
778 'module' => 'node',
779 'custom' => 1,
780 'modified' => 1,
781 'locked' => 0,
782 );
783 $type = $forced + $settings + $defaults;
784 $type = (object)$type;
785
786 $saved_type = node_type_save($type);
787 node_types_rebuild();
788 menu_rebuild();
789
790 $this->assertEqual($saved_type, SAVED_NEW, t('Created content type %type.', array('%type' => $type->type)));
791
792 // Reset permissions so that permissions for this content type are available.
793 $this->checkPermissions(array(), TRUE);
794
795 return $type;
796 }
797
798 /**
799 * Get a list files that can be used in tests.
800 *
801 * @param $type
802 * File type, possible values: 'binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'.
803 * @param $size
804 * File size in bytes to match. Please check the tests/files folder.
805 * @return
806 * List of files that match filter.
807 */
808 protected function drupalGetTestFiles($type, $size = NULL) {
809 $files = array();
810
811 // Make sure type is valid.
812 if (in_array($type, array('binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'))) {
813 // Use original file directory instead of one created during setUp().
814 $path = $this->originalFileDirectory . '/simpletest';
815 $files = file_scan_directory($path, '/' . $type . '\-.*/');
816
817 // If size is set then remove any files that are not of that size.
818 if ($size !== NULL) {
819 foreach ($files as $file) {
820 $stats = stat($file->uri);
821 if ($stats['size'] != $size) {
822 unset($files[$file->uri]);
823 }
824 }
825 }
826 }
827 usort($files, array($this, 'drupalCompareFiles'));
828 return $files;
829 }
830
831 /**
832 * Compare two files based on size and file name.
833 */
834 protected function drupalCompareFiles($file1, $file2) {
835 $compare_size = filesize($file1->uri) - filesize($file2->uri);
836 if ($compare_size) {
837 // Sort by file size.
838 return $compare_size;
839 }
840 else {
841 // The files were the same size, so sort alphabetically.
842 return strnatcmp($file1->name, $file2->name);
843 }
844 }
845
846 /**
847 * Create a user with a given set of permissions. The permissions correspond to the
848 * names given on the privileges page.
849 *
850 * @param $permissions
851 * Array of permission names to assign to user.
852 * @return
853 * A fully loaded user object with pass_raw property, or FALSE if account
854 * creation fails.
855 */
856 protected function drupalCreateUser($permissions = array('access comments', 'access content', 'post comments', 'post comments without approval')) {
857 // Create a role with the given permission set.
858 if (!($rid = $this->drupalCreateRole($permissions))) {
859 return FALSE;
860 }
861
862 // Create a user assigned to that role.
863 $edit = array();
864 $edit['name'] = $this->randomName();
865 $edit['mail'] = $edit['name'] . '@example.com';
866 $edit['roles'] = array($rid => $rid);
867 $edit['pass'] = user_password();
868 $edit['status'] = 1;
869
870 $account = user_save(drupal_anonymous_user(), $edit);
871
872 $this->assertTrue(!empty($account->uid), t('User created with name %name and pass %pass', array('%name' => $edit['name'], '%pass' => $edit['pass'])), t('User login'));
873 if (empty($account->uid)) {
874 return FALSE;
875 }
876
877 // Add the raw password so that we can log in as this user.
878 $account->pass_raw = $edit['pass'];
879 return $account;
880 }
881
882 /**
883 * Internal helper function; Create a role with specified permissions.
884 *
885 * @param $permissions
886 * Array of permission names to assign to role.
887 * @param $name
888 * (optional) String for the name of the role. Defaults to a random string.
889 * @return
890 * Role ID of newly created role, or FALSE if role creation failed.
891 */
892 protected function drupalCreateRole(array $permissions, $name = NULL) {
893 // Generate random name if it was not passed.
894 if (!$name) {
895 $name = $this->randomName();
896 }
897
898 // Check the all the permissions strings are valid.
899 if (!$this->checkPermissions($permissions)) {
900 return FALSE;
901 }
902
903 // Create new role.
904 $role = new stdClass();
905 $role->name = $name;
906 user_role_save($role);
907 user_role_grant_permissions($role->rid, $permissions);
908
909 $this->assertTrue(isset($role->rid), t('Created role of name: @name, id: @rid', array('@name' => $name, '@rid' => (isset($role->rid) ? $role->rid : t('-n/a-')))), t('Role'));
910 if ($role && !empty($role->rid)) {
911 $count = db_query('SELECT COUNT(*) FROM {role_permission} WHERE rid = :rid', array(':rid' => $role->rid))->fetchField();
912 $this->assertTrue($count == count($permissions), t('Created permissions: @perms', array('@perms' => implode(', ', $permissions))), t('Role'));
913 return $role->rid;
914 }
915 else {
916 return FALSE;
917 }
918 }
919
920 /**
921 * Check to make sure that the array of permissions are valid.
922 *
923 * @param $permissions
924 * Permissions to check.
925 * @param $reset
926 * Reset cached available permissions.
927 * @return
928 * TRUE or FALSE depending on whether the permissions are valid.
929 */
930 protected function checkPermissions(array $permissions, $reset = FALSE) {
931 $available = &drupal_static(__FUNCTION__);
932
933 if (!isset($available) || $reset) {
934 $available = array_keys(module_invoke_all('permission'));
935 }
936
937 $valid = TRUE;
938 foreach ($permissions as $permission) {
939 if (!in_array($permission, $available)) {
940 $this->fail(t('Invalid permission %permission.', array('%permission' => $permission)), t('Role'));
941 $valid = FALSE;
942 }
943 }
944 return $valid;
945 }
946
947 /**
948 * Log in a user with the internal browser.
949 *
950 * If a user is already logged in, then the current user is logged out before
951 * logging in the specified user.
952 *
953 * Please note that neither the global $user nor the passed in user object is
954 * populated with data of the logged in user. If you need full access to the
955 * user object after logging in, it must be updated manually. If you also need
956 * access to the plain-text password of the user (set by drupalCreateUser()),
957 * e.g. to login the same user again, then it must be re-assigned manually.
958 * For example:
959 * @code
960 * // Create a user.
961 * $account = $this->drupalCreateUser(array());
962 * $this->drupalLogin($account);
963 * // Load real user object.
964 * $pass_raw = $account->pass_raw;
965 * $account = user_load($account->uid);
966 * $account->pass_raw = $pass_raw;
967 * @endcode
968 *
969 * @param $user
970 * User object representing the user to login.
971 *
972 * @see drupalCreateUser()
973 */
974 protected function drupalLogin(stdClass $user) {
975 if ($this->loggedInUser) {
976 $this->drupalLogout();
977 }
978
979 $edit = array(
980 'name' => $user->name,
981 'pass' => $user->pass_raw
982 );
983 $this->drupalPost('user', $edit, t('Log in'));
984
985 // If a "log out" link appears on the page, it is almost certainly because
986 // the login was successful.
987 $pass = $this->assertLink(t('Log out'), 0, t('User %name successfully logged in.', array('%name' => $user->name)), t('User login'));
988
989 if ($pass) {
990 $this->loggedInUser = $user;
991 }
992 }
993
994 /**
995 * Generate a token for the currently logged in user.
996 */
997 protected function drupalGetToken($value = '') {
998 $private_key = drupal_get_private_key();
999 return md5($this->session_id . $value . $private_key);
1000 }
1001
1002 /*
1003 * Logs a user out of the internal browser, then check the login page to confirm logout.
1004 */
1005 protected function drupalLogout() {
1006 // Make a request to the logout page, and redirect to the user page, the
1007 // idea being if you were properly logged out you should be seeing a login
1008 // screen.
1009 $this->drupalGet('user/logout', array('query' => array('destination' => 'user')));
1010 $pass = $this->assertField('name', t('Username field found.'), t('Logout'));
1011 $pass = $pass && $this->assertField('pass', t('Password field found.'), t('Logout'));
1012
1013 if ($pass) {
1014 $this->loggedInUser = FALSE;
1015 }
1016 }
1017
1018 /**
1019 * Generates a random database prefix, runs the install scripts on the
1020 * prefixed database and enable the specified modules. After installation
1021 * many caches are flushed and the internal browser is setup so that the
1022 * page requests will run on the new prefix. A temporary files directory
1023 * is created with the same name as the database prefix.
1024 *
1025 * @param ...
1026 * List of modules to enable for the duration of the test.
1027 */
1028 protected function setUp() {
1029 global $db_prefix, $user, $language;
1030
1031 // Store necessary current values before switching to prefixed database.
1032 $this->originalLanguage = $language;
1033 $this->originalLanguageDefault = variable_get('language_default');
1034 $this->originalPrefix = $db_prefix;
1035 $this->originalFileDirectory = file_directory_path();
1036 $this->originalProfile = drupal_get_profile();
1037 $clean_url_original = variable_get('clean_url', 0);
1038
1039 // Generate temporary prefixed database to ensure that tests have a clean starting point.
1040 $db_prefix_new = Database::getConnection()->prefixTables('{simpletest' . mt_rand(1000, 1000000) . '}');
1041 db_update('simpletest_test_id')
1042 ->fields(array('last_prefix' => $db_prefix_new))
1043 ->condition('test_id', $this->testId)
1044 ->execute();
1045 $db_prefix = $db_prefix_new;
1046
1047 // Create test directory ahead of installation so fatal errors and debug
1048 // information can be logged during installation process.
1049 $directory = $this->originalFileDirectory . '/simpletest/' . substr($db_prefix, 10);
1050 file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
1051
1052 // Log fatal errors.
1053 ini_set('log_errors', 1);
1054 ini_set('error_log', $directory . '/error.log');
1055
1056 include_once DRUPAL_ROOT . '/includes/install.inc';
1057 drupal_install_system();
1058
1059 $this->preloadRegistry();
1060
1061 // Include the default profile
1062 variable_set('install_profile', 'default');
1063 $profile_details = install_profile_info('default', 'en');
1064
1065 // Add the specified modules to the list of modules in the default profile.
1066 // Install the modules specified by the default profile.
1067 drupal_install_modules($profile_details['dependencies'], TRUE);
1068
1069 drupal_static_reset('_node_types_build');
1070
1071 // Install additional modules one at a time in order to make sure that the
1072 // list of modules is updated between each module's installation.
1073 $modules = func_get_args();
1074 foreach ($modules as $module) {
1075 drupal_install_modules(array($module), TRUE);
1076 }
1077
1078 // Because the schema is static cached, we need to flush
1079 // it between each run. If we don't, then it will contain
1080 // stale data for the previous run's database prefix and all
1081 // calls to it will fail.
1082 drupal_get_schema(NULL, TRUE);
1083
1084 // Run default profile tasks.
1085 $install_state = array();
1086 drupal_install_modules(array('default'), TRUE);
1087
1088 // Rebuild caches.
1089 node_types_rebuild();
1090 actions_synchronize();
1091 _drupal_flush_css_js();
1092 $this->refreshVariables();
1093 $this->checkPermissions(array(), TRUE);
1094
1095 // Log in with a clean $user.
1096 $this->originalUser = $user;
1097 drupal_save_session(FALSE);
1098 $user = user_load(1);
1099
1100 // Restore necessary variables.
1101 variable_set('install_profile', 'default');
1102 variable_set('install_task', 'done');
1103 variable_set('clean_url', $clean_url_original);
1104 variable_set('site_mail', 'simpletest@example.com');
1105 // Set up English language.
1106 unset($GLOBALS['conf']['language_default']);
1107 $language = language_default();
1108
1109 // Use the test mail class instead of the default mail handler class.
1110 variable_set('mail_system', array('default-system' => 'TestingMailSystem'));
1111
1112 // Use temporary files directory with the same prefix as the database.
1113 $public_files_directory = $this->originalFileDirectory . '/' . $db_prefix;
1114 $private_files_directory = $public_files_directory . '/private';
1115
1116 // Set path variables
1117 variable_set('file_public_path', $public_files_directory);
1118 variable_set('file_private_path', $private_files_directory);
1119
1120 // Create the directories
1121 $directory = file_directory_path('public');
1122 file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
1123 file_prepare_directory($private_files_directory, FILE_CREATE_DIRECTORY);
1124
1125 drupal_set_time_limit($this->timeLimit);
1126 }
1127
1128 /**
1129 * This method is called by DrupalWebTestCase::setUp, and preloads the
1130 * registry from the testing site to cut down on the time it takes to
1131 * setup a clean environment for the current test run.
1132 */
1133 protected function preloadRegistry() {
1134 db_query('INSERT INTO {registry} SELECT * FROM ' . $this->originalPrefix . 'registry');
1135 db_query('INSERT INTO {registry_file} SELECT * FROM ' . $this->originalPrefix . 'registry_file');
1136 }
1137
1138 /**
1139 * Refresh the in-memory set of variables. Useful after a page request is made
1140 * that changes a variable in a different thread.
1141 *
1142 * In other words calling a settings page with $this->drupalPost() with a changed
1143 * value would update a variable to reflect that change, but in the thread that
1144 * made the call (thread running the test) the changed variable would not be
1145 * picked up.
1146 *
1147 * This method clears the variables cache and loads a fresh copy from the database
1148 * to ensure that the most up-to-date set of variables is loaded.
1149 */
1150 protected function refreshVariables() {
1151 global $conf;
1152 cache_clear_all('variables', 'cache');
1153 $conf = variable_initialize();
1154 }
1155
1156 /**
1157 * Delete created files and temporary files directory, delete the tables created by setUp(),
1158 * and reset the database prefix.
1159 */
1160 protected function tearDown() {
1161 global $db_prefix, $user, $language;
1162
1163 // In case a fatal error occured that was not in the test process read the
1164 // log to pick up any fatal errors.
1165 $db_prefix_temp = $db_prefix;
1166 $db_prefix = $this->originalPrefix;
1167 simpletest_log_read($this->testId, $db_prefix, get_class($this), TRUE);
1168 $db_prefix = $db_prefix_temp;
1169
1170 $emailCount = count(variable_get('drupal_test_email_collector', array()));
1171 if ($emailCount) {
1172 $message = format_plural($emailCount, t('!count e-mail was sent during this test.'), t('!count e-mails were sent during this test.'), array('!count' => $emailCount));
1173 $this->pass($message, t('E-mail'));
1174 }
1175
1176 if (preg_match('/simpletest\d+/', $db_prefix)) {
1177 // Delete temporary files directory.
1178 file_unmanaged_delete_recursive($this->originalFileDirectory . '/' . $db_prefix);
1179
1180 // Remove all prefixed tables (all the tables in the schema).
1181 $schema = drupal_get_schema(NULL, TRUE);
1182 $ret = array();
1183 foreach ($schema as $name => $table) {
1184 db_drop_table($name);
1185 }
1186
1187 // Return the database prefix to the original.
1188 $db_prefix = $this->originalPrefix;
1189
1190 // Return the user to the original one.
1191 $user = $this->originalUser;
1192 drupal_save_session(TRUE);
1193
1194 // Ensure that internal logged in variable and cURL options are reset.
1195 $this->loggedInUser = FALSE;
1196 $this->additionalCurlOptions = array();
1197
1198 // Reload module list and implementations to ensure that test module hooks
1199 // aren't called after tests.
1200 module_list(TRUE);
1201 module_implements('', FALSE, TRUE);
1202
1203 // Reset the Field API.
1204 field_cache_clear();
1205
1206 // Rebuild caches.
1207 $this->refreshVariables();
1208
1209 // Reset language.
1210 $language = $this->originalLanguage;
1211 if ($this->originalLanguageDefault) {
1212 $GLOBALS['conf']['language_default'] = $this->originalLanguageDefault;
1213 }
1214
1215 // Close the CURL handler.
1216 $this->curlClose();
1217 }
1218 }
1219
1220 /**
1221 * Initializes the cURL connection.
1222 *
1223 * If the simpletest_httpauth_credentials variable is set, this function will
1224 * add HTTP authentication headers. This is necessary for testing sites that
1225 * are protected by login credentials from public access.
1226 * See the description of $curl_options for other options.
1227 */
1228 protected function curlInitialize() {
1229 global $base_url, $db_prefix;
1230
1231 if (!isset($this->curlHandle)) {
1232 $this->curlHandle = curl_init();
1233 $curl_options = $this->additionalCurlOptions + array(
1234 CURLOPT_COOKIEJAR => $this->cookieFile,
1235 CURLOPT_URL => $base_url,
1236 CURLOPT_FOLLOWLOCATION => TRUE,
1237 CURLOPT_MAXREDIRS => 5,
1238 CURLOPT_RETURNTRANSFER => TRUE,
1239 CURLOPT_SSL_VERIFYPEER => FALSE, // Required to make the tests run on https.
1240 CURLOPT_SSL_VERIFYHOST => FALSE, // Required to make the tests run on https.
1241 CURLOPT_HEADERFUNCTION => array(&$this, 'curlHeaderCallback'),
1242 );
1243 if (isset($this->httpauth_credentials)) {
1244 $curl_options[CURLOPT_USERPWD] = $this->httpauth_credentials;
1245 }
1246 curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options);
1247
1248 // By default, the child session name should be the same as the parent.
1249 $this->session_name = session_name();
1250 }
1251 // We set the user agent header on each request so as to use the current
1252 // time and a new uniqid.
1253 if (preg_match('/simpletest\d+/', $db_prefix, $matches)) {
1254 curl_setopt($this->curlHandle, CURLOPT_USERAGENT, drupal_generate_test_ua($matches[0]));
1255 }
1256 }
1257
1258 /**
1259 * Performs a cURL exec with the specified options after calling curlConnect().
1260 *
1261 * @param $curl_options
1262 * Custom cURL options.
1263 * @return
1264 * Content returned from the exec.
1265 */
1266 protected function curlExec($curl_options) {
1267 $this->curlInitialize();
1268 $url = empty($curl_options[CURLOPT_URL]) ? curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL) : $curl_options[CURLOPT_URL];
1269 if (!empty($curl_options[CURLOPT_POST])) {
1270 // This is a fix for the Curl library to prevent Expect: 100-continue
1271 // headers in POST requests, that may cause unexpected HTTP response
1272 // codes from some webservers (like lighttpd that returns a 417 error
1273 // code). It is done by setting an empty "Expect" header field that is
1274 // not overwritten by Curl.
1275 $curl_options[CURLOPT_HTTPHEADER][] = 'Expect:';
1276 }
1277 curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options);
1278
1279 // Reset headers and the session ID.
1280 $this->session_id = NULL;
1281 $this->headers = array();
1282
1283 $this->drupalSetContent(curl_exec($this->curlHandle), curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL));
1284 $message_vars = array(
1285 '!method' => !empty($curl_options[CURLOPT_NOBODY]) ? 'HEAD' : (empty($curl_options[CURLOPT_POSTFIELDS]) ? 'GET' : 'POST'),
1286 '@url' => $url,
1287 '@status' => curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE),
1288 '!length' => format_size(strlen($this->content))
1289 );
1290 $message = t('!method @url returned @status (!length).', $message_vars);
1291 $this->assertTrue($this->content !== FALSE, $message, t('Browser'));
1292 return $this->drupalGetContent();
1293 }
1294
1295 /**
1296 * Reads headers and registers errors received from the tested site.
1297 *
1298 * @see _drupal_log_error().
1299 *
1300 * @param $curlHandler
1301 * The cURL handler.
1302 * @param $header
1303 * An header.
1304 */
1305 protected function curlHeaderCallback($curlHandler, $header) {
1306 $this->headers[] = $header;
1307
1308 // Errors are being sent via X-Drupal-Assertion-* headers,
1309 // generated by _drupal_log_error() in the exact form required
1310 // by DrupalWebTestCase::error().
1311 if (preg_match('/^X-Drupal-Assertion-[0-9]+: (.*)$/', $header, $matches)) {
1312 // Call DrupalWebTestCase::error() with the parameters from the header.
1313 call_user_