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('