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\TestDatabase;
|
Chris@17
|
7 use Drupal\Tests\BrowserTestBase;
|
Chris@17
|
8 use Symfony\Component\Console\Command\Command;
|
Chris@17
|
9 use Symfony\Component\Console\Input\InputArgument;
|
Chris@17
|
10 use Symfony\Component\Console\Input\InputInterface;
|
Chris@17
|
11 use Symfony\Component\Console\Input\InputOption;
|
Chris@17
|
12 use Symfony\Component\Console\Output\OutputInterface;
|
Chris@17
|
13 use Symfony\Component\Console\Style\SymfonyStyle;
|
Chris@17
|
14
|
Chris@17
|
15 /**
|
Chris@17
|
16 * Command to tear down a test Drupal site.
|
Chris@17
|
17 *
|
Chris@17
|
18 * @internal
|
Chris@17
|
19 */
|
Chris@17
|
20 class TestSiteTearDownCommand extends Command {
|
Chris@17
|
21
|
Chris@17
|
22 /**
|
Chris@17
|
23 * {@inheritdoc}
|
Chris@17
|
24 */
|
Chris@17
|
25 protected function configure() {
|
Chris@17
|
26 $this->setName('tear-down')
|
Chris@17
|
27 ->setDescription('Removes a test site added by the install command')
|
Chris@17
|
28 ->setHelp('All the database tables and files will be removed.')
|
Chris@17
|
29 ->addArgument('db-prefix', InputArgument::REQUIRED, 'The database prefix for the test site.')
|
Chris@17
|
30 ->addOption('db-url', NULL, InputOption::VALUE_OPTIONAL, 'URL for database. Defaults to the environment variable SIMPLETEST_DB.', getenv('SIMPLETEST_DB'))
|
Chris@17
|
31 ->addOption('keep-lock', NULL, InputOption::VALUE_NONE, 'Keeps the database prefix lock. Useful for ensuring test isolation when running concurrent tests.')
|
Chris@17
|
32 ->addUsage('test12345678')
|
Chris@17
|
33 ->addUsage('test12345678 --db-url "mysql://username:password@localhost/databasename#table_prefix"')
|
Chris@17
|
34 ->addUsage('test12345678 --keep-lock');
|
Chris@17
|
35 }
|
Chris@17
|
36
|
Chris@17
|
37 /**
|
Chris@17
|
38 * {@inheritdoc}
|
Chris@17
|
39 */
|
Chris@17
|
40 protected function execute(InputInterface $input, OutputInterface $output) {
|
Chris@17
|
41 $db_prefix = $input->getArgument('db-prefix');
|
Chris@17
|
42 // Validate the db_prefix argument.
|
Chris@17
|
43 try {
|
Chris@17
|
44 $test_database = new TestDatabase($db_prefix);
|
Chris@17
|
45 }
|
Chris@17
|
46 catch (\InvalidArgumentException $e) {
|
Chris@17
|
47 $io = new SymfonyStyle($input, $output);
|
Chris@17
|
48 $io->getErrorStyle()->error("Invalid database prefix: $db_prefix\n\nValid database prefixes match the regular expression '/test(\d+)$/'. For example, 'test12345678'.");
|
Chris@17
|
49 // Display the synopsis of the command like Composer does.
|
Chris@17
|
50 $output->writeln(sprintf('<info>%s</info>', sprintf($this->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET);
|
Chris@17
|
51 return 1;
|
Chris@17
|
52 }
|
Chris@17
|
53
|
Chris@17
|
54 $db_url = $input->getOption('db-url');
|
Chris@17
|
55 putenv("SIMPLETEST_DB=$db_url");
|
Chris@17
|
56
|
Chris@17
|
57 // Handle the cleanup of the test site.
|
Chris@17
|
58 $this->tearDown($test_database, $db_url);
|
Chris@17
|
59
|
Chris@17
|
60 // Release the test database prefix lock.
|
Chris@17
|
61 if (!$input->getOption('keep-lock')) {
|
Chris@17
|
62 $test_database->releaseLock();
|
Chris@17
|
63 }
|
Chris@17
|
64
|
Chris@17
|
65 $output->writeln("<info>Successfully uninstalled $db_prefix test site</info>");
|
Chris@17
|
66 }
|
Chris@17
|
67
|
Chris@17
|
68 /**
|
Chris@17
|
69 * Removes a given instance by deleting all the database tables and files.
|
Chris@17
|
70 *
|
Chris@17
|
71 * @param \Drupal\Core\Test\TestDatabase $test_database
|
Chris@17
|
72 * The test database object.
|
Chris@17
|
73 * @param string $db_url
|
Chris@17
|
74 * The database URL.
|
Chris@17
|
75 *
|
Chris@17
|
76 * @see \Drupal\Tests\BrowserTestBase::cleanupEnvironment()
|
Chris@17
|
77 */
|
Chris@17
|
78 protected function tearDown(TestDatabase $test_database, $db_url) {
|
Chris@17
|
79 // Connect to the test database.
|
Chris@17
|
80 $root = dirname(dirname(dirname(dirname(dirname(__DIR__)))));
|
Chris@17
|
81 $database = Database::convertDbUrlToConnectionInfo($db_url, $root);
|
Chris@17
|
82 $database['prefix'] = ['default' => $test_database->getDatabasePrefix()];
|
Chris@17
|
83 Database::addConnectionInfo(__CLASS__, 'default', $database);
|
Chris@17
|
84
|
Chris@17
|
85 // Remove all the tables.
|
Chris@17
|
86 $schema = Database::getConnection('default', __CLASS__)->schema();
|
Chris@17
|
87 $tables = $schema->findTables('%');
|
Chris@17
|
88 array_walk($tables, [$schema, 'dropTable']);
|
Chris@17
|
89
|
Chris@17
|
90 // Delete test site directory.
|
Chris@17
|
91 $this->fileUnmanagedDeleteRecursive($root . DIRECTORY_SEPARATOR . $test_database->getTestSitePath(), [BrowserTestBase::class, 'filePreDeleteCallback']);
|
Chris@17
|
92 }
|
Chris@17
|
93
|
Chris@17
|
94 /**
|
Chris@17
|
95 * Deletes all files and directories in the specified path recursively.
|
Chris@17
|
96 *
|
Chris@17
|
97 * Note this method has no dependencies on Drupal core to ensure that the
|
Chris@17
|
98 * test site can be torn down even if something in the test site is broken.
|
Chris@17
|
99 *
|
Chris@17
|
100 * @param string $path
|
Chris@17
|
101 * A string containing either an URI or a file or directory path.
|
Chris@17
|
102 * @param callable $callback
|
Chris@17
|
103 * (optional) Callback function to run on each file prior to deleting it and
|
Chris@17
|
104 * on each directory prior to traversing it. For example, can be used to
|
Chris@17
|
105 * modify permissions.
|
Chris@17
|
106 *
|
Chris@17
|
107 * @return bool
|
Chris@17
|
108 * TRUE for success or if path does not exist, FALSE in the event of an
|
Chris@17
|
109 * error.
|
Chris@17
|
110 *
|
Chris@17
|
111 * @see file_unmanaged_delete_recursive()
|
Chris@17
|
112 */
|
Chris@17
|
113 protected function fileUnmanagedDeleteRecursive($path, $callback = NULL) {
|
Chris@17
|
114 if (isset($callback)) {
|
Chris@17
|
115 call_user_func($callback, $path);
|
Chris@17
|
116 }
|
Chris@17
|
117 if (is_dir($path)) {
|
Chris@17
|
118 $dir = dir($path);
|
Chris@17
|
119 while (($entry = $dir->read()) !== FALSE) {
|
Chris@17
|
120 if ($entry == '.' || $entry == '..') {
|
Chris@17
|
121 continue;
|
Chris@17
|
122 }
|
Chris@17
|
123 $entry_path = $path . '/' . $entry;
|
Chris@17
|
124 $this->fileUnmanagedDeleteRecursive($entry_path, $callback);
|
Chris@17
|
125 }
|
Chris@17
|
126 $dir->close();
|
Chris@17
|
127
|
Chris@17
|
128 return rmdir($path);
|
Chris@17
|
129 }
|
Chris@17
|
130 return unlink($path);
|
Chris@17
|
131 }
|
Chris@17
|
132
|
Chris@17
|
133 }
|