Update core 8.3.0

This commit is contained in:
Rob Davies 2017-04-13 15:53:35 +01:00
parent da7a7918f8
commit cd7a898e66
6144 changed files with 132297 additions and 87747 deletions

View file

@ -5,25 +5,22 @@ namespace Drupal\Tests;
use Behat\Mink\Driver\GoutteDriver;
use Behat\Mink\Element\Element;
use Behat\Mink\Mink;
use Behat\Mink\Selector\SelectorsHandler;
use Behat\Mink\Session;
use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\Testing\ConfigSchemaChecker;
use Drupal\Core\Database\Database;
use Drupal\Core\DrupalKernel;
use Drupal\Core\Serialization\Yaml;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\Core\Session\UserSession;
use Drupal\Core\Site\Settings;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\Core\Test\FunctionalTestSetupTrait;
use Drupal\Core\Test\TestRunnerKernel;
use Drupal\Core\Test\TestSetupTrait;
use Drupal\Core\Url;
use Drupal\Core\Test\TestDatabase;
use Drupal\Core\Utility\Error;
use Drupal\FunctionalTests\AssertLegacyTrait;
use Drupal\simpletest\AssertHelperTrait;
use Drupal\simpletest\ContentTypeCreationTrait;
@ -32,6 +29,8 @@ use Drupal\simpletest\NodeCreationTrait;
use Drupal\simpletest\UserCreationTrait;
use Symfony\Component\CssSelector\CssSelectorConverter;
use Symfony\Component\HttpFoundation\Request;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Provides a test case for functional Drupal tests.
@ -43,6 +42,9 @@ use Symfony\Component\HttpFoundation\Request;
* @ingroup testing
*/
abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
use FunctionalTestSetupTrait;
use TestSetupTrait;
use AssertHelperTrait;
use BlockCreationTrait {
placeBlock as drupalPlaceBlock;
@ -62,20 +64,7 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
createRole as drupalCreateRole;
createUser as drupalCreateUser;
}
/**
* Class loader.
*
* @var object
*/
protected $classLoader;
/**
* The site directory of this test run.
*
* @var string
*/
protected $siteDirectory;
use XdebugRequestTrait;
/**
* The database prefix of this test run.
@ -84,13 +73,6 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
*/
protected $databasePrefix;
/**
* The site directory of the original parent site.
*
* @var string
*/
protected $originalSiteDirectory;
/**
* Time limit in seconds for the test.
*
@ -98,37 +80,6 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
*/
protected $timeLimit = 500;
/**
* The public file directory for the test environment.
*
* This is set in BrowserTestBase::prepareEnvironment().
*
* @var string
*/
protected $publicFilesDirectory;
/**
* The private file directory for the test environment.
*
* This is set in BrowserTestBase::prepareEnvironment().
*
* @var string
*/
protected $privateFilesDirectory;
/**
* The temp file directory for the test environment.
*
* This is set in BrowserTestBase::prepareEnvironment(). This value has to
* match the temporary directory created in install_base_system() for test
* installs.
*
* @see install_base_system()
*
* @var string
*/
protected $tempFilesDirectory;
/**
* The translation file directory for the test environment.
*
@ -138,20 +89,6 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
*/
protected $translationFilesDirectory;
/**
* The DrupalKernel instance used in the test.
*
* @var \Drupal\Core\DrupalKernel
*/
protected $kernel;
/**
* The dependency injection container used in the test.
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
/**
* The config importer that can be used in a test.
*
@ -159,15 +96,6 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
*/
protected $configImporter;
/**
* Set to TRUE to strict check all configuration saved.
*
* @see \Drupal\Core\Config\Testing\ConfigSchemaChecker
*
* @var bool
*/
protected $strictConfigSchema = TRUE;
/**
* Modules to enable.
*
@ -181,22 +109,6 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
*/
protected static $modules = [];
/**
* An array of config object names that are excluded from schema checking.
*
* @var string[]
*/
protected static $configSchemaCheckerExclusions = array(
// Following are used to test lack of or partial schema. Where partial
// schema is provided, that is explicitly tested in specific tests.
'config_schema_test.noschema',
'config_schema_test.someschema',
'config_schema_test.schema_data_types',
'config_schema_test.no_schema_data_types',
// Used to test application of schema to filtering of configuration.
'config_test.dynamic.system',
);
/**
* The profile to install as a basis for testing.
*
@ -211,20 +123,6 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
*/
protected $loggedInUser = FALSE;
/**
* The root user.
*
* @var \Drupal\Core\Session\UserSession
*/
protected $rootUser;
/**
* The config directories used in this test.
*
* @var array
*/
protected $configDirectories = array();
/**
* An array of custom translations suitable for drupal_rewrite_settings().
*
@ -356,13 +254,26 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
if ($driver instanceof GoutteDriver) {
// Turn off curl timeout. Having a timeout is not a problem in a normal
// test running, but it is a problem when debugging.
// test running, but it is a problem when debugging. Also, disable SSL
// peer verification so that testing under HTTPS always works.
/** @var \GuzzleHttp\Client $client */
$client = $this->container->get('http_client_factory')->fromOptions(['timeout' => NULL]);
$client = $this->container->get('http_client_factory')->fromOptions([
'timeout' => NULL,
'verify' => FALSE,
]);
// Inject a Guzzle middleware to generate debug output for every request
// performed in the test.
$handler_stack = $client->getConfig('handler');
$handler_stack->push($this->getResponseLogHandler());
$driver->getClient()->setClient($client);
}
$session = new Session($driver);
$selectors_handler = new SelectorsHandler([
'hidden_field_selector' => new HiddenFieldSelector()
]);
$session = new Session($driver, $selectors_handler);
$this->mink = new Mink();
$this->mink->registerSession('default', $session);
$this->mink->setDefaultSessionName('default');
@ -418,6 +329,43 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
return $driver;
}
/**
* Provides a Guzzle middleware handler to log every response received.
*
* @return callable
* The callable handler that will do the logging.
*/
protected function getResponseLogHandler() {
return function (callable $handler) {
return function (RequestInterface $request, array $options) use ($handler) {
return $handler($request, $options)
->then(function (ResponseInterface $response) use ($request) {
if ($this->htmlOutputEnabled) {
$caller = $this->getTestMethodCaller();
$html_output = 'Called from ' . $caller['function'] . ' line ' . $caller['line'];
$html_output .= '<hr />' . $request->getMethod() . ' request to: ' . $request->getUri();
// On redirect responses (status code starting with '3') we need
// to remove the meta tag that would do a browser refresh. We
// don't want to redirect developers away when they look at the
// debug output file in their browser.
$body = $response->getBody();
$status_code = (string) $response->getStatusCode();
if ($status_code[0] === '3') {
$body = preg_replace('#<meta http-equiv="refresh" content=.+/>#', '', $body, 1);
}
$html_output .= '<hr />' . $body;
$html_output .= $this->formatHtmlOutputHeaders($response->getHeaders());
$this->htmlOutput($html_output);
}
return $response;
});
};
};
}
/**
* Registers additional Mink sessions.
*
@ -482,29 +430,10 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
// Setup Mink.
$session = $this->initMink();
// In order to debug web tests you need to either set a cookie, have the
// Xdebug session in the URL or set an environment variable in case of CLI
// requests. If the developer listens to connection when running tests, by
// default the cookie is not forwarded to the client side, so you cannot
// debug the code running on the test site. In order to make debuggers work
// this bit of information is forwarded. Make sure that the debugger listens
// to at least three external connections.
$request = \Drupal::request();
$cookie_params = $request->cookies;
if ($cookie_params->has('XDEBUG_SESSION')) {
$session->setCookie('XDEBUG_SESSION', $cookie_params->get('XDEBUG_SESSION'));
}
// For CLI requests, the information is stored in $_SERVER.
$server = $request->server;
if ($server->has('XDEBUG_CONFIG')) {
// $_SERVER['XDEBUG_CONFIG'] has the form "key1=value1 key2=value2 ...".
$pairs = explode(' ', $server->get('XDEBUG_CONFIG'));
foreach ($pairs as $pair) {
list($key, $value) = explode('=', $pair);
// Account for key-value pairs being separated by multiple spaces.
if (trim($key) == 'idekey') {
$session->setCookie('XDEBUG_SESSION', trim($value));
}
$cookies = $this->extractCookiesFromRequest(\Drupal::request());
foreach ($cookies as $cookie_name => $values) {
foreach ($values as $value) {
$session->setCookie($cookie_name, $value);
}
}
@ -564,7 +493,7 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
}
// Delete test site directory.
file_unmanaged_delete_recursive($this->siteDirectory, array($this, 'filePreDeleteCallback'));
file_unmanaged_delete_recursive($this->siteDirectory, [$this, 'filePreDeleteCallback']);
}
/**
@ -643,7 +572,7 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
* @return string
* An absolute URL stsring.
*/
protected function buildUrl($path, array $options = array()) {
protected function buildUrl($path, array $options = []) {
if ($path instanceof Url) {
$url_options = $path->getOptions();
$options = $url_options + $options;
@ -691,7 +620,7 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
* @return string
* The retrieved HTML string, also available as $this->getRawContent()
*/
protected function drupalGet($path, array $options = array(), array $headers = array()) {
protected function drupalGet($path, array $options = [], array $headers = []) {
$options['absolute'] = TRUE;
$url = $this->buildUrl($path, $options);
@ -708,7 +637,9 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
// Ensure that any changes to variables in the other thread are picked up.
$this->refreshVariables();
if ($this->htmlOutputEnabled) {
// Log only for JavascriptTestBase tests because for Goutte we log with
// ::getResponseLogHandler.
if ($this->htmlOutputEnabled && !($this->getSession()->getDriver() instanceof GoutteDriver)) {
$html_output = 'GET request to: ' . $url .
'<hr />Ending URL: ' . $this->getSession()->getCurrentUrl();
$html_output .= '<hr />' . $out;
@ -782,16 +713,16 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
$this->drupalLogout();
}
$this->drupalGet('user');
$this->drupalGet('user/login');
$this->assertSession()->statusCodeEquals(200);
$this->submitForm(array(
$this->submitForm([
'name' => $account->getUsername(),
'pass' => $account->passRaw,
), t('Log in'));
], t('Log in'));
// @see BrowserTestBase::drupalUserIsLoggedIn()
$account->sessionId = $this->getSession()->getCookie($this->getSessionName());
$this->assertTrue($this->drupalUserIsLoggedIn($account), SafeMarkup::format('User %name successfully logged in.', array('name' => $account->getUsername())));
$this->assertTrue($this->drupalUserIsLoggedIn($account), new FormattableMarkup('User %name successfully logged in.', ['%name' => $account->getAccountName()]));
$this->loggedInUser = $account;
$this->container->get('current_user')->setAccount($account);
@ -807,7 +738,7 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
// idea being if you were properly logged out you should be seeing a login
// screen.
$assert_session = $this->assertSession();
$this->drupalGet('user/logout', array('query' => array('destination' => 'user')));
$this->drupalGet('user/logout', ['query' => ['destination' => 'user']]);
$assert_session->statusCodeEquals(200);
$assert_session->fieldExists('name');
$assert_session->fieldExists('pass');
@ -878,7 +809,10 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
// Ensure that any changes to variables in the other thread are picked up.
$this->refreshVariables();
if ($this->htmlOutputEnabled) {
// Log only for JavascriptTestBase tests because for Goutte we log with
// ::getResponseLogHandler.
if ($this->htmlOutputEnabled && !($this->getSession()->getDriver() instanceof GoutteDriver)) {
$out = $this->getSession()->getPage()->getContent();
$html_output = 'POST request to: ' . $action .
'<hr />Ending URL: ' . $this->getSession()->getCurrentUrl();
@ -886,6 +820,7 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
$html_output .= $this->getHtmlOutputHeaders();
$this->htmlOutput($html_output);
}
}
/**
@ -931,6 +866,8 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
* $edit = array();
* $edit['name[]'] = array('value1', 'value2');
* @endcode
* @todo change $edit to disallow NULL as a value for Drupal 9.
* https://www.drupal.org/node/2802401
* @param string $submit
* Value of the submit button whose click is to be emulated. For example,
* t('Save'). The processing of the request depends on this value. For
@ -956,11 +893,14 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
* @param array $options
* Options to be forwarded to the url generator.
*/
protected function drupalPostForm($path, array $edit, $submit, array $options = array()) {
protected function drupalPostForm($path, $edit, $submit, array $options = []) {
if (is_object($submit)) {
// Cast MarkupInterface objects to string.
$submit = (string) $submit;
}
if ($edit === NULL) {
$edit = [];
}
if (is_array($edit)) {
$edit = $this->castSafeStrings($edit);
}
@ -1001,157 +941,14 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
* Installs Drupal into the Simpletest site.
*/
public function installDrupal() {
// Define information about the user 1 account.
$this->rootUser = new UserSession(array(
'uid' => 1,
'name' => 'admin',
'mail' => 'admin@example.com',
'passRaw' => $this->randomMachineName(),
));
// The child site derives its session name from the database prefix when
// running web tests.
$this->generateSessionName($this->databasePrefix);
// Get parameters for install_drupal() before removing global variables.
$parameters = $this->installParameters();
// Prepare installer settings that are not install_drupal() parameters.
// Copy and prepare an actual settings.php, so as to resemble a regular
// installation.
// Not using File API; a potential error must trigger a PHP warning.
$directory = DRUPAL_ROOT . '/' . $this->siteDirectory;
copy(DRUPAL_ROOT . '/sites/default/default.settings.php', $directory . '/settings.php');
// The public file system path is created during installation. Additionally,
// during tests:
// - The temporary directory is set and created by install_base_system().
// - The private file directory is created post install by this method.
// @see system_requirements()
// @see TestBase::prepareEnvironment()
// @see install_base_system()
$settings['settings']['file_public_path'] = (object) array(
'value' => $this->publicFilesDirectory,
'required' => TRUE,
);
$settings['settings']['file_private_path'] = (object) [
'value' => $this->privateFilesDirectory,
'required' => TRUE,
];
$this->writeSettings($settings);
// Allow for test-specific overrides.
$settings_testing_file = DRUPAL_ROOT . '/' . $this->originalSiteDirectory . '/settings.testing.php';
if (file_exists($settings_testing_file)) {
// Copy the testing-specific settings.php overrides in place.
copy($settings_testing_file, $directory . '/settings.testing.php');
// Add the name of the testing class to settings.php and include the
// testing specific overrides.
file_put_contents($directory . '/settings.php', "\n\$test_class = '" . get_class($this) . "';\n" . 'include DRUPAL_ROOT . \'/\' . $site_path . \'/settings.testing.php\';' . "\n", FILE_APPEND);
}
$settings_services_file = DRUPAL_ROOT . '/' . $this->originalSiteDirectory . '/testing.services.yml';
if (!file_exists($settings_services_file)) {
// Otherwise, use the default services as a starting point for overrides.
$settings_services_file = DRUPAL_ROOT . '/sites/default/default.services.yml';
}
// Copy the testing-specific service overrides in place.
copy($settings_services_file, $directory . '/services.yml');
if ($this->strictConfigSchema) {
// Add a listener to validate configuration schema on save.
$content = file_get_contents($directory . '/services.yml');
$services = Yaml::decode($content);
$services['services']['simpletest.config_schema_checker'] = [
'class' => ConfigSchemaChecker::class,
'arguments' => ['@config.typed', $this->getConfigSchemaExclusions()],
'tags' => [['name' => 'event_subscriber']]
];
file_put_contents($directory . '/services.yml', Yaml::encode($services));
}
// Since Drupal is bootstrapped already, install_begin_request() will not
// bootstrap into DRUPAL_BOOTSTRAP_CONFIGURATION (again). Hence, we have to
// reload the newly written custom settings.php manually.
Settings::initialize(DRUPAL_ROOT, $directory, $this->classLoader);
// Execute the non-interactive installer.
require_once DRUPAL_ROOT . '/core/includes/install.core.inc';
install_drupal($parameters);
// Import new settings.php written by the installer.
Settings::initialize(DRUPAL_ROOT, $directory, $this->classLoader);
foreach ($GLOBALS['config_directories'] as $type => $path) {
$this->configDirectories[$type] = $path;
}
// After writing settings.php, the installer removes write permissions from
// the site directory. To allow drupal_generate_test_ua() to write a file
// containing the private key for drupal_valid_test_ua(), the site directory
// has to be writable.
// TestBase::restoreEnvironment() will delete the entire site directory. Not
// using File API; a potential error must trigger a PHP warning.
chmod($directory, 0777);
// During tests, cacheable responses should get the debugging cacheability
// headers by default.
$this->setContainerParameter('http.response.debug_cacheability_headers', TRUE);
$request = \Drupal::request();
$this->kernel = DrupalKernel::createFromRequest($request, $this->classLoader, 'prod', TRUE);
$this->kernel->prepareLegacyRequest($request);
// Force the container to be built from scratch instead of loaded from the
// disk. This forces us to not accidentally load the parent site.
$container = $this->kernel->rebuildContainer();
$config = $container->get('config.factory');
// Manually create the private directory.
file_prepare_directory($this->privateFilesDirectory, FILE_CREATE_DIRECTORY);
// Manually configure the test mail collector implementation to prevent
// tests from sending out emails and collect them in state instead.
// While this should be enforced via settings.php prior to installation,
// some tests expect to be able to test mail system implementations.
$config->getEditable('system.mail')
->set('interface.default', 'test_mail_collector')
->save();
// By default, verbosely display all errors and disable all production
// environment optimizations for all tests to avoid needless overhead and
// ensure a sane default experience for test authors.
// @see https://www.drupal.org/node/2259167
$config->getEditable('system.logging')
->set('error_level', 'verbose')
->save();
$config->getEditable('system.performance')
->set('css.preprocess', FALSE)
->set('js.preprocess', FALSE)
->save();
// Collect modules to install.
$class = get_class($this);
$modules = array();
while ($class) {
if (property_exists($class, 'modules')) {
$modules = array_merge($modules, $class::$modules);
}
$class = get_parent_class($class);
}
if ($modules) {
$modules = array_unique($modules);
$success = $container->get('module_installer')->install($modules, TRUE);
$this->assertTrue($success, SafeMarkup::format('Enabled modules: %modules', array('%modules' => implode(', ', $modules))));
$this->rebuildContainer();
}
// Reset/rebuild all data structures after enabling the modules, primarily
// to synchronize all data structures and caches between the test runner and
// the child site.
// Affects e.g. StreamWrapperManagerInterface::getWrappers().
// @see \Drupal\Core\DrupalKernel::bootCode()
// @todo Test-specific setUp() methods may set up further fixtures; find a
// way to execute this after setUp() is done, or to eliminate it entirely.
$this->resetAll();
$this->kernel->prepareLegacyRequest($request);
$this->initUserSession();
$this->prepareSettings();
$this->doInstall();
$this->initSettings();
$container = $this->initKernel(\Drupal::request());
$this->initConfig($container);
$this->installModulesFromClassProperty($container);
$this->rebuildAll();
}
/**
@ -1168,99 +965,38 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
unset($connection_info['default']['namespace']);
unset($connection_info['default']['pdo']);
unset($connection_info['default']['init_commands']);
$parameters = array(
$parameters = [
'interactive' => FALSE,
'parameters' => array(
'parameters' => [
'profile' => $this->profile,
'langcode' => 'en',
),
'forms' => array(
'install_settings_form' => array(
],
'forms' => [
'install_settings_form' => [
'driver' => $driver,
$driver => $connection_info['default'],
),
'install_configure_form' => array(
],
'install_configure_form' => [
'site_name' => 'Drupal',
'site_mail' => 'simpletest@example.com',
'account' => array(
'account' => [
'name' => $this->rootUser->name,
'mail' => $this->rootUser->getEmail(),
'pass' => array(
'pass1' => $this->rootUser->passRaw,
'pass2' => $this->rootUser->passRaw,
),
),
'pass' => [
'pass1' => $this->rootUser->pass_raw,
'pass2' => $this->rootUser->pass_raw,
],
],
// form_type_checkboxes_value() requires NULL instead of FALSE values
// for programmatic form submissions to disable a checkbox.
'update_status_module' => array(
1 => NULL,
2 => NULL,
),
),
),
);
'enable_update_status_module' => NULL,
'enable_update_status_emails' => NULL,
],
],
];
return $parameters;
}
/**
* Generates a database prefix for running tests.
*
* The database prefix is used by prepareEnvironment() to setup a public files
* directory for the test to be run, which also contains the PHP error log,
* which is written to in case of a fatal error. Since that directory is based
* on the database prefix, all tests (even unit tests) need to have one, in
* order to access and read the error log.
*
* The generated database table prefix is used for the Drupal installation
* being performed for the test. It is also used by the cookie value of
* SIMPLETEST_USER_AGENT by the Mink controlled browser. During early Drupal
* bootstrap, the cookie is parsed, and if it matches, all database queries
* use the database table prefix that has been generated here.
*
* @see drupal_valid_test_ua()
* @see BrowserTestBase::prepareEnvironment()
*/
private function prepareDatabasePrefix() {
$test_db = new TestDatabase();
$this->siteDirectory = $test_db->getTestSitePath();
$this->databasePrefix = $test_db->getDatabasePrefix();
}
/**
* Changes the database connection to the prefixed one.
*
* @see BrowserTestBase::prepareEnvironment()
*/
private function changeDatabasePrefix() {
if (empty($this->databasePrefix)) {
$this->prepareDatabasePrefix();
}
// If the test is run with argument dburl then use it.
$db_url = getenv('SIMPLETEST_DB');
if (!empty($db_url)) {
$database = Database::convertDbUrlToConnectionInfo($db_url, DRUPAL_ROOT);
Database::addConnectionInfo('default', 'default', $database);
}
// Clone the current connection and replace the current prefix.
$connection_info = Database::getConnectionInfo('default');
if (is_null($connection_info)) {
throw new \InvalidArgumentException('There is no database connection so no tests can be run. You must provide a SIMPLETEST_DB environment variable to run PHPUnit based functional tests outside of run-tests.sh.');
}
else {
Database::renameConnection('default', 'simpletest_original_default');
foreach ($connection_info as $target => $value) {
// Replace the full table prefix definition to ensure that no table
// prefixes of the test runner leak into the test.
$connection_info[$target]['prefix'] = array(
'default' => $value['prefix']['default'] . $this->databasePrefix,
);
}
Database::addConnectionInfo('default', 'default', $connection_info['default']);
}
}
/**
* Prepares the current environment for running the test.
*
@ -1282,7 +1018,7 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
$kernel->prepareLegacyRequest($request);
$this->prepareDatabasePrefix();
$this->originalSiteDirectory = $kernel->findSitePath($request);
$this->originalSite = $kernel->findSitePath($request);
// Create test directory ahead of installation so fatal errors and debug
// information can be logged during installation process.
@ -1327,10 +1063,10 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
drupal_valid_test_ua($this->databasePrefix);
// Reset settings.
new Settings(array(
new Settings([
// For performance, simply use the database prefix as hash salt.
'hash_salt' => $this->databasePrefix,
));
]);
drupal_set_time_limit($this->timeLimit);
@ -1343,163 +1079,6 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
$callbacks = [];
}
/**
* Returns the database connection to the site running Simpletest.
*
* @return \Drupal\Core\Database\Connection
* The database connection to use for inserting assertions.
*/
public static function getDatabaseConnection() {
return TestDatabase::getConnection();
}
/**
* Rewrites the settings.php file of the test site.
*
* @param array $settings
* An array of settings to write out, in the format expected by
* drupal_rewrite_settings().
*
* @see drupal_rewrite_settings()
*/
protected function writeSettings(array $settings) {
include_once DRUPAL_ROOT . '/core/includes/install.inc';
$filename = $this->siteDirectory . '/settings.php';
// system_requirements() removes write permissions from settings.php
// whenever it is invoked.
// Not using File API; a potential error must trigger a PHP warning.
chmod($filename, 0666);
drupal_rewrite_settings($settings, $filename);
}
/**
* Rebuilds \Drupal::getContainer().
*
* Use this to build a new kernel and service container. For example, when the
* list of enabled modules is changed via the Mink controlled browser, in
* which case the test process still contains an old kernel and service
* container with an old module list.
*
* @see BrowserTestBase::prepareEnvironment()
* @see BrowserTestBase::restoreEnvironment()
*
* @todo Fix https://www.drupal.org/node/2021959 so that module enable/disable
* changes are immediately reflected in \Drupal::getContainer(). Until then,
* tests can invoke this workaround when requiring services from newly
* enabled modules to be immediately available in the same request.
*/
protected function rebuildContainer() {
// Rebuild the kernel and bring it back to a fully bootstrapped state.
$this->container = $this->kernel->rebuildContainer();
// Make sure the url generator has a request object, otherwise calls to
// $this->drupalGet() will fail.
$this->prepareRequestForGenerator();
}
/**
* Creates a mock request and sets it on the generator.
*
* This is used to manipulate how the generator generates paths during tests.
* It also ensures that calls to $this->drupalGet() will work when running
* from run-tests.sh because the url generator no longer looks at the global
* variables that are set there but relies on getting this information from a
* request object.
*
* @param bool $clean_urls
* Whether to mock the request using clean urls.
* @param array $override_server_vars
* An array of server variables to override.
*
* @return Request
* The mocked request object.
*/
protected function prepareRequestForGenerator($clean_urls = TRUE, $override_server_vars = array()) {
$request = Request::createFromGlobals();
$server = $request->server->all();
if (basename($server['SCRIPT_FILENAME']) != basename($server['SCRIPT_NAME'])) {
// We need this for when the test is executed by run-tests.sh.
// @todo Remove this once run-tests.sh has been converted to use a Request
// object.
$cwd = getcwd();
$server['SCRIPT_FILENAME'] = $cwd . '/' . basename($server['SCRIPT_NAME']);
$base_path = rtrim($server['REQUEST_URI'], '/');
}
else {
$base_path = $request->getBasePath();
}
if ($clean_urls) {
$request_path = $base_path ? $base_path . '/user' : 'user';
}
else {
$request_path = $base_path ? $base_path . '/index.php/user' : '/index.php/user';
}
$server = array_merge($server, $override_server_vars);
$request = Request::create($request_path, 'GET', array(), array(), array(), $server);
// Ensure the request time is REQUEST_TIME to ensure that API calls
// in the test use the right timestamp.
$request->server->set('REQUEST_TIME', REQUEST_TIME);
$this->container->get('request_stack')->push($request);
// The request context is normally set by the router_listener from within
// its KernelEvents::REQUEST listener. In the Simpletest parent site this
// event is not fired, therefore it is necessary to updated the request
// context manually here.
$this->container->get('router.request_context')->fromRequest($request);
return $request;
}
/**
* Resets all data structures after having enabled new modules.
*
* This method is called by \Drupal\simpletest\BrowserTestBase::setUp() after
* enabling the requested modules. It must be called again when additional
* modules are enabled later.
*/
protected function resetAll() {
// Clear all database and static caches and rebuild data structures.
drupal_flush_all_caches();
$this->container = \Drupal::getContainer();
// Reset static variables and reload permissions.
$this->refreshVariables();
}
/**
* Refreshes in-memory configuration and state information.
*
* Useful after a page request is made that changes configuration or state in
* a different thread.
*
* In other words calling a settings page with $this->submitForm() with a
* changed value would update configuration to reflect that change, but in the
* thread that made the call (thread running the test) the changed values
* would not be picked up.
*
* This method clears the cache and loads a fresh copy.
*/
protected function refreshVariables() {
// Clear the tag cache.
$this->container->get('cache_tags.invalidator')->resetChecksums();
// @todo Replace drupal_static() usage within classes and provide a
// proper interface for invoking reset() on a cache backend:
// https://www.drupal.org/node/2311945.
drupal_static_reset('Drupal\Core\Cache\CacheBackendInterface::tagCache');
drupal_static_reset('Drupal\Core\Cache\DatabaseBackend::deletedTags');
drupal_static_reset('Drupal\Core\Cache\DatabaseBackend::invalidatedTags');
foreach (Cache::getBins() as $backend) {
if (is_callable(array($backend, 'reset'))) {
$backend->reset();
}
}
$this->container->get('config.factory')->reset();
$this->container->get('state')->resetCache();
}
/**
* Returns whether a given user account is logged in.
*
@ -1576,15 +1155,28 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
* HTML output headers.
*/
protected function getHtmlOutputHeaders() {
$headers = array_map(function($headers) {
if (is_array($headers)) {
return implode(';', array_map('trim', $headers));
return $this->formatHtmlOutputHeaders($this->getSession()->getResponseHeaders());
}
/**
* Formats HTTP headers as string for HTML output logging.
*
* @param array[] $headers
* Headers that should be formatted.
*
* @return string
* The formatted HTML string.
*/
protected function formatHtmlOutputHeaders(array $headers) {
$flattened_headers = array_map(function($header) {
if (is_array($header)) {
return implode(';', array_map('trim', $header));
}
else {
return $headers;
return $header;
}
}, $this->getSession()->getResponseHeaders());
return '<hr />Headers: <pre>' . Html::escape(var_export($headers, TRUE)) . '</pre>';
}, $headers);
return '<hr />Headers: <pre>' . Html::escape(var_export($flattened_headers, TRUE)) . '</pre>';
}
/**
@ -1680,6 +1272,19 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
return $this->container->get('config.factory')->getEditable($name);
}
/**
* Returns all response headers.
*
* @return array
* The HTTP headers values.
*
* @deprecated Scheduled for removal in Drupal 9.0.0.
* Use $this->getSession()->getResponseHeaders() instead.
*/
protected function drupalGetHeaders() {
return $this->getSession()->getResponseHeaders();
}
/**
* Gets the value of an HTTP response header.
*
@ -1734,43 +1339,34 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
}
/**
* Changes parameters in the services.yml file.
* Retrieves the current calling line in the class under test.
*
* @param string $name
* The name of the parameter.
* @param mixed $value
* The value of the parameter.
* @return array
* An associative array with keys 'file', 'line' and 'function'.
*/
protected function setContainerParameter($name, $value) {
$filename = $this->siteDirectory . '/services.yml';
chmod($filename, 0666);
$services = Yaml::decode(file_get_contents($filename));
$services['parameters'][$name] = $value;
file_put_contents($filename, Yaml::encode($services));
// Ensure that the cache is deleted for the yaml file loader.
$file_cache = FileCacheFactory::get('container_yaml_loader');
$file_cache->delete($filename);
}
/**
* Gets the config schema exclusions for this test.
*
* @return string[]
* An array of config object names that are excluded from schema checking.
*/
protected function getConfigSchemaExclusions() {
$class = get_class($this);
$exceptions = [];
while ($class) {
if (property_exists($class, 'configSchemaCheckerExclusions')) {
$exceptions = array_merge($exceptions, $class::$configSchemaCheckerExclusions);
protected function getTestMethodCaller() {
$backtrace = debug_backtrace();
// Find the test class that has the test method.
while ($caller = Error::getLastCaller($backtrace)) {
if (isset($caller['class']) && $caller['class'] === get_class($this)) {
break;
}
$class = get_parent_class($class);
// If the test method is implemented by a test class's parent then the
// class name of $this will not be part of the backtrace.
// In that case we process the backtrace until the caller is not a
// subclass of $this and return the previous caller.
if (isset($last_caller) && (!isset($caller['class']) || !is_subclass_of($this, $caller['class']))) {
// Return the last caller since that has to be the test class.
$caller = $last_caller;
break;
}
// Otherwise we have not reached our test class yet: save the last caller
// and remove an element from to backtrace to process the next call.
$last_caller = $caller;
array_shift($backtrace);
}
// Filter out any duplicates.
return array_unique($exceptions);
return $caller;
}
}