Commit ab07b4cd authored by webchick's avatar webchick

#305077 by DamZ, boombatower, and cwgordon7: Rework SimpleTest backend.

parent 5ba4c533
......@@ -627,6 +627,35 @@ function drupal_error_handler($errno, $message, $filename, $line, $context) {
}
}
/**
* Gets the last caller (file name and line of the call, function in which the
* call originated) from a backtrace.
*
* @param $backtrace
* A standard PHP backtrace.
* @return
* An associative array with keys 'file', 'line' and 'function'.
*/
function _drupal_get_last_caller($backtrace) {
// The first trace is the call itself.
// It gives us the line and the file of the last call.
$call = $backtrace[0];
// The second call give us the function where the call originated.
if (isset($backtrace[1])) {
if (isset($backtrace[1]['class'])) {
$call['function'] = $backtrace[1]['class'] . $backtrace[1]['type'] . $backtrace[1]['function'] . '()';
}
else {
$call['function'] = $backtrace[1]['function'] . '()';
}
}
else {
$call['function'] = 'main()';
}
return $call;
}
function _fix_gpc_magic(&$item) {
if (is_array($item)) {
array_walk($item, '_fix_gpc_magic');
......
......@@ -43,55 +43,75 @@ function __construct($test_id = NULL) {
* The message string.
* @param $group
* WHich group this assert belongs to.
* @param $custom_caller
* @param $caller
* By default, the assert comes from a function which names start with
* 'test'. Instead, you can specify where this assert originates from
* by passing in an associative array as $custom_caller. Key 'file' is
* 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', $custom_caller = NULL) {
protected function _assert($status, $message = '', $group = 'Other', $caller = NULL) {
global $db_prefix;
// Convert boolean status to string status.
if (is_bool($status)) {
$status = $status ? 'pass' : 'fail';
}
// Increment summary result counter.
$this->_results['#' . $status]++;
if (!isset($custom_caller)) {
$callers = debug_backtrace();
array_shift($callers);
foreach ($callers as $function) {
if (substr($function['function'], 0, 6) != 'assert' && $function['function'] != 'pass' && $function['function'] != 'fail') {
break;
}
}
}
else {
$function = $custom_caller;
// Get the function information about the call to the assertion method.
if (!$caller) {
$caller = $this->getAssertionCall();
}
// Switch to non-testing database to store results in.
$current_db_prefix = $db_prefix;
$db_prefix = $this->db_prefix_original;
db_insert('simpletest')->fields(array(
// Creation assertion array that can be displayed while tests are running.
$this->_assertions[] = $assertion = array(
'test_id' => $this->test_id,
'test_class' => get_class($this),
'status' => $status,
'message' => substr($message, 0, 255), // Some messages are too long for the database.
'message_group' => $group,
'caller' => $function['function'],
'line' => $function['line'],
'file' => $function['file'],
))->execute();
$this->_assertions[] = array(
'test_class' => get_class($this),
'status' => $status,
'message' => $message,
'group' => $group,
'function' => $function['function'],
'line' => $function['line'],
'file' => $function['file'],
'message_group' => $group,
'function' => $caller['function'],
'line' => $caller['line'],
'file' => $caller['file'],
);
// Store assertion for display after the test has completed.
db_insert('simpletest')->fields($assertion)->execute();
// Return to testing prefix.
$db_prefix = $current_db_prefix;
return $status;
}
/**
* 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 occured in one of the methods of DrupalWebTestCase
// or in an assertion function.
while (($caller = $backtrace[1]) &&
((isset($caller['class']) && $caller['class'] == 'DrupalWebTestCase') ||
substr($caller['function'], 0, 6) == 'assert')) {
// We remove that call.
array_shift($backtrace);
}
return _drupal_get_last_caller($backtrace);
}
/**
* Check to see if a value is not false (not an empty string, 0, NULL, or FALSE).
*
......@@ -263,11 +283,11 @@ protected function fail($message = NULL, $group = 'Other') {
* The message to display along with the assertion.
* @param $group
* The type of assertion - examples are "Browser", "PHP".
* @param $custom_caller
* @param $caller
* The caller of the error.
*/
protected function error($message = '', $group = 'Other', $custom_caller = NULL) {
return $this->_assert('exception', $message, $group, $custom_caller);
protected function error($message = '', $group = 'Other', $caller = NULL) {
return $this->_assert('exception', $message, $group, $caller);
}
/**
......@@ -281,8 +301,13 @@ function run() {
// If the current method starts with "test", run it - it's a test.
if (strtolower(substr($method, 0, 4)) == 'test') {
$this->setUp();
$this->$method();
// Finish up.
try {
$this->$method();
// Finish up.
}
catch (Exception $e) {
$this->exceptionHandler($e);
}
$this->tearDown();
}
}
......@@ -308,15 +333,28 @@ function errorHandler($severity, $message, $file = NULL, $line = NULL) {
E_USER_NOTICE => 'User notice',
E_RECOVERABLE_ERROR => 'Recoverable error',
);
$this->error($message, $error_map[$severity], array(
'function' => '',
'line' => $line,
'file' => $file,
));
$backtrace = debug_backtrace();
$this->error($message, $error_map[$severity], _drupal_get_last_caller($backtrace));
}
return TRUE;
}
/**
* Handle exceptions.
*
* @see set_exception_handler
*/
function exceptionHandler($exception) {
$backtrace = $exception->getTrace();
// Push on top of the backtrace the call that generated the exception.
array_unshift($backtrace, array(
'line' => $exception->getLine(),
'file' => $exception->getFile(),
));
$this->error($exception->getMessage(), 'Uncaught exception', _drupal_get_last_caller($backtrace));
}
/**
* Creates a node based on default settings.
*
......@@ -405,7 +443,7 @@ function drupalCreateContentType($settings = array()) {
node_types_rebuild();
$this->assertEqual($saved_type, SAVED_NEW, t('Created content type %type.', array('%type' => $type->type)));
// Reset permissions so that permissions for this content type are available.
$this->checkPermissions(array(), TRUE);
......@@ -645,7 +683,7 @@ function setUp() {
// Generate temporary prefixed database to ensure that tests have a clean starting point.
$db_prefix = 'simpletest' . mt_rand(1000, 1000000);
include_once './includes/install.inc';
drupal_install_system();
......@@ -659,7 +697,7 @@ function setUp() {
// stale data for the previous run's database prefix and all
// calls to it will fail.
drupal_get_schema(NULL, TRUE);
// Run default profile tasks.
$task = 'profile';
default_profile_tasks($task, '');
......@@ -732,7 +770,6 @@ function tearDown() {
// Close the CURL handler.
$this->curlClose();
restore_error_handler();
}
}
......@@ -807,7 +844,7 @@ protected function parse() {
// them.
@$htmlDom = DOMDocument::loadHTML($this->_content);
if ($htmlDom) {
$this->assertTrue(TRUE, t('Valid HTML found on "@path"', array('@path' => $this->getUrl())), t('Browser'));
$this->pass(t('Valid HTML found on "@path"', array('@path' => $this->getUrl())), t('Browser'));
// It's much easier to work with simplexml than DOM, luckily enough
// we can just simply import our DOM tree.
$this->elements = simplexml_import_dom($htmlDom);
......@@ -1290,7 +1327,7 @@ function assertNoRaw($raw, $message = '%s found', $group = 'Other') {
* TRUE on pass, FALSE on fail.
*/
function assertText($text, $message = '', $group = 'Other') {
return $this->assertTextHelper($text, $message, $group = 'Other', FALSE);
return $this->assertTextHelper($text, $message, $group, FALSE);
}
/**
......
......@@ -177,25 +177,25 @@ function simpletest_schema() {
'default' => '',
'description' => t('The message group this message belongs to. For example: warning, browser, user.'),
),
'caller' => array(
'function' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => t('Name of the caller function or method that created this message.'),
'description' => t('Name of the assertion function or method that created this message.'),
),
'line' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => t('Line number of the caller.'),
'description' => t('Line number on which the function is called.'),
),
'file' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => t('Name of the file where the caller is.'),
'description' => t('Name of the file where the function is called.'),
),
),
'primary key' => array('message_id'),
......
......@@ -106,7 +106,7 @@ function simpletest_test_form() {
$result->message_group,
basename($result->file),
$result->line,
$result->caller,
$result->function,
$map[$status],
),
'class' => "simpletest-$status",
......
......@@ -106,17 +106,36 @@ class SimpleTestTestCase extends DrupalWebTestCase {
$this->drupalCreateUser(array($this->invalid_permission));
$this->pass(t('Test ID is @id.', array('@id' => $this->test_id)));
// Generates a warning
$i = 1 / 0;
// Call an assert function specific to that class.
$this->assertNothing();
}
/**
* Assert nothing.
*/
function assertNothing() {
$this->pass("This is nothing.");
}
/**
* Confirm that the stub test produced the desired results.
*/
function confirmStubTestResults() {
$this->assertAssertion($this->pass, 'Other', 'Pass');
$this->assertAssertion($this->fail, 'Other', 'Fail');
$this->assertAssertion($this->pass, 'Other', 'Pass', 'simpletest.test', 'SimpleTestTestCase->stubTest()');
$this->assertAssertion($this->fail, 'Other', 'Fail', 'simpletest.test', 'SimpleTestTestCase->stubTest()');
$this->assertAssertion(t('Created permissions: @perms', array('@perms' => $this->valid_permission)), 'Role', 'Pass', 'simpletest.test', 'SimpleTestTestCase->stubTest()');
$this->assertAssertion(t('Invalid permission %permission.', array('%permission' => $this->invalid_permission)), 'Role', 'Fail', 'simpletest.test', 'SimpleTestTestCase->stubTest()');
// Check that a warning is catched by simpletest.
$this->assertAssertion('Division by zero', 'Warning', 'Fail', 'simpletest.test', 'SimpleTestTestCase->stubTest()');
$this->assertAssertion(t('Created permissions: @perms', array('@perms' => $this->valid_permission)), 'Role', 'Pass');
$this->assertAssertion(t('Invalid permission %permission.', array('%permission' => $this->invalid_permission)), 'Role', 'Fail');
// Check that the backtracing code works for specific assert function.
$this->assertAssertion('This is nothing.', 'Other', 'Pass', 'simpletest.test', 'SimpleTestTestCase->stubTest()');
$this->test_ids[] = $test_id = $this->getTestIdFromResults();
$this->assertTrue($test_id, t('Found test ID in results.'));
......@@ -141,20 +160,24 @@ class SimpleTestTestCase extends DrupalWebTestCase {
* @param string $message Assertion message.
* @param string $type Assertion type.
* @param string $status Assertion status.
* @param string $file File where the assertion originated.
* @param string $functuion Function where the assertion originated.
* @return Assertion result.
*/
function assertAssertion($message, $type, $status) {
function assertAssertion($message, $type, $status, $file, $function) {
$message = trim(strip_tags($message));
$found = FALSE;
foreach ($this->results['assertions'] as $assertion) {
if ($assertion['message'] == $message &&
$assertion['type'] == $type &&
$assertion['status'] == $status) {
$assertion['status'] == $status &&
$assertion['file'] == $file &&
$assertion['function'] == $function) {
$found = TRUE;
break;
}
}
return $this->assertTrue($found, t('Found assertion {"@message", "@type", "@status"}.', array('@message' => $message, '@type' => $type, '@status' => $status)));
return $this->assertTrue($found, t('Found assertion {"@message", "@type", "@status", "@file", "@function"}.', array('@message' => $message, '@type' => $type, '@status' => $status, "@file" => $file, "@function" => $function)));
}
/**
......@@ -175,6 +198,9 @@ class SimpleTestTestCase extends DrupalWebTestCase {
$assertion = array();
$assertion['message'] = $this->asText($row->td[0]);
$assertion['type'] = $this->asText($row->td[1]);
$assertion['file'] = $this->asText($row->td[2]);
$assertion['line'] = $this->asText($row->td[3]);
$assertion['function'] = $this->asText($row->td[4]);
$ok_url = (url('misc/watchdog-ok.png') == 'misc/watchdog-ok.png') ? 'misc/watchdog-ok.png' : (base_path() . 'misc/watchdog-ok.png');
$assertion['status'] = ($row->td[5]->img['src'] == $ok_url) ? 'Pass' : 'Fail';
$results['assertions'][] = $assertion;
......@@ -212,7 +238,7 @@ class SimpleTestTestCase extends DrupalWebTestCase {
if (!is_object($element)) {
return $this->fail('The element is not an element.');
}
return trim(strip_tags($element->asXML()));
return trim(html_entity_decode(strip_tags($element->asXML())));
}
/**
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment