Skip navigation
Help

upgrade.test

  1. drupal
    1. 7 drupal/modules/simpletest/tests/upgrade/upgrade.test

Classes

NameDescription
BasicUpgradePathPerform basic upgrade tests.
UpgradePathTestCasePerform end-to-end tests of the upgrade path.

File

drupal/modules/simpletest/tests/upgrade/upgrade.test
View source
  1. <?php
  2. /**
  3. * Perform end-to-end tests of the upgrade path.
  4. */
  5. abstract class UpgradePathTestCase extends DrupalWebTestCase {
  6. /**
  7. * The file path(s) to the dumped database(s) to load into the child site.
  8. *
  9. * @var array
  10. */
  11. var $databaseDumpFiles = array();
  12. /**
  13. * Flag that indicates whether the child site has been upgraded.
  14. */
  15. var $upgradedSite = FALSE;
  16. /**
  17. * Array of errors triggered during the upgrade process.
  18. */
  19. var $upgradeErrors = array();
  20. /**
  21. * Array of modules loaded when the test starts.
  22. */
  23. var $loadedModules = array();
  24. /**
  25. * Override of DrupalWebTestCase::setUp() specialized for upgrade testing.
  26. */
  27. protected function setUp() {
  28. global $user, $language, $conf;
  29. // Load the Update API.
  30. require_once DRUPAL_ROOT . '/includes/update.inc';
  31. // Reset flags.
  32. $this->upgradedSite = FALSE;
  33. $this->upgradeErrors = array();
  34. $this->loadedModules = module_list();
  35. // Generate a temporary prefixed database to ensure that tests have a clean starting point.
  36. $this->databasePrefix = 'simpletest' . mt_rand(1000, 1000000);
  37. db_update('simpletest_test_id')
  38. ->fields(array('last_prefix' => $this->databasePrefix))
  39. ->condition('test_id', $this->testId)
  40. ->execute();
  41. // Clone the current connection and replace the current prefix.
  42. $connection_info = Database::getConnectionInfo('default');
  43. Database::renameConnection('default', 'simpletest_original_default');
  44. foreach ($connection_info as $target => $value) {
  45. $connection_info[$target]['prefix'] = array(
  46. 'default' => $value['prefix']['default'] . $this->databasePrefix,
  47. );
  48. }
  49. Database::addConnectionInfo('default', 'default', $connection_info['default']);
  50. // Store necessary current values before switching to prefixed database.
  51. $this->originalLanguage = $language;
  52. $this->originalLanguageDefault = variable_get('language_default');
  53. $this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files');
  54. $this->originalProfile = drupal_get_profile();
  55. $clean_url_original = variable_get('clean_url', 0);
  56. // Unregister the registry.
  57. // This is required to make sure that the database layer works properly.
  58. spl_autoload_unregister('drupal_autoload_class');
  59. spl_autoload_unregister('drupal_autoload_interface');
  60. // Create test directories ahead of installation so fatal errors and debug
  61. // information can be logged during installation process.
  62. // Use mock files directories with the same prefix as the database.
  63. $public_files_directory = $this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10);
  64. $private_files_directory = $public_files_directory . '/private';
  65. $temp_files_directory = $private_files_directory . '/temp';
  66. // Create the directories.
  67. file_prepare_directory($public_files_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
  68. file_prepare_directory($private_files_directory, FILE_CREATE_DIRECTORY);
  69. file_prepare_directory($temp_files_directory, FILE_CREATE_DIRECTORY);
  70. $this->generatedTestFiles = FALSE;
  71. // Log fatal errors.
  72. ini_set('log_errors', 1);
  73. ini_set('error_log', $public_files_directory . '/error.log');
  74. // Reset all statics and variables to perform tests in a clean environment.
  75. $conf = array();
  76. // Load the database from the portable PHP dump.
  77. foreach ($this->databaseDumpFiles as $file) {
  78. require $file;
  79. }
  80. // Set path variables.
  81. $this->variable_set('file_public_path', $public_files_directory);
  82. $this->variable_set('file_private_path', $private_files_directory);
  83. $this->variable_set('file_temporary_path', $temp_files_directory);
  84. $this->pass('Finished loading the dump.');
  85. // Load user 1.
  86. $this->originalUser = $user;
  87. drupal_save_session(FALSE);
  88. $user = db_query('SELECT * FROM {users} WHERE uid = :uid', array(':uid' => 1))->fetchObject();
  89. // Generate and set a D6-compatible session cookie.
  90. $this->curlInitialize();
  91. $sid = drupal_hash_base64(uniqid(mt_rand(), TRUE) . drupal_random_bytes(55));
  92. $session_name = update_get_d6_session_name();
  93. curl_setopt($this->curlHandle, CURLOPT_COOKIE, rawurlencode($session_name) . '=' . rawurlencode($sid));
  94. // Force our way into the session of the child site.
  95. drupal_save_session(TRUE);
  96. // A session cannot be written without the ssid column which is missing on
  97. // Drupal 6 sites.
  98. db_add_field('sessions', 'ssid', array('description' => "Secure session ID. The value is generated by Drupal's session handlers.", 'type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''));
  99. _drupal_session_write($sid, '');
  100. // Remove the temporarily added ssid column.
  101. db_drop_field('sessions', 'ssid');
  102. drupal_save_session(FALSE);
  103. // Restore necessary variables.
  104. $this->variable_set('clean_url', $clean_url_original);
  105. $this->variable_set('site_mail', 'simpletest@example.com');
  106. drupal_set_time_limit($this->timeLimit);
  107. }
  108. /**
  109. * Override of DrupalWebTestCase::tearDown() specialized for upgrade testing.
  110. */
  111. protected function tearDown() {
  112. global $user, $language;
  113. // In case a fatal error occured that was not in the test process read the
  114. // log to pick up any fatal errors.
  115. simpletest_log_read($this->testId, $this->databasePrefix, get_class($this), TRUE);
  116. // Delete temporary files directory.
  117. file_unmanaged_delete_recursive($this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10));
  118. // Get back to the original connection.
  119. Database::removeConnection('default');
  120. Database::renameConnection('simpletest_original_default', 'default');
  121. // Remove all prefixed tables.
  122. $tables = db_find_tables($this->databasePrefix . '%');
  123. foreach ($tables as $table) {
  124. db_drop_table($table);
  125. }
  126. // Return the user to the original one.
  127. $user = $this->originalUser;
  128. drupal_save_session(TRUE);
  129. // Ensure that internal logged in variable and cURL options are reset.
  130. $this->loggedInUser = FALSE;
  131. $this->additionalCurlOptions = array();
  132. // Reload module list and implementations to ensure that test module hooks
  133. // aren't called after tests.
  134. module_list(TRUE);
  135. module_implements('', FALSE, TRUE);
  136. // Reset the Field API.
  137. field_cache_clear();
  138. // Rebuild caches.
  139. parent::refreshVariables();
  140. // Reset language.
  141. $language = $this->originalLanguage;
  142. if ($this->originalLanguageDefault) {
  143. $GLOBALS['conf']['language_default'] = $this->originalLanguageDefault;
  144. }
  145. // Close the CURL handler.
  146. $this->curlClose();
  147. }
  148. /**
  149. * Specialized variable_set() that works even if the child site is not upgraded.
  150. *
  151. * @param $name
  152. * The name of the variable to set.
  153. * @param $value
  154. * The value to set. This can be any PHP data type; these functions take care
  155. * of serialization as necessary.
  156. */
  157. protected function variable_set($name, $value) {
  158. db_delete('variable')
  159. ->condition('name', $name)
  160. ->execute();
  161. db_insert('variable')
  162. ->fields(array(
  163. 'name' => $name,
  164. 'value' => serialize($value),
  165. ))
  166. ->execute();
  167. try {
  168. cache_clear_all('variables', 'cache');
  169. cache_clear_all('variables', 'cache_bootstrap');
  170. }
  171. // Since cache_bootstrap won't exist in a Drupal 6 site, ignore the
  172. // exception if the above fails.
  173. catch (Exception $e) {}
  174. }
  175. /**
  176. * Specialized refreshVariables().
  177. */
  178. protected function refreshVariables() {
  179. // No operation if the child has not been upgraded yet.
  180. if (!$this->upgradedSite) {
  181. return parent::refreshVariables();
  182. }
  183. }
  184. /**
  185. * Perform the upgrade.
  186. *
  187. * @param $register_errors
  188. * Register the errors during the upgrade process as failures.
  189. * @return
  190. * TRUE if the upgrade succeeded, FALSE otherwise.
  191. */
  192. protected function performUpgrade($register_errors = TRUE) {
  193. $update_url = $GLOBALS['base_url'] . '/update.php';
  194. // Load the first update screen.
  195. $this->drupalGet($update_url, array('external' => TRUE));
  196. if (!$this->assertResponse(200)) {
  197. return FALSE;
  198. }
  199. // Continue.
  200. $this->drupalPost(NULL, array(), t('Continue'));
  201. if (!$this->assertResponse(200)) {
  202. return FALSE;
  203. }
  204. // Go!
  205. $this->drupalPost(NULL, array(), t('Apply pending updates'));
  206. if (!$this->assertResponse(200)) {
  207. return FALSE;
  208. }
  209. // Check for errors during the update process.
  210. foreach ($this->xpath('//li[@class=:class]', array(':class' => 'failure')) as $element) {
  211. $message = strip_tags($element->asXML());
  212. $this->upgradeErrors[] = $message;
  213. if ($register_errors) {
  214. $this->fail($message);
  215. }
  216. }
  217. if (!empty($this->upgradeErrors)) {
  218. // Upgrade failed, the installation might be in an inconsistent state,
  219. // don't process.
  220. return FALSE;
  221. }
  222. // Check if there still are pending updates.
  223. $this->drupalGet($update_url, array('external' => TRUE));
  224. $this->drupalPost(NULL, array(), t('Continue'));
  225. if (!$this->assertText(t('No pending updates.'), t('No pending updates at the end of the update process.'))) {
  226. return FALSE;
  227. }
  228. // Upgrade succeed, rebuild the environment so that we can call the API
  229. // of the child site directly from this request.
  230. $this->upgradedSite = TRUE;
  231. // Reload module list. For modules that are enabled in the test database,
  232. // but not on the test client, we need to load the code here.
  233. $new_modules = array_diff(module_list(TRUE), $this->loadedModules);
  234. foreach ($new_modules as $module) {
  235. drupal_load('module', $module);
  236. }
  237. // Reload hook implementations
  238. module_implements('', FALSE, TRUE);
  239. // Rebuild caches.
  240. drupal_static_reset();
  241. drupal_flush_all_caches();
  242. // Reload global $conf array and permissions.
  243. $this->refreshVariables();
  244. $this->checkPermissions(array(), TRUE);
  245. return TRUE;
  246. }
  247. /**
  248. * Force uninstall all modules from a test database, except those listed.
  249. *
  250. * @param $modules
  251. * The list of modules to keep installed. Required core modules will
  252. * always be kept.
  253. */
  254. protected function uninstallModulesExcept(array $modules) {
  255. $required_modules = array('block', 'dblog', 'filter', 'node', 'system', 'update', 'user');
  256. $modules = array_merge($required_modules, $modules);
  257. db_delete('system')
  258. ->condition('type', 'module')
  259. ->condition('name', $modules, 'NOT IN')
  260. ->execute();
  261. }
  262. }
  263. /**
  264. * Perform basic upgrade tests.
  265. *
  266. * Load a bare installation of Drupal 6 and run the upgrade process on it.
  267. *
  268. * The install only contains dblog (although it's optional, it's only so that
  269. * another hook_watchdog module can take its place, the site is not functional
  270. * without watchdog) and update.
  271. */
  272. class BasicUpgradePath extends UpgradePathTestCase {
  273. public static function getInfo() {
  274. return array(
  275. 'name' => 'Basic upgrade path',
  276. 'description' => 'Basic upgrade path tests.',
  277. 'group' => 'Upgrade path',
  278. );
  279. }
  280. public function setUp() {
  281. // Path to the database dump files.
  282. $this->databaseDumpFiles = array(
  283. drupal_get_path('module', 'simpletest') . '/tests/upgrade/drupal-6.bare.database.php',
  284. );
  285. parent::setUp();
  286. }
  287. /**
  288. * Test a failed upgrade, and verify that the failure is reported.
  289. */
  290. public function testFailedUpgrade() {
  291. // Destroy a table that the upgrade process needs.
  292. db_drop_table('access');
  293. // Assert that the upgrade fails.
  294. $this->assertFalse($this->performUpgrade(FALSE), t('A failed upgrade should return messages.'));
  295. }
  296. /**
  297. * Test a successful upgrade.
  298. */
  299. public function testBasicUpgrade() {
  300. $this->assertTrue($this->performUpgrade(), t('The upgrade was completed successfully.'));
  301. // Hit the frontpage.
  302. $this->drupalGet('');
  303. $this->assertResponse(200);
  304. // Verify that we are still logged in.
  305. $this->drupalGet('user');
  306. $this->clickLink(t('Edit'));
  307. $this->assertEqual($this->getUrl(), url('user/1/edit', array('absolute' => TRUE)), t('We are still logged in as admin at the end of the upgrade.'));
  308. // Logout and verify that we can login back in with our initial password.
  309. $this->drupalLogout();
  310. $this->drupalLogin((object) array(
  311. 'uid' => 1,
  312. 'name' => 'admin',
  313. 'pass_raw' => 'admin',
  314. ));
  315. // The previous login should've triggered a password rehash, so login one
  316. // more time to make sure the new hash is readable.
  317. $this->drupalLogout();
  318. $this->drupalLogin((object) array(
  319. 'uid' => 1,
  320. 'name' => 'admin',
  321. 'pass_raw' => 'admin',
  322. ));
  323. // Test that the site name is correctly displayed.
  324. $this->assertText('Drupal 6', t('The site name is correctly displayed.'));
  325. // Verify that the main admin sections are available.
  326. $this->drupalGet('admin');
  327. $this->assertText(t('Content'));
  328. $this->assertText(t('Appearance'));
  329. $this->assertText(t('People'));
  330. $this->assertText(t('Configuration'));
  331. $this->assertText(t('Reports'));
  332. $this->assertText(t('Structure'));
  333. $this->assertText(t('Modules'));
  334. // Confirm that no {menu_links} entry exists for user/autocomplete.
  335. $result = db_query('SELECT COUNT(*) FROM {menu_links} WHERE link_path = :user_autocomplete', array(':user_autocomplete' => 'user/autocomplete'))->fetchField();
  336. $this->assertFalse($result, t('No {menu_links} entry exists for user/autocomplete'));
  337. }
  338. }