PageRenderTime 56ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/modules/simpletest/drupal_web_test_case.php

https://github.com/jacobSingh/drupal
PHP | 2767 lines | 1188 code | 228 blank | 1351 comment | 181 complexity | e68609ca32c0feefd59967f83425a5e1 MD5 | raw file
  1. <?php
  2. // $Id: drupal_web_test_case.php,v 1.188 2010-01-04 23:08:34 webchick Exp $
  3. /**
  4. * Base class for Drupal tests.
  5. *
  6. * Do not extend this class, use one of the subclasses in this file.
  7. */
  8. abstract class DrupalTestCase {
  9. /**
  10. * The test run ID.
  11. *
  12. * @var string
  13. */
  14. protected $testId;
  15. /**
  16. * The original database prefix, before it was changed for testing purposes.
  17. *
  18. * @var string
  19. */
  20. protected $originalPrefix = NULL;
  21. /**
  22. * The original file directory, before it was changed for testing purposes.
  23. *
  24. * @var string
  25. */
  26. protected $originalFileDirectory = NULL;
  27. /**
  28. * Time limit for the test.
  29. */
  30. protected $timeLimit = 500;
  31. /**
  32. * Current results of this test case.
  33. *
  34. * @var Array
  35. */
  36. public $results = array(
  37. '#pass' => 0,
  38. '#fail' => 0,
  39. '#exception' => 0,
  40. '#debug' => 0,
  41. );
  42. /**
  43. * Assertions thrown in that test case.
  44. *
  45. * @var Array
  46. */
  47. protected $assertions = array();
  48. /**
  49. * This class is skipped when looking for the source of an assertion.
  50. *
  51. * When displaying which function an assert comes from, it's not too useful
  52. * to see "drupalWebTestCase->drupalLogin()', we would like to see the test
  53. * that called it. So we need to skip the classes defining these helper
  54. * methods.
  55. */
  56. protected $skipClasses = array(__CLASS__ => TRUE);
  57. /**
  58. * Constructor for DrupalWebTestCase.
  59. *
  60. * @param $test_id
  61. * Tests with the same id are reported together.
  62. */
  63. public function __construct($test_id = NULL) {
  64. $this->testId = $test_id;
  65. }
  66. /**
  67. * Internal helper: stores the assert.
  68. *
  69. * @param $status
  70. * Can be 'pass', 'fail', 'exception'.
  71. * TRUE is a synonym for 'pass', FALSE for 'fail'.
  72. * @param $message
  73. * The message string.
  74. * @param $group
  75. * Which group this assert belongs to.
  76. * @param $caller
  77. * By default, the assert comes from a function whose name starts with
  78. * 'test'. Instead, you can specify where this assert originates from
  79. * by passing in an associative array as $caller. Key 'file' is
  80. * the name of the source file, 'line' is the line number and 'function'
  81. * is the caller function itself.
  82. */
  83. protected function assert($status, $message = '', $group = 'Other', array $caller = NULL) {
  84. global $db_prefix;
  85. // Convert boolean status to string status.
  86. if (is_bool($status)) {
  87. $status = $status ? 'pass' : 'fail';
  88. }
  89. // Increment summary result counter.
  90. $this->results['#' . $status]++;
  91. // Get the function information about the call to the assertion method.
  92. if (!$caller) {
  93. $caller = $this->getAssertionCall();
  94. }
  95. // Switch to non-testing database to store results in.
  96. $current_db_prefix = $db_prefix;
  97. $db_prefix = $this->originalPrefix;
  98. // Creation assertion array that can be displayed while tests are running.
  99. $this->assertions[] = $assertion = array(
  100. 'test_id' => $this->testId,
  101. 'test_class' => get_class($this),
  102. 'status' => $status,
  103. 'message' => $message,
  104. 'message_group' => $group,
  105. 'function' => $caller['function'],
  106. 'line' => $caller['line'],
  107. 'file' => $caller['file'],
  108. );
  109. // Store assertion for display after the test has completed.
  110. db_insert('simpletest')
  111. ->fields($assertion)
  112. ->execute();
  113. // Return to testing prefix.
  114. $db_prefix = $current_db_prefix;
  115. // We do not use a ternary operator here to allow a breakpoint on
  116. // test failure.
  117. if ($status == 'pass') {
  118. return TRUE;
  119. }
  120. else {
  121. return FALSE;
  122. }
  123. }
  124. /**
  125. * Store an assertion from outside the testing context.
  126. *
  127. * This is useful for inserting assertions that can only be recorded after
  128. * the test case has been destroyed, such as PHP fatal errors. The caller
  129. * information is not automatically gathered since the caller is most likely
  130. * inserting the assertion on behalf of other code. In all other respects
  131. * the method behaves just like DrupalTestCase::assert() in terms of storing
  132. * the assertion.
  133. *
  134. * @return
  135. * Message ID of the stored assertion.
  136. *
  137. * @see DrupalTestCase::assert()
  138. * @see DrupalTestCase::deleteAssert()
  139. */
  140. public static function insertAssert($test_id, $test_class, $status, $message = '', $group = 'Other', array $caller = array()) {
  141. // Convert boolean status to string status.
  142. if (is_bool($status)) {
  143. $status = $status ? 'pass' : 'fail';
  144. }
  145. $caller += array(
  146. 'function' => t('Unknown'),
  147. 'line' => 0,
  148. 'file' => t('Unknown'),
  149. );
  150. $assertion = array(
  151. 'test_id' => $test_id,
  152. 'test_class' => $test_class,
  153. 'status' => $status,
  154. 'message' => $message,
  155. 'message_group' => $group,
  156. 'function' => $caller['function'],
  157. 'line' => $caller['line'],
  158. 'file' => $caller['file'],
  159. );
  160. return db_insert('simpletest')
  161. ->fields($assertion)
  162. ->execute();
  163. }
  164. /**
  165. * Delete an assertion record by message ID.
  166. *
  167. * @param $message_id
  168. * Message ID of the assertion to delete.
  169. * @return
  170. * TRUE if the assertion was deleted, FALSE otherwise.
  171. *
  172. * @see DrupalTestCase::insertAssert()
  173. */
  174. public static function deleteAssert($message_id) {
  175. return (bool) db_delete('simpletest')
  176. ->condition('message_id', $message_id)
  177. ->execute();
  178. }
  179. /**
  180. * Cycles through backtrace until the first non-assertion method is found.
  181. *
  182. * @return
  183. * Array representing the true caller.
  184. */
  185. protected function getAssertionCall() {
  186. $backtrace = debug_backtrace();
  187. // The first element is the call. The second element is the caller.
  188. // We skip calls that occurred in one of the methods of our base classes
  189. // or in an assertion function.
  190. while (($caller = $backtrace[1]) &&
  191. ((isset($caller['class']) && isset($this->skipClasses[$caller['class']])) ||
  192. substr($caller['function'], 0, 6) == 'assert')) {
  193. // We remove that call.
  194. array_shift($backtrace);
  195. }
  196. return _drupal_get_last_caller($backtrace);
  197. }
  198. /**
  199. * Check to see if a value is not false (not an empty string, 0, NULL, or FALSE).
  200. *
  201. * @param $value
  202. * The value on which the assertion is to be done.
  203. * @param $message
  204. * The message to display along with the assertion.
  205. * @param $group
  206. * The type of assertion - examples are "Browser", "PHP".
  207. * @return
  208. * TRUE if the assertion succeeded, FALSE otherwise.
  209. */
  210. protected function assertTrue($value, $message = '', $group = 'Other') {
  211. return $this->assert((bool) $value, $message ? $message : t('Value @value is TRUE.', array('@value' => var_export($value, TRUE))), $group);
  212. }
  213. /**
  214. * Check to see if a value is false (an empty string, 0, NULL, or FALSE).
  215. *
  216. * @param $value
  217. * The value on which the assertion is to be done.
  218. * @param $message
  219. * The message to display along with the assertion.
  220. * @param $group
  221. * The type of assertion - examples are "Browser", "PHP".
  222. * @return
  223. * TRUE if the assertion succeeded, FALSE otherwise.
  224. */
  225. protected function assertFalse($value, $message = '', $group = 'Other') {
  226. return $this->assert(!$value, $message ? $message : t('Value @value is FALSE.', array('@value' => var_export($value, TRUE))), $group);
  227. }
  228. /**
  229. * Check to see if a value is NULL.
  230. *
  231. * @param $value
  232. * The value on which the assertion is to be done.
  233. * @param $message
  234. * The message to display along with the assertion.
  235. * @param $group
  236. * The type of assertion - examples are "Browser", "PHP".
  237. * @return
  238. * TRUE if the assertion succeeded, FALSE otherwise.
  239. */
  240. protected function assertNull($value, $message = '', $group = 'Other') {
  241. return $this->assert(!isset($value), $message ? $message : t('Value @value is NULL.', array('@value' => var_export($value, TRUE))), $group);
  242. }
  243. /**
  244. * Check to see if a value is not NULL.
  245. *
  246. * @param $value
  247. * The value on which the assertion is to be done.
  248. * @param $message
  249. * The message to display along with the assertion.
  250. * @param $group
  251. * The type of assertion - examples are "Browser", "PHP".
  252. * @return
  253. * TRUE if the assertion succeeded, FALSE otherwise.
  254. */
  255. protected function assertNotNull($value, $message = '', $group = 'Other') {
  256. return $this->assert(isset($value), $message ? $message : t('Value @value is not NULL.', array('@value' => var_export($value, TRUE))), $group);
  257. }
  258. /**
  259. * Check to see if two values are equal.
  260. *
  261. * @param $first
  262. * The first value to check.
  263. * @param $second
  264. * The second value to check.
  265. * @param $message
  266. * The message to display along with the assertion.
  267. * @param $group
  268. * The type of assertion - examples are "Browser", "PHP".
  269. * @return
  270. * TRUE if the assertion succeeded, FALSE otherwise.
  271. */
  272. protected function assertEqual($first, $second, $message = '', $group = 'Other') {
  273. 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);
  274. }
  275. /**
  276. * Check to see if two values are not equal.
  277. *
  278. * @param $first
  279. * The first value to check.
  280. * @param $second
  281. * The second value to check.
  282. * @param $message
  283. * The message to display along with the assertion.
  284. * @param $group
  285. * The type of assertion - examples are "Browser", "PHP".
  286. * @return
  287. * TRUE if the assertion succeeded, FALSE otherwise.
  288. */
  289. protected function assertNotEqual($first, $second, $message = '', $group = 'Other') {
  290. 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);
  291. }
  292. /**
  293. * Check to see if two values are identical.
  294. *
  295. * @param $first
  296. * The first value to check.
  297. * @param $second
  298. * The second value to check.
  299. * @param $message
  300. * The message to display along with the assertion.
  301. * @param $group
  302. * The type of assertion - examples are "Browser", "PHP".
  303. * @return
  304. * TRUE if the assertion succeeded, FALSE otherwise.
  305. */
  306. protected function assertIdentical($first, $second, $message = '', $group = 'Other') {
  307. 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);
  308. }
  309. /**
  310. * Check to see if two values are not identical.
  311. *
  312. * @param $first
  313. * The first value to check.
  314. * @param $second
  315. * The second value to check.
  316. * @param $message
  317. * The message to display along with the assertion.
  318. * @param $group
  319. * The type of assertion - examples are "Browser", "PHP".
  320. * @return
  321. * TRUE if the assertion succeeded, FALSE otherwise.
  322. */
  323. protected function assertNotIdentical($first, $second, $message = '', $group = 'Other') {
  324. 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);
  325. }
  326. /**
  327. * Fire an assertion that is always positive.
  328. *
  329. * @param $message
  330. * The message to display along with the assertion.
  331. * @param $group
  332. * The type of assertion - examples are "Browser", "PHP".
  333. * @return
  334. * TRUE.
  335. */
  336. protected function pass($message = NULL, $group = 'Other') {
  337. return $this->assert(TRUE, $message, $group);
  338. }
  339. /**
  340. * Fire an assertion that is always negative.
  341. *
  342. * @param $message
  343. * The message to display along with the assertion.
  344. * @param $group
  345. * The type of assertion - examples are "Browser", "PHP".
  346. * @return
  347. * FALSE.
  348. */
  349. protected function fail($message = NULL, $group = 'Other') {
  350. return $this->assert(FALSE, $message, $group);
  351. }
  352. /**
  353. * Fire an error assertion.
  354. *
  355. * @param $message
  356. * The message to display along with the assertion.
  357. * @param $group
  358. * The type of assertion - examples are "Browser", "PHP".
  359. * @param $caller
  360. * The caller of the error.
  361. * @return
  362. * FALSE.
  363. */
  364. protected function error($message = '', $group = 'Other', array $caller = NULL) {
  365. if ($group == 'User notice') {
  366. // Since 'User notice' is set by trigger_error() which is used for debug
  367. // set the message to a status of 'debug'.
  368. return $this->assert('debug', $message, 'Debug', $caller);
  369. }
  370. return $this->assert('exception', $message, $group, $caller);
  371. }
  372. /**
  373. * Run all tests in this class.
  374. */
  375. public function run() {
  376. // Initialize verbose debugging.
  377. simpletest_verbose(NULL, file_directory_path(), get_class($this));
  378. // HTTP auth settings (<username>:<password>) for the simpletest browser
  379. // when sending requests to the test site.
  380. $this->httpauth_method = variable_get('simpletest_httpauth_method', CURLAUTH_BASIC);
  381. $username = variable_get('simpletest_httpauth_username', NULL);
  382. $password = variable_get('simpletest_httpauth_password', NULL);
  383. if ($username && $password) {
  384. $this->httpauth_credentials = $username . ':' . $password;
  385. }
  386. set_error_handler(array($this, 'errorHandler'));
  387. $class = get_class($this);
  388. // Iterate through all the methods in this class.
  389. foreach (get_class_methods($class) as $method) {
  390. // If the current method starts with "test", run it - it's a test.
  391. if (strtolower(substr($method, 0, 4)) == 'test') {
  392. // Insert a fail record. This will be deleted on completion to ensure
  393. // that testing completed.
  394. $method_info = new ReflectionMethod($class, $method);
  395. $caller = array(
  396. 'file' => $method_info->getFileName(),
  397. 'line' => $method_info->getStartLine(),
  398. 'function' => $class . '->' . $method . '()',
  399. );
  400. $completion_check_id = DrupalTestCase::insertAssert($this->testId, $class, FALSE, t('The test did not complete due to a fatal error.'), 'Completion check', $caller);
  401. $this->setUp();
  402. try {
  403. $this->$method();
  404. // Finish up.
  405. }
  406. catch (Exception $e) {
  407. $this->exceptionHandler($e);
  408. }
  409. $this->tearDown();
  410. // Remove the completion check record.
  411. DrupalTestCase::deleteAssert($completion_check_id);
  412. }
  413. }
  414. // Clear out the error messages and restore error handler.
  415. drupal_get_messages();
  416. restore_error_handler();
  417. }
  418. /**
  419. * Handle errors during test runs.
  420. *
  421. * Because this is registered in set_error_handler(), it has to be public.
  422. * @see set_error_handler
  423. */
  424. public function errorHandler($severity, $message, $file = NULL, $line = NULL) {
  425. if ($severity & error_reporting()) {
  426. $error_map = array(
  427. E_STRICT => 'Run-time notice',
  428. E_WARNING => 'Warning',
  429. E_NOTICE => 'Notice',
  430. E_CORE_ERROR => 'Core error',
  431. E_CORE_WARNING => 'Core warning',
  432. E_USER_ERROR => 'User error',
  433. E_USER_WARNING => 'User warning',
  434. E_USER_NOTICE => 'User notice',
  435. E_RECOVERABLE_ERROR => 'Recoverable error',
  436. );
  437. $backtrace = debug_backtrace();
  438. $this->error($message, $error_map[$severity], _drupal_get_last_caller($backtrace));
  439. }
  440. return TRUE;
  441. }
  442. /**
  443. * Handle exceptions.
  444. *
  445. * @see set_exception_handler
  446. */
  447. protected function exceptionHandler($exception) {
  448. $backtrace = $exception->getTrace();
  449. // Push on top of the backtrace the call that generated the exception.
  450. array_unshift($backtrace, array(
  451. 'line' => $exception->getLine(),
  452. 'file' => $exception->getFile(),
  453. ));
  454. $this->error($exception->getMessage(), 'Uncaught exception', _drupal_get_last_caller($backtrace));
  455. }
  456. /**
  457. * Generates a random string of ASCII characters of codes 32 to 126.
  458. *
  459. * The generated string includes alpha-numeric characters and common misc
  460. * characters. Use this method when testing general input where the content
  461. * is not restricted.
  462. *
  463. * @param $length
  464. * Length of random string to generate which will be appended to $db_prefix.
  465. * @return
  466. * Randomly generated string.
  467. */
  468. public static function randomString($length = 8) {
  469. global $db_prefix;
  470. $str = '';
  471. for ($i = 0; $i < $length; $i++) {
  472. $str .= chr(mt_rand(32, 126));
  473. }
  474. return str_replace('simpletest', 's', $db_prefix) . $str;
  475. }
  476. /**
  477. * Generates a random string containing letters and numbers.
  478. *
  479. * The letters may be upper or lower case. This method is better for
  480. * restricted inputs that do not accept certain characters. For example,
  481. * when testing input fields that require machine readable values (ie without
  482. * spaces and non-standard characters) this method is best.
  483. *
  484. * @param $length
  485. * Length of random string to generate which will be appended to $db_prefix.
  486. * @return
  487. * Randomly generated string.
  488. */
  489. public static function randomName($length = 8) {
  490. global $db_prefix;
  491. $values = array_merge(range(65, 90), range(97, 122), range(48, 57));
  492. $max = count($values) - 1;
  493. $str = '';
  494. for ($i = 0; $i < $length; $i++) {
  495. $str .= chr($values[mt_rand(0, $max)]);
  496. }
  497. return str_replace('simpletest', 's', $db_prefix) . $str;
  498. }
  499. }
  500. /**
  501. * Test case for Drupal unit tests.
  502. *
  503. * These tests can not access the database nor files. Calling any Drupal
  504. * function that needs the database will throw exceptions. These include
  505. * watchdog(), function_exists(), module_implements(),
  506. * module_invoke_all() etc.
  507. */
  508. class DrupalUnitTestCase extends DrupalTestCase {
  509. /**
  510. * Constructor for DrupalUnitTestCase.
  511. */
  512. function __construct($test_id = NULL) {
  513. parent::__construct($test_id);
  514. $this->skipClasses[__CLASS__] = TRUE;
  515. }
  516. protected function setUp() {
  517. global $db_prefix, $conf;
  518. // Store necessary current values before switching to prefixed database.
  519. $this->originalPrefix = $db_prefix;
  520. $this->originalFileDirectory = file_directory_path();
  521. // Reset all statics so that test is performed with a clean environment.
  522. drupal_static_reset();
  523. // Generate temporary prefixed database to ensure that tests have a clean starting point.
  524. $db_prefix = Database::getConnection()->prefixTables('{simpletest' . mt_rand(1000, 1000000) . '}');
  525. $conf['file_public_path'] = $this->originalFileDirectory . '/' . $db_prefix;
  526. // If locale is enabled then t() will try to access the database and
  527. // subsequently will fail as the database is not accessible.
  528. $module_list = module_list();
  529. if (isset($module_list['locale'])) {
  530. $this->originalModuleList = $module_list;
  531. unset($module_list['locale']);
  532. module_list(TRUE, FALSE, FALSE, $module_list);
  533. }
  534. }
  535. protected function tearDown() {
  536. global $db_prefix, $conf;
  537. if (preg_match('/simpletest\d+/', $db_prefix)) {
  538. $conf['file_public_path'] = $this->originalFileDirectory;
  539. // Return the database prefix to the original.
  540. $db_prefix = $this->originalPrefix;
  541. // Restore modules if necessary.
  542. if (isset($this->originalModuleList)) {
  543. module_list(TRUE, FALSE, FALSE, $this->originalModuleList);
  544. }
  545. }
  546. }
  547. }
  548. /**
  549. * Test case for typical Drupal tests.
  550. */
  551. class DrupalWebTestCase extends DrupalTestCase {
  552. /**
  553. * The URL currently loaded in the internal browser.
  554. *
  555. * @var string
  556. */
  557. protected $url;
  558. /**
  559. * The handle of the current cURL connection.
  560. *
  561. * @var resource
  562. */
  563. protected $curlHandle;
  564. /**
  565. * The headers of the page currently loaded in the internal browser.
  566. *
  567. * @var Array
  568. */
  569. protected $headers;
  570. /**
  571. * The content of the page currently loaded in the internal browser.
  572. *
  573. * @var string
  574. */
  575. protected $content;
  576. /**
  577. * The content of the page currently loaded in the internal browser (plain text version).
  578. *
  579. * @var string
  580. */
  581. protected $plainTextContent;
  582. /**
  583. * The parsed version of the page.
  584. *
  585. * @var SimpleXMLElement
  586. */
  587. protected $elements = NULL;
  588. /**
  589. * The current user logged in using the internal browser.
  590. *
  591. * @var bool
  592. */
  593. protected $loggedInUser = FALSE;
  594. /**
  595. * The current cookie file used by cURL.
  596. *
  597. * We do not reuse the cookies in further runs, so we do not need a file
  598. * but we still need cookie handling, so we set the jar to NULL.
  599. */
  600. protected $cookieFile = NULL;
  601. /**
  602. * Additional cURL options.
  603. *
  604. * DrupalWebTestCase itself never sets this but always obeys what is set.
  605. */
  606. protected $additionalCurlOptions = array();
  607. /**
  608. * The original user, before it was changed to a clean uid = 1 for testing purposes.
  609. *
  610. * @var object
  611. */
  612. protected $originalUser = NULL;
  613. /**
  614. * HTTP authentication method
  615. */
  616. protected $httpauth_method = CURLAUTH_BASIC;
  617. /**
  618. * HTTP authentication credentials (<username>:<password>).
  619. */
  620. protected $httpauth_credentials = NULL;
  621. /**
  622. * The current session name, if available.
  623. */
  624. protected $session_name = NULL;
  625. /**
  626. * The current session ID, if available.
  627. */
  628. protected $session_id = NULL;
  629. /**
  630. * Whether the files were copied to the test files directory.
  631. */
  632. protected $generatedTestFiles = FALSE;
  633. /**
  634. * Constructor for DrupalWebTestCase.
  635. */
  636. function __construct($test_id = NULL) {
  637. parent::__construct($test_id);
  638. $this->skipClasses[__CLASS__] = TRUE;
  639. }
  640. /**
  641. * Get a node from the database based on its title.
  642. *
  643. * @param title
  644. * A node title, usually generated by $this->randomName().
  645. *
  646. * @return
  647. * A node object matching $title.
  648. */
  649. function drupalGetNodeByTitle($title) {
  650. $nodes = node_load_multiple(array(), array('title' => $title));
  651. // Load the first node returned from the database.
  652. $returned_node = reset($nodes);
  653. return $returned_node;
  654. }
  655. /**
  656. * Creates a node based on default settings.
  657. *
  658. * @param $settings
  659. * An associative array of settings to change from the defaults, keys are
  660. * node properties, for example 'title' => 'Hello, world!'.
  661. * @return
  662. * Created node object.
  663. */
  664. protected function drupalCreateNode($settings = array()) {
  665. // Populate defaults array.
  666. $settings += array(
  667. 'body' => array(LANGUAGE_NONE => array(array())),
  668. 'title' => array(LANGUAGE_NONE => array(array('value' => $this->randomName(8)))),
  669. 'comment' => 2,
  670. 'changed' => REQUEST_TIME,
  671. 'moderate' => 0,
  672. 'promote' => 0,
  673. 'revision' => 1,
  674. 'log' => '',
  675. 'status' => 1,
  676. 'sticky' => 0,
  677. 'type' => 'page',
  678. 'revisions' => NULL,
  679. 'taxonomy' => NULL,
  680. 'language' => LANGUAGE_NONE,
  681. );
  682. // Use the original node's created time for existing nodes.
  683. if (isset($settings['created']) && !isset($settings['date'])) {
  684. $settings['date'] = format_date($settings['created'], 'custom', 'Y-m-d H:i:s O');
  685. }
  686. // If the node's user uid is not specified manually, use the currently
  687. // logged in user if available, or else the user running the test.
  688. if (!isset($settings['uid'])) {
  689. if ($this->loggedInUser) {
  690. $settings['uid'] = $this->loggedInUser->uid;
  691. }
  692. else {
  693. global $user;
  694. $settings['uid'] = $user->uid;
  695. }
  696. }
  697. // Merge body field value and format separately.
  698. $body = array(
  699. 'value' => $this->randomName(32),
  700. 'format' => filter_default_format(),
  701. );
  702. $settings['body'][$settings['language']][0] += $body;
  703. $node = (object) $settings;
  704. node_save($node);
  705. // Small hack to link revisions to our test user.
  706. db_update('node_revision')
  707. ->fields(array('uid' => $node->uid))
  708. ->condition('vid', $node->vid)
  709. ->execute();
  710. return $node;
  711. }
  712. /**
  713. * Creates a custom content type based on default settings.
  714. *
  715. * @param $settings
  716. * An array of settings to change from the defaults.
  717. * Example: 'type' => 'foo'.
  718. * @return
  719. * Created content type.
  720. */
  721. protected function drupalCreateContentType($settings = array()) {
  722. // Find a non-existent random type name.
  723. do {
  724. $name = strtolower($this->randomName(8));
  725. } while (node_type_get_type($name));
  726. // Populate defaults array.
  727. $defaults = array(
  728. 'type' => $name,
  729. 'name' => $name,
  730. 'base' => 'node_content',
  731. 'description' => '',
  732. 'help' => '',
  733. 'title_label' => 'Title',
  734. 'body_label' => 'Body',
  735. 'has_title' => 1,
  736. 'has_body' => 1,
  737. );
  738. // Imposed values for a custom type.
  739. $forced = array(
  740. 'orig_type' => '',
  741. 'old_type' => '',
  742. 'module' => 'node',
  743. 'custom' => 1,
  744. 'modified' => 1,
  745. 'locked' => 0,
  746. );
  747. $type = $forced + $settings + $defaults;
  748. $type = (object)$type;
  749. $saved_type = node_type_save($type);
  750. node_types_rebuild();
  751. menu_rebuild();
  752. $this->assertEqual($saved_type, SAVED_NEW, t('Created content type %type.', array('%type' => $type->type)));
  753. // Reset permissions so that permissions for this content type are available.
  754. $this->checkPermissions(array(), TRUE);
  755. return $type;
  756. }
  757. /**
  758. * Get a list files that can be used in tests.
  759. *
  760. * @param $type
  761. * File type, possible values: 'binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'.
  762. * @param $size
  763. * File size in bytes to match. Please check the tests/files folder.
  764. * @return
  765. * List of files that match filter.
  766. */
  767. protected function drupalGetTestFiles($type, $size = NULL) {
  768. if (empty($this->generatedTestFiles)) {
  769. // Generate binary test files.
  770. $lines = array(64, 1024);
  771. $count = 0;
  772. foreach ($lines as $line) {
  773. simpletest_generate_file('binary-' . $count++, 64, $line, 'binary');
  774. }
  775. // Generate text test files.
  776. $lines = array(16, 256, 1024, 2048, 20480);
  777. $count = 0;
  778. foreach ($lines as $line) {
  779. simpletest_generate_file('text-' . $count++, 64, $line);
  780. }
  781. // Copy other test files from simpletest.
  782. $original = drupal_get_path('module', 'simpletest') . '/files';
  783. $files = file_scan_directory($original, '/(html|image|javascript|php|sql)-.*/');
  784. $destination_path = file_directory_path('public');
  785. foreach ($files as $file) {
  786. file_unmanaged_copy($file->uri, $destination_path);
  787. }
  788. $this->generatedTestFiles = TRUE;
  789. }
  790. $files = array();
  791. // Make sure type is valid.
  792. if (in_array($type, array('binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'))) {
  793. $files = file_scan_directory(file_directory_path('public'), '/' . $type . '\-.*/');
  794. // If size is set then remove any files that are not of that size.
  795. if ($size !== NULL) {
  796. foreach ($files as $file) {
  797. $stats = stat($file->uri);
  798. if ($stats['size'] != $size) {
  799. unset($files[$file->uri]);
  800. }
  801. }
  802. }
  803. }
  804. usort($files, array($this, 'drupalCompareFiles'));
  805. return $files;
  806. }
  807. /**
  808. * Compare two files based on size and file name.
  809. */
  810. protected function drupalCompareFiles($file1, $file2) {
  811. $compare_size = filesize($file1->uri) - filesize($file2->uri);
  812. if ($compare_size) {
  813. // Sort by file size.
  814. return $compare_size;
  815. }
  816. else {
  817. // The files were the same size, so sort alphabetically.
  818. return strnatcmp($file1->name, $file2->name);
  819. }
  820. }
  821. /**
  822. * Create a user with a given set of permissions. The permissions correspond to the
  823. * names given on the privileges page.
  824. *
  825. * @param $permissions
  826. * Array of permission names to assign to user.
  827. * @return
  828. * A fully loaded user object with pass_raw property, or FALSE if account
  829. * creation fails.
  830. */
  831. protected function drupalCreateUser($permissions = array('access comments', 'access content', 'post comments', 'post comments without approval')) {
  832. // Create a role with the given permission set.
  833. if (!($rid = $this->drupalCreateRole($permissions))) {
  834. return FALSE;
  835. }
  836. // Create a user assigned to that role.
  837. $edit = array();
  838. $edit['name'] = $this->randomName();
  839. $edit['mail'] = $edit['name'] . '@example.com';
  840. $edit['roles'] = array($rid => $rid);
  841. $edit['pass'] = user_password();
  842. $edit['status'] = 1;
  843. $account = user_save(drupal_anonymous_user(), $edit);
  844. $this->assertTrue(!empty($account->uid), t('User created with name %name and pass %pass', array('%name' => $edit['name'], '%pass' => $edit['pass'])), t('User login'));
  845. if (empty($account->uid)) {
  846. return FALSE;
  847. }
  848. // Add the raw password so that we can log in as this user.
  849. $account->pass_raw = $edit['pass'];
  850. return $account;
  851. }
  852. /**
  853. * Internal helper function; Create a role with specified permissions.
  854. *
  855. * @param $permissions
  856. * Array of permission names to assign to role.
  857. * @param $name
  858. * (optional) String for the name of the role. Defaults to a random string.
  859. * @return
  860. * Role ID of newly created role, or FALSE if role creation failed.
  861. */
  862. protected function drupalCreateRole(array $permissions, $name = NULL) {
  863. // Generate random name if it was not passed.
  864. if (!$name) {
  865. $name = $this->randomName();
  866. }
  867. // Check the all the permissions strings are valid.
  868. if (!$this->checkPermissions($permissions)) {
  869. return FALSE;
  870. }
  871. // Create new role.
  872. $role = new stdClass();
  873. $role->name = $name;
  874. user_role_save($role);
  875. user_role_grant_permissions($role->rid, $permissions);
  876. $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'));
  877. if ($role && !empty($role->rid)) {
  878. $count = db_query('SELECT COUNT(*) FROM {role_permission} WHERE rid = :rid', array(':rid' => $role->rid))->fetchField();
  879. $this->assertTrue($count == count($permissions), t('Created permissions: @perms', array('@perms' => implode(', ', $permissions))), t('Role'));
  880. return $role->rid;
  881. }
  882. else {
  883. return FALSE;
  884. }
  885. }
  886. /**
  887. * Check to make sure that the array of permissions are valid.
  888. *
  889. * @param $permissions
  890. * Permissions to check.
  891. * @param $reset
  892. * Reset cached available permissions.
  893. * @return
  894. * TRUE or FALSE depending on whether the permissions are valid.
  895. */
  896. protected function checkPermissions(array $permissions, $reset = FALSE) {
  897. $available = &drupal_static(__FUNCTION__);
  898. if (!isset($available) || $reset) {
  899. $available = array_keys(module_invoke_all('permission'));
  900. }
  901. $valid = TRUE;
  902. foreach ($permissions as $permission) {
  903. if (!in_array($permission, $available)) {
  904. $this->fail(t('Invalid permission %permission.', array('%permission' => $permission)), t('Role'));
  905. $valid = FALSE;
  906. }
  907. }
  908. return $valid;
  909. }
  910. /**
  911. * Log in a user with the internal browser.
  912. *
  913. * If a user is already logged in, then the current user is logged out before
  914. * logging in the specified user.
  915. *
  916. * Please note that neither the global $user nor the passed in user object is
  917. * populated with data of the logged in user. If you need full access to the
  918. * user object after logging in, it must be updated manually. If you also need
  919. * access to the plain-text password of the user (set by drupalCreateUser()),
  920. * e.g. to login the same user again, then it must be re-assigned manually.
  921. * For example:
  922. * @code
  923. * // Create a user.
  924. * $account = $this->drupalCreateUser(array());
  925. * $this->drupalLogin($account);
  926. * // Load real user object.
  927. * $pass_raw = $account->pass_raw;
  928. * $account = user_load($account->uid);
  929. * $account->pass_raw = $pass_raw;
  930. * @endcode
  931. *
  932. * @param $user
  933. * User object representing the user to login.
  934. *
  935. * @see drupalCreateUser()
  936. */
  937. protected function drupalLogin(stdClass $user) {
  938. if ($this->loggedInUser) {
  939. $this->drupalLogout();
  940. }
  941. $edit = array(
  942. 'name' => $user->name,
  943. 'pass' => $user->pass_raw
  944. );
  945. $this->drupalPost('user', $edit, t('Log in'));
  946. // If a "log out" link appears on the page, it is almost certainly because
  947. // the login was successful.
  948. $pass = $this->assertLink(t('Log out'), 0, t('User %name successfully logged in.', array('%name' => $user->name)), t('User login'));
  949. if ($pass) {
  950. $this->loggedInUser = $user;
  951. }
  952. }
  953. /**
  954. * Generate a token for the currently logged in user.
  955. */
  956. protected function drupalGetToken($value = '') {
  957. $private_key = drupal_get_private_key();
  958. return md5($this->session_id . $value . $private_key);
  959. }
  960. /*
  961. * Logs a user out of the internal browser, then check the login page to confirm logout.
  962. */
  963. protected function drupalLogout() {
  964. // Make a request to the logout page, and redirect to the user page, the
  965. // idea being if you were properly logged out you should be seeing a login
  966. // screen.
  967. $this->drupalGet('user/logout', array('query' => array('destination' => 'user')));
  968. $pass = $this->assertField('name', t('Username field found.'), t('Logout'));
  969. $pass = $pass && $this->assertField('pass', t('Password field found.'), t('Logout'));
  970. if ($pass) {
  971. $this->loggedInUser = FALSE;
  972. }
  973. }
  974. /**
  975. * Generates a random database prefix, runs the install scripts on the
  976. * prefixed database and enable the specified modules. After installation
  977. * many caches are flushed and the internal browser is setup so that the
  978. * page requests will run on the new prefix. A temporary files directory
  979. * is created with the same name as the database prefix.
  980. *
  981. * @param ...
  982. * List of modules to enable for the duration of the test.
  983. */
  984. protected function setUp() {
  985. global $db_prefix, $user, $language;
  986. // Store necessary current values before switching to prefixed database.
  987. $this->originalLanguage = $language;
  988. $this->originalLanguageDefault = variable_get('language_default');
  989. $this->originalPrefix = $db_prefix;
  990. $this->originalFileDirectory = file_directory_path();
  991. $this->originalProfile = drupal_get_profile();
  992. $clean_url_original = variable_get('clean_url', 0);
  993. // Generate temporary prefixed database to ensure that tests have a clean starting point.
  994. $db_prefix_new = Database::getConnection()->prefixTables('{simpletest' . mt_rand(1000, 1000000) . '}');
  995. db_update('simpletest_test_id')
  996. ->fields(array('last_prefix' => $db_prefix_new))
  997. ->condition('test_id', $this->testId)
  998. ->execute();
  999. $db_prefix = $db_prefix_new;
  1000. // Create test directory ahead of installation so fatal errors and debug
  1001. // information can be logged during installation process.
  1002. // Use temporary files directory with the same prefix as the database.
  1003. $public_files_directory = $this->originalFileDirectory . '/simpletest/' . substr($db_prefix, 10);
  1004. $private_files_directory = $public_files_directory . '/private';
  1005. $temp_files_directory = $private_files_directory . '/temp';
  1006. // Create the directories
  1007. file_prepare_directory($public_files_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
  1008. file_prepare_directory($private_files_directory, FILE_CREATE_DIRECTORY);
  1009. file_prepare_directory($temp_files_directory, FILE_CREATE_DIRECTORY);
  1010. $this->generatedTestFiles = FALSE;
  1011. // Log fatal errors.
  1012. ini_set('log_errors', 1);
  1013. ini_set('error_log', $public_files_directory . '/error.log');
  1014. // Reset all statics so that test is performed with a clean environment.
  1015. drupal_static_reset();
  1016. include_once DRUPAL_ROOT . '/includes/install.inc';
  1017. drupal_install_system();
  1018. $this->preloadRegistry();
  1019. // Include the default profile
  1020. variable_set('install_profile', 'standard');
  1021. $profile_details = install_profile_info('standard', 'en');
  1022. // Install the modules specified by the default profile.
  1023. drupal_install_modules($profile_details['dependencies'], TRUE);
  1024. drupal_static_reset('_node_types_build');
  1025. if ($modules = func_get_args()) {
  1026. // Install modules needed for this test.
  1027. drupal_install_modules($modules, TRUE);
  1028. }
  1029. // Because the schema is static cached, we need to flush
  1030. // it between each run. If we don't, then it will contain
  1031. // stale data for the previous run's database prefix and all
  1032. // calls to it will fail.
  1033. drupal_get_schema(NULL, TRUE);
  1034. // Run default profile tasks.
  1035. $install_state = array();
  1036. drupal_install_modules(array('standard'), TRUE);
  1037. // Rebuild caches.
  1038. node_types_rebuild();
  1039. actions_synchronize();
  1040. _drupal_flush_css_js();
  1041. $this->refreshVariables();
  1042. $this->checkPermissions(array(), TRUE);
  1043. // Log in with a clean $user.
  1044. $this->originalUser = $user;
  1045. drupal_save_session(FALSE);
  1046. $user = user_load(1);
  1047. // Restore necessary variables.
  1048. variable_set('install_task', 'done');
  1049. variable_set('clean_url', $clean_url_original);
  1050. variable_set('site_mail', 'simpletest@example.com');
  1051. // Set up English language.
  1052. unset($GLOBALS['conf']['language_default']);
  1053. $language = language_default();
  1054. // Set path variables
  1055. variable_set('file_public_path', $public_files_directory);
  1056. variable_set('file_private_path', $private_files_directory);
  1057. variable_set('file_temporary_path', $temp_files_directory);
  1058. // Use the test mail class instead of the default mail handler class.
  1059. variable_set('mail_system', array('default-system' => 'TestingMailSystem'));
  1060. drupal_set_time_limit($this->timeLimit);
  1061. }
  1062. /**
  1063. * This method is called by DrupalWebTestCase::setUp, and preloads the
  1064. * registry from the testing site to cut down on the time it takes to
  1065. * setup a clean environment for the current test run.
  1066. */
  1067. protected function preloadRegistry() {
  1068. db_query('INSERT INTO {registry} SELECT * FROM ' . $this->originalPrefix . 'registry');
  1069. db_query('INSERT INTO {registry_file} SELECT * FROM ' . $this->originalPrefix . 'registry_file');
  1070. }
  1071. /**
  1072. * Refresh the in-memory set of variables. Useful after a page request is made
  1073. * that changes a variable in a different thread.
  1074. *
  1075. * In other words calling a settings page with $this->drupalPost() with a changed
  1076. * value would update a variable to reflect that change, but in the thread that
  1077. * made the call (thread running the test) the changed variable would not be
  1078. * picked up.
  1079. *
  1080. * This method clears the variables cache and loads a fresh copy from the database
  1081. * to ensure that the most up-to-date set of variables is loaded.
  1082. */
  1083. protected function refreshVariables() {
  1084. global $conf;
  1085. cache_clear_all('variables', 'cache_bootstrap');
  1086. $conf = variable_initialize();
  1087. }
  1088. /**
  1089. * Delete created files and temporary files directory, delete the tables created by setUp(),
  1090. * and reset the database prefix.
  1091. */
  1092. protected function tearDown() {
  1093. global $db_prefix, $user, $language;
  1094. // In case a fatal error occured that was not in the test process read the
  1095. // log to pick up any fatal errors.
  1096. $db_prefix_temp = $db_prefix;
  1097. $db_prefix = $this->originalPrefix;
  1098. simpletest_log_read($this->testId, $db_prefix, get_class($this), TRUE);
  1099. $db_prefix = $db_prefix_temp;
  1100. $emailCount = count(variable_get('drupal_test_email_collector', array()));
  1101. if ($emailCount) {
  1102. $message = format_plural($emailCount, t('!count e-mail was sent during this test.'), t('!count e-mails were sent during this test.'), array('!count' => $emailCount));
  1103. $this->pass($message, t('E-mail'));
  1104. }
  1105. if (preg_match('/simpletest\d+/', $db_prefix)) {
  1106. // Delete temporary files directory.
  1107. file_unmanaged_delete_recursive($this->originalFileDirectory . '/simpletest/' . substr($db_prefix, 10));
  1108. // Remove all prefixed tables (all the tables in the schema).
  1109. $schema = drupal_get_schema(NULL, TRUE);
  1110. $ret = array();
  1111. foreach ($schema as $name => $table) {
  1112. db_drop_table($name);
  1113. }
  1114. // Return the database prefix to the original.
  1115. $db_prefix = $this->originalPrefix;
  1116. // Return the user to the original one.
  1117. $user = $this->originalUser;
  1118. drupal_save_session(TRUE);
  1119. // Ensure that internal logged in variable and cURL options are reset.
  1120. $this->loggedInUser = FALSE;
  1121. $this->additionalCurlOptions = array();
  1122. // Reload module list and implementations to ensure that test module hooks
  1123. // aren't called after tests.
  1124. module_list(TRUE);
  1125. module_implements('', FALSE, TRUE);
  1126. // Reset the Field API.
  1127. field_cache_clear();
  1128. // Rebuild caches.
  1129. $this->refreshVariables();
  1130. // Reset language.
  1131. $language = $this->originalLanguage;
  1132. if ($this->originalLanguageDefault) {
  1133. $GLOBALS['conf']['language_default'] = $this->originalLanguageDefault;
  1134. }
  1135. // Close the CURL handler.
  1136. $this->curlClose();
  1137. }
  1138. }
  1139. /**
  1140. * Initializes the cURL connection.
  1141. *
  1142. * If the simpletest_httpauth_credentials variable is set, this function will
  1143. * add HTTP authentication headers. This is necessary for testing sites that
  1144. * are protected by login credentials from public access.
  1145. * See the description of $curl_options for other options.
  1146. */
  1147. protected function curlInitialize() {
  1148. global $base_url, $db_prefix;
  1149. if (!isset($this->curlHandle)) {
  1150. $this->curlHandle = curl_init();
  1151. $curl_options = $this->additionalCurlOptions + array(
  1152. CURLOPT_COOKIEJAR => $this->cookieFile,
  1153. CURLOPT_URL => $base_url,
  1154. CURLOPT_FOLLOWLOCATION => TRUE,
  1155. CURLOPT_MAXREDIRS => 5,
  1156. CURLOPT_RETURNTRANSFER => TRUE,
  1157. CURLOPT_SSL_VERIFYPEER => FALSE, // Required to make the tests run on https.
  1158. CURLOPT_SSL_VERIFYHOST => FALSE, // Required to make the tests run on https.
  1159. CURLOPT_HEADERFUNCTION => array(&$this, 'curlHeaderCallback'),
  1160. );
  1161. if (isset($this->httpauth_credentials)) {
  1162. $curl_options[CURLOPT_HTTPAUTH] = $this->httpauth_method;
  1163. $curl_options[CURLOPT_USERPWD] = $this->httpauth_credentials;
  1164. }
  1165. curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options);
  1166. // By default, the child session name should be the same as the parent.
  1167. $this->session_name = session_name();
  1168. }
  1169. // We set the user agent header on each request so as to use the current
  1170. // time and a new uniqid.
  1171. if (preg_match('/simpletest\d+/', $db_prefix, $matches)) {
  1172. curl_setopt($this->curlHandle, CURLOPT_USERAGENT, drupal_generate_test_ua($matches[0]));
  1173. }
  1174. }
  1175. /**
  1176. * Performs a cURL exec with the specified options after calling curlConnect().
  1177. *
  1178. * @param $curl_options
  1179. * Custom cURL options.
  1180. * @return
  1181. * Content returned from the exec.
  1182. */
  1183. protected function curlExec($curl_options) {
  1184. $this->curlInitialize();
  1185. $url = empty($curl_options[CURLOPT_URL]) ? curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL) : $curl_options[CURLOPT_URL];
  1186. if (!empty($curl_options[CURLOPT_POST])) {
  1187. // This is a fix for the Curl library to prevent Expect: 100-continue
  1188. // headers in POST requests, that may cause unexpected HTTP response
  1189. // codes from some webservers (like lighttpd that returns a 417 error
  1190. // code). It is done by setting an empty "Expect" header field that is
  1191. // not overwritten by Curl.
  1192. $curl_options[CURLOPT_HTTPHEADER][] = 'Expect:';
  1193. }
  1194. curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options);
  1195. // Reset headers and the session ID.
  1196. $this->session_id = NULL;
  1197. $this->headers = array();
  1198. $this->drupalSetContent(curl_exec($this->curlHandle), curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL));
  1199. $message_vars = array(
  1200. '!method' => !empty($curl_options[CURLOPT_NOBODY]) ? 'HEAD' : (empty($curl_options[CURLOPT_POSTFIELDS]) ? 'GET' : 'POST'),
  1201. '@url' => $url,
  1202. '@status' => curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE),
  1203. '!length' => format_size(strlen($this->content))
  1204. );
  1205. $message = t('!method @url returned @status (!length).', $message_vars);
  1206. $this->assertTrue($this->content !== FALSE, $message, t('Browser'));
  1207. return $this->drupalGetContent();
  1208. }
  1209. /**
  1210. * Reads headers and registers errors received from the tested site.
  1211. *
  1212. * @see _drupal_log_error().
  1213. *
  1214. * @param $curlHandler
  1215. * The cURL handler.
  1216. * @param $header
  1217. * An header.
  1218. */
  1219. protected function curlHeaderCallback($curlHandler, $header) {
  1220. $this->headers[] = $header;
  1221. // Errors are being sent via X-Drupal-Assertion-* headers,
  1222. // generated by _drupal_log_error() in the exact form required
  1223. // by DrupalWebTestCase::error().
  1224. if (preg_match('/^X-Drupal-Assertion-[0-9]+: (.*)$/', $header, $matches)) {
  1225. // Call DrupalWebTestCase::error() with the parameters from the header.
  1226. call_user_func_array(array(&$this, 'error'), unserialize(urldecode($matches[1])));
  1227. }
  1228. // Save cookies.
  1229. if (preg_match('/^Set-Cookie: ([^=]+)=(.+)/', $header, $matches)) {
  1230. $name = $matches[1];
  1231. $parts = array_map('trim', explode(';', $matches[2]));
  1232. $value = array_shift($parts);
  1233. $this->cookies[$name] = array('value' => $value, 'secure' => in_array('secure', $parts));
  1234. if ($name == $this->session_name) {
  1235. if ($value != 'deleted') {
  1236. $this->session_id = $value;
  1237. }
  1238. else {
  1239. $this->session_id = NULL;
  1240. }
  1241. }
  1242. }
  1243. // This is required by cURL.
  1244. return strlen($header);
  1245. }
  1246. /**
  1247. * Close the cURL handler and unset the handler.
  1248. */
  1249. protected function curlClose() {
  1250. if (isset($this->curlHandle)) {
  1251. curl_close($this->curlHandle);
  1252. unset($this->curlHandle);
  1253. }
  1254. }
  1255. /**
  1256. * Parse content returned from curlExec using DOM and SimpleXML.
  1257. *
  1258. * @return
  1259. * A SimpleXMLElement or FALSE on failure.
  1260. */
  1261. protected function parse() {
  1262. if (!$this->elements) {
  1263. // DOM can load HTML soup. But, HTML soup can throw warnings, suppress
  1264. // them.
  1265. @$htmlDom = DOMDocument::loadHTML($this->content);
  1266. if ($htmlDom) {
  1267. $this->pass(t('Valid HTML found on "@path"', array('@path' => $this->getUrl())), t('Browser'));
  1268. // It's much easier to work with simplexml than DOM, luckily enough
  1269. // we can just simply import our DOM tree.
  1270. $this->elements = simplexml_import_dom($htmlDom);
  1271. }
  1272. }
  1273. if (!$this->elements) {
  1274. $this->fail(t('Parsed page successfully.'), t('Browser'));
  1275. }
  1276. return $this->elements;
  1277. }
  1278. /**
  1279. * Retrieves a Drupal path or an absolute path.
  1280. *
  1281. * @param $path
  1282. * Drupal path or URL to load into internal browser
  1283. * @param $options
  1284. * Options to be forwarded to url().
  1285. * @param $headers
  1286. * An array containing additional HTTP request headers, each formatted as
  1287. * "name: value".
  1288. * @return
  1289. * The retrieved HTML string, also available as $this->drupalGetContent()
  1290. */
  1291. protected function drupalGet($path, array $options = array(), array $headers = array()) {
  1292. $options['absolute'] = TRUE;
  1293. // We re-using a CURL connection here. If that connection still has certain
  1294. // options set, it might change the GET into a POST. Make sure we clear out
  1295. // previous options.
  1296. $out = $this->curlExec(array(CURLOPT_HTTPGET => TRUE, CURLOPT_URL => url($path, $options), CURLOPT_NOBODY => FALSE, CURLOPT_HTTPHEADER => $headers));
  1297. $this->refreshVariables(); // Ensure that any changes to variables in the other thread are picked up.
  1298. // Replace original page output with new output from redirected page(s).
  1299. if ($new = $this->checkForMetaRefresh()) {
  1300. $out = $new;
  1301. }
  1302. $this->verbose('GET request to: ' . $path .
  1303. '<hr />Ending URL: ' . $this->getUrl() .
  1304. '<hr />' . $out);
  1305. return $out;
  1306. }
  1307. /**
  1308. * Retrieve a Drupal path or an absolute path and JSON decode the result.
  1309. */
  1310. protected function drupalGetAJAX($path, array $options = array(), array $headers = array()) {
  1311. return drupal_json_decode($this->drupalGet($path, $options, $headers));
  1312. }
  1313. /**
  1314. * Execute a POST request on a Drupal page.
  1315. * It will be done as usual POST request with SimpleBrowser.
  1316. *
  1317. * @param $path
  1318. * Location of the post form. Either a Drupal path or an absolute path or
  1319. * NULL to post to the current page. For multi-stage forms you can set the
  1320. * path to NULL and have it post to the last received page. Example:
  1321. *
  1322. * @code
  1323. * // First step in form.
  1324. * $edit = array(...);
  1325. * $this->drupalPost('some_url', $edit, t('Save'));
  1326. *
  1327. * // Second step in form.
  1328. * $edit = array(...);
  1329. * $this->drupalPost(NULL, $edit, t('Save'));
  1330. * @endcode
  1331. * @param $edit
  1332. * Field data in an associative array. Changes the current input fields
  1333. * (where possible) to the values indicated. A checkbox can be set to
  1334. * TRUE to be checked and FALSE to be unchecked. Note that when a form
  1335. * contains file upload fields, other fields cannot start with the '@'
  1336. * character.
  1337. *
  1338. * Multiple select fields can be set using name[] and setting each of the
  1339. * possible values. Example:
  1340. * @code
  1341. * $edit = array();
  1342. * $edit['name[]'] = array('value1', 'value2');
  1343. * @endcode
  1344. * @param $submit
  1345. * Value of the submit button whose click is to be emulated. For example,
  1346. * t('Save'). The processing of the request depends on this value. For
  1347. * example, a form may have one button with the value t('Save') and another
  1348. * button with the value t('Delete'), and execute different code depending
  1349. * on which one is clicked.
  1350. *
  1351. * This function can also be called to emulate an AJAX submission. In this
  1352. * case, this value needs to be an array with the following keys:
  1353. * - path: A path to submit the form values to for AJAX-specific processing,
  1354. * which is likely different than the $path parameter used for retrieving
  1355. * the initial form. Defaults to 'system/ajax'.
  1356. * - triggering_element: If the value for the 'path' key is 'system/ajax' or
  1357. * another generic AJAX processing path, this needs to be set to the '/'
  1358. * separated path to the element within the server's cached $form array.
  1359. * The callback for the generic AJAX processing path uses this to find
  1360. * the #ajax information for the element, including which specific
  1361. * callback to use for processing the request.
  1362. * @param $options
  1363. * Options to be forwarded to url().
  1364. * @param $headers
  1365. * An array containing additional HTTP request headers, each formatted as
  1366. * "name: value".
  1367. */
  1368. protected function drupalPost($path, $edit, $submit, array $options = array(), array $headers = array()) {
  1369. $submit_matches = FALSE;
  1370. $ajax = is_array($submit);
  1371. if (isset($path)) {
  1372. $this->drupalGet($path, $options);
  1373. }
  1374. if ($this->parse()) {
  1375. $edit_save = $edit;
  1376. // Let's iterate over all the forms.
  1377. $forms = $this->xpath('//form');
  1378. foreach ($forms as $form) {
  1379. // We try to set the fields of this form as specified in $edit.
  1380. $edit = $edit_save;
  1381. $post = array();
  1382. $upload = array();
  1383. $submit_matches = $this->handleForm($post, $edit, $upload, $ajax ? NULL : $submit, $form);
  1384. $action = isset($form['action']) ? $this->getAbsoluteUrl($form['action']) : $this->getUrl();
  1385. if ($ajax) {
  1386. $action = $this->getAbsoluteUrl(!empty($submit['path']) ? $submit['path'] : 'system/ajax');
  1387. // AJAX callbacks verify the triggering element if necessary, so while
  1388. // we may eventually want extra code that verifies it in the
  1389. // handleForm() function, it's not currently a requirement.
  1390. $submit_matches = TRUE;
  1391. }
  1392. // We post only if we managed to handle every field in edit and the
  1393. // submit button matches.
  1394. if (!$edit && $submit_matches) {
  1395. $post_array = $post;
  1396. if ($upload) {
  1397. // TODO: cURL handles file uploads for us, but the implementation
  1398. // is broken. This is a less than elegant workaround. Alternatives
  1399. // are being explored at #253506.
  1400. foreach ($upload as $key => $file) {
  1401. $file = drupal_realpath($file);
  1402. if ($file && is_file($file)) {
  1403. $post[$key] = '@' . $file;
  1404. }
  1405. }
  1406. }
  1407. else {
  1408. foreach ($post as $key => $value) {
  1409. // Encode according to application/x-www-form-urlencoded
  1410. // Both names and values needs to be urlencoded, according to
  1411. // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
  1412. $post[$key] = urlencode($key) . '=' . urlencode($value);
  1413. }
  1414. if ($ajax && isset($submit['triggering_element'])) {
  1415. $post['ajax_triggering_element'] = 'ajax_triggering_element=' . urlencode($submit['triggering_element']);
  1416. }
  1417. $post = implode('&', $post);
  1418. }
  1419. $out = $this->curlExec(array(CURLOPT_URL => $action, CURLOPT_POST => TRUE, CURLOPT_POSTFIELDS => $post, CURLOPT_HTTPHEADER => $headers));
  1420. // Ensure that any changes to variables in the other thread are picked up.
  1421. $this->refreshVariables();
  1422. // Replace original page output with new output from redirected page(s).
  1423. if ($new = $this->checkForMetaRefresh()) {
  1424. $out = $new;
  1425. }
  1426. $this->verbose('POST request to: ' . $path .
  1427. '<hr />Ending URL: ' . $this->getUrl() .
  1428. '<hr />Fields: ' . highlight_string('<?php ' . var_export($post_array, TRUE), TRUE) .
  1429. '<hr />' . $out);
  1430. return $out;
  1431. }
  1432. }
  1433. // We have not found a form which contained all fields of $edit.
  1434. foreach ($edit as $name => $value) {
  1435. $this->fail(t('Failed to set field @name to @value', array('@name' => $name, '@value' => $value)));
  1436. }
  1437. if (!$ajax) {
  1438. $this->assertTrue($submit_matches, t('Found the @submit button', array('@submit' => $submit)));
  1439. }
  1440. $this->fail(t('Found the requested form fields at @path', array('@path' => $path)));
  1441. }
  1442. }
  1443. /**
  1444. * Execute a POST request on an AJAX path and JSON decode the result.
  1445. */
  1446. protected function drupalPostAJAX($path, $edit, $triggering_element, $ajax_path = 'system/ajax', array $options = array(), array $headers = array()) {
  1447. return drupal_json_decode($this->drupalPost($path, $edit, array('path' => $ajax_path, 'triggering_element' => $triggering_element), $options, $headers));
  1448. }
  1449. /**
  1450. * Runs cron in the Drupal installed by Simpletest.
  1451. */
  1452. protected function cronRun() {
  1453. $this->drupalGet($GLOBALS['base_url'] . '/cron.php', array('external' => TRUE, 'query' => array('cron_key' => variable_get('cron_key', 'drupal'))));
  1454. }
  1455. /**
  1456. * Check for meta refresh tag and if found call drupalGet() recursively. This
  1457. * function looks for the http-equiv attribute to be set to "Refresh"
  1458. * and is case-sensitive.
  1459. *
  1460. * @return
  1461. * Either the new page content or FALSE.
  1462. */
  1463. protected function checkForMetaRefresh() {
  1464. if (strpos($this->drupalGetContent(), '<meta ') && $this->parse()) {
  1465. $refresh = $this->xpath('//meta[@http-equiv="Refresh"]');
  1466. if (!empty($refresh)) {
  1467. // Parse the content attribute of the meta tag for the format:
  1468. // "[delay]: URL=[page_to_redirect_to]".
  1469. if (preg_match('/\d+;\s*URL=(?P<url>.*)/i', $refresh[0]['content'], $match)) {
  1470. return $this->drupalGet($this->getAbsoluteUrl(decode_entities($match['url'])));
  1471. }
  1472. }
  1473. }
  1474. return FALSE;
  1475. }
  1476. /**
  1477. * Retrieves only the headers for a Drupal path or an absolute path.
  1478. *
  1479. * @param $path
  1480. * Drupal path or URL to load into internal browser
  1481. * @param $options
  1482. * Options to be forwarded to url().
  1483. * @param $headers
  1484. * An array containing additional HTTP request headers, each formatted as
  1485. * "name: value".
  1486. * @return
  1487. * The retrieved headers, also available as $this->drupalGetContent()
  1488. */
  1489. protected function drupalHead($path, array $options = array(), array $headers = array()) {
  1490. $options['absolute'] = TRUE;
  1491. $out = $this->curlExec(array(CURLOPT_NOBODY => TRUE, CURLOPT_URL => url($path, $options), CURLOPT_HTTPHEADER => $headers));
  1492. $this->refreshVariables(); // Ensure that any changes to variables in the other thread are picked up.
  1493. return $out;
  1494. }
  1495. /**
  1496. * Handle form input related to drupalPost(). Ensure that the specified fields
  1497. * exist and attempt to create POST data in the correct manner for the particular
  1498. * field type.
  1499. *
  1500. * @param $post
  1501. * Reference to array of post values.
  1502. * @param $edit
  1503. * Reference to array of edit values to be checked against the form.
  1504. * @param $submit
  1505. * Form submit button value.
  1506. * @param $form
  1507. * Array of form elements.
  1508. * @return
  1509. * Submit value matches a valid submit input in the form.
  1510. */
  1511. protected function handleForm(&$post, &$edit, &$upload, $submit, $form) {
  1512. // Retrieve the form elements.
  1513. $elements = $form->xpath('.//input|.//textarea|.//select');
  1514. $submit_matches = FALSE;
  1515. foreach ($elements as $element) {
  1516. // SimpleXML objects need string casting all the time.
  1517. $name = (string) $element['name'];
  1518. // This can either be the type of <input> or the name of the tag itself
  1519. // for <select> or <textarea>.
  1520. $type = isset($element['type']) ? (string)$element['type'] : $element->getName();
  1521. $value = isset($element['value']) ? (string)$element['value'] : '';
  1522. $done = FALSE;
  1523. if (isset($edit[$name])) {
  1524. switch ($type) {
  1525. case 'text':
  1526. case 'textarea':
  1527. case 'hidden':
  1528. case 'password':
  1529. $post[$name] = $edit[$name];
  1530. unset($edit[$name]);
  1531. break;
  1532. case 'radio':
  1533. if ($edit[$name] == $value) {
  1534. $post[$name] = $edit[$name];
  1535. unset($edit[$name]);
  1536. }
  1537. break;
  1538. case 'checkbox':
  1539. // To prevent checkbox from being checked.pass in a FALSE,
  1540. // otherwise the checkbox will be set to its value regardless
  1541. // of $edit.
  1542. if ($edit[$name] === FALSE) {
  1543. unset($edit[$name]);
  1544. continue 2;
  1545. }
  1546. else {
  1547. unset($edit[$name]);
  1548. $post[$name] = $value;
  1549. }
  1550. break;
  1551. case 'select':
  1552. $new_value = $edit[$name];
  1553. $options = $this->getAllOptions($element);
  1554. if (is_array($new_value)) {
  1555. // Multiple select box.
  1556. if (!empty($new_value)) {
  1557. $index = 0;
  1558. $key = preg_replace('/\[\]$/', '', $name);
  1559. foreach ($options as $option) {
  1560. $option_value = (string)$option['value'];
  1561. if (in_array($option_value, $new_value)) {
  1562. $post[$key . '[' . $index++ . ']'] = $option_value;
  1563. $done = TRUE;
  1564. unset($edit[$name]);
  1565. }
  1566. }
  1567. }
  1568. else {
  1569. // No options selected: do not include any POST data for the
  1570. // element.
  1571. $done = TRUE;
  1572. unset($edit[$name]);
  1573. }
  1574. }
  1575. else {
  1576. // Single select box.
  1577. foreach ($options as $option) {
  1578. if ($new_value == $option['value']) {
  1579. $post[$name] = $new_value;
  1580. unset($edit[$name]);
  1581. $done = TRUE;
  1582. break;
  1583. }
  1584. }
  1585. }
  1586. break;
  1587. case 'file':
  1588. $upload[$name] = $edit[$name];
  1589. unset($edit[$name]);
  1590. break;
  1591. }
  1592. }
  1593. if (!isset($post[$name]) && !$done) {
  1594. switch ($type) {
  1595. case 'textarea':
  1596. $post[$name] = (string)$element;
  1597. break;
  1598. case 'select':
  1599. $single = empty($element['multiple']);
  1600. $first = TRUE;
  1601. $index = 0;
  1602. $key = preg_replace('/\[\]$/', '', $name);
  1603. $options = $this->getAllOptions($element);
  1604. foreach ($options as $option) {
  1605. // For single select, we load the first option, if there is a
  1606. // selected option that will overwrite it later.
  1607. if ($option['selected'] || ($first && $single)) {
  1608. $first = FALSE;
  1609. if ($single) {
  1610. $post[$name] = (string)$option['value'];
  1611. }
  1612. else {
  1613. $post[$key . '[' . $index++ . ']'] = (string)$option['value'];
  1614. }
  1615. }
  1616. }
  1617. break;
  1618. case 'file':
  1619. break;
  1620. case 'submit':
  1621. case 'image':
  1622. if ($submit == $value) {
  1623. $post[$name] = $value;
  1624. $submit_matches = TRUE;
  1625. }
  1626. break;
  1627. case 'radio':
  1628. case 'checkbox':
  1629. if (!isset($element['checked'])) {
  1630. break;
  1631. }
  1632. // Deliberate no break.
  1633. default:
  1634. $post[$name] = $value;
  1635. }
  1636. }
  1637. }
  1638. return $submit_matches;
  1639. }
  1640. /**
  1641. * Perform an xpath search on the contents of the internal browser. The search
  1642. * is relative to the root element (HTML tag normally) of the page.
  1643. *
  1644. * @param $xpath
  1645. * The xpath string to use in the search.
  1646. * @return
  1647. * The return value of the xpath search. For details on the xpath string
  1648. * format and return values see the SimpleXML documentation,
  1649. * http://us.php.net/manual/function.simplexml-element-xpath.php.
  1650. */
  1651. protected function xpath($xpath) {
  1652. if ($this->parse()) {
  1653. return $this->elements->xpath($xpath);
  1654. }
  1655. return FALSE;
  1656. }
  1657. /**
  1658. * Get all option elements, including nested options, in a select.
  1659. *
  1660. * @param $element
  1661. * The element for which to get the options.
  1662. * @return
  1663. * Option elements in select.
  1664. */
  1665. protected function getAllOptions(SimpleXMLElement $element) {
  1666. $options = array();
  1667. // Add all options items.
  1668. foreach ($element->option as $option) {
  1669. $options[] = $option;
  1670. }
  1671. // Search option group children.
  1672. if (isset($element->optgroup)) {
  1673. foreach ($element->optgroup as $group) {
  1674. $options = array_merge($options, $this->getAllOptions($group));
  1675. }
  1676. }
  1677. return $options;
  1678. }
  1679. /**
  1680. * Pass if a link with the specified label is found, and optional with the
  1681. * specified index.
  1682. *
  1683. * @param $label
  1684. * Text between the anchor tags.
  1685. * @param $index
  1686. * Link position counting from zero.
  1687. * @param $message
  1688. * Message to display.
  1689. * @param $group
  1690. * The group this message belongs to, defaults to 'Other'.
  1691. * @return
  1692. * TRUE if the assertion succeeded, FALSE otherwise.
  1693. */
  1694. protected function assertLink($label, $index = 0, $message = '', $group = 'Other') {
  1695. $links = $this->xpath('//a[text()="' . $label . '"]');
  1696. $message = ($message ? $message : t('Link with label %label found.', array('%label' => $label)));
  1697. return $this->assert(isset($links[$index]), $message, $group);
  1698. }
  1699. /**
  1700. * Pass if a link with the specified label is not found.
  1701. *
  1702. * @param $label
  1703. * Text between the anchor tags.
  1704. * @param $index
  1705. * Link position counting from zero.
  1706. * @param $message
  1707. * Message to display.
  1708. * @param $group
  1709. * The group this message belongs to, defaults to 'Other'.
  1710. * @return
  1711. * TRUE if the assertion succeeded, FALSE otherwise.
  1712. */
  1713. protected function assertNoLink($label, $message = '', $group = 'Other') {
  1714. $links = $this->xpath('//a[text()="' . $label . '"]');
  1715. $message = ($message ? $message : t('Link with label %label not found.', array('%label' => $label)));
  1716. return $this->assert(empty($links), $message, $group);
  1717. }
  1718. /**
  1719. * Pass if a link containing a given href (part) is found.
  1720. *
  1721. * @param $href
  1722. * The full or partial value of the 'href' attribute of the anchor tag.
  1723. * @param $index
  1724. * Link position counting from zero.
  1725. * @param $message
  1726. * Message to display.
  1727. * @param $group
  1728. * The group this message belongs to, defaults to 'Other'.
  1729. *
  1730. * @return
  1731. * TRUE if the assertion succeeded, FALSE otherwise.
  1732. */
  1733. protected function assertLinkByHref($href, $index = 0, $message = '', $group = 'Other') {
  1734. $links = $this->xpath('//a[contains(@href, "' . $href . '")]');
  1735. $message = ($message ? $message : t('Link containing href %href found.', array('%href' => $href)));
  1736. return $this->assert(isset($links[$index]), $message, $group);
  1737. }
  1738. /**
  1739. * Pass if a link containing a given href (part) is not found.
  1740. *
  1741. * @param $href
  1742. * The full or partial value of the 'href' attribute of the anchor tag.
  1743. * @param $message
  1744. * Message to display.
  1745. * @param $group
  1746. * The group this message belongs to, defaults to 'Other'.
  1747. *
  1748. * @return
  1749. * TRUE if the assertion succeeded, FALSE otherwise.
  1750. */
  1751. protected function assertNoLinkByHref($href, $message = '', $group = 'Other') {
  1752. $links = $this->xpath('//a[contains(@href, "' . $href . '")]');
  1753. $message = ($message ? $message : t('No link containing href %href found.', array('%href' => $href)));
  1754. return $this->assert(empty($links), $message, $group);
  1755. }
  1756. /**
  1757. * Follows a link by name.
  1758. *
  1759. * Will click the first link found with this link text by default, or a
  1760. * later one if an index is given. Match is case insensitive with
  1761. * normalized space. The label is translated label. There is an assert
  1762. * for successful click.
  1763. *
  1764. * @param $label
  1765. * Text between the anchor tags.
  1766. * @param $index
  1767. * Link position counting from zero.
  1768. * @return
  1769. * Page on success, or FALSE on failure.
  1770. */
  1771. protected function clickLink($label, $index = 0) {
  1772. $url_before = $this->getUrl();
  1773. $urls = $this->xpath('//a[text()="' . $label . '"]');
  1774. if (isset($urls[$index])) {
  1775. $url_target = $this->getAbsoluteUrl($urls[$index]['href']);
  1776. }
  1777. $this->assertTrue(isset($urls[$index]), t('Clicked link %label (@url_target) from @url_before', array('%label' => $label, '@url_target' => $url_target, '@url_before' => $url_before)), t('Browser'));
  1778. if (isset($url_target)) {
  1779. return $this->drupalGet($url_target);
  1780. }
  1781. return FALSE;
  1782. }
  1783. /**
  1784. * Takes a path and returns an absolute path.
  1785. *
  1786. * @param $path
  1787. * A path from the internal browser content.
  1788. * @return
  1789. * The $path with $base_url prepended, if necessary.
  1790. */
  1791. protected function getAbsoluteUrl($path) {
  1792. global $base_url, $base_path;
  1793. $parts = parse_url($path);
  1794. if (empty($parts['host'])) {
  1795. // Ensure that we have a string (and no xpath object).
  1796. $path = (string) $path;
  1797. // Strip $base_path, if existent.
  1798. $length = strlen($base_path);
  1799. if (substr($path, 0, $length) === $base_path) {
  1800. $path = substr($path, $length);
  1801. }
  1802. // Ensure that we have an absolute path.
  1803. if ($path[0] !== '/') {
  1804. $path = '/' . $path;
  1805. }
  1806. // Finally, prepend the $base_url.
  1807. $path = $base_url . $path;
  1808. }
  1809. return $path;
  1810. }
  1811. /**
  1812. * Get the current url from the cURL handler.
  1813. *
  1814. * @return
  1815. * The current url.
  1816. */
  1817. protected function getUrl() {
  1818. return $this->url;
  1819. }
  1820. /**
  1821. * Gets the HTTP response headers of the requested page. Normally we are only
  1822. * interested in the headers returned by the last request. However, if a page
  1823. * is redirected or HTTP authentication is in use, multiple requests will be
  1824. * required to retrieve the page. Headers from all requests may be requested
  1825. * by passing TRUE to this function.
  1826. *
  1827. * @param $all_requests
  1828. * Boolean value specifying whether to return headers from all requests
  1829. * instead of just the last request. Defaults to FALSE.
  1830. * @return
  1831. * A name/value array if headers from only the last request are requested.
  1832. * If headers from all requests are requested, an array of name/value
  1833. * arrays, one for each request.
  1834. *
  1835. * The pseudonym ":status" is used for the HTTP status line.
  1836. *
  1837. * Values for duplicate headers are stored as a single comma-separated list.
  1838. */
  1839. protected function drupalGetHeaders($all_requests = FALSE) {
  1840. $request = 0;
  1841. $headers = array($request => array());
  1842. foreach ($this->headers as $header) {
  1843. $header = trim($header);
  1844. if ($header === '') {
  1845. $request++;
  1846. }
  1847. else {
  1848. if (strpos($header, 'HTTP/') === 0) {
  1849. $name = ':status';
  1850. $value = $header;
  1851. }
  1852. else {
  1853. list($name, $value) = explode(':', $header, 2);
  1854. $name = strtolower($name);
  1855. }
  1856. if (isset($headers[$request][$name])) {
  1857. $headers[$request][$name] .= ',' . trim($value);
  1858. }
  1859. else {
  1860. $headers[$request][$name] = trim($value);
  1861. }
  1862. }
  1863. }
  1864. if (!$all_requests) {
  1865. $headers = array_pop($headers);
  1866. }
  1867. return $headers;
  1868. }
  1869. /**
  1870. * Gets the value of an HTTP response header. If multiple requests were
  1871. * required to retrieve the page, only the headers from the last request will
  1872. * be checked by default. However, if TRUE is passed as the second argument,
  1873. * all requests will be processed from last to first until the header is
  1874. * found.
  1875. *
  1876. * @param $name
  1877. * The name of the header to retrieve. Names are case-insensitive (see RFC
  1878. * 2616 section 4.2).
  1879. * @param $all_requests
  1880. * Boolean value specifying whether to check all requests if the header is
  1881. * not found in the last request. Defaults to FALSE.
  1882. * @return
  1883. * The HTTP header value or FALSE if not found.
  1884. */
  1885. protected function drupalGetHeader($name, $all_requests = FALSE) {
  1886. $name = strtolower($name);
  1887. $header = FALSE;
  1888. if ($all_requests) {
  1889. foreach (array_reverse($this->drupalGetHeaders(TRUE)) as $headers) {
  1890. if (isset($headers[$name])) {
  1891. $header = $headers[$name];
  1892. break;
  1893. }
  1894. }
  1895. }
  1896. else {
  1897. $headers = $this->drupalGetHeaders();
  1898. if (isset($headers[$name])) {
  1899. $header = $headers[$name];
  1900. }
  1901. }
  1902. return $header;
  1903. }
  1904. /**
  1905. * Gets the current raw HTML of requested page.
  1906. */
  1907. protected function drupalGetContent() {
  1908. return $this->content;
  1909. }
  1910. /**
  1911. * Gets an array containing all e-mails sent during this test case.
  1912. *
  1913. * @param $filter
  1914. * An array containing key/value pairs used to filter the e-mails that are returned.
  1915. * @return
  1916. * An array containing e-mail messages captured during the current test.
  1917. */
  1918. protected function drupalGetMails($filter = array()) {
  1919. $captured_emails = variable_get('drupal_test_email_collector', array());
  1920. $filtered_emails = array();
  1921. foreach ($captured_emails as $message) {
  1922. foreach ($filter as $key => $value) {
  1923. if (!isset($message[$key]) || $message[$key] != $value) {
  1924. continue 2;
  1925. }
  1926. }
  1927. $filtered_emails[] = $message;
  1928. }
  1929. return $filtered_emails;
  1930. }
  1931. /**
  1932. * Sets the raw HTML content. This can be useful when a page has been fetched
  1933. * outside of the internal browser and assertions need to be made on the
  1934. * returned page.
  1935. *
  1936. * A good example would be when testing drupal_http_request(). After fetching
  1937. * the page the content can be set and page elements can be checked to ensure
  1938. * that the function worked properly.
  1939. */
  1940. protected function drupalSetContent($content, $url = 'internal:') {
  1941. $this->content = $content;
  1942. $this->url = $url;
  1943. $this->plainTextContent = FALSE;
  1944. $this->elements = FALSE;
  1945. }
  1946. /**
  1947. * Pass if the raw text IS found on the loaded page, fail otherwise. Raw text
  1948. * refers to the raw HTML that the page generated.
  1949. *
  1950. * @param $raw
  1951. * Raw (HTML) string to look for.
  1952. * @param $message
  1953. * Message to display.
  1954. * @param $group
  1955. * The group this message belongs to, defaults to 'Other'.
  1956. * @return
  1957. * TRUE on pass, FALSE on fail.
  1958. */
  1959. protected function assertRaw($raw, $message = '', $group = 'Other') {
  1960. if (!$message) {
  1961. $message = t('Raw "@raw" found', array('@raw' => $raw));
  1962. }
  1963. return $this->assert(strpos($this->content, $raw) !== FALSE, $message, $group);
  1964. }
  1965. /**
  1966. * Pass if the raw text is NOT found on the loaded page, fail otherwise. Raw text
  1967. * refers to the raw HTML that the page generated.
  1968. *
  1969. * @param $raw
  1970. * Raw (HTML) string to look for.
  1971. * @param $message
  1972. * Message to display.
  1973. * @param $group
  1974. * The group this message belongs to, defaults to 'Other'.
  1975. * @return
  1976. * TRUE on pass, FALSE on fail.
  1977. */
  1978. protected function assertNoRaw($raw, $message = '', $group = 'Other') {
  1979. if (!$message) {
  1980. $message = t('Raw "@raw" not found', array('@raw' => $raw));
  1981. }
  1982. return $this->assert(strpos($this->content, $raw) === FALSE, $message, $group);
  1983. }
  1984. /**
  1985. * Pass if the text IS found on the text version of the page. The text version
  1986. * is the equivalent of what a user would see when viewing through a web browser.
  1987. * In other words the HTML has been filtered out of the contents.
  1988. *
  1989. * @param $text
  1990. * Plain text to look for.
  1991. * @param $message
  1992. * Message to display.
  1993. * @param $group
  1994. * The group this message belongs to, defaults to 'Other'.
  1995. * @return
  1996. * TRUE on pass, FALSE on fail.
  1997. */
  1998. protected function assertText($text, $message = '', $group = 'Other') {
  1999. return $this->assertTextHelper($text, $message, $group, FALSE);
  2000. }
  2001. /**
  2002. * Pass if the text is NOT found on the text version of the page. The text version
  2003. * is the equivalent of what a user would see when viewing through a web browser.
  2004. * In other words the HTML has been filtered out of the contents.
  2005. *
  2006. * @param $text
  2007. * Plain text to look for.
  2008. * @param $message
  2009. * Message to display.
  2010. * @param $group
  2011. * The group this message belongs to, defaults to 'Other'.
  2012. * @return
  2013. * TRUE on pass, FALSE on fail.
  2014. */
  2015. protected function assertNoText($text, $message = '', $group = 'Other') {
  2016. return $this->assertTextHelper($text, $message, $group, TRUE);
  2017. }
  2018. /**
  2019. * Helper for assertText and assertNoText.
  2020. *
  2021. * It is not recommended to call this function directly.
  2022. *
  2023. * @param $text
  2024. * Plain text to look for.
  2025. * @param $message
  2026. * Message to display.
  2027. * @param $group
  2028. * The group this message belongs to.
  2029. * @param $not_exists
  2030. * TRUE if this text should not exist, FALSE if it should.
  2031. * @return
  2032. * TRUE on pass, FALSE on fail.
  2033. */
  2034. protected function assertTextHelper($text, $message, $group, $not_exists) {
  2035. if ($this->plainTextContent === FALSE) {
  2036. $this->plainTextContent = filter_xss($this->content, array());
  2037. }
  2038. if (!$message) {
  2039. $message = !$not_exists ? t('"@text" found', array('@text' => $text)) : t('"@text" not found', array('@text' => $text));
  2040. }
  2041. return $this->assert($not_exists == (strpos($this->plainTextContent, $text) === FALSE), $message, $group);
  2042. }
  2043. /**
  2044. * Pass if the text is found ONLY ONCE on the text version of the page.
  2045. *
  2046. * The text version is the equivalent of what a user would see when viewing
  2047. * through a web browser. In other words the HTML has been filtered out of
  2048. * the contents.
  2049. *
  2050. * @param $text
  2051. * Plain text to look for.
  2052. * @param $message
  2053. * Message to display.
  2054. * @param $group
  2055. * The group this message belongs to, defaults to 'Other'.
  2056. * @return
  2057. * TRUE on pass, FALSE on fail.
  2058. */
  2059. protected function assertUniqueText($text, $message = '', $group = 'Other') {
  2060. return $this->assertUniqueTextHelper($text, $message, $group, TRUE);
  2061. }
  2062. /**
  2063. * Pass if the text is found MORE THAN ONCE on the text version of the page.
  2064. *
  2065. * The text version is the equivalent of what a user would see when viewing
  2066. * through a web browser. In other words the HTML has been filtered out of
  2067. * the contents.
  2068. *
  2069. * @param $text
  2070. * Plain text to look for.
  2071. * @param $message
  2072. * Message to display.
  2073. * @param $group
  2074. * The group this message belongs to, defaults to 'Other'.
  2075. * @return
  2076. * TRUE on pass, FALSE on fail.
  2077. */
  2078. protected function assertNoUniqueText($text, $message = '', $group = 'Other') {
  2079. return $this->assertUniqueTextHelper($text, $message, $group, FALSE);
  2080. }
  2081. /**
  2082. * Helper for assertUniqueText and assertNoUniqueText.
  2083. *
  2084. * It is not recommended to call this function directly.
  2085. *
  2086. * @param $text
  2087. * Plain text to look for.
  2088. * @param $message
  2089. * Message to display.
  2090. * @param $group
  2091. * The group this message belongs to.
  2092. * @param $be_unique
  2093. * TRUE if this text should be found only once, FALSE if it should be found more than once.
  2094. * @return
  2095. * TRUE on pass, FALSE on fail.
  2096. */
  2097. protected function assertUniqueTextHelper($text, $message, $group, $be_unique) {
  2098. if ($this->plainTextContent === FALSE) {
  2099. $this->plainTextContent = filter_xss($this->content, array());
  2100. }
  2101. if (!$message) {
  2102. $message = '"' . $text . '"' . ($be_unique ? ' found only once' : ' found more than once');
  2103. }
  2104. $first_occurance = strpos($this->plainTextContent, $text);
  2105. if ($first_occurance === FALSE) {
  2106. return $this->assert(FALSE, $message, $group);
  2107. }
  2108. $offset = $first_occurance + strlen($text);
  2109. $second_occurance = strpos($this->plainTextContent, $text, $offset);
  2110. return $this->assert($be_unique == ($second_occurance === FALSE), $message, $group);
  2111. }
  2112. /**
  2113. * Will trigger a pass if the Perl regex pattern is found in the raw content.
  2114. *
  2115. * @param $pattern
  2116. * Perl regex to look for including the regex delimiters.
  2117. * @param $message
  2118. * Message to display.
  2119. * @param $group
  2120. * The group this message belongs to.
  2121. * @return
  2122. * TRUE on pass, FALSE on fail.
  2123. */
  2124. protected function assertPattern($pattern, $message = '', $group = 'Other') {
  2125. if (!$message) {
  2126. $message = t('Pattern "@pattern" found', array('@pattern' => $pattern));
  2127. }
  2128. return $this->assert((bool) preg_match($pattern, $this->drupalGetContent()), $message, $group);
  2129. }
  2130. /**
  2131. * Will trigger a pass if the perl regex pattern is not present in raw content.
  2132. *
  2133. * @param $pattern
  2134. * Perl regex to look for including the regex delimiters.
  2135. * @param $message
  2136. * Message to display.
  2137. * @param $group
  2138. * The group this message belongs to.
  2139. * @return
  2140. * TRUE on pass, FALSE on fail.
  2141. */
  2142. protected function assertNoPattern($pattern, $message = '', $group = 'Other') {
  2143. if (!$message) {
  2144. $message = t('Pattern "@pattern" not found', array('@pattern' => $pattern));
  2145. }
  2146. return $this->assert(!preg_match($pattern, $this->drupalGetContent()), $message, $group);
  2147. }
  2148. /**
  2149. * Pass if the page title is the given string.
  2150. *
  2151. * @param $title
  2152. * The string the title should be.
  2153. * @param $message
  2154. * Message to display.
  2155. * @param $group
  2156. * The group this message belongs to.
  2157. * @return
  2158. * TRUE on pass, FALSE on fail.
  2159. */
  2160. protected function assertTitle($title, $message, $group = 'Other') {
  2161. return $this->assertEqual(current($this->xpath('//title')), $title, $message, $group);
  2162. }
  2163. /**
  2164. * Pass if the page title is not the given string.
  2165. *
  2166. * @param $title
  2167. * The string the title should not be.
  2168. * @param $message
  2169. * Message to display.
  2170. * @param $group
  2171. * The group this message belongs to.
  2172. * @return
  2173. * TRUE on pass, FALSE on fail.
  2174. */
  2175. protected function assertNoTitle($title, $message, $group = 'Other') {
  2176. return $this->assertNotEqual(current($this->xpath('//title')), $title, $message, $group);
  2177. }
  2178. /**
  2179. * Assert that a field exists in the current page by the given XPath.
  2180. *
  2181. * @param $xpath
  2182. * XPath used to find the field.
  2183. * @param $value
  2184. * Value of the field to assert.
  2185. * @param $message
  2186. * Message to display.
  2187. * @param $group
  2188. * The group this message belongs to.
  2189. * @return
  2190. * TRUE on pass, FALSE on fail.
  2191. */
  2192. protected function assertFieldByXPath($xpath, $value, $message, $group = 'Other') {
  2193. $fields = $this->xpath($xpath);
  2194. // If value specified then check array for match.
  2195. $found = TRUE;
  2196. if ($value) {
  2197. $found = FALSE;
  2198. if ($fields) {
  2199. foreach ($fields as $field) {
  2200. if (isset($field['value']) && $field['value'] == $value) {
  2201. // Input element with correct value.
  2202. $found = TRUE;
  2203. }
  2204. elseif (isset($field->option)) {
  2205. // Select element found.
  2206. if ($this->getSelectedItem($field) == $value) {
  2207. $found = TRUE;
  2208. }
  2209. else {
  2210. // No item selected so use first item.
  2211. $items = $this->getAllOptions($field);
  2212. if (!empty($items) && $items[0]['value'] == $value) {
  2213. $found = TRUE;
  2214. }
  2215. }
  2216. }
  2217. elseif ((string) $field == $value) {
  2218. // Text area with correct text.
  2219. $found = TRUE;
  2220. }
  2221. }
  2222. }
  2223. }
  2224. return $this->assertTrue($fields && $found, $message, $group);
  2225. }
  2226. /**
  2227. * Get the selected value from a select field.
  2228. *
  2229. * @param $element
  2230. * SimpleXMLElement select element.
  2231. * @return
  2232. * The selected value or FALSE.
  2233. */
  2234. protected function getSelectedItem(SimpleXMLElement $element) {
  2235. foreach ($element->children() as $item) {
  2236. if (isset($item['selected'])) {
  2237. return $item['value'];
  2238. }
  2239. elseif ($item->getName() == 'optgroup') {
  2240. if ($value = $this->getSelectedItem($item)) {
  2241. return $value;
  2242. }
  2243. }
  2244. }
  2245. return FALSE;
  2246. }
  2247. /**
  2248. * Assert that a field does not exist in the current page by the given XPath.
  2249. *
  2250. * @param $xpath
  2251. * XPath used to find the field.
  2252. * @param $value
  2253. * Value of the field to assert.
  2254. * @param $message
  2255. * Message to display.
  2256. * @param $group
  2257. * The group this message belongs to.
  2258. * @return
  2259. * TRUE on pass, FALSE on fail.
  2260. */
  2261. protected function assertNoFieldByXPath($xpath, $value, $message, $group = 'Other') {
  2262. $fields = $this->xpath($xpath);
  2263. // If value specified then check array for match.
  2264. $found = TRUE;
  2265. if ($value) {
  2266. $found = FALSE;
  2267. if ($fields) {
  2268. foreach ($fields as $field) {
  2269. if ($field['value'] == $value) {
  2270. $found = TRUE;
  2271. }
  2272. }
  2273. }
  2274. }
  2275. return $this->assertFalse($fields && $found, $message, $group);
  2276. }
  2277. /**
  2278. * Assert that a field exists in the current page with the given name and value.
  2279. *
  2280. * @param $name
  2281. * Name of field to assert.
  2282. * @param $value
  2283. * Value of the field to assert.
  2284. * @param $message
  2285. * Message to display.
  2286. * @param $group
  2287. * The group this message belongs to.
  2288. * @return
  2289. * TRUE on pass, FALSE on fail.
  2290. */
  2291. protected function assertFieldByName($name, $value = '', $message = '') {
  2292. return $this->assertFieldByXPath($this->constructFieldXpath('name', $name), $value, $message ? $message : t('Found field by name @name', array('@name' => $name)), t('Browser'));
  2293. }
  2294. /**
  2295. * Assert that a field does not exist with the given name and value.
  2296. *
  2297. * @param $name
  2298. * Name of field to assert.
  2299. * @param $value
  2300. * Value of the field to assert.
  2301. * @param $message
  2302. * Message to display.
  2303. * @param $group
  2304. * The group this message belongs to.
  2305. * @return
  2306. * TRUE on pass, FALSE on fail.
  2307. */
  2308. protected function assertNoFieldByName($name, $value = '', $message = '') {
  2309. return $this->assertNoFieldByXPath($this->constructFieldXpath('name', $name), $value, $message ? $message : t('Did not find field by name @name', array('@name' => $name)), t('Browser'));
  2310. }
  2311. /**
  2312. * Assert that a field exists in the current page with the given id and value.
  2313. *
  2314. * @param $id
  2315. * Id of field to assert.
  2316. * @param $value
  2317. * Value of the field to assert.
  2318. * @param $message
  2319. * Message to display.
  2320. * @param $group
  2321. * The group this message belongs to.
  2322. * @return
  2323. * TRUE on pass, FALSE on fail.
  2324. */
  2325. protected function assertFieldById($id, $value = '', $message = '') {
  2326. return $this->assertFieldByXPath($this->constructFieldXpath('id', $id), $value, $message ? $message : t('Found field by id @id', array('@id' => $id)), t('Browser'));
  2327. }
  2328. /**
  2329. * Assert that a field does not exist with the given id and value.
  2330. *
  2331. * @param $id
  2332. * Id of field to assert.
  2333. * @param $value
  2334. * Value of the field to assert.
  2335. * @param $message
  2336. * Message to display.
  2337. * @param $group
  2338. * The group this message belongs to.
  2339. * @return
  2340. * TRUE on pass, FALSE on fail.
  2341. */
  2342. protected function assertNoFieldById($id, $value = '', $message = '') {
  2343. return $this->assertNoFieldByXPath($this->constructFieldXpath('id', $id), $value, $message ? $message : t('Did not find field by id @id', array('@id' => $id)), t('Browser'));
  2344. }
  2345. /**
  2346. * Assert that a checkbox field in the current page is checked.
  2347. *
  2348. * @param $id
  2349. * Id of field to assert.
  2350. * @param $message
  2351. * Message to display.
  2352. * @return
  2353. * TRUE on pass, FALSE on fail.
  2354. */
  2355. protected function assertFieldChecked($id, $message = '') {
  2356. $elements = $this->xpath('//input[@id="' . $id . '"]');
  2357. return $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), $message ? $message : t('Checkbox field @id is checked.', array('@id' => $id)), t('Browser'));
  2358. }
  2359. /**
  2360. * Assert that a checkbox field in the current page is not checked.
  2361. *
  2362. * @param $id
  2363. * Id of field to assert.
  2364. * @param $message
  2365. * Message to display.
  2366. * @return
  2367. * TRUE on pass, FALSE on fail.
  2368. */
  2369. protected function assertNoFieldChecked($id, $message = '') {
  2370. $elements = $this->xpath('//input[@id="' . $id . '"]');
  2371. return $this->assertTrue(isset($elements[0]) && empty($elements[0]['checked']), $message ? $message : t('Checkbox field @id is not checked.', array('@id' => $id)), t('Browser'));
  2372. }
  2373. /**
  2374. * Assert that a select option in the current page is not checked.
  2375. *
  2376. * @param $id
  2377. * Id of select field to assert.
  2378. * @param $option
  2379. * Option to assert.
  2380. * @param $message
  2381. * Message to display.
  2382. * @return
  2383. * TRUE on pass, FALSE on fail.
  2384. */
  2385. protected function assertOptionSelected($id, $option, $message = '') {
  2386. $elements = $this->xpath('//select[@id="' . $id . '"]//option[@value="' . $option . '"]');
  2387. 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'));
  2388. }
  2389. /**
  2390. * Assert that a select option in the current page is not checked.
  2391. *
  2392. * @param $id
  2393. * Id of select field to assert.
  2394. * @param $option
  2395. * Option to assert.
  2396. * @param $message
  2397. * Message to display.
  2398. * @return
  2399. * TRUE on pass, FALSE on fail.
  2400. */
  2401. protected function assertNoOptionSelected($id, $option, $message = '') {
  2402. $elements = $this->xpath('//select[@id="' . $id . '"]//option[@value="' . $option . '"]');
  2403. 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'));
  2404. }
  2405. /**
  2406. * Assert that a field exists with the given name or id.
  2407. *
  2408. * @param $field
  2409. * Name or id of field to assert.
  2410. * @param $message
  2411. * Message to display.
  2412. * @param $group
  2413. * The group this message belongs to.
  2414. * @return
  2415. * TRUE on pass, FALSE on fail.
  2416. */
  2417. protected function assertField($field, $message = '', $group = 'Other') {
  2418. return $this->assertFieldByXPath($this->constructFieldXpath('name', $field) . '|' . $this->constructFieldXpath('id', $field), '', $message, $group);
  2419. }
  2420. /**
  2421. * Assert that a field does not exist with the given name or id.
  2422. *
  2423. * @param $field
  2424. * Name or id of field to assert.
  2425. * @param $message
  2426. * Message to display.
  2427. * @param $group
  2428. * The group this message belongs to.
  2429. * @return
  2430. * TRUE on pass, FALSE on fail.
  2431. */
  2432. protected function assertNoField($field, $message = '', $group = 'Other') {
  2433. return $this->assertNoFieldByXPath($this->constructFieldXpath('name', $field) . '|' . $this->constructFieldXpath('id', $field), '', $message, $group);
  2434. }
  2435. /**
  2436. * Helper function: construct an XPath for the given set of attributes and value.
  2437. *
  2438. * @param $attribute
  2439. * Field attributes.
  2440. * @param $value
  2441. * Value of field.
  2442. * @return
  2443. * XPath for specified values.
  2444. */
  2445. protected function constructFieldXpath($attribute, $value) {
  2446. return '//textarea[@' . $attribute . '="' . $value . '"]|//input[@' . $attribute . '="' . $value . '"]|//select[@' . $attribute . '="' . $value . '"]';
  2447. }
  2448. /**
  2449. * Assert the page responds with the specified response code.
  2450. *
  2451. * @param $code
  2452. * Response code. For example 200 is a successful page request. For a list
  2453. * of all codes see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html.
  2454. * @param $message
  2455. * Message to display.
  2456. * @return
  2457. * Assertion result.
  2458. */
  2459. protected function assertResponse($code, $message = '') {
  2460. $curl_code = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE);
  2461. $match = is_array($code) ? in_array($curl_code, $code) : $curl_code == $code;
  2462. return $this->assertTrue($match, $message ? $message : t('HTTP response expected !code, actual !curl_code', array('!code' => $code, '!curl_code' => $curl_code)), t('Browser'));
  2463. }
  2464. /**
  2465. * Assert that the most recently sent e-mail message has a field with the given value.
  2466. *
  2467. * @param $name
  2468. * Name of field or message property to assert. Examples: subject, body, id, ...
  2469. * @param $value
  2470. * Value of the field to assert.
  2471. * @param $message
  2472. * Message to display.
  2473. * @return
  2474. * TRUE on pass, FALSE on fail.
  2475. */
  2476. protected function assertMail($name, $value = '', $message = '') {
  2477. $captured_emails = variable_get('drupal_test_email_collector', array());
  2478. $email = end($captured_emails);
  2479. return $this->assertTrue($email && isset($email[$name]) && $email[$name] == $value, $message, t('E-mail'));
  2480. }
  2481. /**
  2482. * Log verbose message in a text file.
  2483. *
  2484. * The a link to the vebose message will be placed in the test results via
  2485. * as a passing assertion with the text '[verbose message]'.
  2486. *
  2487. * @param $message
  2488. * The verbose message to be stored.
  2489. * @see simpletest_verbose()
  2490. */
  2491. protected function verbose($message) {
  2492. if ($id = simpletest_verbose($message)) {
  2493. $url = file_create_url($this->originalFileDirectory . '/simpletest/verbose/' . get_class($this) . '-' . $id . '.html');
  2494. $this->error(l(t('Verbose message'), $url, array('attributes' => array('target' => '_blank'))), 'User notice');
  2495. }
  2496. }
  2497. }
  2498. /**
  2499. * Log verbose message in a text file.
  2500. *
  2501. * If verbose mode is enabled then page requests will be dumped to a file and
  2502. * presented on the test result screen. The messages will be placed in a file
  2503. * located in the simpletest directory in the original file system.
  2504. *
  2505. * @param $message
  2506. * The verbose message to be stored.
  2507. * @param $original_file_directory
  2508. * The original file directory, before it was changed for testing purposes.
  2509. * @param $test_class
  2510. * The active test case class.
  2511. * @return
  2512. * The ID of the message to be placed in related assertion messages.
  2513. * @see DrupalTestCase->originalFileDirectory
  2514. * @see DrupalWebTestCase->verbose()
  2515. */
  2516. function simpletest_verbose($message, $original_file_directory = NULL, $test_class = NULL) {
  2517. static $file_directory = NULL, $class = NULL, $id = 1, $verbose = NULL;
  2518. // Will pass first time during setup phase, and when verbose is TRUE.
  2519. if (!isset($original_file_directory) && !$verbose) {
  2520. return FALSE;
  2521. }
  2522. if ($message && $file_directory) {
  2523. $message = '<hr />ID #' . $id . ' (<a href="' . $class . '-' . ($id - 1) . '.html">Previous</a> | <a href="' . $class . '-' . ($id + 1) . '.html">Next</a>)<hr />' . $message;
  2524. file_put_contents($file_directory . "/simpletest/verbose/$class-$id.html", $message, FILE_APPEND);
  2525. return $id++;
  2526. }
  2527. if ($original_file_directory) {
  2528. $file_directory = $original_file_directory;
  2529. $class = $test_class;
  2530. $verbose = variable_get('simpletest_verbose', FALSE);
  2531. $directory = $file_directory . '/simpletest/verbose';
  2532. $writable = file_prepare_directory($directory, FILE_CREATE_DIRECTORY);
  2533. if ($writable && !file_exists($directory . '/.htaccess')) {
  2534. file_put_contents($directory . '/.htaccess', "<IfModule mod_expires.c>\nExpiresActive Off\n</IfModule>\n");
  2535. }
  2536. return $writable;
  2537. }
  2538. return FALSE;
  2539. }