TestSiteTearDownCommand.php 4.8 KB

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