Index: scripts/run-tests.sh =================================================================== RCS file: /cvs/drupal/drupal/scripts/run-tests.sh,v retrieving revision 1.23 diff -u -r1.23 run-tests.sh --- scripts/run-tests.sh 1 Feb 2009 16:42:26 -0000 1.23 +++ scripts/run-tests.sh 17 Feb 2009 08:18:33 -0000 @@ -285,7 +285,7 @@ exit; } if ($args['concurrency'] == 1) { - // Fallback to mono-threaded execution. + // Fallback to mono-process execution. if (count($args['test_names']) > 1) { foreach ($args['test_names'] as $test_class) { // Execute each test in its separate Drupal environment. @@ -302,7 +302,7 @@ } } else { - // Multi-threaded execution. + // Multi-process execution. $children = array(); while (!empty($args['test_names']) || !empty($children)) { // Fork children safely since Drupal is not bootstrapped yet. Index: modules/aggregator/aggregator.test =================================================================== RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.test,v retrieving revision 1.18 diff -u -r1.18 aggregator.test --- modules/aggregator/aggregator.test 22 Jan 2009 12:59:32 -0000 1.18 +++ modules/aggregator/aggregator.test 17 Feb 2009 08:18:32 -0000 @@ -2,7 +2,7 @@ // $Id: aggregator.test,v 1.18 2009/01/22 12:59:32 dries Exp $ class AggregatorTestCase extends DrupalWebTestCase { - private static $prefix = 'simpletest_aggregator_'; + private static $stringPrefix = 'simpletest_aggregator_'; function setUp() { parent::setUp('aggregator'); @@ -41,7 +41,7 @@ * @return array Feed array. */ function getFeedEditArray() { - $feed_name = $this->randomName(10, self::$prefix); + $feed_name = $this->randomName(10, self::$stringPrefix); $feed_url = url(NULL, array('absolute' => TRUE)) . 'rss.xml?feed=' . $feed_name; $edit = array( 'title' => $feed_name, @@ -423,7 +423,7 @@ } class CategorizeFeedItemTestCase extends AggregatorTestCase { - private static $prefix = 'simpletest_aggregator_'; + private static $stringPrefix = 'simpletest_aggregator_'; function getInfo() { return array( @@ -441,7 +441,7 @@ $this->createSampleNodes(); // Simulate form submission on "admin/content/aggregator/add/category". - $edit = array('title' => $this->randomName(10, self::$prefix), 'description' => ''); + $edit = array('title' => $this->randomName(10, self::$stringPrefix), 'description' => ''); $this->drupalPost('admin/content/aggregator/add/category', $edit, t('Save')); $this->assertRaw(t('The category %title has been added.', array('%title' => $edit['title'])), t('The category %title has been added.', array('%title' => $edit['title']))); @@ -482,7 +482,7 @@ } class ImportOPMLTestCase extends AggregatorTestCase { - private static $prefix = 'simpletest_aggregator_'; + private static $stringPrefix = 'simpletest_aggregator_'; function getInfo() { return array( @@ -498,7 +498,7 @@ function openImportForm() { db_delete('aggregator_category')->execute(); - $category = $this->randomName(10, self::$prefix); + $category = $this->randomName(10, self::$stringPrefix); $cid = db_insert('aggregator_category') ->fields(array( 'title' => $category, @@ -561,7 +561,7 @@ db_delete('aggregator_category')->execute(); db_delete('aggregator_category_feed')->execute(); - $category = $this->randomName(10, self::$prefix); + $category = $this->randomName(10, self::$stringPrefix); db_insert('aggregator_category') ->fields(array( 'cid' => 1, Index: modules/simpletest/simpletest.module =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.module,v retrieving revision 1.36 diff -u -r1.36 simpletest.module --- modules/simpletest/simpletest.module 13 Feb 2009 00:39:01 -0000 1.36 +++ modules/simpletest/simpletest.module 17 Feb 2009 08:18:33 -0000 @@ -352,6 +352,9 @@ * drupal being the default. */ function simpletest_run_tests($test_list, $reporter = 'drupal') { + module_load_include('inc', 'simpletest', 'simpletest.environment'); + DrupalTestEnvironment::init(); + cache_clear_all(); $test_id = db_insert('simpletest_test_id')->useDefaults(array('test_id'))->execute(); @@ -435,6 +438,8 @@ } function _simpletest_batch_finished($success, $results, $operations, $elapsed) { + DrupalTestEnvironment::deinit(); + if (isset($results['test_id'])) { drupal_set_session('test_id', $results['test_id']); } @@ -516,6 +521,7 @@ simpletest_clean_database(); simpletest_clean_temporary_directories(); simpletest_clean_results_table(); + variable_del('simpletest_environment_prefixes'); } /** @@ -528,7 +534,7 @@ foreach (array_diff_key($tables, $schema) as $table) { // Strip the prefix and skip tables without digits following "simpletest", // e.g. {simpletest_test_id}. - if (preg_match('/simpletest\d+.*/', $table, $matches)) { + if (preg_match('/simpletest\d+.*/', $table, $matches) || preg_match('/simpletest_base.*/', $table, $matches)) { db_drop_table($ret, $matches[0]); } } Index: modules/simpletest/drupal_web_test_case.php =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v retrieving revision 1.84 diff -u -r1.84 drupal_web_test_case.php --- modules/simpletest/drupal_web_test_case.php 13 Feb 2009 00:39:01 -0000 1.84 +++ modules/simpletest/drupal_web_test_case.php 17 Feb 2009 08:18:33 -0000 @@ -1,10 +1,12 @@ testId = $test_id; } @@ -812,127 +794,17 @@ * List of modules to enable for the duration of the test. */ protected function setUp() { - global $db_prefix, $user; - - // Store necessary current values before switching to prefixed database. - $this->originalPrefix = $db_prefix; - $clean_url_original = variable_get('clean_url', 0); - - // Generate temporary prefixed database to ensure that tests have a clean starting point. - $db_prefix = Database::getConnection()->prefixTables('{simpletest' . mt_rand(1000, 1000000) . '}'); - - include_once DRUPAL_ROOT . '/includes/install.inc'; - drupal_install_system(); - - $this->preloadRegistry(); - - // Add the specified modules to the list of modules in the default profile. - $args = func_get_args(); - $modules = array_unique(array_merge(drupal_get_profile_modules('default', 'en'), $args)); - drupal_install_modules($modules, TRUE); - - // Because the schema is static cached, we need to flush - // it between each run. If we don't, then it will contain - // 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, ''); - - // Rebuild caches. - actions_synchronize(); - _drupal_flush_css_js(); - $this->refreshVariables(); - $this->checkPermissions(array(), TRUE); - - // Log in with a clean $user. - $this->originalUser = $user; - drupal_save_session(FALSE); - $user = user_load(array('uid' => 1)); - - // Restore necessary variables. - variable_set('install_profile', 'default'); - variable_set('install_task', 'profile-finished'); - variable_set('clean_url', $clean_url_original); - variable_set('site_mail', 'simpletest@example.com'); - - // Use temporary files directory with the same prefix as database. - $this->originalFileDirectory = file_directory_path(); - variable_set('file_directory_path', file_directory_path() . '/' . $db_prefix); - $directory = file_directory_path(); - file_check_directory($directory, FILE_CREATE_DIRECTORY); // Create the files directory. + parent::setUp(); set_time_limit($this->timeLimit); } /** - * This method is called by DrupalWebTestCase::setUp, and preloads the - * registry from the testing site to cut down on the time it takes to - * setup a clean environment for the current test run. - */ - protected function preloadRegistry() { - db_query('INSERT INTO {registry} SELECT * FROM ' . $this->originalPrefix . 'registry'); - db_query('INSERT INTO {registry_file} SELECT * FROM ' . $this->originalPrefix . 'registry_file'); - } - - /** - * Refresh the in-memory set of variables. Useful after a page request is made - * that changes a variable in a different thread. - * - * In other words calling a settings page with $this->drupalPost() with a changed - * value would update a variable to reflect that change, but in the thread that - * made the call (thread running the test) the changed variable would not be - * picked up. - * - * This method clears the variables cache and loads a fresh copy from the database - * to ensure that the most up-to-date set of variables is loaded. - */ - protected function refreshVariables() { - global $conf; - cache_clear_all('variables', 'cache'); - $conf = variable_init(); - } - - /** * Delete created files and temporary files directory, delete the tables created by setUp(), * and reset the database prefix. */ protected function tearDown() { - global $db_prefix, $user; if (preg_match('/simpletest\d+/', $db_prefix)) { - // Delete temporary files directory and reset files directory path. - file_unmanaged_delete_recursive(file_directory_path()); - variable_set('file_directory_path', $this->originalFileDirectory); - - // Remove all prefixed tables (all the tables in the schema). - $schema = drupal_get_schema(NULL, TRUE); - $ret = array(); - foreach ($schema as $name => $table) { - db_drop_table($ret, $name); - } - - // Return the database prefix to the original. - $db_prefix = $this->originalPrefix; - - // Return the user to the original one. - $user = $this->originalUser; - drupal_save_session(TRUE); - - // Ensure that internal logged in variable and cURL options are reset. - $this->isLoggedIn = FALSE; - $this->additionalCurlOptions = array(); - - // Reload module list and implementations to ensure that test module hooks - // aren't called after tests. - module_list(TRUE); - module_implements(MODULE_IMPLEMENTS_CLEAR_CACHE); - - // Reset the Field API. - field_cache_clear(); - - // Rebuild caches. - $this->refreshVariables(); + parent::tearDown(); // Close the CURL handler. $this->curlClose(); Index: modules/simpletest/simpletest.environment.inc =================================================================== RCS file: modules/simpletest/simpletest.environment.inc diff -N modules/simpletest/simpletest.environment.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/simpletest/simpletest.environment.inc 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,300 @@ +base = $base; + $this->prefix = self::request($this->base); + } + + protected function setUp() { + global $db_prefix, $user; + $this->modules = func_get_args(); + $this->copyData(); + + if ($this->base == self::$DEFAULT_BASE) { + $this->installModules(); + $this->destroyTestDatabase = (bool) module_implements('schema_alter'); + } + else { + $this->destroyTestDatabase = FALSE; + } + + $this->prefixOriginal = $db_prefix; + $db_prefix = $this->prefix; + $this->flushStatics(); + + // Log in with a clean $user. + $this->userOriginal = $user; + drupal_save_session(FALSE); + $user = user_load(array('uid' => 1)); + + // Use temporary files directory with the same prefix as database. +// $this->directoryOriginal = file_directory_path(); + $directory = file_directory_path() . '/' . $this->prefix; + variable_set('file_directory_path', $directory); + file_check_directory($directory, FILE_CREATE_DIRECTORY); // Create the files directory. + } + + protected function copyData() { + $source_tables = db_find_tables($this->base . '%'); + + foreach ($source_tables as $source_table) { + $table = str_replace($this->base, '', $source_table); + $destination_table = $this->prefix . $table; + + if ($table == 'users') { + // Due to uid 0 the data connot be copied like the rest of tables. + db_query('INSERT INTO ' . $destination_table . ' SELECT * FROM ' . $source_table . ' WHERE uid = 0'); + db_query('UPDATE ' . $destination_table . ' SET uid = uid - 1'); + db_query('INSERT INTO ' . $destination_table . ' SELECT * FROM ' . $source_table . ' WHERE uid > 0'); + continue; + } + db_query('INSERT INTO ' . $destination_table . ' SELECT * FROM ' . $source_table); + } + } + + protected function installModules() { + require_once DRUPAL_ROOT . '/includes/install.inc'; + + drupal_install_modules($this->modules); + + // Ensure that module_implements() check works. + module_implements(MODULE_IMPLEMENTS_CLEAR_CACHE); + } + + protected function tearDown() { + global $db_prefix, $user; + + $db_prefix = $this->prefixOriginal; + + if ($this->destroyTestDatabase) { + self::destoryEnvironment($this->prefix); + + // Request new test environment. + $this->prefix = self::request($this->base); + } + else { + // Removed installed module tables. + $this->uninstallModules(); + + // Empty remaining tables. + $tables = $this->getTables(); + foreach ($tables as $table) { + db_delete($table)->execute(); + } + } + + // Return to the original user. + $user = $this->userOriginal; + drupal_save_session(TRUE); + + $this->flushStatics(); + } + + protected function uninstallModules() { + drupal_uninstall_modules($this->modules); + } + + protected function flushStatics() { + // Because the schema is static cached, we need to flush + // it between each run. If we don't, then it will contain + // stale data for the previous run's database prefix and all + // calls to it will fail. + drupal_get_schema(NULL, TRUE); + + // Rebuild caches. + actions_synchronize(); + _drupal_flush_css_js(); + $this->refreshVariables(); + $this->checkPermissions(array(), TRUE); + + // Reload module list and implementations to ensure that test module hooks + // aren't called after tests. + module_list(TRUE); + module_implements(MODULE_IMPLEMENTS_CLEAR_CACHE); + + // Reset the Field API. + field_cache_clear(); + } + + protected function getTables() { + return db_find_tables($this->prefix . '%'); + } + + protected static final function request($base) { + $prefixes = variable_get('simpletest_environment_prefixes', array()); + + if (isset($prefixes[$base]) && $prefixes[$base]) { + // Use an existing database as a starting shell. + return array_pop($prefixes); + } + + // Ensure that base exists. + if (!self::baseExists($base)) { + // Create base database using default install profile. + self::createBase($base); + } + + // Create test database from base. + $prefix = Database::getConnection()->prefixTables('{simpletest' . mt_rand(1000, 1000000) . '}'); +// self::copySchema($base, $prefix); + return $prefix; + } + + protected static final function copySchema($base, $prefix) { + global $db_prefix; + + $result = db_query('SELECT filename + FROM ' . $base . 'system + WHERE status = 1'); + + $prefixOriginal = $db_prefix; + $db_prefix = $prefix; + + while ($module = db_result($result)) { + module_load_include('install', $module); + drupal_install_schema($module); + } + + $db_prefix = $prefixOriginal; + } + + protected static final function release($base, $prefix) { + $prefixes = variable_get('simpletest_environment_prefixes', array()); + + if (!isset($prefixes[$base])) { + $prefixes[$base] = array(); + } + + $prefixes[$base][] = $prefix; + variable_set('simpletest_environment_prefixes', $prefixes); + } + + /** + * Check for base environment + * + * @param $base + * Name of base to check for. + */ + protected static final function baseExists($base) { + return (bool) db_find_tables($base . '%'); + } + + protected static final function createBase($base) { + global $db_prefix; + + $clean_url_original = variable_get('clean_url', 0); + + $prefixOriginal = $db_prefix; + $db_prefix = $base; + + require_once DRUPAL_ROOT . '/includes/install.inc'; + drupal_install_system(); + + self::preloadRegistry(); + + $modules = drupal_get_profile_modules('default', 'en'); + drupal_install_modules($modules, TRUE); + + // Add the specified modules to the list of modules in the default profile. + $args = func_get_args(); + $modules = array_unique(array_merge(drupal_get_profile_modules('default', 'en'), $args)); + drupal_install_modules($modules, TRUE); + + // Run default profile tasks. + $task = 'profile'; + default_profile_tasks($task, ''); + + // Restore necessary variables. + variable_set('install_profile', 'default'); + variable_set('install_task', 'profile-finished'); + variable_set('clean_url', $clean_url_original); + variable_set('site_mail', 'simpletest@example.com'); + + $db_prefix = $prefixOriginal; + } + + /** + * This method is called by DrupalWebTestCase::setUp, and preloads the + * registry from the testing site to cut down on the time it takes to + * setup a clean environment for the current test run. + */ + protected static final function preloadRegistry() { + db_query('INSERT INTO {registry} SELECT * FROM ' . $this->prefixOriginal . 'registry'); + db_query('INSERT INTO {registry_file} SELECT * FROM ' . $this->prefixOriginal . 'registry_file'); + } + + /** + * Refresh the in-memory set of variables. Useful after a page request is made + * that changes a variable in a different thread. + * + * In other words calling a settings page with $this->drupalPost() with a changed + * value would update a variable to reflect that change, but in the thread that + * made the call (thread running the test) the changed variable would not be + * picked up. + * + * This method clears the variables cache and loads a fresh copy from the database + * to ensure that the most up-to-date set of variables is loaded. + */ + protected function refreshVariables() { + global $conf; + cache_clear_all('variables', 'cache'); + $conf = variable_init(); + } + + public function __destruct() { + if (!$this->destroyTestDatabase) { + // Test database still intact. + self::release($this->base, $this->prefix); + } + } + + /** + * Remove all released environments. + */ + public static final function deinit() { + $prefixes = variable_get('simpletest_environment_prefixes', array()); + foreach ($prefixes as $prefix) { + self::destoryEnvironment($prefix); + } + } + + protected static final function destoryEnvironment($prefix) { + // Remove test database tables due to hook_schema_alter() implementation. + $tables = db_find_tables($prefix . '%'); + $ret = array(); + foreach ($tables as $table) { + db_drop_table($ret, $table); + } + + // Delete temporary files directory and reset files directory path. + file_unmanaged_delete_recursive(file_directory_path()); + } +}