diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php
index 694880b..2c32917 100644
--- a/modules/simpletest/drupal_web_test_case.php
+++ b/modules/simpletest/drupal_web_test_case.php
@@ -447,7 +447,8 @@ abstract class DrupalTestCase {
    */
   protected function verbose($message) {
     if ($id = simpletest_verbose($message)) {
-      $url = file_create_url($this->originalFileDirectory . '/simpletest/verbose/' . get_class($this) . '-' . $id . '.html');
+      $class_safe = str_replace('\\', '-', get_class($this));
+      $url = file_create_url($this->originalFileDirectory . '/simpletest/verbose/' . $class_safe . '-' . $id . '.html');
       $this->error(l(t('Verbose message'), $url, array('attributes' => array('target' => '_blank'))), 'User notice');
     }
   }
@@ -466,7 +467,8 @@ abstract class DrupalTestCase {
    */
   public function run(array $methods = array()) {
     // Initialize verbose debugging.
-    simpletest_verbose(NULL, variable_get('file_public_path', conf_path() . '/files'), get_class($this));
+    $class = get_class($this);
+    simpletest_verbose(NULL, variable_get('file_public_path', conf_path() . '/files'), str_replace('\\', '-', $class));
 
     // HTTP auth settings (<username>:<password>) for the simpletest browser
     // when sending requests to the test site.
@@ -478,7 +480,6 @@ abstract class DrupalTestCase {
     }
 
     set_error_handler(array($this, 'errorHandler'));
-    $class = get_class($this);
     // Iterate through all the methods in this class, unless a specific list of
     // methods to run was passed.
     $class_methods = get_class_methods($class);
diff --git a/modules/simpletest/lib/Drupal/simpletest/Tests/PSR0WebTest.php b/modules/simpletest/lib/Drupal/simpletest/Tests/PSR0WebTest.php
new file mode 100644
index 0000000..7e7f7c2
--- /dev/null
+++ b/modules/simpletest/lib/Drupal/simpletest/Tests/PSR0WebTest.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Drupal\simpletest\Tests;
+
+class PSR0WebTest extends \DrupalWebTestCase {
+
+  /**
+   * Use the Testing profile.
+   *
+   * The Testing profile contains drupal_system_listing_compatible_test.test,
+   * which attempts to:
+   * - run tests using the Minimal profile (which does not contain the
+   *   drupal_system_listing_compatible_test.module)
+   * - but still install the drupal_system_listing_compatible_test.module
+   *   contained in the Testing profile.
+   *
+   * @see DrupalSystemListingCompatibleTestCase
+   */
+  protected $profile = 'testing';
+
+  public static function getInfo() {
+    return array(
+      'name' => 'PSR0 web test',
+      'description' => 'Test PSR0 test classes.',
+      'group' => 'SimpleTest',
+    );
+  }
+
+  function testArithmetics() {
+    $this->assert(1 + 1 == 2, '1 + 1 == 2');
+  }
+}
diff --git a/modules/simpletest/simpletest.module b/modules/simpletest/simpletest.module
index f825755..2b08d64 100644
--- a/modules/simpletest/simpletest.module
+++ b/modules/simpletest/simpletest.module
@@ -157,6 +157,7 @@ function simpletest_run_tests($test_list, $reporter = 'drupal') {
  * Batch operation callback.
  */
 function _simpletest_batch_operation($test_list_init, $test_id, &$context) {
+  simpletest_classloader_register();
   // Get working values.
   if (!isset($context['sandbox']['max'])) {
     // First iteration: initialize working values.
@@ -289,6 +290,9 @@ function simpletest_log_read($test_id, $prefix, $test_class, $during_test = FALS
  * 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.
  *
+ * PSR-0 classes are found by searching the designated directory for each module
+ * for files matching the PSR-0 standard.
+ *
  * @return
  *   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
@@ -309,6 +313,9 @@ function simpletest_test_get_all() {
   $groups = &drupal_static(__FUNCTION__);
 
   if (!$groups) {
+    // Register a simple class loader for PSR-0 test classes.
+    simpletest_classloader_register();
+
     // Load test information from cache if available, otherwise retrieve the
     // information from each tests getInfo() method.
     if ($cache = cache_get('simpletest', 'cache')) {
@@ -318,6 +325,12 @@ function simpletest_test_get_all() {
       // Select all clases in files ending with .test.
       $classes = db_query("SELECT name FROM {registry} WHERE type = :type AND filename LIKE :name", array(':type' => 'class', ':name' => '%.test'))->fetchCol();
 
+      if (version_compare(PHP_VERSION, '5.3') < 0) {
+        // Also discover PSR-0 test classes.
+        $classes_psr0 = _simpletest_test_get_all_psr0();
+        $classes = array_merge($classes, $classes_psr0);
+      }
+
       // Check that each class has a getInfo() method and store the information
       // in an array keyed with the group specified in the test information.
       $groups = array();
@@ -354,6 +367,144 @@ function simpletest_test_get_all() {
 }
 
 /**
+ * Discover all PSR-0 classes in the Tests namespace of all available extensions
+ * This does include modules, themes and install profiles, enabled or not.
+ *
+ * @return array
+ *   Numeric-keyed array of class names of discovered test classes.
+ */
+function _simpletest_class_get_all_psr0() {
+
+  // Loop over all extension directories.
+  $system_list = db_query("SELECT name, filename FROM {system}")->fetchAllKeyed();
+  foreach ($system_list as $extension => $filename) {
+
+    // Build directory in which the PSR-0 test files for this extension would reside.
+    $tests_dir = DRUPAL_ROOT . '/' . dirname($filename) . '/lib/Drupal/' . $extension . '/Tests';
+    $tests_dir_strlen = strlen($tests_dir);
+
+    // Scan it for test files if it exists.
+    if (is_dir($tests_dir)) {
+      $files = file_scan_directory($tests_dir, '/.*\.php/');
+      if (!empty($files)) {
+
+        // Determine the namespace for tests in this extension.
+        $tests_namespace = 'Drupal\\' . $extension . '\\Tests\\';
+
+        // Loop over all supposed test files that were discovered.
+        foreach ($files as $file) {
+
+          // Confirm that the file is indeed within the tests dir.
+          if (substr($file->uri, 0, $tests_dir_strlen + 1) !== $tests_dir . '/') {
+            continue;
+          }
+
+          // We are going to convert the file name into the namespaced class
+          // name.
+          // This is a bit ambiguous due to the role of underscores in PSR-0.
+          // One file path can potentially resolve to multiple class names.
+          // To make our lives easier, we ignore any classes that contain an
+          // underscore after the last namespace separator. But to be safe, we
+          // check if the file does indeed define the class that we expect.
+
+          // Extract the part of the filename after the "/Tests/", but before
+          // the ".php", and replace directory separators with namespace
+          // separators.
+          $suffix = str_replace('/', '\\', substr($file->uri, $tests_dir_strlen + 1, -4));
+
+          // Determine the full class name that we primarily expect.
+          $class = $tests_namespace . $suffix;
+
+          // Step 1: Include the file, if it is not yet included.
+          // Note: The file inclusion is not an issue for performance or memory,
+          // because the file would be included anyway later in the request.
+          include_once $file->uri;
+
+          // Step 2: Check with class_exists(*, FALSE) to skip the class loader.
+          if (class_exists($class, FALSE)) {
+
+            // The file does indeed define the class we expect, so we register it.
+            $classes[] = $class;
+          }
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Register a simple class loader that can find D8-style PSR-0 test classes.
+ *
+ * Other PSR-0 class loading can happen in contrib, but those contrib class
+ * loader modules will not be enabled when testbot runs. So we need to do this
+ * one in core.
+ */
+function simpletest_classloader_register() {
+
+  // Prevent duplicate classloader registration.
+  static $first_run = TRUE;
+  if (!$first_run) {
+    return;
+  }
+  $first_run = FALSE;
+
+  // Only register class loading if we ar eon PHP 5.3 or higher.
+  if (version_compare(PHP_VERSION, '5.3') < 0) {
+    spl_autoload_register('_simpletest_autoload_psr0');
+  }
+}
+
+/**
+ * Autoload callback to find PSR-0 test classes.
+ *
+ * This will only work on classes where the namespace is of the pattern
+ *   "Drupal\$extension\Tests\.."
+ */
+function _simpletest_autoload_psr0($class) {
+
+  // Static cache for extension paths.
+  // This cache is lazily filled as soon as it is needed.
+  static $extensions;
+
+  // Check that the first namespace fragment is "Drupal\"
+  if (substr($class, 0, 7) === 'Drupal\\') {
+    // Find the position of the second namespace separator.
+    $pos = strpos($class, '\\', 7);
+    // Check that the third namespace fragment is "\Tests\".
+    if (substr($class, $pos, 7) === '\\Tests\\') {
+
+      // Extract the second namespace fragment, which we expect to be the
+      // extension name.
+      $extension = substr($class, 7, $pos - 7);
+
+      // Lazy-load the extension paths, both enabled and disabled.
+      if (!isset($extensions)) {
+        $extensions = db_query("SELECT name, filename FROM {system}")->fetchAllKeyed();
+      }
+
+      // Check if the second namespace fragment is a known extension name.
+      if (isset($extensions[$extension])) {
+
+        // Split the class into namespace and classname.
+        $nspos = strrpos($class, '\\');
+        $namespace = substr($class, 0, $nspos);
+        $classname = substr($class, $nspos + 1);
+
+        // Build the filepath where we expect the class to be defined.
+        $path = dirname($extensions[$extension]) . '/lib/' .
+          str_replace('\\', '/', $namespace) . '/' .
+          str_replace('_', '/', $classname) . '.php';
+
+        // Include the file, if it does exist.
+        if (file_exists($path)) {
+          include $path;
+        }
+      }
+    }
+  }
+}
+
+/**
  * Implements hook_registry_files_alter().
  *
  * Add the test files for disabled modules so that we get a list containing
diff --git a/modules/simpletest/simpletest.pages.inc b/modules/simpletest/simpletest.pages.inc
index d1a7e4a..3127459 100644
--- a/modules/simpletest/simpletest.pages.inc
+++ b/modules/simpletest/simpletest.pages.inc
@@ -181,6 +181,7 @@ function theme_simpletest_test_table($variables) {
  * Run selected tests.
  */
 function simpletest_test_form_submit($form, &$form_state) {
+  simpletest_classloader_register();
   // Get list of tests.
   $tests_list = array();
   foreach ($form_state['values'] as $class_name => $value) {
@@ -233,6 +234,8 @@ function simpletest_result_form($form, &$form_state, $test_id) {
     '#debug' => 0,
   );
 
+  simpletest_classloader_register();
+
   // Cycle through each test group.
   $header = array(t('Message'), t('Group'), t('Filename'), t('Line'), t('Function'), array('colspan' => 2, 'data' => t('Status')));
   $form['result']['results'] = array();
diff --git a/modules/simpletest/simpletest.test b/modules/simpletest/simpletest.test
index c67b004..2a55fd5 100644
--- a/modules/simpletest/simpletest.test
+++ b/modules/simpletest/simpletest.test
@@ -655,3 +655,76 @@ class SimpleTestOtherInstallationProfileModuleTestsTestCase extends DrupalWebTes
     $this->assertNoText('Installation profile module tests helper');
   }
 }
+
+/**
+ * Verifies that tests in other installation profiles are not found.
+ *
+ * @see SimpleTestInstallationProfileModuleTestsTestCase
+ */
+class SimpleTestDiscoveryTestCase extends DrupalWebTestCase {
+  /**
+   * Use the Testing profile.
+   *
+   * The Testing profile contains drupal_system_listing_compatible_test.test,
+   * which attempts to:
+   * - run tests using the Minimal profile (which does not contain the
+   *   drupal_system_listing_compatible_test.module)
+   * - but still install the drupal_system_listing_compatible_test.module
+   *   contained in the Testing profile.
+   *
+   * @see DrupalSystemListingCompatibleTestCase
+   */
+  protected $profile = 'testing';
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Discovery of test classes',
+      'description' => 'Verifies that tests classes are discovered and can be autoloaded (class_exists).',
+      'group' => 'SimpleTest',
+    );
+  }
+
+  function setUp() {
+    parent::setUp(array('simpletest'));
+
+    $this->admin_user = $this->drupalCreateUser(array('administer unit tests'));
+    $this->drupalLogin($this->admin_user);
+  }
+
+  /**
+   * Tests existence of test cases.
+   */
+  function testDiscovery() {
+    $this->drupalGet('admin/config/development/testing');
+    // Tests within enabled modules.
+    // (without these, this test wouldn't happen in the first place, so this is
+    // a bit pointless. We still run it for proof-of-concept.)
+    // This one is defined in system module.
+    $this->assertText('Drupal error handlers');
+    // This one is defined in simpletest module.
+    $this->assertText('Discovery of test classes');
+    // Tests within disabled modules.
+    if (version_compare(PHP_VERSION, '5.3') < 0) {
+      // Don't expect PSR-0 tests to be discovered on older PHP versions.
+      return;
+    }
+    // This one is provided by simpletest itself via PSR-0.
+    $this->assertText('PSR0 web test');
+    $this->assertText('PSR0 example test: PSR-0 in disabled modules.');
+    $this->assertText('PSR0 example test: PSR-0 in nested subfolders.');
+    // Assert that the underscore test is ignored by the discovery.
+    $this->assertNoText('PSR0 example test: PSR-0 plus underscore.');
+
+    // Test each test individually.
+    foreach (array(
+      'Drupal\\psr_0_test\\Tests\\ExampleTest',
+      'Drupal\\psr_0_test\\Tests\\Nested\\NestedExampleTest',
+    ) as $class) {
+      $this->drupalGet('admin/config/development/testing');
+      $edit = array($class => TRUE);
+      $this->drupalPost(NULL, $edit, t('Run tests'));
+      $this->assertText('The test run finished');
+      $this->assertText('1 pass, 0 fails, and 0 exceptions');
+    }
+  }
+}
diff --git a/modules/simpletest/tests/psr_0_test/lib/Drupal/psr_0_test/Tests/ExampleTest.php b/modules/simpletest/tests/psr_0_test/lib/Drupal/psr_0_test/Tests/ExampleTest.php
new file mode 100644
index 0000000..af8126f
--- /dev/null
+++ b/modules/simpletest/tests/psr_0_test/lib/Drupal/psr_0_test/Tests/ExampleTest.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Drupal\psr_0_test\Tests;
+
+class ExampleTest extends \DrupalWebTestCase {
+
+  /**
+   * Use the Testing profile.
+   *
+   * The Testing profile contains drupal_system_listing_compatible_test.test,
+   * which attempts to:
+   * - run tests using the Minimal profile (which does not contain the
+   *   drupal_system_listing_compatible_test.module)
+   * - but still install the drupal_system_listing_compatible_test.module
+   *   contained in the Testing profile.
+   *
+   * @see DrupalSystemListingCompatibleTestCase
+   */
+  protected $profile = 'testing';
+
+  public static function getInfo() {
+    return array(
+      'name' => 'PSR0 example test: PSR-0 in disabled modules.',
+      'description' => 'Test PSR-0 classes.',
+      'group' => 'SimpleTest',
+    );
+  }
+
+  function testArithmetics() {
+    $this->assert(1 + 1 == 2, '1 + 1 == 2');
+  }
+}
diff --git a/modules/simpletest/tests/psr_0_test/lib/Drupal/psr_0_test/Tests/Nested/NestedExampleTest.php b/modules/simpletest/tests/psr_0_test/lib/Drupal/psr_0_test/Tests/Nested/NestedExampleTest.php
new file mode 100644
index 0000000..8485067
--- /dev/null
+++ b/modules/simpletest/tests/psr_0_test/lib/Drupal/psr_0_test/Tests/Nested/NestedExampleTest.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Drupal\psr_0_test\Tests\Nested;
+
+class NestedExampleTest extends \DrupalWebTestCase {
+
+  /**
+   * Use the Testing profile.
+   *
+   * The Testing profile contains drupal_system_listing_compatible_test.test,
+   * which attempts to:
+   * - run tests using the Minimal profile (which does not contain the
+   *   drupal_system_listing_compatible_test.module)
+   * - but still install the drupal_system_listing_compatible_test.module
+   *   contained in the Testing profile.
+   *
+   * @see DrupalSystemListingCompatibleTestCase
+   */
+  protected $profile = 'testing';
+
+  public static function getInfo() {
+    return array(
+      'name' => 'PSR0 example test: PSR-0 in nested subfolders.',
+      'description' => 'Test PSR-0 classes.',
+      'group' => 'SimpleTest',
+    );
+  }
+
+  function testArithmetics() {
+    $this->assert(1 + 1 == 2, '1 + 1 == 2');
+  }
+}
diff --git a/modules/simpletest/tests/psr_0_test/lib/Drupal/psr_0_test/Tests/Nested/Underscore/UnderscoreExampleTest.php b/modules/simpletest/tests/psr_0_test/lib/Drupal/psr_0_test/Tests/Nested/Underscore/UnderscoreExampleTest.php
new file mode 100644
index 0000000..9731ed4
--- /dev/null
+++ b/modules/simpletest/tests/psr_0_test/lib/Drupal/psr_0_test/Tests/Nested/Underscore/UnderscoreExampleTest.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Drupal\psr_0_test\Tests\Nested;
+
+class Underscore_UnderscoreExampleTest extends \DrupalWebTestCase {
+
+  /**
+   * Use the Testing profile.
+   *
+   * The Testing profile contains drupal_system_listing_compatible_test.test,
+   * which attempts to:
+   * - run tests using the Minimal profile (which does not contain the
+   *   drupal_system_listing_compatible_test.module)
+   * - but still install the drupal_system_listing_compatible_test.module
+   *   contained in the Testing profile.
+   *
+   * @see DrupalSystemListingCompatibleTestCase
+   */
+  protected $profile = 'testing';
+
+  public static function getInfo() {
+    return array(
+      'name' => 'PSR0 example test: PSR-0 plus underscore.',
+      'description' => 'Test PSR-0 classes.',
+      'group' => 'SimpleTest',
+    );
+  }
+
+  function testArithmetics() {
+    $this->assert(1 + 1 == 2, '1 + 1 == 2');
+  }
+}
diff --git a/modules/simpletest/tests/psr_0_test/psr_0_test.info b/modules/simpletest/tests/psr_0_test/psr_0_test.info
new file mode 100644
index 0000000..b2b2c7e
--- /dev/null
+++ b/modules/simpletest/tests/psr_0_test/psr_0_test.info
@@ -0,0 +1,3 @@
+name = PSR-0 Test cases
+description = Test classes to be discovered by simpletest.
+core = 7.x
diff --git a/modules/simpletest/tests/psr_0_test/psr_0_test.module b/modules/simpletest/tests/psr_0_test/psr_0_test.module
new file mode 100644
index 0000000..b3d9bbc
--- /dev/null
+++ b/modules/simpletest/tests/psr_0_test/psr_0_test.module
@@ -0,0 +1 @@
+<?php
diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh
index 3345015..189d7f2 100755
--- a/scripts/run-tests.sh
+++ b/scripts/run-tests.sh
@@ -362,6 +362,8 @@ function simpletest_script_run_one_test($test_id, $test_class) {
     // Bootstrap Drupal.
     drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
 
+    simpletest_classloader_register();
+
     $test = new $test_class($test_id);
     $test->run();
     $info = $test->getInfo();
@@ -395,7 +397,7 @@ function simpletest_script_command($test_id, $test_class) {
   if ($args['color']) {
     $command .= ' --color';
   }
-  $command .= " --php " . escapeshellarg($php) . " --test-id $test_id --execute-test $test_class";
+  $command .= " --php " . escapeshellarg($php) . " --test-id $test_id --execute-test " . escapeshellarg($test_class);
   return $command;
 }
 
