Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / web / core / tests / Drupal / TestSite / Commands / TestSiteInstallCommand.php
diff --git a/web/core/tests/Drupal/TestSite/Commands/TestSiteInstallCommand.php b/web/core/tests/Drupal/TestSite/Commands/TestSiteInstallCommand.php
new file mode 100644 (file)
index 0000000..d020304
--- /dev/null
@@ -0,0 +1,249 @@
+<?php
+
+namespace Drupal\TestSite\Commands;
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\Test\FunctionalTestSetupTrait;
+use Drupal\Core\Test\TestDatabase;
+use Drupal\Core\Test\TestSetupTrait;
+use Drupal\TestSite\TestSetupInterface;
+use Drupal\Tests\RandomGeneratorTrait;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+/**
+ * Command to create a test Drupal site.
+ *
+ * @internal
+ */
+class TestSiteInstallCommand extends Command {
+
+  use FunctionalTestSetupTrait {
+    installParameters as protected installParametersTrait;
+  }
+  use RandomGeneratorTrait;
+  use TestSetupTrait {
+    changeDatabasePrefix as protected changeDatabasePrefixTrait;
+  }
+
+  /**
+   * The install profile to use.
+   *
+   * @var string
+   */
+  protected $profile = 'testing';
+
+  /**
+   * Time limit in seconds for the test.
+   *
+   * Used by \Drupal\Core\Test\FunctionalTestSetupTrait::prepareEnvironment().
+   *
+   * @var int
+   */
+  protected $timeLimit = 500;
+
+  /**
+   * The database prefix of this test run.
+   *
+   * @var string
+   */
+  protected $databasePrefix;
+
+  /**
+   * The language to install the site in.
+   *
+   * @var string
+   */
+  protected $langcode = 'en';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function configure() {
+    $this->setName('install')
+      ->setDescription('Creates a test Drupal site')
+      ->setHelp('The details to connect to the test site created will be displayed upon success. It will contain the database prefix and the user agent.')
+      ->addOption('setup-file', NULL, InputOption::VALUE_OPTIONAL, 'The path to a PHP file containing a class to setup configuration used by the test, for example, core/tests/Drupal/TestSite/TestSiteInstallTestScript.php.')
+      ->addOption('db-url', NULL, InputOption::VALUE_OPTIONAL, 'URL for database. Defaults to the environment variable SIMPLETEST_DB.', getenv('SIMPLETEST_DB'))
+      ->addOption('base-url', NULL, InputOption::VALUE_OPTIONAL, 'Base URL for site under test. Defaults to the environment variable SIMPLETEST_BASE_URL.', getenv('SIMPLETEST_BASE_URL'))
+      ->addOption('install-profile', NULL, InputOption::VALUE_OPTIONAL, 'Install profile to install the site in. Defaults to testing.', 'testing')
+      ->addOption('langcode', NULL, InputOption::VALUE_OPTIONAL, 'The language to install the site in. Defaults to en.', 'en')
+      ->addOption('json', NULL, InputOption::VALUE_NONE, 'Output test site connection details in JSON.')
+      ->addUsage('--setup-file core/tests/Drupal/TestSite/TestSiteInstallTestScript.php --json')
+      ->addUsage('--install-profile demo_umami --langcode fr')
+      ->addUsage('--base-url "http://example.com" --db-url "mysql://username:password@localhost/databasename#table_prefix"');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function execute(InputInterface $input, OutputInterface $output) {
+    // Determines and validates the setup class prior to installing a database
+    // to avoid creating unnecessary sites.
+    $root = dirname(dirname(dirname(dirname(dirname(__DIR__)))));
+    chdir($root);
+    $class_name = $this->getSetupClass($input->getOption('setup-file'));
+    // Ensure we can install a site in the sites/simpletest directory.
+    $this->ensureDirectory($root);
+
+    $db_url = $input->getOption('db-url');
+    $base_url = $input->getOption('base-url');
+    putenv("SIMPLETEST_DB=$db_url");
+    putenv("SIMPLETEST_BASE_URL=$base_url");
+
+    // Manage site fixture.
+    $this->setup($input->getOption('install-profile'), $class_name, $input->getOption('langcode'));
+
+    $user_agent = drupal_generate_test_ua($this->databasePrefix);
+    if ($input->getOption('json')) {
+      $output->writeln(json_encode([
+        'db_prefix' => $this->databasePrefix,
+        'user_agent' => $user_agent,
+        'site_path' => $this->siteDirectory,
+      ]));
+    }
+    else {
+      $output->writeln('<info>Successfully installed a test site</info>');
+      $io = new SymfonyStyle($input, $output);
+      $io->table([], [
+        ['Database prefix', $this->databasePrefix],
+        ['User agent', $user_agent],
+        ['Site path', $this->siteDirectory],
+      ]);
+    }
+  }
+
+  /**
+   * Gets the setup class.
+   *
+   * @param string|null $file
+   *   The file to get the setup class from.
+   *
+   * @return string|null
+   *   The setup class contained in the provided $file.
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown if the file does not exist, does not contain a class or the class
+   *   does not implement \Drupal\TestSite\TestSetupInterface.
+   */
+  protected function getSetupClass($file) {
+    if ($file === NULL) {
+      return;
+    }
+    if (!file_exists($file)) {
+      throw new \InvalidArgumentException("The file $file does not exist.");
+    }
+
+    $classes = get_declared_classes();
+    include_once $file;
+    $new_classes = array_values(array_diff(get_declared_classes(), $classes));
+    if (empty($new_classes)) {
+      throw new \InvalidArgumentException("The file $file does not contain a class.");
+    }
+    $class = array_pop($new_classes);
+
+    if (!is_subclass_of($class, TestSetupInterface::class)) {
+      throw new \InvalidArgumentException("The class $class contained in $file needs to implement \Drupal\TestSite\TestSetupInterface");
+    }
+    return $class;
+  }
+
+  /**
+   * Ensures that the sites/simpletest directory exists and is writable.
+   *
+   * @param string $root
+   *   The Drupal root.
+   */
+  protected function ensureDirectory($root) {
+    if (!is_writable($root . '/sites/simpletest')) {
+      if (!@mkdir($root . '/sites/simpletest')) {
+        throw new \RuntimeException($root . '/sites/simpletest must exist and be writable to install a test site');
+      }
+    }
+  }
+
+  /**
+   * Creates a test drupal installation.
+   *
+   * @param string $profile
+   *   (optional) The installation profile to use.
+   * @param string $setup_class
+   *   (optional) Setup class. A PHP class to setup configuration used by the
+   *   test.
+   * @param string $langcode
+   *   (optional) The language to install the site in.
+   */
+  public function setup($profile = 'testing', $setup_class = NULL, $langcode = 'en') {
+    $this->profile = $profile;
+    $this->langcode = $langcode;
+    $this->setupBaseUrl();
+    $this->prepareEnvironment();
+    $this->installDrupal();
+
+    if ($setup_class) {
+      $this->executeSetupClass($setup_class);
+    }
+  }
+
+  /**
+   * Installs Drupal into the test site.
+   */
+  protected function installDrupal() {
+    $this->initUserSession();
+    $this->prepareSettings();
+    $this->doInstall();
+    $this->initSettings();
+    $container = $this->initKernel(\Drupal::request());
+    $this->initConfig($container);
+    $this->installModulesFromClassProperty($container);
+    $this->rebuildAll();
+  }
+
+  /**
+   * Uses the setup file to configure Drupal.
+   *
+   * @param string $class
+   *   The fully qualified class name, which should set up Drupal for tests. For
+   *   example this class could create content types and fields or install
+   *   modules. The class needs to implement TestSetupInterface.
+   *
+   * @see \Drupal\TestSite\TestSetupInterface
+   */
+  protected function executeSetupClass($class) {
+    /** @var \Drupal\TestSite\TestSetupInterface $instance */
+    $instance = new $class();
+    $instance->setup();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function installParameters() {
+    $parameters = $this->installParametersTrait();
+    $parameters['parameters']['langcode'] = $this->langcode;
+    return $parameters;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function changeDatabasePrefix() {
+    // Ensure that we use the database from SIMPLETEST_DB environment variable.
+    Database::removeConnection('default');
+    $this->changeDatabasePrefixTrait();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function prepareDatabasePrefix() {
+    // Override this method so that we can force a lock to be created.
+    $test_db = new TestDatabase(NULL, TRUE);
+    $this->siteDirectory = $test_db->getTestSitePath();
+    $this->databasePrefix = $test_db->getDatabasePrefix();
+  }
+
+}