diff --git a/core/tests/Drupal/BuildTests/Framework/BuildTestBase.php b/core/tests/Drupal/BuildTests/Framework/BuildTestBase.php index 75d68c2343..8f3f60a843 100644 --- a/core/tests/Drupal/BuildTests/Framework/BuildTestBase.php +++ b/core/tests/Drupal/BuildTests/Framework/BuildTestBase.php @@ -129,6 +129,15 @@ abstract class BuildTestBase extends TestCase { */ private $locks; + /** + * The most recent command process. + * + * @var \Symfony\Component\Process\Process + * + * @see ::executeCommand() + */ + private $commandProcess; + /** * {@inheritdoc} */ @@ -182,7 +191,7 @@ protected function tearDown() { */ protected function initMink() { // If the Symfony BrowserKit client can followMetaRefresh(), we should use - // its Goutte descendent instead of ours. + // the Goutte descendent instead of ours. if(method_exists(SymfonyClient::class, 'followMetaRefresh')) { $client = new Client; } @@ -206,7 +215,7 @@ protected function initMink() { * request. * * @return \Behat\Mink\Mink - * The Mink object for the last request. + * The Mink object. */ public function getMink() { return $this->mink; @@ -225,78 +234,52 @@ public function getWorkspaceDirectory() { } /** - * Assert that text is present in the error output of a command. + * Assert that text is present in the error output of the most recent command. * * @param string $expected * Text we expect to find in the error output of the command. - * @param string $command_line - * Command line to execute. - * @param string $working_dir - * (optional) A working directory relative to the workspace, within which to - * execute the command. Defaults to the workspace directory. - * - * @return \Symfony\Component\Process\Process - * The process that was used, for further assertion. */ - public function assertCommandErrorOutputContains($expected, $command_line, $working_dir = NULL) { - $process = $this->assertCommand($command_line, $working_dir); - $this->assertContains($expected, $process->getErrorOutput()); - return $process; + public function assertErrorOutputContains($expected) { + $this->assertContains($expected, $this->commandProcess->getErrorOutput()); } /** - * Assert that text is present in the output of a command. + * Assert that text is present in the output of the most recent command. * * @param string $expected * Text we expect to find in the output of the command. - * @param string $command_line - * Command line to execute. - * @param string $working_dir - * (optional) A working directory relative to the workspace, within which to - * execute the command. Defaults to the workspace directory. - * - * @return \Symfony\Component\Process\Process - * The process that was used, for further assertion. */ - public function assertCommandOutputContains($expected, $command_line, $working_dir = NULL) { - $process = $this->assertCommand($command_line, $working_dir); - $this->assertContains($expected, $process->getOutput()); - return $process; + public function assertCommandOutputContains($expected) { + $this->assertContains($expected, $this->commandProcess->getOutput()); } /** - * Asserts that a command runs without error. + * Asserts that the last command ran without error. * - * This method runs the command and then asserts that the process exit code is - * 0. + * This assertion checks whether the last command returned an exit code of 0. * * If you need to assert a different exit code, then you can use - * executeCommand() and perform a different assertion. + * executeCommand() and perform a different assertion on the process object. * * @param string $command_line * A command line to run in an isolated process. - * @param string $working_dir - * (optional) A working directory relative to the workspace, within which to - * execute the command. Defaults to the workspace directory. + */ + public function assertCommandSuccessful() { + return $this->assertCommandExitCode(0); + } + + /** + * Asserts that the last command returned the specified exit code. * - * @return \Symfony\Component\Process\Process - * The process that was used, for further assertion. + * @param int $expected_code + * The expected process exit code. */ - public function assertCommand($command_line, $working_dir = NULL) { - $this->assertNotEmpty($command_line); - $process = NULL; - try { - $process = $this->executeCommand($command_line, $working_dir); - } - catch (\Exception $ex) { - $this->fail('Process failed with message: ' . $ex->getMessage()); - } - $this->assertEquals(0, $process->getExitCode(), - 'COMMAND: ' . $command_line . "\n" . - 'OUTPUT: ' . $process->getOutput() . "\n" . - 'ERROR: ' . $process->getErrorOutput() . "\n" + public function assertCommandExitCode($expected_code) { + $this->assertEquals($expected_code, $this->commandProcess->getExitCode(), + 'COMMAND: ' . $this->commandProcess->getCommandLine() . "\n" . + 'OUTPUT: ' . $this->commandProcess->getOutput() . "\n" . + 'ERROR: ' . $this->commandProcess->getErrorOutput() . "\n" ); - return $process; } /** @@ -315,34 +298,22 @@ public function executeCommand($command_line, $working_dir = NULL) { if ($working_dir) { $working_path .= '/' . $working_dir; } - $process = new Process($command_line); - $process->setWorkingDirectory($working_path) + $this->commandProcess = new Process($command_line); + $this->commandProcess->setWorkingDirectory($working_path) ->setTimeout(300) ->setIdleTimeout(300); - $process->run(); - return $process; + $this->commandProcess->run(); + return $this->commandProcess; } /** - * Assert a visit against an HTTP server using the given docroot. - * - * Use $this->getMink() to get session and assertion objects to test the - * result of this method. + * Helper function to assert that the last visit was a Drupal site. * - * @param string $request_uri - * (optional) The non-host part of the URL. Example: some/path?foo=bar. - * Defaults to visiting the homepage. - * @param int $expected_status_code - * (optional) Expected HTTP status code for this request. Defaults to 200. - * @param string $docroot - * (optional) Relative path within the test workspace file system that will - * be the docroot for the request. Defaults to the workspace directory. + * This method asserts that the X-Generator header shows that the site is a + * Drupal site. */ - public function assertVisit($request_uri = '/', $expected_status_code = 200, $docroot = NULL) { - $this->visit($request_uri, $docroot); - $actual_code = $this->mink->getSession()->getStatusCode(); - $message = sprintf('Current response status code is %d, but %d expected for "%s".', $actual_code, $expected_status_code, $request_uri); - $this->assertEquals((int) $expected_status_code, $actual_code, $message); + public function assertDrupalVisit() { + $this->getMink()->assertSession()->responseHeaderMatches('X-Generator', '/Drupal \d+ \(https:\/\/www.drupal.org\)/'); } /** @@ -351,28 +322,29 @@ public function assertVisit($request_uri = '/', $expected_status_code = 200, $do * The concept here is that there could be multiple potential docroots in the * workspace, so you can use whichever ones you want. * - * Use $this->getMink() to get session and assertion objects to test the - * outcome of this method. - * * @param string $request_uri * (optional) The non-host part of the URL. Example: /some/path?foo=bar. * Defaults to visiting the homepage. - * @param string $docroot + * @param string $working_dir * (optional) Relative path within the test workspace file system that will * be the docroot for the request. Defaults to the workspace directory. * + * @return \Behat\Mink\Mink + * The Mink object. Perform assertions against this. + * * @throws \InvalidArgumentException * Thrown when $request_uri does not start with a slash. */ - public function visit($request_uri = '/', $docroot = NULL) { + public function visit($request_uri = '/', $working_dir = NULL) { if ($request_uri[0] !== '/') { - throw new \InvalidArgumentException('URI: ' . $request_uri . ' should be relative. Example: /some/path?foo=bar'); + throw new \InvalidArgumentException('URI: ' . $request_uri . ' must be relative. Example: /some/path?foo=bar'); } // Try to make a server. - $this->standUpServer($docroot); + $this->standUpServer($working_dir); $request = 'http://localhost:' . $this->getPortNumber() . $request_uri; $this->mink->getSession()->visit($request); + return $this->mink; } /** @@ -380,20 +352,20 @@ public function visit($request_uri = '/', $docroot = NULL) { * * Test authors should call visit() or assertVisit() instead. * - * @param string|null $docroot + * @param string|null $working_dir * (optional) Server docroot relative to the workspace file system. Defaults * to the workspace directory. */ - protected function standUpServer($docroot = NULL) { + protected function standUpServer($working_dir = NULL) { // If the user wants to test a new docroot, we have to shut down the old // server process and generate a new port number. - if ($docroot !== $this->serverDocroot && !empty($this->serverProcess)) { + if ($working_dir !== $this->serverDocroot && !empty($this->serverProcess)) { $this->stopServer(); } // If there's not a server at this point, make one. - $this->serverProcess = $this->instantiateServer($this->getPortNumber(), $docroot); + $this->serverProcess = $this->instantiateServer($this->getPortNumber(), $working_dir); if ($this->serverProcess) { - $this->serverDocroot = $docroot; + $this->serverDocroot = $working_dir; } } @@ -406,7 +378,7 @@ protected function standUpServer($docroot = NULL) { * The hostname for the server. * @param int $port * The port number for the server. - * @param string|null $docroot + * @param string|null $working_dir * (optional) Server docroot relative to the workspace filesystem. Defaults * to the workspace directory. * @@ -416,11 +388,11 @@ protected function standUpServer($docroot = NULL) { * @throws \RuntimeException * Thrown if we were unable to start a web server. */ - protected function instantiateServer($port, $docroot = NULL) { + protected function instantiateServer($port, $working_dir = NULL) { $finder = new PhpExecutableFinder(); $full_docroot = $this->getWorkspaceDirectory(); - if ($docroot) { - $full_docroot .= '/' . $docroot; + if ($working_dir) { + $full_docroot .= '/' . $working_dir; } $server = [ $finder->find(), diff --git a/core/tests/Drupal/BuildTests/Framework/BuildTestInterface.php b/core/tests/Drupal/BuildTests/Framework/BuildTestInterface.php deleted file mode 100644 index 79e7b77ace..0000000000 --- a/core/tests/Drupal/BuildTests/Framework/BuildTestInterface.php +++ /dev/null @@ -1,180 +0,0 @@ -getMink() to get session and assertion objects to test the - * result of this method. - * - * @param string $request_uri - * (optional) The non-host part of the URL. Example: some/path?foo=bar. - * Defaults to visiting the homepage. - * @param int $expected_status_code - * (optional) Expected HTTP status code for this request. Defaults to 200. - * @param string $docroot - * (optional) Relative path within the test workspace file system that will - * be the docroot for the request. Defaults to the workspace directory. - */ - public function assertVisit($request_uri = '', $expected_status_code = 200, $docroot = NULL); - - /** - * Get the Mink instance. - * - * Use the Mink object to perform assertions against the content returned by a - * request. - * - * @return \Behat\Mink\Mink - * The Mink object for the last request. - */ - public function getMink(); - - /** - * Visit a URI on the HTTP server. - * - * The concept here is that there could be multiple potential docroots in the - * workspace, so you can use whichever ones you want. - * - * Use $this->getMink() to get session and assertion objects to test the - * outcome of this method. - * - * @param string $request_uri - * (optional) The non-host part of the URL. Example: /some/path?foo=bar. - * Defaults to visiting the homepage. - * @param string $docroot - * (optional) Relative path within the test workspace file system that will - * be the docroot for the request. Defaults to the workspace directory. - * - * @throws \InvalidArgumentException - * Thrown when $request_uri does not start with a slash. - */ - public function visit($request_uri = '', $docroot = NULL); - - /** - * Copy the current working codebase into a workspace. - * - * Use this method to copy the current codebase, including any patched - * changes, into the workspace. - * - * By default, the copy will exclude sites/default/settings.php, - * sites/default/files, and vendor/. Use the $iterator parameter to override - * this behavior. - * - * @param \Iterator|null $iterator - * (optional) An iterator of all the files to copy. Default behavior is to - * exclude site-specific directories and files. - * @param string|null $working_dir - * (optional) Relative path within the test workspace file system that will - * contain the copy of the codebase. Defaults to the workspace directory. - */ - public function copyCodebase(\Iterator $iterator = NULL, $working_dir = NULL); - -} diff --git a/core/tests/Drupal/BuildTests/QuickStart/InstallTest.php b/core/tests/Drupal/BuildTests/QuickStart/InstallTest.php new file mode 100644 index 0000000000..acd46cdfcd --- /dev/null +++ b/core/tests/Drupal/BuildTests/QuickStart/InstallTest.php @@ -0,0 +1,57 @@ + ['standard'], + 'minimal' => ['minimal'], + 'demo_umami' => ['demo_umami'], + ]; + } + + /** + * @dataProvider providerProfile + * @requires externalCommand composer + */ + public function testInstall($profile) { + // Get the codebase. + $this->copyCodebase(); + + $this->executeCommand('COMPOSER_DISCARD_CHANGES=true composer install --no-dev --no-interaction'); + $this->assertCommandSuccessful(); + // Composer tells you stuff in error output. + $this->assertErrorOutputContains('Generating autoload files'); + $this->installQuickStart($profile); + + // Is the front page generated by Drupal? + $assert = $this->visit()->assertSession(); + $assert->elementExists('css', 'html'); + $this->assertDrupalVisit(); + + // Do we get 404 for a path that does not exist? + $this->visit('/does-not-exist')->assertSession()->statusCodeEquals(404); + + // Can we log in and be an administrator? + $this->visit('/admin'); + $assert->statusCodeEquals(403); + $this->formLogin($this->adminUsername, $this->adminPassword); + $this->visit('/admin'); + $assert->statusCodeEquals(200); + + // Can we try to be an admin even though we logged out? + $this->visit('/user/logout')->assertSession()->statusCodeEquals(200); + $this->visit('/admin'); + $assert->statusCodeEquals(403); + } + +} diff --git a/core/tests/Drupal/BuildTests/QuickStart/QuickStartTestBase.php b/core/tests/Drupal/BuildTests/QuickStart/QuickStartTestBase.php new file mode 100644 index 0000000000..8a65675285 --- /dev/null +++ b/core/tests/Drupal/BuildTests/QuickStart/QuickStartTestBase.php @@ -0,0 +1,66 @@ +executeCommand($finder->find() . ' ./core/scripts/drupal install ' . $profile, $working_dir); + $this->assertCommandSuccessful(); + $this->assertCommandOutputContains('Username:'); + preg_match('/Username: (.+)\vPassword: (.+)/', $process->getOutput(), $matches); + $this->assertNotEmpty($this->adminUsername = $matches[1]); + $this->assertNotEmpty($this->adminPassword = $matches[2]); + } + + /** + * Helper that uses Drupal's user/login form to log in. + * + * @param string $username + * Username. + * @param string $password + * Password. + * @param string $working_dir + * (optional) A working directory within which to login. Defaults to the + * workspace directory. + */ + public function formLogin($username, $password, $working_dir = NULL) { + $mink = $this->visit('/user/login', $working_dir); + $this->assertEquals(200, $mink->getSession()->getStatusCode()); + $assert = $mink->assertSession(); + $assert->fieldExists('edit-name')->setValue($username); + $assert->fieldExists('edit-pass')->setValue($password); + $mink->getSession()->getPage()->findButton('Log in')->submit(); + } + +}