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;
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\Language\Language;
use Drupal\Core\Session\AccountProxy;
use Drupal\Core\Session\AnonymousUserSession;
Alex Pott
committed
use Drupal\Core\Site\Settings;
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
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
95
/**
* 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
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
286
* @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() {
Angie Byron
committed
// Check whether there is a test runner connection.
// @see run-tests.sh
// @todo Convert Simpletest UI runner to create + use this connection, too.
try {
Angie Byron
committed
$connection = Database::getConnection('default', 'test-runner');
}
catch (ConnectionNotDefinedException $e) {
Angie Byron
committed
// Check whether there is a backup of the original default connection.
// @see TestBase::prepareEnvironment()
try {
$connection = Database::getConnection('default', 'simpletest_original_default');
}
catch (ConnectionNotDefinedException $e) {
// If TestBase::prepareEnvironment() or TestBase::restoreEnvironment()
// failed, the test-specific database connection does not exist
// yet/anymore, so fall back to the default of the (UI) test runner.
$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\Core\DrupalKernel::bootConfiguration()
*/
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()) {
$class = get_class($this);
if ($missing_requirements = $this->checkRequirements()) {
$object_info = new \ReflectionObject($this);
$caller = array(
'file' => $object_info->getFileName(),
);
foreach ($missing_requirements as $missing_requirement) {
TestBase::insertAssert($this->testId, $class, FALSE, $missing_requirement, 'Requirements check', $caller);
}
return;
}
TestServiceProvider::$currentTest = $this;
Angie Byron
committed
$simpletest_config = \Drupal::config('simpletest.settings');
// 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.
$test_methods = array_filter(get_class_methods($class), function ($method) {
return strpos($method, 'test') === 0;
});
Angie Byron
committed
if (empty($test_methods)) {
// Call $this->assert() here because we need to pass along custom caller
// information, lest the wrong originating code file/line be identified.
$this->assert(FALSE, 'No test methods found.', 'Requirements', array('function' => __METHOD__ . '()', 'file' => __FILE__, 'line' => __LINE__));
}
catch
committed
if ($methods) {
$test_methods = array_intersect($test_methods, $methods);
}
foreach ($test_methods as $method) {
// Insert a fail record. This will be deleted on completion to ensure
// that testing completed.
$method_info = new \ReflectionMethod($class, $method);
catch
committed
$caller = array(
'file' => $method_info->getFileName(),
'line' => $method_info->getStartLine(),
'function' => $class . '->' . $method . '()',
catch
committed
);
$test_completion_check_id = TestBase::insertAssert($this->testId, $class, FALSE, 'The test did not complete due to a fatal error.', 'Completion check', $caller);
try {
$this->prepareEnvironment();
catch
committed
}
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;
Angie Byron
committed
}
try {
$this->$method();
catch
committed
}
catch (\Exception $e) {
$this->exceptionHandler($e);
}
try {
$this->tearDown();
}
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;
}
$this->restoreEnvironment();
// Remove the test method completion check record.
TestBase::deleteAssert($test_completion_check_id);
catch
committed
}
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.
Angie Byron
committed
$affected_rows = self::getDatabaseConnection()->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();
}
Angie Byron
committed
// If the backup already exists, something went terribly wrong.
// This case is possible, because database connection info is a static
// global state construct on the Database class, which at least persists
// for all test methods executed in one PHP process.
if (Database::getConnectionInfo('simpletest_original_default')) {
throw new \RuntimeException("Bad Database connection state: 'simpletest_original_default' connection key already exists. Broken test?");
}
// 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) {
// Replace the full table prefix definition to ensure that no table
// prefixes of the test runner leak into the test.
$connection_info[$target]['prefix'] = array(
'default' => $value['prefix']['default'] . $this->databasePrefix,
);
}
Database::addConnectionInfo('default', 'default', $connection_info['default']);
catch
committed
}
catch
committed
/**
* Act on global state information before the environment is altered for a test.
*
Alex Pott
committed
* Allows e.g. KernelTestBase to prime system/extension info from the
catch
committed
* 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() {
$user = \Drupal::currentUser();