Chris@17: setName('tear-down')
Chris@17: ->setDescription('Removes a test site added by the install command')
Chris@17: ->setHelp('All the database tables and files will be removed.')
Chris@17: ->addArgument('db-prefix', InputArgument::REQUIRED, 'The database prefix for the test site.')
Chris@17: ->addOption('db-url', NULL, InputOption::VALUE_OPTIONAL, 'URL for database. Defaults to the environment variable SIMPLETEST_DB.', getenv('SIMPLETEST_DB'))
Chris@17: ->addOption('keep-lock', NULL, InputOption::VALUE_NONE, 'Keeps the database prefix lock. Useful for ensuring test isolation when running concurrent tests.')
Chris@17: ->addUsage('test12345678')
Chris@17: ->addUsage('test12345678 --db-url "mysql://username:password@localhost/databasename#table_prefix"')
Chris@17: ->addUsage('test12345678 --keep-lock');
Chris@17: }
Chris@17:
Chris@17: /**
Chris@17: * {@inheritdoc}
Chris@17: */
Chris@17: protected function execute(InputInterface $input, OutputInterface $output) {
Chris@17: $db_prefix = $input->getArgument('db-prefix');
Chris@17: // Validate the db_prefix argument.
Chris@17: try {
Chris@17: $test_database = new TestDatabase($db_prefix);
Chris@17: }
Chris@17: catch (\InvalidArgumentException $e) {
Chris@17: $io = new SymfonyStyle($input, $output);
Chris@17: $io->getErrorStyle()->error("Invalid database prefix: $db_prefix\n\nValid database prefixes match the regular expression '/test(\d+)$/'. For example, 'test12345678'.");
Chris@17: // Display the synopsis of the command like Composer does.
Chris@17: $output->writeln(sprintf('%s', sprintf($this->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET);
Chris@17: return 1;
Chris@17: }
Chris@17:
Chris@17: $db_url = $input->getOption('db-url');
Chris@17: putenv("SIMPLETEST_DB=$db_url");
Chris@17:
Chris@17: // Handle the cleanup of the test site.
Chris@17: $this->tearDown($test_database, $db_url);
Chris@17:
Chris@17: // Release the test database prefix lock.
Chris@17: if (!$input->getOption('keep-lock')) {
Chris@17: $test_database->releaseLock();
Chris@17: }
Chris@17:
Chris@17: $output->writeln("Successfully uninstalled $db_prefix test site");
Chris@17: }
Chris@17:
Chris@17: /**
Chris@17: * Removes a given instance by deleting all the database tables and files.
Chris@17: *
Chris@17: * @param \Drupal\Core\Test\TestDatabase $test_database
Chris@17: * The test database object.
Chris@17: * @param string $db_url
Chris@17: * The database URL.
Chris@17: *
Chris@17: * @see \Drupal\Tests\BrowserTestBase::cleanupEnvironment()
Chris@17: */
Chris@17: protected function tearDown(TestDatabase $test_database, $db_url) {
Chris@17: // Connect to the test database.
Chris@17: $root = dirname(dirname(dirname(dirname(dirname(__DIR__)))));
Chris@17: $database = Database::convertDbUrlToConnectionInfo($db_url, $root);
Chris@17: $database['prefix'] = ['default' => $test_database->getDatabasePrefix()];
Chris@17: Database::addConnectionInfo(__CLASS__, 'default', $database);
Chris@17:
Chris@17: // Remove all the tables.
Chris@17: $schema = Database::getConnection('default', __CLASS__)->schema();
Chris@17: $tables = $schema->findTables('%');
Chris@17: array_walk($tables, [$schema, 'dropTable']);
Chris@17:
Chris@17: // Delete test site directory.
Chris@17: $this->fileUnmanagedDeleteRecursive($root . DIRECTORY_SEPARATOR . $test_database->getTestSitePath(), [BrowserTestBase::class, 'filePreDeleteCallback']);
Chris@17: }
Chris@17:
Chris@17: /**
Chris@17: * Deletes all files and directories in the specified path recursively.
Chris@17: *
Chris@17: * Note this method has no dependencies on Drupal core to ensure that the
Chris@17: * test site can be torn down even if something in the test site is broken.
Chris@17: *
Chris@17: * @param string $path
Chris@17: * A string containing either an URI or a file or directory path.
Chris@17: * @param callable $callback
Chris@17: * (optional) Callback function to run on each file prior to deleting it and
Chris@17: * on each directory prior to traversing it. For example, can be used to
Chris@17: * modify permissions.
Chris@17: *
Chris@17: * @return bool
Chris@17: * TRUE for success or if path does not exist, FALSE in the event of an
Chris@17: * error.
Chris@17: *
Chris@17: * @see file_unmanaged_delete_recursive()
Chris@17: */
Chris@17: protected function fileUnmanagedDeleteRecursive($path, $callback = NULL) {
Chris@17: if (isset($callback)) {
Chris@17: call_user_func($callback, $path);
Chris@17: }
Chris@17: if (is_dir($path)) {
Chris@17: $dir = dir($path);
Chris@17: while (($entry = $dir->read()) !== FALSE) {
Chris@17: if ($entry == '.' || $entry == '..') {
Chris@17: continue;
Chris@17: }
Chris@17: $entry_path = $path . '/' . $entry;
Chris@17: $this->fileUnmanagedDeleteRecursive($entry_path, $callback);
Chris@17: }
Chris@17: $dir->close();
Chris@17:
Chris@17: return rmdir($path);
Chris@17: }
Chris@17: return unlink($path);
Chris@17: }
Chris@17:
Chris@17: }