diff --git a/composer.lock b/composer.lock
index a934df60e4..97d48d4f1f 100644
--- a/composer.lock
+++ b/composer.lock
@@ -2060,7 +2060,7 @@
},
{
"name": "Gert de Pagter",
- "email": "BackEndTea@gmail.com"
+ "email": "backendtea@gmail.com"
}
],
"description": "Symfony polyfill for ctype functions",
@@ -4879,6 +4879,105 @@
"homepage": "https://symfony.com",
"time": "2019-02-23T15:06:07+00:00"
},
+ {
+ "name": "symfony/filesystem",
+ "version": "v3.4.27",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/filesystem.git",
+ "reference": "acf99758b1df8e9295e6b85aa69f294565c9fedb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/acf99758b1df8e9295e6b85aa69f294565c9fedb",
+ "reference": "acf99758b1df8e9295e6b85aa69f294565c9fedb",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5.9|>=7.0.8",
+ "symfony/polyfill-ctype": "~1.8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Filesystem\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Filesystem Component",
+ "homepage": "https://symfony.com",
+ "time": "2019-02-04T21:34:32+00:00"
+ },
+ {
+ "name": "symfony/finder",
+ "version": "v3.4.27",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/finder.git",
+ "reference": "61af5ce0b34b942d414fe8f1b11950d0e9a90e98"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/61af5ce0b34b942d414fe8f1b11950d0e9a90e98",
+ "reference": "61af5ce0b34b942d414fe8f1b11950d0e9a90e98",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5.9|>=7.0.8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Finder\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Finder Component",
+ "homepage": "https://symfony.com",
+ "time": "2019-04-02T19:54:57+00:00"
+ },
{
"name": "symfony/phpunit-bridge",
"version": "v3.4.26",
diff --git a/core/composer.json b/core/composer.json
index 810ec27e59..9adb92b919 100644
--- a/core/composer.json
+++ b/core/composer.json
@@ -63,6 +63,8 @@
"phpunit/phpunit": "^4.8.35 || ^6.5",
"phpspec/prophecy": "^1.7",
"symfony/css-selector": "^3.4.0",
+ "symfony/filesystem": "^3.4",
+ "symfony/finder": "~3.4",
"symfony/phpunit-bridge": "^3.4.3",
"symfony/debug": "^3.4.0",
"justinrainbow/json-schema": "^5.2"
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/modules/simpletest/src/Form/SimpletestResultsForm.php b/core/modules/simpletest/src/Form/SimpletestResultsForm.php
index a2241571eb..b034288986 100644
--- a/core/modules/simpletest/src/Form/SimpletestResultsForm.php
+++ b/core/modules/simpletest/src/Form/SimpletestResultsForm.php
@@ -93,6 +93,7 @@ protected static function buildStatusImageMap() {
'pass' => $image_pass,
'fail' => $image_fail,
'exception' => $image_exception,
+ 'error' => $image_exception,
'debug' => $image_debug,
];
}
@@ -278,6 +279,7 @@ public static function addResultForm(array &$form, array $results) {
'#pass' => 0,
'#fail' => 0,
'#exception' => 0,
+ '#error' => 0,
'#debug' => 0,
];
diff --git a/core/modules/simpletest/src/TestDiscovery.php b/core/modules/simpletest/src/TestDiscovery.php
index a9cbe15db7..9e11dd9c52 100644
--- a/core/modules/simpletest/src/TestDiscovery.php
+++ b/core/modules/simpletest/src/TestDiscovery.php
@@ -92,6 +92,7 @@ public function registerTestNamespaces() {
// Add PHPUnit test namespaces of Drupal core.
$this->testNamespaces['Drupal\\Tests\\'] = [$this->root . '/core/tests/Drupal/Tests'];
+ $this->testNamespaces['Drupal\\BuildTests\\'] = [$this->root . '/core/tests/Drupal/BuildTests'];
$this->testNamespaces['Drupal\\KernelTests\\'] = [$this->root . '/core/tests/Drupal/KernelTests'];
$this->testNamespaces['Drupal\\FunctionalTests\\'] = [$this->root . '/core/tests/Drupal/FunctionalTests'];
$this->testNamespaces['Drupal\\FunctionalJavascriptTests\\'] = [$this->root . '/core/tests/Drupal/FunctionalJavascriptTests'];
diff --git a/core/phpunit.xml.dist b/core/phpunit.xml.dist
index 4f81a2d533..b63985b663 100644
--- a/core/phpunit.xml.dist
+++ b/core/phpunit.xml.dist
@@ -48,6 +48,9 @@
./tests/TestSuites/FunctionalJavascriptTestSuite.php
+
+ ./tests/TestSuites/BuildTestSuite.php
+
diff --git a/core/tests/Drupal/BuildTests/Framework/BuildTestBase.php b/core/tests/Drupal/BuildTests/Framework/BuildTestBase.php
new file mode 100644
index 0000000000..ab422463c5
--- /dev/null
+++ b/core/tests/Drupal/BuildTests/Framework/BuildTestBase.php
@@ -0,0 +1,375 @@
+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) {
+ // @todo Determine if there's a use case for options other than the defaults.
+ $options = array_merge([
+ 'override' => TRUE,
+ 'delete' => FALSE,
+ ], $options);
+ // Use implode() because $working_dir can be NULL.
+ $full_working = implode('/', [$this->getWorkspaceDirectory(), $working_dir]);
+
+ // Filesystem::mirror() uses the iterator differently if we set the delete
+ // option, so we have to handle that special case. See
+ // https://github.com/symfony/symfony/issues/14068
+ // @todo Figure out how to manage this so that we can provide a mirrored
+ // codebase without the paths we want to exclude, while also deleting the
+ // extra files.
+ $iterator = NULL;
+ if (!$options['delete']) {
+ $finder = new Finder();
+ $finder->files()
+ ->in($this->getDrupalRoot())
+ ->exclude([
+ 'sites/default/files',
+ 'sites/default/settings.php',
+ 'sites/simpletest',
+ 'vendor',
+ ]);
+ $finder->ignoreDotFiles(FALSE);
+ $iterator = $finder->getIterator();
+ }
+
+ $fs = new Filesystem();
+ $fs->mirror($this->getDrupalRoot(), $full_working, $iterator, $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..420a156b93
--- /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.
+ *
+ * The copy will exclude sites/default/settings.php, sites/default/files, and
+ * vendor/.
+ *
+ * @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 FALSE.
+ * @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/ExternalCommandRequirementsTrait.php b/core/tests/Drupal/BuildTests/Framework/ExternalCommandRequirementsTrait.php
new file mode 100644
index 0000000000..b9d9eafbc2
--- /dev/null
+++ b/core/tests/Drupal/BuildTests/Framework/ExternalCommandRequirementsTrait.php
@@ -0,0 +1,96 @@
+getAnnotations();
+ if (!empty($annotations['class']['requires'])) {
+ $this->checkExternalCommandRequirements($annotations['class']['requires']);
+ }
+ if (!empty($annotations['method']['requires'])) {
+ $this->checkExternalCommandRequirements($annotations['method']['requires']);
+ }
+ }
+
+ /**
+ * Checks missing external command requirements.
+ *
+ * @param string[] $annotations
+ * A list of requires annotations from either a method or class annotation.
+ *
+ * @throws \PHPUnit_Framework_SkippedTestError
+ * Thrown when the requirements are not met, and this test should be
+ * skipped. Callers should not catch this exception.
+ */
+ private function checkExternalCommandRequirements(array $annotations) {
+ // Make a list of required commands.
+ $required_commands = [];
+ foreach ($annotations as $requirement) {
+ if (strpos($requirement, 'externalCommand ') === 0) {
+ $command = trim(str_replace('externalCommand ', '', $requirement));
+ $required_commands[$command] = $command;
+ }
+ }
+
+ // Figure out which commands are not available.
+ $unavailable = [];
+ foreach (array_keys($required_commands) as $required_command) {
+ if (!in_array($required_command, static::$existingCommands)) {
+ if ($this->externalCommandIsAvailable($required_command)) {
+ // Cache existing commands so we don't have to ask again.
+ static::$existingCommands[] = $required_command;
+ }
+ else {
+ $unavailable[] = $required_command;
+ }
+ }
+ }
+
+ // Skip the test if there were some we couldn't find.
+ if (!empty($unavailable)) {
+ throw new \PHPUnit_Framework_SkippedTestError('Required external commands: ' . implode(', ', $unavailable));
+ }
+ }
+
+ private function externalCommandIsAvailable($command) {
+ // @todo Figure out the Windows version of this.
+ $process = new Process('which ' . $command);
+ $process->run();
+ return $process->getExitCode() == 0;
+ }
+
+}
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'],
+ ['