FunctionalTestSetupTrait.php 28 KB

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