diff --git a/core/modules/simpletest/simpletest.module b/core/modules/simpletest/simpletest.module index 052e0b7..1f16d10 100644 --- a/core/modules/simpletest/simpletest.module +++ b/core/modules/simpletest/simpletest.module @@ -144,7 +144,7 @@ function simpletest_run_tests($test_list) { // Get the info for the first test being run. $first_test = reset($test_list); - $info = TestDiscovery::getTestInfo(new \ReflectionClass($first_test)); + $info = TestDiscovery::getTestInfo($first_test); $batch = array( 'title' => t('Running tests'), @@ -320,7 +320,7 @@ function _simpletest_batch_operation($test_list_init, $test_id, &$context) { $test = new $test_class($test_id); $test->run(); $size = count($test_list); - $info = TestDiscovery::getTestInfo(new \ReflectionClass($test)); + $info = TestDiscovery::getTestInfo($test_class); \Drupal::moduleHandler()->invokeAll('test_finished', array($test->results)); diff --git a/core/modules/simpletest/src/Exception/MissingGroupException.php b/core/modules/simpletest/src/Exception/MissingGroupException.php new file mode 100644 index 0000000..e877b88 --- /dev/null +++ b/core/modules/simpletest/src/Exception/MissingGroupException.php @@ -0,0 +1,14 @@ + $assertions) { // Create group details with summary information. - $info = TestDiscovery::getTestInfo(new \ReflectionClass($group)); + $info = TestDiscovery::getTestInfo($group); $form['result']['results'][$group] = array( '#type' => 'details', '#title' => $info['name'], diff --git a/core/modules/simpletest/src/TestDiscovery.php b/core/modules/simpletest/src/TestDiscovery.php index f09bb14..9d27dd4 100644 --- a/core/modules/simpletest/src/TestDiscovery.php +++ b/core/modules/simpletest/src/TestDiscovery.php @@ -12,10 +12,13 @@ use Doctrine\Common\Annotations\SimpleAnnotationReader; use Doctrine\Common\Reflection\StaticReflectionParser; use Drupal\Component\Annotation\Reflection\MockFileFinder; +use Drupal\Component\Utility\String; use Drupal\Component\Utility\Unicode; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\Extension; use Drupal\Core\Extension\ExtensionDiscovery; +use Drupal\simpletest\Exception\MissingGroupException; +use Drupal\simpletest\Exception\MissingSummaryLineException; use PHPUnit_Util_Test; /** @@ -154,41 +157,14 @@ public function getTestClasses($extension = NULL) { foreach ($classmap as $classname => $pathname) { $finder = MockFileFinder::create($pathname); $parser = new StaticReflectionParser($classname, $finder, TRUE); - $doc_comment = $parser->getDocComment(); - $info = array( - 'name' => $classname, - ); - $annotations = array(); - preg_match_all('/^ \* \@([^\s]*) (.*$)/m', $doc_comment, $matches); - if (isset($matches[1])) { - foreach ($matches[1] as $key => $annotation) { - if (!empty($annotations[$annotation])) { - // Only have the first match per annotation. This deals with - // multiple @group annotations. - continue; - } - $annotations[$annotation] = $matches[2][$key]; - } + try { + $info = static::getTestInfo($classname, $parser->getDocComment()); } - if (!empty($annotations['coversDefaultClass'])) { - $info['description'] = 'Tests ' . $annotations['coversDefaultClass'] . '.'; - } - else { - // Some preg to strip annotations and comment headers. - preg_match('/^ \* (.*)$/m', $doc_comment, $matches); - $info['description'] = isset($matches[1]) ? $matches[1] : 'No description.'; - } - if (isset($annotations['dependencies'])) { - $info['requires']['module'] = explode(',', $annotations['dependencies']); - } - - if (empty($annotations['group'])) { - // Concrete tests must have a group and abstract and + catch (\LogicException $e) { + // If the class is missing a summary line or an @group annotation just + // skip it. Most likely it is an abstract class, trait or test fixture. continue; } - - // Standardise the group name. - $info['group'] = Unicode::ucfirst($annotations['group']); // Skip this test class if it requires unavailable modules. // @todo PHPUnit skips tests with unmet requirements when executing a test // (instead of excluding them upfront). Refactor test runner to follow @@ -302,8 +278,11 @@ public static function scanDirectory($namespace_prefix, $path) { /** * Retrieves information about a test class for UI purposes. * - * @param \ReflectionClass $class - * The reflected test class. + * @param string $class + * The test classname. + * @param string $doc_comment + * (optional) The class PHPDoc comment. If not passed in reflection will be + * used but this is very expensive when parsing all the test classes. * * @return array * An associative array containing: @@ -317,47 +296,52 @@ public static function scanDirectory($namespace_prefix, $path) { * @throws \LogicException * If the class does not have a PHPDoc summary line or @coversDefaultClass * annotation. - * @throws \LogicException + * @throws \Drupal\simpletest\Exception\MissingGroupException * If the class does not have a @group annotation. */ - public static function getTestInfo(\ReflectionClass $class) { - $classname = $class->getName(); + public static function getTestInfo($classname, $doc_comment = NULL) { + if (!$doc_comment) { + $reflection = new \ReflectionClass($classname); + $doc_comment = $reflection->getDocComment(); + } $info = array( 'name' => $classname, ); - - // Automatically convert @coversDefaultClass into summary. - $annotations = static::parseTestClassAnnotations($class); - if (isset($annotations['coversDefaultClass'][0])) { - $info['description'] = 'Tests ' . $annotations['coversDefaultClass'][0] . '.'; - } - elseif ($summary = static::parseTestClassSummary($class)) { - $info['description'] = $summary; - } - else { - throw new \LogicException(sprintf('Missing PHPDoc summary line on %s in %s.', $classname, $class->getFileName())); + $annotations = array(); + preg_match_all('/^ \* \@([^\s]*) (.*$)/m', $doc_comment, $matches); + if (isset($matches[1])) { + foreach ($matches[1] as $key => $annotation) { + if (!empty($annotations[$annotation])) { + // Only have the first match per annotation. This deals with + // multiple @group annotations. + continue; + } + $annotations[$annotation] = $matches[2][$key]; + } } - if (isset($annotations['dependencies'])) { - $info['requires']['module'] = array_merge($info['requires']['module'], explode(',', $annotations['dependencies'])); + if (empty($annotations['group'])) { + // Concrete tests must have a group. + throw new MissingGroupException(sprintf('Missing @group annotation in %s', $classname)); } + // Standardise the group name. + $info['group'] = Unicode::ucfirst($annotations['group']); - // Reduce to @group and @requires. - $info += array_intersect_key($annotations, array('group' => 1, 'requires' => 1)); - - // Process @group information. - // @todo Support multiple @groups + change UI to expose a group select - // dropdown to filter tests by group instead of collapsible table rows. - // @see https://www.drupal.org/node/2296615 - // @todo Replace single enforced PHPUnit group with base class groups. - if ($class->isSubclassOf('PHPUnit_Framework_TestCase')) { - $info['group'] = 'PHPUnit'; + if (!empty($annotations['coversDefaultClass'])) { + $info['description'] = 'Tests ' . $annotations['coversDefaultClass'] . '.'; } else { - if (empty($info['group'])) { - throw new \LogicException("Missing @group for $classname."); + // Some preg to strip annotations and comment headers. + preg_match('/^ \* (.*)$/m', $doc_comment, $matches); + if (isset($matches[1])) { + $info['description'] = $matches[1]; + } + else { + throw new MissingSummaryLineException(sprintf('Missing PHPDoc summary line in %s', $classname)); } - $info['group'] = reset($info['group']); + } + if (isset($annotations['dependencies'])) { + $info['requires']['module'] = explode(',', $annotations['dependencies']); } return $info;