root / drupal7 / modules / simpletest / drupal_web_test_case.php @ a5eb12a2
1 | 85ad3d82 | Assos Assos | <?php
|
---|---|---|---|
2 | |||
3 | /**
|
||
4 | * Global variable that holds information about the tests being run.
|
||
5 | *
|
||
6 | * An array, with the following keys:
|
||
7 | * - 'test_run_id': the ID of the test being run, in the form 'simpletest_%"
|
||
8 | * - 'in_child_site': TRUE if the current request is a cURL request from
|
||
9 | * the parent site.
|
||
10 | *
|
||
11 | * @var array
|
||
12 | */
|
||
13 | global $drupal_test_info; |
||
14 | |||
15 | /**
|
||
16 | * Base class for Drupal tests.
|
||
17 | *
|
||
18 | * Do not extend this class, use one of the subclasses in this file.
|
||
19 | */
|
||
20 | abstract class DrupalTestCase { |
||
21 | /**
|
||
22 | * The test run ID.
|
||
23 | *
|
||
24 | * @var string
|
||
25 | */
|
||
26 | protected $testId; |
||
27 | |||
28 | /**
|
||
29 | * The database prefix of this test run.
|
||
30 | *
|
||
31 | * @var string
|
||
32 | */
|
||
33 | protected $databasePrefix = NULL; |
||
34 | |||
35 | /**
|
||
36 | * The original file directory, before it was changed for testing purposes.
|
||
37 | *
|
||
38 | * @var string
|
||
39 | */
|
||
40 | protected $originalFileDirectory = NULL; |
||
41 | |||
42 | /**
|
||
43 | * Time limit for the test.
|
||
44 | */
|
||
45 | protected $timeLimit = 500; |
||
46 | |||
47 | /**
|
||
48 | * Current results of this test case.
|
||
49 | *
|
||
50 | * @var Array
|
||
51 | */
|
||
52 | public $results = array( |
||
53 | '#pass' => 0, |
||
54 | '#fail' => 0, |
||
55 | '#exception' => 0, |
||
56 | '#debug' => 0, |
||
57 | ); |
||
58 | |||
59 | /**
|
||
60 | * Assertions thrown in that test case.
|
||
61 | *
|
||
62 | * @var Array
|
||
63 | */
|
||
64 | protected $assertions = array(); |
||
65 | |||
66 | /**
|
||
67 | * This class is skipped when looking for the source of an assertion.
|
||
68 | *
|
||
69 | * When displaying which function an assert comes from, it's not too useful
|
||
70 | * to see "drupalWebTestCase->drupalLogin()', we would like to see the test
|
||
71 | * that called it. So we need to skip the classes defining these helper
|
||
72 | * methods.
|
||
73 | */
|
||
74 | protected $skipClasses = array(__CLASS__ => TRUE); |
||
75 | |||
76 | /**
|
||
77 | * Flag to indicate whether the test has been set up.
|
||
78 | *
|
||
79 | * The setUp() method isolates the test from the parent Drupal site by
|
||
80 | * creating a random prefix for the database and setting up a clean file
|
||
81 | * storage directory. The tearDown() method then cleans up this test
|
||
82 | * environment. We must ensure that setUp() has been run. Otherwise,
|
||
83 | * tearDown() will act on the parent Drupal site rather than the test
|
||
84 | * environment, destroying live data.
|
||
85 | */
|
||
86 | protected $setup = FALSE; |
||
87 | |||
88 | protected $setupDatabasePrefix = FALSE; |
||
89 | |||
90 | protected $setupEnvironment = FALSE; |
||
91 | |||
92 | /**
|
||
93 | * Constructor for DrupalTestCase.
|
||
94 | *
|
||
95 | * @param $test_id
|
||
96 | * Tests with the same id are reported together.
|
||
97 | */
|
||
98 | public function __construct($test_id = NULL) { |
||
99 | $this->testId = $test_id; |
||
100 | } |
||
101 | |||
102 | /**
|
||
103 | * Internal helper: stores the assert.
|
||
104 | *
|
||
105 | * @param $status
|
||
106 | * Can be 'pass', 'fail', 'exception'.
|
||
107 | * TRUE is a synonym for 'pass', FALSE for 'fail'.
|
||
108 | * @param $message
|
||
109 | * The message string.
|
||
110 | * @param $group
|
||
111 | * Which group this assert belongs to.
|
||
112 | * @param $caller
|
||
113 | * By default, the assert comes from a function whose name starts with
|
||
114 | * 'test'. Instead, you can specify where this assert originates from
|
||
115 | * by passing in an associative array as $caller. Key 'file' is
|
||
116 | * the name of the source file, 'line' is the line number and 'function'
|
||
117 | * is the caller function itself.
|
||
118 | */
|
||
119 | protected function assert($status, $message = '', $group = 'Other', array $caller = NULL) { |
||
120 | // Convert boolean status to string status.
|
||
121 | if (is_bool($status)) { |
||
122 | $status = $status ? 'pass' : 'fail'; |
||
123 | } |
||
124 | |||
125 | // Increment summary result counter.
|
||
126 | $this->results['#' . $status]++; |
||
127 | |||
128 | // Get the function information about the call to the assertion method.
|
||
129 | if (!$caller) { |
||
130 | $caller = $this->getAssertionCall(); |
||
131 | } |
||
132 | |||
133 | // Creation assertion array that can be displayed while tests are running.
|
||
134 | $this->assertions[] = $assertion = array( |
||
135 | 'test_id' => $this->testId, |
||
136 | 'test_class' => get_class($this), |
||
137 | 'status' => $status, |
||
138 | 'message' => $message, |
||
139 | 'message_group' => $group, |
||
140 | 'function' => $caller['function'], |
||
141 | 'line' => $caller['line'], |
||
142 | 'file' => $caller['file'], |
||
143 | ); |
||
144 | |||
145 | // Store assertion for display after the test has completed.
|
||
146 | b4adf10d | Assos Assos | self::getDatabaseConnection()
|
147 | 85ad3d82 | Assos Assos | ->insert('simpletest')
|
148 | ->fields($assertion)
|
||
149 | ->execute(); |
||
150 | |||
151 | // We do not use a ternary operator here to allow a breakpoint on
|
||
152 | // test failure.
|
||
153 | if ($status == 'pass') { |
||
154 | return TRUE; |
||
155 | } |
||
156 | else {
|
||
157 | return FALSE; |
||
158 | } |
||
159 | } |
||
160 | |||
161 | b4adf10d | Assos Assos | /**
|
162 | * Returns the database connection to the site running Simpletest.
|
||
163 | *
|
||
164 | * @return DatabaseConnection
|
||
165 | * The database connection to use for inserting assertions.
|
||
166 | */
|
||
167 | public static function getDatabaseConnection() { |
||
168 | try {
|
||
169 | $connection = Database::getConnection('default', 'simpletest_original_default'); |
||
170 | } |
||
171 | catch (DatabaseConnectionNotDefinedException $e) { |
||
172 | // If the test was not set up, the simpletest_original_default
|
||
173 | // connection does not exist.
|
||
174 | $connection = Database::getConnection('default', 'default'); |
||
175 | } |
||
176 | |||
177 | return $connection; |
||
178 | } |
||
179 | |||
180 | 85ad3d82 | Assos Assos | /**
|
181 | * Store an assertion from outside the testing context.
|
||
182 | *
|
||
183 | * This is useful for inserting assertions that can only be recorded after
|
||
184 | * the test case has been destroyed, such as PHP fatal errors. The caller
|
||
185 | * information is not automatically gathered since the caller is most likely
|
||
186 | * inserting the assertion on behalf of other code. In all other respects
|
||
187 | * the method behaves just like DrupalTestCase::assert() in terms of storing
|
||
188 | * the assertion.
|
||
189 | *
|
||
190 | * @return
|
||
191 | * Message ID of the stored assertion.
|
||
192 | *
|
||
193 | * @see DrupalTestCase::assert()
|
||
194 | * @see DrupalTestCase::deleteAssert()
|
||
195 | */
|
||
196 | public static function insertAssert($test_id, $test_class, $status, $message = '', $group = 'Other', array $caller = array()) { |
||
197 | // Convert boolean status to string status.
|
||
198 | if (is_bool($status)) { |
||
199 | $status = $status ? 'pass' : 'fail'; |
||
200 | } |
||
201 | |||
202 | $caller += array( |
||
203 | 'function' => t('Unknown'), |
||
204 | 'line' => 0, |
||
205 | 'file' => t('Unknown'), |
||
206 | ); |
||
207 | |||
208 | $assertion = array( |
||
209 | 'test_id' => $test_id, |
||
210 | 'test_class' => $test_class, |
||
211 | 'status' => $status, |
||
212 | 'message' => $message, |
||
213 | 'message_group' => $group, |
||
214 | 'function' => $caller['function'], |
||
215 | 'line' => $caller['line'], |
||
216 | 'file' => $caller['file'], |
||
217 | ); |
||
218 | |||
219 | b4adf10d | Assos Assos | return self::getDatabaseConnection() |
220 | ->insert('simpletest')
|
||
221 | 85ad3d82 | Assos Assos | ->fields($assertion)
|
222 | ->execute(); |
||
223 | } |
||
224 | |||
225 | /**
|
||
226 | * Delete an assertion record by message ID.
|
||
227 | *
|
||
228 | * @param $message_id
|
||
229 | * Message ID of the assertion to delete.
|
||
230 | * @return
|
||
231 | * TRUE if the assertion was deleted, FALSE otherwise.
|
||
232 | *
|
||
233 | * @see DrupalTestCase::insertAssert()
|
||
234 | */
|
||
235 | public static function deleteAssert($message_id) { |
||
236 | b4adf10d | Assos Assos | return (bool) self::getDatabaseConnection() |
237 | ->delete('simpletest') |
||
238 | 85ad3d82 | Assos Assos | ->condition('message_id', $message_id) |
239 | ->execute(); |
||
240 | } |
||
241 | |||
242 | /**
|
||
243 | * Cycles through backtrace until the first non-assertion method is found.
|
||
244 | *
|
||
245 | * @return
|
||
246 | * Array representing the true caller.
|
||
247 | */
|
||
248 | protected function getAssertionCall() { |
||
249 | $backtrace = debug_backtrace(); |
||
250 | |||
251 | // The first element is the call. The second element is the caller.
|
||
252 | // We skip calls that occurred in one of the methods of our base classes
|
||
253 | // or in an assertion function.
|
||
254 | while (($caller = $backtrace[1]) && |
||
255 | ((isset($caller['class']) && isset($this->skipClasses[$caller['class']])) || |
||
256 | substr($caller['function'], 0, 6) == 'assert')) { |
||
257 | // We remove that call.
|
||
258 | array_shift($backtrace); |
||
259 | } |
||
260 | |||
261 | return _drupal_get_last_caller($backtrace); |
||
262 | } |
||
263 | |||
264 | /**
|
||
265 | * Check to see if a value is not false (not an empty string, 0, NULL, or FALSE).
|
||
266 | *
|
||
267 | * @param $value
|
||
268 | * The value on which the assertion is to be done.
|
||
269 | * @param $message
|
||
270 | * The message to display along with the assertion.
|
||
271 | * @param $group
|
||
272 | * The type of assertion - examples are "Browser", "PHP".
|
||
273 | * @return
|
||
274 | * TRUE if the assertion succeeded, FALSE otherwise.
|
||
275 | */
|
||
276 | protected function assertTrue($value, $message = '', $group = 'Other') { |
||
277 | return $this->assert((bool) $value, $message ? $message : t('Value @value is TRUE.', array('@value' => var_export($value, TRUE))), $group); |
||
278 | } |
||
279 | |||
280 | /**
|
||
281 | * Check to see if a value is false (an empty string, 0, NULL, or FALSE).
|
||
282 | *
|
||
283 | * @param $value
|
||
284 | * The value on which the assertion is to be done.
|
||
285 | * @param $message
|
||
286 | * The message to display along with the assertion.
|
||
287 | * @param $group
|
||
288 | * The type of assertion - examples are "Browser", "PHP".
|
||
289 | * @return
|
||
290 | * TRUE if the assertion succeeded, FALSE otherwise.
|
||
291 | */
|
||
292 | protected function assertFalse($value, $message = '', $group = 'Other') { |
||
293 | return $this->assert(!$value, $message ? $message : t('Value @value is FALSE.', array('@value' => var_export($value, TRUE))), $group); |
||
294 | } |
||
295 | |||
296 | /**
|
||
297 | * Check to see if a value is NULL.
|
||
298 | *
|
||
299 | * @param $value
|
||
300 | * The value on which the assertion is to be done.
|
||
301 | * @param $message
|
||
302 | * The message to display along with the assertion.
|
||
303 | * @param $group
|
||
304 | * The type of assertion - examples are "Browser", "PHP".
|
||
305 | * @return
|
||
306 | * TRUE if the assertion succeeded, FALSE otherwise.
|
||
307 | */
|
||
308 | protected function assertNull($value, $message = '', $group = 'Other') { |
||
309 | return $this->assert(!isset($value), $message ? $message : t('Value @value is NULL.', array('@value' => var_export($value, TRUE))), $group); |
||
310 | } |
||
311 | |||
312 | /**
|
||
313 | * Check to see if a value is not NULL.
|
||
314 | *
|
||
315 | * @param $value
|
||
316 | * The value on which the assertion is to be done.
|
||
317 | * @param $message
|
||
318 | * The message to display along with the assertion.
|
||
319 | * @param $group
|
||
320 | * The type of assertion - examples are "Browser", "PHP".
|
||
321 | * @return
|
||
322 | * TRUE if the assertion succeeded, FALSE otherwise.
|
||
323 | */
|
||
324 | protected function assertNotNull($value, $message = '', $group = 'Other') { |
||
325 | return $this->assert(isset($value), $message ? $message : t('Value @value is not NULL.', array('@value' => var_export($value, TRUE))), $group); |
||
326 | } |
||
327 | |||
328 | /**
|
||
329 | * Check to see if two values are equal.
|
||
330 | *
|
||
331 | * @param $first
|
||
332 | * The first value to check.
|
||
333 | * @param $second
|
||
334 | * The second value to check.
|
||
335 | * @param $message
|
||
336 | * The message to display along with the assertion.
|
||
337 | * @param $group
|
||
338 | * The type of assertion - examples are "Browser", "PHP".
|
||
339 | * @return
|
||
340 | * TRUE if the assertion succeeded, FALSE otherwise.
|
||
341 | */
|
||
342 | protected function assertEqual($first, $second, $message = '', $group = 'Other') { |
||
343 | 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); |
||
344 | } |
||
345 | |||
346 | /**
|
||
347 | * Check to see if two values are not equal.
|
||
348 | *
|
||
349 | * @param $first
|
||
350 | * The first value to check.
|
||
351 | * @param $second
|
||
352 | * The second value to check.
|
||
353 | * @param $message
|
||
354 | * The message to display along with the assertion.
|
||
355 | * @param $group
|
||
356 | * The type of assertion - examples are "Browser", "PHP".
|
||
357 | * @return
|
||
358 | * TRUE if the assertion succeeded, FALSE otherwise.
|
||
359 | */
|
||
360 | protected function assertNotEqual($first, $second, $message = '', $group = 'Other') { |
||
361 | 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); |
||
362 | } |
||
363 | |||
364 | /**
|
||
365 | * Check to see if two values are identical.
|
||
366 | *
|
||
367 | * @param $first
|
||
368 | * The first value to check.
|
||
369 | * @param $second
|
||
370 | * The second value to check.
|
||
371 | * @param $message
|
||
372 | * The message to display along with the assertion.
|
||
373 | * @param $group
|
||
374 | * The type of assertion - examples are "Browser", "PHP".
|
||
375 | * @return
|
||
376 | * TRUE if the assertion succeeded, FALSE otherwise.
|
||
377 | */
|
||
378 | protected function assertIdentical($first, $second, $message = '', $group = 'Other') { |
||
379 | 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); |
||
380 | } |
||
381 | |||
382 | /**
|
||
383 | * Check to see if two values are not identical.
|
||
384 | *
|
||
385 | * @param $first
|
||
386 | * The first value to check.
|
||
387 | * @param $second
|
||
388 | * The second value to check.
|
||
389 | * @param $message
|
||
390 | * The message to display along with the assertion.
|
||
391 | * @param $group
|
||
392 | * The type of assertion - examples are "Browser", "PHP".
|
||
393 | * @return
|
||
394 | * TRUE if the assertion succeeded, FALSE otherwise.
|
||
395 | */
|
||
396 | protected function assertNotIdentical($first, $second, $message = '', $group = 'Other') { |
||
397 | 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); |
||
398 | } |
||
399 | |||
400 | /**
|
||
401 | * Fire an assertion that is always positive.
|
||
402 | *
|
||
403 | * @param $message
|
||
404 | * The message to display along with the assertion.
|
||
405 | * @param $group
|
||
406 | * The type of assertion - examples are "Browser", "PHP".
|
||
407 | * @return
|
||
408 | * TRUE.
|
||
409 | */
|
||
410 | protected function pass($message = NULL, $group = 'Other') { |
||
411 | return $this->assert(TRUE, $message, $group); |
||
412 | } |
||
413 | |||
414 | /**
|
||
415 | * Fire an assertion that is always negative.
|
||
416 | *
|
||
417 | * @param $message
|
||
418 | * The message to display along with the assertion.
|
||
419 | * @param $group
|
||
420 | * The type of assertion - examples are "Browser", "PHP".
|
||
421 | * @return
|
||
422 | * FALSE.
|
||
423 | */
|
||
424 | protected function fail($message = NULL, $group = 'Other') { |
||
425 | return $this->assert(FALSE, $message, $group); |
||
426 | } |
||
427 | |||
428 | /**
|
||
429 | * Fire an error assertion.
|
||
430 | *
|
||
431 | * @param $message
|
||
432 | * The message to display along with the assertion.
|
||
433 | * @param $group
|
||
434 | * The type of assertion - examples are "Browser", "PHP".
|
||
435 | * @param $caller
|
||
436 | * The caller of the error.
|
||
437 | * @return
|
||
438 | * FALSE.
|
||
439 | */
|
||
440 | protected function error($message = '', $group = 'Other', array $caller = NULL) { |
||
441 | if ($group == 'User notice') { |
||
442 | // Since 'User notice' is set by trigger_error() which is used for debug
|
||
443 | // set the message to a status of 'debug'.
|
||
444 | return $this->assert('debug', $message, 'Debug', $caller); |
||
445 | } |
||
446 | |||
447 | return $this->assert('exception', $message, $group, $caller); |
||
448 | } |
||
449 | |||
450 | /**
|
||
451 | b4adf10d | Assos Assos | * Logs a verbose message in a text file.
|
452 | 85ad3d82 | Assos Assos | *
|
453 | b4adf10d | Assos Assos | * The link to the verbose message will be placed in the test results as a
|
454 | * passing assertion with the text '[verbose message]'.
|
||
455 | 85ad3d82 | Assos Assos | *
|
456 | * @param $message
|
||
457 | * The verbose message to be stored.
|
||
458 | *
|
||
459 | * @see simpletest_verbose()
|
||
460 | */
|
||
461 | protected function verbose($message) { |
||
462 | if ($id = simpletest_verbose($message)) { |
||
463 | $class_safe = str_replace('\\', '_', get_class($this)); |
||
464 | $url = file_create_url($this->originalFileDirectory . '/simpletest/verbose/' . $class_safe . '-' . $id . '.html'); |
||
465 | $this->error(l(t('Verbose message'), $url, array('attributes' => array('target' => '_blank'))), 'User notice'); |
||
466 | } |
||
467 | } |
||
468 | |||
469 | /**
|
||
470 | * Run all tests in this class.
|
||
471 | *
|
||
472 | * Regardless of whether $methods are passed or not, only method names
|
||
473 | * starting with "test" are executed.
|
||
474 | *
|
||
475 | * @param $methods
|
||
476 | * (optional) A list of method names in the test case class to run; e.g.,
|
||
477 | * array('testFoo', 'testBar'). By default, all methods of the class are
|
||
478 | * taken into account, but it can be useful to only run a few selected test
|
||
479 | * methods during debugging.
|
||
480 | */
|
||
481 | public function run(array $methods = array()) { |
||
482 | // Initialize verbose debugging.
|
||
483 | $class = get_class($this); |
||
484 | simpletest_verbose(NULL, variable_get('file_public_path', conf_path() . '/files'), str_replace('\\', '_', $class)); |
||
485 | |||
486 | // HTTP auth settings (<username>:<password>) for the simpletest browser
|
||
487 | // when sending requests to the test site.
|
||
488 | $this->httpauth_method = variable_get('simpletest_httpauth_method', CURLAUTH_BASIC); |
||
489 | $username = variable_get('simpletest_httpauth_username', NULL); |
||
490 | $password = variable_get('simpletest_httpauth_password', NULL); |
||
491 | if ($username && $password) { |
||
492 | $this->httpauth_credentials = $username . ':' . $password; |
||
493 | } |
||
494 | |||
495 | set_error_handler(array($this, 'errorHandler')); |
||
496 | // Iterate through all the methods in this class, unless a specific list of
|
||
497 | // methods to run was passed.
|
||
498 | $class_methods = get_class_methods($class); |
||
499 | if ($methods) { |
||
500 | $class_methods = array_intersect($class_methods, $methods); |
||
501 | } |
||
502 | foreach ($class_methods as $method) { |
||
503 | // If the current method starts with "test", run it - it's a test.
|
||
504 | if (strtolower(substr($method, 0, 4)) == 'test') { |
||
505 | // Insert a fail record. This will be deleted on completion to ensure
|
||
506 | // that testing completed.
|
||
507 | $method_info = new ReflectionMethod($class, $method); |
||
508 | $caller = array( |
||
509 | 'file' => $method_info->getFileName(), |
||
510 | 'line' => $method_info->getStartLine(), |
||
511 | 'function' => $class . '->' . $method . '()', |
||
512 | ); |
||
513 | $completion_check_id = DrupalTestCase::insertAssert($this->testId, $class, FALSE, t('The test did not complete due to a fatal error.'), 'Completion check', $caller); |
||
514 | $this->setUp();
|
||
515 | if ($this->setup) { |
||
516 | try {
|
||
517 | $this->$method(); |
||
518 | // Finish up.
|
||
519 | } |
||
520 | catch (Exception $e) { |
||
521 | $this->exceptionHandler($e); |
||
522 | } |
||
523 | $this->tearDown();
|
||
524 | } |
||
525 | else {
|
||
526 | $this->fail(t("The test cannot be executed because it has not been set up properly.")); |
||
527 | } |
||
528 | // Remove the completion check record.
|
||
529 | DrupalTestCase::deleteAssert($completion_check_id); |
||
530 | } |
||
531 | } |
||
532 | // Clear out the error messages and restore error handler.
|
||
533 | drupal_get_messages(); |
||
534 | restore_error_handler(); |
||
535 | } |
||
536 | |||
537 | /**
|
||
538 | * Handle errors during test runs.
|
||
539 | *
|
||
540 | * Because this is registered in set_error_handler(), it has to be public.
|
||
541 | * @see set_error_handler
|
||
542 | */
|
||
543 | public function errorHandler($severity, $message, $file = NULL, $line = NULL) { |
||
544 | if ($severity & error_reporting()) { |
||
545 | $error_map = array( |
||
546 | E_STRICT => 'Run-time notice', |
||
547 | E_WARNING => 'Warning', |
||
548 | E_NOTICE => 'Notice', |
||
549 | E_CORE_ERROR => 'Core error', |
||
550 | E_CORE_WARNING => 'Core warning', |
||
551 | E_USER_ERROR => 'User error', |
||
552 | E_USER_WARNING => 'User warning', |
||
553 | E_USER_NOTICE => 'User notice', |
||
554 | E_RECOVERABLE_ERROR => 'Recoverable error', |
||
555 | ); |
||
556 | |||
557 | // PHP 5.3 adds new error logging constants. Add these conditionally for
|
||
558 | // backwards compatibility with PHP 5.2.
|
||
559 | if (defined('E_DEPRECATED')) { |
||
560 | $error_map += array( |
||
561 | E_DEPRECATED => 'Deprecated', |
||
562 | E_USER_DEPRECATED => 'User deprecated', |
||
563 | ); |
||
564 | } |
||
565 | |||
566 | $backtrace = debug_backtrace(); |
||
567 | $this->error($message, $error_map[$severity], _drupal_get_last_caller($backtrace)); |
||
568 | } |
||
569 | return TRUE; |
||
570 | } |
||
571 | |||
572 | /**
|
||
573 | * Handle exceptions.
|
||
574 | *
|
||
575 | * @see set_exception_handler
|
||
576 | */
|
||
577 | protected function exceptionHandler($exception) { |
||
578 | $backtrace = $exception->getTrace(); |
||
579 | // Push on top of the backtrace the call that generated the exception.
|
||
580 | array_unshift($backtrace, array( |
||
581 | 'line' => $exception->getLine(), |
||
582 | 'file' => $exception->getFile(), |
||
583 | )); |
||
584 | require_once DRUPAL_ROOT . '/includes/errors.inc'; |
||
585 | // The exception message is run through check_plain() by _drupal_decode_exception().
|
||
586 | $this->error(t('%type: !message in %function (line %line of %file).', _drupal_decode_exception($exception)), 'Uncaught exception', _drupal_get_last_caller($backtrace)); |
||
587 | } |
||
588 | |||
589 | /**
|
||
590 | * Generates a random string of ASCII characters of codes 32 to 126.
|
||
591 | *
|
||
592 | * The generated string includes alpha-numeric characters and common
|
||
593 | * miscellaneous characters. Use this method when testing general input
|
||
594 | * where the content is not restricted.
|
||
595 | *
|
||
596 | * Do not use this method when special characters are not possible (e.g., in
|
||
597 | * machine or file names that have already been validated); instead,
|
||
598 | * use DrupalWebTestCase::randomName().
|
||
599 | *
|
||
600 | * @param $length
|
||
601 | * Length of random string to generate.
|
||
602 | *
|
||
603 | * @return
|
||
604 | * Randomly generated string.
|
||
605 | *
|
||
606 | * @see DrupalWebTestCase::randomName()
|
||
607 | */
|
||
608 | public static function randomString($length = 8) { |
||
609 | $str = ''; |
||
610 | for ($i = 0; $i < $length; $i++) { |
||
611 | $str .= chr(mt_rand(32, 126)); |
||
612 | } |
||
613 | return $str; |
||
614 | } |
||
615 | |||
616 | /**
|
||
617 | * Generates a random string containing letters and numbers.
|
||
618 | *
|
||
619 | * The string will always start with a letter. The letters may be upper or
|
||
620 | * lower case. This method is better for restricted inputs that do not
|
||
621 | * accept certain characters. For example, when testing input fields that
|
||
622 | * require machine readable values (i.e. without spaces and non-standard
|
||
623 | * characters) this method is best.
|
||
624 | *
|
||
625 | * Do not use this method when testing unvalidated user input. Instead, use
|
||
626 | * DrupalWebTestCase::randomString().
|
||
627 | *
|
||
628 | * @param $length
|
||
629 | * Length of random string to generate.
|
||
630 | *
|
||
631 | * @return
|
||
632 | * Randomly generated string.
|
||
633 | *
|
||
634 | * @see DrupalWebTestCase::randomString()
|
||
635 | */
|
||
636 | public static function randomName($length = 8) { |
||
637 | $values = array_merge(range(65, 90), range(97, 122), range(48, 57)); |
||
638 | $max = count($values) - 1; |
||
639 | $str = chr(mt_rand(97, 122)); |
||
640 | for ($i = 1; $i < $length; $i++) { |
||
641 | $str .= chr($values[mt_rand(0, $max)]); |
||
642 | } |
||
643 | return $str; |
||
644 | } |
||
645 | |||
646 | /**
|
||
647 | * Converts a list of possible parameters into a stack of permutations.
|
||
648 | *
|
||
649 | * Takes a list of parameters containing possible values, and converts all of
|
||
650 | * them into a list of items containing every possible permutation.
|
||
651 | *
|
||
652 | * Example:
|
||
653 | * @code
|
||
654 | * $parameters = array(
|
||
655 | * 'one' => array(0, 1),
|
||
656 | * 'two' => array(2, 3),
|
||
657 | * );
|
||
658 | * $permutations = DrupalTestCase::generatePermutations($parameters)
|
||
659 | * // Result:
|
||
660 | * $permutations == array(
|
||
661 | * array('one' => 0, 'two' => 2),
|
||
662 | * array('one' => 1, 'two' => 2),
|
||
663 | * array('one' => 0, 'two' => 3),
|
||
664 | * array('one' => 1, 'two' => 3),
|
||
665 | * )
|
||
666 | * @endcode
|
||
667 | *
|
||
668 | * @param $parameters
|
||
669 | * An associative array of parameters, keyed by parameter name, and whose
|
||
670 | * values are arrays of parameter values.
|
||
671 | *
|
||
672 | * @return
|
||
673 | * A list of permutations, which is an array of arrays. Each inner array
|
||
674 | * contains the full list of parameters that have been passed, but with a
|
||
675 | * single value only.
|
||
676 | */
|
||
677 | public static function generatePermutations($parameters) { |
||
678 | $all_permutations = array(array()); |
||
679 | foreach ($parameters as $parameter => $values) { |
||
680 | $new_permutations = array(); |
||
681 | // Iterate over all values of the parameter.
|
||
682 | foreach ($values as $value) { |
||
683 | // Iterate over all existing permutations.
|
||
684 | foreach ($all_permutations as $permutation) { |
||
685 | // Add the new parameter value to existing permutations.
|
||
686 | $new_permutations[] = $permutation + array($parameter => $value); |
||
687 | } |
||
688 | } |
||
689 | // Replace the old permutations with the new permutations.
|
||
690 | $all_permutations = $new_permutations; |
||
691 | } |
||
692 | return $all_permutations; |
||
693 | } |
||
694 | } |
||
695 | |||
696 | /**
|
||
697 | * Test case for Drupal unit tests.
|
||
698 | *
|
||
699 | * These tests can not access the database nor files. Calling any Drupal
|
||
700 | * function that needs the database will throw exceptions. These include
|
||
701 | * watchdog(), module_implements(), module_invoke_all() etc.
|
||
702 | */
|
||
703 | class DrupalUnitTestCase extends DrupalTestCase { |
||
704 | |||
705 | /**
|
||
706 | * Constructor for DrupalUnitTestCase.
|
||
707 | */
|
||
708 | function __construct($test_id = NULL) { |
||
709 | parent::__construct($test_id); |
||
710 | $this->skipClasses[__CLASS__] = TRUE; |
||
711 | } |
||
712 | |||
713 | /**
|
||
714 | * Sets up unit test environment.
|
||
715 | *
|
||
716 | * Unlike DrupalWebTestCase::setUp(), DrupalUnitTestCase::setUp() does not
|
||
717 | * install modules because tests are performed without accessing the database.
|
||
718 | * Any required files must be explicitly included by the child class setUp()
|
||
719 | * method.
|
||
720 | */
|
||
721 | protected function setUp() { |
||
722 | global $conf; |
||
723 | |||
724 | // Store necessary current values before switching to the test environment.
|
||
725 | $this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files'); |
||
726 | |||
727 | // Reset all statics so that test is performed with a clean environment.
|
||
728 | drupal_static_reset(); |
||
729 | |||
730 | // Generate temporary prefixed database to ensure that tests have a clean starting point.
|
||
731 | $this->databasePrefix = Database::getConnection()->prefixTables('{simpletest' . mt_rand(1000, 1000000) . '}'); |
||
732 | |||
733 | // Create test directory.
|
||
734 | $public_files_directory = $this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10); |
||
735 | file_prepare_directory($public_files_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); |
||
736 | $conf['file_public_path'] = $public_files_directory; |
||
737 | |||
738 | // Clone the current connection and replace the current prefix.
|
||
739 | $connection_info = Database::getConnectionInfo('default'); |
||
740 | Database::renameConnection('default', 'simpletest_original_default'); |
||
741 | foreach ($connection_info as $target => $value) { |
||
742 | $connection_info[$target]['prefix'] = array( |
||
743 | 'default' => $value['prefix']['default'] . $this->databasePrefix, |
||
744 | ); |
||
745 | } |
||
746 | Database::addConnectionInfo('default', 'default', $connection_info['default']); |
||
747 | |||
748 | // Set user agent to be consistent with web test case.
|
||
749 | $_SERVER['HTTP_USER_AGENT'] = $this->databasePrefix; |
||
750 | |||
751 | // If locale is enabled then t() will try to access the database and
|
||
752 | // subsequently will fail as the database is not accessible.
|
||
753 | $module_list = module_list();
|
||
754 | if (isset($module_list['locale'])) { |
||
755 | // Transform the list into the format expected as input to module_list().
|
||
756 | foreach ($module_list as &$module) { |
||
757 | $module = array('filename' => drupal_get_filename('module', $module)); |
||
758 | } |
||
759 | $this->originalModuleList = $module_list; |
||
760 | unset($module_list['locale']); |
||
761 | module_list(TRUE, FALSE, FALSE, $module_list); |
||
762 | } |
||
763 | $this->setup = TRUE; |
||
764 | } |
||
765 | |||
766 | protected function tearDown() { |
||
767 | global $conf; |
||
768 | |||
769 | // Get back to the original connection.
|
||
770 | Database::removeConnection('default'); |
||
771 | Database::renameConnection('simpletest_original_default', 'default'); |
||
772 | |||
773 | $conf['file_public_path'] = $this->originalFileDirectory; |
||
774 | // Restore modules if necessary.
|
||
775 | if (isset($this->originalModuleList)) { |
||
776 | module_list(TRUE, FALSE, FALSE, $this->originalModuleList); |
||
777 | } |
||
778 | } |
||
779 | } |
||
780 | |||
781 | /**
|
||
782 | * Test case for typical Drupal tests.
|
||
783 | */
|
||
784 | class DrupalWebTestCase extends DrupalTestCase { |
||
785 | /**
|
||
786 | * The profile to install as a basis for testing.
|
||
787 | *
|
||
788 | * @var string
|
||
789 | */
|
||
790 | protected $profile = 'standard'; |
||
791 | |||
792 | /**
|
||
793 | * The URL currently loaded in the internal browser.
|
||
794 | *
|
||
795 | * @var string
|
||
796 | */
|
||
797 | protected $url; |
||
798 | |||
799 | /**
|
||
800 | * The handle of the current cURL connection.
|
||
801 | *
|
||
802 | * @var resource
|
||
803 | */
|
||
804 | protected $curlHandle; |
||
805 | |||
806 | /**
|
||
807 | * The headers of the page currently loaded in the internal browser.
|
||
808 | *
|
||
809 | * @var Array
|
||
810 | */
|
||
811 | protected $headers; |
||
812 | |||
813 | /**
|
||
814 | * The content of the page currently loaded in the internal browser.
|
||
815 | *
|
||
816 | * @var string
|
||
817 | */
|
||
818 | protected $content; |
||
819 | |||
820 | /**
|
||
821 | * The content of the page currently loaded in the internal browser (plain text version).
|
||
822 | *
|
||
823 | * @var string
|
||
824 | */
|
||
825 | protected $plainTextContent; |
||
826 | |||
827 | /**
|
||
828 | * The value of the Drupal.settings JavaScript variable for the page currently loaded in the internal browser.
|
||
829 | *
|
||
830 | * @var Array
|
||
831 | */
|
||
832 | protected $drupalSettings; |
||
833 | |||
834 | /**
|
||
835 | * The parsed version of the page.
|
||
836 | *
|
||
837 | * @var SimpleXMLElement
|
||
838 | */
|
||
839 | protected $elements = NULL; |
||
840 | |||
841 | /**
|
||
842 | * The current user logged in using the internal browser.
|
||
843 | *
|
||
844 | * @var bool
|
||
845 | */
|
||
846 | protected $loggedInUser = FALSE; |
||
847 | |||
848 | /**
|
||
849 | * The current cookie file used by cURL.
|
||
850 | *
|
||
851 | * We do not reuse the cookies in further runs, so we do not need a file
|
||
852 | * but we still need cookie handling, so we set the jar to NULL.
|
||
853 | */
|
||
854 | protected $cookieFile = NULL; |
||
855 | |||
856 | /**
|
||
857 | * Additional cURL options.
|
||
858 | *
|
||
859 | * DrupalWebTestCase itself never sets this but always obeys what is set.
|
||
860 | */
|
||
861 | protected $additionalCurlOptions = array(); |
||
862 | |||
863 | /**
|
||
864 | * The original user, before it was changed to a clean uid = 1 for testing purposes.
|
||
865 | *
|
||
866 | * @var object
|
||
867 | */
|
||
868 | protected $originalUser = NULL; |
||
869 | |||
870 | /**
|
||
871 | * The original shutdown handlers array, before it was cleaned for testing purposes.
|
||
872 | *
|
||
873 | * @var array
|
||
874 | */
|
||
875 | protected $originalShutdownCallbacks = array(); |
||
876 | |||
877 | /**
|
||
878 | * HTTP authentication method
|
||
879 | */
|
||
880 | protected $httpauth_method = CURLAUTH_BASIC; |
||
881 | |||
882 | /**
|
||
883 | * HTTP authentication credentials (<username>:<password>).
|
||
884 | */
|
||
885 | protected $httpauth_credentials = NULL; |
||
886 | |||
887 | /**
|
||
888 | * The current session name, if available.
|
||
889 | */
|
||
890 | protected $session_name = NULL; |
||
891 | |||
892 | /**
|
||
893 | * The current session ID, if available.
|
||
894 | */
|
||
895 | protected $session_id = NULL; |
||
896 | |||
897 | /**
|
||
898 | * Whether the files were copied to the test files directory.
|
||
899 | */
|
||
900 | protected $generatedTestFiles = FALSE; |
||
901 | |||
902 | /**
|
||
903 | * The number of redirects followed during the handling of a request.
|
||
904 | */
|
||
905 | protected $redirect_count; |
||
906 | |||
907 | /**
|
||
908 | * Constructor for DrupalWebTestCase.
|
||
909 | */
|
||
910 | function __construct($test_id = NULL) { |
||
911 | parent::__construct($test_id); |
||
912 | $this->skipClasses[__CLASS__] = TRUE; |
||
913 | } |
||
914 | |||
915 | /**
|
||
916 | * Get a node from the database based on its title.
|
||
917 | *
|
||
918 | * @param $title
|
||
919 | * A node title, usually generated by $this->randomName().
|
||
920 | * @param $reset
|
||
921 | * (optional) Whether to reset the internal node_load() cache.
|
||
922 | *
|
||
923 | * @return
|
||
924 | * A node object matching $title.
|
||
925 | */
|
||
926 | function drupalGetNodeByTitle($title, $reset = FALSE) { |
||
927 | $nodes = node_load_multiple(array(), array('title' => $title), $reset); |
||
928 | // Load the first node returned from the database.
|
||
929 | $returned_node = reset($nodes); |
||
930 | return $returned_node; |
||
931 | } |
||
932 | |||
933 | /**
|
||
934 | * Creates a node based on default settings.
|
||
935 | *
|
||
936 | * @param $settings
|
||
937 | * An associative array of settings to change from the defaults, keys are
|
||
938 | * node properties, for example 'title' => 'Hello, world!'.
|
||
939 | * @return
|
||
940 | * Created node object.
|
||
941 | */
|
||
942 | protected function drupalCreateNode($settings = array()) { |
||
943 | // Populate defaults array.
|
||
944 | $settings += array( |
||
945 | 'body' => array(LANGUAGE_NONE => array(array())), |
||
946 | 'title' => $this->randomName(8), |
||
947 | 'comment' => 2, |
||
948 | 'changed' => REQUEST_TIME, |
||
949 | 'moderate' => 0, |
||
950 | 'promote' => 0, |
||
951 | 'revision' => 1, |
||
952 | 'log' => '', |
||
953 | 'status' => 1, |
||
954 | 'sticky' => 0, |
||
955 | 'type' => 'page', |
||
956 | 'revisions' => NULL, |
||
957 | 'language' => LANGUAGE_NONE, |
||
958 | ); |
||
959 | |||
960 | // Use the original node's created time for existing nodes.
|
||
961 | if (isset($settings['created']) && !isset($settings['date'])) { |
||
962 | $settings['date'] = format_date($settings['created'], 'custom', 'Y-m-d H:i:s O'); |
||
963 | } |
||
964 | |||
965 | // If the node's user uid is not specified manually, use the currently
|
||
966 | // logged in user if available, or else the user running the test.
|
||
967 | if (!isset($settings['uid'])) { |
||
968 | if ($this->loggedInUser) { |
||
969 | $settings['uid'] = $this->loggedInUser->uid; |
||
970 | } |
||
971 | else {
|
||
972 | global $user; |
||
973 | $settings['uid'] = $user->uid; |
||
974 | } |
||
975 | } |
||
976 | |||
977 | // Merge body field value and format separately.
|
||
978 | $body = array( |
||
979 | 'value' => $this->randomName(32), |
||
980 | 'format' => filter_default_format(),
|
||
981 | ); |
||
982 | $settings['body'][$settings['language']][0] += $body; |
||
983 | |||
984 | $node = (object) $settings; |
||
985 | node_save($node);
|
||
986 | |||
987 | // Small hack to link revisions to our test user.
|
||
988 | db_update('node_revision')
|
||
989 | ->fields(array('uid' => $node->uid)) |
||
990 | ->condition('vid', $node->vid) |
||
991 | ->execute(); |
||
992 | return $node; |
||
993 | } |
||
994 | |||
995 | /**
|
||
996 | * Creates a custom content type based on default settings.
|
||
997 | *
|
||
998 | * @param $settings
|
||
999 | * An array of settings to change from the defaults.
|
||
1000 | * Example: 'type' => 'foo'.
|
||
1001 | * @return
|
||
1002 | * Created content type.
|
||
1003 | */
|
||
1004 | protected function drupalCreateContentType($settings = array()) { |
||
1005 | // Find a non-existent random type name.
|
||
1006 | do {
|
||
1007 | $name = strtolower($this->randomName(8)); |
||
1008 | } while (node_type_get_type($name)); |
||
1009 | |||
1010 | // Populate defaults array.
|
||
1011 | $defaults = array( |
||
1012 | 'type' => $name, |
||
1013 | 'name' => $name, |
||
1014 | 'base' => 'node_content', |
||
1015 | 'description' => '', |
||
1016 | 'help' => '', |
||
1017 | 'title_label' => 'Title', |
||
1018 | 'body_label' => 'Body', |
||
1019 | 'has_title' => 1, |
||
1020 | 'has_body' => 1, |
||
1021 | ); |
||
1022 | // Imposed values for a custom type.
|
||
1023 | $forced = array( |
||
1024 | 'orig_type' => '', |
||
1025 | 'old_type' => '', |
||
1026 | 'module' => 'node', |
||
1027 | 'custom' => 1, |
||
1028 | 'modified' => 1, |
||
1029 | 'locked' => 0, |
||
1030 | ); |
||
1031 | $type = $forced + $settings + $defaults; |
||
1032 | $type = (object) $type; |
||
1033 | |||
1034 | $saved_type = node_type_save($type); |
||
1035 | node_types_rebuild(); |
||
1036 | menu_rebuild(); |
||
1037 | node_add_body_field($type);
|
||
1038 | |||
1039 | $this->assertEqual($saved_type, SAVED_NEW, t('Created content type %type.', array('%type' => $type->type))); |
||
1040 | |||
1041 | // Reset permissions so that permissions for this content type are available.
|
||
1042 | $this->checkPermissions(array(), TRUE); |
||
1043 | |||
1044 | return $type; |
||
1045 | } |
||
1046 | |||
1047 | /**
|
||
1048 | * Get a list files that can be used in tests.
|
||
1049 | *
|
||
1050 | * @param $type
|
||
1051 | * File type, possible values: 'binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'.
|
||
1052 | * @param $size
|
||
1053 | * File size in bytes to match. Please check the tests/files folder.
|
||
1054 | * @return
|
||
1055 | * List of files that match filter.
|
||
1056 | */
|
||
1057 | protected function drupalGetTestFiles($type, $size = NULL) { |
||
1058 | if (empty($this->generatedTestFiles)) { |
||
1059 | // Generate binary test files.
|
||
1060 | $lines = array(64, 1024); |
||
1061 | $count = 0; |
||
1062 | foreach ($lines as $line) { |
||
1063 | simpletest_generate_file('binary-' . $count++, 64, $line, 'binary'); |
||
1064 | } |
||
1065 | |||
1066 | // Generate text test files.
|
||
1067 | $lines = array(16, 256, 1024, 2048, 20480); |
||
1068 | $count = 0; |
||
1069 | foreach ($lines as $line) { |
||
1070 | simpletest_generate_file('text-' . $count++, 64, $line); |
||
1071 | } |
||
1072 | |||
1073 | // Copy other test files from simpletest.
|
||
1074 | $original = drupal_get_path('module', 'simpletest') . '/files'; |
||
1075 | $files = file_scan_directory($original, '/(html|image|javascript|php|sql)-.*/'); |
||
1076 | foreach ($files as $file) { |
||
1077 | file_unmanaged_copy($file->uri, variable_get('file_public_path', conf_path() . '/files')); |
||
1078 | } |
||
1079 | |||
1080 | $this->generatedTestFiles = TRUE; |
||
1081 | } |
||
1082 | |||
1083 | $files = array(); |
||
1084 | // Make sure type is valid.
|
||
1085 | if (in_array($type, array('binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'))) { |
||
1086 | $files = file_scan_directory('public://', '/' . $type . '\-.*/'); |
||
1087 | |||
1088 | // If size is set then remove any files that are not of that size.
|
||
1089 | if ($size !== NULL) { |
||
1090 | foreach ($files as $file) { |
||
1091 | $stats = stat($file->uri); |
||
1092 | if ($stats['size'] != $size) { |
||
1093 | unset($files[$file->uri]); |
||
1094 | } |
||
1095 | } |
||
1096 | } |
||
1097 | } |
||
1098 | usort($files, array($this, 'drupalCompareFiles')); |
||
1099 | return $files; |
||
1100 | } |
||
1101 | |||
1102 | /**
|
||
1103 | * Compare two files based on size and file name.
|
||
1104 | */
|
||
1105 | protected function drupalCompareFiles($file1, $file2) { |
||
1106 | $compare_size = filesize($file1->uri) - filesize($file2->uri); |
||
1107 | if ($compare_size) { |
||
1108 | // Sort by file size.
|
||
1109 | return $compare_size; |
||
1110 | } |
||
1111 | else {
|
||
1112 | // The files were the same size, so sort alphabetically.
|
||
1113 | return strnatcmp($file1->name, $file2->name); |
||
1114 | } |
||
1115 | } |
||
1116 | |||
1117 | /**
|
||
1118 | * Create a user with a given set of permissions.
|
||
1119 | *
|
||
1120 | * @param array $permissions
|
||
1121 | * Array of permission names to assign to user. Note that the user always
|
||
1122 | * has the default permissions derived from the "authenticated users" role.
|
||
1123 | *
|
||
1124 | * @return object|false
|
||
1125 | * A fully loaded user object with pass_raw property, or FALSE if account
|
||
1126 | * creation fails.
|
||
1127 | */
|
||
1128 | protected function drupalCreateUser(array $permissions = array()) { |
||
1129 | // Create a role with the given permission set, if any.
|
||
1130 | $rid = FALSE; |
||
1131 | if ($permissions) { |
||
1132 | $rid = $this->drupalCreateRole($permissions); |
||
1133 | if (!$rid) { |
||
1134 | return FALSE; |
||
1135 | } |
||
1136 | } |
||
1137 | |||
1138 | // Create a user assigned to that role.
|
||
1139 | $edit = array(); |
||
1140 | $edit['name'] = $this->randomName(); |
||
1141 | $edit['mail'] = $edit['name'] . '@example.com'; |
||
1142 | $edit['pass'] = user_password(); |
||
1143 | $edit['status'] = 1; |
||
1144 | if ($rid) { |
||
1145 | $edit['roles'] = array($rid => $rid); |
||
1146 | } |
||
1147 | |||
1148 | $account = user_save(drupal_anonymous_user(), $edit); |
||
1149 | |||
1150 | $this->assertTrue(!empty($account->uid), t('User created with name %name and pass %pass', array('%name' => $edit['name'], '%pass' => $edit['pass'])), t('User login')); |
||
1151 | if (empty($account->uid)) { |
||
1152 | return FALSE; |
||
1153 | } |
||
1154 | |||
1155 | // Add the raw password so that we can log in as this user.
|
||
1156 | $account->pass_raw = $edit['pass']; |
||
1157 | return $account; |
||
1158 | } |
||
1159 | |||
1160 | /**
|
||
1161 | 4444412d | Julien Enselme | * Creates a role with specified permissions.
|
1162 | 85ad3d82 | Assos Assos | *
|
1163 | * @param $permissions
|
||
1164 | * Array of permission names to assign to role.
|
||
1165 | * @param $name
|
||
1166 | * (optional) String for the name of the role. Defaults to a random string.
|
||
1167 | * @return
|
||
1168 | * Role ID of newly created role, or FALSE if role creation failed.
|
||
1169 | */
|
||
1170 | protected function drupalCreateRole(array $permissions, $name = NULL) { |
||
1171 | // Generate random name if it was not passed.
|
||
1172 | if (!$name) { |
||
1173 | $name = $this->randomName(); |
||
1174 | } |
||
1175 | |||
1176 | // Check the all the permissions strings are valid.
|
||
1177 | if (!$this->checkPermissions($permissions)) { |
||
1178 | return FALSE; |
||
1179 | } |
||
1180 | |||
1181 | // Create new role.
|
||
1182 | $role = new stdClass(); |
||
1183 | $role->name = $name; |
||
1184 | user_role_save($role);
|
||
1185 | user_role_grant_permissions($role->rid, $permissions); |
||
1186 | |||
1187 | $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')); |
||
1188 | if ($role && !empty($role->rid)) { |
||
1189 | $count = db_query('SELECT COUNT(*) FROM {role_permission} WHERE rid = :rid', array(':rid' => $role->rid))->fetchField(); |
||
1190 | $this->assertTrue($count == count($permissions), t('Created permissions: @perms', array('@perms' => implode(', ', $permissions))), t('Role')); |
||
1191 | return $role->rid; |
||
1192 | } |
||
1193 | else {
|
||
1194 | return FALSE; |
||
1195 | } |
||
1196 | } |
||
1197 | |||
1198 | /**
|
||
1199 | * Check to make sure that the array of permissions are valid.
|
||
1200 | *
|
||
1201 | * @param $permissions
|
||
1202 | * Permissions to check.
|
||
1203 | * @param $reset
|
||
1204 | * Reset cached available permissions.
|
||
1205 | * @return
|
||
1206 | * TRUE or FALSE depending on whether the permissions are valid.
|
||
1207 | */
|
||
1208 | protected function checkPermissions(array $permissions, $reset = FALSE) { |
||
1209 | $available = &drupal_static(__FUNCTION__); |
||
1210 | |||
1211 | if (!isset($available) || $reset) { |
||
1212 | $available = array_keys(module_invoke_all('permission')); |
||
1213 | } |
||
1214 | |||
1215 | $valid = TRUE; |
||
1216 | foreach ($permissions as $permission) { |
||
1217 | if (!in_array($permission, $available)) { |
||
1218 | $this->fail(t('Invalid permission %permission.', array('%permission' => $permission)), t('Role')); |
||
1219 | $valid = FALSE; |
||
1220 | } |
||
1221 | } |
||
1222 | return $valid; |
||
1223 | } |
||
1224 | |||
1225 | /**
|
||
1226 | * Log in a user with the internal browser.
|
||
1227 | *
|
||
1228 | * If a user is already logged in, then the current user is logged out before
|
||
1229 | * logging in the specified user.
|
||
1230 | *
|
||
1231 | * Please note that neither the global $user nor the passed-in user object is
|
||
1232 | * populated with data of the logged in user. If you need full access to the
|
||
1233 | * user object after logging in, it must be updated manually. If you also need
|
||
1234 | * access to the plain-text password of the user (set by drupalCreateUser()),
|
||
1235 | * e.g. to log in the same user again, then it must be re-assigned manually.
|
||
1236 | * For example:
|
||
1237 | * @code
|
||
1238 | * // Create a user.
|
||
1239 | * $account = $this->drupalCreateUser(array());
|
||
1240 | * $this->drupalLogin($account);
|
||
1241 | * // Load real user object.
|
||
1242 | * $pass_raw = $account->pass_raw;
|
||
1243 | * $account = user_load($account->uid);
|
||
1244 | * $account->pass_raw = $pass_raw;
|
||
1245 | * @endcode
|
||
1246 | *
|
||
1247 | * @param $account
|
||
1248 | * User object representing the user to log in.
|
||
1249 | *
|
||
1250 | * @see drupalCreateUser()
|
||
1251 | */
|
||
1252 | protected function drupalLogin(stdClass $account) { |
||
1253 | if ($this->loggedInUser) { |
||
1254 | $this->drupalLogout();
|
||
1255 | } |
||
1256 | |||
1257 | $edit = array( |
||
1258 | 'name' => $account->name, |
||
1259 | 'pass' => $account->pass_raw |
||
1260 | ); |
||
1261 | $this->drupalPost('user', $edit, t('Log in')); |
||
1262 | |||
1263 | // If a "log out" link appears on the page, it is almost certainly because
|
||
1264 | // the login was successful.
|
||
1265 | $pass = $this->assertLink(t('Log out'), 0, t('User %name successfully logged in.', array('%name' => $account->name)), t('User login')); |
||
1266 | |||
1267 | if ($pass) { |
||
1268 | $this->loggedInUser = $account; |
||
1269 | } |
||
1270 | } |
||
1271 | |||
1272 | /**
|
||
1273 | * Generate a token for the currently logged in user.
|
||
1274 | */
|
||
1275 | protected function drupalGetToken($value = '') { |
||
1276 | $private_key = drupal_get_private_key();
|
||
1277 | return drupal_hmac_base64($value, $this->session_id . $private_key); |
||
1278 | } |
||
1279 | |||
1280 | /*
|
||
1281 | * Logs a user out of the internal browser, then check the login page to confirm logout.
|
||
1282 | */
|
||
1283 | protected function drupalLogout() { |
||
1284 | // Make a request to the logout page, and redirect to the user page, the
|
||
1285 | // idea being if you were properly logged out you should be seeing a login
|
||
1286 | // screen.
|
||
1287 | $this->drupalGet('user/logout'); |
||
1288 | $this->drupalGet('user'); |
||
1289 | $pass = $this->assertField('name', t('Username field found.'), t('Logout')); |
||
1290 | $pass = $pass && $this->assertField('pass', t('Password field found.'), t('Logout')); |
||
1291 | |||
1292 | if ($pass) { |
||
1293 | $this->loggedInUser = FALSE; |
||
1294 | } |
||
1295 | } |
||
1296 | |||
1297 | /**
|
||
1298 | * Generates a database prefix for running tests.
|
||
1299 | *
|
||
1300 | * The generated database table prefix is used for the Drupal installation
|
||
1301 | * being performed for the test. It is also used as user agent HTTP header
|
||
1302 | * value by the cURL-based browser of DrupalWebTestCase, which is sent
|
||
1303 | * to the Drupal installation of the test. During early Drupal bootstrap, the
|
||
1304 | * user agent HTTP header is parsed, and if it matches, all database queries
|
||
1305 | * use the database table prefix that has been generated here.
|
||
1306 | *
|
||
1307 | * @see DrupalWebTestCase::curlInitialize()
|
||
1308 | * @see drupal_valid_test_ua()
|
||
1309 | * @see DrupalWebTestCase::setUp()
|
||
1310 | */
|
||
1311 | protected function prepareDatabasePrefix() { |
||
1312 | $this->databasePrefix = 'simpletest' . mt_rand(1000, 1000000); |
||
1313 | |||
1314 | // As soon as the database prefix is set, the test might start to execute.
|
||
1315 | // All assertions as well as the SimpleTest batch operations are associated
|
||
1316 | // with the testId, so the database prefix has to be associated with it.
|
||
1317 | db_update('simpletest_test_id')
|
||
1318 | ->fields(array('last_prefix' => $this->databasePrefix)) |
||
1319 | ->condition('test_id', $this->testId) |
||
1320 | ->execute(); |
||
1321 | } |
||
1322 | |||
1323 | /**
|
||
1324 | * Changes the database connection to the prefixed one.
|
||
1325 | *
|
||
1326 | * @see DrupalWebTestCase::setUp()
|
||
1327 | */
|
||
1328 | protected function changeDatabasePrefix() { |
||
1329 | if (empty($this->databasePrefix)) { |
||
1330 | $this->prepareDatabasePrefix();
|
||
1331 | // If $this->prepareDatabasePrefix() failed to work, return without
|
||
1332 | // setting $this->setupDatabasePrefix to TRUE, so setUp() methods will
|
||
1333 | // know to bail out.
|
||
1334 | if (empty($this->databasePrefix)) { |
||
1335 | return;
|
||
1336 | } |
||
1337 | } |
||
1338 | |||
1339 | // Clone the current connection and replace the current prefix.
|
||
1340 | $connection_info = Database::getConnectionInfo('default'); |
||
1341 | Database::renameConnection('default', 'simpletest_original_default'); |
||
1342 | foreach ($connection_info as $target => $value) { |
||
1343 | $connection_info[$target]['prefix'] = array( |
||
1344 | 'default' => $value['prefix']['default'] . $this->databasePrefix, |
||
1345 | ); |
||
1346 | } |
||
1347 | Database::addConnectionInfo('default', 'default', $connection_info['default']); |
||
1348 | |||
1349 | // Indicate the database prefix was set up correctly.
|
||
1350 | $this->setupDatabasePrefix = TRUE; |
||
1351 | } |
||
1352 | |||
1353 | /**
|
||
1354 | * Prepares the current environment for running the test.
|
||
1355 | *
|
||
1356 | * Backups various current environment variables and resets them, so they do
|
||
1357 | * not interfere with the Drupal site installation in which tests are executed
|
||
1358 | * and can be restored in tearDown().
|
||
1359 | *
|
||
1360 | * Also sets up new resources for the testing environment, such as the public
|
||
1361 | * filesystem and configuration directories.
|
||
1362 | *
|
||
1363 | * @see DrupalWebTestCase::setUp()
|
||
1364 | * @see DrupalWebTestCase::tearDown()
|
||
1365 | */
|
||
1366 | protected function prepareEnvironment() { |
||
1367 | global $user, $language, $conf; |
||
1368 | |||
1369 | // Store necessary current values before switching to prefixed database.
|
||
1370 | $this->originalLanguage = $language; |
||
1371 | $this->originalLanguageDefault = variable_get('language_default'); |
||
1372 | $this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files'); |
||
1373 | $this->originalProfile = drupal_get_profile();
|
||
1374 | $this->originalCleanUrl = variable_get('clean_url', 0); |
||
1375 | $this->originalUser = $user; |
||
1376 | |||
1377 | // Set to English to prevent exceptions from utf8_truncate() from t()
|
||
1378 | // during install if the current language is not 'en'.
|
||
1379 | // The following array/object conversion is copied from language_default().
|
||
1380 | $language = (object) array('language' => 'en', 'name' => 'English', 'native' => 'English', 'direction' => 0, 'enabled' => 1, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => '', 'weight' => 0, 'javascript' => ''); |
||
1381 | |||
1382 | // Save and clean the shutdown callbacks array because it is static cached
|
||
1383 | // and will be changed by the test run. Otherwise it will contain callbacks
|
||
1384 | // from both environments and the testing environment will try to call the
|
||
1385 | // handlers defined by the original one.
|
||
1386 | $callbacks = &drupal_register_shutdown_function();
|
||
1387 | $this->originalShutdownCallbacks = $callbacks; |
||
1388 | $callbacks = array(); |
||
1389 | |||
1390 | // Create test directory ahead of installation so fatal errors and debug
|
||
1391 | // information can be logged during installation process.
|
||
1392 | // Use temporary files directory with the same prefix as the database.
|
||
1393 | $this->public_files_directory = $this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10); |
||
1394 | $this->private_files_directory = $this->public_files_directory . '/private'; |
||
1395 | $this->temp_files_directory = $this->private_files_directory . '/temp'; |
||
1396 | |||
1397 | // Create the directories
|
||
1398 | file_prepare_directory($this->public_files_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); |
||
1399 | file_prepare_directory($this->private_files_directory, FILE_CREATE_DIRECTORY); |
||
1400 | file_prepare_directory($this->temp_files_directory, FILE_CREATE_DIRECTORY); |
||
1401 | $this->generatedTestFiles = FALSE; |
||
1402 | |||
1403 | // Log fatal errors.
|
||
1404 | ini_set('log_errors', 1); |
||
1405 | ini_set('error_log', $this->public_files_directory . '/error.log'); |
||
1406 | |||
1407 | // Set the test information for use in other parts of Drupal.
|
||
1408 | $test_info = &$GLOBALS['drupal_test_info']; |
||
1409 | $test_info['test_run_id'] = $this->databasePrefix; |
||
1410 | $test_info['in_child_site'] = FALSE; |
||
1411 | |||
1412 | // Indicate the environment was set up correctly.
|
||
1413 | $this->setupEnvironment = TRUE; |
||
1414 | } |
||
1415 | |||
1416 | /**
|
||
1417 | * Sets up a Drupal site for running functional and integration tests.
|
||
1418 | *
|
||
1419 | * Generates a random database prefix and installs Drupal with the specified
|
||
1420 | * installation profile in DrupalWebTestCase::$profile into the
|
||
1421 | * prefixed database. Afterwards, installs any additional modules specified by
|
||
1422 | * the test.
|
||
1423 | *
|
||
1424 | * After installation all caches are flushed and several configuration values
|
||
1425 | * are reset to the values of the parent site executing the test, since the
|
||
1426 | * default values may be incompatible with the environment in which tests are
|
||
1427 | * being executed.
|
||
1428 | *
|
||
1429 | * @param ...
|
||
1430 | * List of modules to enable for the duration of the test. This can be
|
||
1431 | * either a single array or a variable number of string arguments.
|
||
1432 | *
|
||
1433 | * @see DrupalWebTestCase::prepareDatabasePrefix()
|
||
1434 | * @see DrupalWebTestCase::changeDatabasePrefix()
|
||
1435 | * @see DrupalWebTestCase::prepareEnvironment()
|
||
1436 | */
|
||
1437 | protected function setUp() { |
||
1438 | global $user, $language, $conf; |
||
1439 | |||
1440 | // Create the database prefix for this test.
|
||
1441 | $this->prepareDatabasePrefix();
|
||
1442 | |||
1443 | // Prepare the environment for running tests.
|
||
1444 | $this->prepareEnvironment();
|
||
1445 | if (!$this->setupEnvironment) { |
||
1446 | return FALSE; |
||
1447 | } |
||
1448 | |||
1449 | // Reset all statics and variables to perform tests in a clean environment.
|
||
1450 | $conf = array(); |
||
1451 | drupal_static_reset(); |
||
1452 | |||
1453 | // Change the database prefix.
|
||
1454 | // All static variables need to be reset before the database prefix is
|
||
1455 | // changed, since DrupalCacheArray implementations attempt to
|
||
1456 | // write back to persistent caches when they are destructed.
|
||
1457 | $this->changeDatabasePrefix();
|
||
1458 | if (!$this->setupDatabasePrefix) { |
||
1459 | return FALSE; |
||
1460 | } |
||
1461 | |||
1462 | // Preset the 'install_profile' system variable, so the first call into
|
||
1463 | // system_rebuild_module_data() (in drupal_install_system()) will register
|
||
1464 | // the test's profile as a module. Without this, the installation profile of
|
||
1465 | // the parent site (executing the test) is registered, and the test
|
||
1466 | // profile's hook_install() and other hook implementations are never invoked.
|
||
1467 | $conf['install_profile'] = $this->profile; |
||
1468 | |||
1469 | // Perform the actual Drupal installation.
|
||
1470 | include_once DRUPAL_ROOT . '/includes/install.inc'; |
||
1471 | drupal_install_system(); |
||
1472 | |||
1473 | $this->preloadRegistry();
|
||
1474 | |||
1475 | // Set path variables.
|
||
1476 | variable_set('file_public_path', $this->public_files_directory); |
||
1477 | variable_set('file_private_path', $this->private_files_directory); |
||
1478 | variable_set('file_temporary_path', $this->temp_files_directory); |
||
1479 | |||
1480 | // Set the 'simpletest_parent_profile' variable to add the parent profile's
|
||
1481 | // search path to the child site's search paths.
|
||
1482 | // @see drupal_system_listing()
|
||
1483 | // @todo This may need to be primed like 'install_profile' above.
|
||
1484 | variable_set('simpletest_parent_profile', $this->originalProfile); |
||
1485 | |||
1486 | // Include the testing profile.
|
||
1487 | variable_set('install_profile', $this->profile); |
||
1488 | $profile_details = install_profile_info($this->profile, 'en'); |
||
1489 | |||
1490 | // Install the modules specified by the testing profile.
|
||
1491 | module_enable($profile_details['dependencies'], FALSE); |
||
1492 | |||
1493 | // Install modules needed for this test. This could have been passed in as
|
||
1494 | // either a single array argument or a variable number of string arguments.
|
||
1495 | // @todo Remove this compatibility layer in Drupal 8, and only accept
|
||
1496 | // $modules as a single array argument.
|
||
1497 | $modules = func_get_args(); |
||
1498 | if (isset($modules[0]) && is_array($modules[0])) { |
||
1499 | $modules = $modules[0]; |
||
1500 | } |
||
1501 | if ($modules) { |
||
1502 | $success = module_enable($modules, TRUE); |
||
1503 | $this->assertTrue($success, t('Enabled modules: %modules', array('%modules' => implode(', ', $modules)))); |
||
1504 | } |
||
1505 | |||
1506 | // Run the profile tasks.
|
||
1507 | $install_profile_module_exists = db_query("SELECT 1 FROM {system} WHERE type = 'module' AND name = :name", array( |
||
1508 | ':name' => $this->profile, |
||
1509 | ))->fetchField(); |
||
1510 | if ($install_profile_module_exists) { |
||
1511 | module_enable(array($this->profile), FALSE); |
||
1512 | } |
||
1513 | |||
1514 | // Reset/rebuild all data structures after enabling the modules.
|
||
1515 | $this->resetAll();
|
||
1516 | |||
1517 | // Run cron once in that environment, as install.php does at the end of
|
||
1518 | // the installation process.
|
||
1519 | drupal_cron_run(); |
||
1520 | |||
1521 | // Ensure that the session is not written to the new environment and replace
|
||
1522 | // the global $user session with uid 1 from the new test site.
|
||
1523 | drupal_save_session(FALSE);
|
||
1524 | // Login as uid 1.
|
||
1525 | $user = user_load(1); |
||
1526 | |||
1527 | // Restore necessary variables.
|
||
1528 | variable_set('install_task', 'done'); |
||
1529 | variable_set('clean_url', $this->originalCleanUrl); |
||
1530 | variable_set('site_mail', 'simpletest@example.com'); |
||
1531 | variable_set('date_default_timezone', date_default_timezone_get());
|
||
1532 | |||
1533 | // Set up English language.
|
||
1534 | unset($conf['language_default']); |
||
1535 | $language = language_default();
|
||
1536 | |||
1537 | // Use the test mail class instead of the default mail handler class.
|
||
1538 | variable_set('mail_system', array('default-system' => 'TestingMailSystem')); |
||
1539 | |||
1540 | drupal_set_time_limit($this->timeLimit);
|
||
1541 | $this->setup = TRUE; |
||
1542 | } |
||
1543 | |||
1544 | /**
|
||
1545 | * Preload the registry from the testing site.
|
||
1546 | *
|
||
1547 | * This method is called by DrupalWebTestCase::setUp(), and preloads the
|
||
1548 | * registry from the testing site to cut down on the time it takes to
|
||
1549 | * set up a clean environment for the current test run.
|
||
1550 | */
|
||
1551 | protected function preloadRegistry() { |
||
1552 | // Use two separate queries, each with their own connections: copy the
|
||
1553 | // {registry} and {registry_file} tables over from the parent installation
|
||
1554 | // to the child installation.
|
||
1555 | $original_connection = Database::getConnection('default', 'simpletest_original_default'); |
||
1556 | $test_connection = Database::getConnection(); |
||
1557 | |||
1558 | foreach (array('registry', 'registry_file') as $table) { |
||
1559 | // Find the records from the parent database.
|
||
1560 | $source_query = $original_connection |
||
1561 | ->select($table, array(), array('fetch' => PDO::FETCH_ASSOC)) |
||
1562 | ->fields($table);
|
||
1563 | |||
1564 | $dest_query = $test_connection->insert($table); |
||
1565 | |||
1566 | $first = TRUE; |
||
1567 | foreach ($source_query->execute() as $row) { |
||
1568 | if ($first) { |
||
1569 | $dest_query->fields(array_keys($row)); |
||
1570 | $first = FALSE; |
||
1571 | } |
||
1572 | // Insert the records into the child database.
|
||
1573 | $dest_query->values($row); |
||
1574 | } |
||
1575 | |||
1576 | $dest_query->execute();
|
||
1577 | } |
||
1578 | } |
||
1579 | |||
1580 | /**
|
||
1581 | * Reset all data structures after having enabled new modules.
|
||
1582 | *
|
||
1583 | * This method is called by DrupalWebTestCase::setUp() after enabling
|
||
1584 | * the requested modules. It must be called again when additional modules
|
||
1585 | * are enabled later.
|
||
1586 | */
|
||
1587 | protected function resetAll() { |
||
1588 | // Reset all static variables.
|
||
1589 | drupal_static_reset(); |
||
1590 | // Reset the list of enabled modules.
|
||
1591 | module_list(TRUE);
|
||
1592 | |||
1593 | // Reset cached schema for new database prefix. This must be done before
|
||
1594 | // drupal_flush_all_caches() so rebuilds can make use of the schema of
|
||
1595 | // modules enabled on the cURL side.
|
||
1596 | drupal_get_schema(NULL, TRUE); |
||
1597 | |||
1598 | // Perform rebuilds and flush remaining caches.
|
||
1599 | drupal_flush_all_caches(); |
||
1600 | |||
1601 | // Reload global $conf array and permissions.
|
||
1602 | $this->refreshVariables();
|
||
1603 | $this->checkPermissions(array(), TRUE); |
||
1604 | } |
||
1605 | |||
1606 | /**
|
||
1607 | * Refresh the in-memory set of variables. Useful after a page request is made
|
||
1608 | * that changes a variable in a different thread.
|
||
1609 | *
|
||
1610 | * In other words calling a settings page with $this->drupalPost() with a changed
|
||
1611 | * value would update a variable to reflect that change, but in the thread that
|
||
1612 | * made the call (thread running the test) the changed variable would not be
|
||
1613 | * picked up.
|
||
1614 | *
|
||
1615 | * This method clears the variables cache and loads a fresh copy from the database
|
||
1616 | * to ensure that the most up-to-date set of variables is loaded.
|
||
1617 | */
|
||
1618 | protected function refreshVariables() { |
||
1619 | global $conf; |
||
1620 | cache_clear_all('variables', 'cache_bootstrap'); |
||
1621 | $conf = variable_initialize();
|
||
1622 | } |
||
1623 | |||
1624 | /**
|
||
1625 | * Delete created files and temporary files directory, delete the tables created by setUp(),
|
||
1626 | * and reset the database prefix.
|
||
1627 | */
|
||
1628 | protected function tearDown() { |
||
1629 | global $user, $language; |
||
1630 | |||
1631 | // In case a fatal error occurred that was not in the test process read the
|
||
1632 | // log to pick up any fatal errors.
|
||
1633 | simpletest_log_read($this->testId, $this->databasePrefix, get_class($this), TRUE); |
||
1634 | |||
1635 | $emailCount = count(variable_get('drupal_test_email_collector', array())); |
||
1636 | if ($emailCount) { |
||
1637 | $message = format_plural($emailCount, '1 e-mail was sent during this test.', '@count e-mails were sent during this test.'); |
||
1638 | $this->pass($message, t('E-mail')); |
||
1639 | } |
||
1640 | |||
1641 | // Delete temporary files directory.
|
||
1642 | file_unmanaged_delete_recursive($this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10)); |
||
1643 | |||
1644 | // Remove all prefixed tables.
|
||
1645 | $tables = db_find_tables($this->databasePrefix . '%'); |
||
1646 | $connection_info = Database::getConnectionInfo('default'); |
||
1647 | $tables = db_find_tables($connection_info['default']['prefix']['default'] . '%'); |
||
1648 | if (empty($tables)) { |
||
1649 | $this->fail('Failed to find test tables to drop.'); |
||
1650 | } |
||
1651 | $prefix_length = strlen($connection_info['default']['prefix']['default']); |
||
1652 | foreach ($tables as $table) { |
||
1653 | if (db_drop_table(substr($table, $prefix_length))) { |
||
1654 | unset($tables[$table]); |
||
1655 | } |
||
1656 | } |
||
1657 | if (!empty($tables)) { |
||
1658 | $this->fail('Failed to drop all prefixed tables.'); |
||
1659 | } |
||
1660 | |||
1661 | // Get back to the original connection.
|
||
1662 | Database::removeConnection('default'); |
||
1663 | Database::renameConnection('simpletest_original_default', 'default'); |
||
1664 | |||
1665 | // Restore original shutdown callbacks array to prevent original
|
||
1666 | // environment of calling handlers from test run.
|
||
1667 | $callbacks = &drupal_register_shutdown_function();
|
||
1668 | $callbacks = $this->originalShutdownCallbacks; |
||
1669 | |||
1670 | // Return the user to the original one.
|
||
1671 | $user = $this->originalUser; |
||
1672 | drupal_save_session(TRUE);
|
||
1673 | |||
1674 | // Ensure that internal logged in variable and cURL options are reset.
|
||
1675 | $this->loggedInUser = FALSE; |
||
1676 | $this->additionalCurlOptions = array(); |
||
1677 | |||
1678 | // Reload module list and implementations to ensure that test module hooks
|
||
1679 | // aren't called after tests.
|
||
1680 | module_list(TRUE);
|
||
1681 | module_implements('', FALSE, TRUE); |
||
1682 | |||
1683 | // Reset the Field API.
|
||
1684 | field_cache_clear(); |
||
1685 | |||
1686 | // Rebuild caches.
|
||
1687 | $this->refreshVariables();
|
||
1688 | |||
1689 | // Reset public files directory.
|
||
1690 | $GLOBALS['conf']['file_public_path'] = $this->originalFileDirectory; |
||
1691 | |||
1692 | // Reset language.
|
||
1693 | $language = $this->originalLanguage; |
||
1694 | if ($this->originalLanguageDefault) { |
||
1695 | $GLOBALS['conf']['language_default'] = $this->originalLanguageDefault; |
||
1696 | } |
||
1697 | |||
1698 | // Close the CURL handler.
|
||
1699 | $this->curlClose();
|
||
1700 | } |
||
1701 | |||
1702 | /**
|
||
1703 | * Initializes the cURL connection.
|
||
1704 | *
|
||
1705 | * If the simpletest_httpauth_credentials variable is set, this function will
|
||
1706 | * add HTTP authentication headers. This is necessary for testing sites that
|
||
1707 | * are protected by login credentials from public access.
|
||
1708 | * See the description of $curl_options for other options.
|
||
1709 | */
|
||
1710 | protected function curlInitialize() { |
||
1711 | global $base_url; |
||
1712 | |||
1713 | if (!isset($this->curlHandle)) { |
||
1714 | $this->curlHandle = curl_init();
|
||
1715 | |||
1716 | // Some versions/configurations of cURL break on a NULL cookie jar, so
|
||
1717 | // supply a real file.
|
||
1718 | if (empty($this->cookieFile)) { |
||
1719 | $this->cookieFile = $this->public_files_directory . '/cookie.jar'; |
||
1720 | } |
||
1721 | |||
1722 | $curl_options = array( |
||
1723 | CURLOPT_COOKIEJAR => $this->cookieFile, |
||
1724 | CURLOPT_URL => $base_url, |
||
1725 | CURLOPT_FOLLOWLOCATION => FALSE, |
||
1726 | CURLOPT_RETURNTRANSFER => TRUE, |
||
1727 | CURLOPT_SSL_VERIFYPEER => FALSE, // Required to make the tests run on HTTPS. |
||
1728 | CURLOPT_SSL_VERIFYHOST => FALSE, // Required to make the tests run on HTTPS. |
||
1729 | CURLOPT_HEADERFUNCTION => array(&$this, 'curlHeaderCallback'), |
||
1730 | CURLOPT_USERAGENT => $this->databasePrefix, |
||
1731 | ); |
||
1732 | if (isset($this->httpauth_credentials)) { |
||
1733 | $curl_options[CURLOPT_HTTPAUTH] = $this->httpauth_method; |
||
1734 | $curl_options[CURLOPT_USERPWD] = $this->httpauth_credentials; |
||
1735 | } |
||
1736 | // curl_setopt_array() returns FALSE if any of the specified options
|
||
1737 | // cannot be set, and stops processing any further options.
|
||
1738 | $result = curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options); |
||
1739 | if (!$result) { |
||
1740 | throw new Exception('One or more cURL options could not be set.'); |
||
1741 | } |
||
1742 | |||
1743 | // By default, the child session name should be the same as the parent.
|
||
1744 | $this->session_name = session_name();
|
||
1745 | } |
||
1746 | // We set the user agent header on each request so as to use the current
|
||
1747 | // time and a new uniqid.
|
||
1748 | if (preg_match('/simpletest\d+/', $this->databasePrefix, $matches)) { |
||
1749 | curl_setopt($this->curlHandle, CURLOPT_USERAGENT, drupal_generate_test_ua($matches[0])); |
||
1750 | } |
||
1751 | } |
||
1752 | |||
1753 | /**
|
||
1754 | * Initializes and executes a cURL request.
|
||
1755 | *
|
||
1756 | * @param $curl_options
|
||
1757 | * An associative array of cURL options to set, where the keys are constants
|
||
1758 | * defined by the cURL library. For a list of valid options, see
|
||
1759 | * http://www.php.net/manual/function.curl-setopt.php
|
||
1760 | * @param $redirect
|
||
1761 | * FALSE if this is an initial request, TRUE if this request is the result
|
||
1762 | * of a redirect.
|
||
1763 | *
|
||
1764 | * @return
|
||
1765 | * The content returned from the call to curl_exec().
|
||
1766 | *
|
||
1767 | * @see curlInitialize()
|
||
1768 | */
|
||
1769 | protected function curlExec($curl_options, $redirect = FALSE) { |
||
1770 | $this->curlInitialize();
|
||
1771 | |||
1772 | 6ff32cea | Florent Torregrosa | if (!empty($curl_options[CURLOPT_URL])) { |
1773 | // Forward XDebug activation if present.
|
||
1774 | if (isset($_COOKIE['XDEBUG_SESSION'])) { |
||
1775 | $options = drupal_parse_url($curl_options[CURLOPT_URL]); |
||
1776 | $options += array('query' => array()); |
||
1777 | $options['query'] += array('XDEBUG_SESSION_START' => $_COOKIE['XDEBUG_SESSION']); |
||
1778 | $curl_options[CURLOPT_URL] = url($options['path'], $options); |
||
1779 | } |
||
1780 | |||
1781 | // cURL incorrectly handles URLs with a fragment by including the
|
||
1782 | // fragment in the request to the server, causing some web servers
|
||
1783 | // to reject the request citing "400 - Bad Request". To prevent
|
||
1784 | // this, we strip the fragment from the request.
|
||
1785 | // TODO: Remove this for Drupal 8, since fixed in curl 7.20.0.
|
||
1786 | if (strpos($curl_options[CURLOPT_URL], '#')) { |
||
1787 | $original_url = $curl_options[CURLOPT_URL]; |
||
1788 | $curl_options[CURLOPT_URL] = strtok($curl_options[CURLOPT_URL], '#'); |
||
1789 | } |
||
1790 | 85ad3d82 | Assos Assos | } |
1791 | |||
1792 | $url = empty($curl_options[CURLOPT_URL]) ? curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL) : $curl_options[CURLOPT_URL]; |
||
1793 | |||
1794 | if (!empty($curl_options[CURLOPT_POST])) { |
||
1795 | // This is a fix for the Curl library to prevent Expect: 100-continue
|
||
1796 | // headers in POST requests, that may cause unexpected HTTP response
|
||
1797 | // codes from some webservers (like lighttpd that returns a 417 error
|
||
1798 | // code). It is done by setting an empty "Expect" header field that is
|
||
1799 | // not overwritten by Curl.
|
||
1800 | $curl_options[CURLOPT_HTTPHEADER][] = 'Expect:'; |
||
1801 | } |
||
1802 | curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options); |
||
1803 | |||
1804 | if (!$redirect) { |
||
1805 | // Reset headers, the session ID and the redirect counter.
|
||
1806 | $this->session_id = NULL; |
||
1807 | $this->headers = array(); |
||
1808 | $this->redirect_count = 0; |
||
1809 | } |
||
1810 | |||
1811 | $content = curl_exec($this->curlHandle); |
||
1812 | $status = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE); |
||
1813 | |||
1814 | // cURL incorrectly handles URLs with fragments, so instead of
|
||
1815 | // letting cURL handle redirects we take of them ourselves to
|
||
1816 | // to prevent fragments being sent to the web server as part
|
||
1817 | // of the request.
|
||
1818 | // TODO: Remove this for Drupal 8, since fixed in curl 7.20.0.
|
||
1819 | if (in_array($status, array(300, 301, 302, 303, 305, 307)) && $this->redirect_count < variable_get('simpletest_maximum_redirects', 5)) { |
||
1820 | if ($this->drupalGetHeader('location')) { |
||
1821 | $this->redirect_count++;
|
||
1822 | $curl_options = array(); |
||
1823 | $curl_options[CURLOPT_URL] = $this->drupalGetHeader('location'); |
||
1824 | $curl_options[CURLOPT_HTTPGET] = TRUE; |
||
1825 | return $this->curlExec($curl_options, TRUE); |
||
1826 | } |
||
1827 | } |
||
1828 | |||
1829 | $this->drupalSetContent($content, isset($original_url) ? $original_url : curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL)); |
||
1830 | $message_vars = array( |
||
1831 | '!method' => !empty($curl_options[CURLOPT_NOBODY]) ? 'HEAD' : (empty($curl_options[CURLOPT_POSTFIELDS]) ? 'GET' : 'POST'), |
||
1832 | '@url' => isset($original_url) ? $original_url : $url, |
||
1833 | '@status' => $status, |
||
1834 | '!length' => format_size(strlen($this->drupalGetContent())) |
||
1835 | ); |
||
1836 | $message = t('!method @url returned @status (!length).', $message_vars); |
||
1837 | $this->assertTrue($this->drupalGetContent() !== FALSE, $message, t('Browser')); |
||
1838 | return $this->drupalGetContent(); |
||
1839 | } |
||
1840 | |||
1841 | /**
|
||
1842 | * Reads headers and registers errors received from the tested site.
|
||
1843 | *
|
||
1844 | * @see _drupal_log_error().
|
||
1845 | *
|
||
1846 | * @param $curlHandler
|
||
1847 | * The cURL handler.
|
||
1848 | * @param $header
|
||
1849 | * An header.
|
||
1850 | */
|
||
1851 | protected function curlHeaderCallback($curlHandler, $header) { |
||
1852 | // Header fields can be extended over multiple lines by preceding each
|
||
1853 | // extra line with at least one SP or HT. They should be joined on receive.
|
||
1854 | // Details are in RFC2616 section 4.
|
||
1855 | if ($header[0] == ' ' || $header[0] == "\t") { |
||
1856 | // Normalize whitespace between chucks.
|
||
1857 | $this->headers[] = array_pop($this->headers) . ' ' . trim($header); |
||
1858 | } |
||
1859 | else {
|
||
1860 | $this->headers[] = $header; |
||
1861 | } |
||
1862 | |||
1863 | // Errors are being sent via X-Drupal-Assertion-* headers,
|
||
1864 | // generated by _drupal_log_error() in the exact form required
|
||
1865 | // by DrupalWebTestCase::error().
|
||
1866 | if (preg_match('/^X-Drupal-Assertion-[0-9]+: (.*)$/', $header, $matches)) { |
||
1867 | // Call DrupalWebTestCase::error() with the parameters from the header.
|
||
1868 | call_user_func_array(array(&$this, 'error'), unserialize(urldecode($matches[1]))); |
||
1869 | } |
||
1870 | |||
1871 | // Save cookies.
|
||
1872 | if (preg_match('/^Set-Cookie: ([^=]+)=(.+)/', $header, $matches)) { |
||
1873 | $name = $matches[1]; |
||
1874 | $parts = array_map('trim', explode(';', $matches[2])); |
||
1875 | $value = array_shift($parts); |
||
1876 | $this->cookies[$name] = array('value' => $value, 'secure' => in_array('secure', $parts)); |
||
1877 | if ($name == $this->session_name) { |
||
1878 | if ($value != 'deleted') { |
||
1879 | $this->session_id = $value; |
||
1880 | } |
||
1881 | else {
|
||
1882 | $this->session_id = NULL; |
||
1883 | } |
||
1884 | } |
||
1885 | } |
||
1886 | |||
1887 | // This is required by cURL.
|
||
1888 | return strlen($header); |
||
1889 | } |
||
1890 | |||
1891 | /**
|
||
1892 | * Close the cURL handler and unset the handler.
|
||
1893 | */
|
||
1894 | protected function curlClose() { |
||
1895 | if (isset($this->curlHandle)) { |
||
1896 | curl_close($this->curlHandle);
|
||
1897 | unset($this->curlHandle); |
||
1898 | } |
||
1899 | } |
||
1900 | |||
1901 | /**
|
||
1902 | * Parse content returned from curlExec using DOM and SimpleXML.
|
||
1903 | *
|
||
1904 | * @return
|
||
1905 | * A SimpleXMLElement or FALSE on failure.
|
||
1906 | */
|
||
1907 | protected function parse() { |
||
1908 | if (!$this->elements) { |
||
1909 | // DOM can load HTML soup. But, HTML soup can throw warnings, suppress
|
||
1910 | // them.
|
||
1911 | $htmlDom = new DOMDocument(); |
||
1912 | @$htmlDom->loadHTML($this->drupalGetContent()); |
||
1913 | if ($htmlDom) { |
||
1914 | $this->pass(t('Valid HTML found on "@path"', array('@path' => $this->getUrl())), t('Browser')); |
||
1915 | // It's much easier to work with simplexml than DOM, luckily enough
|
||
1916 | // we can just simply import our DOM tree.
|
||
1917 | $this->elements = simplexml_import_dom($htmlDom); |
||
1918 | } |
||
1919 | } |
||
1920 | if (!$this->elements) { |
||
1921 | $this->fail(t('Parsed page successfully.'), t('Browser')); |
||
1922 | } |
||
1923 | |||
1924 | return $this->elements; |
||
1925 | } |
||
1926 | |||
1927 | /**
|
||
1928 | * Retrieves a Drupal path or an absolute path.
|
||
1929 | *
|
||
1930 | * @param $path
|
||
1931 | * Drupal path or URL to load into internal browser
|
||
1932 | * @param $options
|
||
1933 | * Options to be forwarded to url().
|
||
1934 | * @param $headers
|
||
1935 | * An array containing additional HTTP request headers, each formatted as
|
||
1936 | * "name: value".
|
||
1937 | * @return
|
||
1938 | * The retrieved HTML string, also available as $this->drupalGetContent()
|
||
1939 | */
|
||
1940 | protected function drupalGet($path, array $options = array(), array $headers = array()) { |
||
1941 | $options['absolute'] = TRUE; |
||
1942 | |||
1943 | // We re-using a CURL connection here. If that connection still has certain
|
||
1944 | // options set, it might change the GET into a POST. Make sure we clear out
|
||
1945 | // previous options.
|
||
1946 | $out = $this->curlExec(array(CURLOPT_HTTPGET => TRUE, CURLOPT_URL => url($path, $options), CURLOPT_NOBODY => FALSE, CURLOPT_HTTPHEADER => $headers)); |
||
1947 | $this->refreshVariables(); // Ensure that any changes to variables in the other thread are picked up. |
||
1948 | |||
1949 | // Replace original page output with new output from redirected page(s).
|
||
1950 | if ($new = $this->checkForMetaRefresh()) { |
||
1951 | $out = $new; |
||
1952 | } |
||
1953 | $this->verbose('GET request to: ' . $path . |
||
1954 | '<hr />Ending URL: ' . $this->getUrl() . |
||
1955 | '<hr />' . $out); |
||
1956 | return $out; |
||
1957 | } |
||
1958 | |||
1959 | /**
|
||
1960 | * Retrieve a Drupal path or an absolute path and JSON decode the result.
|
||
1961 | */
|
||
1962 | protected function drupalGetAJAX($path, array $options = array(), array $headers = array()) { |
||
1963 | return drupal_json_decode($this->drupalGet($path, $options, $headers)); |
||
1964 | } |
||
1965 | |||
1966 | /**
|
||
1967 | * Execute a POST request on a Drupal page.
|
||
1968 | * It will be done as usual POST request with SimpleBrowser.
|
||
1969 | *
|
||
1970 | * @param $path
|
||
1971 | * Location of the post form. Either a Drupal path or an absolute path or
|
||
1972 | * NULL to post to the current page. For multi-stage forms you can set the
|
||
1973 | * path to NULL and have it post to the last received page. Example:
|
||
1974 | *
|
||
1975 | * @code
|
||
1976 | * // First step in form.
|
||
1977 | * $edit = array(...);
|
||
1978 | * $this->drupalPost('some_url', $edit, t('Save'));
|
||
1979 | *
|
||
1980 | * // Second step in form.
|
||
1981 | * $edit = array(...);
|
||
1982 | * $this->drupalPost(NULL, $edit, t('Save'));
|
||
1983 | * @endcode
|
||
1984 | * @param $edit
|
||
1985 | * Field data in an associative array. Changes the current input fields
|
||
1986 | * (where possible) to the values indicated. A checkbox can be set to
|
||
1987 | * TRUE to be checked and FALSE to be unchecked. Note that when a form
|
||
1988 | * contains file upload fields, other fields cannot start with the '@'
|
||
1989 | * character.
|
||
1990 | *
|
||
1991 | * Multiple select fields can be set using name[] and setting each of the
|
||
1992 | * possible values. Example:
|
||
1993 | * @code
|
||
1994 | * $edit = array();
|
||
1995 | * $edit['name[]'] = array('value1', 'value2');
|
||
1996 | * @endcode
|
||
1997 | * @param $submit
|
||
1998 | * Value of the submit button whose click is to be emulated. For example,
|
||
1999 | * t('Save'). The processing of the request depends on this value. For
|
||
2000 | * example, a form may have one button with the value t('Save') and another
|
||
2001 | * button with the value t('Delete'), and execute different code depending
|
||
2002 | * on which one is clicked.
|
||
2003 | *
|
||
2004 | * This function can also be called to emulate an Ajax submission. In this
|
||
2005 | * case, this value needs to be an array with the following keys:
|
||
2006 | * - path: A path to submit the form values to for Ajax-specific processing,
|
||
2007 | * which is likely different than the $path parameter used for retrieving
|
||
2008 | * the initial form. Defaults to 'system/ajax'.
|
||
2009 | * - triggering_element: If the value for the 'path' key is 'system/ajax' or
|
||
2010 | * another generic Ajax processing path, this needs to be set to the name
|
||
2011 | * of the element. If the name doesn't identify the element uniquely, then
|
||
2012 | * this should instead be an array with a single key/value pair,
|
||
2013 | * corresponding to the element name and value. The callback for the
|
||
2014 | * generic Ajax processing path uses this to find the #ajax information
|
||
2015 | * for the element, including which specific callback to use for
|
||
2016 | * processing the request.
|
||
2017 | *
|
||
2018 | * This can also be set to NULL in order to emulate an Internet Explorer
|
||
2019 | * submission of a form with a single text field, and pressing ENTER in that
|
||
2020 | * textfield: under these conditions, no button information is added to the
|
||
2021 | * POST data.
|
||
2022 | * @param $options
|
||
2023 | * Options to be forwarded to url().
|
||
2024 | * @param $headers
|
||
2025 | * An array containing additional HTTP request headers, each formatted as
|
||
2026 | * "name: value".
|
||
2027 | * @param $form_html_id
|
||
2028 | * (optional) HTML ID of the form to be submitted. On some pages
|
||
2029 | * there are many identical forms, so just using the value of the submit
|
||
2030 | * button is not enough. For example: 'trigger-node-presave-assign-form'.
|
||
2031 | * Note that this is not the Drupal $form_id, but rather the HTML ID of the
|
||
2032 | * form, which is typically the same thing but with hyphens replacing the
|
||
2033 | * underscores.
|
||
2034 | * @param $extra_post
|
||
2035 | * (optional) A string of additional data to append to the POST submission.
|
||
2036 | * This can be used to add POST data for which there are no HTML fields, as
|
||
2037 | * is done by drupalPostAJAX(). This string is literally appended to the
|
||
2038 | * POST data, so it must already be urlencoded and contain a leading "&"
|
||
2039 | * (e.g., "&extra_var1=hello+world&extra_var2=you%26me").
|
||
2040 | */
|
||
2041 | protected function drupalPost($path, $edit, $submit, array $options = array(), array $headers = array(), $form_html_id = NULL, $extra_post = NULL) { |
||
2042 | $submit_matches = FALSE; |
||
2043 | $ajax = is_array($submit); |
||
2044 | if (isset($path)) { |
||
2045 | $this->drupalGet($path, $options); |
||
2046 | } |
||
2047 | if ($this->parse()) { |
||
2048 | $edit_save = $edit; |
||
2049 | // Let's iterate over all the forms.
|
||
2050 | $xpath = "//form"; |
||
2051 | if (!empty($form_html_id)) { |
||
2052 | $xpath .= "[@id='" . $form_html_id . "']"; |
||
2053 | } |
||
2054 | $forms = $this->xpath($xpath); |
||
2055 | foreach ($forms as $form) { |
||
2056 | // We try to set the fields of this form as specified in $edit.
|
||
2057 | $edit = $edit_save; |
||
2058 | $post = array(); |
||
2059 | $upload = array(); |
||
2060 | $submit_matches = $this->handleForm($post, $edit, $upload, $ajax ? NULL : $submit, $form); |
||
2061 | $action = isset($form['action']) ? $this->getAbsoluteUrl((string) $form['action']) : $this->getUrl(); |
||
2062 | if ($ajax) { |
||
2063 | $action = $this->getAbsoluteUrl(!empty($submit['path']) ? $submit['path'] : 'system/ajax'); |
||
2064 | // Ajax callbacks verify the triggering element if necessary, so while
|
||
2065 | // we may eventually want extra code that verifies it in the
|
||
2066 | // handleForm() function, it's not currently a requirement.
|
||
2067 | $submit_matches = TRUE; |
||
2068 | } |
||
2069 | |||
2070 | // We post only if we managed to handle every field in edit and the
|
||
2071 | // submit button matches.
|
||
2072 | if (!$edit && ($submit_matches || !isset($submit))) { |
||
2073 | $post_array = $post; |
||
2074 | if ($upload) { |
||
2075 | // TODO: cURL handles file uploads for us, but the implementation
|
||
2076 | // is broken. This is a less than elegant workaround. Alternatives
|
||
2077 | // are being explored at #253506.
|
||
2078 | foreach ($upload as $key => $file) { |
||
2079 | $file = drupal_realpath($file); |
||
2080 | if ($file && is_file($file)) { |
||
2081 | // Use the new CurlFile class for file uploads when using PHP
|
||
2082 | // 5.5 or higher.
|
||
2083 | if (class_exists('CurlFile')) { |
||
2084 | $post[$key] = curl_file_create($file); |
||
2085 | } |
||
2086 | else {
|
||
2087 | $post[$key] = '@' . $file; |
||
2088 | } |
||
2089 | } |
||
2090 | } |
||
2091 | } |
||
2092 | else {
|
||
2093 | foreach ($post as $key => $value) { |
||
2094 | // Encode according to application/x-www-form-urlencoded
|
||
2095 | // Both names and values needs to be urlencoded, according to
|
||
2096 | // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
|
||
2097 | $post[$key] = urlencode($key) . '=' . urlencode($value); |
||
2098 | } |
||
2099 | $post = implode('&', $post) . $extra_post; |
||
2100 | } |
||
2101 | $out = $this->curlExec(array(CURLOPT_URL => $action, CURLOPT_POST => TRUE, CURLOPT_POSTFIELDS => $post, CURLOPT_HTTPHEADER => $headers)); |
||
2102 | // Ensure that any changes to variables in the other thread are picked up.
|
||
2103 | $this->refreshVariables();
|
||
2104 | |||
2105 | // Replace original page output with new output from redirected page(s).
|
||
2106 | if ($new = $this->checkForMetaRefresh()) { |
||
2107 | $out = $new; |
||
2108 | } |
||
2109 | $this->verbose('POST request to: ' . $path . |
||
2110 | '<hr />Ending URL: ' . $this->getUrl() . |
||
2111 | '<hr />Fields: ' . highlight_string('<?php ' . var_export($post_array, TRUE), TRUE) . |
||
2112 | '<hr />' . $out); |
||
2113 | return $out; |
||
2114 | } |
||
2115 | } |
||
2116 | // We have not found a form which contained all fields of $edit.
|
||
2117 | foreach ($edit as $name => $value) { |
||
2118 | $this->fail(t('Failed to set field @name to @value', array('@name' => $name, '@value' => $value))); |
||
2119 | } |
||
2120 | if (!$ajax && isset($submit)) { |
||
2121 | $this->assertTrue($submit_matches, t('Found the @submit button', array('@submit' => $submit))); |
||
2122 | } |
||
2123 | $this->fail(t('Found the requested form fields at @path', array('@path' => $path))); |
||
2124 | } |
||
2125 | } |
||
2126 | |||
2127 | /**
|
||
2128 | * Execute an Ajax submission.
|
||
2129 | *
|
||
2130 | * This executes a POST as ajax.js does. It uses the returned JSON data, an
|
||
2131 | * array of commands, to update $this->content using equivalent DOM
|
||
2132 | * manipulation as is used by ajax.js. It also returns the array of commands.
|
||
2133 | *
|
||
2134 | * @param $path
|
||
2135 | * Location of the form containing the Ajax enabled element to test. Can be
|
||
2136 | * either a Drupal path or an absolute path or NULL to use the current page.
|
||
2137 | * @param $edit
|
||
2138 | * Field data in an associative array. Changes the current input fields
|
||
2139 | * (where possible) to the values indicated.
|
||
2140 | * @param $triggering_element
|
||
2141 | * The name of the form element that is responsible for triggering the Ajax
|
||
2142 | * functionality to test. May be a string or, if the triggering element is
|
||
2143 | * a button, an associative array where the key is the name of the button
|
||
2144 | * and the value is the button label. i.e.) array('op' => t('Refresh')).
|
||
2145 | * @param $ajax_path
|
||
2146 | * (optional) Override the path set by the Ajax settings of the triggering
|
||
2147 | * element. In the absence of both the triggering element's Ajax path and
|
||
2148 | * $ajax_path 'system/ajax' will be used.
|
||
2149 | * @param $options
|
||
2150 | * (optional) Options to be forwarded to url().
|
||
2151 | * @param $headers
|
||
2152 | * (optional) An array containing additional HTTP request headers, each
|
||
2153 | * formatted as "name: value". Forwarded to drupalPost().
|
||
2154 | * @param $form_html_id
|
||
2155 | * (optional) HTML ID of the form to be submitted, use when there is more
|
||
2156 | * than one identical form on the same page and the value of the triggering
|
||
2157 | * element is not enough to identify the form. Note this is not the Drupal
|
||
2158 | * ID of the form but rather the HTML ID of the form.
|
||
2159 | * @param $ajax_settings
|
||
2160 | * (optional) An array of Ajax settings which if specified will be used in
|
||
2161 | * place of the Ajax settings of the triggering element.
|
||
2162 | *
|
||
2163 | * @return
|
||
2164 | * An array of Ajax commands.
|
||
2165 | *
|
||
2166 | * @see drupalPost()
|
||
2167 | * @see ajax.js
|
||
2168 | */
|
||
2169 | protected function drupalPostAJAX($path, $edit, $triggering_element, $ajax_path = NULL, array $options = array(), array $headers = array(), $form_html_id = NULL, $ajax_settings = NULL) { |
||
2170 | // Get the content of the initial page prior to calling drupalPost(), since
|
||
2171 | // drupalPost() replaces $this->content.
|
||
2172 | if (isset($path)) { |
||
2173 | $this->drupalGet($path, $options); |
||
2174 | } |
||
2175 | $content = $this->content; |
||
2176 | $drupal_settings = $this->drupalSettings; |
||
2177 | |||
2178 | // Get the Ajax settings bound to the triggering element.
|
||
2179 | if (!isset($ajax_settings)) { |
||
2180 | if (is_array($triggering_element)) { |
||
2181 | $xpath = '//*[@name="' . key($triggering_element) . '" and @value="' . current($triggering_element) . '"]'; |
||
2182 | } |
||
2183 | else {
|
||
2184 | $xpath = '//*[@name="' . $triggering_element . '"]'; |
||
2185 | } |
||
2186 | if (isset($form_html_id)) { |
||
2187 | $xpath = '//form[@id="' . $form_html_id . '"]' . $xpath; |
||
2188 | } |
||
2189 | $element = $this->xpath($xpath); |
||
2190 | $element_id = (string) $element[0]['id']; |
||
2191 | $ajax_settings = $drupal_settings['ajax'][$element_id]; |
||
2192 | } |
||
2193 | |||
2194 | // Add extra information to the POST data as ajax.js does.
|
||
2195 | $extra_post = ''; |
||
2196 | if (isset($ajax_settings['submit'])) { |
||
2197 | foreach ($ajax_settings['submit'] as $key => $value) { |
||
2198 | $extra_post .= '&' . urlencode($key) . '=' . urlencode($value); |
||
2199 | } |
||
2200 | } |
||
2201 | foreach ($this->xpath('//*[@id]') as $element) { |
||
2202 | $id = (string) $element['id']; |
||
2203 | $extra_post .= '&' . urlencode('ajax_html_ids[]') . '=' . urlencode($id); |
||
2204 | } |
||
2205 | if (isset($drupal_settings['ajaxPageState'])) { |
||
2206 | $extra_post .= '&' . urlencode('ajax_page_state[theme]') . '=' . urlencode($drupal_settings['ajaxPageState']['theme']); |
||
2207 | $extra_post .= '&' . urlencode('ajax_page_state[theme_token]') . '=' . urlencode($drupal_settings['ajaxPageState']['theme_token']); |
||
2208 | foreach ($drupal_settings['ajaxPageState']['css'] as $key => $value) { |
||
2209 | $extra_post .= '&' . urlencode("ajax_page_state[css][$key]") . '=1'; |
||
2210 | } |
||
2211 | foreach ($drupal_settings['ajaxPageState']['js'] as $key => $value) { |
||
2212 | $extra_post .= '&' . urlencode("ajax_page_state[js][$key]") . '=1'; |
||
2213 | } |
||
2214 | } |
||
2215 | |||
2216 | // Unless a particular path is specified, use the one specified by the
|
||
2217 | // Ajax settings, or else 'system/ajax'.
|
||
2218 | if (!isset($ajax_path)) { |
||
2219 | $ajax_path = isset($ajax_settings['url']) ? $ajax_settings['url'] : 'system/ajax'; |
||
2220 | } |
||
2221 | |||
2222 | // Submit the POST request.
|
||
2223 | $return = drupal_json_decode($this->drupalPost(NULL, $edit, array('path' => $ajax_path, 'triggering_element' => $triggering_element), $options, $headers, $form_html_id, $extra_post)); |
||
2224 | |||
2225 | // Change the page content by applying the returned commands.
|
||
2226 | if (!empty($ajax_settings) && !empty($return)) { |
||
2227 | // ajax.js applies some defaults to the settings object, so do the same
|
||
2228 | // for what's used by this function.
|
||
2229 | $ajax_settings += array( |
||
2230 | 'method' => 'replaceWith', |
||
2231 | ); |
||
2232 | // DOM can load HTML soup. But, HTML soup can throw warnings, suppress
|
||
2233 | // them.
|
||
2234 | $dom = new DOMDocument(); |
||
2235 | @$dom->loadHTML($content); |
||
2236 | // XPath allows for finding wrapper nodes better than DOM does.
|
||
2237 | $xpath = new DOMXPath($dom); |
||
2238 | foreach ($return as $command) { |
||
2239 | switch ($command['command']) { |
||
2240 | case 'settings': |
||
2241 | $drupal_settings = drupal_array_merge_deep($drupal_settings, $command['settings']); |
||
2242 | break;
|
||
2243 | |||
2244 | case 'insert': |
||
2245 | $wrapperNode = NULL; |
||
2246 | // When a command doesn't specify a selector, use the
|
||
2247 | // #ajax['wrapper'] which is always an HTML ID.
|
||
2248 | if (!isset($command['selector'])) { |
||
2249 | $wrapperNode = $xpath->query('//*[@id="' . $ajax_settings['wrapper'] . '"]')->item(0); |
||
2250 | } |
||
2251 | // @todo Ajax commands can target any jQuery selector, but these are
|
||
2252 | // hard to fully emulate with XPath. For now, just handle 'head'
|
||
2253 | // and 'body', since these are used by ajax_render().
|
||
2254 | elseif (in_array($command['selector'], array('head', 'body'))) { |
||
2255 | $wrapperNode = $xpath->query('//' . $command['selector'])->item(0); |
||
2256 | } |
||
2257 | if ($wrapperNode) { |
||
2258 | // ajax.js adds an enclosing DIV to work around a Safari bug.
|
||
2259 | $newDom = new DOMDocument(); |
||
2260 | db2d93dd | Benjamin Luce | // DOM can load HTML soup. But, HTML soup can throw warnings,
|
2261 | // suppress them.
|
||
2262 | 85ad3d82 | Assos Assos | $newDom->loadHTML('<div>' . $command['data'] . '</div>'); |
2263 | db2d93dd | Benjamin Luce | // Suppress warnings thrown when duplicate HTML IDs are
|
2264 | // encountered. This probably means we are replacing an element
|
||
2265 | // with the same ID.
|
||
2266 | $newNode = @$dom->importNode($newDom->documentElement->firstChild->firstChild, TRUE); |
||
2267 | 85ad3d82 | Assos Assos | $method = isset($command['method']) ? $command['method'] : $ajax_settings['method']; |
2268 | // The "method" is a jQuery DOM manipulation function. Emulate
|
||
2269 | // each one using PHP's DOMNode API.
|
||
2270 | switch ($method) { |
||
2271 | case 'replaceWith': |
||
2272 | $wrapperNode->parentNode->replaceChild($newNode, $wrapperNode); |
||
2273 | break;
|
||
2274 | case 'append': |
||
2275 | $wrapperNode->appendChild($newNode); |
||
2276 | break;
|
||
2277 | case 'prepend': |
||
2278 | // If no firstChild, insertBefore() falls back to
|
||
2279 | // appendChild().
|
||
2280 | $wrapperNode->insertBefore($newNode, $wrapperNode->firstChild); |
||
2281 | break;
|
||
2282 | case 'before': |
||
2283 | $wrapperNode->parentNode->insertBefore($newNode, $wrapperNode); |
||
2284 | break;
|
||
2285 | case 'after': |
||
2286 | // If no nextSibling, insertBefore() falls back to
|
||
2287 | // appendChild().
|
||
2288 | $wrapperNode->parentNode->insertBefore($newNode, $wrapperNode->nextSibling); |
||
2289 | break;
|
||
2290 | case 'html': |
||
2291 | foreach ($wrapperNode->childNodes as $childNode) { |
||
2292 | $wrapperNode->removeChild($childNode); |
||
2293 | } |
||
2294 | $wrapperNode->appendChild($newNode); |
||
2295 | break;
|
||
2296 | } |
||
2297 | } |
||
2298 | break;
|
||
2299 | |||
2300 | 42e6daf3 | Julien Enselme | case 'updateBuildId': |
2301 | $buildId = $xpath->query('//input[@name="form_build_id" and @value="' . $command['old'] . '"]')->item(0); |
||
2302 | if ($buildId) { |
||
2303 | $buildId->setAttribute('value', $command['new']); |
||
2304 | } |
||
2305 | break;
|
||
2306 | |||
2307 | 85ad3d82 | Assos Assos | // @todo Add suitable implementations for these commands in order to
|
2308 | // have full test coverage of what ajax.js can do.
|
||
2309 | case 'remove': |
||
2310 | break;
|
||
2311 | case 'changed': |
||
2312 | break;
|
||
2313 | case 'css': |
||
2314 | break;
|
||
2315 | case 'data': |
||
2316 | break;
|
||
2317 | case 'restripe': |
||
2318 | break;
|
||
2319 | b4adf10d | Assos Assos | case 'add_css': |
2320 | break;
|
||
2321 | 85ad3d82 | Assos Assos | } |
2322 | } |
||
2323 | $content = $dom->saveHTML(); |
||
2324 | } |
||
2325 | $this->drupalSetContent($content); |
||
2326 | $this->drupalSetSettings($drupal_settings); |
||
2327 | 4444412d | Julien Enselme | |
2328 | $verbose = 'AJAX POST request to: ' . $path; |
||
2329 | $verbose .= '<br />AJAX callback path: ' . $ajax_path; |
||
2330 | $verbose .= '<hr />Ending URL: ' . $this->getUrl(); |
||
2331 | $verbose .= '<hr />' . $this->content; |
||
2332 | |||
2333 | $this->verbose($verbose); |
||
2334 | |||
2335 | 85ad3d82 | Assos Assos | return $return; |
2336 | } |
||
2337 | |||
2338 | /**
|
||
2339 | * Runs cron in the Drupal installed by Simpletest.
|
||
2340 | */
|
||
2341 | protected function cronRun() { |
||
2342 | $this->drupalGet($GLOBALS['base_url'] . '/cron.php', array('external' => TRUE, 'query' => array('cron_key' => variable_get('cron_key', 'drupal')))); |
||
2343 | } |
||
2344 | |||
2345 | /**
|
||
2346 | * Check for meta refresh tag and if found call drupalGet() recursively. This
|
||
2347 | * function looks for the http-equiv attribute to be set to "Refresh"
|
||
2348 | * and is case-sensitive.
|
||
2349 | *
|
||
2350 | * @return
|
||
2351 | * Either the new page content or FALSE.
|
||
2352 | */
|
||
2353 | protected function checkForMetaRefresh() { |
||
2354 | if (strpos($this->drupalGetContent(), '<meta ') && $this->parse()) { |
||
2355 | $refresh = $this->xpath('//meta[@http-equiv="Refresh"]'); |
||
2356 | if (!empty($refresh)) { |
||
2357 | // Parse the content attribute of the meta tag for the format:
|
||
2358 | // "[delay]: URL=[page_to_redirect_to]".
|
||
2359 | if (preg_match('/\d+;\s*URL=(?P<url>.*)/i', $refresh[0]['content'], $match)) { |
||
2360 | return $this->drupalGet($this->getAbsoluteUrl(decode_entities($match['url']))); |
||
2361 | } |
||
2362 | } |
||
2363 | } |
||
2364 | return FALSE; |
||
2365 | } |
||
2366 | |||
2367 | /**
|
||
2368 | * Retrieves only the headers for a Drupal path or an absolute path.
|
||
2369 | *
|
||
2370 | * @param $path
|
||
2371 | * Drupal path or URL to load into internal browser
|
||
2372 | * @param $options
|
||
2373 | * Options to be forwarded to url().
|
||
2374 | * @param $headers
|
||
2375 | * An array containing additional HTTP request headers, each formatted as
|
||
2376 | * "name: value".
|
||
2377 | * @return
|
||
2378 | * The retrieved headers, also available as $this->drupalGetContent()
|
||
2379 | */
|
||
2380 | protected function drupalHead($path, array $options = array(), array $headers = array()) { |
||
2381 | $options['absolute'] = TRUE; |
||
2382 | $out = $this->curlExec(array(CURLOPT_NOBODY => TRUE, CURLOPT_URL => url($path, $options), CURLOPT_HTTPHEADER => $headers)); |
||
2383 | $this->refreshVariables(); // Ensure that any changes to variables in the other thread are picked up. |
||
2384 | return $out; |
||
2385 | } |
||
2386 | |||
2387 | /**
|
||
2388 | * Handle form input related to drupalPost(). Ensure that the specified fields
|
||
2389 | * exist and attempt to create POST data in the correct manner for the particular
|
||
2390 | * field type.
|
||
2391 | *
|
||
2392 | * @param $post
|
||
2393 | * Reference to array of post values.
|
||
2394 | * @param $edit
|
||
2395 | * Reference to array of edit values to be checked against the form.
|
||
2396 | * @param $submit
|
||
2397 | * Form submit button value.
|
||
2398 | * @param $form
|
||
2399 | * Array of form elements.
|
||
2400 | * @return
|
||
2401 | * Submit value matches a valid submit input in the form.
|
||
2402 | */
|
||
2403 | protected function handleForm(&$post, &$edit, &$upload, $submit, $form) { |
||
2404 | // Retrieve the form elements.
|
||
2405 | $elements = $form->xpath('.//input[not(@disabled)]|.//textarea[not(@disabled)]|.//select[not(@disabled)]'); |
||
2406 | $submit_matches = FALSE; |
||
2407 | foreach ($elements as $element) { |
||
2408 | // SimpleXML objects need string casting all the time.
|
||
2409 | $name = (string) $element['name']; |
||
2410 | // This can either be the type of <input> or the name of the tag itself
|
||
2411 | // for <select> or <textarea>.
|
||
2412 | $type = isset($element['type']) ? (string) $element['type'] : $element->getName(); |
||
2413 | $value = isset($element['value']) ? (string) $element['value'] : ''; |
||
2414 | $done = FALSE; |
||
2415 | if (isset($edit[$name])) { |
||
2416 | switch ($type) { |
||
2417 | case 'text': |
||
2418 | case 'tel': |
||
2419 | case 'textarea': |
||
2420 | case 'url': |
||
2421 | case 'number': |
||
2422 | case 'range': |
||
2423 | case 'color': |
||
2424 | case 'hidden': |
||
2425 | case 'password': |
||
2426 | case 'email': |
||
2427 | case 'search': |
||
2428 | $post[$name] = $edit[$name]; |
||
2429 | unset($edit[$name]); |
||
2430 | break;
|
||
2431 | case 'radio': |
||
2432 | if ($edit[$name] == $value) { |
||
2433 | $post[$name] = $edit[$name]; |
||
2434 | unset($edit[$name]); |
||
2435 | } |
||
2436 | break;
|
||
2437 | case 'checkbox': |
||
2438 | // To prevent checkbox from being checked.pass in a FALSE,
|
||
2439 | // otherwise the checkbox will be set to its value regardless
|
||
2440 | // of $edit.
|
||
2441 | if ($edit[$name] === FALSE) { |
||
2442 | unset($edit[$name]); |
||
2443 | continue 2; |
||
2444 | } |
||
2445 | else {
|
||
2446 | unset($edit[$name]); |
||
2447 | $post[$name] = $value; |
||
2448 | } |
||
2449 | break;
|
||
2450 | case 'select': |
||
2451 | $new_value = $edit[$name]; |
||
2452 | $options = $this->getAllOptions($element); |
||
2453 | if (is_array($new_value)) { |
||
2454 | // Multiple select box.
|
||
2455 | if (!empty($new_value)) { |
||
2456 | $index = 0; |
||
2457 | $key = preg_replace('/\[\]$/', '', $name); |
||
2458 | foreach ($options as $option) { |
||
2459 | $option_value = (string) $option['value']; |
||
2460 | if (in_array($option_value, $new_value)) { |
||
2461 | $post[$key . '[' . $index++ . ']'] = $option_value; |
||
2462 | $done = TRUE; |
||
2463 | unset($edit[$name]); |
||
2464 | } |
||
2465 | } |
||
2466 | } |
||
2467 | else {
|
||
2468 | // No options selected: do not include any POST data for the
|
||
2469 | // element.
|
||
2470 | $done = TRUE; |
||
2471 | unset($edit[$name]); |
||
2472 | } |
||
2473 | } |
||
2474 | else {
|
||
2475 | // Single select box.
|
||
2476 | foreach ($options as $option) { |
||
2477 | if ($new_value == $option['value']) { |
||
2478 | $post[$name] = $new_value; |
||
2479 | unset($edit[$name]); |
||
2480 | $done = TRUE; |
||
2481 | break;
|
||
2482 | } |
||
2483 | } |
||
2484 | } |
||
2485 | break;
|
||
2486 | case 'file': |
||
2487 | $upload[$name] = $edit[$name]; |
||
2488 | unset($edit[$name]); |
||
2489 | break;
|
||
2490 | } |
||
2491 | } |
||
2492 | if (!isset($post[$name]) && !$done) { |
||
2493 | switch ($type) { |
||
2494 | case 'textarea': |
||
2495 | $post[$name] = (string) $element; |
||
2496 | break;
|
||
2497 | case 'select': |
||
2498 | $single = empty($element['multiple']); |
||
2499 | $first = TRUE; |
||
2500 | $index = 0; |
||
2501 | $key = preg_replace('/\[\]$/', '', $name); |
||
2502 | $options = $this->getAllOptions($element); |
||
2503 | foreach ($options as $option) { |
||
2504 | // For single select, we load the first option, if there is a
|
||
2505 | // selected option that will overwrite it later.
|
||
2506 | if ($option['selected'] || ($first && $single)) { |
||
2507 | $first = FALSE; |
||
2508 | if ($single) { |
||
2509 | $post[$name] = (string) $option['value']; |
||
2510 | } |
||
2511 | else {
|
||
2512 | $post[$key . '[' . $index++ . ']'] = (string) $option['value']; |
||
2513 | } |
||
2514 | } |
||
2515 | } |
||
2516 | break;
|
||
2517 | case 'file': |
||
2518 | break;
|
||
2519 | case 'submit': |
||
2520 | case 'image': |
||
2521 | if (isset($submit) && $submit == $value) { |
||
2522 | $post[$name] = $value; |
||
2523 | $submit_matches = TRUE; |
||
2524 | } |
||
2525 | break;
|
||
2526 | case 'radio': |
||
2527 | case 'checkbox': |
||
2528 | if (!isset($element['checked'])) { |
||
2529 | break;
|
||
2530 | } |
||
2531 | // Deliberate no break.
|
||
2532 | default:
|
||
2533 | $post[$name] = $value; |
||
2534 | } |
||
2535 | } |
||
2536 | } |
||
2537 | return $submit_matches; |
||
2538 | } |
||
2539 | |||
2540 | /**
|
||
2541 | * Builds an XPath query.
|
||
2542 | *
|
||
2543 | * Builds an XPath query by replacing placeholders in the query by the value
|
||
2544 | * of the arguments.
|
||
2545 | *
|
||
2546 | * XPath 1.0 (the version supported by libxml2, the underlying XML library
|
||
2547 | * used by PHP) doesn't support any form of quotation. This function
|
||
2548 | * simplifies the building of XPath expression.
|
||
2549 | *
|
||
2550 | * @param $xpath
|
||
2551 | * An XPath query, possibly with placeholders in the form ':name'.
|
||
2552 | * @param $args
|
||
2553 | * An array of arguments with keys in the form ':name' matching the
|
||
2554 | * placeholders in the query. The values may be either strings or numeric
|
||
2555 | * values.
|
||
2556 | * @return
|
||
2557 | * An XPath query with arguments replaced.
|
||
2558 | */
|
||
2559 | protected function buildXPathQuery($xpath, array $args = array()) { |
||
2560 | // Replace placeholders.
|
||
2561 | foreach ($args as $placeholder => $value) { |
||
2562 | // XPath 1.0 doesn't support a way to escape single or double quotes in a
|
||
2563 | // string literal. We split double quotes out of the string, and encode
|
||
2564 | // them separately.
|
||
2565 | if (is_string($value)) { |
||
2566 | // Explode the text at the quote characters.
|
||
2567 | $parts = explode('"', $value); |
||
2568 | |||
2569 | // Quote the parts.
|
||
2570 | foreach ($parts as &$part) { |
||
2571 | $part = '"' . $part . '"'; |
||
2572 | } |
||
2573 | |||
2574 | // Return the string.
|
||
2575 | $value = count($parts) > 1 ? 'concat(' . implode(', \'"\', ', $parts) . ')' : $parts[0]; |
||
2576 | } |
||
2577 | $xpath = preg_replace('/' . preg_quote($placeholder) . '\b/', $value, $xpath); |
||
2578 | } |
||
2579 | return $xpath; |
||
2580 | } |
||
2581 | |||
2582 | /**
|
||
2583 | * Perform an xpath search on the contents of the internal browser. The search
|
||
2584 | * is relative to the root element (HTML tag normally) of the page.
|
||
2585 | *
|
||
2586 | * @param $xpath
|
||
2587 | * The xpath string to use in the search.
|
||
2588 | * @return
|
||
2589 | * The return value of the xpath search. For details on the xpath string
|
||
2590 | * format and return values see the SimpleXML documentation,
|
||
2591 | * http://us.php.net/manual/function.simplexml-element-xpath.php.
|
||
2592 | */
|
||
2593 | protected function xpath($xpath, array $arguments = array()) { |
||
2594 | if ($this->parse()) { |
||
2595 | $xpath = $this->buildXPathQuery($xpath, $arguments); |
||
2596 | $result = $this->elements->xpath($xpath); |
||
2597 | // Some combinations of PHP / libxml versions return an empty array
|
||
2598 | // instead of the documented FALSE. Forcefully convert any falsish values
|
||
2599 | // to an empty array to allow foreach(...) constructions.
|
||
2600 | return $result ? $result : array(); |
||
2601 | } |
||
2602 | else {
|
||
2603 | return FALSE; |
||
2604 | } |
||
2605 | } |
||
2606 | |||
2607 | /**
|
||
2608 | * Get all option elements, including nested options, in a select.
|
||
2609 | *
|
||
2610 | * @param $element
|
||
2611 | * The element for which to get the options.
|
||
2612 | * @return
|
||
2613 | * Option elements in select.
|
||
2614 | */
|
||
2615 | protected function getAllOptions(SimpleXMLElement $element) { |
||
2616 | $options = array(); |
||
2617 | // Add all options items.
|
||
2618 | foreach ($element->option as $option) { |
||
2619 | $options[] = $option; |
||
2620 | } |
||
2621 | |||
2622 | // Search option group children.
|
||
2623 | if (isset($element->optgroup)) { |
||
2624 | foreach ($element->optgroup as $group) { |
||
2625 | $options = array_merge($options, $this->getAllOptions($group)); |
||
2626 | } |
||
2627 | } |
||
2628 | return $options; |
||
2629 | } |
||
2630 | |||
2631 | /**
|
||
2632 | * Pass if a link with the specified label is found, and optional with the
|
||
2633 | * specified index.
|
||
2634 | *
|
||
2635 | * @param $label
|
||
2636 | * Text between the anchor tags.
|
||
2637 | * @param $index
|
||
2638 | * Link position counting from zero.
|
||
2639 | * @param $message
|
||
2640 | * Message to display.
|
||
2641 | * @param $group
|
||
2642 | * The group this message belongs to, defaults to 'Other'.
|
||
2643 | * @return
|
||
2644 | * TRUE if the assertion succeeded, FALSE otherwise.
|
||
2645 | */
|
||
2646 | protected function assertLink($label, $index = 0, $message = '', $group = 'Other') { |
||
2647 | $links = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $label)); |
||
2648 | $message = ($message ? $message : t('Link with label %label found.', array('%label' => $label))); |
||
2649 | return $this->assert(isset($links[$index]), $message, $group); |
||
2650 | } |
||
2651 | |||
2652 | /**
|
||
2653 | * Pass if a link with the specified label is not found.
|
||
2654 | *
|
||
2655 | * @param $label
|
||
2656 | * Text between the anchor tags.
|
||
2657 | * @param $message
|
||
2658 | * Message to display.
|
||
2659 | * @param $group
|
||
2660 | * The group this message belongs to, defaults to 'Other'.
|
||
2661 | * @return
|
||
2662 | * TRUE if the assertion succeeded, FALSE otherwise.
|
||
2663 | */
|
||
2664 | protected function assertNoLink($label, $message = '', $group = 'Other') { |
||
2665 | $links = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $label)); |
||
2666 | $message = ($message ? $message : t('Link with label %label not found.', array('%label' => $label))); |
||
2667 | return $this->assert(empty($links), $message, $group); |
||
2668 | } |
||
2669 | |||
2670 | /**
|
||
2671 | * Pass if a link containing a given href (part) is found.
|
||
2672 | *
|
||
2673 | * @param $href
|
||
2674 | * The full or partial value of the 'href' attribute of the anchor tag.
|
||
2675 | * @param $index
|
||
2676 | * Link position counting from zero.
|
||
2677 | * @param $message
|
||
2678 | * Message to display.
|
||
2679 | * @param $group
|
||
2680 | * The group this message belongs to, defaults to 'Other'.
|
||
2681 | *
|
||
2682 | * @return
|
||
2683 | * TRUE if the assertion succeeded, FALSE otherwise.
|
||
2684 | */
|
||
2685 | protected function assertLinkByHref($href, $index = 0, $message = '', $group = 'Other') { |
||
2686 | $links = $this->xpath('//a[contains(@href, :href)]', array(':href' => $href)); |
||
2687 | $message = ($message ? $message : t('Link containing href %href found.', array('%href' => $href))); |
||
2688 | return $this->assert(isset($links[$index]), $message, $group); |
||
2689 | } |
||
2690 | |||
2691 | /**
|
||
2692 | * Pass if a link containing a given href (part) is not found.
|
||
2693 | *
|
||
2694 | * @param $href
|
||
2695 | * The full or partial value of the 'href' attribute of the anchor tag.
|
||
2696 | * @param $message
|
||
2697 | * Message to display.
|
||
2698 | * @param $group
|
||
2699 | * The group this message belongs to, defaults to 'Other'.
|
||
2700 | *
|
||
2701 | * @return
|
||
2702 | * TRUE if the assertion succeeded, FALSE otherwise.
|
||
2703 | */
|
||
2704 | protected function assertNoLinkByHref($href, $message = '', $group = 'Other') { |
||
2705 | $links = $this->xpath('//a[contains(@href, :href)]', array(':href' => $href)); |
||
2706 | $message = ($message ? $message : t('No link containing href %href found.', array('%href' => $href))); |
||
2707 | return $this->assert(empty($links), $message, $group); |
||
2708 | } |
||
2709 | |||
2710 | /**
|
||
2711 | * Follows a link by name.
|
||
2712 | *
|
||
2713 | * Will click the first link found with this link text by default, or a later
|
||
2714 | * one if an index is given. Match is case sensitive with normalized space.
|
||
2715 | b4adf10d | Assos Assos | * The label is translated label.
|
2716 | *
|
||
2717 | * If the link is discovered and clicked, the test passes. Fail otherwise.
|
||
2718 | 85ad3d82 | Assos Assos | *
|
2719 | * @param $label
|
||
2720 | * Text between the anchor tags.
|
||
2721 | * @param $index
|
||
2722 | * Link position counting from zero.
|
||
2723 | * @return
|
||
2724 | b4adf10d | Assos Assos | * Page contents on success, or FALSE on failure.
|
2725 | 85ad3d82 | Assos Assos | */
|
2726 | protected function clickLink($label, $index = 0) { |
||
2727 | $url_before = $this->getUrl(); |
||
2728 | $urls = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $label)); |
||
2729 | if (isset($urls[$index])) { |
||
2730 | $url_target = $this->getAbsoluteUrl($urls[$index]['href']); |
||
2731 | b4adf10d | Assos Assos | $this->pass(t('Clicked link %label (@url_target) from @url_before', array('%label' => $label, '@url_target' => $url_target, '@url_before' => $url_before)), 'Browser'); |
2732 | 85ad3d82 | Assos Assos | return $this->drupalGet($url_target); |
2733 | } |
||
2734 | b4adf10d | Assos Assos | $this->fail(t('Link %label does not exist on @url_before', array('%label' => $label, '@url_before' => $url_before)), 'Browser'); |
2735 | 85ad3d82 | Assos Assos | return FALSE; |
2736 | } |
||
2737 | |||
2738 | /**
|
||
2739 | * Takes a path and returns an absolute path.
|
||
2740 | *
|
||
2741 | * @param $path
|
||
2742 | * A path from the internal browser content.
|
||
2743 | * @return
|
||
2744 | * The $path with $base_url prepended, if necessary.
|
||
2745 | */
|
||
2746 | protected function getAbsoluteUrl($path) { |
||
2747 | global $base_url, $base_path; |
||
2748 | |||
2749 | $parts = parse_url($path); |
||
2750 | if (empty($parts['host'])) { |
||
2751 | // Ensure that we have a string (and no xpath object).
|
||
2752 | $path = (string) $path; |
||
2753 | // Strip $base_path, if existent.
|
||
2754 | $length = strlen($base_path); |
||
2755 | if (substr($path, 0, $length) === $base_path) { |
||
2756 | $path = substr($path, $length); |
||
2757 | } |
||
2758 | // Ensure that we have an absolute path.
|
||
2759 | if ($path[0] !== '/') { |
||
2760 | $path = '/' . $path; |
||
2761 | } |
||
2762 | // Finally, prepend the $base_url.
|
||
2763 | $path = $base_url . $path; |
||
2764 | } |
||
2765 | return $path; |
||
2766 | } |
||
2767 | |||
2768 | /**
|
||
2769 | * Get the current URL from the cURL handler.
|
||
2770 | *
|
||
2771 | * @return
|
||
2772 | * The current URL.
|
||
2773 | */
|
||
2774 | protected function getUrl() { |
||
2775 | return $this->url; |
||
2776 | } |
||
2777 | |||
2778 | /**
|
||
2779 | * Gets the HTTP response headers of the requested page. Normally we are only
|
||
2780 | * interested in the headers returned by the last request. However, if a page
|
||
2781 | * is redirected or HTTP authentication is in use, multiple requests will be
|
||
2782 | * required to retrieve the page. Headers from all requests may be requested
|
||
2783 | * by passing TRUE to this function.
|
||
2784 | *
|
||
2785 | * @param $all_requests
|
||
2786 | * Boolean value specifying whether to return headers from all requests
|
||
2787 | * instead of just the last request. Defaults to FALSE.
|
||
2788 | * @return
|
||
2789 | * A name/value array if headers from only the last request are requested.
|
||
2790 | * If headers from all requests are requested, an array of name/value
|
||
2791 | * arrays, one for each request.
|
||
2792 | *
|
||
2793 | * The pseudonym ":status" is used for the HTTP status line.
|
||
2794 | *
|
||
2795 | * Values for duplicate headers are stored as a single comma-separated list.
|
||
2796 | */
|
||
2797 | protected function drupalGetHeaders($all_requests = FALSE) { |
||
2798 | $request = 0; |
||
2799 | $headers = array($request => array()); |
||
2800 | foreach ($this->headers as $header) { |
||
2801 | $header = trim($header); |
||
2802 | if ($header === '') { |
||
2803 | $request++;
|
||
2804 | } |
||
2805 | else {
|
||
2806 | if (strpos($header, 'HTTP/') === 0) { |
||
2807 | $name = ':status'; |
||
2808 | $value = $header; |
||
2809 | } |
||
2810 | else {
|
||
2811 | list($name, $value) = explode(':', $header, 2); |
||
2812 | $name = strtolower($name); |
||
2813 | } |
||
2814 | if (isset($headers[$request][$name])) { |
||
2815 | $headers[$request][$name] .= ',' . trim($value); |
||
2816 | } |
||
2817 | else {
|
||
2818 | $headers[$request][$name] = trim($value); |
||
2819 | } |
||
2820 | } |
||
2821 | } |
||
2822 | if (!$all_requests) { |
||
2823 | $headers = array_pop($headers); |
||
2824 | } |
||
2825 | return $headers; |
||
2826 | } |
||
2827 | |||
2828 | /**
|
||
2829 | * Gets the value of an HTTP response header. If multiple requests were
|
||
2830 | * required to retrieve the page, only the headers from the last request will
|
||
2831 | * be checked by default. However, if TRUE is passed as the second argument,
|
||
2832 | * all requests will be processed from last to first until the header is
|
||
2833 | * found.
|
||
2834 | *
|
||
2835 | * @param $name
|
||
2836 | * The name of the header to retrieve. Names are case-insensitive (see RFC
|
||
2837 | * 2616 section 4.2).
|
||
2838 | * @param $all_requests
|
||
2839 | * Boolean value specifying whether to check all requests if the header is
|
||
2840 | * not found in the last request. Defaults to FALSE.
|
||
2841 | * @return
|
||
2842 | * The HTTP header value or FALSE if not found.
|
||
2843 | */
|
||
2844 | protected function drupalGetHeader($name, $all_requests = FALSE) { |
||
2845 | $name = strtolower($name); |
||
2846 | $header = FALSE; |
||
2847 | if ($all_requests) { |
||
2848 | foreach (array_reverse($this->drupalGetHeaders(TRUE)) as $headers) { |
||
2849 | if (isset($headers[$name])) { |
||
2850 | $header = $headers[$name]; |
||
2851 | break;
|
||
2852 | } |
||
2853 | } |
||
2854 | } |
||
2855 | else {
|
||
2856 | $headers = $this->drupalGetHeaders(); |
||
2857 | if (isset($headers[$name])) { |
||
2858 | $header = $headers[$name]; |
||
2859 | } |
||
2860 | } |
||
2861 | return $header; |
||
2862 | } |
||
2863 | |||
2864 | /**
|
||
2865 | * Gets the current raw HTML of requested page.
|
||
2866 | */
|
||
2867 | protected function drupalGetContent() { |
||
2868 | return $this->content; |
||
2869 | } |
||
2870 | |||
2871 | /**
|
||
2872 | * Gets the value of the Drupal.settings JavaScript variable for the currently loaded page.
|
||
2873 | */
|
||
2874 | protected function drupalGetSettings() { |
||
2875 | return $this->drupalSettings; |
||
2876 | } |
||
2877 | |||
2878 | /**
|
||
2879 | * Gets an array containing all e-mails sent during this test case.
|
||
2880 | *
|
||
2881 | * @param $filter
|
||
2882 | * An array containing key/value pairs used to filter the e-mails that are returned.
|
||
2883 | * @return
|
||
2884 | * An array containing e-mail messages captured during the current test.
|
||
2885 | */
|
||
2886 | protected function drupalGetMails($filter = array()) { |
||
2887 | $captured_emails = variable_get('drupal_test_email_collector', array()); |
||
2888 | $filtered_emails = array(); |
||
2889 | |||
2890 | foreach ($captured_emails as $message) { |
||
2891 | foreach ($filter as $key => $value) { |
||
2892 | if (!isset($message[$key]) || $message[$key] != $value) { |
||
2893 | continue 2; |
||
2894 | } |
||
2895 | } |
||
2896 | $filtered_emails[] = $message; |
||
2897 | } |
||
2898 | |||
2899 | return $filtered_emails; |
||
2900 | } |
||
2901 | |||
2902 | /**
|
||
2903 | * Sets the raw HTML content. This can be useful when a page has been fetched
|
||
2904 | * outside of the internal browser and assertions need to be made on the
|
||
2905 | * returned page.
|
||
2906 | *
|
||
2907 | * A good example would be when testing drupal_http_request(). After fetching
|
||
2908 | * the page the content can be set and page elements can be checked to ensure
|
||
2909 | * that the function worked properly.
|
||
2910 | */
|
||
2911 | protected function drupalSetContent($content, $url = 'internal:') { |
||
2912 | $this->content = $content; |
||
2913 | $this->url = $url; |
||
2914 | $this->plainTextContent = FALSE; |
||
2915 | $this->elements = FALSE; |
||
2916 | $this->drupalSettings = array(); |
||
2917 | if (preg_match('/jQuery\.extend\(Drupal\.settings, (.*?)\);/', $content, $matches)) { |
||
2918 | $this->drupalSettings = drupal_json_decode($matches[1]); |
||
2919 | } |
||
2920 | } |
||
2921 | |||
2922 | /**
|
||
2923 | * Sets the value of the Drupal.settings JavaScript variable for the currently loaded page.
|
||
2924 | */
|
||
2925 | protected function drupalSetSettings($settings) { |
||
2926 | $this->drupalSettings = $settings; |
||
2927 | } |
||
2928 | |||
2929 | /**
|
||
2930 | * Pass if the internal browser's URL matches the given path.
|
||
2931 | *
|
||
2932 | * @param $path
|
||
2933 | * The expected system path.
|
||
2934 | * @param $options
|
||
2935 | * (optional) Any additional options to pass for $path to url().
|
||
2936 | * @param $message
|
||
2937 | * Message to display.
|
||
2938 | * @param $group
|
||
2939 | * The group this message belongs to, defaults to 'Other'.
|
||
2940 | *
|
||
2941 | * @return
|
||
2942 | * TRUE on pass, FALSE on fail.
|
||
2943 | */
|
||
2944 | protected function assertUrl($path, array $options = array(), $message = '', $group = 'Other') { |
||
2945 | if (!$message) { |
||
2946 | $message = t('Current URL is @url.', array( |
||
2947 | '@url' => var_export(url($path, $options), TRUE), |
||
2948 | )); |
||
2949 | } |
||
2950 | $options['absolute'] = TRUE; |
||
2951 | return $this->assertEqual($this->getUrl(), url($path, $options), $message, $group); |
||
2952 | } |
||
2953 | |||
2954 | /**
|
||
2955 | * Pass if the raw text IS found on the loaded page, fail otherwise. Raw text
|
||
2956 | * refers to the raw HTML that the page generated.
|
||
2957 | *
|
||
2958 | * @param $raw
|
||
2959 | * Raw (HTML) string to look for.
|
||
2960 | * @param $message
|
||
2961 | * Message to display.
|
||
2962 | * @param $group
|
||
2963 | * The group this message belongs to, defaults to 'Other'.
|
||
2964 | * @return
|
||
2965 | * TRUE on pass, FALSE on fail.
|
||
2966 | */
|
||
2967 | protected function assertRaw($raw, $message = '', $group = 'Other') { |
||
2968 | if (!$message) { |
||
2969 | $message = t('Raw "@raw" found', array('@raw' => $raw)); |
||
2970 | } |
||
2971 | return $this->assert(strpos($this->drupalGetContent(), $raw) !== FALSE, $message, $group); |
||
2972 | } |
||
2973 | |||
2974 | /**
|
||
2975 | * Pass if the raw text is NOT found on the loaded page, fail otherwise. Raw text
|
||
2976 | * refers to the raw HTML that the page generated.
|
||
2977 | *
|
||
2978 | * @param $raw
|
||
2979 | * Raw (HTML) string to look for.
|
||
2980 | * @param $message
|
||
2981 | * Message to display.
|
||
2982 | * @param $group
|
||
2983 | * The group this message belongs to, defaults to 'Other'.
|
||
2984 | * @return
|
||
2985 | * TRUE on pass, FALSE on fail.
|
||
2986 | */
|
||
2987 | protected function assertNoRaw($raw, $message = '', $group = 'Other') { |
||
2988 | if (!$message) { |
||
2989 | $message = t('Raw "@raw" not found', array('@raw' => $raw)); |
||
2990 | } |
||
2991 | return $this->assert(strpos($this->drupalGetContent(), $raw) === FALSE, $message, $group); |
||
2992 | } |
||
2993 | |||
2994 | /**
|
||
2995 | * Pass if the text IS found on the text version of the page. The text version
|
||
2996 | * is the equivalent of what a user would see when viewing through a web browser.
|
||
2997 | * In other words the HTML has been filtered out of the contents.
|
||
2998 | *
|
||
2999 | * @param $text
|
||
3000 | * Plain text to look for.
|
||
3001 | * @param $message
|
||
3002 | * Message to display.
|
||
3003 | * @param $group
|
||
3004 | * The group this message belongs to, defaults to 'Other'.
|
||
3005 | * @return
|
||
3006 | * TRUE on pass, FALSE on fail.
|
||
3007 | */
|
||
3008 | protected function assertText($text, $message = '', $group = 'Other') { |
||
3009 | return $this->assertTextHelper($text, $message, $group, FALSE); |
||
3010 | } |
||
3011 | |||
3012 | /**
|
||
3013 | * Pass if the text is NOT found on the text version of the page. The text version
|
||
3014 | * is the equivalent of what a user would see when viewing through a web browser.
|
||
3015 | * In other words the HTML has been filtered out of the contents.
|
||
3016 | *
|
||
3017 | * @param $text
|
||
3018 | * Plain text to look for.
|
||
3019 | * @param $message
|
||
3020 | * Message to display.
|
||
3021 | * @param $group
|
||
3022 | * The group this message belongs to, defaults to 'Other'.
|
||
3023 | * @return
|
||
3024 | * TRUE on pass, FALSE on fail.
|
||
3025 | */
|
||
3026 | protected function assertNoText($text, $message = '', $group = 'Other') { |
||
3027 | return $this->assertTextHelper($text, $message, $group, TRUE); |
||
3028 | } |
||
3029 | |||
3030 | /**
|
||
3031 | * Helper for assertText and assertNoText.
|
||
3032 | *
|
||
3033 | * It is not recommended to call this function directly.
|
||
3034 | *
|
||
3035 | * @param $text
|
||
3036 | * Plain text to look for.
|
||
3037 | * @param $message
|
||
3038 | * Message to display.
|
||
3039 | * @param $group
|
||
3040 | * The group this message belongs to.
|
||
3041 | * @param $not_exists
|
||
3042 | * TRUE if this text should not exist, FALSE if it should.
|
||
3043 | * @return
|
||
3044 | * TRUE on pass, FALSE on fail.
|
||
3045 | */
|
||
3046 | protected function assertTextHelper($text, $message = '', $group, $not_exists) { |
||
3047 | if ($this->plainTextContent === FALSE) { |
||
3048 | $this->plainTextContent = filter_xss($this->drupalGetContent(), array()); |
||
3049 | } |
||
3050 | if (!$message) { |
||
3051 | $message = !$not_exists ? t('"@text" found', array('@text' => $text)) : t('"@text" not found', array('@text' => $text)); |
||
3052 | } |
||
3053 | return $this->assert($not_exists == (strpos($this->plainTextContent, $text) === FALSE), $message, $group); |
||
3054 | } |
||
3055 | |||
3056 | /**
|
||
3057 | * Pass if the text is found ONLY ONCE on the text version of the page.
|
||
3058 | *
|
||
3059 | * The text version is the equivalent of what a user would see when viewing
|
||
3060 | * through a web browser. In other words the HTML has been filtered out of
|
||
3061 | * the contents.
|
||
3062 | *
|
||
3063 | * @param $text
|
||
3064 | * Plain text to look for.
|
||
3065 | * @param $message
|
||
3066 | * Message to display.
|
||
3067 | * @param $group
|
||
3068 | * The group this message belongs to, defaults to 'Other'.
|
||
3069 | * @return
|
||
3070 | * TRUE on pass, FALSE on fail.
|
||
3071 | */
|
||
3072 | protected function assertUniqueText($text, $message = '', $group = 'Other') { |
||
3073 | return $this->assertUniqueTextHelper($text, $message, $group, TRUE); |
||
3074 | } |
||
3075 | |||
3076 | /**
|
||
3077 | * Pass if the text is found MORE THAN ONCE on the text version of the page.
|
||
3078 | *
|
||
3079 | * The text version is the equivalent of what a user would see when viewing
|
||
3080 | * through a web browser. In other words the HTML has been filtered out of
|
||
3081 | * the contents.
|
||
3082 | *
|
||
3083 | * @param $text
|
||
3084 | * Plain text to look for.
|
||
3085 | * @param $message
|
||
3086 | * Message to display.
|
||
3087 | * @param $group
|
||
3088 | * The group this message belongs to, defaults to 'Other'.
|
||
3089 | * @return
|
||
3090 | * TRUE on pass, FALSE on fail.
|
||
3091 | */
|
||
3092 | protected function assertNoUniqueText($text, $message = '', $group = 'Other') { |
||
3093 | return $this->assertUniqueTextHelper($text, $message, $group, FALSE); |
||
3094 | } |
||
3095 | |||
3096 | /**
|
||
3097 | * Helper for assertUniqueText and assertNoUniqueText.
|
||
3098 | *
|
||
3099 | * It is not recommended to call this function directly.
|
||
3100 | *
|
||
3101 | * @param $text
|
||
3102 | * Plain text to look for.
|
||
3103 | * @param $message
|
||
3104 | * Message to display.
|
||
3105 | * @param $group
|
||
3106 | * The group this message belongs to.
|
||
3107 | * @param $be_unique
|
||
3108 | * TRUE if this text should be found only once, FALSE if it should be found more than once.
|
||
3109 | * @return
|
||
3110 | * TRUE on pass, FALSE on fail.
|
||
3111 | */
|
||
3112 | protected function assertUniqueTextHelper($text, $message = '', $group, $be_unique) { |
||
3113 | if ($this->plainTextContent === FALSE) { |
||
3114 | $this->plainTextContent = filter_xss($this->drupalGetContent(), array()); |
||
3115 | } |
||
3116 | if (!$message) { |
||
3117 | $message = '"' . $text . '"' . ($be_unique ? ' found only once' : ' found more than once'); |
||
3118 | } |
||
3119 | $first_occurance = strpos($this->plainTextContent, $text); |
||
3120 | if ($first_occurance === FALSE) { |
||
3121 | return $this->assert(FALSE, $message, $group); |
||
3122 | } |
||
3123 | $offset = $first_occurance + strlen($text); |
||
3124 | $second_occurance = strpos($this->plainTextContent, $text, $offset); |
||
3125 | return $this->assert($be_unique == ($second_occurance === FALSE), $message, $group); |
||
3126 | } |
||
3127 | |||
3128 | /**
|
||
3129 | * Will trigger a pass if the Perl regex pattern is found in the raw content.
|
||
3130 | *
|
||
3131 | * @param $pattern
|
||
3132 | * Perl regex to look for including the regex delimiters.
|
||
3133 | * @param $message
|
||
3134 | * Message to display.
|
||
3135 | * @param $group
|
||
3136 | * The group this message belongs to.
|
||
3137 | * @return
|
||
3138 | * TRUE on pass, FALSE on fail.
|
||
3139 | */
|
||
3140 | protected function assertPattern($pattern, $message = '', $group = 'Other') { |
||
3141 | if (!$message) { |
||
3142 | $message = t('Pattern "@pattern" found', array('@pattern' => $pattern)); |
||
3143 | } |
||
3144 | return $this->assert((bool) preg_match($pattern, $this->drupalGetContent()), $message, $group); |
||
3145 | } |
||
3146 | |||
3147 | /**
|
||
3148 | * Will trigger a pass if the perl regex pattern is not present in raw content.
|
||
3149 | *
|
||
3150 | * @param $pattern
|
||
3151 | * Perl regex to look for including the regex delimiters.
|
||
3152 | * @param $message
|
||
3153 | * Message to display.
|
||
3154 | * @param $group
|
||
3155 | * The group this message belongs to.
|
||
3156 | * @return
|
||
3157 | * TRUE on pass, FALSE on fail.
|
||
3158 | */
|
||
3159 | protected function assertNoPattern($pattern, $message = '', $group = 'Other') { |
||
3160 | if (!$message) { |
||
3161 | $message = t('Pattern "@pattern" not found', array('@pattern' => $pattern)); |
||
3162 | } |
||
3163 | return $this->assert(!preg_match($pattern, $this->drupalGetContent()), $message, $group); |
||
3164 | } |
||
3165 | |||
3166 | /**
|
||
3167 | * Pass if the page title is the given string.
|
||
3168 | *
|
||
3169 | * @param $title
|
||
3170 | * The string the title should be.
|
||
3171 | * @param $message
|
||
3172 | * Message to display.
|
||
3173 | * @param $group
|
||
3174 | * The group this message belongs to.
|
||
3175 | * @return
|
||
3176 | * TRUE on pass, FALSE on fail.
|
||
3177 | */
|
||
3178 | protected function assertTitle($title, $message = '', $group = 'Other') { |
||
3179 | $actual = (string) current($this->xpath('//title')); |
||
3180 | if (!$message) { |
||
3181 | $message = t('Page title @actual is equal to @expected.', array( |
||
3182 | '@actual' => var_export($actual, TRUE), |
||
3183 | '@expected' => var_export($title, TRUE), |
||
3184 | )); |
||
3185 | } |
||
3186 | return $this->assertEqual($actual, $title, $message, $group); |
||
3187 | } |
||
3188 | |||
3189 | /**
|
||
3190 | * Pass if the page title is not the given string.
|
||
3191 | *
|
||
3192 | * @param $title
|
||
3193 | * The string the title should not be.
|
||
3194 | * @param $message
|
||
3195 | * Message to display.
|
||
3196 | * @param $group
|
||
3197 | * The group this message belongs to.
|
||
3198 | * @return
|
||
3199 | * TRUE on pass, FALSE on fail.
|
||
3200 | */
|
||
3201 | protected function assertNoTitle($title, $message = '', $group = 'Other') { |
||
3202 | $actual = (string) current($this->xpath('//title')); |
||
3203 | if (!$message) { |
||
3204 | $message = t('Page title @actual is not equal to @unexpected.', array( |
||
3205 | '@actual' => var_export($actual, TRUE), |
||
3206 | '@unexpected' => var_export($title, TRUE), |
||
3207 | )); |
||
3208 | } |
||
3209 | return $this->assertNotEqual($actual, $title, $message, $group); |
||
3210 | } |
||
3211 | |||
3212 | /**
|
||
3213 | * Asserts themed output.
|
||
3214 | *
|
||
3215 | * @param $callback
|
||
3216 | * The name of the theme function to invoke; e.g. 'links' for theme_links().
|
||
3217 | * @param $variables
|
||
3218 | e33d3026 | Julien Enselme | * (optional) An array of variables to pass to the theme function.
|
3219 | 85ad3d82 | Assos Assos | * @param $expected
|
3220 | * The expected themed output string.
|
||
3221 | * @param $message
|
||
3222 | * (optional) A message to display with the assertion. Do not translate
|
||
3223 | * messages: use format_string() to embed variables in the message text, not
|
||
3224 | * t(). If left blank, a default message will be displayed.
|
||
3225 | * @param $group
|
||
3226 | * (optional) The group this message is in, which is displayed in a column
|
||
3227 | * in test output. Use 'Debug' to indicate this is debugging output. Do not
|
||
3228 | * translate this string. Defaults to 'Other'; most tests do not override
|
||
3229 | * this default.
|
||
3230 | *
|
||
3231 | * @return
|
||
3232 | * TRUE on pass, FALSE on fail.
|
||
3233 | */
|
||
3234 | protected function assertThemeOutput($callback, array $variables = array(), $expected, $message = '', $group = 'Other') { |
||
3235 | $output = theme($callback, $variables); |
||
3236 | $this->verbose('Variables:' . '<pre>' . check_plain(var_export($variables, TRUE)) . '</pre>' |
||
3237 | . '<hr />' . 'Result:' . '<pre>' . check_plain(var_export($output, TRUE)) . '</pre>' |
||
3238 | . '<hr />' . 'Expected:' . '<pre>' . check_plain(var_export($expected, TRUE)) . '</pre>' |
||
3239 | . '<hr />' . $output |
||
3240 | ); |
||
3241 | if (!$message) { |
||
3242 | $message = '%callback rendered correctly.'; |
||
3243 | } |
||
3244 | $message = format_string($message, array('%callback' => 'theme_' . $callback . '()')); |
||
3245 | return $this->assertIdentical($output, $expected, $message, $group); |
||
3246 | } |
||
3247 | |||
3248 | /**
|
||
3249 | * Asserts that a field exists in the current page by the given XPath.
|
||
3250 | *
|
||
3251 | * @param $xpath
|
||
3252 | * XPath used to find the field.
|
||
3253 | * @param $value
|
||
3254 | e33d3026 | Julien Enselme | * (optional) Value of the field to assert. You may pass in NULL (default)
|
3255 | * to skip checking the actual value, while still checking that the field
|
||
3256 | * exists.
|
||
3257 | 85ad3d82 | Assos Assos | * @param $message
|
3258 | * (optional) Message to display.
|
||
3259 | * @param $group
|
||
3260 | * (optional) The group this message belongs to.
|
||
3261 | *
|
||
3262 | * @return
|
||
3263 | * TRUE on pass, FALSE on fail.
|
||
3264 | */
|
||
3265 | protected function assertFieldByXPath($xpath, $value = NULL, $message = '', $group = 'Other') { |
||
3266 | $fields = $this->xpath($xpath); |
||
3267 | |||
3268 | // If value specified then check array for match.
|
||
3269 | $found = TRUE; |
||
3270 | if (isset($value)) { |
||
3271 | $found = FALSE; |
||
3272 | if ($fields) { |
||
3273 | foreach ($fields as $field) { |
||
3274 | if (isset($field['value']) && $field['value'] == $value) { |
||
3275 | // Input element with correct value.
|
||
3276 | $found = TRUE; |
||
3277 | } |
||
3278 | elseif (isset($field->option)) { |
||
3279 | // Select element found.
|
||
3280 | if ($this->getSelectedItem($field) == $value) { |
||
3281 | $found = TRUE; |
||
3282 | } |
||
3283 | else {
|
||
3284 | // No item selected so use first item.
|
||
3285 | $items = $this->getAllOptions($field); |
||
3286 | if (!empty($items) && $items[0]['value'] == $value) { |
||
3287 | $found = TRUE; |
||
3288 | } |
||
3289 | } |
||
3290 | } |
||
3291 | elseif ((string) $field == $value) { |
||
3292 | // Text area with correct text.
|
||
3293 | $found = TRUE; |
||
3294 | } |
||
3295 | } |
||
3296 | } |
||
3297 | } |
||
3298 | return $this->assertTrue($fields && $found, $message, $group); |
||
3299 | } |
||
3300 | |||
3301 | /**
|
||
3302 | * Get the selected value from a select field.
|
||
3303 | *
|
||
3304 | * @param $element
|
||
3305 | * SimpleXMLElement select element.
|
||
3306 | * @return
|
||
3307 | * The selected value or FALSE.
|
||
3308 | */
|
||
3309 | protected function getSelectedItem(SimpleXMLElement $element) { |
||
3310 | foreach ($element->children() as $item) { |
||
3311 | if (isset($item['selected'])) { |
||
3312 | return $item['value']; |
||
3313 | } |
||
3314 | elseif ($item->getName() == 'optgroup') { |
||
3315 | if ($value = $this->getSelectedItem($item)) { |
||
3316 | return $value; |
||
3317 | } |
||
3318 | } |
||
3319 | } |
||
3320 | return FALSE; |
||
3321 | } |
||
3322 | |||
3323 | /**
|
||
3324 | e33d3026 | Julien Enselme | * Asserts that a field doesn't exist or its value doesn't match, by XPath.
|
3325 | 85ad3d82 | Assos Assos | *
|
3326 | * @param $xpath
|
||
3327 | * XPath used to find the field.
|
||
3328 | * @param $value
|
||
3329 | e33d3026 | Julien Enselme | * (optional) Value for the field, to assert that the field's value on the
|
3330 | * page doesn't match it. You may pass in NULL to skip checking the
|
||
3331 | * value, while still checking that the field doesn't exist.
|
||
3332 | 85ad3d82 | Assos Assos | * @param $message
|
3333 | * (optional) Message to display.
|
||
3334 | * @param $group
|
||
3335 | * (optional) The group this message belongs to.
|
||
3336 | *
|
||
3337 | * @return
|
||
3338 | * TRUE on pass, FALSE on fail.
|
||
3339 | */
|
||
3340 | protected function assertNoFieldByXPath($xpath, $value = NULL, $message = '', $group = 'Other') { |
||
3341 | $fields = $this->xpath($xpath); |
||
3342 | |||
3343 | // If value specified then check array for match.
|
||
3344 | $found = TRUE; |
||
3345 | if (isset($value)) { |
||
3346 | $found = FALSE; |
||
3347 | if ($fields) { |
||
3348 | foreach ($fields as $field) { |
||
3349 | if ($field['value'] == $value) { |
||
3350 | $found = TRUE; |
||
3351 | } |
||
3352 | } |
||
3353 | } |
||
3354 | } |
||
3355 | return $this->assertFalse($fields && $found, $message, $group); |
||
3356 | } |
||
3357 | |||
3358 | /**
|
||
3359 | * Asserts that a field exists in the current page with the given name and value.
|
||
3360 | *
|
||
3361 | * @param $name
|
||
3362 | * Name of field to assert.
|
||
3363 | * @param $value
|
||
3364 | e33d3026 | Julien Enselme | * (optional) Value of the field to assert. You may pass in NULL (default)
|
3365 | * to skip checking the actual value, while still checking that the field
|
||
3366 | * exists.
|
||
3367 | 85ad3d82 | Assos Assos | * @param $message
|
3368 | * Message to display.
|
||
3369 | * @param $group
|
||
3370 | * The group this message belongs to.
|
||
3371 | * @return
|
||
3372 | * TRUE on pass, FALSE on fail.
|
||
3373 | */
|
||
3374 | protected function assertFieldByName($name, $value = NULL, $message = NULL) { |
||
3375 | if (!isset($message)) { |
||
3376 | if (!isset($value)) { |
||
3377 | $message = t('Found field with name @name', array( |
||
3378 | '@name' => var_export($name, TRUE), |
||
3379 | )); |
||
3380 | } |
||
3381 | else {
|
||
3382 | $message = t('Found field with name @name and value @value', array( |
||
3383 | '@name' => var_export($name, TRUE), |
||
3384 | '@value' => var_export($value, TRUE), |
||
3385 | )); |
||
3386 | } |
||
3387 | } |
||
3388 | return $this->assertFieldByXPath($this->constructFieldXpath('name', $name), $value, $message, t('Browser')); |
||
3389 | } |
||
3390 | |||
3391 | /**
|
||
3392 | * Asserts that a field does not exist with the given name and value.
|
||
3393 | *
|
||
3394 | * @param $name
|
||
3395 | * Name of field to assert.
|
||
3396 | * @param $value
|
||
3397 | e33d3026 | Julien Enselme | * (optional) Value for the field, to assert that the field's value on the
|
3398 | * page doesn't match it. You may pass in NULL to skip checking the
|
||
3399 | * value, while still checking that the field doesn't exist. However, the
|
||
3400 | * default value ('') asserts that the field value is not an empty string.
|
||
3401 | 85ad3d82 | Assos Assos | * @param $message
|
3402 | e33d3026 | Julien Enselme | * (optional) Message to display.
|
3403 | 85ad3d82 | Assos Assos | * @param $group
|
3404 | * The group this message belongs to.
|
||
3405 | * @return
|
||
3406 | * TRUE on pass, FALSE on fail.
|
||
3407 | */
|
||
3408 | protected function assertNoFieldByName($name, $value = '', $message = '') { |
||
3409 | return $this->assertNoFieldByXPath($this->constructFieldXpath('name', $name), $value, $message ? $message : t('Did not find field by name @name', array('@name' => $name)), t('Browser')); |
||
3410 | } |
||
3411 | |||
3412 | /**
|
||
3413 | e33d3026 | Julien Enselme | * Asserts that a field exists in the current page with the given ID and value.
|
3414 | 85ad3d82 | Assos Assos | *
|
3415 | * @param $id
|
||
3416 | e33d3026 | Julien Enselme | * ID of field to assert.
|
3417 | 85ad3d82 | Assos Assos | * @param $value
|
3418 | e33d3026 | Julien Enselme | * (optional) Value for the field to assert. You may pass in NULL to skip
|
3419 | * checking the value, while still checking that the field exists.
|
||
3420 | * However, the default value ('') asserts that the field value is an empty
|
||
3421 | * string.
|
||
3422 | 85ad3d82 | Assos Assos | * @param $message
|
3423 | e33d3026 | Julien Enselme | * (optional) Message to display.
|
3424 | 85ad3d82 | Assos Assos | * @param $group
|
3425 | * The group this message belongs to.
|
||
3426 | * @return
|
||
3427 | * TRUE on pass, FALSE on fail.
|
||
3428 | */
|
||
3429 | protected function assertFieldById($id, $value = '', $message = '') { |
||
3430 | return $this->assertFieldByXPath($this->constructFieldXpath('id', $id), $value, $message ? $message : t('Found field by id @id', array('@id' => $id)), t('Browser')); |
||
3431 | } |
||
3432 | |||
3433 | /**
|
||
3434 | e33d3026 | Julien Enselme | * Asserts that a field does not exist with the given ID and value.
|
3435 | 85ad3d82 | Assos Assos | *
|
3436 | * @param $id
|
||
3437 | e33d3026 | Julien Enselme | * ID of field to assert.
|
3438 | 85ad3d82 | Assos Assos | * @param $value
|
3439 | e33d3026 | Julien Enselme | * (optional) Value for the field, to assert that the field's value on the
|
3440 | * page doesn't match it. You may pass in NULL to skip checking the value,
|
||
3441 | * while still checking that the field doesn't exist. However, the default
|
||
3442 | * value ('') asserts that the field value is not an empty string.
|
||
3443 | 85ad3d82 | Assos Assos | * @param $message
|
3444 | e33d3026 | Julien Enselme | * (optional) Message to display.
|
3445 | 85ad3d82 | Assos Assos | * @param $group
|
3446 | * The group this message belongs to.
|
||
3447 | * @return
|
||
3448 | * TRUE on pass, FALSE on fail.
|
||
3449 | */
|
||
3450 | protected function assertNoFieldById($id, $value = '', $message = '') { |
||
3451 | return $this->assertNoFieldByXPath($this->constructFieldXpath('id', $id), $value, $message ? $message : t('Did not find field by id @id', array('@id' => $id)), t('Browser')); |
||
3452 | } |
||
3453 | |||
3454 | /**
|
||
3455 | * Asserts that a checkbox field in the current page is checked.
|
||
3456 | *
|
||
3457 | * @param $id
|
||
3458 | e33d3026 | Julien Enselme | * ID of field to assert.
|
3459 | 85ad3d82 | Assos Assos | * @param $message
|
3460 | e33d3026 | Julien Enselme | * (optional) Message to display.
|
3461 | 85ad3d82 | Assos Assos | * @return
|
3462 | * TRUE on pass, FALSE on fail.
|
||
3463 | */
|
||
3464 | protected function assertFieldChecked($id, $message = '') { |
||
3465 | $elements = $this->xpath('//input[@id=:id]', array(':id' => $id)); |
||
3466 | return $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), $message ? $message : t('Checkbox field @id is checked.', array('@id' => $id)), t('Browser')); |
||
3467 | } |
||
3468 | |||
3469 | /**
|
||
3470 | * Asserts that a checkbox field in the current page is not checked.
|
||
3471 | *
|
||
3472 | * @param $id
|
||
3473 | e33d3026 | Julien Enselme | * ID of field to assert.
|
3474 | 85ad3d82 | Assos Assos | * @param $message
|
3475 | e33d3026 | Julien Enselme | * (optional) Message to display.
|
3476 | 85ad3d82 | Assos Assos | * @return
|
3477 | * TRUE on pass, FALSE on fail.
|
||
3478 | */
|
||
3479 | protected function assertNoFieldChecked($id, $message = '') { |
||
3480 | $elements = $this->xpath('//input[@id=:id]', array(':id' => $id)); |
||
3481 | return $this->assertTrue(isset($elements[0]) && empty($elements[0]['checked']), $message ? $message : t('Checkbox field @id is not checked.', array('@id' => $id)), t('Browser')); |
||
3482 | } |
||
3483 | |||
3484 | /**
|
||
3485 | * Asserts that a select option in the current page is checked.
|
||
3486 | *
|
||
3487 | * @param $id
|
||
3488 | e33d3026 | Julien Enselme | * ID of select field to assert.
|
3489 | 85ad3d82 | Assos Assos | * @param $option
|
3490 | * Option to assert.
|
||
3491 | * @param $message
|
||
3492 | e33d3026 | Julien Enselme | * (optional) Message to display.
|
3493 | 85ad3d82 | Assos Assos | * @return
|
3494 | * TRUE on pass, FALSE on fail.
|
||
3495 | *
|
||
3496 | * @todo $id is unusable. Replace with $name.
|
||
3497 | */
|
||
3498 | protected function assertOptionSelected($id, $option, $message = '') { |
||
3499 | $elements = $this->xpath('//select[@id=:id]//option[@value=:option]', array(':id' => $id, ':option' => $option)); |
||
3500 | return $this->assertTrue(isset($elements[0]) && !empty($elements[0]['selected']), $message ? $message : t('Option @option for field @id is selected.', array('@option' => $option, '@id' => $id)), t('Browser')); |
||
3501 | } |
||
3502 | |||
3503 | /**
|
||
3504 | * Asserts that a select option in the current page is not checked.
|
||
3505 | *
|
||
3506 | * @param $id
|
||
3507 | e33d3026 | Julien Enselme | * ID of select field to assert.
|
3508 | 85ad3d82 | Assos Assos | * @param $option
|
3509 | * Option to assert.
|
||
3510 | * @param $message
|
||
3511 | e33d3026 | Julien Enselme | * (optional) Message to display.
|
3512 | 85ad3d82 | Assos Assos | * @return
|
3513 | * TRUE on pass, FALSE on fail.
|
||
3514 | */
|
||
3515 | protected function assertNoOptionSelected($id, $option, $message = '') { |
||
3516 | $elements = $this->xpath('//select[@id=:id]//option[@value=:option]', array(':id' => $id, ':option' => $option)); |
||
3517 | return $this->assertTrue(isset($elements[0]) && empty($elements[0]['selected']), $message ? $message : t('Option @option for field @id is not selected.', array('@option' => $option, '@id' => $id)), t('Browser')); |
||
3518 | } |
||
3519 | |||
3520 | /**
|
||
3521 | e33d3026 | Julien Enselme | * Asserts that a field exists with the given name or ID.
|
3522 | 85ad3d82 | Assos Assos | *
|
3523 | * @param $field
|
||
3524 | e33d3026 | Julien Enselme | * Name or ID of field to assert.
|
3525 | 85ad3d82 | Assos Assos | * @param $message
|
3526 | e33d3026 | Julien Enselme | * (optional) Message to display.
|
3527 | 85ad3d82 | Assos Assos | * @param $group
|
3528 | * The group this message belongs to.
|
||
3529 | * @return
|
||
3530 | * TRUE on pass, FALSE on fail.
|
||
3531 | */
|
||
3532 | protected function assertField($field, $message = '', $group = 'Other') { |
||
3533 | return $this->assertFieldByXPath($this->constructFieldXpath('name', $field) . '|' . $this->constructFieldXpath('id', $field), NULL, $message, $group); |
||
3534 | } |
||
3535 | |||
3536 | /**
|
||
3537 | e33d3026 | Julien Enselme | * Asserts that a field does not exist with the given name or ID.
|
3538 | 85ad3d82 | Assos Assos | *
|
3539 | * @param $field
|
||
3540 | e33d3026 | Julien Enselme | * Name or ID of field to assert.
|
3541 | 85ad3d82 | Assos Assos | * @param $message
|
3542 | e33d3026 | Julien Enselme | * (optional) Message to display.
|
3543 | 85ad3d82 | Assos Assos | * @param $group
|
3544 | * The group this message belongs to.
|
||
3545 | * @return
|
||
3546 | * TRUE on pass, FALSE on fail.
|
||
3547 | */
|
||
3548 | protected function assertNoField($field, $message = '', $group = 'Other') { |
||
3549 | return $this->assertNoFieldByXPath($this->constructFieldXpath('name', $field) . '|' . $this->constructFieldXpath('id', $field), NULL, $message, $group); |
||
3550 | } |
||
3551 | |||
3552 | /**
|
||
3553 | * Asserts that each HTML ID is used for just a single element.
|
||
3554 | *
|
||
3555 | * @param $message
|
||
3556 | * Message to display.
|
||
3557 | * @param $group
|
||
3558 | * The group this message belongs to.
|
||
3559 | * @param $ids_to_skip
|
||
3560 | * An optional array of ids to skip when checking for duplicates. It is
|
||
3561 | * always a bug to have duplicate HTML IDs, so this parameter is to enable
|
||
3562 | * incremental fixing of core code. Whenever a test passes this parameter,
|
||
3563 | * it should add a "todo" comment above the call to this function explaining
|
||
3564 | * the legacy bug that the test wishes to ignore and including a link to an
|
||
3565 | * issue that is working to fix that legacy bug.
|
||
3566 | * @return
|
||
3567 | * TRUE on pass, FALSE on fail.
|
||
3568 | */
|
||
3569 | protected function assertNoDuplicateIds($message = '', $group = 'Other', $ids_to_skip = array()) { |
||
3570 | $status = TRUE; |
||
3571 | foreach ($this->xpath('//*[@id]') as $element) { |
||
3572 | $id = (string) $element['id']; |
||
3573 | if (isset($seen_ids[$id]) && !in_array($id, $ids_to_skip)) { |
||
3574 | $this->fail(t('The HTML ID %id is unique.', array('%id' => $id)), $group); |
||
3575 | $status = FALSE; |
||
3576 | } |
||
3577 | $seen_ids[$id] = TRUE; |
||
3578 | } |
||
3579 | return $this->assert($status, $message, $group); |
||
3580 | } |
||
3581 | |||
3582 | /**
|
||
3583 | * Helper function: construct an XPath for the given set of attributes and value.
|
||
3584 | *
|
||
3585 | * @param $attribute
|
||
3586 | * Field attributes.
|
||
3587 | * @param $value
|
||
3588 | * Value of field.
|
||
3589 | * @return
|
||
3590 | * XPath for specified values.
|
||
3591 | */
|
||
3592 | protected function constructFieldXpath($attribute, $value) { |
||
3593 | $xpath = '//textarea[@' . $attribute . '=:value]|//input[@' . $attribute . '=:value]|//select[@' . $attribute . '=:value]'; |
||
3594 | return $this->buildXPathQuery($xpath, array(':value' => $value)); |
||
3595 | } |
||
3596 | |||
3597 | /**
|
||
3598 | * Asserts the page responds with the specified response code.
|
||
3599 | *
|
||
3600 | * @param $code
|
||
3601 | * Response code. For example 200 is a successful page request. For a list
|
||
3602 | * of all codes see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html.
|
||
3603 | * @param $message
|
||
3604 | * Message to display.
|
||
3605 | * @return
|
||
3606 | * Assertion result.
|
||
3607 | */
|
||
3608 | protected function assertResponse($code, $message = '') { |
||
3609 | $curl_code = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE); |
||
3610 | $match = is_array($code) ? in_array($curl_code, $code) : $curl_code == $code; |
||
3611 | return $this->assertTrue($match, $message ? $message : t('HTTP response expected !code, actual !curl_code', array('!code' => $code, '!curl_code' => $curl_code)), t('Browser')); |
||
3612 | } |
||
3613 | |||
3614 | /**
|
||
3615 | * Asserts the page did not return the specified response code.
|
||
3616 | *
|
||
3617 | * @param $code
|
||
3618 | * Response code. For example 200 is a successful page request. For a list
|
||
3619 | * of all codes see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html.
|
||
3620 | * @param $message
|
||
3621 | * Message to display.
|
||
3622 | *
|
||
3623 | * @return
|
||
3624 | * Assertion result.
|
||
3625 | */
|
||
3626 | protected function assertNoResponse($code, $message = '') { |
||
3627 | $curl_code = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE); |
||
3628 | $match = is_array($code) ? in_array($curl_code, $code) : $curl_code == $code; |
||
3629 | return $this->assertFalse($match, $message ? $message : t('HTTP response not expected !code, actual !curl_code', array('!code' => $code, '!curl_code' => $curl_code)), t('Browser')); |
||
3630 | } |
||
3631 | |||
3632 | /**
|
||
3633 | * Asserts that the most recently sent e-mail message has the given value.
|
||
3634 | *
|
||
3635 | * The field in $name must have the content described in $value.
|
||
3636 | *
|
||
3637 | * @param $name
|
||
3638 | * Name of field or message property to assert. Examples: subject, body, id, ...
|
||
3639 | * @param $value
|
||
3640 | * Value of the field to assert.
|
||
3641 | * @param $message
|
||
3642 | * Message to display.
|
||
3643 | *
|
||
3644 | * @return
|
||
3645 | * TRUE on pass, FALSE on fail.
|
||
3646 | */
|
||
3647 | protected function assertMail($name, $value = '', $message = '') { |
||
3648 | $captured_emails = variable_get('drupal_test_email_collector', array()); |
||
3649 | $email = end($captured_emails); |
||
3650 | return $this->assertTrue($email && isset($email[$name]) && $email[$name] == $value, $message, t('E-mail')); |
||
3651 | } |
||
3652 | |||
3653 | /**
|
||
3654 | * Asserts that the most recently sent e-mail message has the string in it.
|
||
3655 | *
|
||
3656 | * @param $field_name
|
||
3657 | * Name of field or message property to assert: subject, body, id, ...
|
||
3658 | * @param $string
|
||
3659 | * String to search for.
|
||
3660 | * @param $email_depth
|
||
3661 | * Number of emails to search for string, starting with most recent.
|
||
3662 | *
|
||
3663 | * @return
|
||
3664 | * TRUE on pass, FALSE on fail.
|
||
3665 | */
|
||
3666 | protected function assertMailString($field_name, $string, $email_depth) { |
||
3667 | $mails = $this->drupalGetMails(); |
||
3668 | $string_found = FALSE; |
||
3669 | for ($i = sizeof($mails) -1; $i >= sizeof($mails) - $email_depth && $i >= 0; $i--) { |
||
3670 | $mail = $mails[$i]; |
||
3671 | // Normalize whitespace, as we don't know what the mail system might have
|
||
3672 | // done. Any run of whitespace becomes a single space.
|
||
3673 | $normalized_mail = preg_replace('/\s+/', ' ', $mail[$field_name]); |
||
3674 | $normalized_string = preg_replace('/\s+/', ' ', $string); |
||
3675 | $string_found = (FALSE !== strpos($normalized_mail, $normalized_string)); |
||
3676 | if ($string_found) { |
||
3677 | break;
|
||
3678 | } |
||
3679 | } |
||
3680 | return $this->assertTrue($string_found, t('Expected text found in @field of email message: "@expected".', array('@field' => $field_name, '@expected' => $string))); |
||
3681 | } |
||
3682 | |||
3683 | /**
|
||
3684 | * Asserts that the most recently sent e-mail message has the pattern in it.
|
||
3685 | *
|
||
3686 | * @param $field_name
|
||
3687 | * Name of field or message property to assert: subject, body, id, ...
|
||
3688 | * @param $regex
|
||
3689 | * Pattern to search for.
|
||
3690 | *
|
||
3691 | * @return
|
||
3692 | * TRUE on pass, FALSE on fail.
|
||
3693 | */
|
||
3694 | protected function assertMailPattern($field_name, $regex, $message) { |
||
3695 | $mails = $this->drupalGetMails(); |
||
3696 | $mail = end($mails); |
||
3697 | $regex_found = preg_match("/$regex/", $mail[$field_name]); |
||
3698 | return $this->assertTrue($regex_found, t('Expected text found in @field of email message: "@expected".', array('@field' => $field_name, '@expected' => $regex))); |
||
3699 | } |
||
3700 | |||
3701 | /**
|
||
3702 | * Outputs to verbose the most recent $count emails sent.
|
||
3703 | *
|
||
3704 | * @param $count
|
||
3705 | * Optional number of emails to output.
|
||
3706 | */
|
||
3707 | protected function verboseEmail($count = 1) { |
||
3708 | $mails = $this->drupalGetMails(); |
||
3709 | for ($i = sizeof($mails) -1; $i >= sizeof($mails) - $count && $i >= 0; $i--) { |
||
3710 | $mail = $mails[$i]; |
||
3711 | $this->verbose(t('Email:') . '<pre>' . print_r($mail, TRUE) . '</pre>'); |
||
3712 | } |
||
3713 | } |
||
3714 | } |
||
3715 | |||
3716 | /**
|
||
3717 | * Logs verbose message in a text file.
|
||
3718 | *
|
||
3719 | * If verbose mode is enabled then page requests will be dumped to a file and
|
||
3720 | * presented on the test result screen. The messages will be placed in a file
|
||
3721 | * located in the simpletest directory in the original file system.
|
||
3722 | *
|
||
3723 | * @param $message
|
||
3724 | * The verbose message to be stored.
|
||
3725 | * @param $original_file_directory
|
||
3726 | * The original file directory, before it was changed for testing purposes.
|
||
3727 | * @param $test_class
|
||
3728 | * The active test case class.
|
||
3729 | *
|
||
3730 | * @return
|
||
3731 | * The ID of the message to be placed in related assertion messages.
|
||
3732 | *
|
||
3733 | * @see DrupalTestCase->originalFileDirectory
|
||
3734 | * @see DrupalWebTestCase->verbose()
|
||
3735 | */
|
||
3736 | function simpletest_verbose($message, $original_file_directory = NULL, $test_class = NULL) { |
||
3737 | static $file_directory = NULL, $class = NULL, $id = 1, $verbose = NULL; |
||
3738 | |||
3739 | // Will pass first time during setup phase, and when verbose is TRUE.
|
||
3740 | if (!isset($original_file_directory) && !$verbose) { |
||
3741 | return FALSE; |
||
3742 | } |
||
3743 | |||
3744 | if ($message && $file_directory) { |
||
3745 | $message = '<hr />ID #' . $id . ' (<a href="' . $class . '-' . ($id - 1) . '.html">Previous</a> | <a href="' . $class . '-' . ($id + 1) . '.html">Next</a>)<hr />' . $message; |
||
3746 | file_put_contents($file_directory . "/simpletest/verbose/$class-$id.html", $message, FILE_APPEND); |
||
3747 | return $id++; |
||
3748 | } |
||
3749 | |||
3750 | if ($original_file_directory) { |
||
3751 | $file_directory = $original_file_directory; |
||
3752 | $class = $test_class; |
||
3753 | $verbose = variable_get('simpletest_verbose', TRUE); |
||
3754 | $directory = $file_directory . '/simpletest/verbose'; |
||
3755 | $writable = file_prepare_directory($directory, FILE_CREATE_DIRECTORY); |
||
3756 | if ($writable && !file_exists($directory . '/.htaccess')) { |
||
3757 | file_put_contents($directory . '/.htaccess', "<IfModule mod_expires.c>\nExpiresActive Off\n</IfModule>\n"); |
||
3758 | } |
||
3759 | return $writable; |
||
3760 | } |
||
3761 | return FALSE; |
||
3762 | } |