system.test 95.9 KB
Newer Older
1 2
<?php

3 4 5 6 7
/**
 * @file
 * Tests for system.module.
 */

8 9 10 11
/**
 * Helper class for module test cases.
 */
class ModuleTestCase extends DrupalWebTestCase {
12 13
  protected $admin_user;

14 15 16
  function setUp() {
    parent::setUp('system_test');

17
    $this->admin_user = $this->drupalCreateUser(array('access administration pages', 'administer modules'));
18 19 20
    $this->drupalLogin($this->admin_user);
  }

21
  /**
22 23 24 25 26 27 28
   * Assert there are tables that begin with the specified base table name.
   *
   * @param $base_table
   *   Beginning of table name to look for.
   * @param $count
   *   (optional) Whether or not to assert that there are tables that match the
   *   specified base table. Defaults to TRUE.
29
   */
30
  function assertTableCount($base_table, $count = TRUE) {
31
    $tables = db_find_tables(Database::getConnection()->prefixTables('{' . $base_table . '}') . '%');
32 33

    if ($count) {
34
      return $this->assertTrue($tables, t('Tables matching "@base_table" found.', array('@base_table' => $base_table)));
35
    }
36
    return $this->assertFalse($tables, t('Tables matching "@base_table" not found.', array('@base_table' => $base_table)));
37 38
  }

39 40 41 42 43 44 45 46 47 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
  /**
   * Assert that all tables defined in a module's hook_schema() exist.
   *
   * @param $module
   *   The name of the module.
   */
  function assertModuleTablesExist($module) {
    $tables = array_keys(drupal_get_schema_unprocessed($module));
    $tables_exist = TRUE;
    foreach ($tables as $table) {
      if (!db_table_exists($table)) {
        $tables_exist = FALSE;
      }
    }
    return $this->assertTrue($tables_exist, t('All database tables defined by the @module module exist.', array('@module' => $module)));
  }

  /**
   * Assert that none of the tables defined in a module's hook_schema() exist.
   *
   * @param $module
   *   The name of the module.
   */
  function assertModuleTablesDoNotExist($module) {
    $tables = array_keys(drupal_get_schema_unprocessed($module));
    $tables_exist = FALSE;
    foreach ($tables as $table) {
      if (db_table_exists($table)) {
        $tables_exist = TRUE;
      }
    }
    return $this->assertFalse($tables_exist, t('None of the database tables defined by the @module module exist.', array('@module' => $module)));
  }

73
  /**
74 75 76 77 78 79
   * Assert the list of modules are enabled or disabled.
   *
   * @param $modules
   *   Module list to check.
   * @param $enabled
   *   Expected module state.
80
   */
81 82 83 84 85 86 87 88 89
  function assertModules(array $modules, $enabled) {
    module_list(TRUE);
    foreach ($modules as $module) {
      if ($enabled) {
        $message = 'Module "@module" is enabled.';
      }
      else {
        $message = 'Module "@module" is not enabled.';
      }
90
      $this->assertEqual(module_exists($module), $enabled, t($message, array('@module' => $module)));
91 92
    }
  }
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114

  /**
   * Verify a log entry was entered for a module's status change.
   * Called in the same way of the expected original watchdog() execution.
   *
   * @param $type
   *   The category to which this message belongs.
   * @param $message
   *   The message to store in the log. Keep $message translatable
   *   by not concatenating dynamic values into it! Variables in the
   *   message should be added by using placeholder strings alongside
   *   the variables argument to declare the value of the placeholders.
   *   See t() for documentation on how $message and $variables interact.
   * @param $variables
   *   Array of variables to replace in the message on display or
   *   NULL if message is already translated or not possible to
   *   translate.
   * @param $severity
   *   The severity of the message, as per RFC 3164.
   * @param $link
   *   A link to associate with the message.
   */
115
  function assertLogMessage($type, $message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = '') {
116 117 118 119 120 121 122 123 124
    $count = db_select('watchdog', 'w')
      ->condition('type', $type)
      ->condition('message', $message)
      ->condition('variables', serialize($variables))
      ->condition('severity', $severity)
      ->condition('link', $link)
      ->countQuery()
      ->execute()
      ->fetchField();
125
    $this->assertTrue($count > 0, t('watchdog table contains @count rows for @message', array('@count' => $count, '@message' => $message)));
126
  }
127
}
128

129 130 131 132
/**
 * Test module enabling/disabling functionality.
 */
class EnableDisableTestCase extends ModuleTestCase {
133 134
  protected $profile = 'testing';

135
  public static function getInfo() {
136
    return array(
137 138 139
      'name' => 'Enable/disable modules',
      'description' => 'Enable/disable core module and confirm table creation/deletion.',
      'group' => 'Module',
140
    );
141 142 143
  }

  /**
144
   * Test that all core modules can be enabled, disabled and uninstalled.
145 146
   */
  function testEnableDisable() {
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
    // Try to enable, disable and uninstall all core modules, unless they are
    // hidden or required.
    $modules = system_rebuild_module_data();
    foreach ($modules as $name => $module) {
      if ($module->info['package'] != 'Core' || !empty($module->info['hidden']) || !empty($module->info['required'])) {
        unset($modules[$name]);
      }
    }
    $this->assertTrue(count($modules), t('Found @count core modules that we can try to enable in this test.', array('@count' => count($modules))));

    // Enable the dblog module first, since we will be asserting the presence
    // of log messages throughout the test.
   if (isset($modules['dblog'])) {
     $modules = array('dblog' => $modules['dblog']) + $modules;
   }

   // Set a variable so that the hook implementations in system_test.module
   // will display messages via drupal_set_message().
   variable_set('test_verbose_module_hooks', TRUE);

    // Throughout this test, some modules may be automatically enabled (due to
    // dependencies). We'll keep track of them in an array, so we can handle
    // them separately.
    $automatically_enabled = array();

    // Go through each module in the list and try to enable it (unless it was
    // already enabled automatically due to a dependency).
    foreach ($modules as $name => $module) {
      if (empty($automatically_enabled[$name])) {
        // Start a list of modules that we expect to be enabled this time.
        $modules_to_enable = array($name);

        // Find out if the module has any dependencies that aren't enabled yet;
        // if so, add them to the list of modules we expect to be automatically
        // enabled.
        foreach (array_keys($module->requires) as $dependency) {
          if (isset($modules[$dependency]) && empty($automatically_enabled[$dependency])) {
            $modules_to_enable[] = $dependency;
            $automatically_enabled[$dependency] = TRUE;
          }
        }

        // Check that each module is not yet enabled and does not have any
        // database tables yet.
        foreach ($modules_to_enable as $module_to_enable) {
          $this->assertModules(array($module_to_enable), FALSE);
          $this->assertModuleTablesDoNotExist($module_to_enable);
        }

        // Install and enable the module.
        $edit = array();
        $edit['modules[Core][' . $name . '][enable]'] = $name;
        $this->drupalPost('admin/modules', $edit, t('Save configuration'));
        // Handle the case where modules were installed along with this one and
        // where we therefore hit a confirmation screen.
        if (count($modules_to_enable) > 1) {
          $this->drupalPost(NULL, array(), t('Continue'));
        }
        $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.'));

        // Check that hook_modules_installed() and hook_modules_enabled() were
        // invoked with the expected list of modules, that each module's
        // database tables now exist, and that appropriate messages appear in
        // the logs.
        foreach ($modules_to_enable as $module_to_enable) {
          $this->assertText(t('hook_modules_installed fired for @module', array('@module' => $module_to_enable)));
          $this->assertText(t('hook_modules_enabled fired for @module', array('@module' => $module_to_enable)));
          $this->assertModules(array($module_to_enable), TRUE);
          $this->assertModuleTablesExist($module_to_enable);
216 217
          $this->assertLogMessage('system', "%module module installed.", array('%module' => $module_to_enable), WATCHDOG_INFO);
          $this->assertLogMessage('system', "%module module enabled.", array('%module' => $module_to_enable), WATCHDOG_INFO);
218 219 220 221 222 223 224 225 226 227 228 229
        }

        // Disable and uninstall the original module, and check appropriate
        // hooks, tables, and log messages. (Later, we'll go back and do the
        // same thing for modules that were enabled automatically.) Skip this
        // for the dblog module, because that is needed for the test; we'll go
        // back and do that one at the end also.
        if ($name != 'dblog') {
          $this->assertSuccessfulDisableAndUninstall($name);
        }
      }
    }
230

231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
    // Go through all modules that were automatically enabled, and try to
    // disable and uninstall them one by one.
    while (!empty($automatically_enabled)) {
      $initial_count = count($automatically_enabled);
      foreach (array_keys($automatically_enabled) as $name) {
        // If the module can't be disabled due to dependencies, skip it and try
        // again the next time. Otherwise, try to disable it.
        $this->drupalGet('admin/modules');
        $disabled_checkbox = $this->xpath('//input[@type="checkbox" and @disabled="disabled" and @name="modules[Core][' . $name . '][enable]"]');
        if (empty($disabled_checkbox) && $name != 'dblog') {
          unset($automatically_enabled[$name]);
          $this->assertSuccessfulDisableAndUninstall($name);
        }
      }
      $final_count = count($automatically_enabled);
      // If all checkboxes were disabled, something is really wrong with the
      // test. Throw a failure and avoid an infinite loop.
      if ($initial_count == $final_count) {
        $this->fail(t('Remaining modules could not be disabled.'));
        break;
      }
    }
253

254 255 256 257 258
    // Disable and uninstall the dblog module last, since we needed it for
    // assertions in all the above tests.
    if (isset($modules['dblog'])) {
      $this->assertSuccessfulDisableAndUninstall('dblog');
    }
259

260 261 262 263 264 265
    // Now that all modules have been tested, go back and try to enable them
    // all again at once. This tests two things:
    // - That each module can be successfully enabled again after being
    //   uninstalled.
    // - That enabling more than one module at the same time does not lead to
    //   any errors.
266
    $edit = array();
267 268 269
    foreach (array_keys($modules) as $name) {
      $edit['modules[Core][' . $name . '][enable]'] = $name;
    }
270
    $this->drupalPost('admin/modules', $edit, t('Save configuration'));
271
    $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.'));
272
  }
273 274 275 276

  /**
   * Tests entity cache after enabling a module with a dependency on an enitity
   * providing module.
277 278
   *
   * @see entity_cache_test_watchdog()
279 280 281 282
   */
  function testEntityCache() {
    module_enable(array('entity_cache_test'));
    $info = variable_get('entity_cache_test');
283 284
    $this->assertEqual($info['label'], 'Entity Cache Test', 'Entity info label is correct.');
    $this->assertEqual($info['controller class'], 'DrupalDefaultEntityController', 'Entity controller class info is correct.');
285
  }
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303

  /**
   * Disables and uninstalls a module and asserts that it was done correctly.
   *
   * @param $module
   *   The name of the module to disable and uninstall.
   */
  function assertSuccessfulDisableAndUninstall($module) {
    // Disable the module.
    $edit = array();
    $edit['modules[Core][' . $module . '][enable]'] = FALSE;
    $this->drupalPost('admin/modules', $edit, t('Save configuration'));
    $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.'));
    $this->assertModules(array($module), FALSE);

    // Check that the appropriate hook was fired and the appropriate log
    // message appears.
    $this->assertText(t('hook_modules_disabled fired for @module', array('@module' => $module)));
304
    $this->assertLogMessage('system', "%module module disabled.", array('%module' => $module), WATCHDOG_INFO);
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322

    //  Check that the module's database tables still exist.
    $this->assertModuleTablesExist($module);

    // Uninstall the module.
    $edit = array();
    $edit['uninstall[' . $module . ']'] = $module;
    $this->drupalPost('admin/modules/uninstall', $edit, t('Uninstall'));
    $this->drupalPost(NULL, NULL, t('Uninstall'));
    $this->assertText(t('The selected modules have been uninstalled.'), t('Modules status has been updated.'));
    $this->assertModules(array($module), FALSE);

    // Check that the appropriate hook was fired and the appropriate log
    // message appears. (But don't check for the log message if the dblog
    // module was just uninstalled, since the {watchdog} table won't be there
    // anymore.)
    $this->assertText(t('hook_modules_uninstalled fired for @module', array('@module' => $module)));
    if ($module != 'dblog') {
323
      $this->assertLogMessage('system', "%module module uninstalled.", array('%module' => $module), WATCHDOG_INFO);
324 325 326 327 328
    }

    // Check that the module's database tables no longer exist.
    $this->assertModuleTablesDoNotExist($module);
  }
329 330
}

331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
/**
 * Tests failure of hook_requirements('install').
 */
class HookRequirementsTestCase extends ModuleTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Requirements hook failure',
      'description' => "Attempts enabling a module that fails hook_requirements('install').",
      'group' => 'Module',
    );
  }

  /**
   * Assert that a module cannot be installed if it fails hook_requirements().
   */
  function testHookRequirementsFailure() {
    $this->assertModules(array('requirements1_test'), FALSE);

    // Attempt to install the requirements1_test module.
    $edit = array();
351
    $edit['modules[Testing][requirements1_test][enable]'] = 'requirements1_test';
352 353 354
    $this->drupalPost('admin/modules', $edit, t('Save configuration'));

    // Makes sure the module was NOT installed.
355
    $this->assertText(t('Requirements 1 Test failed requirements'), t('Modules status has been updated.'));
356 357 358 359
    $this->assertModules(array('requirements1_test'), FALSE);
  }
}

360 361 362 363
/**
 * Test module dependency functionality.
 */
class ModuleDependencyTestCase extends ModuleTestCase {
364
  public static function getInfo() {
365
    return array(
366 367 368
      'name' => 'Module dependencies',
      'description' => 'Enable module without dependency enabled.',
      'group' => 'Module',
369 370
    );
  }
371 372 373 374

  /**
   * Attempt to enable translation module without locale enabled.
   */
375
  function testEnableWithoutDependency() {
376 377
    // Attempt to enable content translation without locale enabled.
    $edit = array();
378
    $edit['modules[Core][translation][enable]'] = 'translation';
379
    $this->drupalPost('admin/modules', $edit, t('Save configuration'));
380
    $this->assertText(t('Some required modules must be enabled'), t('Dependency required.'));
381 382 383 384 385 386 387 388

    $this->assertModules(array('translation', 'locale'), FALSE);

    // Assert that the locale tables weren't enabled.
    $this->assertTableCount('languages', FALSE);
    $this->assertTableCount('locale', FALSE);

    $this->drupalPost(NULL, NULL, t('Continue'));
389
    $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.'));
390 391 392 393 394 395 396

    $this->assertModules(array('translation', 'locale'), TRUE);

    // Assert that the locale tables were enabled.
    $this->assertTableCount('languages', TRUE);
    $this->assertTableCount('locale', TRUE);
  }
397 398 399 400 401 402 403

  /**
   * Attempt to enable a module with a missing dependency.
   */
  function testMissingModules() {
    // Test that the system_dependencies_test module is marked
    // as missing a dependency.
404
    $this->drupalGet('admin/modules');
405
    $this->assertRaw(t('@module (<span class="admin-missing">missing</span>)', array('@module' => drupal_ucfirst('_missing_dependency'))), t('A module with missing dependencies is marked as such.'));
406
    $checkbox = $this->xpath('//input[@type="checkbox" and @disabled="disabled" and @name="modules[Testing][system_dependencies_test][enable]"]');
407
    $this->assert(count($checkbox) == 1, t('Checkbox for the module is disabled.'));
408 409

    // Force enable the system_dependencies_test module.
410
    module_enable(array('system_dependencies_test'), FALSE);
411 412 413

    // Verify that the module is forced to be disabled when submitting
    // the module page.
414
    $this->drupalPost('admin/modules', array(), t('Save configuration'));
415
    $this->assertText(t('The @module module is missing, so the following module will be disabled: @depends.', array('@module' => '_missing_dependency', '@depends' => 'system_dependencies_test')), t('The module missing dependencies will be disabled.'));
416 417 418 419 420 421 422

    // Confirm.
    $this->drupalPost(NULL, NULL, t('Continue'));

    // Verify that the module has been disabled.
    $this->assertModules(array('system_dependencies_test'), FALSE);
  }
423 424 425 426 427 428 429 430 431 432

  /**
   * Tests enabling a module that depends on a module which fails hook_requirements().
   */
  function testEnableRequirementsFailureDependency() {
    $this->assertModules(array('requirements1_test'), FALSE);
    $this->assertModules(array('requirements2_test'), FALSE);

    // Attempt to install both modules at the same time.
    $edit = array();
433 434
    $edit['modules[Testing][requirements1_test][enable]'] = 'requirements1_test';
    $edit['modules[Testing][requirements2_test][enable]'] = 'requirements2_test';
435 436 437
    $this->drupalPost('admin/modules', $edit, t('Save configuration'));

    // Makes sure the modules were NOT installed.
438
    $this->assertText(t('Requirements 1 Test failed requirements'), t('Modules status has been updated.'));
439 440 441 442 443 444 445 446
    $this->assertModules(array('requirements1_test'), FALSE);
    $this->assertModules(array('requirements2_test'), FALSE);

    // Makes sure that already enabled modules the failing modules depend on
    // were not disabled.
    $this->assertModules(array('comment'), TRUE);

  }
447

448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475
  /**
   * Tests that module dependencies are enabled in the correct order via the
   * UI. Dependencies should be enabled before their dependents.
   */
  function testModuleEnableOrder() {
    module_enable(array('module_test'), FALSE);
    $this->resetAll();
    $this->assertModules(array('module_test'), TRUE);
    variable_set('dependency_test', 'dependency');
    // module_test creates a dependency chain: forum depends on poll, which
    // depends on php. The correct enable order is, php, poll, forum.
    $expected_order = array('php', 'poll', 'forum');

    // Enable the modules through the UI, verifying that the dependency chain
    // is correct.
    $edit = array();
    $edit['modules[Core][forum][enable]'] = 'forum';
    $this->drupalPost('admin/modules', $edit, t('Save configuration'));
    $this->assertModules(array('forum'), FALSE);
    $this->assertText(t('You must enable the Poll, PHP filter modules to install Forum.'), t('Dependency chain created.'));
    $edit['modules[Core][poll][enable]'] = 'poll';
    $edit['modules[Core][php][enable]'] = 'php';
    $this->drupalPost('admin/modules', $edit, t('Save configuration'));
    $this->assertModules(array('forum', 'poll', 'php'), TRUE);

    // Check the actual order which is saved by module_test_modules_enabled().
    $this->assertIdentical(variable_get('test_module_enable_order', FALSE), $expected_order, t('Modules enabled in the correct order.'));
  }
476 477 478 479 480 481 482 483 484 485

  /**
   * Tests attempting to uninstall a module that has installed dependents.
   */
  function testUninstallDependents() {
    // Enable the forum module.
    $edit = array('modules[Core][forum][enable]' => 'forum');
    $this->drupalPost('admin/modules', $edit, t('Save configuration'));
    $this->assertModules(array('forum'), TRUE);

486
    // Disable forum and comment. Both should now be installed but disabled.
487 488 489
    $edit = array('modules[Core][forum][enable]' => FALSE);
    $this->drupalPost('admin/modules', $edit, t('Save configuration'));
    $this->assertModules(array('forum'), FALSE);
490
    $edit = array('modules[Core][comment][enable]' => FALSE);
491
    $this->drupalPost('admin/modules', $edit, t('Save configuration'));
492
    $this->assertModules(array('comment'), FALSE);
493 494 495

    // Check that the taxonomy module cannot be uninstalled.
    $this->drupalGet('admin/modules/uninstall');
496 497
    $checkbox = $this->xpath('//input[@type="checkbox" and @disabled="disabled" and @name="uninstall[comment]"]');
    $this->assert(count($checkbox) == 1, t('Checkbox for uninstalling the comment module is disabled.'));
498 499 500 501 502 503 504

    // Uninstall the forum module, and check that taxonomy now can also be
    // uninstalled.
    $edit = array('uninstall[forum]' => 'forum');
    $this->drupalPost('admin/modules/uninstall', $edit, t('Uninstall'));
    $this->drupalPost(NULL, NULL, t('Uninstall'));
    $this->assertText(t('The selected modules have been uninstalled.'), t('Modules status has been updated.'));
505
    $edit = array('uninstall[comment]' => 'comment');
506 507 508 509
    $this->drupalPost('admin/modules/uninstall', $edit, t('Uninstall'));
    $this->drupalPost(NULL, NULL, t('Uninstall'));
    $this->assertText(t('The selected modules have been uninstalled.'), t('Modules status has been updated.'));
  }
510 511
}

512 513 514 515 516 517 518 519 520 521 522
/**
 * Test module dependency on specific versions.
 */
class ModuleVersionTestCase extends ModuleTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Module versions',
      'description' => 'Check module version dependencies.',
      'group' => 'Module',
    );
  }
523

524
  function setUp() {
525 526 527 528 529 530 531 532
    parent::setUp('module_test');
  }

  /**
   * Test version dependencies.
   */
  function testModuleVersions() {
    $dependencies = array(
533
      // Alternating between being compatible and incompatible with 8.x-2.4-beta3.
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548
      // The first is always a compatible.
      'common_test',
      // Branch incompatibility.
      'common_test (1.x)',
      // Branch compatibility.
      'common_test (2.x)',
      // Another branch incompatibility.
      'common_test (>2.x)',
      // Another branch compatibility.
      'common_test (<=2.x)',
      // Another branch incompatibility.
      'common_test (<2.x)',
      // Another branch compatibility.
      'common_test (>=2.x)',
      // Nonsense, misses a dash. Incompatible with everything.
549
      'common_test (=8.x2.x, >=2.4)',
550
      // Core version is optional. Compatible.
551
      'common_test (=8.x-2.x, >=2.4-alpha2)',
552
      // Test !=, explicitly incompatible.
553
      'common_test (=2.x, !=2.4-beta3)',
554 555
      // Three operations. Compatible.
      'common_test (=2.x, !=2.3, <2.5)',
556 557 558 559 560 561
      // Testing extra version. Incompatible.
      'common_test (<=2.4-beta2)',
      // Testing extra version. Compatible.
      'common_test (>2.4-beta2)',
      // Testing extra version. Incompatible.
      'common_test (>2.4-rc0)',
562 563 564 565
    );
    variable_set('dependencies', $dependencies);
    $n = count($dependencies);
    for ($i = 0; $i < $n; $i++) {
566
      $this->drupalGet('admin/modules');
567
      $checkbox = $this->xpath('//input[@id="edit-modules-testing-module-test-enable"]');
568 569 570 571 572
      $this->assertEqual(!empty($checkbox[0]['disabled']), $i % 2, $dependencies[$i]);
    }
  }
}

573 574 575 576
/**
 * Test required modules functionality.
 */
class ModuleRequiredTestCase extends ModuleTestCase {
577
  public static function getInfo() {
578
    return array(
579 580 581
      'name' => 'Required modules',
      'description' => 'Attempt disabling of required modules.',
      'group' => 'Module',
582 583
    );
  }
584

585 586 587 588
  /**
   * Assert that core required modules cannot be disabled.
   */
  function testDisableRequired() {
589
    $module_info = system_get_info('module');
590
    $this->drupalGet('admin/modules');
591 592 593 594 595 596
    foreach ($module_info as $module => $info) {
      // Check to make sure the checkbox for each required module is disabled
      // and checked (or absent from the page if the module is also hidden).
      if (!empty($info['required'])) {
        $field_name = "modules[{$info['package']}][$module][enable]";
        if (empty($info['hidden'])) {
597
          $this->assertFieldByXPath("//input[@name='$field_name' and @disabled='disabled' and @checked='checked']", '', t('Field @name was disabled and checked.', array('@name' => $field_name)));
598 599 600 601 602
        }
        else {
          $this->assertNoFieldByName($field_name);
        }
      }
603 604
    }
  }
605
}
606

607
class IPAddressBlockingTestCase extends DrupalWebTestCase {
608 609
  protected $blocking_user;

610
  /**
611
   * Implement getInfo().
612
   */
613
  public static function getInfo() {
614
    return array(
615 616 617
      'name' => 'IP address blocking',
      'description' => 'Test IP address blocking.',
      'group' => 'System'
618 619 620 621
    );
  }

  /**
622
   * Implement setUp().
623 624 625 626 627
   */
  function setUp() {
    parent::setUp();

    // Create user.
628
    $this->blocking_user = $this->drupalCreateUser(array('block IP addresses'));
629
    $this->drupalLogin($this->blocking_user);
630 631 632
  }

  /**
633
   * Test a variety of user input to confirm correct validation and saving of data.
634 635
   */
  function testIPAddressValidation() {
636
    $this->drupalGet('admin/config/people/ip-blocking');
637 638 639 640

    // Block a valid IP address.
    $edit = array();
    $edit['ip'] = '192.168.1.1';
641
    $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add'));
642
    $ip = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $edit['ip']))->fetchField();
643 644
    $this->assertTrue($ip, t('IP address found in database.'));
    $this->assertRaw(t('The IP address %ip has been blocked.', array('%ip' => $edit['ip'])), t('IP address was blocked.'));
645 646 647 648

    // Try to block an IP address that's already blocked.
    $edit = array();
    $edit['ip'] = '192.168.1.1';
649
    $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add'));
650 651 652 653 654
    $this->assertText(t('This IP address is already blocked.'));

    // Try to block a reserved IP address.
    $edit = array();
    $edit['ip'] = '255.255.255.255';
655
    $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add'));
656
    $this->assertText(t('Enter a valid IP address.'));
657 658 659 660

    // Try to block a reserved IP address.
    $edit = array();
    $edit['ip'] = 'test.example.com';
661
    $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add'));
662
    $this->assertText(t('Enter a valid IP address.'));
663 664 665 666

    // Submit an empty form.
    $edit = array();
    $edit['ip'] = '';
667
    $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add'));
668
    $this->assertText(t('Enter a valid IP address.'));
669

670 671
    // Pass an IP address as a URL parameter and submit it.
    $submit_ip = '1.2.3.4';
672
    $this->drupalPost('admin/config/people/ip-blocking/' . $submit_ip, NULL, t('Add'));
673
    $ip = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $submit_ip))->fetchField();
674 675
    $this->assertTrue($ip, t('IP address found in database'));
    $this->assertRaw(t('The IP address %ip has been blocked.', array('%ip' => $submit_ip)), t('IP address was blocked.'));
676

677
    // Submit your own IP address. This fails, although it works when testing manually.
678 679 680 681 682
     // TODO: on some systems this test fails due to a bug or inconsistency in cURL.
     // $edit = array();
     // $edit['ip'] = ip_address();
     // $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Save'));
     // $this->assertText(t('You may not block your own IP address.'));
683 684
  }
}
685

686
class CronRunTestCase extends DrupalWebTestCase {
687
  /**
688
   * Implement getInfo().
689
   */
690
  public static function getInfo() {
691
    return array(
692 693 694
      'name' => 'Cron run',
      'description' => 'Test cron run.',
      'group' => 'System'
695 696 697
    );
  }

698 699 700 701
  function setUp() {
    parent::setUp(array('common_test', 'common_test_cron_helper'));
  }

702 703 704 705
  /**
   * Test cron runs.
   */
  function testCronRun() {
706
    global $base_url;
707

708
    // Run cron anonymously without any cron key.
709
    $this->drupalGet($base_url . '/cron.php', array('external' => TRUE));
710 711 712 713
    $this->assertResponse(403);

    // Run cron anonymously with a random cron key.
    $key = $this->randomName(16);
714
    $this->drupalGet($base_url . '/cron.php', array('external' => TRUE, 'query' => array('cron_key' => $key)));
715 716 717 718
    $this->assertResponse(403);

    // Run cron anonymously with the valid cron key.
    $key = variable_get('cron_key', 'drupal');
719
    $this->drupalGet($base_url . '/cron.php', array('external' => TRUE, 'query' => array('cron_key' => $key)));
720 721
    $this->assertResponse(200);
  }
722

723
  /**
724
   * Ensure that the automatic cron run feature is working.
725 726 727 728
   *
   * In these tests we do not use REQUEST_TIME to track start time, because we
   * need the exact time when cron is triggered.
   */
729
  function testAutomaticCron() {
730 731
    // Ensure cron does not run when the cron threshold is enabled and was
    // not passed.
732 733 734 735
    $cron_last = time();
    $cron_safe_threshold = 100;
    variable_set('cron_last', $cron_last);
    variable_set('cron_safe_threshold', $cron_safe_threshold);
736
    $this->drupalGet('');
737
    $this->assertTrue($cron_last == variable_get('cron_last', NULL), t('Cron does not run when the cron threshold is not passed.'));
738 739

    // Test if cron runs when the cron threshold was passed.
740 741
    $cron_last = time() - 200;
    variable_set('cron_last', $cron_last);
742
    $this->drupalGet('');
743
    sleep(1);
744
    $this->assertTrue($cron_last < variable_get('cron_last', NULL), t('Cron runs when the cron threshold is passed.'));
745 746 747 748

    // Disable the cron threshold through the interface.
    $admin_user = $this->drupalCreateUser(array('administer site configuration'));
    $this->drupalLogin($admin_user);
749
    $this->drupalPost('admin/config/system/cron', array('cron_safe_threshold' => 0), t('Save configuration'));
750 751 752 753 754 755
    $this->assertText(t('The configuration options have been saved.'));
    $this->drupalLogout();

    // Test if cron does not run when the cron threshold is disabled.
    $cron_last = time() - 200;
    variable_set('cron_last', $cron_last);
756
    $this->drupalGet('');
757
    $this->assertTrue($cron_last == variable_get('cron_last', NULL), t('Cron does not run when the cron threshold is disabled.'));
758 759
  }

760 761
  /**
   * Ensure that temporary files are removed.
762 763
   *
   * Create files for all the possible combinations of age and status. We are
764
   * using UPDATE statements rather than file_save() because it would set the
765
   * timestamp.
766 767 768 769
   */
  function testTempFileCleanup() {
    // Temporary file that is older than DRUPAL_MAXIMUM_TEMP_FILE_AGE.
    $temp_old = file_save_data('');
770
    db_update('file_managed')
771 772 773 774 775 776
      ->fields(array(
        'status' => 0,
        'timestamp' => 1,
      ))
      ->condition('fid', $temp_old->fid)
      ->execute();
777
    $this->assertTrue(file_exists($temp_old->uri), t('Old temp file was created correctly.'));
778 779 780

    // Temporary file that is less than DRUPAL_MAXIMUM_TEMP_FILE_AGE.
    $temp_new = file_save_data('');
781
    db_update('file_managed')
782 783 784
      ->fields(array('status' => 0))
      ->condition('fid', $temp_new->fid)
      ->execute();
785
    $this->assertTrue(file_exists($temp_new->uri), t('New temp file was created correctly.'));
786 787 788

    // Permanent file that is older than DRUPAL_MAXIMUM_TEMP_FILE_AGE.
    $perm_old = file_save_data('');
789
    db_update('file_managed')
790 791 792
      ->fields(array('timestamp' => 1))
      ->condition('fid', $temp_old->fid)
      ->execute();
793
    $this->assertTrue(file_exists($perm_old->uri), t('Old permanent file was created correctly.'));
794 795 796

    // Permanent file that is newer than DRUPAL_MAXIMUM_TEMP_FILE_AGE.
    $perm_new = file_save_data('');
797
    $this->assertTrue(file_exists($perm_new->uri), t('New permanent file was created correctly.'));
798 799

    // Run cron and then ensure that only the old, temp file was deleted.
800
    $this->cronRun();
801 802 803 804
    $this->assertFalse(file_exists($temp_old->uri), t('Old temp file was correctly removed.'));
    $this->assertTrue(file_exists($temp_new->uri), t('New temp file was correctly ignored.'));
    $this->assertTrue(file_exists($perm_old->uri), t('Old permanent file was correctly ignored.'));
    $this->assertTrue(file_exists($perm_new->uri), t('New permanent file was correctly ignored.'));
805
  }
806 807 808 809 810 811 812 813 814 815 816 817 818

  /**
   * Make sure exceptions thrown on hook_cron() don't affect other modules.
   */
  function testCronExceptions() {
    variable_del('common_test_cron');
    // The common_test module throws an exception. If it isn't caught, the tests
    // won't finish successfully.
    // The common_test_cron_helper module sets the 'common_test_cron' variable.
    $this->cronRun();
    $result = variable_get('common_test_cron');
    $this->assertEqual($result, 'success', t('Cron correctly handles exceptions thrown during hook_cron() invocations.'));
  }
819 820
}

821 822
class AdminMetaTagTestCase extends DrupalWebTestCase {
  /**
823
   * Implement getInfo().
824
   */
825
  public static function getInfo() {
826
    return array(
827 828 829
      'name' => 'Fingerprinting meta tag',
      'description' => 'Confirm that the fingerprinting meta tag appears as expected.',
      'group' => 'System'
830 831 832 833 834 835 836
    );
  }

  /**
   * Verify that the meta tag HTML is generated correctly.
   */
  public function testMetaTag() {
837
    list($version, ) = explode('.', VERSION);
838
    $string = '<meta name="Generator" content="Drupal ' . $version . ' (http://drupal.org)" />';
839
    $this->drupalGet('node');
840
    $this->assertRaw($string, t('Fingerprinting meta tag generated correctly.'), t('System'));
841 842
  }
}
843

844 845 846 847 848 849
/**
 * Tests custom access denied functionality.
 */
class AccessDeniedTestCase extends DrupalWebTestCase {
  protected $admin_user;

850
  public static function getInfo() {
851
    return array(
852
      'name' => '403 functionality',
853
      'description' => 'Tests page access denied functionality, including custom 403 pages.',
854
      'group' => 'System'
855 856 857 858 859 860 861
    );
  }

  function setUp() {
    parent::setUp();

    // Create an administrative user.
862
    $this->admin_user = $this->drupalCreateUser(array('access administration pages', 'administer site configuration', 'administer blocks'));
863 864 865 866
  }

  function testAccessDenied() {
    $this->drupalGet('admin');
867
    $this->assertText(t('Access denied'), t('Found the default 403 page'));
868
    $this->assertResponse(403);
869

870
    $this->drupalLogin($this->admin_user);
871
    $edit = array(
872
      'title' => $this->randomName(10),
873
      'body' => array(LANGUAGE_NONE => array(array('value' => $this->randomName(100)))),
874 875 876 877
    );
    $node = $this->drupalCreateNode($edit);

    // Use a custom 403 page.
878
    $this->drupalPost('admin/config/system/site-information', array('site_403' => 'node/' . $node->nid), t('Save configuration'));
879

880
    $this->drupalLogout();
881
    $this->drupalGet('admin');
882
    $this->assertText($node->title, t('Found the custom 403 page'));
883 884 885 886 887

    // Logout and check that the user login block is shown on custom 403 pages.
    $this->drupalLogout();

    $this->drupalGet('admin');
888 889
    $this->assertText($node->title, t('Found the custom 403 page'));
    $this->assertText(t('User login'), t('Blocks are shown on the custom 403 page'));
890 891 892

    // Log back in and remove the custom 403 page.
    $this->drupalLogin($this->admin_user);
893
    $this->drupalPost('admin/config/system/site-information', array('site_403' => ''), t('Save configuration'));
894 895 896 897 898

    // Logout and check that the user login block is shown on default 403 pages.
    $this->drupalLogout();

    $this->drupalGet('admin');
899
    $this->assertText(t('Access denied'), t('Found the default 403 page'));
900
    $this->assertResponse(403);
901
    $this->assertText(t('User login'), t('Blocks are shown on the default 403 page'));
902 903 904 905

    // Log back in, set the custom 403 page to /user and remove the block
    $this->drupalLogin($this->admin_user);
    variable_set('site_403', 'user');
906
    $this->drupalPost('admin/structure/block', array('blocks[user_login][region]' => '-1'), t('Save blocks'));
907 908 909 910 911 912 913

    // Check that we can log in from the 403 page.
    $this->drupalLogout();
    $edit = array(
      'name' => $this->admin_user->name,
      'pass' => $this->admin_user->pass_raw,
    );
914
    $this->drupalPost('admin/config/system/site-information', $edit, t('Log in'));
915 916 917

    // Check that we're still on the same page.
    $this->assertText(t('Site information'));
918 919 920
  }
}

921 922
class PageNotFoundTestCase extends DrupalWebTestCase {
  protected $admin_user;
923

924
  /**
925
   * Implement getInfo().
926
   */
927
  public static function getInfo() {
928
    return array(
929 930 931
      'name' => '404 functionality',
      'description' => "Tests page not found functionality, including custom 404 pages.",
      'group' => 'System'
932 933
    );
  }
934

935
  /**
936
   * Implement setUp().
937 938 939 940 941 942 943 944 945 946 947
   */
  function setUp() {
    parent::setUp();

    // Create an administrative user.
    $this->admin_user = $this->drupalCreateUser(array('administer site configuration'));
    $this->drupalLogin($this->admin_user);
  }

  function testPageNotFound() {
    $this->drupalGet($this->randomName(10));
948
    $this->assertText(t('Page not found'), t('Found the default 404 page'));
949

950
    $edit = array(
951
      'title' => $this->randomName(10),
952
      'body' => array(LANGUAGE_NONE => array(array('value' => $this->randomName(100)))),
953 954 955 956
    );
    $node = $this->drupalCreateNode($edit);

    // Use a custom 404 page.
957
    $this->drupalPost('admin/config/system/site-information', array('site_404' => 'node/' . $node->nid), t('Save configuration'));
958

959
    $this->drupalGet($this->randomName(10));
960
    $this->assertText($node->title, t('Found the custom 404 page'));
961 962
  }
}
963

964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002
/**
 * Tests site maintenance functionality.
 */
class SiteMaintenanceTestCase extends DrupalWebTestCase {
  protected $admin_user;

  public static function getInfo() {
    return array(
      'name' => 'Site maintenance mode functionality',
      'description' => 'Test access to site while in maintenance mode.',
      'group' => 'System',
    );
  }

  function setUp() {
    parent::setUp();

    // Create a user allowed to access site in maintenance mode.
    $this->user = $this->drupalCreateUser(array('access site in maintenance mode'));
    // Create an administrative user.
    $this->admin_user = $this->drupalCreateUser(array('administer site configuration', 'access site in maintenance mode'));
    $this->drupalLogin($this->admin_user);
  }

  /**
   * Verify site maintenance mode functionality.
   */
  function testSiteMaintenance() {
    // Turn on maintenance mode.
    $edit = array(
      'maintenance_mode' => 1,
    );
    $this->drupalPost('admin/config/development/maintenance', $edit, t('Save configuration'));

    $admin_message = t('Operating in maintenance mode. <a href="@url">Go online.</a>', array('@url' => url('admin/config/development/maintenance')));
    $user_message = t('Operating in maintenance mode.');
    $offline_message = t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')));

    $this->drupalGet('');
1003
    $this->assertRaw($admin_message, t('Found the site maintenance mode message.'));
1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032

    // Logout and verify that offline message is displayed.
    $this->drupalLogout();
    $this->drupalGet('');
    $this->assertText($offline_message);
    $this->drupalGet('node');
    $this->assertText($offline_message);
    $this->drupalGet('user/register');
    $this->assertText($offline_message);

    // Verify that user is able to log in.
    $this->drupalGet('user');
    $this->assertNoText($offline_message);
    $this->drupalGet('user/login');
    $this->assertNoText($offline_message);

    // Log in user and verify that maintenance mode message is displayed
    // directly after login.
    $edit = array(
      'name' => $this->user->name,
      'pass' => $this->user->pass_raw,
    );
    $this->drupalPost(NULL, $edit, t('Log in'));
    $this->assertText($user_message);

    // Log in administrative user and configure a custom site offline message.
    $this->drupalLogout();
    $this->drupalLogin($this->admin_user);
    $this->drupalGet('admin/config/development/maintenance');
1033
    $this->assertNoRaw($admin_message, t('Site maintenance mode message not displayed.'));
1034 1035 1036 1037 1038 1039 1040 1041 1042 1043

    $offline_message = 'Sorry, not online.';
    $edit = array(
      'maintenance_mode_message' => $offline_message,
    );
    $this->drupalPost(NULL, $edit, t('Save configuration'));

    // Logout and verify that custom site offline message is displayed.
    $this->drupalLogout();
    $this->drupalGet('');
1044
    $this->assertRaw($offline_message, t('Found the site offline message.'));
1045 1046 1047

    // Verify that custom site offline message is not displayed on user/password.
    $this->drupalGet('user/password');
1048
    $this->assertText(t('Username or e-mail address'), t('Anonymous users can access user/password'));
1049 1050 1051 1052 1053 1054 1055

    // Submit password reset form.
    $edit = array(
      'name' => $this->user->name,
    );
    $this->drupalPost('user/password', $edit, t('E-mail new password'));
    $mails = $this->drupalGetMails();
1056
    $start = strpos($mails[0]['body'], 'user/reset/'. $this->user->uid);
1057 1058 1059 1060 1061
    $path = substr($mails[0]['body'], $start, 66 + strlen($this->user->uid));

    // Log in with temporary login link.
    $this->drupalPost($path, array(), t('Log in'));
    $this->assertText($user_message);
1062 1063 1064
  }
}

1065 1066 1067 1068
/**
 * Tests generic date and time handling capabilities of Drupal.
 */
class DateTimeFunctionalTest extends DrupalWebTestCase {
1069
  public static function getInfo() {
1070
    return array(
1071 1072 1073
      'name' => 'Date and time',
      'description' => 'Configure date and time settings. Test date formatting and time zone handling, including daylight saving time.',
      'group' => 'System',
1074 1075 1076
    );
  }

1077
  function setUp() {
1078
    parent::setUp