Problem/Motivation
Discovered via PHPStan in #3178534: Start running PHPStan on Drupal core (level 0)
------ --------------------------------------------------------------------------------------------------------------------------------
Line core/tests/Drupal/Tests/Listeners/DrupalStandardsListenerTrait.php (in context of class Drupal\Tests\Listeners\DrupalListener)
------ --------------------------------------------------------------------------------------------------------------------------------
175 Call to an undefined static method PHPUnit\Util\ErrorHandler::handleError().
------ --------------------------------------------------------------------------------------------------------------------------------
The code in question is
use PHPUnit\Util\ErrorHandler;
...
public static function errorHandler($type, $msg, $file, $line, $context = []) {
if ($type === E_USER_DEPRECATED) {
return;
}
return ErrorHandler::handleError($type, $msg, $file, $line, $context);
}
However PHPStan is correct, the method does not exist.
Steps to reproduce
This error handler is only used while checking @covers annotations:
set_error_handler([$this, 'errorHandler']);
$this->checkValidCoversForTest($test);
restore_error_handler();
If we introduce an error here via trigger_error() then when running a test, PHPUnit crashes:
PHPUnit 8.5.15 by Sebastian Bergmann and contributors.
...
PHP Fatal error: Uncaught Error: Call to undefined method PHPUnit\Util\ErrorHandler::handleError() in /var/www/html/drupal/core/tests/Drupal/Tests/Listeners/DrupalStandardsListenerTrait.php:175
Stack trace:
#0 [internal function]: Drupal\Tests\Listeners\DrupalListener::errorHandler(1024, 'test', '/var/www/html/d...', 204, Array)
#1 /var/www/html/drupal/core/tests/Drupal/Tests/Listeners/DrupalStandardsListenerTrait.php(204): trigger_error('test')
Proposed resolution
The PHPUnit ErrorHandler is @internal so we probably shouldn't rely on it, but not sure what the correct fix is.
Remaining tasks
User interface changes
API changes
Data model changes
Release notes snippet
Comments
Comment #2
longwaveThis was first introduced in #2933991: Deprecation tests fail when all PHPUnit tests are run via PHPUnit but it looks like the PHPUnit ErrorHandler was refactored in PHPUnit 8.3 and handleError() was removed: https://github.com/sebastianbergmann/phpunit/commit/db1dce395c1843f09531...
Comment #5
neclimdulYeah, this should have broke in #3063887: Support PHPUnit 8 in Drupal 9, drop support for PHPUnit 7 but I guess there's no coverage for it. It would be pretty tricky to do. Tagging.
Comment #6
neclimdulHere's one way of handling it.
Comment #7
longwaveWhat happens if we don't change the error handler at all? The method documentation first of all says
then the comment in the body says the opposite
so surely at least one of the error handler and
@group legacyis redundant here?Comment #8
longwave#2933991: Deprecation tests fail when all PHPUnit tests are run via PHPUnit was where this was first introduced.
Comment #9
neclimdulYeah I thought this weird bit of code felt familiar. There's was some scary complexity there. Interaction with phpunits process separation and complexities. And yeah... a lot of this documentation doesn't track. Feels like a couple things where tried to reign in errors and they're just left hanging around.
I can't see how the @legacy could have ever done anything outside the actual tests(maybe it did though) so I removed that. Then I removed the handler to see how the errors where triggering and.
So... I just removed it all to see remind myself how it actually failed and the test passes with flying colors. No deprecation. Which is super confusing so lets see what testbot has to say. I may have to bug alex to sort out if something changed in phpunit to make this unnecessary.
Anyways, here's ripping it all out and lets see what the bot thinks.
Comment #10
mondrakeIs this code strictly connected with the fact that we are kinda encapsulating PHPUnit-brdige deprecation handler through
DeprecationListenerTrait? And, is DeprecationListenerTrait there only to skip some deprecations?If so, I filed upstream PHPUnitBridge - add a deprecation 'ignoreFile option' to allow silencing deprecation patterns and PR.
When that materializes, we may move the deprecation patterns to be skipped to the bridge via a JSON file instead of code, which may trigger removal of a quite a bit of this code.
Not sure if this is OT, but would love your thoughts.
Comment #11
longwaveRerolled #9
Comment #12
longwaveLocally I tried to reproduce the issue in #2933991: Deprecation tests fail when all PHPUnit tests are run via PHPUnit but was not successful. Selected output is as follows:
In other words I ran into #3255749: Composer v2.2 prompts to authorize plugins and
PhpUnitCliTest::testFunctionalTestDebugHtmlOutputfailed for some reason, but I did not run into any issues with deprecations as per the original report in #2933991: Deprecation tests fail when all PHPUnit tests are run via PHPUnit.Therefore as far as I can see this code is redundant, presumably due to changes either in PHPUnit or the Symfony PHPUnit bridge, and can be removed.
Comment #13
neclimdulThanks.
maybe connected but we don't want to exclude the files this was triggering on. It was because we where running
class_existon @covers tags and that was was triggering autoloading and bubbling up deprecation's outside the actual @legacy handling.We have an explicit test for this though and it doesn't seem to be a problem for some reason. Something in the stack maybe shook out to behave different or that test is broken.
I also wonder if PHPStan can handle this whole thing better. The newest hammer and all but static analysis would neatly avoid this autoloading problem. PHPStan upstream might even want the rule, its not a Drupal specific.
Comment #14
mondrakeRe #12 the failure of
PhpUnitCliTest::testFunctionalTestDebugHtmlOutputmay be due to wrong setting of env variable BROWSERTEST_OUTPUT_DIRECTORY or a test webserver not running? That test introduced by #3254723: Remove SimpletestUiPrinter BTW.Do I understand right this was originally due to failing test coverage report? In that case is it worth then trying to run PHPUnit with one of the coverage report options? https://phpunit.readthedocs.io/en/9.5/textui.html#command-line-options
Comment #15
longwave@mondrake I don't think so because we skip all processing if coverage reports are enabled:
BROWSERTEST_OUTPUT_DIRECTORY was not set which is why PhpUnitCliTest failed. Maybe we should skip that test if it would fail because of that? edit: there is a skip already but it is too strict, in my case it is an empty string and not FALSE.
Comment #16
mondrakeSo we need to remove this from phpstan's baseline then
and then call this done?
Comment #17
longwaveAddressed #16, also took the opportunity to refactor away
isTestSuite()as it seems pointless.Comment #18
mondrakeThanks.
Re #15 do you mind opening a followup to #3254723: Remove SimpletestUiPrinter?
Comment #19
longwaveOpened #3264764: PhpUnitCliTest::testFunctionalTestDebugHtmlOutput fails if BROWSERTEST_OUTPUT_DIRECTORY is an empty string
Comment #21
mondrakeTestbot fluke
Comment #23
catchNice to be able to just remove this. As a test listener this counts as @internal so I don't think we need any bc for the methods, but leaving in 10.0.x since that's also the only branch with phpstan.
Committed f555c85 and pushed to 10.0.x. Thanks!