FunctionalTestSetupTrait.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  1. <?php
  2. namespace Drupal\Core\Test;
  3. use Drupal\Component\FileCache\FileCacheFactory;
  4. use Drupal\Component\Utility\SafeMarkup;
  5. use Drupal\Core\Cache\Cache;
  6. use Drupal\Core\Config\Development\ConfigSchemaChecker;
  7. use Drupal\Core\Database\Database;
  8. use Drupal\Core\DrupalKernel;
  9. use Drupal\Core\Extension\MissingDependencyException;
  10. use Drupal\Core\Serialization\Yaml;
  11. use Drupal\Core\Session\UserSession;
  12. use Drupal\Core\Site\Settings;
  13. use Drupal\Core\StreamWrapper\StreamWrapperInterface;
  14. use Symfony\Component\DependencyInjection\ContainerInterface;
  15. use Symfony\Component\HttpFoundation\Request;
  16. use Symfony\Component\Yaml\Yaml as SymfonyYaml;
  17. /**
  18. * Defines a trait for shared functional test setup functionality.
  19. */
  20. trait FunctionalTestSetupTrait {
  21. /**
  22. * The "#1" admin user.
  23. *
  24. * @var \Drupal\Core\Session\AccountInterface
  25. */
  26. protected $rootUser;
  27. /**
  28. * The class loader to use for installation and initialization of setup.
  29. *
  30. * @var \Symfony\Component\Classloader\Classloader
  31. */
  32. protected $classLoader;
  33. /**
  34. * The config directories used in this test.
  35. */
  36. protected $configDirectories = [];
  37. /**
  38. * The flag to set 'apcu_ensure_unique_prefix' setting.
  39. *
  40. * Wide use of a unique prefix can lead to problems with memory, if tests are
  41. * run with a concurrency higher than 1. Therefore, FALSE by default.
  42. *
  43. * @var bool
  44. *
  45. * @see \Drupal\Core\Site\Settings::getApcuPrefix().
  46. */
  47. protected $apcuEnsureUniquePrefix = FALSE;
  48. /**
  49. * Prepares site settings and services before installation.
  50. */
  51. protected function prepareSettings() {
  52. // Prepare installer settings that are not install_drupal() parameters.
  53. // Copy and prepare an actual settings.php, so as to resemble a regular
  54. // installation.
  55. // Not using File API; a potential error must trigger a PHP warning.
  56. $directory = DRUPAL_ROOT . '/' . $this->siteDirectory;
  57. copy(DRUPAL_ROOT . '/sites/default/default.settings.php', $directory . '/settings.php');
  58. // The public file system path is created during installation. Additionally,
  59. // during tests:
  60. // - The temporary directory is set and created by install_base_system().
  61. // - The private file directory is created post install by
  62. // FunctionalTestSetupTrait::initConfig().
  63. // @see system_requirements()
  64. // @see TestBase::prepareEnvironment()
  65. // @see install_base_system()
  66. // @see \Drupal\Core\Test\FunctionalTestSetupTrait::initConfig()
  67. $settings['settings']['file_public_path'] = (object) [
  68. 'value' => $this->publicFilesDirectory,
  69. 'required' => TRUE,
  70. ];
  71. $settings['settings']['file_private_path'] = (object) [
  72. 'value' => $this->privateFilesDirectory,
  73. 'required' => TRUE,
  74. ];
  75. // Save the original site directory path, so that extensions in the
  76. // site-specific directory can still be discovered in the test site
  77. // environment.
  78. // @see \Drupal\Core\Extension\ExtensionDiscovery::scan()
  79. $settings['settings']['test_parent_site'] = (object) [
  80. 'value' => $this->originalSite,
  81. 'required' => TRUE,
  82. ];
  83. // Add the parent profile's search path to the child site's search paths.
  84. // @see \Drupal\Core\Extension\ExtensionDiscovery::getProfileDirectories()
  85. $settings['conf']['simpletest.settings']['parent_profile'] = (object) [
  86. 'value' => $this->originalProfile,
  87. 'required' => TRUE,
  88. ];
  89. $settings['settings']['apcu_ensure_unique_prefix'] = (object) [
  90. 'value' => $this->apcuEnsureUniquePrefix,
  91. 'required' => TRUE,
  92. ];
  93. $this->writeSettings($settings);
  94. // Allow for test-specific overrides.
  95. $settings_testing_file = DRUPAL_ROOT . '/' . $this->originalSite . '/settings.testing.php';
  96. if (file_exists($settings_testing_file)) {
  97. // Copy the testing-specific settings.php overrides in place.
  98. copy($settings_testing_file, $directory . '/settings.testing.php');
  99. // Add the name of the testing class to settings.php and include the
  100. // testing specific overrides.
  101. file_put_contents($directory . '/settings.php', "\n\$test_class = '" . get_class($this) . "';\n" . 'include DRUPAL_ROOT . \'/\' . $site_path . \'/settings.testing.php\';' . "\n", FILE_APPEND);
  102. }
  103. $settings_services_file = DRUPAL_ROOT . '/' . $this->originalSite . '/testing.services.yml';
  104. if (!file_exists($settings_services_file)) {
  105. // Otherwise, use the default services as a starting point for overrides.
  106. $settings_services_file = DRUPAL_ROOT . '/sites/default/default.services.yml';
  107. }
  108. // Copy the testing-specific service overrides in place.
  109. copy($settings_services_file, $directory . '/services.yml');
  110. if ($this->strictConfigSchema) {
  111. // Add a listener to validate configuration schema on save.
  112. $yaml = new SymfonyYaml();
  113. $content = file_get_contents($directory . '/services.yml');
  114. $services = $yaml->parse($content);
  115. $services['services']['simpletest.config_schema_checker'] = [
  116. 'class' => ConfigSchemaChecker::class,
  117. 'arguments' => ['@config.typed', $this->getConfigSchemaExclusions()],
  118. 'tags' => [['name' => 'event_subscriber']],
  119. ];
  120. file_put_contents($directory . '/services.yml', $yaml->dump($services));
  121. }
  122. // Since Drupal is bootstrapped already, install_begin_request() will not
  123. // bootstrap again. Hence, we have to reload the newly written custom
  124. // settings.php manually.
  125. Settings::initialize(DRUPAL_ROOT, $this->siteDirectory, $this->classLoader);
  126. }
  127. /**
  128. * Rewrites the settings.php file of the test site.
  129. *
  130. * @param array $settings
  131. * An array of settings to write out, in the format expected by
  132. * drupal_rewrite_settings().
  133. *
  134. * @see drupal_rewrite_settings()
  135. */
  136. protected function writeSettings(array $settings) {
  137. include_once DRUPAL_ROOT . '/core/includes/install.inc';
  138. $filename = $this->siteDirectory . '/settings.php';
  139. // system_requirements() removes write permissions from settings.php
  140. // whenever it is invoked.
  141. // Not using File API; a potential error must trigger a PHP warning.
  142. chmod($filename, 0666);
  143. drupal_rewrite_settings($settings, $filename);
  144. }
  145. /**
  146. * Changes parameters in the services.yml file.
  147. *
  148. * @param string $name
  149. * The name of the parameter.
  150. * @param string $value
  151. * The value of the parameter.
  152. */
  153. protected function setContainerParameter($name, $value) {
  154. $filename = $this->siteDirectory . '/services.yml';
  155. chmod($filename, 0666);
  156. $services = Yaml::decode(file_get_contents($filename));
  157. $services['parameters'][$name] = $value;
  158. file_put_contents($filename, Yaml::encode($services));
  159. // Ensure that the cache is deleted for the yaml file loader.
  160. $file_cache = FileCacheFactory::get('container_yaml_loader');
  161. $file_cache->delete($filename);
  162. }
  163. /**
  164. * Rebuilds \Drupal::getContainer().
  165. *
  166. * Use this to update the test process's kernel with a new service container.
  167. * For example, when the list of enabled modules is changed via the internal
  168. * browser the test process's kernel has a service container with an out of
  169. * date module list.
  170. *
  171. * @see TestBase::prepareEnvironment()
  172. * @see TestBase::restoreEnvironment()
  173. *
  174. * @todo Fix https://www.drupal.org/node/2021959 so that module enable/disable
  175. * changes are immediately reflected in \Drupal::getContainer(). Until then,
  176. * tests can invoke this workaround when requiring services from newly
  177. * enabled modules to be immediately available in the same request.
  178. */
  179. protected function rebuildContainer() {
  180. // Rebuild the kernel and bring it back to a fully bootstrapped state.
  181. $this->container = $this->kernel->rebuildContainer();
  182. // Make sure the url generator has a request object, otherwise calls to
  183. // $this->drupalGet() will fail.
  184. $this->prepareRequestForGenerator();
  185. }
  186. /**
  187. * Resets all data structures after having enabled new modules.
  188. *
  189. * This method is called by FunctionalTestSetupTrait::rebuildAll() after
  190. * enabling the requested modules. It must be called again when additional
  191. * modules are enabled later.
  192. *
  193. * @see \Drupal\Core\Test\FunctionalTestSetupTrait::rebuildAll()
  194. * @see \Drupal\Tests\BrowserTestBase::installDrupal()
  195. * @see \Drupal\simpletest\WebTestBase::setUp()
  196. */
  197. protected function resetAll() {
  198. // Clear all database and static caches and rebuild data structures.
  199. drupal_flush_all_caches();
  200. $this->container = \Drupal::getContainer();
  201. // Reset static variables and reload permissions.
  202. $this->refreshVariables();
  203. }
  204. /**
  205. * Refreshes in-memory configuration and state information.
  206. *
  207. * Useful after a page request is made that changes configuration or state in
  208. * a different thread.
  209. *
  210. * In other words calling a settings page with $this->drupalPostForm() with a
  211. * changed value would update configuration to reflect that change, but in the
  212. * thread that made the call (thread running the test) the changed values
  213. * would not be picked up.
  214. *
  215. * This method clears the cache and loads a fresh copy.
  216. */
  217. protected function refreshVariables() {
  218. // Clear the tag cache.
  219. \Drupal::service('cache_tags.invalidator')->resetChecksums();
  220. foreach (Cache::getBins() as $backend) {
  221. if (is_callable([$backend, 'reset'])) {
  222. $backend->reset();
  223. }
  224. }
  225. $this->container->get('config.factory')->reset();
  226. $this->container->get('state')->resetCache();
  227. }
  228. /**
  229. * Creates a mock request and sets it on the generator.
  230. *
  231. * This is used to manipulate how the generator generates paths during tests.
  232. * It also ensures that calls to $this->drupalGet() will work when running
  233. * from run-tests.sh because the url generator no longer looks at the global
  234. * variables that are set there but relies on getting this information from a
  235. * request object.
  236. *
  237. * @param bool $clean_urls
  238. * Whether to mock the request using clean urls.
  239. * @param array $override_server_vars
  240. * An array of server variables to override.
  241. *
  242. * @return \Symfony\Component\HttpFoundation\Request
  243. * The mocked request object.
  244. */
  245. protected function prepareRequestForGenerator($clean_urls = TRUE, $override_server_vars = []) {
  246. $request = Request::createFromGlobals();
  247. $server = $request->server->all();
  248. if (basename($server['SCRIPT_FILENAME']) != basename($server['SCRIPT_NAME'])) {
  249. // We need this for when the test is executed by run-tests.sh.
  250. // @todo Remove this once run-tests.sh has been converted to use a Request
  251. // object.
  252. $cwd = getcwd();
  253. $server['SCRIPT_FILENAME'] = $cwd . '/' . basename($server['SCRIPT_NAME']);
  254. $base_path = rtrim($server['REQUEST_URI'], '/');
  255. }
  256. else {
  257. $base_path = $request->getBasePath();
  258. }
  259. if ($clean_urls) {
  260. $request_path = $base_path ? $base_path . '/user' : 'user';
  261. }
  262. else {
  263. $request_path = $base_path ? $base_path . '/index.php/user' : '/index.php/user';
  264. }
  265. $server = array_merge($server, $override_server_vars);
  266. $request = Request::create($request_path, 'GET', [], [], [], $server);
  267. // Ensure the request time is REQUEST_TIME to ensure that API calls
  268. // in the test use the right timestamp.
  269. $request->server->set('REQUEST_TIME', REQUEST_TIME);
  270. $this->container->get('request_stack')->push($request);
  271. // The request context is normally set by the router_listener from within
  272. // its KernelEvents::REQUEST listener. In the simpletest parent site this
  273. // event is not fired, therefore it is necessary to updated the request
  274. // context manually here.
  275. $this->container->get('router.request_context')->fromRequest($request);
  276. return $request;
  277. }
  278. /**
  279. * Execute the non-interactive installer.
  280. *
  281. * @see install_drupal()
  282. */
  283. protected function doInstall() {
  284. require_once DRUPAL_ROOT . '/core/includes/install.core.inc';
  285. install_drupal($this->classLoader, $this->installParameters());
  286. }
  287. /**
  288. * Initialize settings created during install.
  289. */
  290. protected function initSettings() {
  291. Settings::initialize(DRUPAL_ROOT, $this->siteDirectory, $this->classLoader);
  292. foreach ($GLOBALS['config_directories'] as $type => $path) {
  293. $this->configDirectories[$type] = $path;
  294. }
  295. // After writing settings.php, the installer removes write permissions
  296. // from the site directory. To allow drupal_generate_test_ua() to write
  297. // a file containing the private key for drupal_valid_test_ua(), the site
  298. // directory has to be writable.
  299. // TestBase::restoreEnvironment() will delete the entire site directory.
  300. // Not using File API; a potential error must trigger a PHP warning.
  301. chmod(DRUPAL_ROOT . '/' . $this->siteDirectory, 0777);
  302. // During tests, cacheable responses should get the debugging cacheability
  303. // headers by default.
  304. $this->setContainerParameter('http.response.debug_cacheability_headers', TRUE);
  305. }
  306. /**
  307. * Initialize various configurations post-installation.
  308. *
  309. * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
  310. * The container.
  311. */
  312. protected function initConfig(ContainerInterface $container) {
  313. $config = $container->get('config.factory');
  314. // Manually create the private directory.
  315. file_prepare_directory($this->privateFilesDirectory, FILE_CREATE_DIRECTORY);
  316. // Manually configure the test mail collector implementation to prevent
  317. // tests from sending out emails and collect them in state instead.
  318. // While this should be enforced via settings.php prior to installation,
  319. // some tests expect to be able to test mail system implementations.
  320. $config->getEditable('system.mail')
  321. ->set('interface.default', 'test_mail_collector')
  322. ->save();
  323. // By default, verbosely display all errors and disable all production
  324. // environment optimizations for all tests to avoid needless overhead and
  325. // ensure a sane default experience for test authors.
  326. // @see https://www.drupal.org/node/2259167
  327. $config->getEditable('system.logging')
  328. ->set('error_level', 'verbose')
  329. ->save();
  330. $config->getEditable('system.performance')
  331. ->set('css.preprocess', FALSE)
  332. ->set('js.preprocess', FALSE)
  333. ->save();
  334. // Set an explicit time zone to not rely on the system one, which may vary
  335. // from setup to setup. The Australia/Sydney time zone is chosen so all
  336. // tests are run using an edge case scenario (UTC10 and DST). This choice
  337. // is made to prevent time zone related regressions and reduce the
  338. // fragility of the testing system in general.
  339. $config->getEditable('system.date')
  340. ->set('timezone.default', 'Australia/Sydney')
  341. ->save();
  342. }
  343. /**
  344. * Initializes user 1 for the site to be installed.
  345. */
  346. protected function initUserSession() {
  347. $password = $this->randomMachineName();
  348. // Define information about the user 1 account.
  349. $this->rootUser = new UserSession([
  350. 'uid' => 1,
  351. 'name' => 'admin',
  352. 'mail' => 'admin@example.com',
  353. 'pass_raw' => $password,
  354. 'passRaw' => $password,
  355. 'timezone' => date_default_timezone_get(),
  356. ]);
  357. // The child site derives its session name from the database prefix when
  358. // running web tests.
  359. $this->generateSessionName($this->databasePrefix);
  360. }
  361. /**
  362. * Initializes the kernel after installation.
  363. *
  364. * @param \Symfony\Component\HttpFoundation\Request $request
  365. * Request object.
  366. *
  367. * @return \Symfony\Component\DependencyInjection\ContainerInterface
  368. * The container.
  369. */
  370. protected function initKernel(Request $request) {
  371. $this->kernel = DrupalKernel::createFromRequest($request, $this->classLoader, 'prod', TRUE);
  372. $this->kernel->prepareLegacyRequest($request);
  373. // Force the container to be built from scratch instead of loaded from the
  374. // disk. This forces us to not accidentally load the parent site.
  375. return $this->kernel->rebuildContainer();
  376. }
  377. /**
  378. * Install modules defined by `static::$modules`.
  379. *
  380. * To install test modules outside of the testing environment, add
  381. * @code
  382. * $settings['extension_discovery_scan_tests'] = TRUE;
  383. * @endcode
  384. * to your settings.php.
  385. *
  386. * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
  387. * The container.
  388. */
  389. protected function installModulesFromClassProperty(ContainerInterface $container) {
  390. $class = get_class($this);
  391. $modules = [];
  392. while ($class) {
  393. if (property_exists($class, 'modules')) {
  394. $modules = array_merge($modules, $class::$modules);
  395. }
  396. $class = get_parent_class($class);
  397. }
  398. if ($modules) {
  399. $modules = array_unique($modules);
  400. try {
  401. $success = $container->get('module_installer')->install($modules, TRUE);
  402. $this->assertTrue($success, SafeMarkup::format('Enabled modules: %modules', ['%modules' => implode(', ', $modules)]));
  403. }
  404. catch (MissingDependencyException $e) {
  405. // The exception message has all the details.
  406. $this->fail($e->getMessage());
  407. }
  408. $this->rebuildContainer();
  409. }
  410. }
  411. /**
  412. * Resets and rebuilds the environment after setup.
  413. */
  414. protected function rebuildAll() {
  415. // Reset/rebuild all data structures after enabling the modules, primarily
  416. // to synchronize all data structures and caches between the test runner and
  417. // the child site.
  418. // @see \Drupal\Core\DrupalKernel::bootCode()
  419. // @todo Test-specific setUp() methods may set up further fixtures; find a
  420. // way to execute this after setUp() is done, or to eliminate it entirely.
  421. $this->resetAll();
  422. $this->kernel->prepareLegacyRequest(\Drupal::request());
  423. // Explicitly call register() again on the container registered in \Drupal.
  424. // @todo This should already be called through
  425. // DrupalKernel::prepareLegacyRequest() -> DrupalKernel::boot() but that
  426. // appears to be calling a different container.
  427. $this->container->get('stream_wrapper_manager')->register();
  428. }
  429. /**
  430. * Returns the parameters that will be used when Simpletest installs Drupal.
  431. *
  432. * @see install_drupal()
  433. * @see install_state_defaults()
  434. *
  435. * @return array
  436. * Array of parameters for use in install_drupal().
  437. */
  438. protected function installParameters() {
  439. $connection_info = Database::getConnectionInfo();
  440. $driver = $connection_info['default']['driver'];
  441. $connection_info['default']['prefix'] = $connection_info['default']['prefix']['default'];
  442. unset($connection_info['default']['driver']);
  443. unset($connection_info['default']['namespace']);
  444. unset($connection_info['default']['pdo']);
  445. unset($connection_info['default']['init_commands']);
  446. // Remove database connection info that is not used by SQLite.
  447. if ($driver === 'sqlite') {
  448. unset($connection_info['default']['username']);
  449. unset($connection_info['default']['password']);
  450. unset($connection_info['default']['host']);
  451. unset($connection_info['default']['port']);
  452. }
  453. $parameters = [
  454. 'interactive' => FALSE,
  455. 'parameters' => [
  456. 'profile' => $this->profile,
  457. 'langcode' => 'en',
  458. ],
  459. 'forms' => [
  460. 'install_settings_form' => [
  461. 'driver' => $driver,
  462. $driver => $connection_info['default'],
  463. ],
  464. 'install_configure_form' => [
  465. 'site_name' => 'Drupal',
  466. 'site_mail' => 'simpletest@example.com',
  467. 'account' => [
  468. 'name' => $this->rootUser->name,
  469. 'mail' => $this->rootUser->getEmail(),
  470. 'pass' => [
  471. 'pass1' => isset($this->rootUser->pass_raw) ? $this->rootUser->pass_raw : $this->rootUser->passRaw,
  472. 'pass2' => isset($this->rootUser->pass_raw) ? $this->rootUser->pass_raw : $this->rootUser->passRaw,
  473. ],
  474. ],
  475. // form_type_checkboxes_value() requires NULL instead of FALSE values
  476. // for programmatic form submissions to disable a checkbox.
  477. 'enable_update_status_module' => NULL,
  478. 'enable_update_status_emails' => NULL,
  479. ],
  480. ],
  481. ];
  482. // If we only have one db driver available, we cannot set the driver.
  483. include_once DRUPAL_ROOT . '/core/includes/install.inc';
  484. if (count($this->getDatabaseTypes()) == 1) {
  485. unset($parameters['forms']['install_settings_form']['driver']);
  486. }
  487. return $parameters;
  488. }
  489. /**
  490. * Sets up the base URL based upon the environment variable.
  491. *
  492. * @throws \Exception
  493. * Thrown when no SIMPLETEST_BASE_URL environment variable is provided.
  494. */
  495. protected function setupBaseUrl() {
  496. global $base_url;
  497. // Get and set the domain of the environment we are running our test
  498. // coverage against.
  499. $base_url = getenv('SIMPLETEST_BASE_URL');
  500. if (!$base_url) {
  501. throw new \Exception(
  502. 'You must provide a SIMPLETEST_BASE_URL environment variable to run some PHPUnit based functional tests.'
  503. );
  504. }
  505. // Setup $_SERVER variable.
  506. $parsed_url = parse_url($base_url);
  507. $host = $parsed_url['host'] . (isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '');
  508. $path = isset($parsed_url['path']) ? rtrim(rtrim($parsed_url['path']), '/') : '';
  509. $port = isset($parsed_url['port']) ? $parsed_url['port'] : 80;
  510. $this->baseUrl = $base_url;
  511. // If the passed URL schema is 'https' then setup the $_SERVER variables
  512. // properly so that testing will run under HTTPS.
  513. if ($parsed_url['scheme'] === 'https') {
  514. $_SERVER['HTTPS'] = 'on';
  515. }
  516. $_SERVER['HTTP_HOST'] = $host;
  517. $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
  518. $_SERVER['SERVER_ADDR'] = '127.0.0.1';
  519. $_SERVER['SERVER_PORT'] = $port;
  520. $_SERVER['SERVER_SOFTWARE'] = NULL;
  521. $_SERVER['SERVER_NAME'] = 'localhost';
  522. $_SERVER['REQUEST_URI'] = $path . '/';
  523. $_SERVER['REQUEST_METHOD'] = 'GET';
  524. $_SERVER['SCRIPT_NAME'] = $path . '/index.php';
  525. $_SERVER['SCRIPT_FILENAME'] = $path . '/index.php';
  526. $_SERVER['PHP_SELF'] = $path . '/index.php';
  527. $_SERVER['HTTP_USER_AGENT'] = 'Drupal command line';
  528. }
  529. /**
  530. * Prepares the current environment for running the test.
  531. *
  532. * Also sets up new resources for the testing environment, such as the public
  533. * filesystem and configuration directories.
  534. *
  535. * This method is private as it must only be called once by
  536. * BrowserTestBase::setUp() (multiple invocations for the same test would have
  537. * unpredictable consequences) and it must not be callable or overridable by
  538. * test classes.
  539. */
  540. protected function prepareEnvironment() {
  541. // Bootstrap Drupal so we can use Drupal's built in functions.
  542. $this->classLoader = require __DIR__ . '/../../../../../autoload.php';
  543. $request = Request::createFromGlobals();
  544. $kernel = TestRunnerKernel::createFromRequest($request, $this->classLoader);
  545. // TestRunnerKernel expects the working directory to be DRUPAL_ROOT.
  546. chdir(DRUPAL_ROOT);
  547. $kernel->prepareLegacyRequest($request);
  548. $this->prepareDatabasePrefix();
  549. $this->originalSite = $kernel->findSitePath($request);
  550. // Create test directory ahead of installation so fatal errors and debug
  551. // information can be logged during installation process.
  552. file_prepare_directory($this->siteDirectory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
  553. // Prepare filesystem directory paths.
  554. $this->publicFilesDirectory = $this->siteDirectory . '/files';
  555. $this->privateFilesDirectory = $this->siteDirectory . '/private';
  556. $this->tempFilesDirectory = $this->siteDirectory . '/temp';
  557. $this->translationFilesDirectory = $this->siteDirectory . '/translations';
  558. // Ensure the configImporter is refreshed for each test.
  559. $this->configImporter = NULL;
  560. // Unregister all custom stream wrappers of the parent site.
  561. $wrappers = \Drupal::service('stream_wrapper_manager')->getWrappers(StreamWrapperInterface::ALL);
  562. foreach ($wrappers as $scheme => $info) {
  563. stream_wrapper_unregister($scheme);
  564. }
  565. // Reset statics.
  566. drupal_static_reset();
  567. $this->container = NULL;
  568. // Unset globals.
  569. unset($GLOBALS['config_directories']);
  570. unset($GLOBALS['config']);
  571. unset($GLOBALS['conf']);
  572. // Log fatal errors.
  573. ini_set('log_errors', 1);
  574. ini_set('error_log', DRUPAL_ROOT . '/' . $this->siteDirectory . '/error.log');
  575. // Change the database prefix.
  576. $this->changeDatabasePrefix();
  577. // After preparing the environment and changing the database prefix, we are
  578. // in a valid test environment.
  579. drupal_valid_test_ua($this->databasePrefix);
  580. // Reset settings.
  581. new Settings([
  582. // For performance, simply use the database prefix as hash salt.
  583. 'hash_salt' => $this->databasePrefix,
  584. ]);
  585. drupal_set_time_limit($this->timeLimit);
  586. // Save and clean the shutdown callbacks array because it is static cached
  587. // and will be changed by the test run. Otherwise it will contain callbacks
  588. // from both environments and the testing environment will try to call the
  589. // handlers defined by the original one.
  590. $callbacks = &drupal_register_shutdown_function();
  591. $this->originalShutdownCallbacks = $callbacks;
  592. $callbacks = [];
  593. }
  594. /**
  595. * Returns all supported database driver installer objects.
  596. *
  597. * This wraps drupal_get_database_types() for use without a current container.
  598. *
  599. * @return \Drupal\Core\Database\Install\Tasks[]
  600. * An array of available database driver installer objects.
  601. */
  602. protected function getDatabaseTypes() {
  603. if ($this->originalContainer) {
  604. \Drupal::setContainer($this->originalContainer);
  605. }
  606. $database_types = drupal_get_database_types();
  607. if ($this->originalContainer) {
  608. \Drupal::unsetContainer();
  609. }
  610. return $database_types;
  611. }
  612. }