Commit af474609 authored by Dries's avatar Dries

- Added a test framework to Drupal along with a first batch of tests for

  Drupal core!  This is an important milestone for the project so enable
  the module and check it out ... :)

  Thanks to Rok Žlender, Károly Négyesi, Jimmy Berry, Kevin Bridges, Charlie
  Gordon, Douglas Hubler, Miglius Alaburda, Andy Kirkham, Dimitri13, Kieran
  Lal, Moshe Weitzman, and the many other people that helped with testing
  over the past years and that drove this home.

  It all works but it is still rough around the edges (i.e. documentation
  is still being written, the coding style is not 100% yet, a number of
  tests still fail) but we spent the entire weekend working on it in Paris
  and made a ton of progress.  The best way to help and to get up to speed,
  is to start writing and contributing some tests ... as well as fixing
  some of the failures.

  For those willing to help with improving the test framework, here are
  some next steps and issues to resolve:

    - How to best approach unit tests and mock functions?
    - How to test drupal_mail() and drupal_http_request()?
    - How to improve the admin UI so we have a nice progress bar?
    - How best to do code coverage?
    - See http://g.d.o/node/10099 for more ...
parent fe7b9baf
......@@ -14,6 +14,8 @@ Drupal 7.0, xxxx-xx-xx (development version)
* Provide descriptions for permissions on the administration page.
- Search:
* Made it possible to language-aware searches.
- Testing:
* Added test framework and tests.
- Removed ping module:
* This module has been removed from the core download. Contributed
alternatives are available.
......
......@@ -122,7 +122,7 @@ function db_prefix_tables($sql) {
* @return the name of the previously active database or FALSE if non was found.
*/
function db_set_active($name = 'default') {
global $db_url, $db_type, $active_db;
global $db_url, $db_type, $active_db, $db_prefix;
static $db_conns, $active_name = FALSE;
if (empty($db_url)) {
......@@ -150,6 +150,12 @@ function db_set_active($name = 'default') {
}
$db_conns[$name] = db_connect($connect_url);
// We need to pass around the simpletest database prefix in the request
// and we put that in the user_agent header.
if (preg_match("/^simpletest\d+$/", $_SERVER['HTTP_USER_AGENT'])) {
$db_prefix = $_SERVER['HTTP_USER_AGENT'];
}
}
$previous_name = $active_name;
......
<?php
// $Id$
class XMLRPCValidator1Test extends DrupalUnitTestCase {
function getInfo() {
return array('name' => t('XML-RPC validator1'),
'description' => t('See !validator-link. note: simpletest_xmlrpc.module must be enabled', array('!validator-link' => l('the xmlrpc validator1 specification', 'http://www.xmlrpc.com/validator1Docs'))),
'group' => t('XML-RPC'));
}
function test_run_all_tests() {
if (!$this->drupalModuleEnable('simpletest_xmlrpc')) {
return FALSE;
}
$xml_url = url(NULL, array('absolute' => TRUE)) . 'xmlrpc.php';
srand();
mt_srand();
$array_1 = array(array('curly' => mt_rand(-100,100)),
array('curly' => mt_rand(-100,100)),
array('larry' => mt_rand(-100,100)),
array('larry' => mt_rand(-100,100)),
array('moe' => mt_rand(-100,100)),
array('moe' => mt_rand(-100,100)),
array('larry' => mt_rand(-100,100)));
shuffle($array_1);
$l_res_1 = simpletest_xmlrpc_arrayOfStructsTest($array_1);
$r_res_1 = xmlrpc($xml_url, 'validator1.arrayOfStructsTest', $array_1);
$this->assertIdentical($l_res_1, $r_res_1, 'array of structs test: %s');
$string_2 = 't\'&>>zf"md>yr>xlcev<h<"k&j<og"w&&>">>uai"np&s>>q\'&b<>"&&&';
$l_res_2 = simpletest_xmlrpc_countTheEntities($string_2);
$r_res_2 = xmlrpc($xml_url, 'validator1.countTheEntities', $string_2);
$this->assertIdentical($l_res_2, $r_res_2, 'count the entities test: %s');
$struct_3 = array('moe' => mt_rand(-100,100), 'larry' => mt_rand(-100,100), 'curly' => mt_rand(-100,100), 'homer' => mt_rand(-100,100));
$l_res_3 = simpletest_xmlrpc_easyStructTest($struct_3);
$r_res_3 = xmlrpc($xml_url, 'validator1.easyStructTest', $struct_3);
$this->assertIdentical($l_res_3, $r_res_3, 'easy struct test: %s');
$struct_4 = array('sub1' => array('bar' => 13),
'sub2' => 14,
'sub3' => array('foo' => 1, 'baz' => 2),
'sub4' => array('ss' => array('sss' => array('ssss' => 'sssss'))));
$l_res_4 = simpletest_xmlrpc_echoStructTest($struct_4);
$r_res_4 = xmlrpc($xml_url, 'validator1.echoStructTest', $struct_4);
$this->assertIdentical($l_res_4, $r_res_4, 'echo struct test: %s');
$int_5 = mt_rand(-100,100);
$bool_5 = (($int_5 % 2) == 0);
$string_5 = $this->randomName();
$double_5 = (double)(mt_rand(-1000,1000) / 100);
$time_5 = time();
$base64_5 = $this->randomName(100);
$l_res_5 = simpletest_xmlrpc_manyTypesTest($int_5, $bool_5, $string_5, $double_5, xmlrpc_date($time_5), $base64_5);
$l_res_5[5] = $l_res_5[5]->data; /* override warpping */
$r_res_5 = xmlrpc($xml_url, 'validator1.manyTypesTest', $int_5, $bool_5, $string_5, $double_5, xmlrpc_date($time_5), xmlrpc_base64($base64_5));
/* Contains objects, objects are not equal */
// See http://drupal.org/node/37766 why this currnetly fails
$this->assertEqual($l_res_5, $r_res_5, 'many types test: %s');
$size = mt_rand(100,200);
$array_6 = array();
for ($i = 0; $i < $size; $i++) {
$array_6[] = $this->randomName(mt_rand(8,12));
}
$l_res_6 = simpletest_xmlrpc_moderateSizeArrayCheck($array_6);
$r_res_6 = xmlrpc($xml_url, 'validator1.moderateSizeArrayCheck', $array_6);
$this->assertIdentical($l_res_6, $r_res_6, 'moderate size array check: %s');
$struct_7 = array();
for ($y = 2000; $y < 2002; $y++) {
for ($m = 3; $m < 5; $m++) {
for ($d = 1; $d < 6; $d++) {
$ys = (string)$y;
$ms = sprintf('%02d', $m);
$ds = sprintf('%02d', $d);
$struct_7[$ys][$ms][$ds]['moe'] = mt_rand(-100,100);
$struct_7[$ys][$ms][$ds]['larry'] = mt_rand(-100,100);
$struct_7[$ys][$ms][$ds]['curly'] = mt_rand(-100,100);
}
}
}
$l_res_7 = simpletest_xmlrpc_nestedStructTest($struct_7);
$r_res_7 = xmlrpc($xml_url, 'validator1.nestedStructTest', $struct_7);
$this->assertIdentical($l_res_7, $r_res_7, 'nested struct test: %s');
$int_8 = mt_rand(-100,100);
$l_res_8 = simpletest_xmlrpc_simpleStructReturnTest($int_8);
$r_res_8 = xmlrpc($xml_url, 'validator1.simpleStructReturnTest', $int_8);
$this->assertIdentical($l_res_8, $r_res_8, 'nested struct test: %s');
/* Now test multicall */
$x = array();
$x[] = array('validator1.arrayOfStructsTest', $array_1);
$x[] = array('validator1.countTheEntities', $string_2);
$x[] = array('validator1.easyStructTest', $struct_3);
$x[] = array('validator1.echoStructTest', $struct_4);
$x[] = array('validator1.manyTypesTest', $int_5, $bool_5, $string_5, $double_5, xmlrpc_date($time_5), xmlrpc_base64($base64_5));
$x[] = array('validator1.moderateSizeArrayCheck', $array_6);
$x[] = array('validator1.nestedStructTest', $struct_7);
$x[] = array('validator1.simpleStructReturnTest', $int_8);
$a_l_res = array($l_res_1, $l_res_2, $l_res_3, $l_res_4, $l_res_5, $l_res_6, $l_res_7, $l_res_8);
$a_r_res = xmlrpc($xml_url, $x);
$this->assertEqual($a_l_res, $a_r_res, 'multicall equals result');
}
}
?>
\ No newline at end of file
<?php
// $Id$
class AggregatorTestCase extends DrupalWebTestCase {
private static $prefix = 'simpletest_aggregator_';
/**
* Implementation of setUp().
*/
function setUp() {
parent::setUp('aggregator');
$web_user = $this->drupalCreateUser(array('administer news feeds', 'access news feeds'));
$this->drupalLogin($web_user);
}
/**
* Create an aggregator feed (simulate form submission on admin/content/aggregator/add/feed).
*
* @return $feed Full feed object if possible.
*/
function createFeed() {
$edit = $this->getFeedEditArray();
$this->drupalPost('admin/content/aggregator/add/feed', $edit, t('Save'));
$this->assertRaw(t('The feed %name has been added.', array('%name' => $edit['title'])), t('The feed !name has been added.', array('!name' => $edit['title'])));
$feed = db_fetch_object(db_query("SELECT * FROM {aggregator_feed} WHERE title = '%s' AND url='%s'", $edit['title'], $edit['url']));
$this->assertTrue(!empty($feed), t('The feed found in database.'));
return $feed;
}
/**
* Delete an aggregator feed.
*
* @param object $feed Feed object representing the feed.
*/
function deleteFeed($feed) {
$this->drupalPost('admin/content/aggregator/edit/feed/'. $feed->fid, array(), t('Delete'));
$this->assertRaw(t('The feed %title has been deleted.', array('%title' => $feed->title)), t('Feed deleted successfully.'));
}
/**
* Return a randomly generated feed edit array.
*
* @return array Feed array.
*/
function getFeedEditArray() {
$feed_name = $this->randomName(10, self::$prefix);
$feed_url = url(NULL, array('absolute' => TRUE)) .'rss.xml?feed='. $feed_name;
$edit = array(
'title' => $feed_name,
'url' => $feed_url,
'refresh' => '900',
);
return $edit;
}
/**
* Update feed items (simulate click to admin/content/aggregator/update/$fid).
*
* @param object $feed Feed object representing the feed.
*/
function updateFeedItems(&$feed) {
// First, let's ensure we could get to the rss xml
$this->drupalGet('rss.xml');
$this->assertResponse(200, t('rss.xml is reachable.'));
// our tests are based off of rss.xml, so let's find out how many elements should be related
$feed_count = db_result(db_query_range(db_rewrite_sql('SELECT COUNT(*) FROM {node} n WHERE n.promote = 1 AND n.status = 1'), 0, variable_get('feed_default_items', 10)));
$feed_count = $feed_count > 10 ? 10 : $feed_count;
// refresh the feed (simulated link click)
$this->drupalGet('admin/content/aggregator/update/'. $feed->fid);
// ensure we have the right number of items
$result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = %d', $feed->fid);
$items = array();
$feed->items = array();
while ($item = db_fetch_object($result)) {
$feed->items[] = $item->iid;
}
$feed->item_count = count($feed->items);
$this->assertEqual($feed_count, $feed->item_count, t('Total items in feed equal to the total items in database (!val1 != !val2)', array('!val1' => $feed_count, '!val2' => $feed->item_count)));
}
/**
* Confirm item removal from a feed.
*
* @param object $feed Feed object representing the feed.
*/
function removeFeedItems($feed) {
$this->drupalPost('admin/content/aggregator/remove/' . $feed->fid, array(), t('Remove items'));
$this->assertRaw(t('The news items from %title have been removed.', array('%title' => $feed->title)), t('Feed items removed.'));
}
/**
* Pull feed categories from aggregator_category_feed table.
*
* @param object $feed Feed object representing the feed.
*/
function getFeedCategories($feed) {
// add the categories to the feed so we can use them
$result = db_query('SELECT cid FROM {aggregator_category_feed} WHERE fid = %d', $feed->fid);
while ($category = db_fetch_object($result)) {
$feed->categories[] = $category->cid;
}
}
/**
* Check if the feed name and url is unique.
*
* @param string $feed_name Feed name to check.
* @param string $feed_url Feed url to check.
* @return boolean Feed is unique.
*/
function uniqueFeed($feed_name, $feed_url) {
$result = db_result(db_query("SELECT count(*) FROM {aggregator_feed} WHERE title = '%s' AND url='%s'", $feed_name, $feed_url));
return (1 == $result);
}
}
class AddFeedTestCase extends AggregatorTestCase {
/**
* Implementation of getInfo().
*/
function getInfo() {
return array(
'name' => t('Add feed functionality'),
'description' => t('Add feed test.'),
'group' => t('Aggregator')
);
}
/**
* Create a feed, ensure that it is unique, check the source, and delete the feed.
*/
function testAddFeed() {
$feed = $this->createFeed();
// Check feed data.
$this->assertEqual($this->getUrl(), url('admin/content/aggregator/add/feed', array('absolute' => TRUE)), t('Directed to correct url.'));
$this->assertTrue($this->uniqueFeed($feed->title, $feed->url), t('The feed is unique.'));
// Check feed source.
$this->drupalGet('aggregator/sources/'. $feed->fid);
$this->assertResponse(200, t('Feed source exists.'));
$this->assertText($feed->title, t('Page title'));
// Delete feed.
$this->deleteFeed($feed);
}
}
class UpdateFeedTestCase extends AggregatorTestCase {
/**
* Implementation of getInfo().
*/
function getInfo() {
return array(
'name' => t('Update feed functionality'),
'description' => t('Update feed test.'),
'group' => t('Aggregator')
);
}
/**
* Create a feed and attempt to update it.
*/
function testUpdateFeed() {
$feed = $this->createFeed();
// Get new feed data array and modify newly created feed.
$edit = $this->getFeedEditArray();
$edit['refresh'] = 1800; // Change refresh value.
$this->drupalPost('admin/content/aggregator/edit/feed/'. $feed->fid, $edit, t('Save'));
$this->assertRaw(t('The feed %name has been updated.', array('%name' => $edit['title'])), t('The feed %name has been updated.', array('%name' => $edit['title'])));
// Check feed data.
$this->assertEqual($this->getUrl(), url('admin/content/aggregator/', array('absolute' => TRUE)));
$this->assertTrue($this->uniqueFeed($edit['title'], $edit['url']), t('The feed is unique.'));
// Check feed source.
$this->drupalGet('aggregator/sources/'. $feed->fid);
$this->assertResponse(200, t('Feed source exists.'));
$this->assertText($edit['title'], t('Page title'));
// Delete feed.
$feed->title = $edit['title']; // Set correct title so deleteFeed() will work.
$this->deleteFeed($feed);
}
}
class RemoveFeedTestCase extends AggregatorTestCase {
/**
* Implementation of getInfo().
*/
function getInfo() {
return array(
'name' => t('Remove feed functionality'),
'description' => t('Remove feed test.'),
'group' => t('Aggregator')
);
}
/**
* Remove a feed and ensure that all it services are removed.
*/
function testRemoveFeed() {
$feed = $this->createFeed();
// Delete feed.
$this->deleteFeed($feed);
// Check feed source.
$this->drupalGet('aggregator/sources/'. $feed->fid);
$this->assertResponse(404, t('Deleted feed source does not exists.'));
// Check database for feed.
$result = db_result(db_query("SELECT count(*) FROM {aggregator_feed} WHERE title = '%s' AND url='%s'", $feed->title, $feed->url));
$this->assertFalse($result, t('Feed not found in database'));
}
}
class UpdateFeedItemTestCase extends AggregatorTestCase {
/**
* Implementation of getInfo().
*/
function getInfo() {
return array(
'name' => t('Update feed item functionality'),
'description' => t('Update feed items from a feed.'),
'group' => t('Aggregator')
);
}
/**
* Test running "update items" from the 'admin/content/aggregator' page.
*/
function testUpdateFeedItem() {
// Create a feed and test updating feed items if possible.
$feed = $this->createFeed();
if (!empty($feed)) {
$this->updateFeedItems($feed);
$this->removeFeedItems($feed);
}
// Delete feed.
$this->deleteFeed($feed);
}
}
class RemoveFeedItemTestCase extends AggregatorTestCase {
/**
* Implementation of getInfo().
*/
function getInfo() {
return array(
'name' => t('Remove feed item functionality'),
'description' => t('Remove feed items from a feed.'),
'group' => t('Aggregator')
);
}
/**
* Test running "remove items" from the 'admin/content/aggregator' page.
*/
function testRemoveFeedItem() {
$feed = $this->createFeed();
// Add and remove feed items and ensure that the count is zero.
$this->updateFeedItems($feed);
$this->removeFeedItems($feed);
$count = db_result(db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = %d', $feed->fid));
$this->assertTrue($count == 0);
// Delete feed.
$this->deleteFeed($feed);
}
}
class CategorizeFeedItemTestCase extends AggregatorTestCase {
/**
* Implementation of getInfo().
*/
function getInfo() {
return array(
'name' => t('Categorize feed item functionality'),
'description' => t('Test feed item categorization.'),
'group' => t('Aggregator')
);
}
/**
* If a feed has a category, make sure that the children inherit that
* categorization.
*/
function testCategorizeFeedItem() {
// TODO: Need to add categories to the feed on creation.
$feed = $this->createFeed();
$this->updateFeedItems($feed);
$this->getFeedCategories($feed);
// For each category of a feed, ensure feed items have that category, too.
if (!empty($feed->categories) && !empty($feed->items)) {
foreach ($feed->categories as $category) {
$items_str = implode(', ', $feed->items);
$categorized_count = db_result(db_query('SELECT COUNT(*) FROM {aggregator_category_item} WHERE iid IN (' . $items_str . ')'));
$this->assertEqual($feed->item_count, $categorized_count, t('Total items in feed equal to the total categorized feed items in database'));
}
}
// Delete feed.
$this->deleteFeed($feed);
}
}
<?php
// $Id$
class BlockTestCase extends DrupalWebTestCase {
/**
* Implementation of getInfo().
*/
function getInfo() {
return array(
'name' => t('Block functionality'),
'description' => t('Add, edit and delete custom block. Configure and move a module-defined block.'),
'group' => t('Block'),
);
}
/**
* Implementation of setUp().
*/
function setUp() {
parent::setUp();
// Create and login user
$admin_user = $this->drupalCreateUser(array('administer blocks'));
$this->drupalLogin($admin_user);
}
/**
* Test creating custom block (i.e. box), moving it to a specific region and then deleting it.
*/
function testBox() {
// Add a new box by filling out the input form on the admin/build/block/add page.
$box = array();
$box['info'] = $this->randomName(8);
$box['title'] = $this->randomName(8);
$box['body'] = $this->randomName(32);
$this->drupalPost('admin/build/block/add', $box, t('Save block'));
// Confirm that the box has been created, and then query the created bid.
$this->assertText(t('The block has been created.'), t('Box successfully created.'));
$bid = db_result(db_query("SELECT bid FROM {boxes} WHERE info = '%s'", array($box['info'])));
// Check to see if the box was created by checking that it's in the database..
$this->assertNotNull($bid, t('Box found in database'));
// Set the created box to a specific region.
// TODO: Implement full region checking.
$edit = array();
$edit['block_'. $bid .'[region]'] = 'left';
$this->drupalPost('admin/build/block', $edit, t('Save blocks'));
// Confirm that the box was moved to the proper region.
$this->assertText(t('The block settings have been updated.'), t('Box successfully moved to left region.'));
// Confirm that the box is being displayed.
$this->assertText(t($box['title']), t('Box successfully being displayed on the page.'));
// Delete the created box & verify that it's been deleted and no longer appearing on the page.
$this->drupalPost('admin/build/block/delete/'. $bid, array(), t('Delete'));
$this->assertRaw(t('The block %title has been removed.', array('%title' => $box['info'])), t('Box successfully deleted.'));
$this->assertNoText(t($box['title']), t('Box no longer appears on page.'));
}
/**
* Test configuring and moving a module-define block to specific regions.
*/
function testBlock() {
// Select the Navigation block to be configured and moved.
$block = array();
$block['module'] = 'user';
$block['delta'] = 'navigation';
$block['title'] = $this->randomName(8);
// Set block title to confirm that interface works and override any custom titles.
$this->drupalPost('admin/build/block/configure/'. $block['module'] .'/'. $block['delta'], array('title' => $block['title']), t('Save block'));
$this->assertText(t('The block configuration has been saved.'), t('Block title set.'));
$bid = db_result(db_query("SELECT bid FROM {blocks} WHERE module = '%s' AND delta = %d", array($block['module'], $block['delta'])));
// Check to see if the block was created by checking that it's in the database.
$this->assertNotNull($bid, t('Block found in database'));
// Set the created block to a specific region.
$edit = array();
$edit[$block['module'] .'_'. $block['delta'] .'[region]'] = 'left';
$this->drupalPost('admin/build/block', $edit, t('Save blocks'));
// Confirm that the block was moved to the proper region.
// TODO: Implement full region checking.
$this->assertText(t('The block settings have been updated.'), t('Block successfully moved to left region.'));
// Confirm that the block is being displayed.
$this->assertText(t($block['title']), t('Block successfully being displayed on the page.'));
// Set the block to the disabled region.
$edit = array();
$edit[$block['module'] .'_'. $block['delta'] .'[region]'] = '-1';
$this->drupalPost('admin/build/block', $edit, t('Save blocks'));
// Confirm that the block was moved to the proper region.
$this->assertText(t('The block settings have been updated.'), t('Block successfully move to disabled region.'));
$this->assertNoText(t($block['title']), t('Block no longer appears on page.'));
// For convenience of developers, put the navigation block back.
$edit = array();
$edit[$block['module'] .'_'. $block['delta'] .'[region]'] = 'left';
$this->drupalPost('admin/build/block', $edit, t('Save blocks'));
$this->assertText(t('The block settings have been updated.'), t('Block successfully move to disabled region.'));
$this->drupalPost('admin/build/block/configure/'. $block['module'] .'/'. $block['delta'], array('title' => 'Navigation'), t('Save block'));
$this->assertText(t('The block configuration has been saved.'), t('Block title set.'));
}
}
<?php
// $Id$
class BlogTestCase extends DrupalWebTestCase {
protected $big_user;
protected $own_user;
protected $any_user;
/**
* Implementation of getInfo().
*/
function getInfo() {
return array(
'name' => t('Blog functionality'),
'description' => t('Create, view, edit, delete, and change blog entries and verify its consistency in the database.'),
'group' => t('Blog'),
);
}
/**
* Enable modules and create users with specific permissions.
*/
function setUp() {
parent::setUp('blog');
// Create users.
$this->big_user = $this->drupalCreateUser(array('administer blocks'));
$this->own_user = $this->drupalCreateUser(array('create blog content', 'edit own blog content', 'delete own blog content'));
$this->any_user = $this->drupalCreateUser(array('create blog content', 'edit any blog content', 'delete any blog content', 'access administration pages'));
}
/**
* Login users, create blog nodes, and test blog functionality through the admin and user interfaces.
*/
function testBlog() {
// Login the admin user.
$this->drupalLogin($this->big_user);
// Enable the recent blog block.
$edit = array();
$edit['blog_recent[region]'] = 'right';
$this->drupalPost('admin/build/block', $edit, t('Save blocks'));
$this->assertResponse(200);
// Do basic tests for each user.
$this->doBasicTests($this->any_user, TRUE);
$this->doBasicTests($this->own_user, FALSE);
// Create another blog node for the any blog user.
$node = $this->drupalCreateNode(array('type' => 'blog', 'uid' => $this->any_user->uid));
// Verify the own blog user only has access to the blog view node.
$this->verifyBlogs($this->any_user, $node, FALSE, 403);
// Create another blog node for the own blog user.
$node = $this->drupalCreateNode(array('type' => 'blog', 'uid' => $this->own_user->uid));
// Login the any blog user.
$this->drupalLogin($this->any_user);
// Verify the any blog user has access to all the blog nodes.
$this->verifyBlogs($this->own_user, $node, TRUE);
}
/**
* Run basic tests on the indicated user.
*
* @param object $user The logged in user.
* @param boolean $admin User has 'access administration pages' privilege.
*/
private function doBasicTests($user, $admin) {
// Login the user.
$this->drupalLogin($user);
// Create blog node.
$node = $this->drupalCreateNode(array('type' => 'blog', 'uid' => $user->uid));
// Verify the user has access to all the blog nodes.
$this->verifyBlogs($user, $node, $admin);
// Verify the blog links are displayed.
$this->verifyBlogLinks($user);
}
/**