Newer
Older
<?php
/**
catch
committed
* @file
Jennifer Hodgdon
committed
* Definition of \Drupal\simpletest\WebTestBase.
*/
catch
committed
namespace Drupal\simpletest;
Angie Byron
committed
catch
committed
use Drupal\block\Entity\Block;
use Drupal\Component\FileCache\FileCacheFactory;
Angie Byron
committed
use Drupal\Component\Serialization\Json;
Angie Byron
committed
use Drupal\Component\Serialization\Yaml;
Alex Pott
committed
use Drupal\Component\Utility\Html;
Dries Buytaert
committed
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\UrlHelper;
catch
committed
use Drupal\Core\Cache\Cache;
use Drupal\Component\Utility\SafeMarkup;
catch
committed
use Drupal\Core\Database\Database;
catch
committed
use Drupal\Core\DrupalKernel;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Render\Element;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\Core\Session\UserSession;
use Drupal\Core\Site\Settings;
use Drupal\Core\StreamWrapper\PublicStream;
use Drupal\Core\Url;
catch
committed
use Drupal\node\Entity\NodeType;
use Symfony\Component\HttpFoundation\Request;
Dries Buytaert
committed
/**
* Test case for typical Drupal tests.
Angie Byron
committed
*
* @ingroup testing
Dries Buytaert
committed
*/
catch
committed
abstract class WebTestBase extends TestBase {
use AssertContentTrait;
catch
committed
use UserCreationTrait {
createUser as drupalCreateUser;
createRole as drupalCreateRole;
createAdminRole as drupalCreateAdminRole;
}
Dries Buytaert
committed
/**
* The profile to install as a basis for testing.
*
* @var string
*/
protected $profile = 'testing';
Dries Buytaert
committed
Dries Buytaert
committed
/**
* The URL currently loaded in the internal browser.
*
* @var string
*/
protected $url;
/**
* The handle of the current cURL connection.
*
* @var resource
*/
protected $curlHandle;
/**
* The headers of the page currently loaded in the internal browser.
*
* @var Array
*/
protected $headers;
/**
* The cookies of the page currently loaded in the internal browser.
*
* @var array
*/
protected $cookies;
/**
* Indicates that headers should be dumped if verbose output is enabled.
*
* Headers are dumped to verbose by drupalGet(), drupalHead(), and
* drupalPostForm().
*
* @var bool
*/
protected $dumpHeaders = FALSE;
Dries Buytaert
committed
/**
* The current user logged in using the internal browser.
*
Jennifer Hodgdon
committed
* @var \Drupal\Core\Session\AccountInterface|bool
Dries Buytaert
committed
*/
protected $loggedInUser = FALSE;
/**
* The "#1" admin user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $rootUser;
Dries Buytaert
committed
/**
* The current cookie file used by cURL.
*
* We do not reuse the cookies in further runs, so we do not need a file
* but we still need cookie handling, so we set the jar to NULL.
*/
protected $cookieFile = NULL;
/**
* Additional cURL options.
*
Jennifer Hodgdon
committed
* \Drupal\simpletest\WebTestBase itself never sets this but always obeys what
* is set.
Dries Buytaert
committed
*/
protected $additionalCurlOptions = array();
/**
* The original batch, before it was changed for testing purposes.
*
* @var array
*/
protected $originalBatch;
Dries Buytaert
committed
/**
Jennifer Hodgdon
committed
* The original user, before it was changed to a clean uid = 1 for testing.
Dries Buytaert
committed
*
* @var object
*/
protected $originalUser = NULL;
/**
Jennifer Hodgdon
committed
* The original shutdown handlers array, before it was cleaned for testing.
*
* @var array
*/
protected $originalShutdownCallbacks = array();
/**
* The current session ID, if available.
*/
protected $sessionId = NULL;
Dries Buytaert
committed
/**
* Whether the files were copied to the test files directory.
*/
protected $generatedTestFiles = FALSE;
/**
* The maximum number of redirects to follow when handling responses.
*/
protected $maximumRedirects = 5;
Dries Buytaert
committed
/**
* The number of redirects followed during the handling of a request.
*/
protected $redirectCount;
Dries Buytaert
committed
Angie Byron
committed
/**
* The kernel used in this test.
*
* @var \Drupal\Core\DrupalKernel
Angie Byron
committed
*/
protected $kernel;
/**
* The config directories used in this test.
*/
protected $configDirectories = array();
catch
committed
/**
* Cookies to set on curl requests.
*
* @var array
*/
protected $curlCookies = array();
/**
* An array of custom translations suitable for drupal_rewrite_settings().
*
* @var array
*/
protected $customTranslations;
Dries Buytaert
committed
/**
Jennifer Hodgdon
committed
* Constructor for \Drupal\simpletest\WebTestBase.
Dries Buytaert
committed
*/
function __construct($test_id = NULL) {
parent::__construct($test_id);
$this->skipClasses[__CLASS__] = TRUE;
}
/**
* Get a node from the database based on its title.
*
Angie Byron
committed
* @param $title
Alex Pott
committed
* A node title, usually generated by $this->randomMachineName().
Dries Buytaert
committed
* @param $reset
* (optional) Whether to reset the entity cache.
*
* @return \Drupal\node\NodeInterface
* A node entity matching $title.
*/
Dries Buytaert
committed
function drupalGetNodeByTitle($title, $reset = FALSE) {
if ($reset) {
catch
committed
\Drupal::entityManager()->getStorage('node')->resetCache();
}
$nodes = entity_load_multiple_by_properties('node', array('title' => $title));
// Load the first node returned from the database.
$returned_node = reset($nodes);
return $returned_node;
}
/**
* Creates a node based on default settings.
*
Angie Byron
committed
* @param array $settings
* (optional) An associative array of settings for the node, as used in
* entity_create(). Override the defaults by specifying the key and value
* in the array, for example:
* @code
* $this->drupalCreateNode(array(
* 'title' => t('Hello, world!'),
* 'type' => 'article',
* ));
* @endcode
* The following defaults are provided:
* - body: Random string using the default filter format:
* @code
Angie Byron
committed
* $settings['body'][0] = array(
Alex Pott
committed
* 'value' => $this->randomMachineName(32),
Angie Byron
committed
* 'format' => filter_default_format(),
* );
* @endcode
* - title: Random string.
* - type: 'page'.
Alex Pott
committed
* - uid: The currently logged in user, or anonymous.
Angie Byron
committed
*
* @return \Drupal\node\NodeInterface
Angie Byron
committed
* The created node entity.
*/
protected function drupalCreateNode(array $settings = array()) {
// Populate defaults array.
Dries Buytaert
committed
$settings += array(
Alex Pott
committed
'body' => array(array(
'value' => $this->randomMachineName(32),
'format' => filter_default_format(),
)),
Alex Pott
committed
'title' => $this->randomMachineName(8),
'type' => 'page',
Alex Pott
committed
'uid' => \Drupal::currentUser()->id(),
);
$node = entity_create('node', $settings);
$node->save();
return $node;
}
/**
* Creates a custom content type based on default settings.
*
* @param array $values
* An array of settings to change from the defaults.
* Example: 'type' => 'foo'.
*
* @return \Drupal\node\Entity\NodeType
Dries Buytaert
committed
* Created content type.
*/
protected function drupalCreateContentType(array $values = array()) {
// Find a non-existent random type name.
if (!isset($values['type'])) {
do {
Alex Pott
committed
$id = strtolower($this->randomMachineName(8));
} while (NodeType::load($id));
}
else {
$id = $values['type'];
}
$values += array(
'type' => $id,
'name' => $id,
);
$type = entity_create('node_type', $values);
$status = $type->save();
node_add_body_field($type);
Angie Byron
committed
\Drupal::service('router.builder')->rebuild();
$this->assertEqual($status, SAVED_NEW, SafeMarkup::format('Created content type %type.', array('%type' => $type->id())));
return $type;
}
/**
* Builds the renderable view of an entity.
*
* Entities postpone the composition of their renderable arrays to #pre_render
* functions in order to maximize cache efficacy. This means that the full
Alex Pott
committed
* renderable array for an entity is constructed in drupal_render(). Some
* tests require the complete renderable array for an entity outside of the
* drupal_render process in order to verify the presence of specific values.
* This method isolates the steps in the render process that produce an
* entity's renderable array.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to prepare a renderable array for.
* @param string $view_mode
* (optional) The view mode that should be used to build the entity.
* @param null $langcode
* (optional) For which language the entity should be prepared, defaults to
* the current content language.
* @param bool $reset
* (optional) Whether to clear the cache for this entity.
* @return array
*
* @see drupal_render()
*/
protected function drupalBuildEntityView(EntityInterface $entity, $view_mode = 'full', $langcode = NULL, $reset = FALSE) {
$ensure_fully_built = function(&$elements) use (&$ensure_fully_built) {
// If the default values for this element have not been loaded yet, populate
// them.
if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) {
$elements += \Drupal::service('element_info')->getInfo($elements['#type']);
}
// Make any final changes to the element before it is rendered. This means
// that the $element or the children can be altered or corrected before the
// element is rendered into the final text.
if (isset($elements['#pre_render'])) {
foreach ($elements['#pre_render'] as $callable) {
$elements = call_user_func($callable, $elements);
}
}
// And recurse.
$children = Element::children($elements, TRUE);
foreach ($children as $key) {
$ensure_fully_built($elements[$key]);
}
};
$render_controller = $this->container->get('entity.manager')->getViewBuilder($entity->getEntityTypeId());
if ($reset) {
$render_controller->resetCache(array($entity->id()));
}
$build = $render_controller->view($entity, $view_mode, $langcode);
$ensure_fully_built($build);
return $build;
}
Angie Byron
committed
/**
* Creates a block instance based on default settings.
*
* @param string $plugin_id
* The plugin ID of the block type for this block instance.
* @param array $settings
* (optional) An associative array of settings for the block entity.
Angie Byron
committed
* Override the defaults by specifying the key and value in the array, for
Angie Byron
committed
* example:
* @code
* $this->drupalPlaceBlock('system_powered_by_block', array(
Angie Byron
committed
* 'label' => t('Hello, world!'),
Angie Byron
committed
* ));
* @endcode
* The following defaults are provided:
Angie Byron
committed
* - label: Random string.
Jennifer Hodgdon
committed
* - ID: Random string.
Angie Byron
committed
* - region: 'sidebar_first'.
Angie Byron
committed
* - theme: The default theme.
* - visibility: Empty array.
catch
committed
* - cache: array('max_age' => Cache::PERMANENT).
Angie Byron
committed
*
* @return \Drupal\block\Entity\Block
Angie Byron
committed
* The block entity.
Angie Byron
committed
*
* @todo
* Add support for creating custom block instances.
*/
protected function drupalPlaceBlock($plugin_id, array $settings = array()) {
$settings += array(
Angie Byron
committed
'plugin' => $plugin_id,
Angie Byron
committed
'region' => 'sidebar_first',
Alex Pott
committed
'id' => strtolower($this->randomMachineName(8)),
'theme' => $this->config('system.theme')->get('default'),
Alex Pott
committed
'label' => $this->randomMachineName(8),
'visibility' => array(),
Alex Pott
committed
'weight' => 0,
'cache' => array(
catch
committed
'max_age' => Cache::PERMANENT,
),
Angie Byron
committed
);
$values = [];
foreach (array('region', 'id', 'theme', 'plugin', 'weight', 'visibility') as $key) {
$values[$key] = $settings[$key];
Alex Pott
committed
// Remove extra values that do not belong in the settings array.
unset($settings[$key]);
}
foreach ($values['visibility'] as $id => $visibility) {
$values['visibility'][$id]['id'] = $id;
Angie Byron
committed
}
$values['settings'] = $settings;
Angie Byron
committed
$block = entity_create('block', $values);
$block->save();
return $block;
Angie Byron
committed
}
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
/**
* Checks to see whether a block appears on the page.
*
* @param \Drupal\block\Entity\Block $block
* The block entity to find on the page.
*/
protected function assertBlockAppears(Block $block) {
$result = $this->findBlockInstance($block);
$this->assertTrue(!empty($result), format_string('Ensure the block @id appears on the page', array('@id' => $block->id())));
}
/**
* Checks to see whether a block does not appears on the page.
*
* @param \Drupal\block\Entity\Block $block
* The block entity to find on the page.
*/
protected function assertNoBlockAppears(Block $block) {
$result = $this->findBlockInstance($block);
$this->assertFalse(!empty($result), format_string('Ensure the block @id does not appear on the page', array('@id' => $block->id())));
}
/**
* Find a block instance on the page.
*
* @param \Drupal\block\Entity\Block $block
* The block entity to find on the page.
*
* @return array
* The result from the xpath query.
*/
protected function findBlockInstance(Block $block) {
return $this->xpath('//div[@id = :id]', array(':id' => 'block-' . $block->id()));
}
/**
Jennifer Hodgdon
committed
* Gets a list files that can be used in tests.
*
Dries Buytaert
committed
* @param $type
Jennifer Hodgdon
committed
* File type, possible values: 'binary', 'html', 'image', 'javascript',
* 'php', 'sql', 'text'.
Dries Buytaert
committed
* @param $size
* File size in bytes to match. Please check the tests/files folder.
*
Dries Buytaert
committed
* @return
* List of files that match filter.
*/
protected function drupalGetTestFiles($type, $size = NULL) {
Dries Buytaert
committed
if (empty($this->generatedTestFiles)) {
// Generate binary test files.
$lines = array(64, 1024);
$count = 0;
foreach ($lines as $line) {
simpletest_generate_file('binary-' . $count++, 64, $line, 'binary');
}
// Generate text test files.
$lines = array(16, 256, 1024, 2048, 20480);
$count = 0;
foreach ($lines as $line) {
simpletest_generate_file('text-' . $count++, 64, $line);
}
// Copy other test files from simpletest.
$original = drupal_get_path('module', 'simpletest') . '/files';
$files = file_scan_directory($original, '/(html|image|javascript|php|sql)-.*/');
foreach ($files as $file) {
file_unmanaged_copy($file->uri, PublicStream::basePath());
Dries Buytaert
committed
}
Dries Buytaert
committed
$this->generatedTestFiles = TRUE;
}
$files = array();
// Make sure type is valid.
if (in_array($type, array('binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'))) {
Angie Byron
committed
$files = file_scan_directory('public://', '/' . $type . '\-.*/');
// If size is set then remove any files that are not of that size.
if ($size !== NULL) {
foreach ($files as $file) {
$stats = stat($file->uri);
if ($stats['size'] != $size) {
unset($files[$file->uri]);
}
}
}
}
usort($files, array($this, 'drupalCompareFiles'));
return $files;
}
/**
* Compare two files based on size and file name.
*/
protected function drupalCompareFiles($file1, $file2) {
$compare_size = filesize($file1->uri) - filesize($file2->uri);
if ($compare_size) {
// Sort by file size.
return $compare_size;
}
else {
// The files were the same size, so sort alphabetically.
return strnatcmp($file1->name, $file2->name);
}
}
/**
Dries Buytaert
committed
* Log in a user with the internal browser.
*
* If a user is already logged in, then the current user is logged out before
* logging in the specified user.
*
* Please note that neither the current user nor the passed-in user object is
Dries Buytaert
committed
* populated with data of the logged in user. If you need full access to the
* user object after logging in, it must be updated manually. If you also need
* access to the plain-text password of the user (set by drupalCreateUser()),
Angie Byron
committed
* e.g. to log in the same user again, then it must be re-assigned manually.
Dries Buytaert
committed
* For example:
* @code
* // Create a user.
* $account = $this->drupalCreateUser(array());
* $this->drupalLogin($account);
* // Load real user object.
* $pass_raw = $account->pass_raw;
* $account = User::load($account->id());
Dries Buytaert
committed
* $account->pass_raw = $pass_raw;
* @endcode
*
* @param \Drupal\Core\Session\AccountInterface $account
Angie Byron
committed
* User object representing the user to log in.
Dries Buytaert
committed
*
* @see drupalCreateUser()
*/
protected function drupalLogin(AccountInterface $account) {
Dries Buytaert
committed
if ($this->loggedInUser) {
$this->drupalLogout();
}
$edit = array(
'name' => $account->getUsername(),
Angie Byron
committed
'pass' => $account->pass_raw
);
$this->drupalPostForm('user/login', $edit, t('Log in'));
Dries Buytaert
committed
// @see WebTestBase::drupalUserIsLoggedIn()
if (isset($this->sessionId)) {
$account->session_id = $this->sessionId;
Dries Buytaert
committed
}
$pass = $this->assert($this->drupalUserIsLoggedIn($account), format_string('User %name successfully logged in.', array('%name' => $account->getUsername())), 'User login');
Dries Buytaert
committed
if ($pass) {
Angie Byron
committed
$this->loggedInUser = $account;
$this->container->get('current_user')->setAccount($account);
Dries Buytaert
committed
}
}
Dries Buytaert
committed
/**
* Returns whether a given user account is logged in.
*
Alex Pott
committed
* @param \Drupal\user\UserInterface $account
Dries Buytaert
committed
* The user account object to check.
*/
protected function drupalUserIsLoggedIn($account) {
if (!isset($account->session_id)) {
return FALSE;
}
Alex Pott
committed
$session_id = $account->session_id;
$request_stack = $this->container->get('request_stack');
$request = $request_stack->getCurrentRequest();
$cookies = $request->cookies->all();
foreach ($this->cookies as $name => $value) {
$cookies[$name] = $value['value'];
}
$request_stack->push($request->duplicate(NULL, NULL, NULL, $cookies));
$logged_in = (bool) $this->container->get('session_manager')->getSaveHandler()->read($session_id);
$request_stack->pop();
return $logged_in;
Dries Buytaert
committed
}
Jennifer Hodgdon
committed
/**
* Logs a user out of the internal browser and confirms.
*
* Confirms logout by checking the login page.
*/
protected function drupalLogout() {
Dries Buytaert
committed
// Make a request to the logout page, and redirect to the user page, the
// idea being if you were properly logged out you should be seeing a login
// screen.
$this->drupalGet('user/logout', array('query' => array('destination' => 'user/login')));
Jennifer Hodgdon
committed
$this->assertResponse(200, 'User was logged out.');
$pass = $this->assertField('name', 'Username field found.', 'Logout');
$pass = $pass && $this->assertField('pass', 'Password field found.', 'Logout');
Dries Buytaert
committed
if ($pass) {
Dries Buytaert
committed
// @see WebTestBase::drupalUserIsLoggedIn()
unset($this->loggedInUser->session_id);
Dries Buytaert
committed
$this->loggedInUser = FALSE;
$this->container->get('current_user')->setAccount(new AnonymousUserSession());
Dries Buytaert
committed
}
}
/**
* Sets up a Drupal site for running functional and integration tests.
*
catch
committed
* Installs Drupal with the installation profile specified in
* \Drupal\simpletest\WebTestBase::$profile into the prefixed database.
*
catch
committed
* Afterwards, installs any additional modules specified in the static
* \Drupal\simpletest\WebTestBase::$modules property of each class in the
* class hierarchy.
*
* After installation all caches are flushed and several configuration values
* are reset to the values of the parent site executing the test, since the
* default values may be incompatible with the environment in which tests are
* being executed.
*/
protected function setUp() {
// When running tests through the Simpletest UI (vs. on the command line),
// Simpletest's batch conflicts with the installer's batch. Batch API does
// not support the concept of nested batches (in which the nested is not
// progressive), so we need to temporarily pretend there was no batch.
// Backup the currently running Simpletest batch.
$this->originalBatch = batch_get();
Alex Pott
committed
// Define information about the user 1 account.
$this->rootUser = new UserSession(array(
'uid' => 1,
'name' => 'admin',
'mail' => 'admin@example.com',
Alex Pott
committed
'pass_raw' => $this->randomMachineName(),
Alex Pott
committed
// The child site derives its session name from the database prefix when
// running web tests.
$this->generateSessionName($this->databasePrefix);
Dries Buytaert
committed
// Reset the static batch to remove Simpletest's batch operations.
$batch = &batch_get();
$batch = array();
catch
committed
// 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');
Angie Byron
committed
copy(DRUPAL_ROOT . '/sites/default/default.services.yml', $directory . '/services.yml');
// All file system paths are created by System module during installation.
// @see system_requirements()
// @see TestBase::prepareEnvironment()
$settings['settings']['file_public_path'] = (object) array(
'value' => $this->publicFilesDirectory,
'required' => TRUE,
);
$settings['settings']['file_private_path'] = (object) array(
'value' => $this->privateFilesDirectory,
'required' => TRUE,
);
// Save the original site directory path, so that extensions in the
// site-specific directory can still be discovered in the test site
// environment.
Alex Pott
committed
// @see \Drupal\Core\Extension\ExtensionDiscovery::scan()
$settings['settings']['test_parent_site'] = (object) array(
'value' => $this->originalSite,
'required' => TRUE,
);
// Add the parent profile's search path to the child site's search paths.
catch
committed
// @see \Drupal\Core\Extension\ExtensionDiscovery::getProfileDirectories()
$settings['conf']['simpletest.settings']['parent_profile'] = (object) array(
'value' => $this->originalProfile,
'required' => TRUE,
);
Angie Byron
committed
$settings['settings']['apcu_ensure_unique_prefix'] = (object) array(
'value' => FALSE,
'required' => TRUE,
);
$this->writeSettings($settings);
// Allow for test-specific overrides.
$settings_testing_file = DRUPAL_ROOT . '/' . $this->originalSite . '/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->originalSite . '/testing.services.yml';
if (file_exists($settings_services_file)) {
// 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.
$yaml = new \Symfony\Component\Yaml\Yaml();
$services = $yaml->parse($directory . '/services.yml');
$services['services']['simpletest.config_schema_checker'] = [
'class' => 'Drupal\Core\Config\Testing\ConfigSchemaChecker',
'arguments' => ['@config.typed'],
'tags' => [['name' => 'event_subscriber']]
];
file_put_contents($directory . '/services.yml', $yaml->dump($services));
}
// Since Drupal is bootstrapped already, install_begin_request() will not
catch
committed
// bootstrap again. Hence, we have to reload the newly written custom
// settings.php manually.
$class_loader = require DRUPAL_ROOT . '/autoload.php';
Settings::initialize(DRUPAL_ROOT, $this->siteDirectory, $class_loader);
// Execute the non-interactive installer.
require_once DRUPAL_ROOT . '/core/includes/install.core.inc';
install_drupal($class_loader, $parameters);
Dries Buytaert
committed
// Import new settings.php written by the installer.
Settings::initialize(DRUPAL_ROOT, $this->siteDirectory, $class_loader);
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);
Dries Buytaert
committed
$request = \Drupal::request();
$this->kernel = DrupalKernel::createFromRequest($request, $class_loader, 'prod', TRUE);
Dries Buytaert
committed
$this->kernel->prepareLegacyRequest($request);
// Force the container to be built from scratch instead of loaded from the
Alex Pott
committed
// disk. This forces us to not accidentally load the parent site.
$container = $this->kernel->rebuildContainer();
Dries Buytaert
committed
$config = $container->get('config.factory');
// Manually create and configure private and temporary files directories.
// While these could be preset/enforced in settings.php like the public
// files directory above, some tests expect them to be configurable in the
// UI. If declared in settings.php, they would no longer be configurable.
file_prepare_directory($this->privateFilesDirectory, FILE_CREATE_DIRECTORY);
file_prepare_directory($this->tempFilesDirectory, FILE_CREATE_DIRECTORY);
$config->getEditable('system.file')
->set('path.temporary', $this->tempFilesDirectory)
->save();
// 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')
Alex Pott
committed
->set('interface.default', 'test_mail_collector')
->save();
Angie Byron
committed
// 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://drupal.org/node/2259167
$config->getEditable('system.logging')
Angie Byron
committed
->set('error_level', 'verbose')
->save();
$config->getEditable('system.performance')
Angie Byron
committed
->set('css.preprocess', FALSE)
->set('js.preprocess', FALSE)
Dries Buytaert
committed
->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);
}
Angie Byron
committed
if ($modules) {
Angie Byron
committed
$modules = array_unique($modules);
try {
$success = $container->get('module_installer')->install($modules, TRUE);
$this->assertTrue($success, SafeMarkup::format('Enabled modules: %modules', array('%modules' => implode(', ', $modules))));
}
catch (\Drupal\Core\Extension\MissingDependencyException $e) {
// The exception message has all the details.
$this->fail($e->getMessage());
}
$this->rebuildContainer();
Dries Buytaert
committed
}
Dries Buytaert
committed
Alex Pott
committed
// Restore the original Simpletest batch.
$batch = &batch_get();
$batch = $this->originalBatch;
// 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.
// @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);
// Explicitly call register() again on the container registered in \Drupal.
// @todo This should already be called through
// DrupalKernel::prepareLegacyRequest() -> DrupalKernel::boot() but that
// appears to be calling a different container.
$this->container->get('stream_wrapper_manager')->register();
}
Alex Pott
committed
/**
* Returns the parameters that will be used when Simpletest installs Drupal.
*
* @see install_drupal()
* @see install_state_defaults()
*/
protected function installParameters() {
$connection_info = Database::getConnectionInfo();
$driver = $connection_info['default']['driver'];
$connection_info['default']['prefix'] = $connection_info['default']['prefix']['default'];
unset($connection_info['default']['driver']);
unset($connection_info['default']['namespace']);
unset($connection_info['default']['pdo']);
unset($connection_info['default']['init_commands']);
// Remove database connection info that is not used by SQLite.
if ($driver == 'sqlite') {
unset($connection_info['default']['username']);
unset($connection_info['default']['password']);
unset($connection_info['default']['host']);
unset($connection_info['default']['port']);
}
Alex Pott
committed
$parameters = array(
'interactive' => FALSE,
'parameters' => array(
'profile' => $this->profile,
'langcode' => 'en',
),
'forms' => array(
'install_settings_form' => array(
'driver' => $driver,
$driver => $connection_info['default'],
),
Alex Pott
committed
'install_configure_form' => array(
'site_name' => 'Drupal',
'site_mail' => 'simpletest@example.com',
'account' => array(
'name' => $this->rootUser->name,
'mail' => $this->rootUser->getEmail(),
Alex Pott
committed
'pass' => array(
'pass1' => $this->rootUser->pass_raw,
'pass2' => $this->rootUser->pass_raw,
Alex Pott
committed
),
),
Jennifer Hodgdon
committed
// \Drupal\Core\Render\Element\Checkboxes::valueCallback() requires
// NULL instead of FALSE values for programmatic form submissions to
// disable a checkbox.
Alex Pott
committed
'update_status_module' => array(
1 => NULL,
2 => NULL,
),
),
),
);
Angie Byron
committed
// If we only have one db driver available, we cannot set the driver.
include_once DRUPAL_ROOT . '/core/includes/install.inc';
if (count($this->getDatabaseTypes()) == 1) {
Angie Byron
committed
unset($parameters['forms']['install_settings_form']['driver']);
}
Alex Pott
committed
return $parameters;
}
/**
* Returns all supported database driver installer objects.
*
* This wraps drupal_get_database_types() for use without a current container.
*
* @return \Drupal\Core\Database\Install\Tasks[]
* An array of available database driver installer objects.
*/
protected function getDatabaseTypes() {
\Drupal::setContainer($this->originalContainer);
$database_types = drupal_get_database_types();
Alex Pott
committed
\Drupal::unsetContainer();
return $database_types;
}
Angie Byron
committed
/**
* Rewrites the settings.php file of the test site.
Angie Byron
committed
*
* @param array $settings
* An array of settings to write out, in the format expected by
* drupal_rewrite_settings().
Angie Byron
committed
*
* @see drupal_rewrite_settings()
*/
protected function writeSettings(array $settings) {
Angie Byron
committed
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);
Angie Byron
committed
drupal_rewrite_settings($settings, $filename);
}
Angie Byron
committed
/**
* Changes parameters in the services.yml file.
*
* @param $name
* The name of the parameter.
* @param $value
* The value of the parameter.
*/
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);
Angie Byron
committed
}
* Queues custom translations to be written to settings.php.
* Use WebTestBase::writeCustomTranslations() to apply and write the queued
* translations.
*
* @param string $langcode
* The langcode to add translations for.
* @param array $values
* Array of values containing the untranslated string and its translation.
* For example:
* @code
* array(
* '' => array('Sunday' => 'domingo'),
* 'Long month name' => array('March' => 'marzo'),
* );
* @endcode
* Pass an empty array to remove all existing custom translations for the
* given $langcode.
*/
protected function addCustomTranslations($langcode, array $values) {
// If $values is empty, then the test expects all custom translations to be
// cleared.
if (empty($values)) {
$this->customTranslations[$langcode] = array();
}
// Otherwise, $values are expected to be merged into previously passed
// values, while retaining keys that are not explicitly set.
else {
foreach ($values as $context => $translations) {
foreach ($translations as $original => $translation) {
$this->customTranslations[$langcode][$context][$original] = $translation;
}
}
}
}
/**
* Writes custom translations to the test site's settings.php.
*
* Use TestBase::addCustomTranslations() to queue custom translations before
* calling this method.
*/
protected function writeCustomTranslations() {
$settings = array();
foreach ($this->customTranslations as $langcode => $values) {
$settings_key = 'locale_custom_strings_' . $langcode;
// Update in-memory settings directly.
$this->settingsSet($settings_key, $values);
$settings['settings'][$settings_key] = (object) array(
'value' => $values,
'required' => TRUE,
);
}
// Only rewrite settings if there are any translation changes to write.
if (!empty($settings)) {
$this->writeSettings($settings);