annotate core/tests/Drupal/TestSite/Commands/TestSiteInstallCommand.php @ 4:a9cd425dd02b

Update, including to Drupal core 8.6.10
author Chris Cannam
date Thu, 28 Feb 2019 13:11:55 +0000
parents
children
rev   line source
Chris@4 1 <?php
Chris@4 2
Chris@4 3 namespace Drupal\TestSite\Commands;
Chris@4 4
Chris@4 5 use Drupal\Core\Database\Database;
Chris@4 6 use Drupal\Core\Test\FunctionalTestSetupTrait;
Chris@4 7 use Drupal\Core\Test\TestDatabase;
Chris@4 8 use Drupal\Core\Test\TestSetupTrait;
Chris@4 9 use Drupal\TestSite\TestSetupInterface;
Chris@4 10 use Drupal\Tests\RandomGeneratorTrait;
Chris@4 11 use Symfony\Component\Console\Command\Command;
Chris@4 12 use Symfony\Component\Console\Input\InputInterface;
Chris@4 13 use Symfony\Component\Console\Input\InputOption;
Chris@4 14 use Symfony\Component\Console\Output\OutputInterface;
Chris@4 15 use Symfony\Component\Console\Style\SymfonyStyle;
Chris@4 16
Chris@4 17 /**
Chris@4 18 * Command to create a test Drupal site.
Chris@4 19 *
Chris@4 20 * @internal
Chris@4 21 */
Chris@4 22 class TestSiteInstallCommand extends Command {
Chris@4 23
Chris@4 24 use FunctionalTestSetupTrait {
Chris@4 25 installParameters as protected installParametersTrait;
Chris@4 26 }
Chris@4 27 use RandomGeneratorTrait;
Chris@4 28 use TestSetupTrait {
Chris@4 29 changeDatabasePrefix as protected changeDatabasePrefixTrait;
Chris@4 30 }
Chris@4 31
Chris@4 32 /**
Chris@4 33 * The install profile to use.
Chris@4 34 *
Chris@4 35 * @var string
Chris@4 36 */
Chris@4 37 protected $profile = 'testing';
Chris@4 38
Chris@4 39 /**
Chris@4 40 * Time limit in seconds for the test.
Chris@4 41 *
Chris@4 42 * Used by \Drupal\Core\Test\FunctionalTestSetupTrait::prepareEnvironment().
Chris@4 43 *
Chris@4 44 * @var int
Chris@4 45 */
Chris@4 46 protected $timeLimit = 500;
Chris@4 47
Chris@4 48 /**
Chris@4 49 * The database prefix of this test run.
Chris@4 50 *
Chris@4 51 * @var string
Chris@4 52 */
Chris@4 53 protected $databasePrefix;
Chris@4 54
Chris@4 55 /**
Chris@4 56 * The language to install the site in.
Chris@4 57 *
Chris@4 58 * @var string
Chris@4 59 */
Chris@4 60 protected $langcode = 'en';
Chris@4 61
Chris@4 62 /**
Chris@4 63 * {@inheritdoc}
Chris@4 64 */
Chris@4 65 protected function configure() {
Chris@4 66 $this->setName('install')
Chris@4 67 ->setDescription('Creates a test Drupal site')
Chris@4 68 ->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.')
Chris@4 69 ->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.')
Chris@4 70 ->addOption('db-url', NULL, InputOption::VALUE_OPTIONAL, 'URL for database. Defaults to the environment variable SIMPLETEST_DB.', getenv('SIMPLETEST_DB'))
Chris@4 71 ->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'))
Chris@4 72 ->addOption('install-profile', NULL, InputOption::VALUE_OPTIONAL, 'Install profile to install the site in. Defaults to testing.', 'testing')
Chris@4 73 ->addOption('langcode', NULL, InputOption::VALUE_OPTIONAL, 'The language to install the site in. Defaults to en.', 'en')
Chris@4 74 ->addOption('json', NULL, InputOption::VALUE_NONE, 'Output test site connection details in JSON.')
Chris@4 75 ->addUsage('--setup-file core/tests/Drupal/TestSite/TestSiteInstallTestScript.php --json')
Chris@4 76 ->addUsage('--install-profile demo_umami --langcode fr')
Chris@4 77 ->addUsage('--base-url "http://example.com" --db-url "mysql://username:password@localhost/databasename#table_prefix"');
Chris@4 78 }
Chris@4 79
Chris@4 80 /**
Chris@4 81 * {@inheritdoc}
Chris@4 82 */
Chris@4 83 protected function execute(InputInterface $input, OutputInterface $output) {
Chris@4 84 // Determines and validates the setup class prior to installing a database
Chris@4 85 // to avoid creating unnecessary sites.
Chris@4 86 $root = dirname(dirname(dirname(dirname(dirname(__DIR__)))));
Chris@4 87 chdir($root);
Chris@4 88 $class_name = $this->getSetupClass($input->getOption('setup-file'));
Chris@4 89 // Ensure we can install a site in the sites/simpletest directory.
Chris@4 90 $this->ensureDirectory($root);
Chris@4 91
Chris@4 92 $db_url = $input->getOption('db-url');
Chris@4 93 $base_url = $input->getOption('base-url');
Chris@4 94 putenv("SIMPLETEST_DB=$db_url");
Chris@4 95 putenv("SIMPLETEST_BASE_URL=$base_url");
Chris@4 96
Chris@4 97 // Manage site fixture.
Chris@4 98 $this->setup($input->getOption('install-profile'), $class_name, $input->getOption('langcode'));
Chris@4 99
Chris@4 100 $user_agent = drupal_generate_test_ua($this->databasePrefix);
Chris@4 101 if ($input->getOption('json')) {
Chris@4 102 $output->writeln(json_encode([
Chris@4 103 'db_prefix' => $this->databasePrefix,
Chris@4 104 'user_agent' => $user_agent,
Chris@4 105 'site_path' => $this->siteDirectory,
Chris@4 106 ]));
Chris@4 107 }
Chris@4 108 else {
Chris@4 109 $output->writeln('<info>Successfully installed a test site</info>');
Chris@4 110 $io = new SymfonyStyle($input, $output);
Chris@4 111 $io->table([], [
Chris@4 112 ['Database prefix', $this->databasePrefix],
Chris@4 113 ['User agent', $user_agent],
Chris@4 114 ['Site path', $this->siteDirectory],
Chris@4 115 ]);
Chris@4 116 }
Chris@4 117 }
Chris@4 118
Chris@4 119 /**
Chris@4 120 * Gets the setup class.
Chris@4 121 *
Chris@4 122 * @param string|null $file
Chris@4 123 * The file to get the setup class from.
Chris@4 124 *
Chris@4 125 * @return string|null
Chris@4 126 * The setup class contained in the provided $file.
Chris@4 127 *
Chris@4 128 * @throws \InvalidArgumentException
Chris@4 129 * Thrown if the file does not exist, does not contain a class or the class
Chris@4 130 * does not implement \Drupal\TestSite\TestSetupInterface.
Chris@4 131 */
Chris@4 132 protected function getSetupClass($file) {
Chris@4 133 if ($file === NULL) {
Chris@4 134 return;
Chris@4 135 }
Chris@4 136 if (!file_exists($file)) {
Chris@4 137 throw new \InvalidArgumentException("The file $file does not exist.");
Chris@4 138 }
Chris@4 139
Chris@4 140 $classes = get_declared_classes();
Chris@4 141 include_once $file;
Chris@4 142 $new_classes = array_values(array_diff(get_declared_classes(), $classes));
Chris@4 143 if (empty($new_classes)) {
Chris@4 144 throw new \InvalidArgumentException("The file $file does not contain a class.");
Chris@4 145 }
Chris@4 146 $class = array_pop($new_classes);
Chris@4 147
Chris@4 148 if (!is_subclass_of($class, TestSetupInterface::class)) {
Chris@4 149 throw new \InvalidArgumentException("The class $class contained in $file needs to implement \Drupal\TestSite\TestSetupInterface");
Chris@4 150 }
Chris@4 151 return $class;
Chris@4 152 }
Chris@4 153
Chris@4 154 /**
Chris@4 155 * Ensures that the sites/simpletest directory exists and is writable.
Chris@4 156 *
Chris@4 157 * @param string $root
Chris@4 158 * The Drupal root.
Chris@4 159 */
Chris@4 160 protected function ensureDirectory($root) {
Chris@4 161 if (!is_writable($root . '/sites/simpletest')) {
Chris@4 162 if (!@mkdir($root . '/sites/simpletest')) {
Chris@4 163 throw new \RuntimeException($root . '/sites/simpletest must exist and be writable to install a test site');
Chris@4 164 }
Chris@4 165 }
Chris@4 166 }
Chris@4 167
Chris@4 168 /**
Chris@4 169 * Creates a test drupal installation.
Chris@4 170 *
Chris@4 171 * @param string $profile
Chris@4 172 * (optional) The installation profile to use.
Chris@4 173 * @param string $setup_class
Chris@4 174 * (optional) Setup class. A PHP class to setup configuration used by the
Chris@4 175 * test.
Chris@4 176 * @param string $langcode
Chris@4 177 * (optional) The language to install the site in.
Chris@4 178 */
Chris@4 179 public function setup($profile = 'testing', $setup_class = NULL, $langcode = 'en') {
Chris@4 180 $this->profile = $profile;
Chris@4 181 $this->langcode = $langcode;
Chris@4 182 $this->setupBaseUrl();
Chris@4 183 $this->prepareEnvironment();
Chris@4 184 $this->installDrupal();
Chris@4 185
Chris@4 186 if ($setup_class) {
Chris@4 187 $this->executeSetupClass($setup_class);
Chris@4 188 }
Chris@4 189 }
Chris@4 190
Chris@4 191 /**
Chris@4 192 * Installs Drupal into the test site.
Chris@4 193 */
Chris@4 194 protected function installDrupal() {
Chris@4 195 $this->initUserSession();
Chris@4 196 $this->prepareSettings();
Chris@4 197 $this->doInstall();
Chris@4 198 $this->initSettings();
Chris@4 199 $container = $this->initKernel(\Drupal::request());
Chris@4 200 $this->initConfig($container);
Chris@4 201 $this->installModulesFromClassProperty($container);
Chris@4 202 $this->rebuildAll();
Chris@4 203 }
Chris@4 204
Chris@4 205 /**
Chris@4 206 * Uses the setup file to configure Drupal.
Chris@4 207 *
Chris@4 208 * @param string $class
Chris@4 209 * The fully qualified class name, which should set up Drupal for tests. For
Chris@4 210 * example this class could create content types and fields or install
Chris@4 211 * modules. The class needs to implement TestSetupInterface.
Chris@4 212 *
Chris@4 213 * @see \Drupal\TestSite\TestSetupInterface
Chris@4 214 */
Chris@4 215 protected function executeSetupClass($class) {
Chris@4 216 /** @var \Drupal\TestSite\TestSetupInterface $instance */
Chris@4 217 $instance = new $class();
Chris@4 218 $instance->setup();
Chris@4 219 }
Chris@4 220
Chris@4 221 /**
Chris@4 222 * {@inheritdoc}
Chris@4 223 */
Chris@4 224 protected function installParameters() {
Chris@4 225 $parameters = $this->installParametersTrait();
Chris@4 226 $parameters['parameters']['langcode'] = $this->langcode;
Chris@4 227 return $parameters;
Chris@4 228 }
Chris@4 229
Chris@4 230 /**
Chris@4 231 * {@inheritdoc}
Chris@4 232 */
Chris@4 233 protected function changeDatabasePrefix() {
Chris@4 234 // Ensure that we use the database from SIMPLETEST_DB environment variable.
Chris@4 235 Database::removeConnection('default');
Chris@4 236 $this->changeDatabasePrefixTrait();
Chris@4 237 }
Chris@4 238
Chris@4 239 /**
Chris@4 240 * {@inheritdoc}
Chris@4 241 */
Chris@4 242 protected function prepareDatabasePrefix() {
Chris@4 243 // Override this method so that we can force a lock to be created.
Chris@4 244 $test_db = new TestDatabase(NULL, TRUE);
Chris@4 245 $this->siteDirectory = $test_db->getTestSitePath();
Chris@4 246 $this->databasePrefix = $test_db->getDatabasePrefix();
Chris@4 247 }
Chris@4 248
Chris@4 249 }