3 namespace Drupal\Tests\Listeners;
6 * Listens for PHPUnit tests and fails those with invalid coverage annotations.
8 * Enforces various coding standards within test runs.
10 class DrupalStandardsListener extends \PHPUnit_Framework_BaseTestListener {
13 * Signals a coding standards failure to the user.
15 * @param \PHPUnit_Framework_TestCase $test
16 * The test where we should insert our test failure.
17 * @param string $message
18 * The message to add to the failure notice. The test class name and test
19 * name will be appended to this message automatically.
21 protected function fail(\PHPUnit_Framework_TestCase $test, $message) {
22 // Add the report to the test's results.
23 $message .= ': ' . get_class($test) . '::' . $test->getName();
24 $fail = new \PHPUnit_Framework_AssertionFailedError($message);
25 $result = $test->getTestResultObject();
26 $result->addFailure($test, $fail, 0);
30 * Helper method to check if a string names a valid class or trait.
32 * @param string $class
33 * Name of the class to check.
36 * TRUE if the class exists, FALSE otherwise.
38 protected function classExists($class) {
39 return class_exists($class, TRUE) || trait_exists($class, TRUE) || interface_exists($class, TRUE);
43 * Check an individual test run for valid @covers annotation.
45 * This method is called from $this::endTest().
47 * @param \PHPUnit_Framework_TestCase $test
48 * The test to examine.
50 public function checkValidCoversForTest(\PHPUnit_Framework_TestCase $test) {
51 // If we're generating a coverage report already, don't do anything here.
52 if ($test->getTestResultObject() && $test->getTestResultObject()->getCollectCodeCoverageInformation()) {
55 // Gather our annotations.
56 $annotations = $test->getAnnotations();
57 // Glean the @coversDefaultClass annotation.
59 $valid_default_class = FALSE;
60 if (isset($annotations['class']['coversDefaultClass'])) {
61 if (count($annotations['class']['coversDefaultClass']) > 1) {
62 $this->fail($test, '@coversDefaultClass has too many values');
64 // Grab the first one.
65 $default_class = reset($annotations['class']['coversDefaultClass']);
66 // Check whether the default class exists.
67 $valid_default_class = $this->classExists($default_class);
68 if (!$valid_default_class) {
69 $this->fail($test, "@coversDefaultClass does not exist '$default_class'");
72 // Glean @covers annotation.
73 if (isset($annotations['method']['covers'])) {
74 // Drupal allows multiple @covers per test method, so we have to check
76 foreach ($annotations['method']['covers'] as $covers) {
77 // Ensure the annotation isn't empty.
78 if (trim($covers) === '') {
79 $this->fail($test, '@covers should not be empty');
80 // If @covers is empty, we can't proceed.
83 // Ensure we don't have ().
84 if (strpos($covers, '()') !== FALSE) {
85 $this->fail($test, "@covers invalid syntax: Do not use '()'");
87 // Glean the class and method from @covers.
90 if (strpos($covers, '::') !== FALSE) {
91 list($class, $method) = explode('::', $covers);
93 // Check for the existence of the class if it's specified by @covers.
95 // If the class doesn't exist we have either a bad classname or
96 // are missing the :: for a method. Either way we can't proceed.
97 if (!$this->classExists($class)) {
99 $this->fail($test, "@covers invalid syntax: Needs '::' or class does not exist in $covers");
103 $this->fail($test, '@covers class does not exist ' . $class);
109 // The class isn't specified and we have the ::, so therefore this
110 // test either covers a function, or relies on a default class.
111 if (empty($default_class)) {
112 // If there's no default class, then we need to check if the global
113 // function exists. Since this listener should always be listening
114 // for endTest(), the function should have already been loaded from
115 // its .module or .inc file.
116 if (!function_exists($method)) {
117 $this->fail($test, '@covers global method does not exist ' . $method);
121 // We have a default class and this annotation doesn't act like a
122 // global function, so we should use the default class if it's
124 if ($valid_default_class) {
125 $class = $default_class;
129 // Finally, after all that, let's see if the method exists.
130 if (!empty($class) && !empty($method)) {
131 $ref_class = new \ReflectionClass($class);
132 if (!$ref_class->hasMethod($method)) {
133 $this->fail($test, '@covers method does not exist ' . $class . '::' . $method);
143 public function endTest(\PHPUnit_Framework_Test $test, $time) {
144 // \PHPUnit_Framework_Test does not have any useful methods of its own for
145 // our purpose, so we have to distinguish between the different known
147 if ($test instanceof \PHPUnit_Framework_TestCase) {
148 $this->checkValidCoversForTest($test);
150 elseif ($test instanceof \PHPUnit_Framework_TestSuite) {
151 foreach ($test->getGroupDetails() as $tests) {
152 foreach ($tests as $test) {
153 $this->endTest($test, $time);