diff --git a/includes/registry.inc b/includes/registry.inc
index cfc7ae118a053e075609a1ba54de5c33e5336557..89de42356786f95877c115fe62efad3a30db8181 100644
--- a/includes/registry.inc
+++ b/includes/registry.inc
@@ -45,11 +45,19 @@ function _registry_rebuild() {
   system_get_files_database($modules, 'module');
   // Get the list of files we are going to parse.
   $files = array();
-  foreach ($modules as $module) {
+  foreach ($modules as &$module) {
+    $dir = dirname($module->filepath);
+
+    // Store the module directory for use in hook_registry_files_alter().
+    $module->dir = $dir;
+
+    // Parse the .info file for all modules, reguardless of their status so the
+    // list of files can then be used in hook_registry_files_alter()
+    // implementations.
+    $module->info = drupal_parse_info_file($dir . '/' . $module->name . '.info');
+
     if ($module->status) {
-      // Parse .info file only for enabled modules.
-      $module->info = drupal_parse_info_file(dirname($module->filepath) . '/' . $module->name . '.info');
-      $dir = dirname($module->filepath);
+      // Add files for enabled modules to the registry.
       foreach ($module->info['files'] as $file) {
         $files["$dir/$file"] = array('module' => $module->name, 'weight' => $module->weight);
       }
@@ -59,6 +67,13 @@ function _registry_rebuild() {
     $files["$filename"] = array('module' => '', 'weight' => 0);
   }
 
+  // Allow modules to manually modify the list of files before the registry
+  // parses them. The $modules array provides the .info file information, which
+  // includes the list of files registered to each module. Any files in the
+  // list can then be added to the list of files that the registry will parse,
+  // or modify attributes of a file.
+  drupal_alter('registry_files', $files, $modules);
+
   foreach (registry_get_parsed_files() as $filename => $file) {
     // Add the md5 to those files we've already parsed.
     if (isset($files[$filename])) {
diff --git a/modules/aggregator/aggregator.info b/modules/aggregator/aggregator.info
index fb65a7fa94a2d59428bfbeb90d2fb77ed93b30d8..2b75e247a5226bc272a57a6ab19a150c01ccc6c1 100644
--- a/modules/aggregator/aggregator.info
+++ b/modules/aggregator/aggregator.info
@@ -11,3 +11,4 @@ files[] = aggregator.fetcher.inc
 files[] = aggregator.parser.inc
 files[] = aggregator.processor.inc
 files[] = aggregator.install
+files[] = aggregator.test
diff --git a/modules/block/block.api.php b/modules/block/block.api.php
index 5540302e8f35c08b12cea967e9269898f9a2433e..c1f5d6145b2886f75e2c6630c72012979ee93442 100644
--- a/modules/block/block.api.php
+++ b/modules/block/block.api.php
@@ -171,6 +171,52 @@ function hook_block_view($delta = '') {
   return $block;
 }
 
+/**
+ * Act on blocks prior to rendering.
+ *
+ * This hook allows you to add, remove or modify blocks in the block list. The
+ * block list contains the block definitions not the rendered blocks. The blocks
+ * are rendered after the modules have had a chance to manipulate the block
+ * list.
+ * Alternatively you can set $block->content here, which will override the
+ * content of the block and prevent hook_block_view() from running.
+ *
+ * @param $blocks
+ *   An array of $blocks, keyed by $bid
+ *
+ * This example shows how to achieve language specific visibility setting for
+ * blocks.
+ */
+function hook_block_list_alter(&$blocks) {
+  global $language, $theme_key;
+
+  $result = db_query('SELECT module, delta, language FROM {my_table}');
+  $block_languages = array();
+  foreach ($result as $record) {
+    $block_languages[$record->module][$record->delta][$record->language] = TRUE;
+  }
+
+  foreach ($blocks as $key => $block) {
+    // Any module using this alter should inspect the data before changing it,
+    // to ensure it is what they expect.
+    if ($block->theme != $theme_key || $block->status != 1) {
+      // This block was added by a contrib module, leave it in the list.
+      continue;
+    }
+
+    if (!isset($block_languages[$block->module][$block->delta])) {
+      // No language setting for this block, leave it in the list.
+      continue;
+    }
+
+    if (!isset($block_languages[$block->module][$block->delta][$language->language])) {
+      // This block should not be displayed with the active language, remove
+      // from the list.
+      unset($blocks[$key]);
+    }
+  }
+}
+
 /**
  * @} End of "addtogroup hooks".
  */
diff --git a/modules/block/block.info b/modules/block/block.info
index a74f5d7213e301cec8dd24282fa3904bb041998f..bb14187aa357bd5bd170791349e4724f6901a67c 100644
--- a/modules/block/block.info
+++ b/modules/block/block.info
@@ -8,3 +8,4 @@ core = 7.x
 files[] = block.module
 files[] = block.admin.inc
 files[] = block.install
+files[] = block.test
diff --git a/modules/block/block.module b/modules/block/block.module
index 13e9821ada0b616540ead0bbc97579c78133e350..80073729831e9004f83a28118ef73f3f304fd3ee 100644
--- a/modules/block/block.module
+++ b/modules/block/block.module
@@ -586,30 +586,64 @@ function block_list($region) {
  * Load blocks information from the database.
  */
 function _block_load_blocks() {
-  global $user, $theme_key;
+  global $theme_key;
 
-  $blocks = array();
-  $rids = array_keys($user->roles);
   $query = db_select('block', 'b');
-  $query->leftJoin('block_role', 'r', 'b.module = r.module AND b.delta = r.delta');
   $result = $query
-    ->distinct()
     ->fields('b')
     ->condition('b.theme', $theme_key)
     ->condition('b.status', 1)
-    ->condition(db_or()
-      ->condition('r.rid', $rids, 'IN')
-      ->isNull('r.rid')
-    )
     ->orderBy('b.region')
     ->orderBy('b.weight')
     ->orderBy('b.module')
     ->addTag('block_load')
     ->execute();
-  foreach ($result as $block) {
-    if (!isset($blocks[$block->region])) {
-      $blocks[$block->region] = array();
+
+  $block_list = $result->fetchAllAssoc('bid');
+  // Allow modules to modify the block list.
+  drupal_alter('block_list', $block_list);
+
+  $blocks = array();
+  foreach ($block_list as $block) {
+    $blocks[$block->region]["{$block->module}_{$block->delta}"] = $block;
+  }
+  return $blocks;
+}
+
+/**
+ * Implement hook_block_list_alter().
+ *
+ * Check the page, role and user specific visibilty settings. Remove the block
+ * if the visibility conditions are not met.
+ */
+function block_block_list_alter(&$blocks) {
+  global $user, $theme_key;
+
+  // Build an array of roles for each block.
+  $block_roles = array();
+  $result = db_query('SELECT module, delta, rid FROM {block_role}');
+  foreach ($result as $record) {
+    $block_roles[$record->module][$record->delta][] = $record->rid;
+  }
+
+  foreach ($blocks as $key => $block) {
+    if ($block->theme != $theme_key || $block->status != 1) {
+      // This block was added by a contrib module, leave it in the list.
+      continue;
     }
+
+    // If a block has no roles associated, it is displayed for every role.
+    // For blocks with roles associated, if none of the user's roles matches
+    // the settings from this block, remove it from the block list.
+    if (!isset($block_roles[$block->module][$block->delta])) {
+      // No roles associated.
+    }
+    elseif (!array_intersect($block_roles[$block->module][$block->delta], array_keys($user->roles))) {
+      // No match.
+      unset($blocks[$key]);
+      continue;
+     }
+
     // Use the user's block visibility setting, if necessary.
     if ($block->custom != 0) {
       if ($user->uid && isset($user->block[$block->module][$block->delta])) {
@@ -622,6 +656,10 @@ function _block_load_blocks() {
     else {
       $enabled = TRUE;
     }
+    if (!$enabled) {
+      unset($blocks[$key]);
+      continue;
+    }
 
     // Match path if necessary.
     if ($block->pages) {
@@ -647,12 +685,10 @@ function _block_load_blocks() {
     else {
       $page_match = TRUE;
     }
-    $block->enabled = $enabled;
-    $block->page_match = $page_match;
-    $blocks[$block->region]["{$block->module}_{$block->delta}"] = $block;
+    if (!$page_match) {
+      unset($blocks[$key]);
+    }
   }
-
-  return $blocks;
 }
 
 /**
@@ -668,38 +704,39 @@ function _block_render_blocks($region_blocks) {
   foreach ($region_blocks as $key => $block) {
     // Render the block content if it has not been created already.
     if (!isset($block->content)) {
-      // Erase the block from the static array - we'll put it back if it has content.
+      // Erase the block from the static array - we'll put it back if it has
+      // content.
       unset($region_blocks[$key]);
-      if ($block->enabled && $block->page_match) {
-        // Try fetching the block from cache. Block caching is not compatible with
-        // node_access modules. We also preserve the submission of forms in blocks,
-        // by fetching from cache only if the request method is 'GET' (or 'HEAD').
-        if (!count(module_implements('node_grants')) && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') && ($cid = _block_get_cache_id($block)) && ($cache = cache_get($cid, 'cache_block'))) {
-          $array = $cache->data;
-        }
-        else {
-          $array = module_invoke($block->module, 'block_view', $block->delta);
-          if (isset($cid)) {
-            cache_set($cid, $array, 'cache_block', CACHE_TEMPORARY);
-          }
+      // Try fetching the block from cache. Block caching is not compatible
+      // with node_access modules. We also preserve the submission of forms in
+      // blocks, by fetching from cache only if the request method is 'GET'
+      // (or 'HEAD').
+      if (!count(module_implements('node_grants')) && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') && ($cid = _block_get_cache_id($block)) && ($cache = cache_get($cid, 'cache_block'))) {
+        $array = $cache->data;
+      }
+      else {
+        $array = module_invoke($block->module, 'block_view', $block->delta);
+        if (isset($cid)) {
+          cache_set($cid, $array, 'cache_block', CACHE_TEMPORARY);
         }
+      }
 
-        if (isset($array) && is_array($array)) {
-          foreach ($array as $k => $v) {
-            $block->$k = $v;
-          }
+      if (isset($array) && is_array($array)) {
+        foreach ($array as $k => $v) {
+          $block->$k = $v;
         }
-        if (isset($block->content) && $block->content) {
-          // Override default block title if a custom display title is present.
-          if ($block->title) {
-            // Check plain here to allow module generated titles to keep any markup.
-            $block->subject = $block->title == '<none>' ? '' : check_plain($block->title);
-          }
-          if (!isset($block->subject)) {
-            $block->subject = '';
-          }
-          $region_blocks["{$block->module}_{$block->delta}"] = $block;
+      }
+      if (isset($block->content) && $block->content) {
+        // Override default block title if a custom display title is present.
+        if ($block->title) {
+          // Check plain here to allow module generated titles to keep any
+          // markup.
+          $block->subject = $block->title == '<none>' ? '' : check_plain($block->title);
+        }
+        if (!isset($block->subject)) {
+          $block->subject = '';
         }
+        $region_blocks["{$block->module}_{$block->delta}"] = $block;
       }
     }
   }
diff --git a/modules/block/block.test b/modules/block/block.test
index 9dcd199b6f7de3bac8c041f01ccb7bc504ce953b..73d256ac003b64024ac99d4c1c3b491e82c493d8 100644
--- a/modules/block/block.test
+++ b/modules/block/block.test
@@ -99,6 +99,37 @@ class BlockTestCase extends DrupalWebTestCase {
     $this->assertRaw('<h1>Full HTML</h1>', t('Box successfully being displayed using Full HTML.'));
   }
 
+  /**
+   * Test block visibility.
+   */
+  function testBlockVisibility() {
+    $block = array();
+    $block['title'] = 'Syndicate';
+    $block['module'] = 'node';
+    $block['delta'] = 'syndicate';
+
+    // Set the block to be hidden on any user path, and to be shown only to
+    // authenticated users.
+    $edit = array();
+    $edit['pages'] = 'user*';
+    $edit['roles[2]'] = TRUE;
+    $this->drupalPost('admin/build/block/configure/' . $block['module'] . '/' . $block['delta'], $edit, t('Save block'));
+
+    // Move block to the left sidebar.
+    $this->moveBlockToRegion($block, $this->regions[1]);
+
+    $this->drupalGet('');
+    $this->assertText('Syndicate', t('Block was displayed on the front page.'));
+
+    $this->drupalGet('user*');
+    $this->assertNoText('Syndicate', t('Block was not displayed according to block visibility rules.'));
+
+    // Confirm that the block is not displayed to anonymous users.
+    $this->drupalLogout();
+    $this->drupalGet('');
+    $this->assertNoText('Syndicate', t('Block was not displayed to anonymous users.'));
+  }
+
   /**
    * Test configuring and moving a module-define block to specific regions.
    */
diff --git a/modules/blog/blog.info b/modules/blog/blog.info
index 2d047f304c9557b27b6d6eb1f30ed687ebf9aded..938993c0e475abba5928e0ed5bba040c4de80947 100644
--- a/modules/blog/blog.info
+++ b/modules/blog/blog.info
@@ -7,3 +7,4 @@ version = VERSION
 core = 7.x
 files[] = blog.module
 files[] = blog.pages.inc
+files[] = blog.test
diff --git a/modules/blogapi/blogapi.info b/modules/blogapi/blogapi.info
index 257ffd25b9e7d74c2442e186c9d86df4e80516e9..87e365e5bc748e081ff258eaaef9c44f045dc45c 100644
--- a/modules/blogapi/blogapi.info
+++ b/modules/blogapi/blogapi.info
@@ -7,3 +7,4 @@ version = VERSION
 core = 7.x
 files[] = blogapi.module
 files[] = blogapi.install
+files[] = blogapi.test
diff --git a/modules/book/book.info b/modules/book/book.info
index e4445800590e8bb5166ff0302a9ea8e382af0574..b30b4a59eb4778607c269ca46dfad0d57c63c493 100644
--- a/modules/book/book.info
+++ b/modules/book/book.info
@@ -9,3 +9,4 @@ files[] = book.module
 files[] = book.admin.inc
 files[] = book.pages.inc
 files[] = book.install
+files[] = book.test
diff --git a/modules/comment/comment.info b/modules/comment/comment.info
index 76237130745cf6544042e44a69f275b70d94674a..71a62e3ed44b4f740bf4b83dd279ca670190e110 100644
--- a/modules/comment/comment.info
+++ b/modules/comment/comment.info
@@ -9,3 +9,4 @@ files[] = comment.module
 files[] = comment.admin.inc
 files[] = comment.pages.inc
 files[] = comment.install
+files[] = comment.test
diff --git a/modules/contact/contact.info b/modules/contact/contact.info
index 750e18176ba195e7f8b2f47b2661328d1ac62c9a..fda41ae0f524d781e7494e1fc54e5deb0b9308e8 100644
--- a/modules/contact/contact.info
+++ b/modules/contact/contact.info
@@ -8,3 +8,4 @@ files[] = contact.module
 files[] = contact.admin.inc
 files[] = contact.pages.inc
 files[] = contact.install
+files[] = contact.test
diff --git a/modules/dblog/dblog.info b/modules/dblog/dblog.info
index 5e9d284a73ab77bb546c94f1fac371555a4e71c5..6b9f82390c087641e32a09c64d37a54eeb95445c 100644
--- a/modules/dblog/dblog.info
+++ b/modules/dblog/dblog.info
@@ -7,3 +7,4 @@ core = 7.x
 files[] = dblog.module
 files[] = dblog.admin.inc
 files[] = dblog.install
+files[] = dblog.test
diff --git a/modules/field/field.info b/modules/field/field.info
index 76f6687b3734b88dd710b1fdab111cb7aec38d6b..9c06c176083bb70f1191c213ba3050235dbb620f 100644
--- a/modules/field/field.info
+++ b/modules/field/field.info
@@ -11,5 +11,6 @@ files[] = field.info.inc
 files[] = field.default.inc
 files[] = field.attach.inc
 files[] = field.form.inc
+files[] = field.test
 dependencies[] = field_sql_storage
 required = TRUE
diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.info b/modules/field/modules/field_sql_storage/field_sql_storage.info
index 4d0f4a575719cc9a995937a2b7b14770beff9188..5587fbe32c1c69eceb5da55ddbadb6d6e7c9c8c7 100644
--- a/modules/field/modules/field_sql_storage/field_sql_storage.info
+++ b/modules/field/modules/field_sql_storage/field_sql_storage.info
@@ -6,4 +6,5 @@ version = VERSION
 core = 7.x
 files[] = field_sql_storage.module
 files[] = field_sql_storage.install
+files[] = field_sql_storage.test
 required = TRUE
diff --git a/modules/field/modules/text/text.info b/modules/field/modules/text/text.info
index 328bfc3f8ec0231d09845cd11b2364058cf81e6e..17a6760254b8d81c319c629b78cc8f3bf8ee2955 100644
--- a/modules/field/modules/text/text.info
+++ b/modules/field/modules/text/text.info
@@ -4,4 +4,5 @@ description = Defines simple text field types.
 package = Core - fields
 version = VERSION
 core = 7.x
-files[]=text.module
+files[] = text.module
+files[] = text.test
diff --git a/modules/filter/filter.info b/modules/filter/filter.info
index 05d996a0b8cdfedc0186041d0f0895145da12f3b..ecd3de188fb7b5c21c8bd4387e8aa5a364f068bb 100644
--- a/modules/filter/filter.info
+++ b/modules/filter/filter.info
@@ -8,4 +8,5 @@ files[] = filter.module
 files[] = filter.admin.inc
 files[] = filter.pages.inc
 files[] = filter.install
+files[] = filter.test
 required = TRUE
diff --git a/modules/forum/forum.info b/modules/forum/forum.info
index 27c588bc792fcc4865a8499bebb48a19eb2ce868..ff4621a5f4c7aa03886625253138a953d34dfbef 100644
--- a/modules/forum/forum.info
+++ b/modules/forum/forum.info
@@ -10,3 +10,4 @@ files[] = forum.module
 files[] = forum.admin.inc
 files[] = forum.pages.inc
 files[] = forum.install
+files[] = forum.test
diff --git a/modules/help/help.info b/modules/help/help.info
index e5ece02c6fccd4ff64ae0c596f7fe75a4036439c..4c84b26219aedbd31c5611e267eb699d0d8d84c0 100644
--- a/modules/help/help.info
+++ b/modules/help/help.info
@@ -6,3 +6,4 @@ version = VERSION
 core = 7.x
 files[] = help.module
 files[] = help.admin.inc
+files[] = help.test
diff --git a/modules/locale/locale.info b/modules/locale/locale.info
index 2638c4960b3f75a3657d2c3f388d5a9707bbb742..02b86521d5d8c6e3d86552ece1023d4c88cbb720 100644
--- a/modules/locale/locale.info
+++ b/modules/locale/locale.info
@@ -6,3 +6,4 @@ version = VERSION
 core = 7.x
 files[] = locale.module
 files[] = locale.install
+files[] = locale.test
diff --git a/modules/menu/menu.info b/modules/menu/menu.info
index 31bd386e65f39e4f254fd05b2801152b4d8edcfc..f3db2a75b7e98b8cf951fcb3022952ecc8f61745 100644
--- a/modules/menu/menu.info
+++ b/modules/menu/menu.info
@@ -7,3 +7,4 @@ core = 7.x
 files[] = menu.module
 files[] = menu.admin.inc
 files[] = menu.install
+files[] = menu.test
diff --git a/modules/node/node.info b/modules/node/node.info
index 975b5dcc56863bdd7ba2a8d8ddea37e43b9e5fc4..2f62a99013b4135b7de7798c89f6ddf942148bb7 100644
--- a/modules/node/node.info
+++ b/modules/node/node.info
@@ -9,4 +9,5 @@ files[] = content_types.inc
 files[] = node.admin.inc
 files[] = node.pages.inc
 files[] = node.install
+files[] = node.test
 required = TRUE
diff --git a/modules/openid/openid.info b/modules/openid/openid.info
index 4d14448a0b73f23d7575b80aa55f91fb88e98f9a..9335fa8bac602077049da99944dd5c25e8be089c 100644
--- a/modules/openid/openid.info
+++ b/modules/openid/openid.info
@@ -9,3 +9,4 @@ files[] = openid.inc
 files[] = openid.pages.inc
 files[] = xrds.inc
 files[] = openid.install
+files[] = openid.test
diff --git a/modules/path/path.info b/modules/path/path.info
index 7453923d899a67e14eb6f149cc7e5b41677d2c28..ee05f40f0bcee05517409a7c158ad3b3bb08a9e4 100644
--- a/modules/path/path.info
+++ b/modules/path/path.info
@@ -6,3 +6,4 @@ version = VERSION
 core = 7.x
 files[] = path.module
 files[] = path.admin.inc
+files[] = path.test
diff --git a/modules/php/php.info b/modules/php/php.info
index e353bf9b8d56c4380b68fa22401238da0f20caf9..2f1588340393513bb8789aac4652a997e220ed16 100644
--- a/modules/php/php.info
+++ b/modules/php/php.info
@@ -6,3 +6,4 @@ version = VERSION
 core = 7.x
 files[] = php.module
 files[] = php.install
+files[] = php.test
diff --git a/modules/poll/poll.info b/modules/poll/poll.info
index 9aff7b502343066b431e9116f968c0d1f98a84d5..d59cc9b93f80f995e36676dff6c437585fe527a0 100644
--- a/modules/poll/poll.info
+++ b/modules/poll/poll.info
@@ -7,3 +7,4 @@ core = 7.x
 files[] = poll.module
 files[] = poll.pages.inc
 files[] = poll.install
+files[] = poll.test
diff --git a/modules/profile/profile.info b/modules/profile/profile.info
index 522284781f64a34896d7be84b07b86c6881e076b..7e6e511ec1d3b29f14df51287d44552b50f105eb 100644
--- a/modules/profile/profile.info
+++ b/modules/profile/profile.info
@@ -8,3 +8,4 @@ files[] = profile.module
 files[] = profile.admin.inc
 files[] = profile.pages.inc
 files[] = profile.install
+files[] = profile.test
diff --git a/modules/simpletest/simpletest.info b/modules/simpletest/simpletest.info
index 0fefa967d24433795ff52a791b7fe951ced99c7c..a90e5dd2c859ebc2711dabb7850c7ee244f4f649 100644
--- a/modules/simpletest/simpletest.info
+++ b/modules/simpletest/simpletest.info
@@ -7,3 +7,26 @@ core = 7.x
 files[] = simpletest.module
 files[] = simpletest.pages.inc
 files[] = simpletest.install
+files[] = simpletest.test
+files[] = drupal_web_test_case.php
+
+; Tests in tests directory.
+files[] = tests/actions.test
+files[] = tests/batch.test
+files[] = tests/bootstrap.test
+files[] = tests/cache.test
+files[] = tests/common.test
+files[] = tests/database_test.test
+files[] = tests/error.test
+files[] = tests/file.test
+files[] = tests/form.test
+files[] = tests/graph.test
+files[] = tests/image.test
+files[] = tests/menu.test
+files[] = tests/module.test
+files[] = tests/registry.test
+files[] = tests/schema.test
+files[] = tests/session.test
+files[] = tests/theme.test
+files[] = tests/unicode.test
+files[] = tests/xmlrpc.test
diff --git a/modules/simpletest/simpletest.module b/modules/simpletest/simpletest.module
index 1300c24169d60b04f7a5468f4fab18c53a656e45..630ddb3f3dac89630c797514dbe1c7547097ed66 100644
--- a/modules/simpletest/simpletest.module
+++ b/modules/simpletest/simpletest.module
@@ -139,9 +139,6 @@ function simpletest_run_tests($test_list, $reporter = 'drupal') {
  * Batch operation callback.
  */
 function _simpletest_batch_operation($test_list_init, $test_id, &$context) {
-  // Ensure that all classes are loaded before we unserialize some instances.
-  simpletest_get_all_tests();
-
   // Get working values.
   if (!isset($context['sandbox']['max'])) {
     // First iteration: initialize working values.
@@ -200,65 +197,94 @@ function _simpletest_batch_finished($success, $results, $operations, $elapsed) {
 }
 
 /**
- * Get a list of all of the tests.
+ * Get a list of all of the tests provided by the system.
+ *
+ * The list of test classes is loaded from the registry where it looks for
+ * files ending in ".test". Once loaded the test list is cached and stored in
+ * a static variable. In order to list tests provided by disabled modules
+ * hook_registry_files_alter() is used to forcefully add them to the registry.
  *
  * @return
- *   An array of tests, with the class name as the keys and the instantiated
- *   versions of the classes as the values.
+ *   An array of tests keyed with the groups specified in each of the tests
+ *   getInfo() method and then keyed by the test class. An example of the array
+ *   structure is provided below.
+ *
+ *   @code
+ *     $groups['Blog'] => array(
+ *       'BlogTestCase' => array(
+ *         'name' => 'Blog functionality',
+ *         'description' => 'Create, view, edit, delete, ...',
+ *         'group' => 'Blog',
+ *       ),
+ *     );
+ *   @endcode
+ * @see simpletest_registry_files_alter()
  */
-function simpletest_get_all_tests() {
-  static $classes;
-  if (!isset($classes)) {
-    require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'simpletest') . '/drupal_web_test_case.php';
-    $files = array();
-    foreach (array_keys(system_get_module_data()) as $module) {
-      $module_path = drupal_get_path('module', $module);
-      $test = $module_path . "/$module.test";
-      if (file_exists($test)) {
-        $files[] = $test;
-      }
+function simpletest_test_get_all() {
+  $groups = &drupal_static(__FUNCTION__);
+
+  if (!$groups) {
+    // Load test information from cache if available, otherwise retrieve the
+    // information from each tests getInfo() method.
+    if ($cache = cache_get('simpletest', 'cache')) {
+      $groups = $cache->data;
+    }
+    else {
+      // Select all clases in files ending with .test.
+      $classes = db_select('registry')
+        ->fields('registry', array('name'))
+        ->condition('type', 'class')
+        ->condition('filename', '%.test', 'LIKE')
+        ->execute();
 
-      $tests_directory = $module_path . '/tests';
-      if (is_dir($tests_directory)) {
-        foreach (file_scan_directory($tests_directory, '/\.test$/') as $file) {
-          $files[] = $file->filepath;
+      $groups = array();
+
+      // Check that each class has a getInfo() method and store the information
+      // in an array keyed with the group specified in the test information.
+      foreach ($classes as $class) {
+        $class = $class->name;
+        if (class_exists($class) && method_exists($class, 'getInfo')) {
+          // Valid test class, retrieve test information.
+          $info = call_user_func(array($class, 'getInfo'));
+
+          // Initialize test groups.
+          if (!isset($groups[$info['group']])) {
+            $groups[$info['group']] = array();
+          }
+          $groups[$info['group']][$class] = $info;
         }
       }
-    }
-
-    $existing_classes = get_declared_classes();
-    foreach ($files as $file) {
-      include_once DRUPAL_ROOT . '/' . $file;
-    }
-    $classes = array_values(array_diff(get_declared_classes(), $existing_classes));
-    foreach ($classes as $key => $class) {
-      if (!is_subclass_of($class, 'DrupalTestCase') || !method_exists($class, 'getInfo')) {
-        unset($classes[$key]);
+      // Sort the groups and tests within the groups by name.
+      uksort($groups, 'strnatcasecmp');
+      foreach ($groups as $group => &$tests) {
+        uksort($tests, 'strnatcasecmp');
       }
+
+      cache_set('simpletest', $groups);
     }
   }
-  if (count($classes) == 0) {
-    drupal_set_message('No test cases found.', 'error');
-    return FALSE;
-  }
-  return $classes;
+  return $groups;
 }
 
 /**
- * Categorize the tests into groups.
+ * Implementation of hook_registry_files_alter().
  *
- * @param $tests
- *   A list of tests from simpletest_get_all_tests.
- * @see simpletest_get_all_tests.
+ * Add the test files for disabled modules so that we get a list containing
+ * all the avialable tests.
  */
-function simpletest_categorize_tests($tests) {
-  $groups = array();
-  foreach ($tests as $test) {
-    $info = call_user_func(array($test, 'getInfo'));
-    $groups[$info['group']][$test] = $info;
+function simpletest_registry_files_alter(&$files, $modules) {
+  foreach ($modules as $module) {
+    // Only add test files for disabled modules, as enabled modules should
+    // already include any test files they provide.
+    if (!$module->status) {
+      $dir = $module->dir;
+      foreach ($module->info['files'] as $file) {
+        if (substr($file, -5) == '.test') {
+          $files["$dir/$file"] = array('module' => $module->name, 'weight' => $module->weight);
+        }
+      }
+    }
   }
-  uksort($groups, 'strnatcasecmp');
-  return $groups;
 }
 
 /**
diff --git a/modules/simpletest/simpletest.pages.inc b/modules/simpletest/simpletest.pages.inc
index 24a779348cf528ae5554d5b12b139f4bf6b44367..2df1765fa76d84322c5e7a2e9240acce7dd1977d 100644
--- a/modules/simpletest/simpletest.pages.inc
+++ b/modules/simpletest/simpletest.pages.inc
@@ -12,10 +12,6 @@
 function simpletest_test_form() {
   $form = array();
 
-  // Categorize the tests for display.
-  $uncategorized_tests = simpletest_get_all_tests();
-  $tests = simpletest_categorize_tests($uncategorized_tests);
-
   $form['tests'] = array(
     '#type' => 'fieldset',
     '#title' => t('Tests'),
@@ -27,13 +23,14 @@ function simpletest_test_form() {
   );
 
   // Generate the list of tests arranged by group.
-  foreach ($tests as $group_name => $test_group) {
-    $form['tests']['table'][$group_name] = array(
+  $groups = simpletest_test_get_all();
+  foreach ($groups as $group => $tests) {
+    $form['tests']['table'][$group] = array(
       '#collapsed' => TRUE,
     );
 
-    foreach ($test_group as $class => $info) {
-      $form['tests']['table'][$group_name][$class] = array(
+    foreach ($tests as $class => $info) {
+      $form['tests']['table'][$group][$class] = array(
         '#type' => 'checkbox',
         '#title' => $info['name'],
         '#description' => $info['description'],
@@ -166,9 +163,6 @@ function theme_simpletest_test_table($table) {
  * Run selected tests.
  */
 function simpletest_test_form_submit($form, &$form_state) {
-  // Ensure that all classes are loaded before we create instances to get test information and run.
-  simpletest_get_all_tests();
-
   // Get list of tests.
   $tests_list = array();
   foreach ($form_state['values'] as $class_name => $value) {
@@ -200,7 +194,6 @@ function simpletest_result_form(&$form_state, $test_id) {
 
   // Load all classes and include CSS.
   drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css');
-  simpletest_get_all_tests();
 
   // Keep track of which test cases passed or failed.
   $filter = array(
diff --git a/modules/simpletest/tests/graph.test b/modules/simpletest/tests/graph.test
index a5ed9314b7d98fdb6007173b970a8fd4e183df5a..b3270f843a9bfcb76e345dae03b54dcd88be2848 100644
--- a/modules/simpletest/tests/graph.test
+++ b/modules/simpletest/tests/graph.test
@@ -23,7 +23,7 @@ class GraphUnitTest extends DrupalUnitTestCase {
    */
   function testDepthFirstSearch() {
     // Provoke the inclusion of graph.inc.
-    drupal_function_exists('drupal_depth_first_search');
+    require_once 'includes/graph.inc';
 
     // The sample graph used is:
     // 1 --> 2 --> 3     5 ---> 6
diff --git a/modules/statistics/statistics.info b/modules/statistics/statistics.info
index aaeb7d0152b1fdf7c64a4fd057d12f3d84f2a3cb..a3a509124d5ece51a45488fc787646cdbcb42770 100644
--- a/modules/statistics/statistics.info
+++ b/modules/statistics/statistics.info
@@ -8,3 +8,4 @@ files[] = statistics.module
 files[] = statistics.admin.inc
 files[] = statistics.pages.inc
 files[] = statistics.install
+files[] = statistics.test
diff --git a/modules/syslog/syslog.info b/modules/syslog/syslog.info
index 55ff66b26d792d1ba08d8fdf85825810d01e59ec..ff6246aee156718ab88558e801e5848ef7ceb0c8 100644
--- a/modules/syslog/syslog.info
+++ b/modules/syslog/syslog.info
@@ -5,3 +5,4 @@ package = Core
 version = VERSION
 core = 7.x
 files[] = syslog.module
+files[] = syslog.test
diff --git a/modules/system/system.api.php b/modules/system/system.api.php
index 0efcedbc3536136a8e8f1da5fd8b8c23f27380da..1349a7b8fba15226dc00b98b4514fdebfa42c48b 100644
--- a/modules/system/system.api.php
+++ b/modules/system/system.api.php
@@ -1700,6 +1700,59 @@ function hook_disable() {
   mymodule_cache_rebuild();
 }
 
+/**
+ * Perform necessary alterations to the list of files parsed by the registry.
+ *
+ * Modules can manually modify the list of files before the registry parses
+ * them. The $modules array provides the .info file information, which includes
+ * the list of files registered to each module. Any files in the list can then
+ * be added to the list of files that the registry will parse, or modify
+ * attributes of a file.
+ *
+ * A necessary alteration made by the core SimpleTest module is to force .test
+ * files provided by disabled modules into the list of files parsed by the
+ * registry.
+ *
+ * @param $files
+ *   List of files to be parsed by the registry. The list will contain
+ *   files found in each enabled module's info file and the core includes
+ *   directory. The array is keyed by the file path and contains an array of
+ *   the related module's name and weight as used internally by
+ *   _registry_rebuild() and related functions.
+ *
+ *   For example:
+ *   @code
+ *     $files["modules/system/system.module"] = array(
+ *       'module' => 'system',
+ *       'weight' => 0,
+ *     );
+ *   @endcode
+ * @param $modules
+ *   List of all the modules provided as returned by drupal_system_listing().
+ *   The list also contains the .info file information in the property 'info'.
+ *   An additional 'dir' property has been added to the module information
+ *   which provides the path to the directory in which the module resides. The
+ *   example shows how to take advantage of the property both properties.
+ *
+ * @see _registry_rebuild()
+ * @see drupal_system_listing()
+ * @see simpletest_test_get_all()
+ */
+function hook_registry_files_alter(&$files, $module_cache) {
+  foreach ($modules as $module) {
+    // Only add test files for disabled modules, as enabled modules should
+    // already include any test files they provide.
+    if (!$module->status) {
+      $dir = $module->dir;
+      foreach ($module->info['files'] as $file) {
+        if (substr($file, -5) == '.test') {
+          $files["$dir/$file"] = array('module' => $module->name, 'weight' => $module->weight);
+        }
+      }
+    }
+  }
+}
+
 /**
  * @} End of "addtogroup hooks".
  */
diff --git a/modules/system/system.info b/modules/system/system.info
index 0afcdaf9a20e4b3fabe0273b6a7720c7f93aead5..9c93a096094e6bc331c1ed54d1d515a638c5034a 100644
--- a/modules/system/system.info
+++ b/modules/system/system.info
@@ -9,4 +9,5 @@ files[] = system.admin.inc
 files[] = system.queue.inc
 files[] = image.gd.inc
 files[] = system.install
+files[] = system.test
 required = TRUE
diff --git a/modules/taxonomy/taxonomy.info b/modules/taxonomy/taxonomy.info
index 4d05fc442918fee836ed9b8010a6fc317dbded72..35dfa1480566071609aa862c981612c46ac5aa47 100644
--- a/modules/taxonomy/taxonomy.info
+++ b/modules/taxonomy/taxonomy.info
@@ -8,3 +8,4 @@ files[] = taxonomy.module
 files[] = taxonomy.admin.inc
 files[] = taxonomy.pages.inc
 files[] = taxonomy.install
+files[] = taxonomy.test
diff --git a/modules/tracker/tracker.info b/modules/tracker/tracker.info
index b26cff0556eddd104a206d3174dacb9a0d1aefc0..f70550d371f08a14944c15b57b9cb618e3a97c4e 100644
--- a/modules/tracker/tracker.info
+++ b/modules/tracker/tracker.info
@@ -7,3 +7,4 @@ version = VERSION
 core = 7.x
 files[] = tracker.module
 files[] = tracker.pages.inc
+files[] = tracker.test
diff --git a/modules/translation/translation.info b/modules/translation/translation.info
index 63c2c1c63a385020b487ae007195cde5e949b531..3f6841c2a930ac46a14e44abaf6ea0a70568f5a8 100644
--- a/modules/translation/translation.info
+++ b/modules/translation/translation.info
@@ -7,3 +7,4 @@ version = VERSION
 core = 7.x
 files[] = translation.module
 files[] = translation.pages.inc
+files[] = translation.test
diff --git a/modules/trigger/trigger.info b/modules/trigger/trigger.info
index 9ba7439f4675144169c0672dca4b36bba60ad19e..e097d59deedbbba612ba47896f5366eb0bf33386 100644
--- a/modules/trigger/trigger.info
+++ b/modules/trigger/trigger.info
@@ -7,3 +7,4 @@ core = 7.x
 files[] = trigger.module
 files[] = trigger.admin.inc
 files[] = trigger.install
+files[] = trigger.test
diff --git a/modules/upload/upload.info b/modules/upload/upload.info
index 92d04e09a70b7b67fe73675a09e671248c335042..d5039f5214a0039a42474493ba7151d29f202a85 100644
--- a/modules/upload/upload.info
+++ b/modules/upload/upload.info
@@ -7,3 +7,4 @@ core = 7.x
 files[] = upload.module
 files[] = upload.admin.inc
 files[] = upload.install
+files[] = upload.test
diff --git a/modules/user/user.info b/modules/user/user.info
index 98ef2dfb002b16e9463d7015fa8278e91a82c442..d4c24fa77220d9c57288c9ade1ccc893c76e4ac6 100644
--- a/modules/user/user.info
+++ b/modules/user/user.info
@@ -8,4 +8,5 @@ files[] = user.module
 files[] = user.admin.inc
 files[] = user.pages.inc
 files[] = user.install
+files[] = user.test
 required = TRUE
diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh
index 795c67886003f69dad780e18411d502c8099108d..ada07c75c9e394cb3dde83ecefd939d2e6e15386 100755
--- a/scripts/run-tests.sh
+++ b/scripts/run-tests.sh
@@ -44,8 +44,11 @@
 }
 
 // Load SimpleTest files.
-$all_tests = simpletest_get_all_tests();
-$groups = simpletest_categorize_tests($all_tests);
+$groups = simpletest_test_get_all();
+$all_tests = array();
+foreach ($groups as $group => $tests) {
+  $all_tests = array_merge($all_tests, array_keys($tests));
+}
 $test_list = array();
 
 if ($args['list']) {
@@ -54,8 +57,8 @@
   echo   "-------------------------------\n\n";
   foreach ($groups as $group => $tests) {
     echo $group . "\n";
-    foreach ($tests as $class_name => $info) {
-      echo " - " . $info['name'] . ' (' . $class_name . ')' . "\n";
+    foreach ($tests as $class => $info) {
+      echo " - " . $info['name'] . ' (' . $class . ')' . "\n";
     }
   }
   exit;
@@ -339,7 +342,6 @@ function simpletest_script_execute_batch() {
  * Run a single test (assume a Drupal bootstrapped environment).
  */
 function simpletest_script_run_one_test($test_id, $test_class) {
-  simpletest_get_all_tests();
   $test = new $test_class($test_id);
   $test->run();
   $info = $test->getInfo();