diff --git a/core/modules/simpletest/simpletest.api.php b/core/modules/simpletest/simpletest.api.php index dad9a39..e5251b7 100644 --- a/core/modules/simpletest/simpletest.api.php +++ b/core/modules/simpletest/simpletest.api.php @@ -14,9 +14,9 @@ * Alter the list of tests. * * @param $groups - * A two dimension array, the first key is the test group (as defined in - * getInfo) the second is the name of the class and the value is the return - * value of the getInfo method. + * A two dimensional array, the first key is the test group, the second is the + * name of the test class, and the value is in associative array containing + * 'name', 'description', 'group', and 'requires' keys. */ function hook_simpletest_alter(&$groups) { // An alternative session handler module would not want to run the original diff --git a/core/modules/simpletest/simpletest.module b/core/modules/simpletest/simpletest.module index 1d70f35..1654c3a 100644 --- a/core/modules/simpletest/simpletest.module +++ b/core/modules/simpletest/simpletest.module @@ -5,6 +5,7 @@ use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\Core\Render\Element; use Drupal\simpletest\TestBase; +use Drupal\simpletest\TestDiscovery; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Process\PhpExecutableFinder; @@ -88,40 +89,34 @@ function _simpletest_format_summary_line($summary) { * * @param $test_list * List of tests to run. - * @param $reporter - * Which reporter to use. Allowed values are: text, xml, html and drupal, - * drupal being the default. * * @return string * The test ID. */ -function simpletest_run_tests($test_list, $reporter = 'drupal') { +function simpletest_run_tests($test_list) { $test_id = db_insert('simpletest_test_id') ->useDefaults(array('test_id')) ->execute(); - $phpunit_tests = isset($test_list['UnitTest']) ? $test_list['UnitTest'] : array(); - if ($phpunit_tests) { + if (!empty($test_list['phpunit'])) { $phpunit_results = simpletest_run_phpunit_tests($test_id, $phpunit_tests); simpletest_process_phpunit_results($phpunit_results); } - if (!array_key_exists('WebTest', $test_list) || empty($test_list['WebTest'])) { - // Early return if there are no WebTests to run. + // Early return if there are no further tests to run. + if (empty($test_list['simpletest'])) { return $test_id; } - // Contine with SimpleTests only. - $test_list = $test_list['WebTest']; + // Continue with SimpleTests only. + $test_list = $test_list['simpletest']; // Clear out the previous verbose files. file_unmanaged_delete_recursive('public://simpletest/verbose'); // Get the info for the first test being run. - $first_test = array_shift($test_list); - $first_instance = new $first_test(); - array_unshift($test_list, $first_test); - $info = $first_instance->getInfo(); + $first_test = reset($test_list); + $info = TestDiscovery::getTestInfo(new \ReflectionClass($first_test)); $batch = array( 'title' => t('Running tests'), @@ -295,7 +290,7 @@ function _simpletest_batch_operation($test_list_init, $test_id, &$context) { $test = new $test_class($test_id); $test->run(); $size = count($test_list); - $info = $test->getInfo(); + $info = TestDiscovery::getTestInfo(new \ReflectionClass($test)); \Drupal::moduleHandler()->invokeAll('test_finished', array($test->results)); @@ -422,8 +417,8 @@ function simpletest_log_read($test_id, $database_prefix, $test_class) { * returned. * * @return array[] - * An array of tests keyed with the groups specified in each of the tests' - * getInfo() methods and then keyed by the test classes. For example: + * An array of tests keyed with the groups, and then keyed by test classes. + * For example: * @code * $groups['Block'] => array( * 'BlockTestCase' => array( @@ -603,59 +598,6 @@ function simpletest_mail_alter(&$message) { } /** - * Gets PHPUnit classes. - * - * @param string $module - * Name of a module. If set then only tests belonging to this module is - * returned. - * - * @return array - * Returns an array of test classes. - * - * @throws \RuntimeException - * This is thrown when anything is wrong with a test. - */ -function simpletest_phpunit_get_available_tests($module = NULL) { - // Try to load the class names array from cache. - $cid = 'simpletest_phpunit:' . $module; - if ($cache = \Drupal::cache()->get($cid)) { - $test_classes = $cache->data; - } - else { - if ($module) { - $prefix = 'Drupal\\' . $module . '\\'; - $n = strlen($prefix); - } - // If there was no cached data available we have to find the tests. - // Load the PHPUnit configuration file, which tells us where to find the - // tests. - $phpunit_config = simpletest_phpunit_configuration_filepath(); - $configuration = PHPUnit_Util_Configuration::getInstance($phpunit_config); - // Find all the tests and get a list of unique class names. - $test_suite = $configuration->getTestSuiteConfiguration(NULL); - $test_classes = array(); - foreach ($test_suite as $test) { - // PHPUnit returns a warning message if something is wrong with a test, - // throw an exception to avoid an error when trying to call getInfo() on - // this. - if ($test instanceof PHPUnit_Framework_Warning) { - throw new RuntimeException($test->getMessage()); - } - - $name = get_class($test); - if (!array_key_exists($name, $test_classes) && (!$module || substr($name, 0, $n) == $prefix)) { - $test_classes[$name] = $test->getInfo(); - } - } - - // Since we have recalculated, we now need to store the new data into cache. - \Drupal::cache()->set($cid, $test_classes); - } - - return $test_classes; -} - -/** * Converts PHPUnit's JUnit XML output to an array. * * @param $test_id diff --git a/core/modules/simpletest/src/Form/SimpletestTestForm.php b/core/modules/simpletest/src/Form/SimpletestTestForm.php index ca84a43..a85f941 100644 --- a/core/modules/simpletest/src/Form/SimpletestTestForm.php +++ b/core/modules/simpletest/src/Form/SimpletestTestForm.php @@ -27,6 +27,20 @@ public function getFormId() { * {@inheritdoc} */ public function buildForm(array $form, array &$form_state) { + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => $this->t('Run tests'), + '#tableselect' => TRUE, + '#button_type' => 'primary', + ); + + // Do not needlessly re-execute a full test discovery if the user input + // already contains an explicit list of test classes to run. + if (!empty($form_state['input']['tests'])) { + return $form; + } + // JavaScript-only table filters. $form['filters'] = array( '#type' => 'container', @@ -96,8 +110,6 @@ public function buildForm(array $form, array &$form_state) { // Generate the list of tests arranged by group. $groups = simpletest_test_get_all(); - $form_state['storage']['PHPUnit'] = $groups['PHPUnit']; - foreach ($groups as $group => $tests) { $form['tests'][$group] = array( '#attributes' => array('class' => array('simpletest-group')), @@ -158,15 +170,6 @@ public function buildForm(array $form, array &$form_state) { } } - // Action buttons. - $form['actions'] = array('#type' => 'actions'); - $form['actions']['submit'] = array( - '#type' => 'submit', - '#value' => $this->t('Run tests'), - '#tableselect' => TRUE, - '#button_type' => 'primary', - ); - $form['clean'] = array( '#type' => 'fieldset', '#title' => $this->t('Clean test environment'), @@ -186,16 +189,31 @@ public function buildForm(array $form, array &$form_state) { * {@inheritdoc} */ public function submitForm(array &$form, array &$form_state) { + // Test discovery does not run upon form submission. simpletest_classloader_register(); - $phpunit_all = $form_state['storage']['PHPUnit']; + // This form accepts arbitrary user input for 'tests'. + // An invalid value will cause the $class_name lookup below to die with a + // fatal error. Regular user access mechanisms to this form are intact. + // The only validation effectively being skipped is the validation of + // available checkboxes vs. submitted checkboxes. + // @todo Refactor Form API to allow to POST values without constructing the + // entire form more easily, BUT retaining routing access security and + // retaining Form API CSRF #token security validation, and without having + // to rely on form caching. + if (empty($form_state['values']['tests']) && !empty($form_state['input']['tests'])) { + $form_state['values']['tests'] = $form_state['input']['tests']; + } $tests_list = array(); foreach ($form_state['values']['tests'] as $class_name => $value) { - // Since class_exists() will likely trigger an autoload lookup, - // we do the fast check first. - if ($value === $class_name && class_exists($class_name)) { - $test_type = isset($phpunit_all[$class_name]) ? 'UnitTest' : 'WebTest'; + if ($value === $class_name) { + if (is_subclass_of($class_name, 'PHPUnit_Framework_TestCase')) { + $test_type = 'phpunit'; + } + else { + $test_type = 'simpletest'; + } $tests_list[$test_type][] = $class_name; } } diff --git a/core/modules/simpletest/src/TestDiscovery.php b/core/modules/simpletest/src/TestDiscovery.php index 71e3025..dac7a43 100644 --- a/core/modules/simpletest/src/TestDiscovery.php +++ b/core/modules/simpletest/src/TestDiscovery.php @@ -9,6 +9,8 @@ use Drupal\Core\Extension\Extension; use Drupal\Core\Extension\ExtensionDiscovery; +use PHPUnit_Framework_TestSuite; +use PHPUnit_Framework_Warning; use PHPUnit_Util_Test; /** @@ -140,7 +142,8 @@ public function getTestClasses() { continue; } // Skip non-test classes. - if (!$class->isSubclassOf('Drupal\simpletest\TestBase') && !$class->isSubclassOf('PHPUnit_Framework_TestCase')) { + $phpunit = FALSE; + if (!$class->isSubclassOf('Drupal\simpletest\TestBase') && !$phpunit = $class->isSubclassOf('PHPUnit_Framework_TestCase')) { continue; } $info = static::getTestInfo($class); @@ -154,10 +157,14 @@ public function getTestClasses() { continue; } } - // @todo Check whether PHPUnit's native validation is required. If so: - // Construct a PHPUnit_Framework_TestSuite($class) and omit the test - // class if that throws a PHPUnit_Framework_Warning exception. - // @see simpletest_phpunit_get_available_tests() + // @todo Check whether PHPUnit's native validation is really required. + if ($phpunit) { + $suite = new PHPUnit_Framework_TestSuite($class); + // PHPUnit returns a warning if something is wrong with a test. + if ($suite instanceof PHPUnit_Framework_Warning) { + throw new \RuntimeException($test->getMessage()); + } + } $this->testClasses[$info['group']][$classname] = $info; }