X-Git-Url: http://www.aleph1.co.uk/gitweb/?p=yaffs-website;a=blobdiff_plain;f=web%2Fcore%2Ftests%2FDrupal%2FTests%2FListeners%2FDrupalStandardsListenerTrait.php;fp=web%2Fcore%2Ftests%2FDrupal%2FTests%2FListeners%2FDrupalStandardsListenerTrait.php;h=3352d900b380da0e382e25cde7ec2c9df245ee00;hp=0000000000000000000000000000000000000000;hb=af6d1fb995500ae68849458ee10d66abbdcfb252;hpb=680c79a86e3ed402f263faeac92e89fb6d9edcc0 diff --git a/web/core/tests/Drupal/Tests/Listeners/DrupalStandardsListenerTrait.php b/web/core/tests/Drupal/Tests/Listeners/DrupalStandardsListenerTrait.php new file mode 100644 index 000000000..3352d900b --- /dev/null +++ b/web/core/tests/Drupal/Tests/Listeners/DrupalStandardsListenerTrait.php @@ -0,0 +1,237 @@ +getName(); + $fail = new AssertionFailedError($message); + $result = $test->getTestResultObject(); + $result->addFailure($test, $fail, 0); + } + + /** + * Helper method to check if a string names a valid class or trait. + * + * @param string $class + * Name of the class to check. + * + * @return bool + * TRUE if the class exists, FALSE otherwise. + */ + private function classExists($class) { + return class_exists($class, TRUE) || trait_exists($class, TRUE) || interface_exists($class, TRUE); + } + + /** + * Check an individual test run for valid @covers annotation. + * + * This method is called from $this::endTest(). + * + * @param \PHPUnit\Framework\TestCase $test + * The test to examine. + */ + private function checkValidCoversForTest(TestCase $test) { + // If we're generating a coverage report already, don't do anything here. + if ($test->getTestResultObject() && $test->getTestResultObject()->getCollectCodeCoverageInformation()) { + return; + } + // Gather our annotations. + $annotations = $test->getAnnotations(); + // Glean the @coversDefaultClass annotation. + $default_class = ''; + $valid_default_class = FALSE; + if (isset($annotations['class']['coversDefaultClass'])) { + if (count($annotations['class']['coversDefaultClass']) > 1) { + $this->fail($test, '@coversDefaultClass has too many values'); + } + // Grab the first one. + $default_class = reset($annotations['class']['coversDefaultClass']); + // Check whether the default class exists. + $valid_default_class = $this->classExists($default_class); + if (!$valid_default_class) { + $this->fail($test, "@coversDefaultClass does not exist '$default_class'"); + } + } + // Glean @covers annotation. + if (isset($annotations['method']['covers'])) { + // Drupal allows multiple @covers per test method, so we have to check + // them all. + foreach ($annotations['method']['covers'] as $covers) { + // Ensure the annotation isn't empty. + if (trim($covers) === '') { + $this->fail($test, '@covers should not be empty'); + // If @covers is empty, we can't proceed. + return; + } + // Ensure we don't have (). + if (strpos($covers, '()') !== FALSE) { + $this->fail($test, "@covers invalid syntax: Do not use '()'"); + } + // Glean the class and method from @covers. + $class = $covers; + $method = ''; + if (strpos($covers, '::') !== FALSE) { + list($class, $method) = explode('::', $covers); + } + // Check for the existence of the class if it's specified by @covers. + if (!empty($class)) { + // If the class doesn't exist we have either a bad classname or + // are missing the :: for a method. Either way we can't proceed. + if (!$this->classExists($class)) { + if (empty($method)) { + $this->fail($test, "@covers invalid syntax: Needs '::' or class does not exist in $covers"); + return; + } + else { + $this->fail($test, '@covers class does not exist ' . $class); + return; + } + } + } + else { + // The class isn't specified and we have the ::, so therefore this + // test either covers a function, or relies on a default class. + if (empty($default_class)) { + // If there's no default class, then we need to check if the global + // function exists. Since this listener should always be listening + // for endTest(), the function should have already been loaded from + // its .module or .inc file. + if (!function_exists($method)) { + $this->fail($test, '@covers global method does not exist ' . $method); + } + } + else { + // We have a default class and this annotation doesn't act like a + // global function, so we should use the default class if it's + // valid. + if ($valid_default_class) { + $class = $default_class; + } + } + } + // Finally, after all that, let's see if the method exists. + if (!empty($class) && !empty($method)) { + $ref_class = new \ReflectionClass($class); + if (!$ref_class->hasMethod($method)) { + $this->fail($test, '@covers method does not exist ' . $class . '::' . $method); + } + } + } + } + } + + /** + * Handles errors to ensure deprecation messages are not triggered. + * + * @param int $type + * The severity level of the error. + * @param string $msg + * The error message. + * @param $file + * The file that caused the error. + * @param $line + * The line number that caused the error. + * @param array $context + * The error context. + */ + public static function errorHandler($type, $msg, $file, $line, $context = []) { + if ($type === E_USER_DEPRECATED) { + return; + } + $error_handler = class_exists('PHPUnit_Util_ErrorHandler') ? 'PHPUnit_Util_ErrorHandler' : 'PHPUnit\Util\ErrorHandler'; + return $error_handler::handleError($type, $msg, $file, $line, $context); + } + + /** + * Reacts to the end of a test. + * + * We must mark this method as belonging to the special legacy group because + * it might trigger an E_USER_DEPRECATED error during coverage annotation + * validation. The legacy group allows symfony/phpunit-bridge to keep the + * deprecation notice as a warning instead of an error, which would fail the + * test. + * + * @group legacy + * + * @param \PHPUnit\Framework\Test|\PHPUnit_Framework_Test $test + * The test object that has ended its test run. + * @param float $time + * The time the test took. + * + * @see http://symfony.com/doc/current/components/phpunit_bridge.html#mark-tests-as-legacy + */ + private function doEndTest($test, $time) { + // \PHPUnit_Framework_Test does not have any useful methods of its own for + // our purpose, so we have to distinguish between the different known + // subclasses. + if ($test instanceof TestCase) { + // Change the error handler to ensure deprecation messages are not + // triggered. + set_error_handler([$this, 'errorHandler']); + $this->checkValidCoversForTest($test); + restore_error_handler(); + } + elseif ($this->isTestSuite($test)) { + foreach ($test->getGroupDetails() as $tests) { + foreach ($tests as $test) { + $this->doEndTest($test, $time); + } + } + } + } + + /** + * Determine if a test object is a test suite regardless of PHPUnit version. + * + * @param \PHPUnit\Framework\Test|\PHPUnit_Framework_Test $test + * The test object to test if it is a test suite. + * + * @return bool + * TRUE if it is a test suite, FALSE if not. + */ + private function isTestSuite($test) { + if (class_exists('\PHPUnit_Framework_TestSuite') && $test instanceof \PHPUnit_Framework_TestSuite) { + return TRUE; + } + if (class_exists('PHPUnit\Framework\TestSuite') && $test instanceof TestSuite) { + return TRUE; + } + return FALSE; + } + + /** + * Reacts to the end of a test. + * + * @param \PHPUnit\Framework\Test|\PHPUnit_Framework_Test $test + * The test object that has ended its test run. + * @param float $time + * The time the test took. + */ + protected function standardsEndTest($test, $time) { + $this->doEndTest($test, $time); + } + +}