setName('install') ->setDescription('Creates a test Drupal site') ->setHelp('The details to connect to the test site created will be displayed upon success. It will contain the database prefix and the user agent.') ->addOption('setup-file', NULL, InputOption::VALUE_OPTIONAL, 'The path to a PHP file containing a class to setup configuration used by the test, for example, core/tests/Drupal/TestSite/TestSiteInstallTestScript.php.') ->addOption('db-url', NULL, InputOption::VALUE_OPTIONAL, 'URL for database. Defaults to the environment variable SIMPLETEST_DB.', getenv('SIMPLETEST_DB')) ->addOption('base-url', NULL, InputOption::VALUE_OPTIONAL, 'Base URL for site under test. Defaults to the environment variable SIMPLETEST_BASE_URL.', getenv('SIMPLETEST_BASE_URL')) ->addOption('install-profile', NULL, InputOption::VALUE_OPTIONAL, 'Install profile to install the site in. Defaults to testing.', 'testing') ->addOption('langcode', NULL, InputOption::VALUE_OPTIONAL, 'The language to install the site in. Defaults to en.', 'en') ->addOption('json', NULL, InputOption::VALUE_NONE, 'Output test site connection details in JSON.') ->addUsage('--setup-file core/tests/Drupal/TestSite/TestSiteInstallTestScript.php --json') ->addUsage('--install-profile demo_umami --langcode fr') ->addUsage('--base-url "http://example.com" --db-url "mysql://username:password@localhost/databasename#table_prefix"'); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { // Determines and validates the setup class prior to installing a database // to avoid creating unnecessary sites. $root = dirname(dirname(dirname(dirname(dirname(__DIR__))))); chdir($root); $class_name = $this->getSetupClass($input->getOption('setup-file')); // Ensure we can install a site in the sites/simpletest directory. $this->ensureDirectory($root); $db_url = $input->getOption('db-url'); $base_url = $input->getOption('base-url'); putenv("SIMPLETEST_DB=$db_url"); putenv("SIMPLETEST_BASE_URL=$base_url"); // Manage site fixture. $this->setup($input->getOption('install-profile'), $class_name, $input->getOption('langcode')); $user_agent = drupal_generate_test_ua($this->databasePrefix); if ($input->getOption('json')) { $output->writeln(json_encode([ 'db_prefix' => $this->databasePrefix, 'user_agent' => $user_agent, 'site_path' => $this->siteDirectory, ])); } else { $output->writeln('Successfully installed a test site'); $io = new SymfonyStyle($input, $output); $io->table([], [ ['Database prefix', $this->databasePrefix], ['User agent', $user_agent], ['Site path', $this->siteDirectory], ]); } } /** * Gets the setup class. * * @param string|null $file * The file to get the setup class from. * * @return string|null * The setup class contained in the provided $file. * * @throws \InvalidArgumentException * Thrown if the file does not exist, does not contain a class or the class * does not implement \Drupal\TestSite\TestSetupInterface. */ protected function getSetupClass($file) { if ($file === NULL) { return; } if (!file_exists($file)) { throw new \InvalidArgumentException("The file $file does not exist."); } $classes = get_declared_classes(); include_once $file; $new_classes = array_values(array_diff(get_declared_classes(), $classes)); if (empty($new_classes)) { throw new \InvalidArgumentException("The file $file does not contain a class."); } $class = array_pop($new_classes); if (!is_subclass_of($class, TestSetupInterface::class)) { throw new \InvalidArgumentException("The class $class contained in $file needs to implement \Drupal\TestSite\TestSetupInterface"); } return $class; } /** * Ensures that the sites/simpletest directory exists and is writable. * * @param string $root * The Drupal root. */ protected function ensureDirectory($root) { if (!is_writable($root . '/sites/simpletest')) { if (!@mkdir($root . '/sites/simpletest')) { throw new \RuntimeException($root . '/sites/simpletest must exist and be writable to install a test site'); } } } /** * Creates a test drupal installation. * * @param string $profile * (optional) The installation profile to use. * @param string $setup_class * (optional) Setup class. A PHP class to setup configuration used by the * test. * @param string $langcode * (optional) The language to install the site in. */ public function setup($profile = 'testing', $setup_class = NULL, $langcode = 'en') { $this->profile = $profile; $this->langcode = $langcode; $this->setupBaseUrl(); $this->prepareEnvironment(); $this->installDrupal(); if ($setup_class) { $this->executeSetupClass($setup_class); } } /** * Installs Drupal into the test site. */ protected function installDrupal() { $this->initUserSession(); $this->prepareSettings(); $this->doInstall(); $this->initSettings(); $container = $this->initKernel(\Drupal::request()); $this->initConfig($container); $this->installModulesFromClassProperty($container); $this->rebuildAll(); } /** * Uses the setup file to configure Drupal. * * @param string $class * The fully qualified class name, which should set up Drupal for tests. For * example this class could create content types and fields or install * modules. The class needs to implement TestSetupInterface. * * @see \Drupal\TestSite\TestSetupInterface */ protected function executeSetupClass($class) { /** @var \Drupal\TestSite\TestSetupInterface $instance */ $instance = new $class(); $instance->setup(); } /** * {@inheritdoc} */ protected function installParameters() { $parameters = $this->installParametersTrait(); $parameters['parameters']['langcode'] = $this->langcode; return $parameters; } /** * {@inheritdoc} */ protected function changeDatabasePrefix() { // Ensure that we use the database from SIMPLETEST_DB environment variable. Database::removeConnection('default'); $this->changeDatabasePrefixTrait(); } /** * {@inheritdoc} */ protected function prepareDatabasePrefix() { // Override this method so that we can force a lock to be created. $test_db = new TestDatabase(NULL, TRUE); $this->siteDirectory = $test_db->getTestSitePath(); $this->databasePrefix = $test_db->getDatabasePrefix(); } }