InstallerTestBase.php 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. <?php
  2. namespace Drupal\simpletest;
  3. use Drupal\Core\DrupalKernel;
  4. use Drupal\Core\Language\Language;
  5. use Drupal\Core\Session\UserSession;
  6. use Drupal\Core\Site\Settings;
  7. use Symfony\Component\DependencyInjection\ContainerBuilder;
  8. use Symfony\Component\DependencyInjection\Reference;
  9. use Symfony\Component\HttpFoundation\Request;
  10. use Symfony\Component\HttpFoundation\RequestStack;
  11. /**
  12. * Base class for testing the interactive installer.
  13. */
  14. abstract class InstallerTestBase extends WebTestBase {
  15. /**
  16. * Custom settings.php values to write for a test run.
  17. *
  18. * @var array
  19. * An array of settings to write out, in the format expected by
  20. * drupal_rewrite_settings().
  21. */
  22. protected $settings = [];
  23. /**
  24. * The language code in which to install Drupal.
  25. *
  26. * @var string
  27. */
  28. protected $langcode = 'en';
  29. /**
  30. * The installation profile to install.
  31. *
  32. * @var string
  33. */
  34. protected $profile = 'testing';
  35. /**
  36. * Additional parameters to use for installer screens.
  37. *
  38. * @see WebTestBase::installParameters()
  39. *
  40. * @var array
  41. */
  42. protected $parameters = [];
  43. /**
  44. * A string translation map used for translated installer screens.
  45. *
  46. * Keys are English strings, values are translated strings.
  47. *
  48. * @var array
  49. */
  50. protected $translations = [
  51. 'Save and continue' => 'Save and continue',
  52. ];
  53. /**
  54. * Whether the installer has completed.
  55. *
  56. * @var bool
  57. */
  58. protected $isInstalled = FALSE;
  59. /**
  60. * {@inheritdoc}
  61. */
  62. protected function setUp() {
  63. $this->isInstalled = FALSE;
  64. // Define information about the user 1 account.
  65. $this->rootUser = new UserSession([
  66. 'uid' => 1,
  67. 'name' => 'admin',
  68. 'mail' => 'admin@example.com',
  69. 'pass_raw' => $this->randomMachineName(),
  70. ]);
  71. // If any $settings are defined for this test, copy and prepare an actual
  72. // settings.php, so as to resemble a regular installation.
  73. if (!empty($this->settings)) {
  74. // Not using File API; a potential error must trigger a PHP warning.
  75. copy(DRUPAL_ROOT . '/sites/default/default.settings.php', DRUPAL_ROOT . '/' . $this->siteDirectory . '/settings.php');
  76. $this->writeSettings($this->settings);
  77. }
  78. // Note that WebTestBase::installParameters() returns form input values
  79. // suitable for a programmed \Drupal::formBuilder()->submitForm().
  80. // @see WebTestBase::translatePostValues()
  81. $this->parameters = $this->installParameters();
  82. // Set up a minimal container (required by WebTestBase). Set cookie and
  83. // server information so that XDebug works.
  84. // @see install_begin_request()
  85. $request = Request::create($GLOBALS['base_url'] . '/core/install.php', 'GET', [], $_COOKIE, [], $_SERVER);
  86. $this->container = new ContainerBuilder();
  87. $request_stack = new RequestStack();
  88. $request_stack->push($request);
  89. $this->container
  90. ->set('request_stack', $request_stack);
  91. $this->container
  92. ->setParameter('language.default_values', Language::$defaultValues);
  93. $this->container
  94. ->register('language.default', 'Drupal\Core\Language\LanguageDefault')
  95. ->addArgument('%language.default_values%');
  96. $this->container
  97. ->register('string_translation', 'Drupal\Core\StringTranslation\TranslationManager')
  98. ->addArgument(new Reference('language.default'));
  99. $this->container
  100. ->set('app.root', DRUPAL_ROOT);
  101. \Drupal::setContainer($this->container);
  102. $this->visitInstaller();
  103. // Select language.
  104. $this->setUpLanguage();
  105. // Select profile.
  106. $this->setUpProfile();
  107. // Address the requirements problem screen, if any.
  108. $this->setUpRequirementsProblem();
  109. // Configure settings.
  110. $this->setUpSettings();
  111. // @todo Allow test classes based on this class to act on further installer
  112. // screens.
  113. // Configure site.
  114. $this->setUpSite();
  115. if ($this->isInstalled) {
  116. // Import new settings.php written by the installer.
  117. $request = Request::createFromGlobals();
  118. $class_loader = require $this->container->get('app.root') . '/autoload.php';
  119. Settings::initialize($this->container->get('app.root'), DrupalKernel::findSitePath($request), $class_loader);
  120. foreach ($GLOBALS['config_directories'] as $type => $path) {
  121. $this->configDirectories[$type] = $path;
  122. }
  123. // After writing settings.php, the installer removes write permissions
  124. // from the site directory. To allow drupal_generate_test_ua() to write
  125. // a file containing the private key for drupal_valid_test_ua(), the site
  126. // directory has to be writable.
  127. // WebTestBase::tearDown() will delete the entire test site directory.
  128. // Not using File API; a potential error must trigger a PHP warning.
  129. chmod($this->container->get('app.root') . '/' . $this->siteDirectory, 0777);
  130. $this->kernel = DrupalKernel::createFromRequest($request, $class_loader, 'prod', FALSE);
  131. $this->kernel->prepareLegacyRequest($request);
  132. $this->container = $this->kernel->getContainer();
  133. // Manually configure the test mail collector implementation to prevent
  134. // tests from sending out emails and collect them in state instead.
  135. $this->container->get('config.factory')
  136. ->getEditable('system.mail')
  137. ->set('interface.default', 'test_mail_collector')
  138. ->save();
  139. }
  140. }
  141. /**
  142. * Visits the interactive installer.
  143. */
  144. protected function visitInstaller() {
  145. $this->drupalGet($GLOBALS['base_url'] . '/core/install.php');
  146. }
  147. /**
  148. * Installer step: Select language.
  149. */
  150. protected function setUpLanguage() {
  151. $edit = [
  152. 'langcode' => $this->langcode,
  153. ];
  154. $this->drupalPostForm(NULL, $edit, $this->translations['Save and continue']);
  155. }
  156. /**
  157. * Installer step: Select installation profile.
  158. */
  159. protected function setUpProfile() {
  160. $edit = [
  161. 'profile' => $this->profile,
  162. ];
  163. $this->drupalPostForm(NULL, $edit, $this->translations['Save and continue']);
  164. }
  165. /**
  166. * Installer step: Configure settings.
  167. */
  168. protected function setUpSettings() {
  169. $edit = $this->translatePostValues($this->parameters['forms']['install_settings_form']);
  170. $this->drupalPostForm(NULL, $edit, $this->translations['Save and continue']);
  171. }
  172. /**
  173. * Installer step: Requirements problem.
  174. *
  175. * Override this method to test specific requirements warnings or errors
  176. * during the installer.
  177. *
  178. * @see system_requirements()
  179. */
  180. protected function setUpRequirementsProblem() {
  181. // By default, skip the "recommended PHP version" warning on older test
  182. // environments. This allows the installer to be tested consistently on
  183. // both recommended PHP versions and older (but still supported) versions.
  184. if (version_compare(phpversion(), '7.0') < 0) {
  185. $this->continueOnExpectedWarnings(['PHP']);
  186. }
  187. }
  188. /**
  189. * Final installer step: Configure site.
  190. */
  191. protected function setUpSite() {
  192. $edit = $this->translatePostValues($this->parameters['forms']['install_configure_form']);
  193. $this->drupalPostForm(NULL, $edit, $this->translations['Save and continue']);
  194. // If we've got to this point the site is installed using the regular
  195. // installation workflow.
  196. $this->isInstalled = TRUE;
  197. }
  198. /**
  199. * {@inheritdoc}
  200. *
  201. * WebTestBase::refreshVariables() tries to operate on persistent storage,
  202. * which is only available after the installer completed.
  203. */
  204. protected function refreshVariables() {
  205. if ($this->isInstalled) {
  206. parent::refreshVariables();
  207. }
  208. }
  209. /**
  210. * Continues installation when an expected warning is found.
  211. *
  212. * @param string[] $expected_warnings
  213. * A list of warning summaries to expect on the requirements screen (e.g.
  214. * 'PHP', 'PHP OPcode caching', etc.). If only the expected warnings
  215. * are found, the test will click the "continue anyway" link to go to the
  216. * next screen of the installer. If an expected warning is not found, or if
  217. * a warning not in the list is present, a fail is raised.
  218. */
  219. protected function continueOnExpectedWarnings($expected_warnings = []) {
  220. // Don't try to continue if there are errors.
  221. if (strpos($this->getTextContent(), 'Errors found') !== FALSE) {
  222. return;
  223. }
  224. // Allow only details elements that are directly after the warning header
  225. // or each other. There is no guaranteed wrapper we can rely on across
  226. // distributions. When there are multiple warnings, the selectors will be:
  227. // - h3#warning+details summary
  228. // - h3#warning+details+details summary
  229. // - etc.
  230. // We add one more selector than expected warnings to confirm that there
  231. // isn't any other warning before clicking the link.
  232. // @todo Make this more reliable in
  233. // https://www.drupal.org/project/drupal/issues/2927345.
  234. $selectors = [];
  235. for ($i = 0; $i <= count($expected_warnings); $i++) {
  236. $selectors[] = 'h3#warning' . implode('', array_fill(0, $i + 1, '+details')) . ' summary';
  237. }
  238. $warning_elements = $this->cssSelect(implode(', ', $selectors));
  239. // Confirm that there are only the expected warnings.
  240. $warnings = [];
  241. foreach ($warning_elements as $warning) {
  242. $warnings[] = trim((string) $warning);
  243. }
  244. $this->assertEqual($expected_warnings, $warnings);
  245. $this->clickLink('continue anyway');
  246. }
  247. }