Revert "Issue #2171683 by sun, tstoeckler, larowlan: Remove all Simpletest overrides and rely on native multi-site functionality instead."

This reverts commit 91db25eba4. Broke web
tests.
8.0.x
Nathaniel Catchpole 2014-02-10 11:14:28 +00:00
parent cd661a19a7
commit ec06f877a4
32 changed files with 687 additions and 731 deletions

View File

@ -240,15 +240,15 @@ define('DRUPAL_ROOT', dirname(dirname(__DIR__)));
* @see default.settings.php
*/
function conf_path($require_settings = TRUE, $reset = FALSE) {
static $conf_path;
$conf_path = &drupal_static(__FUNCTION__, '');
if (isset($conf_path) && !$reset) {
if ($conf_path && !$reset) {
return $conf_path;
}
// Check for a simpletest override.
if ($test_prefix = drupal_valid_test_ua()) {
$conf_path = 'sites/simpletest/' . substr($test_prefix, 10);
if ($simpletest_conf_path = _drupal_simpletest_conf_path()) {
$conf_path = $simpletest_conf_path;
return $conf_path;
}
@ -262,6 +262,50 @@ function conf_path($require_settings = TRUE, $reset = FALSE) {
return $conf_path;
}
/**
* Determines whether to use an overridden value for conf_path().
*
* Simpletest may provide a secondary, test-specific settings.php file to load
* after the primary one used by the parent site and override its variables.
* - If the child settings.php does not override $conf_path, then this function
* returns FALSE and conf_path() returns the directory of the primary
* settings.php.
* - If the child settings.php does override $conf_path, then
* _drupal_load_test_overrides() sets the 'simpletest_conf_path' setting, and
* this function returns that to conf_path(), causing installations and
* upgrades to act on that one.
*
* @return string|false
* The overridden $conf_path, or FALSE if the $conf_path should not currently
* be overridden.
*
* @see conf_path()
* @see _drupal_load_test_overrides()
*/
function _drupal_simpletest_conf_path() {
// Ensure that the settings object is available. conf_path() is called once
// before the Settings class is included, and at that point it should still
// load the primary $conf_path. See drupal_settings_initialize().
if (!class_exists('Drupal\Component\Utility\Settings', FALSE)) {
return FALSE;
}
// If no $simpletest_conf_path is set, use the normal $conf_path.
if (!($simpletest_conf_path = settings()->get('simpletest_conf_path'))) {
return FALSE;
}
// Ensure that this is actually a simpletest request. We can't check this
// before settings.php is loaded.
if (!drupal_valid_test_ua()) {
return FALSE;
}
// When the $simpletest_conf_path is set in a valid test request,
// return that path.
return $simpletest_conf_path;
}
/**
* Finds the appropriate configuration directory for a given host and path.
*
@ -490,32 +534,20 @@ function drupal_valid_http_host($host) {
* Sets the base URL, cookie domain, and session name from configuration.
*/
function drupal_settings_initialize() {
global $base_url, $base_path, $base_root, $script_path;
// Export these settings.php variables to the global namespace.
global $base_url, $databases, $cookie_domain, $drupal_hash_salt, $config_directories, $config;
global $databases, $cookie_domain, $db_prefix, $drupal_hash_salt, $base_secure_url, $base_insecure_url, $config_directories, $config;
$settings = array();
$config = array();
// Make conf_path() available as local variable in settings.php.
$conf_path = conf_path();
if (is_readable(DRUPAL_ROOT . '/' . $conf_path . '/settings.php')) {
require DRUPAL_ROOT . '/' . $conf_path . '/settings.php';
include_once DRUPAL_ROOT . '/' . $conf_path . '/settings.php';
}
// Initialize Settings.
new Settings($settings);
}
/**
* Initializes global request variables.
*
* @todo D8: Eliminate this entirely in favor of Request object.
*/
function _drupal_request_initialize() {
// Provided by settings.php.
// @see drupal_settings_initialize()
global $base_url, $cookie_domain;
// Set and derived from $base_url by this function.
global $base_path, $base_root, $script_path;
global $base_secure_url, $base_insecure_url;
$is_https = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on';
@ -1721,27 +1753,11 @@ function _drupal_exception_handler($exception) {
*/
function _drupal_bootstrap_configuration() {
drupal_environment_initialize();
// Indicate that code is operating in a test child site.
if ($test_prefix = drupal_valid_test_ua()) {
// Only code that interfaces directly with tests should rely on this
// constant; e.g., the error/exception handler conditionally adds further
// error information into HTTP response headers that are consumed by
// Simpletest's internal browser.
define('DRUPAL_TEST_IN_CHILD_SITE', TRUE);
// Log fatal errors to the test site directory.
ini_set('log_errors', 1);
ini_set('error_log', DRUPAL_ROOT . '/sites/simpletest/' . substr($test_prefix, 10) . '/error.log');
}
else {
// Ensure that no other code defines this.
define('DRUPAL_TEST_IN_CHILD_SITE', FALSE);
}
// Initialize the configuration, including variables from settings.php.
drupal_settings_initialize();
_drupal_request_initialize();
// Make sure we are using the test database prefix in child Drupal sites.
_drupal_initialize_db_test_prefix();
// Activate the class loader.
drupal_classloader();
@ -1829,6 +1845,39 @@ function _drupal_bootstrap_page_cache() {
}
}
/**
* In a test environment, get the test db prefix and set it in $databases.
*/
function _drupal_initialize_db_test_prefix() {
// The user agent header is used to pass a database prefix in the request when
// running tests. However, for security reasons, it is imperative that we
// validate we ourselves made the request.
if ($test_prefix = drupal_valid_test_ua()) {
// Set the test run id for use in other parts of Drupal.
$test_info = &$GLOBALS['drupal_test_info'];
$test_info['test_run_id'] = $test_prefix;
$test_info['in_child_site'] = TRUE;
foreach ($GLOBALS['databases']['default'] as &$value) {
// Extract the current default database prefix.
if (!isset($value['prefix'])) {
$current_prefix = '';
}
elseif (is_array($value['prefix'])) {
$current_prefix = $value['prefix']['default'];
}
else {
$current_prefix = $value['prefix'];
}
// Remove the current database prefix and replace it by our own.
$value['prefix'] = array(
'default' => $current_prefix . $test_prefix,
);
}
}
}
/**
* Returns the current bootstrap phase for this Drupal process.
*
@ -1943,9 +1992,10 @@ function module_hook($module, $hook) {
* Returns the test prefix if this is an internal request from SimpleTest.
*
* @param string $new_prefix
* Internal use only. A new prefix to be stored.
* Internal use only. A new prefix to be stored. Passed in by tests that use
* the test runner from within a test.
*
* @return string|FALSE
* @return
* Either the simpletest prefix (the string "simpletest" followed by any
* number of digits) or FALSE if the user agent does not contain a valid
* HMAC and timestamp.
@ -1959,68 +2009,82 @@ function drupal_valid_test_ua($new_prefix = NULL) {
if (isset($test_prefix)) {
return $test_prefix;
}
// Unless the below User-Agent and HMAC validation succeeds, we are not in
// a test environment.
$test_prefix = FALSE;
// Perform a basic check on the User-Agent HTTP request header first. Any
// inbound request that uses the simpletest UA header needs to be validated.
if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^(simpletest\d+);(.+);(.+);(.+)$/", $_SERVER['HTTP_USER_AGENT'], $matches)) {
list(, $prefix, $time, $salt, $hmac) = $matches;
$check_string = $prefix . ';' . $time . ';' . $salt;
// Read the hash salt prepared by drupal_generate_test_ua().
// This function is called before settings.php is read and Drupal's error
// handlers are set up. While Drupal's error handling may be properly
// configured on production sites, the server's PHP error_reporting may not.
// Ensure that no information leaks on production sites.
$key_file = DRUPAL_ROOT . '/sites/simpletest/' . substr($prefix, 10) . '/.htkey';
if (!is_readable($key_file)) {
header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
exit;
}
$private_key = file_get_contents($key_file);
// The file properties add more entropy not easily accessible to others.
$key = $private_key . filectime(__FILE__) . fileinode(__FILE__);
// We use the salt from settings.php to make the HMAC key, since
// the database is not yet initialized and we can't access the configuration
// system. The file properties add more entropy not easily accessible to
// others.
$key = drupal_get_hash_salt() . filectime(__FILE__) . fileinode(__FILE__);
$time_diff = REQUEST_TIME - $time;
$test_hmac = Crypt::hmacBase64($check_string, $key);
// We can't use Crypt::hmacBase64() yet because this can be called in very
// early bootstrap when autoloader has not been initialized yet.
$test_hmac = base64_encode(hash_hmac('sha256', $check_string, $key, TRUE));
$test_hmac = strtr($test_hmac, array('+' => '-', '/' => '_', '=' => ''));
// Since we are making a local request a 5 second time window is allowed,
// and the HMAC must match.
if ($time_diff >= 0 && $time_diff <= 5 && $hmac === $test_hmac) {
if ($time_diff >= 0 && $time_diff <= 5 && $hmac == $test_hmac) {
$test_prefix = $prefix;
_drupal_load_test_overrides($test_prefix);
return $test_prefix;
}
}
$test_prefix = FALSE;
return $test_prefix;
}
/**
* Overrides low-level and environment-specific configuration.
*
* Very strictly for internal use only.
*
* Loads settings.php from the simpletest public files directory. These files
* can change the global $config_directories, the return value of conf_path(),
* settings(), and $config overrides.
*
* @param string $test_prefix
* The simpletest prefix.
*/
function _drupal_load_test_overrides($test_prefix) {
global $config_directories, $config;
// Do not use the parent site's config directories. Use only the child site's.
// @see \Drupal\simpletest\TestBase::prepareConfigDirectories()
$path_prefix = 'simpletest/' . substr($test_prefix, 10);
$config_directories = array();
foreach (array(CONFIG_ACTIVE_DIRECTORY, CONFIG_STAGING_DIRECTORY) as $type) {
$config_directories[$type] = conf_path() . '/files/' . $path_prefix . '/config_' . $type;
}
// Check for and load a settings.php file in the simpletest files directory.
$filename = conf_path() . '/files/' . $path_prefix . '/settings.php';
if (file_exists($filename)) {
$settings = settings()->getAll();
$conf_path = &drupal_static('conf_path');
// This can override $config, $conf_path, $settings, and $config_directories.
include $filename;
// Keep the overriden $conf_path alive across drupal_static_reset() calls.
// @see conf_path()
$settings['simpletest_conf_path'] = $conf_path;
new Settings($settings);
}
}
/**
* Generates a user agent string with a HMAC and timestamp for simpletest.
*/
function drupal_generate_test_ua($prefix) {
static $key, $last_prefix;
static $key;
if (!isset($key) || $last_prefix != $prefix) {
$last_prefix = $prefix;
$key_file = DRUPAL_ROOT . '/sites/simpletest/' . substr($prefix, 10) . '/.htkey';
// When issuing an outbound HTTP client request from within an inbound test
// request, then the outbound request has to use the same User-Agent header
// as the inbound request. A newly generated private key for the same test
// prefix would invalidate all subsequent inbound requests.
// @see \Drupal\Core\Http\Plugin\SimpletestHttpRequestSubscriber
if (DRUPAL_TEST_IN_CHILD_SITE && $parent_prefix = drupal_valid_test_ua()) {
if ($parent_prefix != $prefix) {
throw new \RuntimeException("Malformed User-Agent: Expected '$parent_prefix' but got '$prefix'.");
}
// If the file is not readable, a PHP warning is expected in this case.
$private_key = file_get_contents($key_file);
}
else {
// Generate and save a new hash salt for a test run.
// Consumed by drupal_valid_test_ua() before settings.php is loaded.
$private_key = Crypt::randomStringHashed(55);
file_put_contents($key_file, $private_key);
}
// The file properties add more entropy not easily accessible to others.
$key = $private_key . filectime(__FILE__) . fileinode(__FILE__);
if (!isset($key)) {
// We use the salt from settings.php to make the HMAC key, since
// the database is not yet initialized and we can't access the configuration
// system. The file properties add more entropy not easily accessible to
// others.
$key = drupal_get_hash_salt() . filectime(__FILE__) . fileinode(__FILE__);
}
// Generate a moderately secure HMAC based on the database credentials.
$salt = uniqid('', TRUE);

View File

@ -3071,6 +3071,14 @@ function _drupal_bootstrap_code() {
// Make sure all stream wrappers are registered.
file_get_stream_wrappers();
// Now that stream wrappers are registered, log fatal errors from a simpletest
// child site to a test specific file directory.
$test_info = &$GLOBALS['drupal_test_info'];
if (!empty($test_info['in_child_site'])) {
ini_set('log_errors', 1);
ini_set('error_log', 'public://error.log');
}
// Set the allowed protocols once we have the config available.
$allowed_protocols = \Drupal::config('system.filter')->get('protocols');
if (!isset($allowed_protocols)) {

View File

@ -141,7 +141,8 @@ function _drupal_log_error($error, $fatal = FALSE) {
// When running inside the testing framework, we relay the errors
// to the tested site by the way of HTTP headers.
if (DRUPAL_TEST_IN_CHILD_SITE && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
$test_info = &$GLOBALS['drupal_test_info'];
if (!empty($test_info['in_child_site']) && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
// $number does not use drupal_static as it should not be reset
// as it uniquely identifies each PHP error.
static $number = 0;

View File

@ -276,18 +276,19 @@ function install_begin_request(&$install_state) {
// which will be used for installing Drupal.
conf_path(FALSE);
drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
// If the hash salt leaks, it becomes possible to forge a valid testing user
// agent, install a new copy of Drupal, and take over the original site.
// The user agent header is used to pass a database prefix in the request when
// running tests. However, for security reasons, it is imperative that no
// installation be permitted using such a prefix.
if ($install_state['interactive'] && strpos($request->server->get('HTTP_USER_AGENT'), 'simpletest') !== FALSE && !drupal_valid_test_ua()) {
// agent, install a new copy of Drupal, and take over the original site. To
// avoid this yet allow for automated testing of the installer, make sure
// there is also a special test-specific settings.php overriding conf_path().
// _drupal_load_test_overrides() sets the simpletest_conf_path in-memory
// setting in this case.
if ($install_state['interactive'] && drupal_valid_test_ua() && !settings()->get('simpletest_conf_path')) {
header($request->server->get('SERVER_PROTOCOL') . ' 403 Forbidden');
exit;
}
drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
// Ensure that procedural dependencies are loaded as early as possible,
// since the error/exception handlers depend on them.
require_once __DIR__ . '/../modules/system/system.install';
@ -1079,6 +1080,7 @@ function install_verify_database_settings() {
global $databases;
if (!empty($databases)) {
$database = $databases['default']['default'];
drupal_static_reset('conf_path');
$settings_file = './' . conf_path(FALSE) . '/settings.php';
$errors = install_database_errors($database, $settings_file);
if (empty($errors)) {
@ -1101,6 +1103,7 @@ function install_verify_database_settings() {
function install_settings_form($form, &$form_state, &$install_state) {
global $databases;
drupal_static_reset('conf_path');
$conf_path = './' . conf_path(FALSE);
$settings_file = $conf_path . '/settings.php';
$database = isset($databases['default']['default']) ? $databases['default']['default'] : array();
@ -1162,6 +1165,12 @@ function install_settings_form($form, &$form_state, &$install_state) {
function install_settings_form_validate($form, &$form_state) {
$driver = $form_state['values']['driver'];
$database = $form_state['values'][$driver];
// When testing the interactive installer, copy the database password and
// the test prefix.
if ($test_prefix = drupal_valid_test_ua()) {
$database['prefix'] = $test_prefix;
$database['password'] = $GLOBALS['databases']['default']['default']['password'];
}
$drivers = drupal_get_database_types();
$reflection = new \ReflectionClass($drivers[$driver]);
$install_namespace = $reflection->getNamespaceName();
@ -1228,14 +1237,33 @@ function install_settings_form_submit($form, &$form_state) {
// Update global settings array and save.
$settings = array();
$database = $form_state['storage']['database'];
$settings['databases']['default']['default'] = (object) array(
'value' => $database,
'required' => TRUE,
);
$settings['drupal_hash_salt'] = (object) array(
'value' => Crypt::randomStringHashed(55),
'required' => TRUE,
);
// Ideally, there is no difference between the code executed by the
// automated test browser and an ordinary browser. However, the database
// settings need a different format and also need to skip the password
// when testing. The hash salt also needs to be skipped because the original
// salt is used to verify the validity of the automated test browser.
// Because of these, there's a little difference in the code following but
// it is small and self-contained.
if ($test_prefix = drupal_valid_test_ua()) {
foreach ($form_state['storage']['database'] as $k => $v) {
if ($k != 'password') {
$settings['databases']['default']['default'][$k] = (object) array(
'value' => $v,
'required' => TRUE,
);
}
}
}
else {
$settings['databases']['default']['default'] = (object) array(
'value' => $database,
'required' => TRUE,
);
$settings['drupal_hash_salt'] = (object) array(
'value' => Crypt::randomStringHashed(55),
'required' => TRUE,
);
}
// Remember the profile which was used.
$settings['settings'] = array(
@ -2353,7 +2381,7 @@ function install_check_requirements($install_state) {
if (!$install_state['settings_verified']) {
$readable = FALSE;
$writable = FALSE;
$conf_path = './' . conf_path(FALSE);
$conf_path = './' . conf_path(FALSE, TRUE);
$settings_file = $conf_path . '/settings.php';
$default_settings_file = './sites/default/default.settings.php';
$file = $conf_path;

View File

@ -292,7 +292,8 @@ class ExceptionController extends HtmlControllerBase implements ContainerAwareIn
// When running inside the testing framework, we relay the errors
// to the tested site by the way of HTTP headers.
if (DRUPAL_TEST_IN_CHILD_SITE && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
$test_info = &$GLOBALS['drupal_test_info'];
if (!empty($test_info['in_child_site']) && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
// $number does not use drupal_static as it should not be reset
// as it uniquely identifies each PHP error.
static $number = 0;

View File

@ -49,7 +49,7 @@ class Tasks extends InstallTasks {
// Make the text more accurate for SQLite.
$form['database']['#title'] = t('Database file');
$form['database']['#description'] = t('The absolute path to the file where @drupal data will be stored. This must be writable by the web server and should exist outside of the web root.', array('@drupal' => drupal_install_profile_distribution_name()));
$default_database = conf_path(FALSE) . '/files/.ht.sqlite';
$default_database = conf_path(FALSE, TRUE) . '/files/.ht.sqlite';
$form['database']['#default_value'] = empty($database['database']) ? $default_database : $database['database'];
return $form;
}

View File

@ -335,13 +335,20 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
}
/**
* Returns the classname based on environment.
* Returns the classname based on environment and testing prefix.
*
* @return string
* The class name.
*/
protected function getClassName() {
$parts = array('service_container', $this->environment);
// Make sure to use a testing-specific container even in the parent site.
if (!empty($GLOBALS['drupal_test_info']['test_run_id'])) {
$parts[] = $GLOBALS['drupal_test_info']['test_run_id'];
}
elseif ($prefix = drupal_valid_test_ua()) {
$parts[] = $prefix;
}
return implode('_', $parts);
}

View File

@ -34,8 +34,9 @@ class SimpletestHttpRequestSubscriber implements EventSubscriberInterface {
// user-agent is used to ensure that multiple testing sessions running at the
// same time won't interfere with each other as they would if the database
// prefix were stored statically in a file or database variable.
if ($test_prefix = drupal_valid_test_ua()) {
$event['request']->setHeader('User-Agent', drupal_generate_test_ua($test_prefix));
$test_info = &$GLOBALS['drupal_test_info'];
if (!empty($test_info['test_run_id'])) {
$event['request']->setHeader('User-Agent', drupal_generate_test_ua($test_info['test_run_id']));
}
}
}

View File

@ -38,6 +38,13 @@ class PublicStream extends LocalStream {
*/
public static function basePath() {
$base_path = settings()->get('file_public_path', conf_path() . '/files');
if ($test_prefix = drupal_valid_test_ua()) {
// Append the testing suffix unless already given.
// @see \Drupal\simpletest\WebTestBase::setUp()
if (strpos($base_path, '/simpletest/' . substr($test_prefix, 10)) === FALSE) {
return $base_path . '/simpletest/' . substr($test_prefix, 10);
}
}
return $base_path;
}

View File

@ -95,12 +95,11 @@ class FieldImportCreateTest extends FieldUnitTestBase {
// Add the new files to the staging directory.
$src_dir = drupal_get_path('module', 'field_test_config') . '/staging';
$target_dir = $this->configDirectories[CONFIG_STAGING_DIRECTORY];
$this->assertTrue(file_unmanaged_copy("$src_dir/$field_config_name.yml", "$target_dir/$field_config_name.yml"));
$this->assertTrue(file_unmanaged_copy("$src_dir/$instance_config_name.yml", "$target_dir/$instance_config_name.yml"));
$this->assertTrue(file_unmanaged_copy("$src_dir/$field_config_name_2.yml", "$target_dir/$field_config_name_2.yml"));
$this->assertTrue(file_unmanaged_copy("$src_dir/$instance_config_name_2a.yml", "$target_dir/$instance_config_name_2a.yml"));
$this->assertTrue(file_unmanaged_copy("$src_dir/$instance_config_name_2b.yml", "$target_dir/$instance_config_name_2b.yml"));
$this->assertTrue(file_unmanaged_copy("$src_dir/$field_config_name.yml", "public://config_staging/$field_config_name.yml"));
$this->assertTrue(file_unmanaged_copy("$src_dir/$instance_config_name.yml", "public://config_staging/$instance_config_name.yml"));
$this->assertTrue(file_unmanaged_copy("$src_dir/$field_config_name_2.yml", "public://config_staging/$field_config_name_2.yml"));
$this->assertTrue(file_unmanaged_copy("$src_dir/$instance_config_name_2a.yml", "public://config_staging/$instance_config_name_2a.yml"));
$this->assertTrue(file_unmanaged_copy("$src_dir/$instance_config_name_2b.yml", "public://config_staging/$instance_config_name_2b.yml"));
// Import the content of the staging directory.
$this->configImporter()->import();

View File

@ -16,7 +16,7 @@ use Drupal\Core\StreamWrapper\LocalReadOnlyStream;
*/
class DummyReadOnlyStreamWrapper extends LocalReadOnlyStream {
function getDirectoryPath() {
return conf_path() . '/files';
return 'sites/default/files';
}
/**

View File

@ -16,7 +16,7 @@ use Drupal\Core\StreamWrapper\LocalStream;
*/
class DummyStreamWrapper extends LocalStream {
function getDirectoryPath() {
return conf_path() . '/files';
return 'sites/default/files';
}
/**

View File

@ -23,6 +23,13 @@ class LanguageNegotiationInfoTest extends WebTestBase {
*/
public static $modules = array('language');
/**
* The language manager.
*
* @var \Drupal\language\ConfigurableLanguageManagerInterface
*/
protected $languageManager;
/**
* {@inheritdoc}
*/
@ -39,60 +46,31 @@ class LanguageNegotiationInfoTest extends WebTestBase {
*/
function setUp() {
parent::setUp();
$this->languageManager = $this->container->get('language_manager');
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'view the administration theme'));
$this->drupalLogin($admin_user);
$this->drupalPostForm('admin/config/regional/language/add', array('predefined_langcode' => 'it'), t('Add language'));
}
/**
* Returns the configurable language manager.
*
* @return \Drupal\language\ConfigurableLanguageManager
*/
protected function languageManager() {
return $this->container->get('language_manager');
}
/**
* Sets state flags for language_test module.
*
* Ensures to correctly update data both in the child site and the test runner
* environment.
*
* @param array $values
* The key/value pairs to set in state.
*/
protected function stateSet(array $values) {
// Set the new state values.
$this->container->get('state')->setMultiple($values);
// Refresh in-memory static state/config caches and static variables.
$this->refreshVariables();
// Refresh/rewrite language negotiation configuration, in order to pick up
// the manipulations performed by language_test module's info alter hooks.
$this->container->get('language_negotiator')->purgeConfiguration();
}
/**
* Tests alterations to language types/negotiation info.
*/
function testInfoAlterations() {
$this->stateSet(array(
// Enable language_test type info.
'language_test.language_types' => TRUE,
// Enable language_test negotiation info (not altered yet).
'language_test.language_negotiation_info' => TRUE,
// Alter Language::TYPE_CONTENT to be configurable.
'language_test.content_language_type' => TRUE,
));
$this->container->get('module_handler')->install(array('language_test'));
$this->rebuildContainer();
// Enable language type/negotiation info alterations.
\Drupal::state()->set('language_test.language_types', TRUE);
\Drupal::state()->set('language_test.language_negotiation_info', TRUE);
$this->languageNegotiationUpdate();
// Check that fixed language types are properly configured without the need
// of saving the language negotiation settings.
$this->checkFixedLanguageTypes();
// Make the content language type configurable by updating the language
// negotiation settings with the proper flag enabled.
\Drupal::state()->set('language_test.content_language_type', TRUE);
$this->languageNegotiationUpdate();
$type = Language::TYPE_CONTENT;
$language_types = $this->languageManager()->getLanguageTypes();
$language_types = $this->languageManager->getLanguageTypes();
$this->assertTrue(in_array($type, $language_types), 'Content language type is configurable.');
// Enable some core and custom language negotiation methods. The test
@ -109,34 +87,30 @@ class LanguageNegotiationInfoTest extends WebTestBase {
);
$this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
// Alter language negotiation info to remove interface language negotiation
// method.
$this->stateSet(array(
'language_test.language_negotiation_info_alter' => TRUE,
));
$negotiation = $this->container->get('config.factory')->get('language.types')->get('negotiation.' . $type . '.enabled');
// Remove the interface language negotiation method by updating the language
// negotiation settings with the proper flag enabled.
\Drupal::state()->set('language_test.language_negotiation_info_alter', TRUE);
$this->languageNegotiationUpdate();
$negotiation = \Drupal::config('language.types')->get('negotiation.' . $type . '.enabled') ?: array();
$this->assertFalse(isset($negotiation[$interface_method_id]), 'Interface language negotiation method removed from the stored settings.');
$this->drupalGet('admin/config/regional/language/detection');
$this->assertNoFieldByName($form_field, NULL, 'Interface language negotiation method unavailable.');
$this->assertNoFieldByXPath("//input[@name=\"$form_field\"]", NULL, 'Interface language negotiation method unavailable.');
// Check that type-specific language negotiation methods can be assigned
// only to the corresponding language types.
foreach ($this->languageManager()->getLanguageTypes() as $type) {
foreach ($this->languageManager->getLanguageTypes() as $type) {
$form_field = $type . '[enabled][test_language_negotiation_method_ts]';
if ($type == $test_type) {
$this->assertFieldByName($form_field, NULL, format_string('Type-specific test language negotiation method available for %type.', array('%type' => $type)));
$this->assertFieldByXPath("//input[@name=\"$form_field\"]", NULL, format_string('Type-specific test language negotiation method available for %type.', array('%type' => $type)));
}
else {
$this->assertNoFieldByName($form_field, NULL, format_string('Type-specific test language negotiation method unavailable for %type.', array('%type' => $type)));
$this->assertNoFieldByXPath("//input[@name=\"$form_field\"]", NULL, format_string('Type-specific test language negotiation method unavailable for %type.', array('%type' => $type)));
}
}
// Check language negotiation results.
$this->drupalGet('');
$last = $this->container->get('state')->get('language_test.language_negotiation_last');
foreach ($this->languageManager()->getDefinedLanguageTypes() as $type) {
$last = \Drupal::state()->get('language_test.language_negotiation_last');
foreach ($this->languageManager->getDefinedLanguageTypes() as $type) {
$langcode = $last[$type];
$value = $type == Language::TYPE_CONTENT || strpos($type, 'test') !== FALSE ? 'it' : 'en';
$this->assertEqual($langcode, $value, format_string('The negotiated language for %type is %language', array('%type' => $type, '%language' => $value)));
@ -144,11 +118,10 @@ class LanguageNegotiationInfoTest extends WebTestBase {
// Uninstall language_test and check that everything is set back to the
// original status.
$this->container->get('module_handler')->uninstall(array('language_test'));
$this->rebuildContainer();
$this->languageNegotiationUpdate('uninstall');
// Check that only the core language types are available.
foreach ($this->languageManager()->getDefinedLanguageTypes() as $type) {
foreach ($this->languageManager->getDefinedLanguageTypes() as $type) {
$this->assertTrue(strpos($type, 'test') === FALSE, format_string('The %type language is still available', array('%type' => $type)));
}
@ -158,7 +131,7 @@ class LanguageNegotiationInfoTest extends WebTestBase {
// Check that unavailable language negotiation methods are not present in
// the negotiation settings.
$negotiation = $this->container->get('config.factory')->get('language.types')->get('negotiation.' . $type . '.enabled');
$negotiation = \Drupal::config('language.types')->get('negotiation.' . $type . '.enabled') ?: array();
$this->assertFalse(isset($negotiation[$test_method_id]), 'The disabled test language negotiation method is not part of the content language negotiation settings.');
// Check that configuration page presents the correct options and settings.
@ -166,14 +139,41 @@ class LanguageNegotiationInfoTest extends WebTestBase {
$this->assertNoRaw(t('This is a test language negotiation method'), 'No test language negotiation method available.');
}
/**
* Update language types/negotiation information.
*
* Manually invoke language_modules_installed()/language_modules_uninstalled()
* since they would not be invoked after installing/uninstalling language_test
* the first time.
*/
protected function languageNegotiationUpdate($op = 'install') {
static $last_op = NULL;
$modules = array('language_test');
// Install/uninstall language_test only if we did not already before.
if ($last_op != $op) {
call_user_func(array($this->container->get('module_handler'), $op), $modules);
$last_op = $op;
}
else {
$function = "language_modules_{$op}ed";
if (function_exists($function)) {
$function($modules);
}
}
$this->languageManager->reset();
$this->drupalGet('admin/config/regional/language/detection');
}
/**
* Check that language negotiation for fixed types matches the stored one.
*/
protected function checkFixedLanguageTypes() {
$configurable = $this->languageManager()->getLanguageTypes();
foreach ($this->languageManager()->getDefinedLanguageTypesInfo() as $type => $info) {
$configurable = $this->languageManager->getLanguageTypes();
foreach ($this->languageManager->getDefinedLanguageTypesInfo() as $type => $info) {
if (!in_array($type, $configurable) && isset($info['fixed'])) {
$negotiation = $this->container->get('config.factory')->get('language.types')->get('negotiation.' . $type . '.enabled');
$negotiation = \Drupal::config('language.types')->get('negotiation.' . $type . '.enabled') ?: array();
$equal = count($info['fixed']) == count($negotiation);
while ($equal && list($id) = each($negotiation)) {
list(, $info_id) = each($info['fixed']);

View File

@ -71,8 +71,7 @@ class NodeImportCreateTest extends DrupalUnitTestBase {
$this->copyConfig($active, $staging);
// Manually add new node type.
$src_dir = drupal_get_path('module', 'node_test_config') . '/staging';
$target_dir = $this->configDirectories[CONFIG_STAGING_DIRECTORY];
$this->assertTrue(file_unmanaged_copy("$src_dir/$node_type_config_name.yml", "$target_dir/$node_type_config_name.yml"));
$this->assertTrue(file_unmanaged_copy("$src_dir/$node_type_config_name.yml", "public://config_staging/$node_type_config_name.yml"));
// Import the content of the staging directory.
$this->configImporter()->import();

View File

@ -57,13 +57,6 @@ abstract class DrupalUnitTestBase extends UnitTestBase {
private $themeFiles;
private $themeData;
/**
* The configuration directories for this test run.
*
* @var array
*/
protected $configDirectories = array();
/**
* A KeyValueMemoryFactory instance to use when building the container.
*
@ -100,27 +93,6 @@ abstract class DrupalUnitTestBase extends UnitTestBase {
}
}
/**
* Create and set new configuration directories.
*
* @see config_get_config_directory()
*/
protected function prepareConfigDirectories() {
$this->configDirectories = array();
include_once DRUPAL_ROOT . '/core/includes/install.inc';
foreach (array(CONFIG_ACTIVE_DIRECTORY, CONFIG_STAGING_DIRECTORY) as $type) {
// Assign the relative path to the global variable.
$path = $this->siteDirectory . '/config_' . $type;
$GLOBALS['config_directories'][$type] = $path;
// Ensure the directory can be created and is writeable.
if (!install_ensure_config_directory($type)) {
throw new \RuntimeException("Failed to create '$type' config directory $path");
}
// Provide the already resolved path for tests.
$this->configDirectories[$type] = $path;
}
}
/**
* Sets up Drupal unit test environment.
*/
@ -129,9 +101,6 @@ abstract class DrupalUnitTestBase extends UnitTestBase {
parent::setUp();
// Create and set new configuration directories.
$this->prepareConfigDirectories();
// Build a minimal, partially mocked environment for unit tests.
$this->containerBuild(\Drupal::getContainer());
// Make sure it survives kernel rebuilds.
@ -188,9 +157,7 @@ abstract class DrupalUnitTestBase extends UnitTestBase {
}
protected function tearDown() {
if ($this->kernel instanceof DrupalKernel) {
$this->kernel->shutdown();
}
$this->kernel->shutdown();
// Before tearing down the test environment, ensure that no stream wrapper
// of this test leaks into the parent environment. Unlike all other global
// state variables in Drupal, stream wrappers are a global state construct

View File

@ -36,13 +36,6 @@ abstract class TestBase {
*/
protected $testId;
/**
* The site directory of this test run.
*
* @var string
*/
protected $siteDirectory = NULL;
/**
* The database prefix of this test run.
*
@ -158,13 +151,6 @@ abstract class TestBase {
*/
public $dieOnFail = FALSE;
/**
* The DrupalKernel instance used in the test.
*
* @var \Drupal\Core\DrupalKernel
*/
protected $kernel;
/**
* The dependency injection container used in the test.
*
@ -622,20 +608,7 @@ abstract class TestBase {
return $this->assertTrue($identical, $message, $group);
}
/**
* Asserts that no errors have been logged to the PHP error.log thus far.
*
* @return bool
* TRUE if the assertion succeeded, FALSE otherwise.
*
* @see TestBase::prepareEnvironment()
* @see _drupal_bootstrap_configuration()
*/
protected function assertNoErrorsLogged() {
// Since PHP only creates the error.log file when an actual error is
// triggered, it is sufficient to check whether the file exists.
return $this->assertFalse(file_exists(DRUPAL_ROOT . '/' . $this->siteDirectory . '/error.log'), 'PHP error.log is empty.');
}
/**
* Fire an assertion that is always positive.
@ -884,9 +857,7 @@ abstract class TestBase {
* @see drupal_valid_test_ua()
*/
private function prepareDatabasePrefix() {
$suffix = mt_rand(1000, 1000000);
$this->siteDirectory = 'sites/simpletest/' . $suffix;
$this->databasePrefix = 'simpletest' . $suffix;
$this->databasePrefix = 'simpletest' . mt_rand(1000, 1000000);
// As soon as the database prefix is set, the test might start to execute.
// All assertions as well as the SimpleTest batch operations are associated
@ -914,9 +885,19 @@ abstract class TestBase {
$connection_info = Database::getConnectionInfo('default');
Database::renameConnection('default', 'simpletest_original_default');
foreach ($connection_info as $target => $value) {
$connection_info[$target]['prefix'] = $value['prefix']['default'] . $this->databasePrefix;
$connection_info[$target]['prefix'] = array(
'default' => $value['prefix']['default'] . $this->databasePrefix,
);
}
Database::addConnectionInfo('default', 'default', $connection_info['default']);
// Additionally override global $databases, since the installer does not use
// the Database connection info.
// @see install_verify_database_settings()
// @see install_database_errors()
// @todo Fix installer to use Database connection info.
global $databases;
$databases['default']['default'] = $connection_info['default'];
}
/**
@ -957,9 +938,10 @@ abstract class TestBase {
$language_interface = language(Language::TYPE_INTERFACE);
// When running the test runner within a test, back up the original database
// prefix.
if (DRUPAL_TEST_IN_CHILD_SITE) {
// prefix and re-set the new/nested prefix in drupal_valid_test_ua().
if (drupal_valid_test_ua()) {
$this->originalPrefix = drupal_valid_test_ua();
drupal_valid_test_ua($this->databasePrefix);
}
// Backup current in-memory configuration.
@ -999,16 +981,22 @@ abstract class TestBase {
// Create test directory ahead of installation so fatal errors and debug
// information can be logged during installation process.
file_prepare_directory($this->siteDirectory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
// Prepare filesystem directory paths.
$this->public_files_directory = $this->siteDirectory . '/files';
$this->private_files_directory = $this->siteDirectory . '/private';
$this->temp_files_directory = $this->siteDirectory . '/temp';
$this->translation_files_directory = $this->siteDirectory . '/translations';
// Use temporary files directory with the same prefix as the database.
$this->public_files_directory = $this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10);
$this->private_files_directory = $this->public_files_directory . '/private';
$this->temp_files_directory = $this->private_files_directory . '/temp';
$this->translation_files_directory = $this->public_files_directory . '/translations';
// Create the directories
file_prepare_directory($this->public_files_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
file_prepare_directory($this->private_files_directory, FILE_CREATE_DIRECTORY);
file_prepare_directory($this->temp_files_directory, FILE_CREATE_DIRECTORY);
file_prepare_directory($this->translation_files_directory, FILE_CREATE_DIRECTORY);
$this->generatedTestFiles = FALSE;
// Create and set new configuration directories.
$this->prepareConfigDirectories();
// Unregister all custom stream wrappers of the parent site.
// Availability of Drupal stream wrappers varies by test base class:
// - UnitTestBase operates in a completely empty environment.
@ -1052,31 +1040,53 @@ abstract class TestBase {
\Drupal::setContainer($this->container);
// Unset globals.
unset($GLOBALS['config_directories']);
unset($GLOBALS['theme_key']);
unset($GLOBALS['theme']);
// Log fatal errors.
ini_set('log_errors', 1);
ini_set('error_log', DRUPAL_ROOT . '/' . $this->siteDirectory . '/error.log');
ini_set('error_log', $this->public_files_directory . '/error.log');
// Set the test information for use in other parts of Drupal.
$test_info = &$GLOBALS['drupal_test_info'];
$test_info['test_run_id'] = $this->databasePrefix;
$test_info['in_child_site'] = FALSE;
// Change the database prefix.
// All static variables need to be reset before the database prefix is
// changed, since \Drupal\Core\Utility\CacheArray implementations attempt to
// write back to persistent caches when they are destructed.
$this->changeDatabasePrefix();
// Remove all configuration overrides.
$GLOBALS['config'] = array();
// After preparing the environment and changing the database prefix, we are
// in a valid test environment.
drupal_valid_test_ua($this->databasePrefix);
conf_path(FALSE, TRUE);
drupal_set_time_limit($this->timeLimit);
}
/**
* Create and set new configuration directories.
*
* The child site uses drupal_valid_test_ua() to adjust the config directory
* paths to a test-prefix-specific directory within the public files
* directory.
*
* @see config_get_config_directory()
*/
protected function prepareConfigDirectories() {
$GLOBALS['config_directories'] = array();
$this->configDirectories = array();
include_once DRUPAL_ROOT . '/core/includes/install.inc';
foreach (array(CONFIG_ACTIVE_DIRECTORY, CONFIG_STAGING_DIRECTORY) as $type) {
// Assign the relative path to the global variable.
$path = conf_path() . '/files/simpletest/' . substr($this->databasePrefix, 10) . '/config_' . $type;
$GLOBALS['config_directories'][$type] = $path;
// Ensure the directory can be created and is writeable.
if (!install_ensure_config_directory($type)) {
return FALSE;
}
// Provide the already resolved path for tests.
$this->configDirectories[$type] = $path;
}
}
/**
* Rebuild \Drupal::getContainer().
*
@ -1093,11 +1103,11 @@ abstract class TestBase {
* tests can invoke this workaround when requiring services from newly
* enabled modules to be immediately available in the same request.
*/
protected function rebuildContainer($environment = 'testing') {
protected function rebuildContainer() {
// Preserve the request object after the container rebuild.
$request = \Drupal::request();
$this->kernel = new DrupalKernel($environment, drupal_classloader(), FALSE);
$this->kernel = new DrupalKernel('testing', drupal_classloader(), FALSE);
$this->kernel->boot();
// DrupalKernel replaces the container in \Drupal::getContainer() with a
// different object, so we need to replace the instance on this test class.
@ -1159,12 +1169,8 @@ abstract class TestBase {
}
}
// In case a fatal error occurred that was not in the test process read the
// log to pick up any fatal errors.
simpletest_log_read($this->testId, $this->databasePrefix, get_class($this));
// Delete test site directory.
file_unmanaged_delete_recursive($this->siteDirectory, array($this, 'filePreDeleteCallback'));
// Delete temporary files directory.
file_unmanaged_delete_recursive($this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10), array($this, 'filePreDeleteCallback'));
// Restore original database connection.
Database::removeConnection('default');
@ -1193,13 +1199,19 @@ abstract class TestBase {
\Drupal::setContainer($this->originalContainer);
$GLOBALS['config_directories'] = $this->originalConfigDirectories;
// Re-initialize original stream wrappers of the parent site.
// This must happen after static variables have been reset and the original
// container and $config_directories are restored, as simpletest_log_read()
// uses the public stream wrapper to locate the error.log.
file_get_stream_wrappers();
// In case a fatal error occurred that was not in the test process read the
// log to pick up any fatal errors.
simpletest_log_read($this->testId, $this->databasePrefix, get_class($this), TRUE);
if (isset($this->originalPrefix)) {
drupal_valid_test_ua($this->originalPrefix);
}
else {
drupal_valid_test_ua(FALSE);
}
conf_path(TRUE, TRUE);
// Restore original shutdown callbacks.
$callbacks = &drupal_register_shutdown_function();

View File

@ -28,13 +28,6 @@ class BrokenSetUpTest extends WebTestBase {
*/
public static $modules = array('simpletest');
/**
* The path to the shared trigger file.
*
* @var string
*/
protected $sharedTriggerFile;
public static function getInfo() {
return array(
'name' => 'Broken SimpleTest method',
@ -45,20 +38,15 @@ class BrokenSetUpTest extends WebTestBase {
function setUp() {
// If the test is being run from the main site, set up normally.
if (!$this->isInChildSite()) {
if (!drupal_valid_test_ua()) {
parent::setUp();
$this->sharedTriggerFile = $this->public_files_directory . '/trigger';
// Create and log in user.
$admin_user = $this->drupalCreateUser(array('administer unit tests'));
$this->drupalLogin($admin_user);
}
// If the test is being run from within simpletest, set up the broken test.
else {
$this->sharedTriggerFile = $this->originalFileDirectory . '/trigger';
if (file_get_contents($this->sharedTriggerFile) === 'setup') {
if (file_get_contents($this->originalFileDirectory . '/simpletest/trigger') === 'setup') {
throw new \Exception('Broken setup');
}
$this->pass('The setUp() method has run.');
@ -67,13 +55,13 @@ class BrokenSetUpTest extends WebTestBase {
function tearDown() {
// If the test is being run from the main site, tear down normally.
if (!$this->isInChildSite()) {
unlink($this->sharedTriggerFile);
if (!drupal_valid_test_ua()) {
unlink($this->originalFileDirectory . '/simpletest/trigger');
parent::tearDown();
}
// If the test is being run from within simpletest, output a message.
else {
if (file_get_contents($this->sharedTriggerFile) === 'teardown') {
if (file_get_contents($this->originalFileDirectory . '/simpletest/trigger') === 'teardown') {
throw new \Exception('Broken teardown');
}
$this->pass('The tearDown() method has run.');
@ -86,9 +74,9 @@ class BrokenSetUpTest extends WebTestBase {
function testMethod() {
// If the test is being run from the main site, run it again from the web
// interface within the simpletest child site.
if (!$this->isInChildSite()) {
if (!drupal_valid_test_ua()) {
// Verify that a broken setUp() method is caught.
file_put_contents($this->sharedTriggerFile, 'setup');
file_put_contents($this->originalFileDirectory . '/simpletest/trigger', 'setup');
$edit['Drupal\simpletest\Tests\BrokenSetUpTest'] = TRUE;
$this->drupalPostForm('admin/config/development/testing', $edit, t('Run tests'));
$this->assertRaw('Broken setup');
@ -99,7 +87,7 @@ class BrokenSetUpTest extends WebTestBase {
$this->assertNoRaw('The tearDown() method has run.');
// Verify that a broken tearDown() method is caught.
file_put_contents($this->sharedTriggerFile, 'teardown');
file_put_contents($this->originalFileDirectory . '/simpletest/trigger', 'teardown');
$edit['Drupal\simpletest\Tests\BrokenSetUpTest'] = TRUE;
$this->drupalPostForm('admin/config/development/testing', $edit, t('Run tests'));
$this->assertNoRaw('Broken setup');
@ -110,7 +98,7 @@ class BrokenSetUpTest extends WebTestBase {
$this->assertNoRaw('The tearDown() method has run.');
// Verify that a broken test method is caught.
file_put_contents($this->sharedTriggerFile, 'test');
file_put_contents($this->originalFileDirectory . '/simpletest/trigger', 'test');
$edit['Drupal\simpletest\Tests\BrokenSetUpTest'] = TRUE;
$this->drupalPostForm('admin/config/development/testing', $edit, t('Run tests'));
$this->assertNoRaw('Broken setup');
@ -122,7 +110,7 @@ class BrokenSetUpTest extends WebTestBase {
}
// If the test is being run from within simpletest, output a message.
else {
if (file_get_contents($this->sharedTriggerFile) === 'test') {
if (file_get_contents($this->originalFileDirectory . '/simpletest/trigger') === 'test') {
throw new \Exception('Broken test');
}
$this->pass('The test method has run.');

View File

@ -39,7 +39,7 @@ class MissingCheckedRequirementsTest extends WebTestBase {
* Overrides checkRequirements().
*/
protected function checkRequirements() {
if ($this->isInChildSite()) {
if (drupal_valid_test_ua()) {
return array(
'Test is not allowed to run.'
);
@ -53,7 +53,7 @@ class MissingCheckedRequirementsTest extends WebTestBase {
protected function testCheckRequirements() {
// If this is the main request, run the web test script and then assert
// that the child tests did not run.
if (!$this->isInChildSite()) {
if (!drupal_valid_test_ua()) {
// Run this test from web interface.
$edit['Drupal\simpletest\Tests\MissingCheckedRequirementsTest'] = TRUE;
$this->drupalPostForm('admin/config/development/testing', $edit, t('Run tests'));

View File

@ -39,7 +39,7 @@ class SimpleTestTest extends WebTestBase {
}
function setUp() {
if (!$this->isInChildSite()) {
if (!$this->inCURL()) {
parent::setUp();
// Create and log in an admin user.
$this->drupalLogin($this->drupalCreateUser(array('administer unit tests')));
@ -54,7 +54,7 @@ class SimpleTestTest extends WebTestBase {
* Test the internal browsers functionality.
*/
function testInternalBrowser() {
if (!$this->isInChildSite()) {
if (!$this->inCURL()) {
// Retrieve the test page and check its title and headers.
$this->drupalGet('test-page');
$this->assertTrue($this->drupalGetHeader('Date'), 'An HTTP header was received.');
@ -91,12 +91,10 @@ class SimpleTestTest extends WebTestBase {
$headers = $this->drupalGetHeaders(TRUE);
$this->assertEqual(count($headers), 2, 'Simpletest stopped following redirects after the first one.');
// Remove the Simpletest private key file so we can test the protection
// Remove the Simpletest settings.php so we can test the protection
// against requests that forge a valid testing user agent to gain access
// to the installer.
// @see drupal_valid_test_ua()
// Not using File API; a potential error must trigger a PHP warning.
unlink($this->siteDirectory . '/.htkey');
drupal_unlink($this->public_files_directory . '/settings.php');
global $base_url;
$this->drupalGet(url($base_url . '/core/install.php', array('external' => TRUE, 'absolute' => TRUE)));
$this->assertResponse(403, 'Cannot access install.php.');
@ -107,7 +105,7 @@ class SimpleTestTest extends WebTestBase {
* Test validation of the User-Agent header we use to perform test requests.
*/
function testUserAgentValidation() {
if (!$this->isInChildSite()) {
if (!$this->inCURL()) {
global $base_url;
$system_path = $base_url . '/' . drupal_get_path('module', 'system');
$HTTP_path = $system_path .'/tests/http.php?q=node';
@ -149,7 +147,7 @@ class SimpleTestTest extends WebTestBase {
$this->valid_permission = 'access content';
$this->invalid_permission = 'invalid permission';
if ($this->isInChildSite()) {
if ($this->inCURL()) {
// Only run following code if this test is running itself through a CURL
// request.
$this->stubTest();
@ -337,4 +335,10 @@ class SimpleTestTest extends WebTestBase {
return trim(html_entity_decode(strip_tags($element->asXML())));
}
/**
* Check if the test is being run from inside a CURL request.
*/
function inCURL() {
return (bool) drupal_valid_test_ua();
}
}

View File

@ -19,6 +19,11 @@ use Drupal\Core\Database\ConnectionNotDefinedException;
*/
abstract class UnitTestBase extends TestBase {
/**
* @var array
*/
protected $configDirectories;
/**
* Constructor for UnitTestBase.
*/
@ -36,7 +41,6 @@ abstract class UnitTestBase extends TestBase {
* setUp() method.
*/
protected function setUp() {
file_prepare_directory($this->public_files_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
$this->settingsSet('file_public_path', $this->public_files_directory);
}
}

View File

@ -767,78 +767,46 @@ abstract class WebTestBase extends TestBase {
$batch = &batch_get();
$batch = array();
// 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.
copy(DRUPAL_ROOT . '/sites/default/default.settings.php', DRUPAL_ROOT . '/' . $this->siteDirectory . '/settings.php');
// 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->public_files_directory,
'required' => TRUE,
);
// Add the parent profile's search path to the child site's search paths.
// @see drupal_system_listing()
$settings['conf']['simpletest.settings']['parent_profile'] = (object) array(
'value' => $this->originalProfile,
'required' => TRUE,
);
$this->writeSettings($settings);
// 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.
drupal_settings_initialize();
$this->settingsSet('file_public_path', $this->public_files_directory);
$GLOBALS['config']['system.file']['path']['private'] = $this->private_files_directory;
$GLOBALS['config']['system.file']['path']['temporary'] = $this->temp_files_directory;
$GLOBALS['config']['locale.settings']['translation']['path'] = $this->translation_files_directory;
// Execute the non-interactive installer.
require_once DRUPAL_ROOT . '/core/includes/install.core.inc';
$this->settingsSet('cache', array('default' => 'cache.backend.memory'));
$parameters = $this->installParameters();
install_drupal($parameters);
// Import new settings.php written by the installer.
drupal_settings_initialize();
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(DRUPAL_ROOT . '/' . $this->siteDirectory, 0777);
// Set the install_profile so that web requests to the requests to the child
// site have the correct profile.
$settings = array(
'settings' => array(
'install_profile' => (object) array(
'value' => $this->profile,
'required' => TRUE,
),
),
);
$this->writeSettings($settings);
// Override install profile in Settings to so the correct profile is used by
// tests.
$this->settingsSet('install_profile', $this->profile);
$this->settingsSet('cache', array());
$this->rebuildContainer();
// 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->private_files_directory, FILE_CREATE_DIRECTORY);
file_prepare_directory($this->temp_files_directory, FILE_CREATE_DIRECTORY);
\Drupal::config('system.file')
->set('path.private', $this->private_files_directory)
->set('path.temporary', $this->temp_files_directory)
->save();
// Manually configure the test mail collector implementation to prevent
// tests from sending out e-mails 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.
\Drupal::config('system.mail')
->set('interface.default', 'Drupal\Core\Mail\TestMailCollector')
->save();
// Restore the original Simpletest batch.
$batch = &batch_get();
$batch = $this->originalBatch;
// Set path variables.
// Set 'parent_profile' of simpletest to add the parent profile's
// search path to the child site's search paths.
// @see drupal_system_listing()
\Drupal::config('simpletest.settings')->set('parent_profile', $this->originalProfile)->save();
// Collect modules to install.
$class = get_class($this);
$modules = array();
@ -855,18 +823,22 @@ abstract class WebTestBase extends TestBase {
$this->rebuildContainer();
}
// Like DRUPAL_BOOTSTRAP_CONFIGURATION above, any further bootstrap phases
// are not re-executed by the installer, as Drupal is bootstrapped already.
// 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. file_get_stream_wrappers().
// @see _drupal_bootstrap_code()
// @see _drupal_bootstrap_full()
// @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.
// Reset/rebuild all data structures after enabling the modules.
$this->resetAll();
// Now make sure that the file path configurations are saved. This is done
// after we install the modules to override default values.
\Drupal::config('system.file')
->set('path.private', $this->private_files_directory)
->set('path.temporary', $this->temp_files_directory)
->save();
\Drupal::config('locale.settings')
->set('translation.path', $this->translation_files_directory)
->save();
// Use the test mail class instead of the default mail handler class.
\Drupal::config('system.mail')->set('interface.default', 'Drupal\Core\Mail\TestMailCollector')->save();
// Temporary fix so that when running from run-tests.sh we don't get an
// empty current path which would indicate we're on the home page.
$path = current_path();
@ -923,29 +895,40 @@ abstract class WebTestBase extends TestBase {
}
/**
* Rewrites the settings.php file of the test site.
* Writes a test-specific settings.php file for the child site.
*
* @param array $settings
* An array of settings to write out, in the format expected by
* drupal_rewrite_settings().
* The child site loads this after the parent site's settings.php, so settings
* here override those.
*
* @param $settings An array of settings to write out, in the format expected
* by drupal_rewrite_settings().
*
* @see _drupal_load_test_overrides()
* @see drupal_rewrite_settings()
*/
protected function writeSettings(array $settings) {
protected function writeSettings($settings) {
// drupal_rewrite_settings() sets the in-memory global variables in addition
// to writing the file. We'll want to restore the original globals.
foreach (array_keys($settings) as $variable_name) {
$original_globals[$variable_name] = isset($GLOBALS[$variable_name]) ? $GLOBALS[$variable_name] : NULL;
}
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);
$filename = $this->public_files_directory . '/settings.php';
file_put_contents($filename, "<?php\n");
drupal_rewrite_settings($settings, $filename);
// Restore the original globals.
foreach ($original_globals as $variable_name => $value) {
$GLOBALS[$variable_name] = $value;
}
}
/**
* Queues custom translations to be written to settings.php.
* Sets custom translations to the settings object and queues them to writing.
*
* Use WebTestBase::writeCustomTranslations() to apply and write the queued
* translations.
* In order for those custom translations to persist (being written in test
* site's settings.php) make sure to also call self::writeCustomTranslations()
*
* @param string $langcode
* The langcode to add translations for.
@ -958,56 +941,32 @@ abstract class WebTestBase extends TestBase {
* '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;
}
$this->settingsSet('locale_custom_strings_' . $langcode, $values);
foreach ($values as $key => $translations) {
foreach ($translations as $label => $value) {
$this->customTranslations['locale_custom_strings_' . $langcode][$key][$label] = (object) array(
'value' => $value,
'required' => TRUE,
);
}
}
}
/**
* Writes custom translations to the test site's settings.php.
*
* Use TestBase::addCustomTranslations() to queue custom translations before
* calling this method.
* Writes custom translations to test site's settings.php.
*/
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);
}
$this->writeSettings(array('settings' => $this->customTranslations));
$this->customTranslations = array();
}
/**
* Overrides \Drupal\simpletest\TestBase::rebuildContainer().
*/
protected function rebuildContainer($environment = 'prod') {
parent::rebuildContainer($environment);
protected function rebuildContainer() {
parent::rebuildContainer();
// Make sure the url generator has a request object, otherwise calls to
// $this->drupalGet() will fail.
$this->prepareRequestForGenerator();
@ -1047,8 +1006,8 @@ abstract class WebTestBase extends TestBase {
// Clear the tag cache.
drupal_static_reset('Drupal\Core\Cache\CacheBackendInterface::tagCache');
$this->container->get('config.factory')->reset();
$this->container->get('state')->resetCache();
\Drupal::service('config.factory')->reset();
\Drupal::state()->resetCache();
}
/**
@ -1311,21 +1270,6 @@ abstract class WebTestBase extends TestBase {
}
}
/**
* Returns whether the test is being executed from within a test site.
*
* Mainly used by recursive tests (i.e. to test the testing framework).
*
* @return bool
* TRUE if this test was instantiated in a request within the test site,
* FALSE otherwise.
*
* @see _drupal_bootstrap_configuration()
*/
protected function isInChildSite() {
return DRUPAL_TEST_IN_CHILD_SITE;
}
/**
* Parse content returned from curlExec using DOM and SimpleXML.
*
@ -1369,18 +1313,10 @@ abstract class WebTestBase extends TestBase {
protected function drupalGet($path, array $options = array(), array $headers = array()) {
$options['absolute'] = TRUE;
// The URL generator service is not necessarily available yet; e.g., in
// interactive installer tests.
if ($this->container->has('url_generator')) {
$url = $this->container->get('url_generator')->generateFromPath($path, $options);
}
else {
$url = $this->getAbsoluteUrl($path);
}
// We re-using a CURL connection here. If that connection still has certain
// options set, it might change the GET into a POST. Make sure we clear out
// previous options.
$url = $this->container->get('url_generator')->generateFromPath($path, $options);
$out = $this->curlExec(array(CURLOPT_HTTPGET => TRUE, CURLOPT_URL => $url, CURLOPT_NOBODY => FALSE, CURLOPT_HTTPHEADER => $headers));
// Ensure that any changes to variables in the other thread are picked up.
$this->refreshVariables();
@ -1900,28 +1836,6 @@ abstract class WebTestBase extends TestBase {
return implode('&', $post);
}
/**
* Transforms a nested array into a flat array suitable for WebTestBase::drupalPostForm().
*
* @param array $values
* A multi-dimensional form values array to convert.
*
* @return array
* The flattened $edit array suitable for WebTestBase::drupalPostForm().
*/
protected function translatePostValues(array $values) {
$edit = array();
// The easiest and most straightforward way to translate values suitable for
// WebTestBase::drupalPostForm() is to actually build the POST data string
// and convert the resulting key/value pairs back into a flat array.
$query = http_build_query($values);
foreach (explode('&', $query) as $item) {
list($key, $value) = explode('=', $item);
$edit[urldecode($key)] = urldecode($value);
}
return $edit;
}
/**
* Runs cron in the Drupal installed by Simpletest.
*/

View File

@ -5,6 +5,18 @@
* Hooks provided by the SimpleTest module.
*/
/**
* Global variable that holds information about the tests being run.
*
* An array, with the following keys:
* - 'test_run_id': the ID of the test being run, in the form 'simpletest_%"
* - 'in_child_site': TRUE if the current request is a cURL request from
* the parent site.
*
* @var array
*/
global $drupal_test_info;
/**
* @addtogroup hooks
* @{

View File

@ -58,28 +58,6 @@ function simpletest_requirements($phase) {
$requirements['php_memory_limit']['description'] = t('The testing framework requires the PHP memory limit to be at least %memory_minimum_limit. The current value is %memory_limit. <a href="@url">Follow these steps to continue</a>.', array('%memory_limit' => $memory_limit, '%memory_minimum_limit' => SIMPLETEST_MINIMUM_PHP_MEMORY_LIMIT, '@url' => 'http://drupal.org/node/207036'));
}
$site_directory = 'sites/simpletest';
if (!drupal_verify_install_file(DRUPAL_ROOT . '/' . $site_directory, FILE_EXIST|FILE_READABLE|FILE_WRITABLE|FILE_EXECUTABLE, 'dir')) {
$requirements['simpletest_site_directory'] = array(
'title' => t('Simpletest site directory'),
'value' => is_dir(DRUPAL_ROOT . '/' . $site_directory) ? t('Not writable') : t('Missing'),
'severity' => REQUIREMENT_ERROR,
'description' => t('The testing framework requires the !sites-simpletest directory to exist and be writable in order to run tests.', array(
'!sites-simpletest' => '<code>./' . check_plain($site_directory) . '</code>',
)),
);
}
elseif (!file_save_htaccess(DRUPAL_ROOT . '/' . $site_directory, FALSE)) {
$requirements['simpletest_site_directory'] = array(
'title' => t('Simpletest site directory'),
'value' => t('Not protected'),
'severity' => REQUIREMENT_ERROR,
'description' => t('The file !file does not exist and could not be created automatically, which poses a security risk. Ensure that the directory is writable.', array(
'!file' => '<code>./' . check_plain($site_directory) . '/.htaccess</code>',
)),
);
}
return $requirements;
}
@ -179,12 +157,8 @@ function simpletest_schema() {
* Implements hook_uninstall().
*/
function simpletest_uninstall() {
// Do not clean the environment in case the Simpletest module is uninstalled
// in a (recursive) test for itself, since simpletest_clean_environment()
// would also delete the test site of the parent test process.
if (!DRUPAL_TEST_IN_CHILD_SITE) {
simpletest_clean_environment();
}
// Delete verbose test output and any other testing framework files.
simpletest_clean_database();
// Remove generated files.
file_unmanaged_delete_recursive('public://simpletest');
}

View File

@ -383,16 +383,18 @@ function simpletest_last_test_get($test_id) {
*
* @param $test_id
* The test ID to which the log relates.
* @param $database_prefix
* @param $prefix
* The database prefix to which the log relates.
* @param $test_class
* The test class to which the log relates.
*
* @param $during_test
* Indicates that the current file directory path is a temporary file
* file directory used during testing.
* @return
* Found any entries in log.
*/
function simpletest_log_read($test_id, $database_prefix, $test_class) {
$log = DRUPAL_ROOT . '/sites/simpletest/' . substr($database_prefix, 10) . '/error.log';
function simpletest_log_read($test_id, $prefix, $test_class, $during_test = FALSE) {
$log = 'public://' . ($during_test ? '' : '/simpletest/' . substr($prefix, 10)) . '/error.log';
$found = FALSE;
if (file_exists($log)) {
foreach (file($log) as $line) {
@ -646,11 +648,11 @@ function simpletest_clean_database() {
*/
function simpletest_clean_temporary_directories() {
$count = 0;
if (is_dir(DRUPAL_ROOT . '/sites/simpletest')) {
$files = scandir(DRUPAL_ROOT . '/sites/simpletest');
if (is_dir('public://simpletest')) {
$files = scandir('public://simpletest');
foreach ($files as $file) {
if ($file[0] != '.') {
$path = DRUPAL_ROOT . '/sites/simpletest/' . $file;
$path = 'public://simpletest/' . $file;
if (is_dir($path) && (is_numeric($file) || strpos($file, 'config_simpletest') !== FALSE)) {
file_unmanaged_delete_recursive($path, array('Drupal\simpletest\TestBase', 'filePreDeleteCallback'));
$count++;
}

View File

@ -53,8 +53,6 @@ class FormValuesTest extends AjaxTestBase {
}
// Verify that AJAX elements with invalid callbacks return error code 500.
// Ensure the test error log is empty before these tests.
$this->assertNoErrorsLogged();
foreach (array('null', 'empty', 'nonexistent') as $key) {
$element_name = 'select_' . $key . '_callback';
$edit = array(
@ -63,8 +61,5 @@ class FormValuesTest extends AjaxTestBase {
$commands = $this->drupalPostAjaxForm('ajax_forms_test_get_form', $edit, $element_name);
$this->assertResponse(500);
}
// The exceptions are expected. Do not interpret them as a test failure.
// Not using File API; a potential error must trigger a PHP warning.
unlink(DRUPAL_ROOT . '/' . $this->siteDirectory . '/error.log');
}
}

View File

@ -10,12 +10,12 @@ namespace Drupal\system\Tests\DrupalKernel;
use Drupal\Core\DrupalKernel;
use Drupal\Component\PhpStorage\MTimeProtectedFastFileStorage;
use Drupal\Component\PhpStorage\FileReadOnlyStorage;
use Drupal\simpletest\DrupalUnitTestBase;
use Drupal\simpletest\UnitTestBase;
/**
* Tests compilation of the DIC.
*/
class DrupalKernelTest extends DrupalUnitTestBase {
class DrupalKernelTest extends UnitTestBase {
public static function getInfo() {
return array(
@ -26,13 +26,7 @@ class DrupalKernelTest extends DrupalUnitTestBase {
}
function setUp() {
// DrupalKernel relies on global $config_directories and requires those
// directories to exist. Therefore, create the directories, but do not
// invoke DrupalUnitTestBase::setUp(), since that would set up further
// environment aspects, which would distort this test, because it tests
// the DrupalKernel (re-)building itself.
$this->prepareConfigDirectories();
parent::setUp();
$this->settingsSet('php_storage', array('service_container' => array(
'bin' => 'service_container',
'class' => 'Drupal\Component\PhpStorage\MTimeProtectedFileStorage',

View File

@ -7,6 +7,7 @@
namespace Drupal\system\Tests\Installer;
use Drupal\Component\Utility\NestedArray;
use Drupal\system\Tests\InstallerTest;
/**
@ -15,11 +16,11 @@ use Drupal\system\Tests\InstallerTest;
class InstallerTranslationTest extends InstallerTest {
/**
* Overrides the language code in which to install Drupal.
* Whether the installer has completed.
*
* @var string
* @var bool
*/
protected $langcode = 'de';
protected $isInstalled = FALSE;
public static function getInfo() {
return array(
@ -29,34 +30,104 @@ class InstallerTranslationTest extends InstallerTest {
);
}
/**
* Overrides InstallerTest::setUpLanguage().
*/
protected function setUpLanguage() {
parent::setUpLanguage();
// After selecting a different language than English, all following screens
// should be translated already.
// @todo Instead of actually downloading random translations that cannot be
// asserted, write and supply a German translation file. Until then, take
// over whichever string happens to be there, but ensure that the English
// string no longer appears.
$elements = $this->xpath('//input[@type="submit"]/@value');
$string = (string) current($elements);
$this->assertNotEqual($string, 'Save and continue');
$this->translations['Save and continue'] = $string;
}
protected function setUp() {
$this->isInstalled = FALSE;
/**
* Overrides InstallerTest::setUpConfirm().
*/
protected function setUpConfirm() {
// We don't know the translated link text of "Visit your new site", but
// luckily, there is only one link.
$elements = $this->xpath('//a');
$string = (string) current($elements);
$this->assertNotEqual($string, 'Visit your new site');
$this->translations['Visit your new site'] = $string;
parent::setUpConfirm();
$settings['conf_path'] = (object) array(
'value' => $this->public_files_directory,
'required' => TRUE,
);
$settings['config_directories'] = (object) array(
'value' => array(),
'required' => TRUE,
);
$settings['config']['system.file'] = (object) array(
'value' => array(
'path' => array(
'private' => $this->private_files_directory,
'temporary' => $this->temp_files_directory,
),
),
'required' => TRUE,
);
// Add the translations directory so we can retrieve German translations.
$settings['config']['locale.settings'] = (object) array(
'value' => array(
'translation' => array(
'path' => drupal_get_path('module', 'simpletest') . '/files/translations',
),
),
'required' => TRUE,
);
$this->writeSettings($settings);
// Submit the installer with German language.
$edit = array(
'langcode' => 'de',
);
$this->drupalPostForm($GLOBALS['base_url'] . '/core/install.php', $edit, 'Save and continue');
// On the following page where installation profile is being selected the
// interface should be already translated, so there is no "Set up database"
// text anymore.
$this->assertNoText('Set up database', '"Set up database" string was not found.');
// After this assertion all we needed to test is tested, but the test
// expects the installation to succeed. If the test would finish here, an
// exception would occur. That is why the full installation has to be
// finished in the further steps.
// Get the "Save and continue" submit button translated value from the
// translated interface.
$submit_value = (string) current($this->xpath('//input[@type="submit"]/@value'));
$this->assertNotEqual($submit_value, 'Save and continue');
// Submit the Standard profile installation.
$edit = array(
'profile' => 'standard',
);
$this->drupalPostForm(NULL, $edit, $submit_value);
// Submit the next step.
$this->drupalPostForm(NULL, array(), $submit_value);
// Reload config directories.
include $this->public_files_directory . '/settings.php';
foreach ($config_directories as $type => $path) {
$GLOBALS['config_directories'][$type] = $path;
}
$this->rebuildContainer();
\Drupal::config('system.file')
->set('path.private', $this->private_files_directory)
->set('path.temporary', $this->temp_files_directory)
->save();
\Drupal::config('locale.settings')
->set('translation.path', $this->translation_files_directory)
->save();
// Submit site configuration form.
$this->drupalPostForm(NULL, array(
'site_mail' => 'admin@test.de',
'account[name]' => 'admin',
'account[mail]' => 'admin@test.de',
'account[pass][pass1]' => '123',
'account[pass][pass2]' => '123',
'site_default_country' => 'DE',
), $submit_value);
// Use the test mail class instead of the default mail handler class.
\Drupal::config('system.mail')->set('interface.default', 'Drupal\Core\Mail\TestMailCollector')->save();
// When running from run-tests.sh we don't get an empty current path which
// would indicate we're on the home page.
$path = current_path();
if (empty($path)) {
_current_path('run-tests');
}
$this->isInstalled = TRUE;
}
}

View File

@ -8,60 +8,13 @@
namespace Drupal\system\Tests;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Session\UserSession;
use Drupal\simpletest\WebTestBase;
/**
* Allows testing of the interactive installer.
*
* @todo Move majority of code into new Drupal\simpletest\InstallerTestBase.
*/
class InstallerTest extends WebTestBase {
/**
* Custom settings.php values to write for a test run.
*
* @var array
* An array of settings to write out, in the format expected by
* drupal_rewrite_settings().
*/
protected $settings = array();
/**
* The language code in which to install Drupal.
*
* @var string
*/
protected $langcode = 'en';
/**
* The installation profile to install.
*
* @var string
*/
protected $profile = 'minimal';
/**
* Additional parameters to use for installer screens.
*
* @see WebTestBase::installParameters()
*
* @var array
*/
protected $parameters = array();
/**
* A string translation map used for translated installer screens.
*
* Keys are English strings, values are translated strings.
*
* @var array
*/
protected $translations = array(
'Save and continue' => 'Save and continue',
'Visit your new site' => 'Visit your new site',
);
/**
* Whether the installer has completed.
*
@ -77,74 +30,57 @@ class InstallerTest extends WebTestBase {
);
}
/**
* Overrides WebTestBase::setUp().
*/
protected function setUp() {
$this->isInstalled = FALSE;
// Define information about the user 1 account.
$this->root_user = new UserSession(array(
'uid' => 1,
'name' => 'admin',
'mail' => 'admin@example.com',
'pass_raw' => $this->randomName(),
));
// If any $settings are defined for this test, copy and prepare an actual
// settings.php, so as to resemble a regular installation.
if (!empty($this->settings)) {
// Not using File API; a potential error must trigger a PHP warning.
copy(DRUPAL_ROOT . '/sites/default/default.settings.php', DRUPAL_ROOT . '/' . $this->siteDirectory . '/settings.php');
$this->writeSettings($settings);
$settings['conf_path'] = (object) array(
'value' => $this->public_files_directory,
'required' => TRUE,
);
$settings['config_directories'] = (object) array(
'value' => array(),
'required' => TRUE,
);
$settings['config']['system.file'] = (object) array(
'value' => array(
'path' => array(
'private' => $this->private_files_directory,
'temporary' => $this->temp_files_directory,
),
),
'required' => TRUE,
);
$settings['config']['locale.settings'] = (object) array(
'value' => array(
'translation' => array(
'path' => $this->translation_files_directory,
),
),
'required' => TRUE,
);
$this->writeSettings($settings);
$this->drupalGet($GLOBALS['base_url'] . '/core/install.php?langcode=en&profile=minimal');
$this->drupalPostForm(NULL, array(), 'Save and continue');
// Reload config directories.
include $this->public_files_directory . '/settings.php';
foreach ($config_directories as $type => $path) {
$GLOBALS['config_directories'][$type] = $path;
}
// Note that WebTestBase::installParameters() returns form input values
// suitable for a programmed drupal_form_submit().
// @see WebTestBase::translatePostValues()
$this->parameters = $this->installParameters();
$this->drupalGet($GLOBALS['base_url'] . '/core/install.php');
// Select language.
$this->setUpLanguage();
// Select profile.
$this->setUpProfile();
// Configure settings.
$this->setUpSettings();
// @todo Allow test classes based on this class to act on further installer
// screens.
// Configure site.
$this->setUpSite();
// Confirm installation.
$this->setUpConfirm();
// Import new settings.php written by the installer.
drupal_settings_initialize();
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.
// WebTestBase::tearDown() will delete the entire test site directory.
// Not using File API; a potential error must trigger a PHP warning.
chmod(DRUPAL_ROOT . '/' . $this->siteDirectory, 0777);
$this->rebuildContainer();
// Manually configure the test mail collector implementation to prevent
// tests from sending out e-mails and collect them in state instead.
\Drupal::config('system.mail')
->set('interface.default', 'Drupal\Core\Mail\TestMailCollector')
\Drupal::config('system.file')
->set('path.private', $this->private_files_directory)
->set('path.temporary', $this->temp_files_directory)
->save();
\Drupal::config('locale.settings')
->set('translation.path', $this->translation_files_directory)
->save();
// Use the test mail class instead of the default mail handler class.
\Drupal::config('system.mail')->set('interface.default', 'Drupal\Core\Mail\TestMailCollector')->save();
// When running from run-tests.sh we don't get an empty current path which
// would indicate we're on the home page.
@ -156,49 +92,6 @@ class InstallerTest extends WebTestBase {
$this->isInstalled = TRUE;
}
/**
* Installer step: Select language.
*/
protected function setUpLanguage() {
$edit = array(
'langcode' => $this->langcode,
);
$this->drupalPostForm(NULL, $edit, $this->translations['Save and continue']);
}
/**
* Installer step: Select installation profile.
*/
protected function setUpProfile() {
$edit = array(
'profile' => $this->profile,
);
$this->drupalPostForm(NULL, $edit, $this->translations['Save and continue']);
}
/**
* Installer step: Configure settings.
*/
protected function setUpSettings() {
$edit = $this->translatePostValues($this->parameters['forms']['install_settings_form']);
$this->drupalPostForm(NULL, $edit, $this->translations['Save and continue']);
}
/**
* Installer step: Configure site.
*/
protected function setUpSite() {
$edit = $this->translatePostValues($this->parameters['forms']['install_configure_form']);
$this->drupalPostForm(NULL, $edit, $this->translations['Save and continue']);
}
/**
* Installer step: Confirm installation.
*/
protected function setUpConfirm() {
$this->clickLink($this->translations['Visit your new site']);
}
/**
* {@inheritdoc}
*
@ -211,14 +104,35 @@ class InstallerTest extends WebTestBase {
}
}
/**
* {@inheritdoc}
*
* This override is necessary because the parent drupalGet() calls t(), which
* is not available early during installation.
*/
protected function drupalGet($path, array $options = array(), array $headers = array()) {
// We are re-using a CURL connection here. If that connection still has
// certain options set, it might change the GET into a POST. Make sure we
// clear out previous options.
$out = $this->curlExec(array(CURLOPT_HTTPGET => TRUE, CURLOPT_URL => $this->getAbsoluteUrl($path), CURLOPT_NOBODY => FALSE, CURLOPT_HTTPHEADER => $headers));
$this->refreshVariables(); // Ensure that any changes to variables in the other thread are picked up.
// Replace original page output with new output from redirected page(s).
if ($new = $this->checkForMetaRefresh()) {
$out = $new;
}
$this->verbose('GET request to: ' . $path .
'<hr />Ending URL: ' . $this->getUrl() .
'<hr />' . $out);
return $out;
}
/**
* Ensures that the user page is available after every test installation.
*/
public function testInstaller() {
$this->assertUrl('user/1');
$this->drupalGet('user');
$this->assertResponse(200);
// Confirm that we are logged-in after installation.
$this->assertText($this->root_user->getUsername());
}
}

View File

@ -94,9 +94,6 @@ class ErrorHandlerTest extends WebTestBase {
* Test the exception handler.
*/
function testExceptionHandler() {
// Ensure the test error log is empty before these tests.
$this->assertNoErrorsLogged();
$error_exception = array(
'%type' => 'Exception',
'!message' => 'Drupal is awesome',
@ -124,10 +121,6 @@ class ErrorHandlerTest extends WebTestBase {
$this->assertText($error_pdo_exception['!message'], format_string('Found !message in error page.', $error_pdo_exception));
$error_details = format_string('in %function (line ', $error_pdo_exception);
$this->assertRaw($error_details, format_string("Found '!message' in error page.", array('!message' => $error_details)));
// The exceptions are expected. Do not interpret them as a test failure.
// Not using File API; a potential error must trigger a PHP warning.
unlink(DRUPAL_ROOT . '/' . $this->siteDirectory . '/error.log');
}
/**

View File

@ -29,14 +29,6 @@ class ShutdownFunctionsTest extends WebTestBase {
);
}
protected function tearDown() {
// This test intentionally throws an exception in a PHP shutdown function.
// Prevent it from being interpreted as an actual test failure.
// Not using File API; a potential error must trigger a PHP warning.
unlink(DRUPAL_ROOT . '/' . $this->siteDirectory . '/error.log');
parent::tearDown();
}
/**
* Test shutdown functions.
*/

View File

@ -326,8 +326,9 @@ function system_requirements($phase) {
}
else {
// If we are installing Drupal, the settings.php file might not exist yet
// in the intended site directory, so don't require it.
$directories[] = conf_path(FALSE) . '/files';
// in the intended conf_path() directory, so don't require it. The
// conf_path() cache must also be reset in this case.
$directories[] = conf_path(FALSE, TRUE) . '/files';
}
if (!empty($GLOBALS['config']['system.file']['path']['private'])) {
$directories[] = $GLOBALS['config']['system.file']['path']['private'];

View File

@ -7,6 +7,7 @@
require_once __DIR__ . '/../vendor/autoload.php';
use Drupal\Component\Utility\Timer;
use Drupal\Core\StreamWrapper\PublicStream;
const SIMPLETEST_SCRIPT_COLOR_PASS = 32; // Green.
const SIMPLETEST_SCRIPT_COLOR_FAIL = 31; // Red.
@ -415,7 +416,8 @@ function simpletest_script_execute_batch($test_classes) {
echo 'FATAL ' . $child['class'] . ': test runner returned a non-zero error code (' . $status['exitcode'] . ').' . "\n";
if ($args['die-on-fail']) {
list($db_prefix, ) = simpletest_last_test_get($child['test_id']);
$test_directory = 'sites/simpletest/' . substr($db_prefix, 10);
$public_files = PublicStream::basePath();
$test_directory = $public_files . '/simpletest/' . substr($db_prefix, 10);
echo 'Simpletest database and files kept and test exited immediately on fail so should be reproducible if you change settings.php to use the database prefix '. $db_prefix . ' and config directories in '. $test_directory . "\n";
$args['keep-results'] = TRUE;
// Exit repeat loop immediately.
@ -581,9 +583,10 @@ function simpletest_script_cleanup($test_id, $test_class, $exitcode) {
// Read the log file in case any fatal errors caused the test to crash.
simpletest_log_read($test_id, $db_prefix, $test_class);
// Check whether a test site directory was setup already.
// @see \Drupal\simpletest\TestBase::prepareEnvironment()
$test_directory = DRUPAL_ROOT . '/sites/simpletest/' . substr($db_prefix, 10);
// Check whether a test file directory was setup already.
// @see prepareEnvironment()
$public_files = PublicStream::basePath();
$test_directory = $public_files . '/simpletest/' . substr($db_prefix, 10);
if (is_dir($test_directory)) {
// Output the error_log.
if (is_file($test_directory . '/error.log')) {
@ -592,7 +595,8 @@ function simpletest_script_cleanup($test_id, $test_class, $exitcode) {
$messages[] = $errors;
}
}
// Delete the test site directory.
// Delete the test files directory.
// simpletest_clean_temporary_directories() cannot be used here, since it
// would also delete file directories of other tests that are potentially
// running concurrently.