Newer
Older
catch
committed
<?php
/**
* @file
Jennifer Hodgdon
committed
* Definition of \Drupal\simpletest\TestBase.
catch
committed
*/
namespace Drupal\simpletest;
use Drupal\Component\Utility\Random;
catch
committed
use Drupal\Core\Database\Database;
use Drupal\Component\Utility\Settings;
catch
committed
use Drupal\Component\Utility\String;
use Drupal\Core\Config\ConfigImporter;
use Drupal\Core\Config\StorageComparer;
use Drupal\Core\DependencyInjection\ContainerBuilder;
catch
committed
use Drupal\Core\Database\ConnectionNotDefinedException;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\DrupalKernel;
use Drupal\Core\Language\Language;
use Drupal\Core\StreamWrapper\PublicStream;
catch
committed
use Drupal\Core\Utility\Error;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\DependencyInjection\Reference;
catch
committed
/**
* Base class for Drupal tests.
*
Jennifer Hodgdon
committed
* Do not extend this class directly; use either
* \Drupal\simpletest\WebTestBase or \Drupal\simpletest\UnitTestBase.
catch
committed
*/
abstract class TestBase {
/**
* The test run ID.
*
* @var string
*/
protected $testId;
/**
* The site directory of this test run.
*
* @var string
*/
protected $siteDirectory = NULL;
catch
committed
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/**
* The database prefix of this test run.
*
* @var string
*/
protected $databasePrefix = NULL;
/**
* The original file directory, before it was changed for testing purposes.
*
* @var string
*/
protected $originalFileDirectory = NULL;
/**
* Time limit for the test.
*/
protected $timeLimit = 500;
/**
* Current results of this test case.
*
* @var Array
*/
public $results = array(
'#pass' => 0,
'#fail' => 0,
'#exception' => 0,
'#debug' => 0,
);
/**
* Assertions thrown in that test case.
*
* @var Array
*/
protected $assertions = array();
/**
* This class is skipped when looking for the source of an assertion.
*
* When displaying which function an assert comes from, it's not too useful
* to see "WebTestBase->drupalLogin()', we would like to see the test
* that called it. So we need to skip the classes defining these helper
* methods.
*/
protected $skipClasses = array(__CLASS__ => TRUE);
catch
committed
/**
* TRUE if verbose debugging is enabled.
*
* @var boolean
*/
public $verbose;
catch
committed
/**
* Incrementing identifier for verbose output filenames.
*
* @var integer
*/
protected $verboseId = 0;
/**
* Safe class name for use in verbose output filenames.
*
* Namespaces separator (\) replaced with _.
*
* @var string
*/
protected $verboseClassName;
/**
* Directory where verbose output files are put.
*
* @var string
*/
protected $verboseDirectory;
/**
* The original database prefix when running inside Simpletest.
*
* @var string
*/
protected $originalPrefix;
Angie Byron
committed
/**
* URL to the verbose output file directory.
*
* @var string
*/
protected $verboseDirectoryUrl;
/**
* The settings array.
*/
protected $originalSettings;
/**
* The public file directory for the test environment.
*
* This is set in TestBase::prepareEnvironment().
*
* @var string
*/
protected $public_files_directory;
Angie Byron
committed
/**
* Whether to die in case any test assertion fails.
*
Angie Byron
committed
* @var boolean
*
* @see run-tests.sh
Angie Byron
committed
*/
public $dieOnFail = FALSE;
Angie Byron
committed
/**
* The DrupalKernel instance used in the test.
*
* @var \Drupal\Core\DrupalKernel
*/
protected $kernel;
Jennifer Hodgdon
committed
/**
* The dependency injection container used in the test.
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
/**
* The config importer that can used in a test.
*
* @var \Drupal\Core\Config\ConfigImporter
*/
protected $configImporter;
/**
* The random generator.
*
* @var \Drupal\Component\Utility\Random
*/
protected $randomGenerator;
catch
committed
/**
* Constructor for Test.
*
* @param $test_id
* Tests with the same id are reported together.
*/
public function __construct($test_id = NULL) {
$this->testId = $test_id;
}
Dries Buytaert
committed
/**
* Provides meta information about this test case, such as test name.
*
* @return array
* An array of untranslated strings with the following keys:
* - name: An overview of what is tested by the class; for example, "User
* access rules".
* - description: One sentence describing the test, starting with a verb.
* - group: The human-readable name of the module ("Node", "Statistics"), or
* the human-readable name of the Drupal facility tested (e.g. "Form API"
* or "XML-RPC").
*/
public static function getInfo() {
// PHP does not allow us to declare this method as abstract public static,
// so we simply throw an exception here if this has not been implemented by
// a child class.
throw new \RuntimeException(String::format('@class must implement \Drupal\simpletest\TestBase::getInfo().', array(
'@class' => get_called_class(),
)));
Dries Buytaert
committed
}
/**
* Performs setup tasks before each individual test method is run.
*/
abstract protected function setUp();
catch
committed
/**
* Checks the matching requirements for Test.
*
* @return
* Array of errors containing a list of unmet requirements.
*/
protected function checkRequirements() {
return array();
}
/**
* Internal helper: stores the assert.
*
* @param $status
* Can be 'pass', 'fail', 'exception', 'debug'.
catch
committed
* TRUE is a synonym for 'pass', FALSE for 'fail'.
* @param $message
Jennifer Hodgdon
committed
* (optional) A message to display with the assertion. Do not translate
catch
committed
* messages: use \Drupal\Component\Utility\String::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
catch
committed
* @param $group
Jennifer Hodgdon
committed
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
catch
committed
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
* @param $caller
* By default, the assert comes from a function whose name starts with
* 'test'. Instead, you can specify where this assert originates from
* by passing in an associative array as $caller. Key 'file' is
* the name of the source file, 'line' is the line number and 'function'
* is the caller function itself.
*/
protected function assert($status, $message = '', $group = 'Other', array $caller = NULL) {
// Convert boolean status to string status.
if (is_bool($status)) {
$status = $status ? 'pass' : 'fail';
}
// Increment summary result counter.
$this->results['#' . $status]++;
// Get the function information about the call to the assertion method.
if (!$caller) {
$caller = $this->getAssertionCall();
}
// Creation assertion array that can be displayed while tests are running.
$this->assertions[] = $assertion = array(
'test_id' => $this->testId,
'test_class' => get_class($this),
'status' => $status,
'message' => $message,
'message_group' => $group,
'function' => $caller['function'],
'line' => $caller['line'],
'file' => $caller['file'],
);
// Store assertion for display after the test has completed.
self::getDatabaseConnection()
catch
committed
->insert('simpletest')
->fields($assertion)
->execute();
// We do not use a ternary operator here to allow a breakpoint on
// test failure.
if ($status == 'pass') {
return TRUE;
}
else {
if ($this->dieOnFail && ($status == 'fail' || $status == 'exception')) {
Angie Byron
committed
exit(1);
}
catch
committed
return FALSE;
}
}
/**
* Store an assertion from outside the testing context.
*
* This is useful for inserting assertions that can only be recorded after
* the test case has been destroyed, such as PHP fatal errors. The caller
* information is not automatically gathered since the caller is most likely
* inserting the assertion on behalf of other code. In all other respects
Jennifer Hodgdon
committed
* the method behaves just like \Drupal\simpletest\TestBase::assert() in terms
catch
committed
* of storing the assertion.
*
* @return
* Message ID of the stored assertion.
*
Jennifer Hodgdon
committed
* @see \Drupal\simpletest\TestBase::assert()
* @see \Drupal\simpletest\TestBase::deleteAssert()
catch
committed
*/
public static function insertAssert($test_id, $test_class, $status, $message = '', $group = 'Other', array $caller = array()) {
// Convert boolean status to string status.
if (is_bool($status)) {
$status = $status ? 'pass' : 'fail';
}
$caller += array(
catch
committed
'function' => 'Unknown',
catch
committed
'line' => 0,
catch
committed
'file' => 'Unknown',
catch
committed
);
$assertion = array(
'test_id' => $test_id,
'test_class' => $test_class,
'status' => $status,
'message' => $message,
'message_group' => $group,
'function' => $caller['function'],
'line' => $caller['line'],
'file' => $caller['file'],
);
return self::getDatabaseConnection()
->insert('simpletest')
catch
committed
->fields($assertion)
->execute();
}
/**
* Delete an assertion record by message ID.
*
* @param $message_id
* Message ID of the assertion to delete.
*
catch
committed
* @return
* TRUE if the assertion was deleted, FALSE otherwise.
*
Jennifer Hodgdon
committed
* @see \Drupal\simpletest\TestBase::insertAssert()
catch
committed
*/
public static function deleteAssert($message_id) {
return (bool) self::getDatabaseConnection()
->delete('simpletest')
catch
committed
->condition('message_id', $message_id)
->execute();
}
/**
* Returns the database connection to the site running Simpletest.
*
Jennifer Hodgdon
committed
* @return \Drupal\Core\Database\Connection
* The database connection to use for inserting assertions.
*/
public static function getDatabaseConnection() {
try {
$connection = Database::getConnection('default', 'simpletest_original_default');
}
catch (ConnectionNotDefinedException $e) {
// If the test was not set up, the simpletest_original_default
// connection does not exist.
$connection = Database::getConnection('default', 'default');
}
return $connection;
}
catch
committed
/**
* Cycles through backtrace until the first non-assertion method is found.
*
* @return
* Array representing the true caller.
*/
protected function getAssertionCall() {
$backtrace = debug_backtrace();
// The first element is the call. The second element is the caller.
// We skip calls that occurred in one of the methods of our base classes
// or in an assertion function.
while (($caller = $backtrace[1]) &&
((isset($caller['class']) && isset($this->skipClasses[$caller['class']])) ||
substr($caller['function'], 0, 6) == 'assert')) {
// We remove that call.
array_shift($backtrace);
}
catch
committed
return Error::getLastCaller($backtrace);
catch
committed
}
/**
Jennifer Hodgdon
committed
* Check to see if a value is not false.
*
* False values are: empty string, 0, NULL, and FALSE.
catch
committed
*
* @param $value
* The value on which the assertion is to be done.
* @param $message
Jennifer Hodgdon
committed
* (optional) A message to display with the assertion. Do not translate
catch
committed
* messages: use \Drupal\Component\Utility\String::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
catch
committed
* @param $group
Jennifer Hodgdon
committed
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
*
catch
committed
* @return
* TRUE if the assertion succeeded, FALSE otherwise.
*/
protected function assertTrue($value, $message = '', $group = 'Other') {
catch
committed
return $this->assert((bool) $value, $message ? $message : String::format('Value @value is TRUE.', array('@value' => var_export($value, TRUE))), $group);
catch
committed
}
/**
Jennifer Hodgdon
committed
* Check to see if a value is false.
*
* False values are: empty string, 0, NULL, and FALSE.
catch
committed
*
* @param $value
* The value on which the assertion is to be done.
* @param $message
Jennifer Hodgdon
committed
* (optional) A message to display with the assertion. Do not translate
catch
committed
* messages: use \Drupal\Component\Utility\String::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
catch
committed
* @param $group
Jennifer Hodgdon
committed
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
*
catch
committed
* @return
* TRUE if the assertion succeeded, FALSE otherwise.
*/
protected function assertFalse($value, $message = '', $group = 'Other') {
catch
committed
return $this->assert(!$value, $message ? $message : String::format('Value @value is FALSE.', array('@value' => var_export($value, TRUE))), $group);
catch
committed
}
/**
* Check to see if a value is NULL.
*
* @param $value
* The value on which the assertion is to be done.
* @param $message
Jennifer Hodgdon
committed
* (optional) A message to display with the assertion. Do not translate
catch
committed
* messages: use \Drupal\Component\Utility\String::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
catch
committed
* @param $group
Jennifer Hodgdon
committed
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
*
catch
committed
* @return
* TRUE if the assertion succeeded, FALSE otherwise.
*/
protected function assertNull($value, $message = '', $group = 'Other') {
catch
committed
return $this->assert(!isset($value), $message ? $message : String::format('Value @value is NULL.', array('@value' => var_export($value, TRUE))), $group);
catch
committed
}
/**
* Check to see if a value is not NULL.
*
* @param $value
* The value on which the assertion is to be done.
* @param $message
Jennifer Hodgdon
committed
* (optional) A message to display with the assertion. Do not translate
catch
committed
* messages: use \Drupal\Component\Utility\String::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
catch
committed
* @param $group
Jennifer Hodgdon
committed
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
*
catch
committed
* @return
* TRUE if the assertion succeeded, FALSE otherwise.
*/
protected function assertNotNull($value, $message = '', $group = 'Other') {
catch
committed
return $this->assert(isset($value), $message ? $message : String::format('Value @value is not NULL.', array('@value' => var_export($value, TRUE))), $group);
catch
committed
}
/**
* Check to see if two values are equal.
*
* @param $first
* The first value to check.
* @param $second
* The second value to check.
* @param $message
Jennifer Hodgdon
committed
* (optional) A message to display with the assertion. Do not translate
catch
committed
* messages: use \Drupal\Component\Utility\String::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
catch
committed
* @param $group
Jennifer Hodgdon
committed
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
*
catch
committed
* @return
* TRUE if the assertion succeeded, FALSE otherwise.
*/
protected function assertEqual($first, $second, $message = '', $group = 'Other') {
catch
committed
return $this->assert($first == $second, $message ? $message : String::format('Value @first is equal to value @second.', array('@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE))), $group);
catch
committed
}
/**
* Check to see if two values are not equal.
*
* @param $first
* The first value to check.
* @param $second
* The second value to check.
* @param $message
Jennifer Hodgdon
committed
* (optional) A message to display with the assertion. Do not translate
catch
committed
* messages: use \Drupal\Component\Utility\String::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
catch
committed
* @param $group
Jennifer Hodgdon
committed
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
*
catch
committed
* @return
* TRUE if the assertion succeeded, FALSE otherwise.
*/
protected function assertNotEqual($first, $second, $message = '', $group = 'Other') {
catch
committed
return $this->assert($first != $second, $message ? $message : String::format('Value @first is not equal to value @second.', array('@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE))), $group);
catch
committed
}
/**
* Check to see if two values are identical.
*
* @param $first
* The first value to check.
* @param $second
* The second value to check.
* @param $message
Jennifer Hodgdon
committed
* (optional) A message to display with the assertion. Do not translate
catch
committed
* messages: use \Drupal\Component\Utility\String::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
catch
committed
* @param $group
Jennifer Hodgdon
committed
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
*
catch
committed
* @return
* TRUE if the assertion succeeded, FALSE otherwise.
*/
protected function assertIdentical($first, $second, $message = '', $group = 'Other') {
catch
committed
return $this->assert($first === $second, $message ? $message : String::format('Value @first is identical to value @second.', array('@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE))), $group);
catch
committed
}
/**
* Check to see if two values are not identical.
*
* @param $first
* The first value to check.
* @param $second
* The second value to check.
* @param $message
Jennifer Hodgdon
committed
* (optional) A message to display with the assertion. Do not translate
catch
committed
* messages: use \Drupal\Component\Utility\String::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
catch
committed
* @param $group
Jennifer Hodgdon
committed
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
*
catch
committed
* @return
* TRUE if the assertion succeeded, FALSE otherwise.
*/
protected function assertNotIdentical($first, $second, $message = '', $group = 'Other') {
catch
committed
return $this->assert($first !== $second, $message ? $message : String::format('Value @first is not identical to value @second.', array('@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE))), $group);
catch
committed
}
/**
* Checks to see if two objects are identical.
*
* @param object $object1
* The first object to check.
* @param object $object2
* The second object to check.
* @param $message
Jennifer Hodgdon
committed
* (optional) A message to display with the assertion. Do not translate
catch
committed
* messages: use \Drupal\Component\Utility\String::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
* @param $group
Jennifer Hodgdon
committed
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
*
* @return
* TRUE if the assertion succeeded, FALSE otherwise.
*/
Angie Byron
committed
protected function assertIdenticalObject($object1, $object2, $message = '', $group = 'Other') {
catch
committed
$message = $message ?: String::format('!object1 is identical to !object2', array(
'!object1' => var_export($object1, TRUE),
'!object2' => var_export($object2, TRUE),
));
$identical = TRUE;
foreach ($object1 as $key => $value) {
$identical = $identical && isset($object2->$key) && $object2->$key === $value;
}
Angie Byron
committed
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.');
}
catch
committed
/**
* Fire an assertion that is always positive.
*
* @param $message
Jennifer Hodgdon
committed
* (optional) A message to display with the assertion. Do not translate
catch
committed
* messages: use \Drupal\Component\Utility\String::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
catch
committed
* @param $group
Jennifer Hodgdon
committed
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
*
catch
committed
* @return
* TRUE.
*/
protected function pass($message = NULL, $group = 'Other') {
return $this->assert(TRUE, $message, $group);
}
/**
* Fire an assertion that is always negative.
*
* @param $message
Jennifer Hodgdon
committed
* (optional) A message to display with the assertion. Do not translate
catch
committed
* messages: use \Drupal\Component\Utility\String::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
catch
committed
* @param $group
Jennifer Hodgdon
committed
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
*
catch
committed
* @return
* FALSE.
*/
protected function fail($message = NULL, $group = 'Other') {
return $this->assert(FALSE, $message, $group);
}
/**
* Fire an error assertion.
*
* @param $message
Jennifer Hodgdon
committed
* (optional) A message to display with the assertion. Do not translate
catch
committed
* messages: use \Drupal\Component\Utility\String::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
catch
committed
* @param $group
Jennifer Hodgdon
committed
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
catch
committed
* @param $caller
* The caller of the error.
*
catch
committed
* @return
* FALSE.
*/
protected function error($message = '', $group = 'Other', array $caller = NULL) {
if ($group == 'User notice') {
// Since 'User notice' is set by trigger_error() which is used for debug
// set the message to a status of 'debug'.
return $this->assert('debug', $message, 'Debug', $caller);
}
return $this->assert('exception', $message, $group, $caller);
}
/**
* Logs verbose message in a text file.
*
* The a link to the vebose message will be placed in the test results via
* as a passing assertion with the text '[verbose message]'.
*
* @param $message
* The verbose message to be stored.
*
* @see simpletest_verbose()
*/
protected function verbose($message) {
catch
committed
// Do nothing if verbose debugging is disabled.
if (!$this->verbose) {
return;
}
$message = '<hr />ID #' . $this->verboseId . ' (<a href="' . $this->verboseClassName . '-' . ($this->verboseId - 1) . '.html">Previous</a> | <a href="' . $this->verboseClassName . '-' . ($this->verboseId + 1) . '.html">Next</a>)<hr />' . $message;
$verbose_filename = $this->verboseDirectory . '/' . $this->verboseClassName . '-' . $this->verboseId . '.html';
if (file_put_contents($verbose_filename, $message, FILE_APPEND)) {
Angie Byron
committed
$url = $this->verboseDirectoryUrl . '/' . $this->verboseClassName . '-' . $this->verboseId . '.html';
catch
committed
// Not using l() to avoid invoking the theme system, so that unit tests
// can use verbose() as well.
catch
committed
$url = '<a href="' . $url . '" target="_blank">Verbose message</a>';
catch
committed
$this->error($url, 'User notice');
catch
committed
}
catch
committed
$this->verboseId++;
catch
committed
}
/**
* Run all tests in this class.
*
* Regardless of whether $methods are passed or not, only method names
* starting with "test" are executed.
*
* @param $methods
* (optional) A list of method names in the test case class to run; e.g.,
* array('testFoo', 'testBar'). By default, all methods of the class are
* taken into account, but it can be useful to only run a few selected test
* methods during debugging.
*/
public function run(array $methods = array()) {
TestServiceProvider::$currentTest = $this;
Angie Byron
committed
$simpletest_config = \Drupal::config('simpletest.settings');
$class = get_class($this);
// Unless preset from run-tests.sh, retrieve the current verbose setting.
if (!isset($this->verbose)) {
$this->verbose = $simpletest_config->get('verbose');
}
if ($this->verbose) {
catch
committed
// Initialize verbose debugging.
$this->verbose = TRUE;
$this->verboseDirectory = PublicStream::basePath() . '/simpletest/verbose';
Angie Byron
committed
$this->verboseDirectoryUrl = file_create_url($this->verboseDirectory);
catch
committed
if (file_prepare_directory($this->verboseDirectory, FILE_CREATE_DIRECTORY) && !file_exists($this->verboseDirectory . '/.htaccess')) {
file_put_contents($this->verboseDirectory . '/.htaccess', "<IfModule mod_expires.c>\nExpiresActive Off\n</IfModule>\n");
}
$this->verboseClassName = str_replace("\\", "_", $class);
}
catch
committed
// HTTP auth settings (<username>:<password>) for the simpletest browser
// when sending requests to the test site.
$this->httpauth_method = (int) $simpletest_config->get('httpauth.method');
$username = $simpletest_config->get('httpauth.username');
$password = $simpletest_config->get('httpauth.password');
if (!empty($username) && !empty($password)) {
catch
committed
$this->httpauth_credentials = $username . ':' . $password;
}
set_error_handler(array($this, 'errorHandler'));
// Iterate through all the methods in this class, unless a specific list of
// methods to run was passed.
$class_methods = get_class_methods($class);
if ($methods) {
$class_methods = array_intersect($class_methods, $methods);
}
$missing_requirements = $this->checkRequirements();
if (!empty($missing_requirements)) {
Angie Byron
committed
$missing_requirements_object = new \ReflectionObject($this);
catch
committed
$caller = array(
'file' => $missing_requirements_object->getFileName(),
);
foreach ($missing_requirements as $missing_requirement) {
TestBase::insertAssert($this->testId, $class, FALSE, $missing_requirement, 'Requirements check.', $caller);
}
}
else {
Angie Byron
committed
if (defined("$class::SORT_METHODS")) {
sort($class_methods);
}
catch
committed
foreach ($class_methods as $method) {
// If the current method starts with "test", run it - it's a test.
if (strtolower(substr($method, 0, 4)) == 'test') {
// Insert a fail record. This will be deleted on completion to ensure
// that testing completed.
Angie Byron
committed
$method_info = new \ReflectionMethod($class, $method);
catch
committed
$caller = array(
'file' => $method_info->getFileName(),
'line' => $method_info->getStartLine(),
'function' => $class . '->' . $method . '()',
);
catch
committed
$completion_check_id = TestBase::insertAssert($this->testId, $class, FALSE, 'The test did not complete due to a fatal error.', 'Completion check', $caller);
catch
committed
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
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 this fails, a test would possibly operate in the
// parent site. Therefore, the entire test run for this test class
// has to be aborted.
// restoreEnvironment() cannot be called, because we do not know
// where exactly the environment setup failed.
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->restoreEnvironment();
break;
}
try {
$this->$method();
}
catch (\Exception $e) {
$this->exceptionHandler($e);
}
try {
catch
committed
$this->tearDown();
}
catch
committed
catch (\Exception $e) {
$this->exceptionHandler($e);
// If a test fails to tear down, abort the entire test class, since
// it is likely that all tests will fail in the same way and a
// failure here only results in additional test artifacts that have
// to be manually deleted.
$this->restoreEnvironment();
break;
catch
committed
}
catch
committed
$this->restoreEnvironment();
catch
committed
// Remove the completion check record.
TestBase::deleteAssert($completion_check_id);
}
}
}
TestServiceProvider::$currentTest = NULL;
catch
committed
// Clear out the error messages and restore error handler.
drupal_get_messages();
restore_error_handler();
}
/**
* Generates a database prefix for running tests.
*
* The database prefix is used by prepareEnvironment() to setup a public files
* directory for the test to be run, which also contains the PHP error log,
* which is written to in case of a fatal error. Since that directory is based
* on the database prefix, all tests (even unit tests) need to have one, in
* order to access and read the error log.
*
* @see TestBase::prepareEnvironment()
*
* The generated database table prefix is used for the Drupal installation
* being performed for the test. It is also used as user agent HTTP header
* value by the cURL-based browser of DrupalWebTestCase, which is sent to the
* Drupal installation of the test. During early Drupal bootstrap, the user
* agent HTTP header is parsed, and if it matches, all database queries use
* the database table prefix that has been generated here.
*
* @see WebTestBase::curlInitialize()
* @see drupal_valid_test_ua()
*/
catch
committed
private function prepareDatabasePrefix() {
Angie Byron
committed
// Ensure that the generated test site directory does not exist already,
// which may happen with a large amount of concurrent threads and
// long-running tests.
do {
$suffix = mt_rand(100000, 999999);
$this->siteDirectory = 'sites/simpletest/' . $suffix;
$this->databasePrefix = 'simpletest' . $suffix;
} while (is_dir(DRUPAL_ROOT . '/' . $this->siteDirectory));
// 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.
catch
committed
$affected_rows = db_update('simpletest_test_id')
->fields(array('last_prefix' => $this->databasePrefix))
->condition('test_id', $this->testId)
->execute();
catch
committed
if (!$affected_rows) {
throw new \RuntimeException('Failed to set up database prefix.');
}
}
/**
* Changes the database connection to the prefixed one.
*
catch
committed
* @see TestBase::prepareEnvironment()
*/
catch
committed
private function changeDatabasePrefix() {
if (empty($this->databasePrefix)) {
$this->prepareDatabasePrefix();
}
// Clone the current connection and replace the current prefix.
$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;
}
Database::addConnectionInfo('default', 'default', $connection_info['default']);
catch
committed
}
catch
committed
/**
* 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() {
}
/**
* Prepares the current environment for running the test.
*
* Backups various current environment variables and resets them, so they do
* not interfere with the Drupal site installation in which tests are executed
catch
committed
* and can be restored in TestBase::restoreEnvironment().
*
* Also sets up new resources for the testing environment, such as the public
* filesystem and configuration directories.
*
catch
committed
* This method is private as it must only be called once by TestBase::run()
* (multiple invocations for the same test would have unpredictable
* consequences) and it must not be callable or overridable by test classes.
*
* @see TestBase::beforePrepareEnvironment()
*/
catch
committed
private function prepareEnvironment() {
catch
committed
global $user;
catch
committed
// 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.
if (DRUPAL_TEST_IN_CHILD_SITE) {
$this->originalPrefix = drupal_valid_test_ua();
}
// Backup current in-memory configuration.
$this->originalSite = conf_path();
$this->originalSettings = settings()->getAll();
catch
committed
$this->originalConfig = $GLOBALS['config'];
// Backup statics and globals.
$this->originalContainer = clone \Drupal::getContainer();
$this->originalLanguage = $language_interface;
Angie Byron
committed
$this->originalConfigDirectories = $GLOBALS['config_directories'];
if (isset($GLOBALS['theme_key'])) {
$this->originalThemeKey = $GLOBALS['theme_key'];
}
$this->originalTheme = isset($GLOBALS['theme']) ? $GLOBALS['theme'] : NULL;
// Save further contextual information.
// Use the original files directory to avoid nesting it within an existing
// simpletest directory if a test is executed within a test.