You are here

class TestSiteTearDownCommand in Drupal 9

Same name and namespace in other branches
  1. 8 core/tests/Drupal/TestSite/Commands/TestSiteTearDownCommand.php \Drupal\TestSite\Commands\TestSiteTearDownCommand

Command to tear down a test Drupal site.

@internal

Hierarchy

Expanded class hierarchy of TestSiteTearDownCommand

1 file declares its use of TestSiteTearDownCommand
TestSiteApplication.php in core/tests/Drupal/TestSite/TestSiteApplication.php

File

core/tests/Drupal/TestSite/Commands/TestSiteTearDownCommand.php, line 20

Namespace

Drupal\TestSite\Commands
View source
class TestSiteTearDownCommand extends Command {

  /**
   * {@inheritdoc}
   */
  protected function configure() {
    $this
      ->setName('tear-down')
      ->setDescription('Removes a test site added by the install command')
      ->setHelp('All the database tables and files will be removed.')
      ->addArgument('db-prefix', InputArgument::REQUIRED, 'The database prefix for the test site.')
      ->addOption('db-url', NULL, InputOption::VALUE_OPTIONAL, 'URL for database. Defaults to the environment variable SIMPLETEST_DB.', getenv('SIMPLETEST_DB'))
      ->addOption('keep-lock', NULL, InputOption::VALUE_NONE, 'Keeps the database prefix lock. Useful for ensuring test isolation when running concurrent tests.')
      ->addUsage('test12345678')
      ->addUsage('test12345678 --db-url "mysql://username:password@localhost/databasename#table_prefix"')
      ->addUsage('test12345678 --keep-lock');
  }

  /**
   * {@inheritdoc}
   */
  protected function execute(InputInterface $input, OutputInterface $output) {
    $db_prefix = $input
      ->getArgument('db-prefix');

    // Validate the db_prefix argument.
    try {
      $test_database = new TestDatabase($db_prefix);
    } catch (\InvalidArgumentException $e) {
      $io = new SymfonyStyle($input, $output);
      $io
        ->getErrorStyle()
        ->error("Invalid database prefix: {$db_prefix}\n\nValid database prefixes match the regular expression '/test(\\d+)\$/'. For example, 'test12345678'.");

      // Display the synopsis of the command like Composer does.
      $output
        ->writeln(sprintf('<info>%s</info>', sprintf($this
        ->getSynopsis(), $this
        ->getName())), OutputInterface::VERBOSITY_QUIET);
      return 1;
    }
    $db_url = $input
      ->getOption('db-url');
    putenv("SIMPLETEST_DB={$db_url}");

    // Handle the cleanup of the test site.
    $this
      ->tearDown($test_database, $db_url);

    // Release the test database prefix lock.
    if (!$input
      ->getOption('keep-lock')) {
      $test_database
        ->releaseLock();
    }
    $output
      ->writeln("<info>Successfully uninstalled {$db_prefix} test site</info>");
    return 0;
  }

  /**
   * Removes a given instance by deleting all the database tables and files.
   *
   * @param \Drupal\Core\Test\TestDatabase $test_database
   *   The test database object.
   * @param string $db_url
   *   The database URL.
   *
   * @see \Drupal\Tests\BrowserTestBase::cleanupEnvironment()
   */
  protected function tearDown(TestDatabase $test_database, $db_url) : void {

    // Connect to the test database.
    $root = dirname(__DIR__, 5);
    $database = Database::convertDbUrlToConnectionInfo($db_url, $root);
    $database['prefix'] = $test_database
      ->getDatabasePrefix();
    Database::addConnectionInfo(__CLASS__, 'default', $database);

    // Remove all the tables.
    $schema = Database::getConnection('default', __CLASS__)
      ->schema();
    $tables = $schema
      ->findTables('%');
    array_walk($tables, [
      $schema,
      'dropTable',
    ]);

    // Delete test site directory.
    $this
      ->fileUnmanagedDeleteRecursive($root . DIRECTORY_SEPARATOR . $test_database
      ->getTestSitePath(), [
      BrowserTestBase::class,
      'filePreDeleteCallback',
    ]);
  }

  /**
   * Deletes all files and directories in the specified path recursively.
   *
   * Note this method has no dependencies on Drupal core to ensure that the
   * test site can be torn down even if something in the test site is broken.
   *
   * @param string $path
   *   A string containing either a URI or a file or directory path.
   * @param callable $callback
   *   (optional) Callback function to run on each file prior to deleting it and
   *   on each directory prior to traversing it. For example, can be used to
   *   modify permissions.
   *
   * @return bool
   *   TRUE for success or if path does not exist, FALSE in the event of an
   *   error.
   *
   * @see \Drupal\Core\File\FileSystemInterface::deleteRecursive()
   */
  protected function fileUnmanagedDeleteRecursive($path, $callback = NULL) {
    if (isset($callback)) {
      call_user_func($callback, $path);
    }
    if (is_dir($path)) {
      $dir = dir($path);
      while (($entry = $dir
        ->read()) !== FALSE) {
        if ($entry == '.' || $entry == '..') {
          continue;
        }
        $entry_path = $path . '/' . $entry;
        $this
          ->fileUnmanagedDeleteRecursive($entry_path, $callback);
      }
      $dir
        ->close();
      return rmdir($path);
    }
    return unlink($path);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
TestSiteTearDownCommand::configure protected function
TestSiteTearDownCommand::execute protected function
TestSiteTearDownCommand::fileUnmanagedDeleteRecursive protected function Deletes all files and directories in the specified path recursively.
TestSiteTearDownCommand::tearDown protected function Removes a given instance by deleting all the database tables and files.