fd15f8218bc1763e3035df9bcfa860ebcd06a194
[yaffs-website] / web / core / tests / Drupal / Tests / Listeners / DrupalStandardsListener.php
1 <?php
2
3 namespace Drupal\Tests\Listeners;
4
5 use PHPUnit\Framework\BaseTestListener;
6 use PHPUnit\Framework\TestCase;
7
8 /**
9  * Listens for PHPUnit tests and fails those with invalid coverage annotations.
10  *
11  * Enforces various coding standards within test runs.
12  */
13 class DrupalStandardsListener extends BaseTestListener {
14
15   /**
16    * Signals a coding standards failure to the user.
17    *
18    * @param \PHPUnit\Framework\TestCase $test
19    *   The test where we should insert our test failure.
20    * @param string $message
21    *   The message to add to the failure notice. The test class name and test
22    *   name will be appended to this message automatically.
23    */
24   protected function fail(TestCase $test, $message) {
25     // Add the report to the test's results.
26     $message .= ': ' . get_class($test) . '::' . $test->getName();
27     $fail = new \PHPUnit_Framework_AssertionFailedError($message);
28     $result = $test->getTestResultObject();
29     $result->addFailure($test, $fail, 0);
30   }
31
32   /**
33    * Helper method to check if a string names a valid class or trait.
34    *
35    * @param string $class
36    *   Name of the class to check.
37    *
38    * @return bool
39    *   TRUE if the class exists, FALSE otherwise.
40    */
41   protected function classExists($class) {
42     return class_exists($class, TRUE) || trait_exists($class, TRUE) || interface_exists($class, TRUE);
43   }
44
45   /**
46    * Check an individual test run for valid @covers annotation.
47    *
48    * This method is called from $this::endTest().
49    *
50    * @param \PHPUnit\Framework\TestCase $test
51    *   The test to examine.
52    */
53   public function checkValidCoversForTest(TestCase $test) {
54     // If we're generating a coverage report already, don't do anything here.
55     if ($test->getTestResultObject() && $test->getTestResultObject()->getCollectCodeCoverageInformation()) {
56       return;
57     }
58     // Gather our annotations.
59     $annotations = $test->getAnnotations();
60     // Glean the @coversDefaultClass annotation.
61     $default_class = '';
62     $valid_default_class = FALSE;
63     if (isset($annotations['class']['coversDefaultClass'])) {
64       if (count($annotations['class']['coversDefaultClass']) > 1) {
65         $this->fail($test, '@coversDefaultClass has too many values');
66       }
67       // Grab the first one.
68       $default_class = reset($annotations['class']['coversDefaultClass']);
69       // Check whether the default class exists.
70       $valid_default_class = $this->classExists($default_class);
71       if (!$valid_default_class) {
72         $this->fail($test, "@coversDefaultClass does not exist '$default_class'");
73       }
74     }
75     // Glean @covers annotation.
76     if (isset($annotations['method']['covers'])) {
77       // Drupal allows multiple @covers per test method, so we have to check
78       // them all.
79       foreach ($annotations['method']['covers'] as $covers) {
80         // Ensure the annotation isn't empty.
81         if (trim($covers) === '') {
82           $this->fail($test, '@covers should not be empty');
83           // If @covers is empty, we can't proceed.
84           return;
85         }
86         // Ensure we don't have ().
87         if (strpos($covers, '()') !== FALSE) {
88           $this->fail($test, "@covers invalid syntax: Do not use '()'");
89         }
90         // Glean the class and method from @covers.
91         $class = $covers;
92         $method = '';
93         if (strpos($covers, '::') !== FALSE) {
94           list($class, $method) = explode('::', $covers);
95         }
96         // Check for the existence of the class if it's specified by @covers.
97         if (!empty($class)) {
98           // If the class doesn't exist we have either a bad classname or
99           // are missing the :: for a method. Either way we can't proceed.
100           if (!$this->classExists($class)) {
101             if (empty($method)) {
102               $this->fail($test, "@covers invalid syntax: Needs '::' or class does not exist in $covers");
103               return;
104             }
105             else {
106               $this->fail($test, '@covers class does not exist ' . $class);
107               return;
108             }
109           }
110         }
111         else {
112           // The class isn't specified and we have the ::, so therefore this
113           // test either covers a function, or relies on a default class.
114           if (empty($default_class)) {
115             // If there's no default class, then we need to check if the global
116             // function exists. Since this listener should always be listening
117             // for endTest(), the function should have already been loaded from
118             // its .module or .inc file.
119             if (!function_exists($method)) {
120               $this->fail($test, '@covers global method does not exist ' . $method);
121             }
122           }
123           else {
124             // We have a default class and this annotation doesn't act like a
125             // global function, so we should use the default class if it's
126             // valid.
127             if ($valid_default_class) {
128               $class = $default_class;
129             }
130           }
131         }
132         // Finally, after all that, let's see if the method exists.
133         if (!empty($class) && !empty($method)) {
134           $ref_class = new \ReflectionClass($class);
135           if (!$ref_class->hasMethod($method)) {
136             $this->fail($test, '@covers method does not exist ' . $class . '::' . $method);
137           }
138         }
139       }
140     }
141   }
142
143   /**
144    * {@inheritdoc}
145    *
146    * We must mark this method as belonging to the special legacy group because
147    * it might trigger an E_USER_DEPRECATED error during coverage annotation
148    * validation. The legacy group allows symfony/phpunit-bridge to keep the
149    * deprecation notice as a warning instead of an error, which would fail the
150    * test.
151    *
152    * @group legacy
153    *
154    * @see http://symfony.com/doc/current/components/phpunit_bridge.html#mark-tests-as-legacy
155    */
156   public function endTest(\PHPUnit_Framework_Test $test, $time) {
157     // \PHPUnit_Framework_Test does not have any useful methods of its own for
158     // our purpose, so we have to distinguish between the different known
159     // subclasses.
160     if ($test instanceof TestCase) {
161       $this->checkValidCoversForTest($test);
162     }
163     elseif ($test instanceof \PHPUnit_Framework_TestSuite) {
164       foreach ($test->getGroupDetails() as $tests) {
165         foreach ($tests as $test) {
166           $this->endTest($test, $time);
167         }
168       }
169     }
170   }
171
172 }