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