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: }