diff --git a/core/includes/file.inc b/core/includes/file.inc
index 622a6ca..dc044d2 100644
--- a/core/includes/file.inc
+++ b/core/includes/file.inc
@@ -1496,7 +1496,8 @@ function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) {
// Configuration system stores default mode as strings.
$mode = FALSE;
// During early update there's no container.
- if (is_object(\Drupal::getContainer())) {
+ $container = \Drupal::getContainer();
+ if (is_object($container) && $container->has('config.factory')) {
$mode = octdec(\Drupal::config('system.file')->get('chmod.directory'));
}
if (!$mode) {
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
index 021e1f9..3580996 100644
--- a/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
+++ b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
@@ -139,13 +139,15 @@ protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL) {
}
$response = $this->curlExec($curl_options);
- $headers = $this->drupalGetHeaders();
- $headers = implode("\n", $headers);
- $this->verbose($method . ' request to: ' . $url .
- '
Code: ' . curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE) .
- '
Response headers: ' . $headers .
- '
Response body: ' . $response);
+ $verbose = $method . ' request to: ' . $url;
+ $verbose .= '
Response code: ' . curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE);
+ for ($i = 0; $i < $this->requestId; $i++) {
+ $verbose .= '
Request headers: ' . check_plain($this->requestHeaders[$i]) . '
';
+ $verbose .= '
Response headers: ' . check_plain($this->responseHeaders[$i]) . '
';
+ }
+ $verbose .= "\n
\n" . $response;
+ $this->verbose($verbose);
return $response;
}
diff --git a/core/modules/simpletest/config/schema/simpletest.schema.yml b/core/modules/simpletest/config/schema/simpletest.schema.yml
index 5423adc..528e717 100644
--- a/core/modules/simpletest/config/schema/simpletest.schema.yml
+++ b/core/modules/simpletest/config/schema/simpletest.schema.yml
@@ -4,6 +4,13 @@ simpletest.settings:
type: mapping
label: 'Testing'
mapping:
+ clean:
+ type: mapping
+ label: 'Clean-up options'
+ mapping:
+ artifacts:
+ type: boolean
+ label: 'Artifacts'
clear_results:
type: boolean
label: 'Clear results after each complete test suite run'
diff --git a/core/modules/simpletest/config/simpletest.settings.yml b/core/modules/simpletest/config/simpletest.settings.yml
index 52b2d3d..193d83f 100644
--- a/core/modules/simpletest/config/simpletest.settings.yml
+++ b/core/modules/simpletest/config/simpletest.settings.yml
@@ -1,3 +1,5 @@
+clean:
+ artifacts: true
clear_results: '1'
httpauth:
method: '1'
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
index b7848de..dff3d53 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
@@ -57,6 +57,13 @@
private $themeData;
/**
+ * The configuration directories for this test run.
+ *
+ * @var array
+ */
+ protected $configDirectories = array();
+
+ /**
* A KeyValueMemoryFactory instance to use when building the container.
*
* @var \Drupal\Core\KeyValueStore\KeyValueMemoryFactory.
@@ -72,22 +79,49 @@ function __construct($test_id = NULL) {
}
/**
- * Sets up Drupal unit test environment.
- *
- * @see \DrupalUnitTestBase::$modules
- * @see \DrupalUnitTestBase
+ * Overrides TestBase::beforePrepareEnvironment().
*/
- protected function setUp() {
+ protected function beforePrepareEnvironment() {
// Copy/prime extension file lists once to avoid filesystem scans.
if (!isset($this->moduleFiles)) {
$this->moduleFiles = \Drupal::state()->get('system.module.files') ?: array();
$this->themeFiles = \Drupal::state()->get('system.theme.files') ?: array();
$this->themeData = \Drupal::state()->get('system.theme.data') ?: array();
}
+ }
+
+ /**
+ * 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.
+ */
+ protected function setUp() {
$this->keyValueFactory = new KeyValueMemoryFactory();
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.
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/Form/SimpletestSettingsForm.php b/core/modules/simpletest/lib/Drupal/simpletest/Form/SimpletestSettingsForm.php
index 24bdb85..bf268b7 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/Form/SimpletestSettingsForm.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/Form/SimpletestSettingsForm.php
@@ -30,18 +30,24 @@ public function buildForm(array $form, array &$form_state) {
'#type' => 'details',
'#title' => $this->t('General'),
);
- $form['general']['simpletest_clear_results'] = array(
- '#type' => 'checkbox',
- '#title' => $this->t('Clear results after each complete test suite run'),
- '#description' => $this->t('By default SimpleTest will clear the results after they have been viewed on the results page, but in some cases it may be useful to leave the results in the database. The results can then be viewed at admin/config/development/testing/results/[test_id]. The test ID can be found in the database, simpletest table, or kept track of when viewing the results the first time. Additionally, some modules may provide more analysis or features that require this setting to be disabled.'),
- '#default_value' => $config->get('clear_results'),
- );
$form['general']['simpletest_verbose'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Provide verbose information when running tests'),
'#description' => $this->t('The verbose data will be printed along with the standard assertions and is useful for debugging. The verbose data will be erased between each test suite run. The verbose data output is very detailed and should only be used when debugging.'),
'#default_value' => $config->get('verbose'),
);
+ $form['general']['simpletest_clean_artifacts'] = array(
+ '#type' => 'checkbox',
+ '#title' => $this->t('Keep test artifacts'),
+ '#description' => $this->t('Retains the test site directory and database tables of all executed tests instead of deleting them to allow for advanced debugging.'),
+ '#default_value' => !$config->get('clean.artifacts'),
+ );
+ $form['general']['simpletest_clear_results'] = array(
+ '#type' => 'checkbox',
+ '#title' => $this->t('Clear results after each complete test suite run'),
+ '#description' => $this->t('By default SimpleTest will clear the results after they have been viewed on the results page, but in some cases it may be useful to leave the results in the database. The results can then be viewed at admin/config/development/testing/results/[test_id]. The test ID can be found in the database, simpletest table, or kept track of when viewing the results the first time. Additionally, some modules may provide more analysis or features that require this setting to be disabled.'),
+ '#default_value' => $config->get('clear_results'),
+ );
$form['httpauth'] = array(
'#type' => 'details',
@@ -108,8 +114,9 @@ public function validateForm(array &$form, array &$form_state) {
*/
public function submitForm(array &$form, array &$form_state) {
$this->configFactory->get('simpletest.settings')
- ->set('clear_results', $form_state['values']['simpletest_clear_results'])
->set('verbose', $form_state['values']['simpletest_verbose'])
+ ->set('clean.artifacts', !$form_state['values']['simpletest_clean_artifacts'])
+ ->set('clear_results', $form_state['values']['simpletest_clear_results'])
->set('httpauth.method', $form_state['values']['simpletest_httpauth_method'])
->set('httpauth.username', $form_state['values']['simpletest_httpauth_username'])
->set('httpauth.password', $form_state['values']['simpletest_httpauth_password'])
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
index 9654182..dcc4953 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
@@ -91,20 +91,11 @@
protected $skipClasses = array(__CLASS__ => TRUE);
/**
- * Flag to indicate whether the test has been set up.
- *
- * The setUp() method isolates the test from the parent Drupal site by
- * creating a random prefix for the database and setting up a clean file
- * storage directory. The tearDown() method then cleans up this test
- * environment. We must ensure that setUp() has been run. Otherwise,
- * tearDown() will act on the parent Drupal site rather than the test
- * environment, destroying live data.
+ * Whether to clean up all artifacts after a test run.
+ *
+ * @var bool
*/
- protected $setup = FALSE;
-
- protected $setupDatabasePrefix = FALSE;
-
- protected $setupEnvironment = FALSE;
+ public $cleanArtifacts;
/**
* TRUE if verbose debugging is enabled.
@@ -224,6 +215,11 @@ public static function getInfo() {
}
/**
+ * Performs setup tasks before each individual test method is run.
+ */
+ abstract protected function setUp();
+
+ /**
* Checks the matching requirements for Test.
*
* @return
@@ -739,6 +735,11 @@ public function run(array $methods = array()) {
$simpletest_config = \Drupal::config('simpletest.settings');
$class = get_class($this);
+
+ if (!isset($this->cleanArtifacts)) {
+ $this->cleanArtifacts = $simpletest_config->get('clean.artifacts');
+ }
+
if ($simpletest_config->get('verbose')) {
// Initialize verbose debugging.
$this->verbose = TRUE;
@@ -791,19 +792,34 @@ public function run(array $methods = array()) {
'function' => $class . '->' . $method . '()',
);
$completion_check_id = TestBase::insertAssert($this->testId, $class, FALSE, t('The test did not complete due to a fatal error.'), 'Completion check', $caller);
- $this->setUp();
- if ($this->setup) {
- try {
- $this->$method();
- // Finish up.
- }
- catch (\Exception $e) {
- $this->exceptionHandler($e);
- }
+ try {
+ $this->prepareEnvironment();
+ }
+ catch (\Exception $e) {
+ $this->exceptionHandler($e);
+ // The prepareEnvironment() method isolates the test from the parent
+ // Drupal site by creating a random database prefix and test site
+ // directory. If it fails, a test would possibly operate in the
+ // parent site and thus must be aborted.
+ break;
+ }
+ try {
+ $this->setUp();
+ }
+ catch (\Exception $e) {
+ $this->exceptionHandler($e);
+ // Abort if setUp() fails, since all test methods will fail.
+ // But ensure to clean up and restore the environment, since
+ // prepareEnvironment() succeeded.
$this->tearDown();
+ break;
}
- else {
- $this->fail(t("The test cannot be executed because it has not been set up properly."));
+ try {
+ $this->$method();
+ $this->tearDown();
+ }
+ catch (\Exception $e) {
+ $this->exceptionHandler($e);
}
// Remove the completion check record.
TestBase::deleteAssert($completion_check_id);
@@ -846,10 +862,13 @@ protected function prepareDatabasePrefix() {
// 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
// with the testId, so the database prefix has to be associated with it.
- db_update('simpletest_test_id')
+ $affected_rows = db_update('simpletest_test_id')
->fields(array('last_prefix' => $this->databasePrefix))
->condition('test_id', $this->testId)
->execute();
+ if (!$affected_rows) {
+ throw new \RuntimeException('Failed to set up database prefix.');
+ }
}
/**
@@ -860,12 +879,6 @@ protected function prepareDatabasePrefix() {
protected function changeDatabasePrefix() {
if (empty($this->databasePrefix)) {
$this->prepareDatabasePrefix();
- // If $this->prepareDatabasePrefix() failed to work, return without
- // setting $this->setupDatabasePrefix to TRUE, so setUp() methods will
- // know to bail out.
- if (empty($this->databasePrefix)) {
- return;
- }
}
// Clone the current connection and replace the current prefix.
@@ -875,9 +888,16 @@ protected function changeDatabasePrefix() {
$connection_info[$target]['prefix'] = $value['prefix']['default'] . $this->databasePrefix;
}
Database::addConnectionInfo('default', 'default', $connection_info['default']);
+ }
- // Indicate the database prefix was set up correctly.
- $this->setupDatabasePrefix = TRUE;
+ /**
+ * Act on global state information before the environment is altered for a test.
+ *
+ * Allows e.g. DrupalUnitTestBase to prime system/extension info from the
+ * parent site (and inject it into the test environment so as to improve
+ * performance).
+ */
+ protected function beforePrepareEnvironment() {
}
/**
@@ -891,16 +911,26 @@ protected function changeDatabasePrefix() {
* filesystem and configuration directories.
*
* @see TestBase::tearDown()
+ *
+ * This method is final as it must not be overridden by tests.
+ *
+ * @see TestBase::beforePrepareEnvironment()
*/
- protected function prepareEnvironment() {
+ protected final function prepareEnvironment() {
global $user, $conf;
+
+ // Allow (base) test classes to backup global state information.
+ $this->beforePrepareEnvironment();
+
+ // Create the database prefix for this test.
+ $this->prepareDatabasePrefix();
+
$language_interface = language(Language::TYPE_INTERFACE);
// When running the test runner within a test, back up the original database
- // prefix and re-set the new/nested prefix in drupal_valid_test_ua().
- if (drupal_valid_test_ua()) {
+ // prefix.
+ if (DRUPAL_TEST_IN_CHILD_SITE) {
$this->originalPrefix = drupal_valid_test_ua();
- drupal_valid_test_ua($this->databasePrefix);
}
// Backup current in-memory configuration.
@@ -948,21 +978,6 @@ protected function prepareEnvironment() {
$this->temp_files_directory = $this->siteDirectory . '/temp';
$this->translation_files_directory = $this->siteDirectory . '/translations';
- // Create filesystem directories, unless the installer will be executed.
- // @todo Move into DrupalUnitTestBase::setUp().
- if (!$this instanceof WebTestBase) {
- 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);
-
- // Create and set new configuration directories.
- $this->prepareConfigDirectories();
- }
- else {
- $GLOBALS['config_directories'] = array();
- $this->configDirectories = array();
- }
$this->generatedTestFiles = FALSE;
// Reset statics before the old container is replaced so that objects with a
@@ -985,6 +1000,7 @@ protected function prepareEnvironment() {
\Drupal::setContainer($this->container);
// Unset globals.
+ unset($GLOBALS['config_directories']);
unset($GLOBALS['theme_key']);
unset($GLOBALS['theme']);
@@ -992,34 +1008,21 @@ protected function prepareEnvironment() {
ini_set('log_errors', 1);
ini_set('error_log', $this->siteDirectory . '/error.log');
- // Indicate the environment was set up correctly.
- $this->setupEnvironment = TRUE;
- }
+ // 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();
- /**
- * 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 = $this->siteDirectory . '/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;
- }
+ // Reset all variables to perform tests in a clean environment.
+ $conf = 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);
}
/**
@@ -1053,36 +1056,6 @@ protected function rebuildContainer($environment = 'testing') {
}
/**
- * Performs setup tasks before each individual test method is run.
- */
- protected function setUp() {
- // Create the database prefix for this test.
- $this->prepareDatabasePrefix();
-
- // Prepare the environment for running tests.
- $this->prepareEnvironment();
- if (!$this->setupEnvironment) {
- throw new \RuntimeException('Failed to set up test environment.');
- }
-
- // 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();
- if (!$this->setupDatabasePrefix) {
- throw new \RuntimeException('Failed to set up database prefix.');
- }
-
- // 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);
-
- $this->setup = TRUE;
- }
-
- /**
* Performs cleanup tasks after each individual test method has been run.
*
* Deletes created files, database tables, and reverts environment changes.
@@ -1112,28 +1085,31 @@ protected function tearDown() {
}
}
- // Remove all prefixed tables.
- // @todo Connection prefix info is not normalized into an array.
- $original_connection_info = Database::getConnectionInfo('simpletest_original_default');
- $original_prefix = is_array($original_connection_info['default']['prefix']) ? $original_connection_info['default']['prefix']['default'] : $original_connection_info['default']['prefix'];
- $test_connection_info = Database::getConnectionInfo('default');
- $test_prefix = is_array($test_connection_info['default']['prefix']) ? $test_connection_info['default']['prefix']['default'] : $test_connection_info['default']['prefix'];
- if ($original_prefix != $test_prefix) {
- $tables = Database::getConnection()->schema()->findTables($test_prefix . '%');
- $prefix_length = strlen($test_prefix);
- foreach ($tables as $table) {
- if (Database::getConnection()->schema()->dropTable(substr($table, $prefix_length))) {
- unset($tables[$table]);
- }
- }
- }
-
// 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);
- // Delete test site directory.
- file_unmanaged_delete_recursive($this->siteDirectory, array($this, 'filePreDeleteCallback'));
+ // @todo Move into TestBase::run().
+ if ($this->cleanArtifacts) {
+ // Remove all prefixed tables.
+ // @todo Connection prefix info is not normalized into an array.
+ $original_connection_info = Database::getConnectionInfo('simpletest_original_default');
+ $original_prefix = is_array($original_connection_info['default']['prefix']) ? $original_connection_info['default']['prefix']['default'] : $original_connection_info['default']['prefix'];
+ $test_connection_info = Database::getConnectionInfo('default');
+ $test_prefix = is_array($test_connection_info['default']['prefix']) ? $test_connection_info['default']['prefix']['default'] : $test_connection_info['default']['prefix'];
+ if ($original_prefix != $test_prefix) {
+ $tables = Database::getConnection()->schema()->findTables($test_prefix . '%');
+ $prefix_length = strlen($test_prefix);
+ foreach ($tables as $table) {
+ if (Database::getConnection()->schema()->dropTable(substr($table, $prefix_length))) {
+ unset($tables[$table]);
+ }
+ }
+ }
+
+ // Delete test site directory.
+ file_unmanaged_delete_recursive($this->siteDirectory, array($this, 'filePreDeleteCallback'));
+ }
// Restore original database connection.
Database::removeConnection('default');
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php
index 733d51f..643f652 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php
@@ -20,11 +20,6 @@
abstract class UnitTestBase extends TestBase {
/**
- * @var array
- */
- protected $configDirectories;
-
- /**
* Constructor for UnitTestBase.
*/
function __construct($test_id = NULL) {
@@ -41,13 +36,7 @@ function __construct($test_id = NULL) {
* setUp() method.
*/
protected function setUp() {
- global $conf;
-
- parent::setUp();
-
- // Reset all variables to perform tests in a clean environment.
- $conf = array();
-
+ file_prepare_directory($this->public_files_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
$this->settingsSet('file_public_path', $this->public_files_directory);
}
}
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
index f562008..c280fb8 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
@@ -732,8 +732,6 @@ protected function drupalLogout() {
* @see \Drupal\simpletest\WebTestBase::prepareEnvironment()
*/
protected function setUp() {
- global $conf;
-
// 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
@@ -741,11 +739,6 @@ protected function setUp() {
// Backup the currently running Simpletest batch.
$this->originalBatch = batch_get();
- parent::setUp();
-
- // Reset all variables to perform tests in a clean environment.
- $conf = array();
-
// Define information about the user 1 account.
$this->root_user = new UserSession(array(
'uid' => 1,
@@ -789,10 +782,14 @@ protected function setUp() {
);
*/
// Use the test mail class instead of the default mail handler class.
+ // @todo Some mail system specific tests expect to be able to override the
+ // mail implementation.
+ /*
$settings['conf']['system.mail']['interface']['default'] = (object) array(
'value' => 'Drupal\Core\Mail\TestMailCollector',
'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(
@@ -824,6 +821,8 @@ protected function setUp() {
// WebTestBase::tearDown() will delete the entire test site directory.
chmod(DRUPAL_ROOT . '/' . $this->siteDirectory, 0777);
+ $this->rebuildContainer();
+
// Manually provide private and temporary files directories.
// (see @todo above)
file_prepare_directory($this->private_files_directory, FILE_CREATE_DIRECTORY);
@@ -833,7 +832,11 @@ protected function setUp() {
->set('path.temporary', $this->temp_files_directory)
->save();
- $this->rebuildContainer();
+ // Manually override the default mail handler implementation.
+ // (see @todo above)
+ \Drupal::config('system.mail')
+ ->set('interface.default', 'Drupal\Core\Mail\TestMailCollector')
+ ->save();
// Restore the original Simpletest batch.
$batch = &batch_get();
@@ -867,14 +870,12 @@ protected function setUp() {
// way to execute this after setUp() is done, or to eliminate it entirely.
$this->resetAll();
- drupal_set_time_limit($this->timeLimit);
// 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();
if (empty($path)) {
_current_path('run-tests');
}
- $this->setup = TRUE;
}
/**
@@ -887,6 +888,7 @@ protected function installParameters() {
$connection_info = Database::getConnectionInfo();
$driver = $connection_info['default']['driver'];
unset($connection_info['default']['driver']);
+ unset($connection_info['default']['namespace']);
unset($connection_info['default']['pdo']);
unset($connection_info['default']['init_commands']);
$parameters = array(
@@ -1206,6 +1208,7 @@ protected function curlExec($curl_options, $redirect = FALSE) {
$this->responseHeaders = array();
$this->redirect_count = 0;
}
+ $this->responseHeaders[$this->requestId] = array();
$content = curl_exec($this->curlHandle);
$status = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE);
@@ -1367,10 +1370,18 @@ protected function parse() {
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();
@@ -1896,6 +1907,28 @@ protected function serializePostValues($post = array()) {
}
/**
+ * 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.
*/
protected function cronRun() {
diff --git a/core/modules/system/lib/Drupal/system/Tests/Installer/InstallerTranslationTest.php b/core/modules/system/lib/Drupal/system/Tests/Installer/InstallerTranslationTest.php
index afda79d..6c02193 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Installer/InstallerTranslationTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Installer/InstallerTranslationTest.php
@@ -7,7 +7,6 @@
namespace Drupal\system\Tests\Installer;
-use Drupal\Component\Utility\NestedArray;
use Drupal\system\Tests\InstallerTest;
/**
@@ -15,6 +14,8 @@
*/
class InstallerTranslationTest extends InstallerTest {
+ protected $langcode = 'de';
+
public static function getInfo() {
return array(
'name' => 'Installer translation test',
@@ -23,92 +24,34 @@ public static function getInfo() {
);
}
- protected function setUp() {
- global $conf;
-
- // 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.
- // Back up the currently running SimpleTest batch.
- $this->originalBatch = batch_get();
-
- // Create the database prefix for this test.
- $this->prepareDatabasePrefix();
-
- // Prepare the environment for running tests.
- $this->prepareEnvironment();
- if (!$this->setupEnvironment) {
- throw new \RuntimeException('Failed to set up test environment.');
- }
-
- // Reset all statics and variables to perform tests in a clean environment.
- $conf = array();
-
- // 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();
- if (!$this->setupDatabasePrefix) {
- throw new \RuntimeException('Failed to set up database prefix.');
- }
-
- // After preparing the environment and changing the database prefix, we are
- // in a valid test environment.
- drupal_valid_test_ua($this->databasePrefix);
-
- // 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 Minimal profile installation.
- $edit = array(
- 'profile' => 'minimal',
- );
- $this->drupalPostForm(NULL, $edit, $submit_value);
-
- // Submit the next step.
- $this->drupalPostForm(NULL, array(), $submit_value);
-
- // 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();
+ /**
+ * 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;
+ }
- drupal_set_time_limit($this->timeLimit);
- // 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->setup = TRUE;
+ /**
+ * 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();
}
}
diff --git a/core/modules/system/lib/Drupal/system/Tests/InstallerTest.php b/core/modules/system/lib/Drupal/system/Tests/InstallerTest.php
index 923424f..9713438 100644
--- a/core/modules/system/lib/Drupal/system/Tests/InstallerTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/InstallerTest.php
@@ -8,13 +8,67 @@
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.
+ *
+ * @var bool
+ */
+ protected $isInstalled = FALSE;
+
public static function getInfo() {
return array(
'name' => 'Installer tests',
@@ -24,125 +78,126 @@ public static function getInfo() {
}
protected function setUp() {
- global $conf;
-
- // 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.
- // Back up the currently running SimpleTest batch.
- $this->originalBatch = batch_get();
-
- // Create the database prefix for this test.
- $this->prepareDatabasePrefix();
-
- // Prepare the environment for running tests.
- $this->prepareEnvironment();
- if (!$this->setupEnvironment) {
- throw new \RuntimeException('Failed to set up test environment.');
+ $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)) {
+ copy(DRUPAL_ROOT . '/sites/default/default.settings.php', DRUPAL_ROOT . '/' . $this->siteDirectory . '/settings.php');
+ $this->writeSettings($settings);
}
- // Reset all statics and variables to perform tests in a clean environment.
- $conf = array();
- drupal_static_reset();
-
- // 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();
- if (!$this->setupDatabasePrefix) {
- throw new \RuntimeException('Failed to set up database prefix.');
- }
+ // Note that WebTestBase::installParameters() returns form input values
+ // suitable for a programmed drupal_form_submit().
+ // @see WebTestBase::translatePostValues()
+ $this->parameters = $this->installParameters();
- // After preparing the environment and changing the database prefix, we are
- // in a valid test environment.
- drupal_valid_test_ua($this->databasePrefix);
-
- $settings['conf']['system.file'] = (object) array(
- 'value' => array(
- 'path' => array(
- 'private' => $this->private_files_directory,
- 'temporary' => $this->temp_files_directory,
- ),
- ),
- 'required' => TRUE,
- );
- $settings['conf']['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');
+
+ // Select language.
+ $this->setUpLanguage();
+
+ // Select profile.
+ $this->setUpProfile();
+
+ // Set up settings.
+ $this->setUpSettings();
- $this->drupalGet($GLOBALS['base_url'] . '/core/install.php?langcode=en&profile=minimal');
- $this->drupalPostForm(NULL, array(), 'Save and continue');
+ // @todo Allow test classes based on this class to act on further installer
+ // screens.
- // Reload config directories.
- include $this->public_files_directory . '/settings.php';
- foreach ($config_directories as $type => $path) {
- $GLOBALS['config_directories'][$type] = $path;
+ // 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.
+ // Use chmod() without a Drupal wrapper, so potential errors are visible.
+ // WebTestBase::tearDown() will delete the entire test site directory.
+ chmod(DRUPAL_ROOT . '/' . $this->siteDirectory, 0777);
+
$this->rebuildContainer();
- // Use the test mail class instead of the default mail handler class.
- \Drupal::config('system.mail')->set('interface.default', 'Drupal\Core\Mail\TestMailCollector')->save();
+ \Drupal::config('system.mail')
+ ->set('interface.default', 'Drupal\Core\Mail\TestMailCollector')
+ ->save();
- drupal_set_time_limit($this->timeLimit);
// 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->setup = TRUE;
+
+ $this->isInstalled = TRUE;
}
- /**
- * {@inheritdoc}
- *
- * During setup(), drupalPost calls refreshVariables() which tries to read
- * variables which are not yet there because the child Drupal is not yet
- * installed.
- */
- protected function refreshVariables() {
- if (!empty($this->setup)) {
- parent::refreshVariables();
- }
+ protected function setUpLanguage() {
+ $edit = array(
+ 'langcode' => $this->langcode,
+ );
+ $this->drupalPostForm(NULL, $edit, $this->translations['Save and continue']);
+ }
+
+ protected function setUpProfile() {
+ $edit = array(
+ 'profile' => $this->profile,
+ );
+ $this->drupalPostForm(NULL, $edit, $this->translations['Save and continue']);
+ }
+
+ protected function setUpSettings() {
+ $edit = $this->translatePostValues($this->parameters['forms']['install_settings_form']);
+ $this->drupalPostForm(NULL, $edit, $this->translations['Save and continue']);
+ }
+
+ protected function setUpSite() {
+ $edit = $this->translatePostValues($this->parameters['forms']['install_configure_form']);
+ $this->drupalPostForm(NULL, $edit, $this->translations['Save and continue']);
+ }
+
+ protected function setUpConfirm() {
+ $this->clickLink($this->translations['Visit your new site']);
}
/**
* {@inheritdoc}
*
- * This override is necessary because the parent drupalGet() calls t(), which
- * is not available early during installation.
+ * WebTestBase::refreshVariables() tries to operate on persistent storage,
+ * which is only available after the installer completed.
*/
- 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;
+ protected function refreshVariables() {
+ if ($this->isInstalled) {
+ parent::refreshVariables();
}
- $this->verbose('GET request to: ' . $path .
- '
Ending URL: ' . $this->getUrl() .
- '
' . $out);
- return $out;
}
/**
* Ensures that the user page is available after every test installation.
*/
public function testInstaller() {
- $this->drupalGet('user');
+ $this->assertUrl('user/1');
$this->assertResponse(200);
+ // Confirm that we are logged-in after installation.
+ $this->assertText($this->root_user->getUsername());
}
}
diff --git a/core/modules/system/lib/Drupal/system/Tests/System/ErrorHandlerTest.php b/core/modules/system/lib/Drupal/system/Tests/System/ErrorHandlerTest.php
index 7d20be9..3d50251 100644
--- a/core/modules/system/lib/Drupal/system/Tests/System/ErrorHandlerTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/System/ErrorHandlerTest.php
@@ -29,6 +29,13 @@ public static function getInfo() {
);
}
+ protected function tearDown() {
+ // This test intentionally triggers errors and exceptions; prevent them from
+ // being interpreted as actual test failures.
+ file_put_contents(DRUPAL_ROOT . '/' . $this->siteDirectory . '/error.log', '');
+ parent::tearDown();
+ }
+
/**
* Test the error handler.
*/
diff --git a/core/modules/system/lib/Drupal/system/Tests/System/ShutdownFunctionsTest.php b/core/modules/system/lib/Drupal/system/Tests/System/ShutdownFunctionsTest.php
index 96b2f8d..18aadc7 100644
--- a/core/modules/system/lib/Drupal/system/Tests/System/ShutdownFunctionsTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/System/ShutdownFunctionsTest.php
@@ -29,6 +29,13 @@ public static function getInfo() {
);
}
+ protected function tearDown() {
+ // This test intentionally throws an exception in a PHP shutdown function.
+ // Prevent it from being interpreted as an actual test failure.
+ file_put_contents(DRUPAL_ROOT . '/' . $this->siteDirectory . '/error.log', '');
+ parent::tearDown();
+ }
+
/**
* Test shutdown functions.
*/
diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh
index 21e6fb5..366fde2 100755
--- a/core/scripts/run-tests.sh
+++ b/core/scripts/run-tests.sh
@@ -165,6 +165,11 @@ function simpletest_script_help() {
--verbose Output detailed assertion messages in addition to summary.
+ --keep-artifacts
+
+ Retains the test site directory and database tables of all
+ executed tests. By default, all artifacts are deleted.
+
--keep-results
Keeps detailed assertion results (in the database) after tests
@@ -222,6 +227,7 @@ function simpletest_script_parse_args() {
'file' => FALSE,
'color' => FALSE,
'verbose' => FALSE,
+ 'keep-artifacts' => FALSE,
'keep-results' => FALSE,
'test_names' => array(),
'repeat' => 1,
@@ -516,6 +522,7 @@ function simpletest_script_run_one_test($test_id, $test_class) {
$conf['simpletest.settings']['clear_results'] = !$args['keep-results'];
$test = new $test_class($test_id);
+ $test->cleanArtifacts = !$args['keep-artifacts'];
$test->dieOnFail = (bool) $args['die-on-fail'];
$test->run();
$info = $test->getInfo();