Skip to content
Snippets Groups Projects
Verified Commit 4e74e716 authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3350400 by mondrake: Remove DrupalStandardsListenerTrait - it's broken,...

Issue #3350400 by mondrake: Remove DrupalStandardsListenerTrait - it's broken, does not prevent errors, blocks better tooling
parent 0fadc153
No related branches found
No related tags found
27 merge requests!12227Issue #3181946 by jonmcl, mglaman,!54479.5.x SF update,!5014Issue #3071143: Table Render Array Example Is Incorrect,!4868Issue #1428520: Improve menu parent link selection,!4289Issue #1344552 by marcingy, Niklas Fiekas, Ravi.J, aleevas, Eduardo Morales...,!4114Issue #2707291: Disable body-level scrolling when a dialog is open as a modal,!4100Issue #3249600: Add support for PHP 8.1 Enums as allowed values for list_* data types,!2378Issue #2875033: Optimize joins and table selection in SQL entity query implementation,!2334Issue #3228209: Add hasRole() method to AccountInterface,!2062Issue #3246454: Add weekly granularity to views date sort,!1591Issue #3199697: Add JSON:API Translation experimental module,!1484Exposed filters get values from URL when Ajax is on,!1255Issue #3238922: Refactor (if feasible) uses of the jQuery serialize function to use vanillaJS,!1162Issue #3100350: Unable to save '/' root path alias,!1105Issue #3025039: New non translatable field on translatable content throws error,!1073issue #3191727: Focus states on mobile second level navigation items fixed,!10223132456: Fix issue where views instances are emptied before an ajax request is complete,!925Issue #2339235: Remove taxonomy hard dependency on node module,!877Issue #2708101: Default value for link text is not saved,!872Draft: Issue #3221319: Race condition when creating menu links and editing content deletes menu links,!844Resolve #3036010 "Updaters",!579Issue #2230909: Simple decimals fail to pass validation,!560Move callback classRemove outside of the loop,!555Issue #3202493,!485Sets the autocomplete attribute for username/password input field on login form.,!213Issue #2906496: Give Media a menu item under Content,!30Issue #3182188: Updates composer usage to point at ./vendor/bin/composer
......@@ -17,7 +17,6 @@ class DrupalListener implements TestListener {
use TestListenerDefaultImplementation;
use DrupalComponentTestListenerTrait;
use DrupalStandardsListenerTrait;
/**
* The wrapped Symfony test listener.
......@@ -65,7 +64,6 @@ public function startTest(Test $test): void {
public function endTest(Test $test, float $time): void {
$this->symfonyListener->endTest($test, $time);
$this->componentEndTest($test, $time);
$this->standardsEndTest($test, $time);
}
}
<?php
namespace Drupal\Tests\Listeners;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Util\Test;
/**
* Listens for PHPUnit tests and fails those with invalid coverage annotations.
*
* Enforces various coding standards within test runs.
*
* @internal
*/
trait DrupalStandardsListenerTrait {
/**
* Signals a coding standards failure to the user.
*
* @param \PHPUnit\Framework\TestCase $test
* The test where we should insert our test failure.
* @param string $message
* The message to add to the failure notice. The test class name and test
* name will be appended to this message automatically.
*/
private function fail(TestCase $test, $message) {
// Add the report to the test's results.
$message .= ': ' . get_class($test) . '::' . $test->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);
}
/**
* 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::parseTestMethodAnnotations(
static::class,
$test->getName()
);
// 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 && interface_exists($default_class)) {
$this->fail($test, "@coversDefaultClass refers to an interface '$default_class' and those can not be tested.");
}
elseif (!$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 (str_contains($covers, '()')) {
$this->fail($test, "@covers invalid syntax: Do not use '()'");
}
// Glean the class and method from @covers.
$class = $covers;
$method = '';
if (str_contains($covers, '::')) {
[$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;
}
elseif (interface_exists($class)) {
$this->fail($test, "@covers refers to an interface '$class' and those can not be tested.");
}
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);
}
}
}
}
}
/**
* Reacts to the end of a test.
*
* @param \PHPUnit\Framework\Test $test
* The test object that has ended its test run.
* @param float $time
* The time the test took.
*/
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) {
$this->checkValidCoversForTest($test);
}
elseif ($test instanceof TestSuite) {
foreach ($test->getGroupDetails() as $tests) {
foreach ($tests as $test) {
$this->doEndTest($test, $time);
}
}
}
}
/**
* Reacts to the end of a test.
*
* @param \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);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment