diff --git a/core/lib/Drupal/Core/Test/TestSetupTrait.php b/core/lib/Drupal/Core/Test/TestSetupTrait.php index 44409078db..8bef66c3dc 100644 --- a/core/lib/Drupal/Core/Test/TestSetupTrait.php +++ b/core/lib/Drupal/Core/Test/TestSetupTrait.php @@ -159,6 +159,8 @@ protected function changeDatabasePrefix() { $db_url = getenv('SIMPLETEST_DB'); if (!empty($db_url)) { $database = Database::convertDbUrlToConnectionInfo($db_url, isset($this->root) ? $this->root : DRUPAL_ROOT); + // Ensure that the connection is added. + Database::removeConnection('default'); Database::addConnectionInfo('default', 'default', $database); } diff --git a/core/tests/Drupal/KernelTests/Setup/Commands/SetupDrupalTestScriptTest.php b/core/tests/Drupal/KernelTests/Setup/Commands/SetupDrupalTestScriptTest.php deleted file mode 100644 index fb45b50527..0000000000 --- a/core/tests/Drupal/KernelTests/Setup/Commands/SetupDrupalTestScriptTest.php +++ /dev/null @@ -1,211 +0,0 @@ -root = static::getDrupalRoot(); - } - - /** - * @coversNothing - */ - public function testInstallWithNonExistingClass() { - $autoloader = $this->root . '/autoload.php'; - $app = new TestInstallationSetupApplication(require $autoloader); - $app->setAutoExit(FALSE); - - $app_tester = new ApplicationTester($app); - $app_tester->run( - [ - 'command' => 'setup-drupal-test', - '--setup_class' => 'this-class-does-not-exist', - ], - [ - 'interactive' => FALSE, - ] - ); - - $this->assertContains('There was a problem loading this-class-does-not-exist', $app_tester->getDisplay()); - } - - /** - * @coversNothing - */ - public function testInstallWithNonSetupClass() { - $autoloader = $this->root . '/autoload.php'; - $app = new TestInstallationSetupApplication(require $autoloader); - $app->setAutoExit(FALSE); - - $app_tester = new ApplicationTester($app); - $app_tester->run( - [ - 'command' => 'setup-drupal-test', - '--setup_class' => static::class, - ], - [ - 'interactive' => FALSE, - ] - ); - - $this->assertContains('You need to define a class implementing \Drupal\Setup\TestSetupInterface ', $app_tester->getDisplay()); - } - - /** - * @coversNothing - */ - public function testInstallScript() { - $autoloader = $this->root . '/autoload.php'; - $app = new TestInstallationSetupApplication(require $autoloader); - $app->setAutoExit(FALSE); - - $app_tester = new ApplicationTester($app); - $app_tester->run( - [ - 'command' => 'setup-drupal-test', - '--setup_class' => SetupDrupalTestScript::class, - ], - [ - 'interactive' => FALSE, - ] - ); - - $this->assertNotRegExp('/PHPUnit_Framework_Error_Warning/', $app_tester->getDisplay()); - $this->assertNotRegExp('/AlreadyInstalledException/', $app_tester->getDisplay()); - - $result = json_decode($app_tester->getDisplay(), TRUE); - $db_prefix = $result['db_prefix']; - $this->assertNotEmpty($db_prefix); - $this->assertStringStartsWith('simpletest' . substr($db_prefix, 4) . ':', $result['user_agent']); - - $this->assertEquals(0, $app_tester->getStatusCode()); - - $http_client = new Client(); - $request = (new Request('GET', getenv('SIMPLETEST_BASE_URL') . '/test-page')) - ->withHeader('User-Agent', trim($result['user_agent'])); - - $response = $http_client->send($request); - // Ensure the test_page_test module got installed. - $this->assertContains('Test page | Drupal', (string) $response->getBody()); - - // Ensure that there are files and database tables for teardown to clean up. - $this->addTestDatabase($db_prefix); - $this->assertGreaterThan(0, count(Database::getConnection(__CLASS__)->schema()->findTables('%'))); - $test_database = new TestDatabase($db_prefix); - $test_file = $test_database->getTestSitePath() . DIRECTORY_SEPARATOR . '.htkey'; - $this->assertFileExists($test_file); - - // Now test the tear down process as well. - $status = $app_tester->run( - [ - 'command' => 'teardown-drupal-test', - 'db_prefix' => "$db_prefix", - ], - [ - 'interactive' => FALSE, - ] - ); - $this->assertSame(0, $status); - - // Ensure that all the tables and files for this DB prefix are gone. - $this->assertCount(0, Database::getConnection(__CLASS__)->schema()->findTables('%')); - $this->assertFileNotExists($test_file); - } - - /** - * @coversNothing - */ - public function testInstallInDifferentLanguage() { - $autoloader = $this->root . '/autoload.php'; - $app = new TestInstallationSetupApplication(require $autoloader); - $app->setAutoExit(FALSE); - - $app_tester = new ApplicationTester($app); - $app_tester->run( - [ - 'command' => 'setup-drupal-test', - '--langcode' => 'fr', - '--setup_class' => SetupDrupalTestScript::class, - ], - [ - 'interactive' => FALSE, - ] - ); - - $this->assertNotRegExp('/PHPUnit_Framework_Error_Warning/', $app_tester->getDisplay()); - $this->assertNotRegExp('/AlreadyInstalledException/', $app_tester->getDisplay()); - $this->assertEquals(0, $app_tester->getStatusCode()); - - $result = json_decode($app_tester->getDisplay(), TRUE); - $db_prefix = $result['db_prefix']; - $http_client = new Client(); - $request = (new Request('GET', getenv('SIMPLETEST_BASE_URL') . '/test-page')) - ->withHeader('User-Agent', trim($result['user_agent'])); - - $response = $http_client->send($request); - // Ensure the test_page_test module got installed. - $this->assertContains('Test page | Drupal', (string) $response->getBody()); - $this->assertContains('lang="fr"', (string) $response->getBody()); - - // Now test the tear down process as well. - $app_tester->run( - [ - 'command' => 'teardown-drupal-test', - 'db_prefix' => $db_prefix, - ], - [ - 'interactive' => FALSE, - ] - ); - - // Ensure that all the tables for this DB prefix are gone. - $this->addTestDatabase($db_prefix); - $this->assertCount(0, Database::getConnection(__CLASS__)->schema()->findTables('%')); - } - - /** - * Adds the installed test site to the database connection info. - * - * The target for the added connection is the class name of the test. - * - * @param $db_prefix - * The prefix of the installed test site. - */ - protected function addTestDatabase($db_prefix) { - // Use the original database connection info. - $connection_info = Database::getConnectionInfo('simpletest_original_default'); - if (!empty($connection_info)) { - foreach ($connection_info as $target => $value) { - $connection_info[$target]['prefix'] = [ - 'default' => $value['prefix']['default'] . $db_prefix, - ]; - } - } - Database::addConnectionInfo('default', __CLASS__, $connection_info['default']); - } - -} diff --git a/core/tests/Drupal/Setup/Commands/TestInstallationSetupApplication.php b/core/tests/Drupal/Setup/Commands/TestInstallationSetupApplication.php index 15a5ab1804..1ec945958c 100644 --- a/core/tests/Drupal/Setup/Commands/TestInstallationSetupApplication.php +++ b/core/tests/Drupal/Setup/Commands/TestInstallationSetupApplication.php @@ -35,7 +35,7 @@ public function __construct($autoloader) { protected function getDefaultCommands() { // Even though this is a single command, keep the HelpCommand (--help). $default_commands = parent::getDefaultCommands(); - $default_commands[] = new TestInstallationSetupCommand($this->autoloader); + $default_commands[] = new TestInstallationSetupCommand(); $default_commands[] = new TestTeardownCommand($this->autoloader); return $default_commands; } diff --git a/core/tests/Drupal/Setup/Commands/TestInstallationSetupCommand.php b/core/tests/Drupal/Setup/Commands/TestInstallationSetupCommand.php index 9262499a72..8e01727fcf 100644 --- a/core/tests/Drupal/Setup/Commands/TestInstallationSetupCommand.php +++ b/core/tests/Drupal/Setup/Commands/TestInstallationSetupCommand.php @@ -2,8 +2,6 @@ namespace Drupal\Setup\Commands; -use Drupal\Core\DrupalKernel; -use Drupal\Core\Site\Settings; use Drupal\Core\Test\FunctionalTestSetupTrait; use Drupal\Core\Test\TestSetupTrait; use Drupal\Setup\TestSetupInterface; @@ -13,7 +11,6 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\HttpFoundation\Request; /** * Symfony console command to setup Drupal. @@ -58,28 +55,6 @@ class TestInstallationSetupCommand extends Command { */ protected $langcode = 'en'; - /** - * The used PHP autoloader. - * - * @var object - */ - protected $autoloader; - - /** - * Constructs a new TestInstallationSetupCommand. - * - * @param string $autoloader - * The used PHP autoloader. - * @param string|null $name - * The name of the command. Passing NULL means it must be set in - * configure(). - */ - public function __construct($autoloader, $name = NULL) { - parent::__construct($name); - - $this->autoloader = $autoloader; - } - /** * {@inheritdoc} */ @@ -96,13 +71,14 @@ protected function configure() { * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { + // Validate the setup class prior to installing a database to avoid creating + // unnecessary sites. + $this->validateSetupClass($input->getOption('setup_class')); $db_url = $input->getOption('db_url'); $base_url = $input->getOption('base_url'); putenv("SIMPLETEST_DB=$db_url"); putenv("SIMPLETEST_BASE_URL=$base_url"); - $this->bootstrapDrupal(); - // Manage site fixture. $this->setup($input->getOption('install_profile'), $input->getOption('setup_class'), $input->getOption('langcode')); @@ -113,18 +89,26 @@ protected function execute(InputInterface $input, OutputInterface $output) { } /** - * Bootstraps the drupal kernel. + * Validates the setup class. + * + * @param string|null $class + * The setup class to validate. + * + * @throws \InvalidArgumentException + * Thrown if the class does not exist or does not implement + * TestSetupInterface. */ - protected function bootstrapDrupal() { - $request = Request::createFromGlobals(); - $kernel = DrupalKernel::createFromRequest($request, $this->autoloader, $this->getApplication()->getName()); - DrupalKernel::bootEnvironment($kernel->getAppRoot()); - - Settings::initialize( - dirname(dirname(dirname(dirname(__DIR__)))), - DrupalKernel::findSitePath($request), - $this->autoloader - ); + protected function validateSetupClass($class) { + if ($class === NULL) { + return; + } + if (!class_exists($class)) { + throw new \InvalidArgumentException("There was a problem loading {$class}"); + } + + if (!is_subclass_of($class, TestSetupInterface::class)) { + throw new \InvalidArgumentException(sprintf('You need to define a class implementing \Drupal\Setup\TestSetupInterface')); + } } /** @@ -175,14 +159,6 @@ protected function installDrupal() { * @see \Drupal\Setup\TestSetupInterface */ protected function executeSetupClass($class) { - if (!class_exists($class)) { - throw new \InvalidArgumentException("There was a problem loading {$class}"); - } - - if (!is_subclass_of($class, TestSetupInterface::class)) { - throw new \InvalidArgumentException(sprintf('You need to define a class implementing \Drupal\Setup\TestSetupInterface')); - } - /** @var \Drupal\Setup\TestSetupInterface $instance */ $instance = new $class(); $instance->setup(); diff --git a/core/tests/Drupal/Setup/Commands/TestTeardownCommand.php b/core/tests/Drupal/Setup/Commands/TestTeardownCommand.php index 4b8aef8841..cfec6c558c 100644 --- a/core/tests/Drupal/Setup/Commands/TestTeardownCommand.php +++ b/core/tests/Drupal/Setup/Commands/TestTeardownCommand.php @@ -4,9 +4,9 @@ use Drupal\Core\Database\Database; use Drupal\Core\DrupalKernel; -use Drupal\Core\Site\Settings; use Drupal\Core\Test\TestDatabase; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -46,7 +46,7 @@ public function __construct($autoloader, $name = NULL) { */ protected function configure() { $this->setName('teardown-drupal-test') - ->addArgument('db_prefix') + ->addArgument('db_prefix', InputArgument::REQUIRED, 'The database prefix for the test site') ->addOption('db_url', NULL, InputOption::VALUE_OPTIONAL, 'URL for database or SIMPLETEST_DB', getenv('SIMPLETEST_DB')) ->addOption('base_url', NULL, InputOption::VALUE_OPTIONAL, 'Base URL for site under test or SIMPLETEST_BASE_URL', getenv('SIMPLETEST_BASE_URL')); } @@ -61,22 +61,33 @@ protected function execute(InputInterface $input, OutputInterface $output) { putenv("SIMPLETEST_DB=$db_url"); putenv("SIMPLETEST_BASE_URL=$base_url"); - $this->bootstrapDrupal($db_prefix); + $kernel = $this->bootstrapDrupal(); // Handle the cleanup of the test site. - $this->teardown($db_prefix); + $this->teardown($db_prefix, $db_url, $kernel->getAppRoot()); } /** - * Removes a given instance by deleting all the database tables. + * Removes a given instance by deleting all the database tables and files. * * @param string $db_prefix * The used database prefix. + * @param $db_url + * The database URL. + * @param $app_root + * The app root. */ - public function teardown($db_prefix) { - $tables = Database::getConnection()->schema()->findTables('%'); + public function teardown($db_prefix, $db_url, $app_root) { + // Connect to the test database. + $database = Database::convertDbUrlToConnectionInfo($db_url, $app_root); + $database['prefix'] = ['default' => $db_prefix]; + Database::addConnectionInfo(__CLASS__, 'default', $database); + + // Remove all the tables. + $connection = Database::getConnection('default', __CLASS__); + $tables = $connection->schema()->findTables('%'); foreach ($tables as $table) { - if (Database::getConnection()->schema()->dropTable($table)) { + if ($connection->schema()->dropTable($table)) { unset($tables[$table]); } } @@ -107,21 +118,18 @@ public static function filePreDeleteCallback($path) { /** * Bootstraps the drupal kernel. * - * @param string $db_prefix - * The database prefix. + * @return \Drupal\Core\DrupalKernel + * The Drupal kernel. */ - protected function bootstrapDrupal($db_prefix) { + protected function bootstrapDrupal() { $request = Request::createFromGlobals(); - $_COOKIE['SIMPLETEST_USER_AGENT'] = drupal_generate_test_ua($db_prefix); - $kernel = DrupalKernel::createFromRequest($request, $this->autoloader, $this->getApplication()->getName()); - DrupalKernel::bootEnvironment($kernel->getAppRoot()); - Settings::initialize( - dirname(dirname(dirname(dirname(__DIR__)))), - DrupalKernel::findSitePath($request), - $this->autoloader - ); + // Finish booting Drupal in order to use functions like + // file_unmanaged_delete_recursive(). + $kernel->boot(); + $kernel->loadLegacyIncludes(); + return $kernel; } } diff --git a/core/tests/Drupal/Tests/Setup/Commands/SetupDrupalTestScriptTest.php b/core/tests/Drupal/Tests/Setup/Commands/SetupDrupalTestScriptTest.php new file mode 100644 index 0000000000..20fe0a7932 --- /dev/null +++ b/core/tests/Drupal/Tests/Setup/Commands/SetupDrupalTestScriptTest.php @@ -0,0 +1,189 @@ +root = dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__)))); + } + + /** + * @coversNothing + */ + public function testInstallWithNonExistingClass() { + $phpBinaryFinder = new PhpExecutableFinder(); + $phpBinaryPath = $phpBinaryFinder->find(); + + $command_line = $phpBinaryPath . ' core/scripts/setup-drupal-test.php setup-drupal-test --setup_class "this-class-does-not-exist" --db_url "' . getenv('SIMPLETEST_DB') . '"'; + $process = new Process($command_line, $this->root); + $process->run(); + + $this->assertContains('There was a problem loading this-class-does-not-exist', $process->getErrorOutput()); + $this->assertSame(1, $process->getExitCode()); + } + + /** + * @coversNothing + */ + public function testInstallWithNonSetupClass() { + $phpBinaryFinder = new PhpExecutableFinder(); + $phpBinaryPath = $phpBinaryFinder->find(); + + $command_line = $phpBinaryPath . ' core/scripts/setup-drupal-test.php setup-drupal-test --setup_class "' . static::class . '" --db_url "' . getenv('SIMPLETEST_DB') . '"'; + $process = new Process($command_line, $this->root); + $process->run(); + + $this->assertContains('You need to define a class implementing \Drupal\Setup\TestSetupInterface', $process->getErrorOutput()); + $this->assertSame(1, $process->getExitCode()); + } + + /** + * @coversNothing + */ + public function testInstallScript() { + $phpBinaryFinder = new PhpExecutableFinder(); + $phpBinaryPath = $phpBinaryFinder->find(); + + $command_line = $phpBinaryPath . ' core/scripts/setup-drupal-test.php setup-drupal-test --setup_class "' . SetupDrupalTestScript::class . '" --db_url "' . getenv('SIMPLETEST_DB') . '"'; + $process = new Process($command_line, $this->root); + $process->setTimeout(500); + $process->run(); + + $this->assertSame(0, $process->getExitCode()); + $result = json_decode($process->getOutput(), TRUE); + $db_prefix = $result['db_prefix']; + $this->assertStringStartsWith('simpletest' . substr($db_prefix, 4) . ':', $result['user_agent']); + + $http_client = new Client(); + $request = (new Request('GET', getenv('SIMPLETEST_BASE_URL') . '/test-page')) + ->withHeader('User-Agent', trim($result['user_agent'])); + + $response = $http_client->send($request); + // Ensure the test_page_test module got installed. + $this->assertContains('Test page | Drupal', (string) $response->getBody()); + + // Ensure that there are files and database tables for teardown to clean up. + $key = $this->addTestDatabase($db_prefix); + $this->assertGreaterThan(0, count(Database::getConnection('default', $key)->schema()->findTables('%'))); + $test_database = new TestDatabase($db_prefix); + $test_file = $test_database->getTestSitePath() . DIRECTORY_SEPARATOR . '.htkey'; + $this->assertFileExists($test_file); + + // Install another site so we can ensure tear down only removes one site at + // a time. + $process->run(); + $result = json_decode($process->getOutput(), TRUE); + $other_db_prefix = $result['db_prefix']; + $other_key = $this->addTestDatabase($other_db_prefix); + $this->assertGreaterThan(0, count(Database::getConnection('default', $other_key)->schema()->findTables('%'))); + + // Now test the tear down process as well. + $command_line = $phpBinaryPath . ' core/scripts/setup-drupal-test.php teardown-drupal-test ' . $db_prefix . ' --db_url "' . getenv('SIMPLETEST_DB') . '"'; + $process = new Process($command_line, $this->root); + $process->setTimeout(500); + $process->run(); + $this->assertSame(0, $process->getExitCode()); + + // Ensure that all the tables and files for this DB prefix are gone. + $this->assertCount(0, Database::getConnection('default', $key)->schema()->findTables('%')); + $this->assertFileNotExists($test_file); + + // Ensure the other sites tables and files still exist. + $this->assertGreaterThan(0, count(Database::getConnection('default', $other_key)->schema()->findTables('%'))); + $test_database = new TestDatabase($other_db_prefix); + $test_file = $test_database->getTestSitePath() . DIRECTORY_SEPARATOR . '.htkey'; + $this->assertFileExists($test_file); + + // Now test the tear down process as well. + $command_line = $phpBinaryPath . ' core/scripts/setup-drupal-test.php teardown-drupal-test ' . $other_db_prefix . ' --db_url "' . getenv('SIMPLETEST_DB') . '"'; + $process = new Process($command_line, $this->root); + $process->setTimeout(500); + $process->run(); + $this->assertSame(0, $process->getExitCode()); + + $this->assertCount(0, Database::getConnection('default', $other_key)->schema()->findTables('%')); + $this->assertFileNotExists($test_file); + } + + /** + * @coversNothing + */ + public function testInstallInDifferentLanguage() { + $phpBinaryFinder = new PhpExecutableFinder(); + $phpBinaryPath = $phpBinaryFinder->find(); + + $command_line = $phpBinaryPath . ' core/scripts/setup-drupal-test.php setup-drupal-test --langcode fr --setup_class "' . SetupDrupalTestScript::class . '" --db_url "' . getenv('SIMPLETEST_DB') . '"'; + $process = new Process($command_line, $this->root); + $process->setTimeout(500); + $process->run(); + $this->assertEquals(0, $process->getExitCode()); + + $result = json_decode($process->getOutput(), TRUE); + $db_prefix = $result['db_prefix']; + $http_client = new Client(); + $request = (new Request('GET', getenv('SIMPLETEST_BASE_URL') . '/test-page')) + ->withHeader('User-Agent', trim($result['user_agent'])); + + $response = $http_client->send($request); + // Ensure the test_page_test module got installed. + $this->assertContains('Test page | Drupal', (string) $response->getBody()); + $this->assertContains('lang="fr"', (string) $response->getBody()); + + // Now test the tear down process as well. + $command_line = $phpBinaryPath . ' core/scripts/setup-drupal-test.php teardown-drupal-test ' . $db_prefix . ' --db_url "' . getenv('SIMPLETEST_DB') . '"'; + $process = new Process($command_line, $this->root); + $process->setTimeout(500); + $process->run(); + $this->assertSame(0, $process->getExitCode()); + + // Ensure that all the tables for this DB prefix are gone. + $this->assertCount(0, Database::getConnection('default', $this->addTestDatabase($db_prefix))->schema()->findTables('%')); + } + + /** + * Adds the installed test site to the database connection info. + * + * @param $db_prefix + * The prefix of the installed test site. + * + * @return string + * The database key of the added connection. + */ + protected function addTestDatabase($db_prefix) { + $database = Database::convertDbUrlToConnectionInfo(getenv('SIMPLETEST_DB'), $this->root); + $database['prefix'] = ['default' => $db_prefix]; + $target = __CLASS__ . $db_prefix; + Database::addConnectionInfo($target, 'default', $database); + return $target; + } + +}