bootstrap.test

Classes

Namesort descending Description
BootstrapAutoloadTestCase Tests the auto-loading behavior of the code registry.
BootstrapDestinationTestCase Tests for $_GET['destination'] and $_REQUEST['destination'] validation.
BootstrapGetFilenameTestCase Test drupal_get_filename()'s availability.
BootstrapGetFilenameWebTestCase Test drupal_get_filename() in the context of a full Drupal installation.
BootstrapIPAddressTestCase
BootstrapMiscTestCase Test miscellaneous functions in bootstrap.inc.
BootstrapOverrideServerVariablesTestCase Tests for overriding server variables via the API.
BootstrapPageCacheTestCase
BootstrapResettableStaticTestCase Test that resetting static variables works.
BootstrapTimerTestCase
BootstrapVariableTestCase
HookBootExitTestCase Test hook_boot() and hook_exit().

File

drupal/modules/simpletest/tests/bootstrap.test
View source
  1. <?php
  2. class BootstrapIPAddressTestCase extends DrupalWebTestCase {
  3. public static function getInfo() {
  4. return array(
  5. 'name' => 'IP address and HTTP_HOST test',
  6. 'description' => 'Get the IP address from the current visitor from the server variables, check hostname validation.',
  7. 'group' => 'Bootstrap'
  8. );
  9. }
  10. function setUp() {
  11. $this->oldserver = $_SERVER;
  12. $this->remote_ip = '127.0.0.1';
  13. $this->proxy_ip = '127.0.0.2';
  14. $this->proxy2_ip = '127.0.0.3';
  15. $this->forwarded_ip = '127.0.0.4';
  16. $this->cluster_ip = '127.0.0.5';
  17. $this->untrusted_ip = '0.0.0.0';
  18. drupal_static_reset('ip_address');
  19. $_SERVER['REMOTE_ADDR'] = $this->remote_ip;
  20. unset($_SERVER['HTTP_X_FORWARDED_FOR']);
  21. unset($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']);
  22. parent::setUp();
  23. }
  24. function tearDown() {
  25. $_SERVER = $this->oldserver;
  26. drupal_static_reset('ip_address');
  27. parent::tearDown();
  28. }
  29. /**
  30. * test IP Address and hostname
  31. */
  32. function testIPAddressHost() {
  33. // Test the normal IP address.
  34. $this->assertTrue(
  35. ip_address() == $this->remote_ip,
  36. 'Got remote IP address.'
  37. );
  38. // Proxy forwarding on but no proxy addresses defined.
  39. variable_set('reverse_proxy', 1);
  40. $this->assertTrue(
  41. ip_address() == $this->remote_ip,
  42. 'Proxy forwarding without trusted proxies got remote IP address.'
  43. );
  44. // Proxy forwarding on and proxy address not trusted.
  45. variable_set('reverse_proxy_addresses', array($this->proxy_ip, $this->proxy2_ip));
  46. drupal_static_reset('ip_address');
  47. $_SERVER['REMOTE_ADDR'] = $this->untrusted_ip;
  48. $this->assertTrue(
  49. ip_address() == $this->untrusted_ip,
  50. 'Proxy forwarding with untrusted proxy got remote IP address.'
  51. );
  52. // Proxy forwarding on and proxy address trusted.
  53. $_SERVER['REMOTE_ADDR'] = $this->proxy_ip;
  54. $_SERVER['HTTP_X_FORWARDED_FOR'] = $this->forwarded_ip;
  55. drupal_static_reset('ip_address');
  56. $this->assertTrue(
  57. ip_address() == $this->forwarded_ip,
  58. 'Proxy forwarding with trusted proxy got forwarded IP address.'
  59. );
  60. // Proxy forwarding on and proxy address trusted and visiting from proxy.
  61. $_SERVER['REMOTE_ADDR'] = $this->proxy_ip;
  62. $_SERVER['HTTP_X_FORWARDED_FOR'] = $this->proxy_ip;
  63. drupal_static_reset('ip_address');
  64. $this->assertTrue(
  65. ip_address() == $this->proxy_ip,
  66. 'Visiting from trusted proxy got proxy IP address.'
  67. );
  68. // Multi-tier architecture with comma separated values in header.
  69. $_SERVER['REMOTE_ADDR'] = $this->proxy_ip;
  70. $_SERVER['HTTP_X_FORWARDED_FOR'] = implode(', ', array($this->untrusted_ip, $this->forwarded_ip, $this->proxy2_ip));
  71. drupal_static_reset('ip_address');
  72. $this->assertTrue(
  73. ip_address() == $this->forwarded_ip,
  74. 'Proxy forwarding with trusted 2-tier proxy got forwarded IP address.'
  75. );
  76. // Custom client-IP header.
  77. variable_set('reverse_proxy_header', 'HTTP_X_CLUSTER_CLIENT_IP');
  78. $_SERVER['HTTP_X_CLUSTER_CLIENT_IP'] = $this->cluster_ip;
  79. drupal_static_reset('ip_address');
  80. $this->assertTrue(
  81. ip_address() == $this->cluster_ip,
  82. 'Cluster environment got cluster client IP.'
  83. );
  84. // Verifies that drupal_valid_http_host() prevents invalid characters.
  85. $this->assertFalse(drupal_valid_http_host('security/.drupal.org:80'), 'HTTP_HOST with / is invalid');
  86. $this->assertFalse(drupal_valid_http_host('security\\.drupal.org:80'), 'HTTP_HOST with \\ is invalid');
  87. $this->assertFalse(drupal_valid_http_host('security<.drupal.org:80'), 'HTTP_HOST with &lt; is invalid');
  88. $this->assertFalse(drupal_valid_http_host('security..drupal.org:80'), 'HTTP_HOST with .. is invalid');
  89. // Verifies that host names are shorter than 1000 characters.
  90. $this->assertFalse(drupal_valid_http_host(str_repeat('x', 1001)), 'HTTP_HOST with more than 1000 characters is invalid.');
  91. $this->assertFalse(drupal_valid_http_host(str_repeat('.', 101)), 'HTTP_HOST with more than 100 subdomains is invalid.');
  92. $this->assertFalse(drupal_valid_http_host(str_repeat(':', 101)), 'HTTP_HOST with more than 100 portseparators is invalid.');
  93. // IPv6 loopback address
  94. $this->assertTrue(drupal_valid_http_host('[::1]:80'), 'HTTP_HOST containing IPv6 loopback is valid');
  95. }
  96. }
  97. class BootstrapPageCacheTestCase extends DrupalWebTestCase {
  98. public static function getInfo() {
  99. return array(
  100. 'name' => 'Page cache test',
  101. 'description' => 'Enable the page cache and test it with various HTTP requests.',
  102. 'group' => 'Bootstrap'
  103. );
  104. }
  105. function setUp() {
  106. parent::setUp('system_test');
  107. }
  108. /**
  109. * Test support for requests containing If-Modified-Since and If-None-Match headers.
  110. */
  111. function testConditionalRequests() {
  112. variable_set('cache', 1);
  113. // Fill the cache.
  114. $this->drupalGet('');
  115. $this->drupalHead('');
  116. $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.');
  117. $etag = $this->drupalGetHeader('ETag');
  118. $last_modified = $this->drupalGetHeader('Last-Modified');
  119. $this->drupalGet('', array(), array('If-Modified-Since: ' . $last_modified, 'If-None-Match: ' . $etag));
  120. $this->assertResponse(304, 'Conditional request returned 304 Not Modified.');
  121. $this->drupalGet('', array(), array('If-Modified-Since: ' . gmdate(DATE_RFC822, strtotime($last_modified)), 'If-None-Match: ' . $etag));
  122. $this->assertResponse(304, 'Conditional request with obsolete If-Modified-Since date returned 304 Not Modified.');
  123. $this->drupalGet('', array(), array('If-Modified-Since: ' . gmdate(DATE_RFC850, strtotime($last_modified)), 'If-None-Match: ' . $etag));
  124. $this->assertResponse(304, 'Conditional request with obsolete If-Modified-Since date returned 304 Not Modified.');
  125. $this->drupalGet('', array(), array('If-Modified-Since: ' . $last_modified));
  126. $this->assertResponse(200, 'Conditional request without If-None-Match returned 200 OK.');
  127. $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.');
  128. $this->drupalGet('', array(), array('If-Modified-Since: ' . gmdate(DATE_RFC7231, strtotime($last_modified) + 1), 'If-None-Match: ' . $etag));
  129. $this->assertResponse(200, 'Conditional request with new a If-Modified-Since date newer than Last-Modified returned 200 OK.');
  130. $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.');
  131. $user = $this->drupalCreateUser();
  132. $this->drupalLogin($user);
  133. $this->drupalGet('', array(), array('If-Modified-Since: ' . $last_modified, 'If-None-Match: ' . $etag));
  134. $this->assertResponse(200, 'Conditional request returned 200 OK for authenticated user.');
  135. $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Absence of Page was not cached.');
  136. $this->assertFalse($this->drupalGetHeader('ETag'), 'ETag HTTP headers are not present for logged in users.');
  137. $this->assertFalse($this->drupalGetHeader('Last-Modified'), 'Last-Modified HTTP headers are not present for logged in users.');
  138. }
  139. /**
  140. * Test cache headers.
  141. */
  142. function testPageCache() {
  143. variable_set('cache', 1);
  144. // Fill the cache.
  145. $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar')));
  146. $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.');
  147. $this->assertEqual($this->drupalGetHeader('Vary'), 'Cookie,Accept-Encoding', 'Vary header was sent.');
  148. $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'public, max-age=0', 'Cache-Control header was sent.');
  149. $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.');
  150. $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', 'Custom header was sent.');
  151. // Check cache.
  152. $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar')));
  153. $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.');
  154. $this->assertEqual($this->drupalGetHeader('Vary'), 'Cookie,Accept-Encoding', 'Vary: Cookie header was sent.');
  155. $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'public, max-age=0', 'Cache-Control header was sent.');
  156. $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.');
  157. $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', 'Custom header was sent.');
  158. // Check replacing default headers.
  159. $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Expires', 'value' => 'Fri, 19 Nov 2008 05:00:00 GMT')));
  160. $this->assertEqual($this->drupalGetHeader('Expires'), 'Fri, 19 Nov 2008 05:00:00 GMT', 'Default header was replaced.');
  161. $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Vary', 'value' => 'User-Agent')));
  162. $this->assertEqual($this->drupalGetHeader('Vary'), 'User-Agent,Accept-Encoding', 'Default header was replaced.');
  163. // Check that authenticated users bypass the cache.
  164. $user = $this->drupalCreateUser();
  165. $this->drupalLogin($user);
  166. $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar')));
  167. $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Caching was bypassed.');
  168. $this->assertTrue(strpos($this->drupalGetHeader('Vary'), 'Cookie') === FALSE, 'Vary: Cookie header was not sent.');
  169. $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate', 'Cache-Control header was sent.');
  170. $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.');
  171. $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', 'Custom header was sent.');
  172. }
  173. /**
  174. * Test page compression.
  175. *
  176. * The test should pass even if zlib.output_compression is enabled in php.ini,
  177. * .htaccess or similar, or if compression is done outside PHP, e.g. by the
  178. * mod_deflate Apache module.
  179. */
  180. function testPageCompression() {
  181. variable_set('cache', 1);
  182. // Fill the cache and verify that output is compressed.
  183. $this->drupalGet('', array(), array('Accept-Encoding: gzip,deflate'));
  184. $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.');
  185. $this->drupalSetContent(gzinflate(substr($this->drupalGetContent(), 10, -8)));
  186. $this->assertRaw('</html>', 'Page was gzip compressed.');
  187. // Verify that cached output is compressed.
  188. $this->drupalGet('', array(), array('Accept-Encoding: gzip,deflate'));
  189. $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.');
  190. $this->assertEqual($this->drupalGetHeader('Content-Encoding'), 'gzip', 'A Content-Encoding header was sent.');
  191. $this->drupalSetContent(gzinflate(substr($this->drupalGetContent(), 10, -8)));
  192. $this->assertRaw('</html>', 'Page was gzip compressed.');
  193. // Verify that a client without compression support gets an uncompressed page.
  194. $this->drupalGet('');
  195. $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.');
  196. $this->assertFalse($this->drupalGetHeader('Content-Encoding'), 'A Content-Encoding header was not sent.');
  197. $this->assertTitle(t('Welcome to @site-name | @site-name', array('@site-name' => variable_get('site_name', 'Drupal'))), 'Site title matches.');
  198. $this->assertRaw('</html>', 'Page was not compressed.');
  199. // Disable compression mode.
  200. variable_set('page_compression', FALSE);
  201. // Verify if cached page is still available for a client with compression support.
  202. $this->drupalGet('', array(), array('Accept-Encoding: gzip,deflate'));
  203. $this->drupalSetContent(gzinflate(substr($this->drupalGetContent(), 10, -8)));
  204. $this->assertRaw('</html>', 'Page was delivered after compression mode is changed (compression support enabled).');
  205. // Verify if cached page is still available for a client without compression support.
  206. $this->drupalGet('');
  207. $this->assertRaw('</html>', 'Page was delivered after compression mode is changed (compression support disabled).');
  208. }
  209. }
  210. class BootstrapVariableTestCase extends DrupalWebTestCase {
  211. function setUp() {
  212. parent::setUp('system_test');
  213. }
  214. public static function getInfo() {
  215. return array(
  216. 'name' => 'Variable test',
  217. 'description' => 'Make sure the variable system functions correctly.',
  218. 'group' => 'Bootstrap'
  219. );
  220. }
  221. /**
  222. * testVariable
  223. */
  224. function testVariable() {
  225. // Setting and retrieving values.
  226. $variable = $this->randomName();
  227. variable_set('simpletest_bootstrap_variable_test', $variable);
  228. $this->assertIdentical($variable, variable_get('simpletest_bootstrap_variable_test'), 'Setting and retrieving values');
  229. // Make sure the variable persists across multiple requests.
  230. $this->drupalGet('system-test/variable-get');
  231. $this->assertText($variable, 'Variable persists across multiple requests');
  232. // Deleting variables.
  233. $default_value = $this->randomName();
  234. variable_del('simpletest_bootstrap_variable_test');
  235. $variable = variable_get('simpletest_bootstrap_variable_test', $default_value);
  236. $this->assertIdentical($variable, $default_value, 'Deleting variables');
  237. }
  238. /**
  239. * Makes sure that the default variable parameter is passed through okay.
  240. */
  241. function testVariableDefaults() {
  242. // Tests passing nothing through to the default.
  243. $this->assertIdentical(NULL, variable_get('simpletest_bootstrap_variable_test'), 'Variables are correctly defaulting to NULL.');
  244. // Tests passing 5 to the default parameter.
  245. $this->assertIdentical(5, variable_get('simpletest_bootstrap_variable_test', 5), 'The default variable parameter is passed through correctly.');
  246. }
  247. }
  248. /**
  249. * Tests the auto-loading behavior of the code registry.
  250. */
  251. class BootstrapAutoloadTestCase extends DrupalWebTestCase {
  252. public static function getInfo() {
  253. return array(
  254. 'name' => 'Code registry',
  255. 'description' => 'Test that the code registry functions correctly.',
  256. 'group' => 'Bootstrap',
  257. );
  258. }
  259. function setUp() {
  260. parent::setUp('drupal_autoload_test');
  261. }
  262. /**
  263. * Tests that autoloader name matching is not case sensitive.
  264. */
  265. function testAutoloadCase() {
  266. // Test interface autoloader.
  267. $this->assertTrue(drupal_autoload_interface('drupalautoloadtestinterface'), 'drupal_autoload_interface() recognizes <em>DrupalAutoloadTestInterface</em> in lower case.');
  268. // Test class autoloader.
  269. $this->assertTrue(drupal_autoload_class('drupalautoloadtestclass'), 'drupal_autoload_class() recognizes <em>DrupalAutoloadTestClass</em> in lower case.');
  270. // Test trait autoloader.
  271. if (version_compare(PHP_VERSION, '5.4') >= 0) {
  272. $this->assertTrue(drupal_autoload_trait('drupalautoloadtesttrait'), 'drupal_autoload_trait() recognizes <em>DrupalAutoloadTestTrait</em> in lower case.');
  273. }
  274. }
  275. }
  276. /**
  277. * Test hook_boot() and hook_exit().
  278. */
  279. class HookBootExitTestCase extends DrupalWebTestCase {
  280. public static function getInfo() {
  281. return array(
  282. 'name' => 'Boot and exit hook invocation',
  283. 'description' => 'Test that hook_boot() and hook_exit() are called correctly.',
  284. 'group' => 'Bootstrap',
  285. );
  286. }
  287. function setUp() {
  288. parent::setUp('system_test', 'dblog');
  289. }
  290. /**
  291. * Test calling of hook_boot() and hook_exit().
  292. */
  293. function testHookBootExit() {
  294. // Test with cache disabled. Boot and exit should always fire.
  295. variable_set('cache', 0);
  296. $this->drupalGet('');
  297. $calls = 1;
  298. $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_boot'))->fetchField(), $calls, t('hook_boot called with disabled cache.'));
  299. $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_exit'))->fetchField(), $calls, t('hook_exit called with disabled cache.'));
  300. // Test with normal cache. Boot and exit should be called.
  301. variable_set('cache', 1);
  302. $this->drupalGet('');
  303. $calls++;
  304. $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_boot'))->fetchField(), $calls, t('hook_boot called with normal cache.'));
  305. $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_exit'))->fetchField(), $calls, t('hook_exit called with normal cache.'));
  306. // Boot and exit should not fire since the page is cached.
  307. variable_set('page_cache_invoke_hooks', FALSE);
  308. $this->assertTrue(cache_get(url('', array('absolute' => TRUE)), 'cache_page'), t('Page has been cached.'));
  309. $this->drupalGet('');
  310. $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_boot'))->fetchField(), $calls, t('hook_boot not called with aggressive cache and a cached page.'));
  311. $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_exit'))->fetchField(), $calls, t('hook_exit not called with aggressive cache and a cached page.'));
  312. // Test with page cache cleared, boot and exit should be called.
  313. $this->assertTrue(db_delete('cache_page')->execute(), t('Page cache cleared.'));
  314. $this->drupalGet('');
  315. $calls++;
  316. $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_boot'))->fetchField(), $calls, t('hook_boot called with aggressive cache and no cached page.'));
  317. $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_exit'))->fetchField(), $calls, t('hook_exit called with aggressive cache and no cached page.'));
  318. }
  319. }
  320. /**
  321. * Test drupal_get_filename()'s availability.
  322. */
  323. class BootstrapGetFilenameTestCase extends DrupalUnitTestCase {
  324. public static function getInfo() {
  325. return array(
  326. 'name' => 'Get filename test (without the system table)',
  327. 'description' => 'Test that drupal_get_filename() works correctly when the database is not available.',
  328. 'group' => 'Bootstrap',
  329. );
  330. }
  331. /**
  332. * The last file-related error message triggered by the filename test.
  333. *
  334. * Used by BootstrapGetFilenameTestCase::testDrupalGetFilename().
  335. */
  336. protected $getFilenameTestTriggeredError;
  337. /**
  338. * Test that drupal_get_filename() works correctly when the file is not found in the database.
  339. */
  340. function testDrupalGetFilename() {
  341. // Reset the static cache so we can test the "db is not active" code of
  342. // drupal_get_filename().
  343. drupal_static_reset('drupal_get_filename');
  344. // Retrieving the location of a module.
  345. $this->assertIdentical(drupal_get_filename('module', 'php'), 'modules/php/php.module', t('Retrieve module location.'));
  346. // Retrieving the location of a theme.
  347. $this->assertIdentical(drupal_get_filename('theme', 'stark'), 'themes/stark/stark.info', t('Retrieve theme location.'));
  348. // Retrieving the location of a theme engine.
  349. $this->assertIdentical(drupal_get_filename('theme_engine', 'phptemplate'), 'themes/engines/phptemplate/phptemplate.engine', t('Retrieve theme engine location.'));
  350. // Retrieving the location of a profile. Profiles are a special case with
  351. // a fixed location and naming.
  352. $this->assertIdentical(drupal_get_filename('profile', 'standard'), 'profiles/standard/standard.profile', t('Retrieve install profile location.'));
  353. // When a file is not found in the database cache, drupal_get_filename()
  354. // searches several locations on the filesystem, including the DRUPAL_ROOT
  355. // directory. We use the '.script' extension below because this is a
  356. // non-existent filetype that will definitely not exist in the database.
  357. // Since there is already a scripts directory, drupal_get_filename() will
  358. // automatically check there for 'script' files, just as it does for (e.g.)
  359. // 'module' files in modules.
  360. $this->assertIdentical(drupal_get_filename('script', 'test'), 'scripts/test.script', t('Retrieve test script location.'));
  361. // When searching for a module that does not exist, drupal_get_filename()
  362. // should return NULL and trigger an appropriate error message.
  363. $this->getFilenameTestTriggeredError = NULL;
  364. set_error_handler(array($this, 'fileNotFoundErrorHandler'));
  365. $non_existing_module = $this->randomName();
  366. $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that does not exist returns NULL.');
  367. $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) === 0, 'Searching for an item that does not exist triggers the correct error.');
  368. restore_error_handler();
  369. // Check that the result is stored in the file system scan cache.
  370. $file_scans = _drupal_file_scan_cache();
  371. $this->assertIdentical($file_scans['module'][$non_existing_module], FALSE, 'Searching for a module that does not exist creates a record in the missing and moved files static variable.');
  372. // Performing the search again in the same request still should not find
  373. // the file, but the error message should not be repeated (therefore we do
  374. // not override the error handler here).
  375. $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that does not exist returns NULL during the second search.');
  376. }
  377. /**
  378. * Skips handling of "file not found" errors.
  379. */
  380. public function fileNotFoundErrorHandler($error_level, $message, $filename, $line, $context) {
  381. // Skip error handling if this is a "file not found" error.
  382. if (strpos($message, 'is missing from the file system:') !== FALSE || strpos($message, 'has moved within the file system:') !== FALSE) {
  383. $this->getFilenameTestTriggeredError = $message;
  384. return;
  385. }
  386. _drupal_error_handler($error_level, $message, $filename, $line, $context);
  387. }
  388. }
  389. /**
  390. * Test drupal_get_filename() in the context of a full Drupal installation.
  391. */
  392. class BootstrapGetFilenameWebTestCase extends DrupalWebTestCase {
  393. public static function getInfo() {
  394. return array(
  395. 'name' => 'Get filename test (full installation)',
  396. 'description' => 'Test that drupal_get_filename() works correctly in the context of a full Drupal installation.',
  397. 'group' => 'Bootstrap',
  398. );
  399. }
  400. function setUp() {
  401. parent::setUp('system_test');
  402. }
  403. /**
  404. * The last file-related error message triggered by the filename test.
  405. *
  406. * Used by BootstrapGetFilenameWebTestCase::testDrupalGetFilename().
  407. */
  408. protected $getFilenameTestTriggeredError;
  409. /**
  410. * Test that drupal_get_filename() works correctly with a full Drupal site.
  411. */
  412. function testDrupalGetFilename() {
  413. // Search for a module that exists in the file system and the {system}
  414. // table and make sure that it is found.
  415. $this->assertIdentical(drupal_get_filename('module', 'node'), 'modules/node/node.module', 'Module found at expected location.');
  416. // Search for a module that does not exist in either the file system or the
  417. // {system} table. Make sure that an appropriate error is triggered and
  418. // that the module winds up in the static and persistent cache.
  419. $this->getFilenameTestTriggeredError = NULL;
  420. set_error_handler(array($this, 'fileNotFoundErrorHandler'));
  421. $non_existing_module = $this->randomName();
  422. $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that does not exist returns NULL.');
  423. $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) === 0, 'Searching for a module that does not exist triggers the correct error.');
  424. restore_error_handler();
  425. $file_scans = _drupal_file_scan_cache();
  426. $this->assertIdentical($file_scans['module'][$non_existing_module], FALSE, 'Searching for a module that does not exist creates a record in the missing and moved files static variable.');
  427. drupal_file_scan_write_cache();
  428. $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap');
  429. $this->assertIdentical($cache->data['module'][$non_existing_module], FALSE, 'Searching for a module that does not exist creates a record in the missing and moved files persistent cache.');
  430. // Simulate moving a module to a location that does not match the location
  431. // in the {system} table and perform similar tests as above.
  432. db_update('system')
  433. ->fields(array('filename' => 'modules/simpletest/tests/fake_location/module_test.module'))
  434. ->condition('name', 'module_test')
  435. ->condition('type', 'module')
  436. ->execute();
  437. $this->getFilenameTestTriggeredError = NULL;
  438. set_error_handler(array($this, 'fileNotFoundErrorHandler'));
  439. $this->assertIdentical(drupal_get_filename('module', 'module_test'), 'modules/simpletest/tests/module_test.module', 'Searching for a module that has moved finds the module at its new location.');
  440. $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module has moved within the file system: %name', array('%name' => 'module_test'))) === 0, 'Searching for a module that has moved triggers the correct error.');
  441. restore_error_handler();
  442. $file_scans = _drupal_file_scan_cache();
  443. $this->assertIdentical($file_scans['module']['module_test'], 'modules/simpletest/tests/module_test.module', 'Searching for a module that has moved creates a record in the missing and moved files static variable.');
  444. drupal_file_scan_write_cache();
  445. $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap');
  446. $this->assertIdentical($cache->data['module']['module_test'], 'modules/simpletest/tests/module_test.module', 'Searching for a module that has moved creates a record in the missing and moved files persistent cache.');
  447. // Simulate a module that exists in the {system} table but does not exist
  448. // in the file system and perform similar tests as above.
  449. $non_existing_module = $this->randomName();
  450. db_update('system')
  451. ->fields(array('name' => $non_existing_module))
  452. ->condition('name', 'module_test')
  453. ->condition('type', 'module')
  454. ->execute();
  455. $this->getFilenameTestTriggeredError = NULL;
  456. set_error_handler(array($this, 'fileNotFoundErrorHandler'));
  457. $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that exists in the system table but not in the file system returns NULL.');
  458. $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) === 0, 'Searching for a module that exists in the system table but not in the file system triggers the correct error.');
  459. restore_error_handler();
  460. $file_scans = _drupal_file_scan_cache();
  461. $this->assertIdentical($file_scans['module'][$non_existing_module], FALSE, 'Searching for a module that exists in the system table but not in the file system creates a record in the missing and moved files static variable.');
  462. drupal_file_scan_write_cache();
  463. $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap');
  464. $this->assertIdentical($cache->data['module'][$non_existing_module], FALSE, 'Searching for a module that exists in the system table but not in the file system creates a record in the missing and moved files persistent cache.');
  465. // Simulate a module that exists in the file system but not in the {system}
  466. // table and perform similar tests as above.
  467. db_delete('system')
  468. ->condition('name', 'common_test')
  469. ->condition('type', 'module')
  470. ->execute();
  471. system_list_reset();
  472. $this->getFilenameTestTriggeredError = NULL;
  473. set_error_handler(array($this, 'fileNotFoundErrorHandler'));
  474. $this->assertIdentical(drupal_get_filename('module', 'common_test'), 'modules/simpletest/tests/common_test.module', 'Searching for a module that does not exist in the system table finds the module at its actual location.');
  475. $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module has moved within the file system: %name', array('%name' => 'common_test'))) === 0, 'Searching for a module that does not exist in the system table triggers the correct error.');
  476. restore_error_handler();
  477. $file_scans = _drupal_file_scan_cache();
  478. $this->assertIdentical($file_scans['module']['common_test'], 'modules/simpletest/tests/common_test.module', 'Searching for a module that does not exist in the system table creates a record in the missing and moved files static variable.');
  479. drupal_file_scan_write_cache();
  480. $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap');
  481. $this->assertIdentical($cache->data['module']['common_test'], 'modules/simpletest/tests/common_test.module', 'Searching for a module that does not exist in the system table creates a record in the missing and moved files persistent cache.');
  482. }
  483. /**
  484. * Skips handling of "file not found" errors.
  485. */
  486. public function fileNotFoundErrorHandler($error_level, $message, $filename, $line, $context) {
  487. // Skip error handling if this is a "file not found" error.
  488. if (strpos($message, 'is missing from the file system:') !== FALSE || strpos($message, 'has moved within the file system:') !== FALSE) {
  489. $this->getFilenameTestTriggeredError = $message;
  490. return;
  491. }
  492. _drupal_error_handler($error_level, $message, $filename, $line, $context);
  493. }
  494. /**
  495. * Test that watchdog messages about missing files are correctly recorded.
  496. */
  497. public function testWatchdog() {
  498. // Search for a module that does not exist in either the file system or the
  499. // {system} table. Make sure that an appropriate warning is recorded in the
  500. // logs.
  501. $non_existing_module = $this->randomName();
  502. $query_parameters = array(
  503. ':type' => 'php',
  504. ':severity' => WATCHDOG_WARNING,
  505. );
  506. $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND severity = :severity', $query_parameters)->fetchField(), 0, 'No warning message appears in the logs before searching for a module that does not exist.');
  507. // Trigger the drupal_get_filename() call. This must be done via a request
  508. // to a separate URL since the watchdog() will happen in a shutdown
  509. // function, and so that SimpleTest can be told to ignore (and not fail as
  510. // a result of) the expected PHP warnings generated during this process.
  511. variable_set('system_test_drupal_get_filename_test_module_name', $non_existing_module);
  512. $this->drupalGet('system-test/drupal-get-filename');
  513. $message_variables = db_query('SELECT variables FROM {watchdog} WHERE type = :type AND severity = :severity', $query_parameters)->fetchCol();
  514. $this->assertEqual(count($message_variables), 1, 'A single warning message appears in the logs after searching for a module that does not exist.');
  515. $variables = reset($message_variables);
  516. $variables = unserialize($variables);
  517. $this->assertTrue(isset($variables['!message']) && strpos($variables['!message'], format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) !== FALSE, 'The warning message that appears in the logs after searching for a module that does not exist contains the expected text.');
  518. }
  519. /**
  520. * Test that drupal_get_filename() does not break recursive rebuilds.
  521. */
  522. public function testRecursiveRebuilds() {
  523. // Ensure that the drupal_get_filename() call due to a missing module does
  524. // not break the data returned by an attempted recursive rebuild. The code
  525. // path which is tested is as follows:
  526. // - Call drupal_get_schema().
  527. // - Within a hook_schema() implementation, trigger a drupal_get_filename()
  528. // search for a nonexistent module.
  529. // - In the watchdog() call that results from that, trigger
  530. // drupal_get_schema() again.
  531. // Without some kind of recursion protection, this could cause the second
  532. // drupal_get_schema() call to return incomplete results. This test ensures
  533. // that does not happen.
  534. $non_existing_module = $this->randomName();
  535. variable_set('system_test_drupal_get_filename_test_module_name', $non_existing_module);
  536. $this->drupalGet('system-test/drupal-get-filename-with-schema-rebuild');
  537. $original_drupal_get_schema_tables = variable_get('system_test_drupal_get_filename_with_schema_rebuild_original_tables');
  538. $final_drupal_get_schema_tables = variable_get('system_test_drupal_get_filename_with_schema_rebuild_final_tables');
  539. $this->assertTrue(!empty($original_drupal_get_schema_tables));
  540. $this->assertTrue(!empty($final_drupal_get_schema_tables));
  541. $this->assertEqual($original_drupal_get_schema_tables, $final_drupal_get_schema_tables);
  542. }
  543. }
  544. class BootstrapTimerTestCase extends DrupalUnitTestCase {
  545. public static function getInfo() {
  546. return array(
  547. 'name' => 'Timer test',
  548. 'description' => 'Test that timer_read() works both when a timer is running and when a timer is stopped.',
  549. 'group' => 'Bootstrap',
  550. );
  551. }
  552. /**
  553. * Test timer_read() to ensure it properly accumulates time when the timer
  554. * started and stopped multiple times.
  555. * @return
  556. */
  557. function testTimer() {
  558. timer_start('test');
  559. sleep(1);
  560. $this->assertTrue(timer_read('test') >= 1000, 'Timer measured 1 second of sleeping while running.');
  561. sleep(1);
  562. timer_stop('test');
  563. $this->assertTrue(timer_read('test') >= 2000, 'Timer measured 2 seconds of sleeping after being stopped.');
  564. timer_start('test');
  565. sleep(1);
  566. $this->assertTrue(timer_read('test') >= 3000, 'Timer measured 3 seconds of sleeping after being restarted.');
  567. sleep(1);
  568. $timer = timer_stop('test');
  569. $this->assertTrue(timer_read('test') >= 4000, 'Timer measured 4 seconds of sleeping after being stopped for a second time.');
  570. $this->assertEqual($timer['count'], 2, 'Timer counted 2 instances of being started.');
  571. }
  572. }
  573. /**
  574. * Test that resetting static variables works.
  575. */
  576. class BootstrapResettableStaticTestCase extends DrupalUnitTestCase {
  577. public static function getInfo() {
  578. return array(
  579. 'name' => 'Resettable static variables test',
  580. 'description' => 'Test that drupal_static() and drupal_static_reset() work.',
  581. 'group' => 'Bootstrap',
  582. );
  583. }
  584. /**
  585. * Test that a variable reference returned by drupal_static() gets reset when
  586. * drupal_static_reset() is called.
  587. */
  588. function testDrupalStatic() {
  589. $name = __CLASS__ . '_' . __METHOD__;
  590. $var = &drupal_static($name, 'foo');
  591. $this->assertEqual($var, 'foo', 'Variable returned by drupal_static() was set to its default.');
  592. // Call the specific reset and the global reset each twice to ensure that
  593. // multiple resets can be issued without odd side effects.
  594. $var = 'bar';
  595. drupal_static_reset($name);
  596. $this->assertEqual($var, 'foo', 'Variable was reset after first invocation of name-specific reset.');
  597. $var = 'bar';
  598. drupal_static_reset($name);
  599. $this->assertEqual($var, 'foo', 'Variable was reset after second invocation of name-specific reset.');
  600. $var = 'bar';
  601. drupal_static_reset();
  602. $this->assertEqual($var, 'foo', 'Variable was reset after first invocation of global reset.');
  603. $var = 'bar';
  604. drupal_static_reset();
  605. $this->assertEqual($var, 'foo', 'Variable was reset after second invocation of global reset.');
  606. }
  607. }
  608. /**
  609. * Test miscellaneous functions in bootstrap.inc.
  610. */
  611. class BootstrapMiscTestCase extends DrupalUnitTestCase {
  612. public static function getInfo() {
  613. return array(
  614. 'name' => 'Miscellaneous bootstrap unit tests',
  615. 'description' => 'Test miscellaneous functions in bootstrap.inc.',
  616. 'group' => 'Bootstrap',
  617. );
  618. }
  619. /**
  620. * Test miscellaneous functions in bootstrap.inc.
  621. */
  622. function testMisc() {
  623. // Test drupal_array_merge_deep().
  624. $link_options_1 = array('fragment' => 'x', 'attributes' => array('title' => 'X', 'class' => array('a', 'b')), 'language' => 'en');
  625. $link_options_2 = array('fragment' => 'y', 'attributes' => array('title' => 'Y', 'class' => array('c', 'd')), 'html' => TRUE);
  626. $expected = array('fragment' => 'y', 'attributes' => array('title' => 'Y', 'class' => array('a', 'b', 'c', 'd')), 'language' => 'en', 'html' => TRUE);
  627. $this->assertIdentical(drupal_array_merge_deep($link_options_1, $link_options_2), $expected, 'drupal_array_merge_deep() returned a properly merged array.');
  628. }
  629. /**
  630. * Tests that the drupal_check_memory_limit() function works as expected.
  631. */
  632. function testCheckMemoryLimit() {
  633. $memory_limit = ini_get('memory_limit');
  634. // Test that a very reasonable amount of memory is available.
  635. $this->assertTrue(drupal_check_memory_limit('30MB'), '30MB of memory tested available.');
  636. // Get the available memory and multiply it by two to make it unreasonably
  637. // high.
  638. $twice_avail_memory = ($memory_limit * 2) . 'MB';
  639. // The function should always return true if the memory limit is set to -1.
  640. $this->assertTrue(drupal_check_memory_limit($twice_avail_memory, -1), 'drupal_check_memory_limit() returns TRUE when a limit of -1 (none) is supplied');
  641. // Test that even though we have 30MB of memory available - the function
  642. // returns FALSE when given an upper limit for how much memory can be used.
  643. $this->assertFalse(drupal_check_memory_limit('30MB', '16MB'), 'drupal_check_memory_limit() returns FALSE with a 16MB upper limit on a 30MB requirement.');
  644. // Test that an equal amount of memory to the amount requested returns TRUE.
  645. $this->assertTrue(drupal_check_memory_limit('30MB', '30MB'), 'drupal_check_memory_limit() returns TRUE when requesting 30MB on a 30MB requirement.');
  646. }
  647. }
  648. /**
  649. * Tests for overriding server variables via the API.
  650. */
  651. class BootstrapOverrideServerVariablesTestCase extends DrupalUnitTestCase {
  652. public static function getInfo() {
  653. return array(
  654. 'name' => 'Overriding server variables',
  655. 'description' => 'Test that drupal_override_server_variables() works correctly.',
  656. 'group' => 'Bootstrap',
  657. );
  658. }
  659. /**
  660. * Test providing a direct URL to to drupal_override_server_variables().
  661. */
  662. function testDrupalOverrideServerVariablesProvidedURL() {
  663. $tests = array(
  664. 'http://example.com' => array(
  665. 'HTTP_HOST' => 'example.com',
  666. 'SCRIPT_NAME' => isset($_SERVER['SCRIPT_NAME']) ? $_SERVER['SCRIPT_NAME'] : NULL,
  667. ),
  668. 'http://example.com/index.php' => array(
  669. 'HTTP_HOST' => 'example.com',
  670. 'SCRIPT_NAME' => '/index.php',
  671. ),
  672. 'http://example.com/subdirectory/index.php' => array(
  673. 'HTTP_HOST' => 'example.com',
  674. 'SCRIPT_NAME' => '/subdirectory/index.php',
  675. ),
  676. );
  677. foreach ($tests as $url => $expected_server_values) {
  678. // Remember the original value of $_SERVER, since the function call below
  679. // will modify it.
  680. $original_server = $_SERVER;
  681. // Call drupal_override_server_variables() and ensure that all expected
  682. // $_SERVER variables were modified correctly.
  683. drupal_override_server_variables(array('url' => $url));
  684. foreach ($expected_server_values as $key => $value) {
  685. $this->assertIdentical($_SERVER[$key], $value);
  686. }
  687. // Restore the original value of $_SERVER.
  688. $_SERVER = $original_server;
  689. }
  690. }
  691. }
  692. /**
  693. * Tests for $_GET['destination'] and $_REQUEST['destination'] validation.
  694. */
  695. class BootstrapDestinationTestCase extends DrupalWebTestCase {
  696. public static function getInfo() {
  697. return array(
  698. 'name' => 'URL destination validation',
  699. 'description' => 'Test that $_GET[\'destination\'] and $_REQUEST[\'destination\'] cannot contain external URLs.',
  700. 'group' => 'Bootstrap',
  701. );
  702. }
  703. function setUp() {
  704. parent::setUp('system_test');
  705. }
  706. /**
  707. * Tests that $_GET/$_REQUEST['destination'] only contain internal URLs.
  708. *
  709. * @see _drupal_bootstrap_variables()
  710. * @see system_test_get_destination()
  711. * @see system_test_request_destination()
  712. */
  713. public function testDestination() {
  714. $test_cases = array(
  715. array(
  716. 'input' => 'node',
  717. 'output' => 'node',
  718. 'message' => "Standard internal example node path is present in the 'destination' parameter.",
  719. ),
  720. array(
  721. 'input' => '/example.com',
  722. 'output' => '/example.com',
  723. 'message' => 'Internal path with one leading slash is allowed.',
  724. ),
  725. array(
  726. 'input' => '//example.com/test',
  727. 'output' => '',
  728. 'message' => 'External URL without scheme is not allowed.',
  729. ),
  730. array(
  731. 'input' => 'example:test',
  732. 'output' => 'example:test',
  733. 'message' => 'Internal URL using a colon is allowed.',
  734. ),
  735. array(
  736. 'input' => 'http://example.com',
  737. 'output' => '',
  738. 'message' => 'External URL is not allowed.',
  739. ),
  740. array(
  741. 'input' => 'javascript:alert(0)',
  742. 'output' => 'javascript:alert(0)',
  743. 'message' => 'Javascript URL is allowed because it is treated as an internal URL.',
  744. ),
  745. );
  746. foreach ($test_cases as $test_case) {
  747. // Test $_GET['destination'].
  748. $this->drupalGet('system-test/get-destination', array('query' => array('destination' => $test_case['input'])));
  749. $this->assertIdentical($test_case['output'], $this->drupalGetContent(), $test_case['message']);
  750. // Test $_REQUEST['destination']. There's no form to submit to, so
  751. // drupalPost() won't work here; this just tests a direct $_POST request
  752. // instead.
  753. $curl_parameters = array(
  754. CURLOPT_URL => $this->getAbsoluteUrl('system-test/request-destination'),
  755. CURLOPT_POST => TRUE,
  756. CURLOPT_POSTFIELDS => 'destination=' . urlencode($test_case['input']),
  757. CURLOPT_HTTPHEADER => array(),
  758. );
  759. $post_output = $this->curlExec($curl_parameters);
  760. $this->assertIdentical($test_case['output'], $post_output, $test_case['message']);
  761. }
  762. // Make sure that 404 pages do not populate $_GET['destination'] with
  763. // external URLs.
  764. variable_set('site_404', 'system-test/get-destination');
  765. $this->drupalGet('http://example.com', array('external' => FALSE));
  766. $this->assertIdentical('', $this->drupalGetContent(), 'External URL is not allowed on 404 pages.');
  767. }
  768. }