diff --git a/core/modules/system/lib/Drupal/system/Tests/Graph/GraphUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Graph/GraphUnitTest.php new file mode 100644 index 000000000000..e1fa8dabec1a --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Graph/GraphUnitTest.php @@ -0,0 +1,198 @@ + 'Directed acyclic graph manipulation', + 'description' => 'Depth first search and sort unit tests.', + 'group' => 'Graph', + ); + } + + /** + * Test depth-first-search features. + */ + function testDepthFirstSearch() { + // The sample graph used is: + // 1 --> 2 --> 3 5 ---> 6 + // | ^ ^ + // | | | + // | | | + // +---> 4 <-- 7 8 ---> 9 + $graph = $this->normalizeGraph(array( + 1 => array(2), + 2 => array(3, 4), + 3 => array(), + 4 => array(3), + 5 => array(6), + 7 => array(4, 5), + 8 => array(9), + 9 => array(), + )); + $graph_object = new Graph($graph); + $graph = $graph_object->searchAndSort(); + + $expected_paths = array( + 1 => array(2, 3, 4), + 2 => array(3, 4), + 3 => array(), + 4 => array(3), + 5 => array(6), + 7 => array(4, 3, 5, 6), + 8 => array(9), + 9 => array(), + ); + $this->assertPaths($graph, $expected_paths); + + $expected_reverse_paths = array( + 1 => array(), + 2 => array(1), + 3 => array(2, 1, 4, 7), + 4 => array(2, 1, 7), + 5 => array(7), + 7 => array(), + 8 => array(), + 9 => array(8), + ); + $this->assertReversePaths($graph, $expected_reverse_paths); + + // Assert that DFS didn't created "missing" vertexes automatically. + $this->assertFALSE(isset($graph[6]), t('Vertex 6 has not been created')); + + $expected_components = array( + array(1, 2, 3, 4, 5, 7), + array(8, 9), + ); + $this->assertComponents($graph, $expected_components); + + $expected_weights = array( + array(1, 2, 3), + array(2, 4, 3), + array(7, 4, 3), + array(7, 5), + array(8, 9), + ); + $this->assertWeights($graph, $expected_weights); + } + + /** + * Return a normalized version of a graph. + */ + function normalizeGraph($graph) { + $normalized_graph = array(); + foreach ($graph as $vertex => $edges) { + // Create vertex even if it hasn't any edges. + $normalized_graph[$vertex] = array(); + foreach ($edges as $edge) { + $normalized_graph[$vertex]['edges'][$edge] = TRUE; + } + } + return $normalized_graph; + } + + /** + * Verify expected paths in a graph. + * + * @param $graph + * A graph array processed by + * Drupal\Component\Graph\Graph::searchAndSort(). + * @param $expected_paths + * An associative array containing vertices with their expected paths. + */ + function assertPaths($graph, $expected_paths) { + foreach ($expected_paths as $vertex => $paths) { + // Build an array with keys = $paths and values = TRUE. + $expected = array_fill_keys($paths, TRUE); + $result = isset($graph[$vertex]['paths']) ? $graph[$vertex]['paths'] : array(); + $this->assertEqual($expected, $result, t('Expected paths for vertex @vertex: @expected-paths, got @paths', array('@vertex' => $vertex, '@expected-paths' => $this->displayArray($expected, TRUE), '@paths' => $this->displayArray($result, TRUE)))); + } + } + + /** + * Verify expected reverse paths in a graph. + * + * @param $graph + * A graph array processed by Drupal\Component\Graph\Graph::searchAndSort(). + * @param $expected_reverse_paths + * An associative array containing vertices with their expected reverse + * paths. + */ + function assertReversePaths($graph, $expected_reverse_paths) { + foreach ($expected_reverse_paths as $vertex => $paths) { + // Build an array with keys = $paths and values = TRUE. + $expected = array_fill_keys($paths, TRUE); + $result = isset($graph[$vertex]['reverse_paths']) ? $graph[$vertex]['reverse_paths'] : array(); + $this->assertEqual($expected, $result, t('Expected reverse paths for vertex @vertex: @expected-paths, got @paths', array('@vertex' => $vertex, '@expected-paths' => $this->displayArray($expected, TRUE), '@paths' => $this->displayArray($result, TRUE)))); + } + } + + /** + * Verify expected components in a graph. + * + * @param $graph + * A graph array processed by Drupal\Component\Graph\Graph::searchAndSort(). + * @param $expected_components + * An array containing of components defined as a list of their vertices. + */ + function assertComponents($graph, $expected_components) { + $unassigned_vertices = array_fill_keys(array_keys($graph), TRUE); + foreach ($expected_components as $component) { + $result_components = array(); + foreach ($component as $vertex) { + $result_components[] = $graph[$vertex]['component']; + unset($unassigned_vertices[$vertex]); + } + $this->assertEqual(1, count(array_unique($result_components)), t('Expected one unique component for vertices @vertices, got @components', array('@vertices' => $this->displayArray($component), '@components' => $this->displayArray($result_components)))); + } + $this->assertEqual(array(), $unassigned_vertices, t('Vertices not assigned to a component: @vertices', array('@vertices' => $this->displayArray($unassigned_vertices, TRUE)))); + } + + /** + * Verify expected order in a graph. + * + * @param $graph + * A graph array processed by Drupal\Component\Graph\Graph::searchAndSort(). + * @param $expected_orders + * An array containing lists of vertices in their expected order. + */ + function assertWeights($graph, $expected_orders) { + foreach ($expected_orders as $order) { + $previous_vertex = array_shift($order); + foreach ($order as $vertex) { + $this->assertTrue($graph[$previous_vertex]['weight'] < $graph[$vertex]['weight'], t('Weights of @previous-vertex and @vertex are correct relative to each other', array('@previous-vertex' => $previous_vertex, '@vertex' => $vertex))); + } + } + } + + /** + * Helper function to output vertices as comma-separated list. + * + * @param $paths + * An array containing a list of vertices. + * @param $keys + * (optional) Whether to output the keys of $paths instead of the values. + */ + function displayArray($paths, $keys = FALSE) { + if (!empty($paths)) { + return implode(', ', $keys ? array_keys($paths) : $paths); + } + else { + return '(empty)'; + } + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/LockFunctionalTest.php b/core/modules/system/lib/Drupal/system/Tests/Lock/LockFunctionalTest.php similarity index 97% rename from core/modules/system/lib/Drupal/system/Tests/Common/LockFunctionalTest.php rename to core/modules/system/lib/Drupal/system/Tests/Lock/LockFunctionalTest.php index 4c1e4193df2e..9eb77808ee3f 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Common/LockFunctionalTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Lock/LockFunctionalTest.php @@ -2,10 +2,10 @@ /** * @file - * Definition of Drupal\system\Tests\Common\LockFunctionalTest. + * Definition of Drupal\system\Tests\Lock\LockFunctionalTest. */ -namespace Drupal\system\Tests\Common; +namespace Drupal\system\Tests\Lock; use Drupal\simpletest\WebTestBase; @@ -18,7 +18,7 @@ class LockFunctionalTest extends WebTestBase { return array( 'name' => 'Locking framework tests', 'description' => 'Confirm locking works between two separate requests.', - 'group' => 'System', + 'group' => 'Lock', ); } diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/HtmlToTextTest.php b/core/modules/system/lib/Drupal/system/Tests/Mail/HtmlToTextTest.php similarity index 99% rename from core/modules/system/lib/Drupal/system/Tests/Common/HtmlToTextTest.php rename to core/modules/system/lib/Drupal/system/Tests/Mail/HtmlToTextTest.php index a30c3c106dcb..5970114414e8 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Common/HtmlToTextTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Mail/HtmlToTextTest.php @@ -2,10 +2,10 @@ /** * @file - * Definition of Drupal\system\Tests\Common\HtmlToTextTest. + * Definition of Drupal\system\Tests\Mail\HtmlToTextTest. */ -namespace Drupal\system\Tests\Common; +namespace Drupal\system\Tests\Mail; use Drupal\simpletest\WebTestBase; diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/MailTest.php b/core/modules/system/lib/Drupal/system/Tests/Mail/MailTest.php similarity index 95% rename from core/modules/system/lib/Drupal/system/Tests/Common/MailTest.php rename to core/modules/system/lib/Drupal/system/Tests/Mail/MailTest.php index cf33f487e778..2ad73a1c5a07 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Common/MailTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Mail/MailTest.php @@ -2,10 +2,10 @@ /** * @file - * Definition of Drupal\system\Tests\Common\MailTest. + * Definition of Drupal\system\Tests\Mail\MailTest. */ -namespace Drupal\system\Tests\Common; +namespace Drupal\system\Tests\Mail; use Drupal\Core\Mail\MailInterface; use Drupal\simpletest\WebTestBase; @@ -34,7 +34,7 @@ class MailTest extends WebTestBase implements MailInterface { parent::setUp(array('simpletest')); // Set MailTestCase (i.e. this class) as the SMTP library - variable_set('mail_system', array('default-system' => 'Drupal\system\Tests\Common\MailTest')); + variable_set('mail_system', array('default-system' => 'Drupal\system\Tests\Mail\MailTest')); } /** diff --git a/core/modules/system/lib/Drupal/system/Tests/Module/DependencyTest.php b/core/modules/system/lib/Drupal/system/Tests/Module/DependencyTest.php new file mode 100644 index 000000000000..7e0c5e956772 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Module/DependencyTest.php @@ -0,0 +1,196 @@ + 'Module dependencies', + 'description' => 'Enable module without dependency enabled.', + 'group' => 'Module', + ); + } + + /** + * Attempt to enable translation module without language enabled. + */ + function testEnableWithoutDependency() { + // Attempt to enable content translation without language enabled. + $edit = array(); + $edit['modules[Core][translation][enable]'] = 'translation'; + $this->drupalPost('admin/modules', $edit, t('Save configuration')); + $this->assertText(t('Some required modules must be enabled'), t('Dependency required.')); + + $this->assertModules(array('translation', 'locale', 'language'), FALSE); + + // Assert that the language tables weren't enabled. + $this->assertTableCount('language', FALSE); + + $this->drupalPost(NULL, NULL, t('Continue')); + $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.')); + + $this->assertModules(array('translation', 'language'), TRUE); + + // Assert that the language tables were enabled. + $this->assertTableCount('language', TRUE); + } + + /** + * Attempt to enable a module with a missing dependency. + */ + function testMissingModules() { + // Test that the system_dependencies_test module is marked + // as missing a dependency. + $this->drupalGet('admin/modules'); + $this->assertRaw(t('@module (missing)', array('@module' => drupal_ucfirst('_missing_dependency'))), t('A module with missing dependencies is marked as such.')); + $checkbox = $this->xpath('//input[@type="checkbox" and @disabled="disabled" and @name="modules[Testing][system_dependencies_test][enable]"]'); + $this->assert(count($checkbox) == 1, t('Checkbox for the module is disabled.')); + + // Force enable the system_dependencies_test module. + module_enable(array('system_dependencies_test'), FALSE); + + // Verify that the module is forced to be disabled when submitting + // the module page. + $this->drupalPost('admin/modules', array(), t('Save configuration')); + $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.')); + + // Confirm. + $this->drupalPost(NULL, NULL, t('Continue')); + + // Verify that the module has been disabled. + $this->assertModules(array('system_dependencies_test'), FALSE); + } + + /** + * Tests enabling a module that depends on an incompatible version of a module. + */ + function testIncompatibleModuleVersionDependency() { + // Test that the system_incompatible_module_version_dependencies_test is + // marked as having an incompatible dependency. + $this->drupalGet('admin/modules'); + $this->assertRaw(t('@module (incompatible with version @version)', array( + '@module' => 'System incompatible module version test (>2.0)', + '@version' => '1.0', + )), 'A module that depends on an incompatible version of a module is marked as such.'); + $checkbox = $this->xpath('//input[@type="checkbox" and @disabled="disabled" and @name="modules[Testing][system_incompatible_module_version_dependencies_test][enable]"]'); + $this->assert(count($checkbox) == 1, t('Checkbox for the module is disabled.')); + } + + /** + * Tests enabling a module that depends on a module with an incompatible core version. + */ + function testIncompatibleCoreVersionDependency() { + // Test that the system_incompatible_core_version_dependencies_test is + // marked as having an incompatible dependency. + $this->drupalGet('admin/modules'); + $this->assertRaw(t('@module (incompatible with this version of Drupal core)', array( + '@module' => 'System incompatible core version test', + )), 'A module that depends on a module with an incompatible core version is marked as such.'); + $checkbox = $this->xpath('//input[@type="checkbox" and @disabled="disabled" and @name="modules[Testing][system_incompatible_core_version_dependencies_test][enable]"]'); + $this->assert(count($checkbox) == 1, t('Checkbox for the module is disabled.')); + } + + /** + * Tests enabling a module that depends on a module which fails hook_requirements(). + */ + function testEnableRequirementsFailureDependency() { + module_enable(array('comment')); + + $this->assertModules(array('requirements1_test'), FALSE); + $this->assertModules(array('requirements2_test'), FALSE); + + // Attempt to install both modules at the same time. + $edit = array(); + $edit['modules[Testing][requirements1_test][enable]'] = 'requirements1_test'; + $edit['modules[Testing][requirements2_test][enable]'] = 'requirements2_test'; + $this->drupalPost('admin/modules', $edit, t('Save configuration')); + + // Makes sure the modules were NOT installed. + $this->assertText(t('Requirements 1 Test failed requirements'), t('Modules status has been updated.')); + $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); + + } + + /** + * 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 taxonomy, comment, and poll (via module_test) + // - taxonomy depends on options + // - poll depends on php (via module_test) + // The correct enable order is: + $expected_order = array('comment', 'options', 'taxonomy', '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 Taxonomy, Options, Comment, Poll, PHP Filter modules to install Forum.')); + $edit['modules[Core][options][enable]'] = 'options'; + $edit['modules[Core][taxonomy][enable]'] = 'taxonomy'; + $edit['modules[Core][comment][enable]'] = 'comment'; + $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', 'comment', 'taxonomy', 'options'), TRUE); + + // Check the actual order which is saved by module_test_modules_enabled(). + $this->assertIdentical(variable_get('test_module_enable_order', array()), $expected_order); + } + + /** + * 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->drupalPost(NULL, array(), t('Continue')); + $this->assertModules(array('forum'), TRUE); + + // Disable forum and comment. Both should now be installed but disabled. + $edit = array('modules[Core][forum][enable]' => FALSE); + $this->drupalPost('admin/modules', $edit, t('Save configuration')); + $this->assertModules(array('forum'), FALSE); + $edit = array('modules[Core][comment][enable]' => FALSE); + $this->drupalPost('admin/modules', $edit, t('Save configuration')); + $this->assertModules(array('comment'), FALSE); + + // Check that the taxonomy module cannot be uninstalled. + $this->drupalGet('admin/modules/uninstall'); + $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.')); + + // 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.')); + $edit = array('uninstall[comment]' => 'comment'); + $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.')); + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/Module/EnableDisableTest.php b/core/modules/system/lib/Drupal/system/Tests/Module/EnableDisableTest.php new file mode 100644 index 000000000000..631957224db7 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Module/EnableDisableTest.php @@ -0,0 +1,214 @@ + 'Enable/disable modules', + 'description' => 'Enable/disable core module and confirm table creation/deletion.', + 'group' => 'Module', + ); + } + + /** + * Test that all core modules can be enabled, disabled and uninstalled. + */ + function testEnableDisable() { + // 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]); + } + } + + // 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(); + + // Remove already enabled modules (via installation profile). + // @todo Remove this after removing all dependencies from Testing profile. + foreach (module_list() as $dependency) { + // Exclude required modules. Only installation profile "suggestions" can + // be disabled and uninstalled. + if (isset($modules[$dependency])) { + $automatically_enabled[$dependency] = TRUE; + } + } + + $this->assertTrue(count($modules), t('Found @count modules that can be enabled: %modules', array( + '@count' => count($modules), + '%modules' => implode(', ', array_keys($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); + + // 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); + $this->assertModuleConfigFilesExist($module_to_enable); + $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); + } + + // 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); + } + } + } + + // 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; + } + } + + // 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'); + } + + // 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. + $edit = array(); + foreach (array_keys($modules) as $name) { + $edit['modules[Core][' . $name . '][enable]'] = $name; + } + $this->drupalPost('admin/modules', $edit, t('Save configuration')); + $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.')); + } + + /** + * 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))); + $this->assertLogMessage('system', "%module module disabled.", array('%module' => $module), WATCHDOG_INFO); + + // Check that the module's database tables still exist. + $this->assertModuleTablesExist($module); + // Check that the module's config files still exist. + $this->assertModuleConfigFilesExist($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') { + $this->assertLogMessage('system', "%module module uninstalled.", array('%module' => $module), WATCHDOG_INFO); + } + + // Check that the module's database tables no longer exist. + $this->assertModuleTablesDoNotExist($module); + // Check that the module's config files no longer exist. + $this->assertModuleConfigFilesDoNotExist($module); + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/Module/HookRequirementsTest.php b/core/modules/system/lib/Drupal/system/Tests/Module/HookRequirementsTest.php new file mode 100644 index 000000000000..6e71f1fd98a8 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Module/HookRequirementsTest.php @@ -0,0 +1,37 @@ + '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(); + $edit['modules[Testing][requirements1_test][enable]'] = 'requirements1_test'; + $this->drupalPost('admin/modules', $edit, t('Save configuration')); + + // Makes sure the module was NOT installed. + $this->assertText(t('Requirements 1 Test failed requirements'), t('Modules status has been updated.')); + $this->assertModules(array('requirements1_test'), FALSE); + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/Module/ModuleTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Module/ModuleTestBase.php new file mode 100644 index 000000000000..3b2ed5c3c603 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Module/ModuleTestBase.php @@ -0,0 +1,195 @@ +admin_user = $this->drupalCreateUser(array('access administration pages', 'administer modules')); + $this->drupalLogin($this->admin_user); + } + + /** + * 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. + */ + function assertTableCount($base_table, $count = TRUE) { + $tables = db_find_tables(Database::getConnection()->prefixTables('{' . $base_table . '}') . '%'); + + if ($count) { + return $this->assertTrue($tables, t('Tables matching "@base_table" found.', array('@base_table' => $base_table))); + } + return $this->assertFalse($tables, t('Tables matching "@base_table" not found.', array('@base_table' => $base_table))); + } + + /** + * 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))); + } + + /** + * Assert that a module's config files have been loaded. + * + * @param string $module + * The name of the module. + * + * @return bool + * TRUE if the module's config files exist, FALSE otherwise. + */ + function assertModuleConfigFilesExist($module) { + // Define test variable. + $files_exist = TRUE; + // Get the path to the module's config dir. + $module_config_dir = drupal_get_path('module', $module) . '/config'; + if (is_dir($module_config_dir)) { + $files = glob($module_config_dir . '/*.' . FileStorage::getFileExtension()); + $this->assertTrue($files); + $config_dir = config_get_config_directory(); + // Get the filename of each config file. + foreach ($files as $file) { + $parts = explode('/', $file); + $filename = array_pop($parts); + if (!file_exists($config_dir . '/' . $filename)) { + $files_exist = FALSE; + } + } + } + + return $this->assertTrue($files_exist, t('All config files defined by the @module module have been copied to the live config directory.', array('@module' => $module))); + } + + /** + * Assert that none of a module's default config files are loaded. + * + * @param string $module + * The name of the module. + * + * @return bool + * TRUE if the module's config files do not exist, FALSE otherwise. + */ + function assertModuleConfigFilesDoNotExist($module) { + // Define test variable. + $files_exist = FALSE; + // Get the path to the module's config dir. + $module_config_dir = drupal_get_path('module', $module) . '/config'; + if (is_dir($module_config_dir)) { + $files = glob($module_config_dir . '/*.' . FileStorage::getFileExtension()); + $this->assertTrue($files); + $config_dir = config_get_config_directory(); + // Get the filename of each config file. + foreach ($files as $file) { + $parts = explode('/', $file); + $filename = array_pop($parts); + if (file_exists($config_dir . '/' . $filename)) { + $files_exist = TRUE; + } + } + } + + return $this->assertFalse($files_exist, t('All config files defined by the @module module have been deleted from the live config directory.', array('@module' => $module))); + } + + /** + * Assert the list of modules are enabled or disabled. + * + * @param $modules + * Module list to check. + * @param $enabled + * Expected module state. + */ + 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.'; + } + $this->assertEqual(module_exists($module), $enabled, t($message, array('@module' => $module))); + } + } + + /** + * 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. + */ + function assertLogMessage($type, $message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = '') { + $count = db_select('watchdog', 'w') + ->condition('type', $type) + ->condition('message', $message) + ->condition('variables', serialize($variables)) + ->condition('severity', $severity) + ->condition('link', $link) + ->countQuery() + ->execute() + ->fetchField(); + $this->assertTrue($count > 0, t('watchdog table contains @count rows for @message', array('@count' => $count, '@message' => $message))); + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/Module/RequiredTest.php b/core/modules/system/lib/Drupal/system/Tests/Module/RequiredTest.php new file mode 100644 index 000000000000..fcde5ca9fdbd --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Module/RequiredTest.php @@ -0,0 +1,42 @@ + 'Required modules', + 'description' => 'Attempt disabling of required modules.', + 'group' => 'Module', + ); + } + + /** + * Assert that core required modules cannot be disabled. + */ + function testDisableRequired() { + $module_info = system_get_info('module'); + $this->drupalGet('admin/modules'); + 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'])) { + $this->assertFieldByXPath("//input[@name='$field_name' and @disabled='disabled' and @checked='checked']", '', t('Field @name was disabled and checked.', array('@name' => $field_name))); + } + else { + $this->assertNoFieldByName($field_name); + } + } + } + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/Module/VersionTest.php b/core/modules/system/lib/Drupal/system/Tests/Module/VersionTest.php new file mode 100644 index 000000000000..f7b4bd211879 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Module/VersionTest.php @@ -0,0 +1,69 @@ + 'Module versions', + 'description' => 'Check module version dependencies.', + 'group' => 'Module', + ); + } + + function setUp() { + parent::setUp('module_test'); + } + + /** + * Test version dependencies. + */ + function testModuleVersions() { + $dependencies = array( + // Alternating between being compatible and incompatible with 8.x-2.4-beta3. + // 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. + 'common_test (=8.x2.x, >=2.4)', + // Core version is optional. Compatible. + 'common_test (=8.x-2.x, >=2.4-alpha2)', + // Test !=, explicitly incompatible. + 'common_test (=2.x, !=2.4-beta3)', + // Three operations. Compatible. + 'common_test (=2.x, !=2.3, <2.5)', + // 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)', + ); + variable_set('dependencies', $dependencies); + $n = count($dependencies); + for ($i = 0; $i < $n; $i++) { + $this->drupalGet('admin/modules'); + $checkbox = $this->xpath('//input[@id="edit-modules-testing-module-test-enable"]'); + $this->assertEqual(!empty($checkbox[0]['disabled']), $i % 2, $dependencies[$i]); + } + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/QueueTest.php b/core/modules/system/lib/Drupal/system/Tests/Queue/QueueTest.php similarity index 97% rename from core/modules/system/lib/Drupal/system/Tests/QueueTest.php rename to core/modules/system/lib/Drupal/system/Tests/Queue/QueueTest.php index f13e1018a5cc..9e824dc5777c 100644 --- a/core/modules/system/lib/Drupal/system/Tests/QueueTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Queue/QueueTest.php @@ -2,10 +2,10 @@ /** * @file - * Definition of Drupal\system\Tests\QueueTest. + * Definition of Drupal\system\Tests\Queue\QueueTest. */ -namespace Drupal\system\Tests; +namespace Drupal\system\Tests\Queue; use Drupal\Core\Queue\Memory; use Drupal\Core\Queue\System; diff --git a/core/modules/system/lib/Drupal/system/Tests/System/AccessDeniedTest.php b/core/modules/system/lib/Drupal/system/Tests/System/AccessDeniedTest.php new file mode 100644 index 000000000000..6c17c16792d4 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/System/AccessDeniedTest.php @@ -0,0 +1,90 @@ + '403 functionality', + 'description' => 'Tests page access denied functionality, including custom 403 pages.', + 'group' => 'System' + ); + } + + function setUp() { + parent::setUp(array('block')); + + // Create an administrative user. + $this->admin_user = $this->drupalCreateUser(array('access administration pages', 'administer site configuration', 'administer blocks')); + + user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access user profiles')); + user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('access user profiles')); + } + + function testAccessDenied() { + $this->drupalGet('admin'); + $this->assertText(t('Access denied'), t('Found the default 403 page')); + $this->assertResponse(403); + + // Use a custom 403 page. + $this->drupalLogin($this->admin_user); + $edit = array( + 'site_403' => 'user/' . $this->admin_user->uid, + ); + $this->drupalPost('admin/config/system/site-information', $edit, t('Save configuration')); + + // Enable the user login block. + $edit = array( + 'blocks[user_login][region]' => 'sidebar_first', + ); + $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); + + // Logout and check that the user login block is shown on custom 403 pages. + $this->drupalLogout(); + $this->drupalGet('admin'); + $this->assertText($this->admin_user->name, t('Found the custom 403 page')); + $this->assertText(t('User login'), t('Blocks are shown on the custom 403 page')); + + // Log back in and remove the custom 403 page. + $this->drupalLogin($this->admin_user); + $edit = array( + 'site_403' => '', + ); + $this->drupalPost('admin/config/system/site-information', $edit, t('Save configuration')); + + // Logout and check that the user login block is shown on default 403 pages. + $this->drupalLogout(); + $this->drupalGet('admin'); + $this->assertText(t('Access denied'), t('Found the default 403 page')); + $this->assertResponse(403); + $this->assertText(t('User login'), t('Blocks are shown on the default 403 page')); + + // Log back in, set the custom 403 page to /user and remove the block + $this->drupalLogin($this->admin_user); + variable_set('site_403', 'user'); + $this->drupalPost('admin/structure/block', array('blocks[user_login][region]' => '-1'), t('Save blocks')); + + // 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, + ); + $this->drupalPost('admin/config/system/site-information', $edit, t('Log in')); + + // Check that we're still on the same page. + $this->assertText(t('Site information')); + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/System/AdminMetaTagTest.php b/core/modules/system/lib/Drupal/system/Tests/System/AdminMetaTagTest.php new file mode 100644 index 000000000000..ea07456b4905 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/System/AdminMetaTagTest.php @@ -0,0 +1,33 @@ + 'Fingerprinting meta tag', + 'description' => 'Confirm that the fingerprinting meta tag appears as expected.', + 'group' => 'System' + ); + } + + /** + * Verify that the meta tag HTML is generated correctly. + */ + public function testMetaTag() { + list($version, ) = explode('.', VERSION); + $string = ''; + $this->drupalGet('node'); + $this->assertRaw($string, t('Fingerprinting meta tag generated correctly.'), t('System')); + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/System/AdminTest.php b/core/modules/system/lib/Drupal/system/Tests/System/AdminTest.php new file mode 100644 index 000000000000..4e8d49aa6534 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/System/AdminTest.php @@ -0,0 +1,123 @@ + 'Administrative pages', + 'description' => 'Tests output on administrative pages and compact mode functionality.', + 'group' => 'System', + ); + } + + function setUp() { + // testAdminPages() requires Locale module. + parent::setUp(array('locale')); + + // Create an administrator with all permissions, as well as a regular user + // who can only access administration pages and perform some Locale module + // administrative tasks, but not all of them. + $this->admin_user = $this->drupalCreateUser(array_keys(module_invoke_all('permission'))); + $this->web_user = $this->drupalCreateUser(array( + 'access administration pages', + 'translate interface', + )); + $this->drupalLogin($this->admin_user); + } + + /** + * Tests output on administrative listing pages. + */ + function testAdminPages() { + // Go to Administration. + $this->drupalGet('admin'); + + // Verify that all visible, top-level administration links are listed on + // the main administration page. + foreach (menu_get_router() as $path => $item) { + if (strpos($path, 'admin/') === 0 && ($item['type'] & MENU_VISIBLE_IN_TREE) && $item['_number_parts'] == 2) { + $this->assertLink($item['title']); + $this->assertLinkByHref($path); + $this->assertText($item['description']); + } + } + + // For each administrative listing page on which the Locale module appears, + // verify that there are links to the module's primary configuration pages, + // but no links to its individual sub-configuration pages. Also verify that + // a user with access to only some Locale module administration pages only + // sees links to the pages they have access to. + $admin_list_pages = array( + 'admin/index', + 'admin/config', + 'admin/config/regional', + ); + + foreach ($admin_list_pages as $page) { + // For the administrator, verify that there are links to Locale's primary + // configuration pages, but no links to individual sub-configuration + // pages. + $this->drupalLogin($this->admin_user); + $this->drupalGet($page); + $this->assertLinkByHref('admin/config'); + $this->assertLinkByHref('admin/config/regional/settings'); + $this->assertLinkByHref('admin/config/regional/date-time'); + $this->assertLinkByHref('admin/config/regional/language'); + $this->assertNoLinkByHref('admin/config/regional/language/detection/session'); + $this->assertNoLinkByHref('admin/config/regional/language/detection/url'); + $this->assertLinkByHref('admin/config/regional/translate'); + // On admin/index only, the administrator should also see a "Configure + // permissions" link for the Locale module. + if ($page == 'admin/index') { + $this->assertLinkByHref("admin/people/permissions#module-locale"); + } + + // For a less privileged user, verify that there are no links to Locale's + // primary configuration pages, but a link to the translate page exists. + $this->drupalLogin($this->web_user); + $this->drupalGet($page); + $this->assertLinkByHref('admin/config'); + $this->assertNoLinkByHref('admin/config/regional/settings'); + $this->assertNoLinkByHref('admin/config/regional/date-time'); + $this->assertNoLinkByHref('admin/config/regional/language'); + $this->assertNoLinkByHref('admin/config/regional/language/detection/session'); + $this->assertNoLinkByHref('admin/config/regional/language/detection/url'); + $this->assertLinkByHref('admin/config/regional/translate'); + // This user cannot configure permissions, so even on admin/index should + // not see a "Configure permissions" link for the Locale module. + if ($page == 'admin/index') { + $this->assertNoLinkByHref("admin/people/permissions#module-locale"); + } + } + } + + /** + * Test compact mode. + */ + function testCompactMode() { + $this->drupalGet('admin/compact/on'); + $this->assertTrue($this->cookies['Drupal.visitor.admin_compact_mode']['value'], t('Compact mode turns on.')); + $this->drupalGet('admin/compact/on'); + $this->assertTrue($this->cookies['Drupal.visitor.admin_compact_mode']['value'], t('Compact mode remains on after a repeat call.')); + $this->drupalGet(''); + $this->assertTrue($this->cookies['Drupal.visitor.admin_compact_mode']['value'], t('Compact mode persists on new requests.')); + + $this->drupalGet('admin/compact/off'); + $this->assertEqual($this->cookies['Drupal.visitor.admin_compact_mode']['value'], 'deleted', t('Compact mode turns off.')); + $this->drupalGet('admin/compact/off'); + $this->assertEqual($this->cookies['Drupal.visitor.admin_compact_mode']['value'], 'deleted', t('Compact mode remains off after a repeat call.')); + $this->drupalGet(''); + $this->assertTrue($this->cookies['Drupal.visitor.admin_compact_mode']['value'], t('Compact mode persists on new requests.')); + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/System/BlockTest.php b/core/modules/system/lib/Drupal/system/Tests/System/BlockTest.php new file mode 100644 index 000000000000..92d50b06e0fa --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/System/BlockTest.php @@ -0,0 +1,74 @@ + 'Block functionality', + 'description' => 'Configure and move powered-by block.', + 'group' => 'System', + ); + } + + function setUp() { + parent::setUp('block'); + + // Create and login user + $admin_user = $this->drupalCreateUser(array('administer blocks', 'access administration pages')); + $this->drupalLogin($admin_user); + } + + /** + * Test displaying and hiding the powered-by and help blocks. + */ + function testSystemBlocks() { + // Set block title and some settings to confirm that the interface is available. + $this->drupalPost('admin/structure/block/manage/system/powered-by/configure', array('title' => $this->randomName(8)), t('Save block')); + $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.')); + + // Set the powered-by block to the footer region. + $edit = array(); + $edit['blocks[system_powered-by][region]'] = 'footer'; + $edit['blocks[system_main][region]'] = 'content'; + $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); + $this->assertText(t('The block settings have been updated.'), t('Block successfully moved to footer region.')); + + // Confirm that the block is being displayed. + $this->drupalGet('node'); + $this->assertRaw('id="block-system-powered-by"', t('Block successfully being displayed on the page.')); + + // Set the block to the disabled region. + $edit = array(); + $edit['blocks[system_powered-by][region]'] = '-1'; + $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); + + // Confirm that the block is hidden. + $this->assertNoRaw('id="block-system-powered-by"', t('Block no longer appears on page.')); + + // For convenience of developers, set the block to its default settings. + $edit = array(); + $edit['blocks[system_powered-by][region]'] = 'footer'; + $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); + $this->drupalPost('admin/structure/block/manage/system/powered-by/configure', array('title' => ''), t('Save block')); + + // Set the help block to the help region. + $edit = array(); + $edit['blocks[system_help][region]'] = 'help'; + $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); + + // Test displaying the help block with block caching enabled. + variable_set('block_cache', TRUE); + $this->drupalGet('admin/structure/block/add'); + $this->assertRaw(t('Use this page to create a new custom block.')); + $this->drupalGet('admin/index'); + $this->assertRaw(t('This page shows you all available administration tasks for each module.')); + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/System/CronRunTest.php b/core/modules/system/lib/Drupal/system/Tests/System/CronRunTest.php new file mode 100644 index 000000000000..601c5a83eb19 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/System/CronRunTest.php @@ -0,0 +1,143 @@ + 'Cron run', + 'description' => 'Test cron run.', + 'group' => 'System', + ); + } + + function setUp() { + parent::setUp(array('common_test', 'common_test_cron_helper')); + } + + /** + * Test cron runs. + */ + function testCronRun() { + global $base_url; + + // Run cron anonymously without any cron key. + $this->drupalGet('cron'); + $this->assertResponse(404); + + // Run cron anonymously with a random cron key. + $key = $this->randomName(16); + $this->drupalGet('cron/' . $key); + $this->assertResponse(403); + + // Run cron anonymously with the valid cron key. + $key = config('system.cron')->get('cron_key'); + $this->drupalGet('cron/' . $key); + $this->assertResponse(204); + } + + /** + * Ensure that the automatic cron run feature is working. + * + * In these tests we do not use REQUEST_TIME to track start time, because we + * need the exact time when cron is triggered. + */ + function testAutomaticCron() { + // Ensure cron does not run when the cron threshold is enabled and was + // not passed. + $cron_last = time(); + $cron_safe_threshold = 100; + variable_set('cron_last', $cron_last); + config('system.cron') + ->set('cron_safe_threshold', $cron_safe_threshold) + ->save(); + $this->drupalGet(''); + $this->assertTrue($cron_last == variable_get('cron_last', NULL), t('Cron does not run when the cron threshold is not passed.')); + + // Test if cron runs when the cron threshold was passed. + $cron_last = time() - 200; + variable_set('cron_last', $cron_last); + $this->drupalGet(''); + sleep(1); + $this->assertTrue($cron_last < variable_get('cron_last', NULL), t('Cron runs when the cron threshold is passed.')); + + // Disable the cron threshold through the interface. + $admin_user = $this->drupalCreateUser(array('administer site configuration')); + $this->drupalLogin($admin_user); + $this->drupalPost('admin/config/system/cron', array('cron_safe_threshold' => 0), t('Save configuration')); + $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); + $this->drupalGet(''); + $this->assertTrue($cron_last == variable_get('cron_last', NULL), t('Cron does not run when the cron threshold is disabled.')); + } + + /** + * Ensure that temporary files are removed. + * + * Create files for all the possible combinations of age and status. We are + * using UPDATE statements because using the API would set the timestamp. + */ + function testTempFileCleanup() { + // Temporary file that is older than DRUPAL_MAXIMUM_TEMP_FILE_AGE. + $temp_old = file_save_data(''); + db_update('file_managed') + ->fields(array( + 'status' => 0, + 'timestamp' => 1, + )) + ->condition('fid', $temp_old->fid) + ->execute(); + $this->assertTrue(file_exists($temp_old->uri), t('Old temp file was created correctly.')); + + // Temporary file that is less than DRUPAL_MAXIMUM_TEMP_FILE_AGE. + $temp_new = file_save_data(''); + db_update('file_managed') + ->fields(array('status' => 0)) + ->condition('fid', $temp_new->fid) + ->execute(); + $this->assertTrue(file_exists($temp_new->uri), t('New temp file was created correctly.')); + + // Permanent file that is older than DRUPAL_MAXIMUM_TEMP_FILE_AGE. + $perm_old = file_save_data(''); + db_update('file_managed') + ->fields(array('timestamp' => 1)) + ->condition('fid', $temp_old->fid) + ->execute(); + $this->assertTrue(file_exists($perm_old->uri), t('Old permanent file was created correctly.')); + + // Permanent file that is newer than DRUPAL_MAXIMUM_TEMP_FILE_AGE. + $perm_new = file_save_data(''); + $this->assertTrue(file_exists($perm_new->uri), t('New permanent file was created correctly.')); + + // Run cron and then ensure that only the old, temp file was deleted. + $this->cronRun(); + $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.')); + } + + /** + * 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.')); + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/System/DateTimeTest.php b/core/modules/system/lib/Drupal/system/Tests/System/DateTimeTest.php new file mode 100644 index 000000000000..84bd7c630a5f --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/System/DateTimeTest.php @@ -0,0 +1,206 @@ + 'Date and time', + 'description' => 'Configure date and time settings. Test date formatting and time zone handling, including daylight saving time.', + 'group' => 'System', + ); + } + + function setUp() { + parent::setUp(array('language')); + + // Create admin user and log in admin user. + $this->admin_user = $this->drupalCreateUser(array('administer site configuration')); + $this->drupalLogin($this->admin_user); + } + + + /** + * Test time zones and DST handling. + */ + function testTimeZoneHandling() { + // Setup date/time settings for Honolulu time. + variable_set('date_default_timezone', 'Pacific/Honolulu'); + variable_set('configurable_timezones', 0); + variable_set('date_format_medium', 'Y-m-d H:i:s O'); + + // Create some nodes with different authored-on dates. + $date1 = '2007-01-31 21:00:00 -1000'; + $date2 = '2007-07-31 21:00:00 -1000'; + $node1 = $this->drupalCreateNode(array('created' => strtotime($date1), 'type' => 'article')); + $node2 = $this->drupalCreateNode(array('created' => strtotime($date2), 'type' => 'article')); + + // Confirm date format and time zone. + $this->drupalGet("node/$node1->nid"); + $this->assertText('2007-01-31 21:00:00 -1000', t('Date should be identical, with GMT offset of -10 hours.')); + $this->drupalGet("node/$node2->nid"); + $this->assertText('2007-07-31 21:00:00 -1000', t('Date should be identical, with GMT offset of -10 hours.')); + + // Set time zone to Los Angeles time. + variable_set('date_default_timezone', 'America/Los_Angeles'); + + // Confirm date format and time zone. + $this->drupalGet("node/$node1->nid"); + $this->assertText('2007-01-31 23:00:00 -0800', t('Date should be two hours ahead, with GMT offset of -8 hours.')); + $this->drupalGet("node/$node2->nid"); + $this->assertText('2007-08-01 00:00:00 -0700', t('Date should be three hours ahead, with GMT offset of -7 hours.')); + } + + /** + * Test date type configuration. + */ + function testDateTypeConfiguration() { + // Confirm system date types appear. + $this->drupalGet('admin/config/regional/date-time'); + $this->assertText(t('Medium'), 'System date types appear in date type list.'); + $this->assertNoRaw('href="/admin/config/regional/date-time/types/medium/delete"', 'No delete link appear for system date types.'); + + // Add custom date type. + $this->clickLink(t('Add date type')); + $date_type = strtolower($this->randomName(8)); + $machine_name = 'machine_' . $date_type; + $date_format = 'd.m.Y - H:i'; + $edit = array( + 'date_type' => $date_type, + 'machine_name' => $machine_name, + 'date_format' => $date_format, + ); + $this->drupalPost('admin/config/regional/date-time/types/add', $edit, t('Add date type')); + $this->assertEqual($this->getUrl(), url('admin/config/regional/date-time', array('absolute' => TRUE)), t('Correct page redirection.')); + $this->assertText(t('New date type added successfully.'), 'Date type added confirmation message appears.'); + $this->assertText($date_type, 'Custom date type appears in the date type list.'); + $this->assertText(t('delete'), 'Delete link for custom date type appears.'); + + // Delete custom date type. + $this->clickLink(t('delete')); + $this->drupalPost('admin/config/regional/date-time/types/' . $machine_name . '/delete', array(), t('Remove')); + $this->assertEqual($this->getUrl(), url('admin/config/regional/date-time', array('absolute' => TRUE)), t('Correct page redirection.')); + $this->assertText(t('Removed date type ' . $date_type), 'Custom date type removed.'); + } + + /** + * Test date format configuration. + */ + function testDateFormatConfiguration() { + // Confirm 'no custom date formats available' message appears. + $this->drupalGet('admin/config/regional/date-time/formats'); + $this->assertText(t('No custom date formats available.'), 'No custom date formats message appears.'); + + // Add custom date format. + $this->clickLink(t('Add format')); + $edit = array( + 'date_format' => 'Y', + ); + $this->drupalPost('admin/config/regional/date-time/formats/add', $edit, t('Add format')); + $this->assertEqual($this->getUrl(), url('admin/config/regional/date-time/formats', array('absolute' => TRUE)), t('Correct page redirection.')); + $this->assertNoText(t('No custom date formats available.'), 'No custom date formats message does not appear.'); + $this->assertText(t('Custom date format added.'), 'Custom date format added.'); + + // Ensure custom date format appears in date type configuration options. + $this->drupalGet('admin/config/regional/date-time'); + $this->assertRaw('