diff --git a/core/drupalci.yml b/core/drupalci.yml
index 2085b9737b..dce85e6e2c 100644
--- a/core/drupalci.yml
+++ b/core/drupalci.yml
@@ -39,6 +39,11 @@ build:
testgroups: '--all'
suppress-deprecations: false
halt-on-fail: false
+ run_tests.build:
+ types: 'PHPUnit-Build'
+ testgroups: '--all'
+ suppress-deprecations: false
+ halt-on-fail: false
run_tests.javascript:
concurrency: 15
types: 'PHPUnit-FunctionalJavascript'
diff --git a/core/lib/Drupal/Component/FileSystem/nbproject/private/phpcsmd.xml b/core/lib/Drupal/Component/FileSystem/nbproject/private/phpcsmd.xml
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/core/tests/Drupal/BuildTests/Dependencies/ComponentDependencyBoundsTest.php b/core/tests/Drupal/BuildTests/Dependencies/ComponentDependencyBoundsTest.php
new file mode 100644
index 0000000000..45e6605401
--- /dev/null
+++ b/core/tests/Drupal/BuildTests/Dependencies/ComponentDependencyBoundsTest.php
@@ -0,0 +1,195 @@
+copyCodebase([], 'drupal');
+
+ // Copy the component tests out of the codebase.
+ $fs->mkdir($this->getWorkspaceDirectory() . '/workspace/core/tests/Drupal/Tests');
+ $fs->mirror(
+ $this->getWorkspaceDirectory() . '/drupal/core/tests/Drupal/Tests/Component',
+ $this->getWorkspaceDirectory() . '/workspace/core/tests/Drupal/Tests'
+ );
+
+ // Copy over the fixture files.
+ // @todo Use the Composer scaffold plugin to do this.
+ $files = [
+ 'bootstrap.php.fixture' => 'bootstrap.php',
+ 'phpunit.xml' => 'phpunit.xml',
+ ];
+ foreach ($files as $source => $destination) {
+ $fs->copy(
+ __DIR__ . '/fixtures/' . $source,
+ $this->getWorkspaceDirectory() . '/workspace/' . $destination,
+ TRUE
+ );
+ }
+ }
+
+ /**
+ * Provider for testBounds().
+ *
+ * Some components are not provided here because they fail. Fixes are
+ * available in https://www.drupal.org/project/drupal/issues/2876669.
+ *
+ * @see https://www.drupal.org/project/drupal/issues/2876669
+ */
+ public function componentsProvider() {
+ return [
+ // ['package/name' , 'Group'],
+ ['drupal/core-assertion', 'Assertion'],
+ ['drupal/core-diff', 'Diff'],
+ ['drupal/core-http-foundation', 'HttpFoundation'],
+ ['drupal/core-proxy-builder', 'ProxyBuilder'],
+ ['drupal/core-render', 'Render'],
+ ];
+ }
+
+ public function allTheComponents() {
+ return [
+ // ['package/name' , 'Group'],
+ ['drupal/core-annotation', 'Annotation'],
+ ['drupal/core-assertion', 'Assertion'],
+ ['drupal/core-bridge', 'Bridge'],
+ ['drupal/core-class-finder', 'ClassFinder'],
+ ['drupal/core-datetime', 'Datetime'],
+ ['drupal/core-dependency-injection', 'DependencyInjection'],
+ ['drupal/core-diff', 'Diff'],
+ ['drupal/core-discovery', 'Discovery'],
+ ['drupal/core-event-dispatcher', 'EventDispatcher'],
+ ['drupal/core-file-cache', 'FileCache'],
+ ['drupal/core-file-system', 'FileSystem'],
+ ['drupal/core-gettext', 'Gettext'],
+ ['drupal/core-graph', 'Graph'],
+ ['drupal/core-http-foundation', 'HttpFoundation'],
+ ['drupal/core-php-storage', 'PhpStorage'],
+ ['drupal/core-plugin', 'Plugin'],
+ ['drupal/core-proxy-builder', 'ProxyBuilder'],
+ ['drupal/core-render', 'Render'],
+ ['drupal/core-serialization', 'Serialization'],
+ ['drupal/core-transliteration', 'Transliteration'],
+ ['drupal/core-utility' , 'Utility'],
+ ['drupal/core-uuid', 'Uuid'],
+ ];
+ }
+
+ /**
+ * @dataProvider componentsProvider
+ */
+ public function testBounds($package, $group) {
+ $fs = new Filesystem();
+ $path = 'core/lib/Drupal/Component/' . $group;
+
+ // Move the component to the workspace.
+ $fs->mirror(
+ $this->getWorkspaceDirectory() . '/drupal/' . $path,
+ $this->getWorkspaceDirectory() . '/workspace/component'
+ );
+
+ // Write out a composer.json to the workspace.
+ file_put_contents(
+ $this->getWorkspaceDirectory() . '/workspace/composer.json',
+ json_encode(
+ $this->buildComposerPackageArray($package, $group, 'component'),
+ JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
+ )
+ );
+
+ // 'Priming' required because we use wikimedia merge plugin.
+ $this->assertCommand('composer install --no-progress --no-suggest --prefer-dist', 'workspace');
+
+ // Run the tests after updating to both the highest and lowest possible
+ // dependency constraint.
+ foreach ([' ', '--prefer-lowest'] as $argument) {
+ // Perform an update.
+ $this->assertCommand('composer update --no-progress --no-suggest --prefer-dist ' . $argument, 'workspace');
+ $this->assertCommand('composer run-script drupal-phpunit-upgrade', 'workspace');
+ // Run the tests.
+ $this->assertCommand('./vendor/bin/phpunit core/tests/Drupal/Tests/' . $group, 'workspace');
+ }
+ }
+
+ public function buildComposerPackageArray($component_package, $group, $path) {
+ $package = [
+ 'name' => 'drupal_component/test_package',
+ // @todo: Use VCS tags for versioning.
+ 'version' => '8.5.0',
+ 'description' => 'Dummy package for the component.',
+ 'license' => 'GPL-2.0-or-later',
+ 'minimum-stability' => 'dev',
+ 'prefer-stable' => TRUE,
+ 'require' => [
+ 'phpunit/phpunit' => '^4.8.35 || ^6.1',
+ 'wikimedia/composer-merge-plugin' => '^1.4',
+ ],
+ 'replace' => [
+ $component_package => 'self.version',
+ ],
+ 'extra' => [
+ 'merge-plugin' => [
+ 'require' => [
+ "$path/composer.json",
+ ],
+ 'ignore-duplicates' => FALSE,
+ 'merge-dev' => TRUE,
+ 'merge-extra' => FALSE,
+ 'merge-scripts' => FALSE,
+ 'recurse' => FALSE,
+ 'replace' => FALSE,
+ ],
+ ],
+ 'autoload-dev' => [
+ 'psr-4' => [
+ "DrupalComponentTester\\" => 'src',
+ "Drupal\\Tests\\Component\\$group\\" => "core/tests/Drupal/Tests/Component/$group",
+ ],
+ ],
+ 'repositories' => [
+ [
+ 'type' => 'composer',
+ 'url' => 'https://packages.drupal.org/8',
+ ],
+ ],
+ 'scripts' => [
+ 'drupal-phpunit-upgrade' => '@composer update phpunit/phpunit --with-dependencies --no-progress',
+ ],
+ ];
+
+ // Set up the repositories so that we can require existing components.
+ $repositories = [];
+ $components_directory = $this->getWorkspaceDirectory() . '/drupal/core/lib/Component';
+ $packages = $this->allTheComponents();
+ foreach ($packages as $file_package) {
+ $repositories[] = [
+ 'type' => 'path',
+ 'url' => $components_directory . '/' . $file_package[1],
+ ];
+ }
+
+ $package['repositories'] = array_merge($package['repositories'], $repositories);
+
+ return $package;
+ }
+
+}
diff --git a/core/tests/Drupal/BuildTests/Dependencies/CoreDependencyRegressionTest.php b/core/tests/Drupal/BuildTests/Dependencies/CoreDependencyRegressionTest.php
new file mode 100644
index 0000000000..a01f38d845
--- /dev/null
+++ b/core/tests/Drupal/BuildTests/Dependencies/CoreDependencyRegressionTest.php
@@ -0,0 +1,53 @@
+copyCodebase();
+ // Install using composer. We have to do this because subsequent update
+ // commands will not work if we haven't already installed the merge plugin.
+ $this->assertCommandErrorOutputContains(
+ 'Generating autoload files', 'composer install --no-interaction'
+ );
+ // Remove drupal/coder. It's usually been modified in some way, so we use
+ // the filesystem to remove it. Otherwise Composer will balk.
+ $fs->remove($this->getWorkspaceDirectory() . '/vendor/drupal/coder');
+
+ // Downgrade to minimum versions.
+ $this->assertCommandErrorOutputContains('Generating autoload files', 'composer update --prefer-lowest --no-interaction');
+ // Perform the PHPUnit upgrade.
+ $this->assertCommand('composer run-script drupal-phpunit-upgrade');
+ // Run unit tests.
+ $this->assertCommand('./vendor/bin/phpunit -c core/ --testsuite unit');
+
+ // Remove drupal/coder again.
+ $fs->remove($this->getWorkspaceDirectory() . '/vendor/drupal/coder');
+ // Update to maximum versions.
+ $this->assertCommandErrorOutputContains('Generating autoload files', 'composer update --no-interaction');
+ // Run unit tests.
+ $this->assertCommand('./vendor/bin/phpunit -c core/ --testsuite unit');
+ }
+
+}
diff --git a/core/tests/Drupal/BuildTests/Dependencies/fixtures/bootstrap.php.fixture b/core/tests/Drupal/BuildTests/Dependencies/fixtures/bootstrap.php.fixture
new file mode 100644
index 0000000000..fe2243afef
--- /dev/null
+++ b/core/tests/Drupal/BuildTests/Dependencies/fixtures/bootstrap.php.fixture
@@ -0,0 +1,6 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/core/tests/Drupal/BuildTests/Framework/BuildTestBase.php b/core/tests/Drupal/BuildTests/Framework/BuildTestBase.php
new file mode 100644
index 0000000000..d7c93bc492
--- /dev/null
+++ b/core/tests/Drupal/BuildTests/Framework/BuildTestBase.php
@@ -0,0 +1,346 @@
+phpFinder = new PhpExecutableFinder();
+ // Set up the workspace directory.
+ // @todo Glean working directory from env vars, etc.
+ $this->workspaceDir = implode(DIRECTORY_SEPARATOR, [
+ sys_get_temp_dir(),
+ 'build_workspace_' . md5($this->getName() . microtime()),
+ ]);
+ $fs = new Filesystem();
+ $fs->mkdir($this->workspaceDir);
+ $this->assertFileExists($this->workspaceDir);
+ $this->initMink();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function tearDown() {
+ parent::tearDown();
+
+ $fs = new Filesystem();
+ $ws = $this->getWorkspaceDirectory();
+ if ($fs->exists($ws)) {
+ $fs->chmod($ws, 0775, 0000, TRUE);
+ $fs->remove($ws);
+ }
+
+ $this->stopServer();
+ }
+
+ protected function initMink() {
+ $client = new DrupalMinkClient();
+ $client->followMetaRefresh(TRUE);
+ $driver = new GoutteDriver($client);
+ $session = new Session($driver);
+ $this->mink = new Mink();
+ $this->mink->registerSession('default', $session);
+ $this->mink->setDefaultSessionName('default');
+ $session->start();
+ return $session;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getWorkspaceDirectory() {
+ return $this->workspaceDir;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function assertCommandErrorOutputContains($expected, $command_line, $working_dir = NULL) {
+ $process = $this->assertCommand($command_line, $working_dir);
+ $this->assertContains($expected, $process->getErrorOutput());
+ return $process;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function assertCommandOutputContains($expected, $command_line, $working_dir = NULL) {
+ $process = $this->assertCommand($command_line, $working_dir);
+ $this->assertContains($expected, $process->getOutput());
+ return $process;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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 exit code: ' . $process->getExitCode() . ' Message: ' . $ex->getMessage());
+ }
+ $this->assertEquals(0, $process->getExitCode(),
+ 'COMMAND: ' . $command_line . "\n" .
+ 'OUTPUT: ' . $process->getOutput() . "\n" .
+ 'ERROR: ' . $process->getErrorOutput() . "\n"
+ );
+ return $process;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function executeCommand($command_line, $working_dir = NULL) {
+ $working_path = implode('/', [$this->workspaceDir, $working_dir]);
+ $process = new Process($command_line);
+ $process->setWorkingDirectory($working_path)
+ ->setTimeout(300)
+ ->setIdleTimeout(300);
+ // Synchronously run the process.
+ $process->run();
+ return $process;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function assertRequest($request_uri, $expected_status_code = 200, $docroot = NULL) {
+ $this->visit($request_uri, $docroot);
+ $this->mink->assertSession()->statusCodeEquals($expected_status_code);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function visit($request_uri, $docroot = NULL) {
+ // Try to make a server.
+ $this->standUpServer($docroot);
+ $this->assertTrue($this->serverProcess->isRunning(), 'PHP HTTP server not running.');
+
+ $request = 'http://localhost:' . $this->getPortNumber() . '/' . $request_uri;
+ $this->mink->getSession()->visit($request);
+ }
+
+ /**
+ * Make a local test server using PHP's internal HTTP server.
+ *
+ * @param string|null $docroot
+ * (optional) Server docroot relative to the workspace filesystem. Defaults
+ * to the workspace directory.
+ */
+ protected function standUpServer($docroot = 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)) {
+ $this->stopServer();
+ }
+ // If there's not a server at this point, make one.
+ if (empty($this->serverProcess)) {
+ $this->serverProcess = $this->instantiateServer(static::$hostName, $this->getPortNumber(), $docroot);
+ if (!empty($this->serverProcess)) {
+ $this->serverDocroot = $docroot;
+ }
+ }
+ }
+
+ /**
+ * Do the work of making a server process.
+ *
+ * @param string $hostname
+ * The hostname for the server.
+ * @param int $port
+ * The port number for the server.
+ * @param string|null $docroot
+ * Server docroot relative to the workspace filesystem. Defaults to the
+ * workspace directory.
+ * @param int $sleep_time
+ * (optional) The time to sleep in seconds after creating the process. This
+ * allows the server to come to life. Might also be voodoo. Defaults to 10.
+ *
+ * @return \Symfony\Component\Process\Process
+ * The server process.
+ */
+ protected function instantiateServer($hostname, $port, $docroot, $sleep_time = 10) {
+ // Use implode because $docroot can be NULL.
+ $full_docroot = implode('/', [$this->getWorkspaceDirectory(), $docroot]);
+ $server = [
+ $this->phpFinder->find(),
+ '-S',
+ $hostname . ':' . $port,
+ '-t',
+ $full_docroot,
+ ];
+ $ps = new Process(implode(' ', $server), $full_docroot);
+ $ps->setIdleTimeout(30)
+ ->setTimeout(30)
+ ->start();
+ // @todo Tweak the sleep, or find a better way to wait for this process
+ // to fully start up.
+ sleep($sleep_time);
+ return $ps;
+ }
+
+ /**
+ * Stop the HTTP server, zero out all necessary variables.
+ */
+ protected function stopServer() {
+ if (!empty($this->serverProcess)) {
+ $this->serverProcess->stop();
+ }
+ $this->serverProcess = NULL;
+ $this->serverDocroot = NULL;
+ $this->hostPort = NULL;
+ $this->initMink();
+ }
+
+ /**
+ * Discover an available port number.
+ *
+ * @param int $min
+ * The lowest port number to start with.
+ * @param int $max
+ * The highest port number to allow.
+ *
+ * @return int
+ * The available port number that we discovered.
+ *
+ * @throws \RuntimeException
+ * Thrown when there are no available ports within the range.
+ */
+ protected static function findBestPort($min = 8000, $max = 8100) {
+ $port = $min;
+ // If we can open the port, then it's unavailable to us.
+ while (($fp = @fsockopen(static::$hostName, $port, $errno, $errstr, 1)) !== FALSE) {
+ fclose($fp);
+ if (++$port > $max) {
+ throw new \RuntimeException('Unable to find a port available to run the web server.');
+ }
+ }
+ return $port;
+ }
+
+ /**
+ * Get the port number for requests.
+ *
+ * Test should never call this. Used by standUpServer().
+ *
+ * @return int
+ */
+ protected function getPortNumber() {
+ if (empty($this->hostPort)) {
+ $this->hostPort = static::findBestPort();
+ }
+ return $this->hostPort;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function copyCodebase($options = [], $working_dir = NULL) {
+ $options = array_merge(['override' => TRUE, 'delete' => TRUE], $options);
+ $full_working = implode('/', [$this->getWorkspaceDirectory(), $working_dir]);
+
+ $fs = new Filesystem();
+ $fs->mirror($this->getDrupalRoot(), $full_working, NULL, $options);
+ }
+
+ protected function getDrupalRoot() {
+ return realpath(dirname(__DIR__, 5));
+ }
+
+}
diff --git a/core/tests/Drupal/BuildTests/Framework/BuildTestInterface.php b/core/tests/Drupal/BuildTests/Framework/BuildTestInterface.php
new file mode 100644
index 0000000000..e358330879
--- /dev/null
+++ b/core/tests/Drupal/BuildTests/Framework/BuildTestInterface.php
@@ -0,0 +1,172 @@
+mink to get session and assertion objects to test the result
+ * of this method.
+ *
+ * @param string $request_uri
+ * The non-host part of the URL to request. Example: some/path?foo=bar.
+ * @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 assertRequest($request_uri, $expected_status_code = 200, $docroot = NULL);
+
+ /**
+ * 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->mink to get session and assertion objects to test the outcome
+ * of this method.
+ *
+ * @param string $request_uri
+ * The non-host part of the URL to request. Example: some/path?foo=bar.
+ * @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 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.
+ *
+ * To specify a working directory but keep default options, pass an empty
+ * array for options.
+ *
+ * @param bool[] $options
+ * (optional) Array of file handling options. Pass in an empty array for
+ * defaults. Valid options are:
+ * - $options['override'] If true, target files newer than origin files are
+ * overwritten. Defaults to TRUE.
+ * - $options['delete'] Whether to delete files that are not in the source
+ * directory. Defaults to TRUE.
+ * @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($options = [], $working_dir = NULL);
+
+}
diff --git a/core/tests/Drupal/BuildTests/Framework/DrupalMinkClient.php b/core/tests/Drupal/BuildTests/Framework/DrupalMinkClient.php
new file mode 100644
index 0000000000..527349766f
--- /dev/null
+++ b/core/tests/Drupal/BuildTests/Framework/DrupalMinkClient.php
@@ -0,0 +1,65 @@
+followMetaRefresh = $followMetaRefresh;
+ }
+
+ /**
+ * Glean the meta refresh URL from the current page content.
+ *
+ * @return string|null
+ * Either the redirect URL that was found, or NULL if none was found.
+ */
+ private function getMetaRefreshUrl() {
+ $metaRefresh = $this->getCrawler()->filter('meta[http-equiv="Refresh"], meta[http-equiv="refresh"]');
+ foreach ($metaRefresh->extract(array('content')) as $content) {
+ if (preg_match('/^\s*0\s*;\s*URL\s*=\s*(?|\'([^\']++)|"([^"]++)|([^\'"].*))/i', $content, $m)) {
+ return str_replace("\t\r\n", '', rtrim($m[1]));
+ }
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function request($method, $uri, array $parameters = array(), array $files = array(), array $server = array(), $content = null, $changeHistory = true) {
+ $this->crawler = parent::request($method, $uri, $parameters, $files, $server, $content, $changeHistory);
+ // Check for meta refresh redirect and follow it.
+ if ($this->followMetaRefresh && null !== $redirect = $this->getMetaRefreshUrl()) {
+ $this->redirect = $redirect;
+ $this->redirects[serialize($this->history->current())] = true;
+ $this->crawler = $this->followRedirect();
+ }
+ return $this->crawler;
+ }
+
+}
diff --git a/core/tests/Drupal/BuildTests/Framework/Tests/BuildTestTest.php b/core/tests/Drupal/BuildTests/Framework/Tests/BuildTestTest.php
new file mode 100644
index 0000000000..c61c886bd9
--- /dev/null
+++ b/core/tests/Drupal/BuildTests/Framework/Tests/BuildTestTest.php
@@ -0,0 +1,82 @@
+executeCommand('test -f composer.json');
+ $this->assertEquals(1, $process->getExitCode());
+
+ // We could assertCommand() and build our workspace, but we'll use code
+ // here, so that this test doesn't only run on *nix.
+ $workspace = $this->getWorkspaceDirectory();
+ $test_directory = 'test_directory';
+ $working_dir = $workspace . '/' . $test_directory;
+
+ $fs = new Filesystem();
+ $fs->mkdir($working_dir);
+ $this->assertFileExists($working_dir);
+
+ // Load up a process with our expecations. assertCommand() would fail a call
+ // with an empty command line, but executeCommand() does not.
+ $process = $this->executeCommand('', $test_directory);
+
+ $working_dir = $process->getWorkingDirectory();
+ $this->assertNotEquals($test_directory, $working_dir);
+ $this->assertEquals($test_directory, basename($working_dir));
+ }
+
+ public function testCopyCodebase() {
+ $test_directory = 'copied_codebase';
+ $this->copyCodebase([], $test_directory);
+ $full_path = $this->getWorkspaceDirectory() . '/' . $test_directory;
+ $files = [
+ 'autoload.php',
+ 'composer.json',
+ 'index.php',
+ 'README.txt',
+ ];
+ foreach ($files as $file) {
+ $this->assertFileExists($full_path . '/' . $file);
+ }
+ }
+
+ public function testPort() {
+ // We should find the same port twice in a row, since we don't use it.
+ $this->assertEquals(static::findBestPort(), static::findBestPort());
+
+ // Grab a port, make a server, make sure the next port we grab is not the
+ // same.
+ $port = static::findBestPort();
+ $process = $this->instantiateServer(static::$hostName, $port, NULL);
+ $this->assertNotEquals($port, static::findBestPort());
+ }
+
+ public function testPortMany() {
+ $processes = [];
+ foreach(range(1,15) as $instance) {
+ $port = static::findBestPort();
+ $this->assertArrayNotHasKey($port, $processes, 'Port ' . $port . ' was already in use by a process.');
+ $this->assertNotEmpty(
+ $processes[$port] = $this->instantiateServer(static::$hostName, $port, NULL, 1)
+ );
+ }
+ foreach ($processes as $port => $process) {
+ $this->assertTrue($process->isRunning(), 'Process on port ' . $port . ' is not still running.');
+ }
+ }
+
+}
diff --git a/core/tests/Drupal/BuildTests/Framework/Tests/DrupalMinkClientTest.php b/core/tests/Drupal/BuildTests/Framework/Tests/DrupalMinkClientTest.php
new file mode 100644
index 0000000000..1251185012
--- /dev/null
+++ b/core/tests/Drupal/BuildTests/Framework/Tests/DrupalMinkClientTest.php
@@ -0,0 +1,105 @@
+followMetaRefresh($followMetaRefresh);
+ $client->setNextResponse(new Response($content));
+ $client->request('GET', 'http://www.example.com/foo/foobar');
+ $this->assertEquals($expectedEndingUrl, $client->getRequest()->getUri());
+ }
+
+ public function getTestsForMetaRefresh() {
+ return [
+ ['
', 'http://www.example.com/redirected'],
+ ['', 'http://www.example.com/redirected'],
+ ['', 'http://www.example.com/redirected'],
+ ['', 'http://www.example.com/redirected'],
+ ['', 'http://www.example.com/redirected'],
+ ['', 'http://www.example.com/redirected'],
+ ['', 'http://www.example.com/redirected'],
+ ['