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

longwave created an issue. See original summary.

longwave’s picture

This 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...

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

neclimdul’s picture

Issue tags: +PHPStan-0

Yeah, 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.

neclimdul’s picture

Version: 9.4.x-dev » 10.0.x-dev
Status: Active » Needs review
StatusFileSize
new2.26 KB

Here's one way of handling it.

longwave’s picture

What happens if we don't change the error handler at all? The method documentation first of all says

   * 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.

then the comment in the body says the opposite

      // Change the error handler to ensure deprecation messages are not
      // triggered.

so surely at least one of the error handler and @group legacy is redundant here?

longwave’s picture

neclimdul’s picture

StatusFileSize
new2.18 KB

Yeah 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.

mondrake’s picture

Is 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.

longwave’s picture

StatusFileSize
new2.43 KB

Rerolled #9

longwave’s picture

Locally 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:

$ ddev exec vendor/bin/phpunit -c ./core --testsuite unit
PHPUnit 9.5.11 by Sebastian Bergmann and contributors.

Testing 
...........................................................    59 / 19334 (  0%)
...........................................................   118 / 19334 (  0%)
...........................................................   177 / 19334 (  0%)
........................................................... 16166 / 19334 ( 83%)
........................................................... 16225 / 19334 ( 83%)
....S.......................S.............................. 16284 / 19334 ( 84%)
........S................drupal/core-composer-scaffold contains a Composer plugin which is currently not in your allow-plugins config. See https://getcomposer.org/allow-plugins
Do you trust "drupal/core-composer-scaffold" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [y,n,d,? ] y
.................................. 16343 / 19334 ( 84%)
I...I...................................................... 16402 / 19334 ( 84%)
........................................................... 16461 / 19334 ( 85%)
........................................................... 16520 / 19334 ( 85%)
........................................................... 19234 / 19334 ( 99%)
........................................................... 19293 / 19334 ( 99%)
.........................................                   19334 / 19334 (100%)

Time: 04:27.184, Memory: 336.00 MB

There was 1 failure:

1) Drupal\Tests\Core\Test\PhpUnitCliTest::testFunctionalTestDebugHtmlOutput
Failed asserting that 'PHPUnit 9.5.11 by Sebastian Bergmann and contributors.\n
\n
Runtime:       PHP 8.0.13\n
Configuration: /var/www/html/drupal/core/phpunit.xml.dist\n
\n
Testing Drupal\Tests\image\Functional\ImageDimensionsTest\n
.                                                                   1 / 1 (100%)\n
\n
Time: 00:05.966, Memory: 4.00 MB\n
\n
OK (1 test, 60 assertions)\n
' contains "HTML output was generated".

/var/www/html/drupal/vendor/phpunit/phpunit/src/Framework/Constraint/Constraint.php:121
/var/www/html/drupal/vendor/phpunit/phpunit/src/Framework/Constraint/Constraint.php:55
/var/www/html/drupal/core/tests/Drupal/Tests/Core/Test/PhpUnitCliTest.php:52
/var/www/html/drupal/vendor/phpunit/phpunit/src/Framework/TestResult.php:726
/var/www/html/drupal/vendor/phpunit/phpunit/src/Framework/TestSuite.php:678
/var/www/html/drupal/vendor/phpunit/phpunit/src/Framework/TestSuite.php:678
/var/www/html/drupal/vendor/phpunit/phpunit/src/Framework/TestSuite.php:678
/var/www/html/drupal/vendor/phpunit/phpunit/src/Framework/TestSuite.php:678
/var/www/html/drupal/vendor/phpunit/phpunit/src/TextUI/TestRunner.php:670
/var/www/html/drupal/vendor/phpunit/phpunit/src/TextUI/Command.php:143
/var/www/html/drupal/vendor/phpunit/phpunit/src/TextUI/Command.php:96

FAILURES!
Tests: 19334, Assertions: 36696, Failures: 1, Skipped: 6, Incomplete: 3.

Legacy deprecation notices (5)
Failed to execute command vendor/bin/phpunit -c ./core --testsuite unit: exit status 1

In other words I ran into #3255749: Composer v2.2 prompts to authorize plugins and PhpUnitCliTest::testFunctionalTestDebugHtmlOutput failed 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.

neclimdul’s picture

Thanks.

we are kinda encapsulating PHPUnit-brdige deprecation handler through DeprecationListenerTrait

maybe connected but we don't want to exclude the files this was triggering on. It was because we where running class_exist on @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.

mondrake’s picture

Re #12 the failure of PhpUnitCliTest::testFunctionalTestDebugHtmlOutput may 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

longwave’s picture

@mondrake I don't think so because we skip all processing if coverage reports are enabled:

  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;
    }

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.

mondrake’s picture

Status: Needs review » Needs work

So we need to remove this from phpstan's baseline then

		-
			message: "#^Call to an undefined static method PHPUnit\\\\Util\\\\ErrorHandler\\:\\:handleError\\(\\)\\.$#"
			count: 1
			path: tests/Drupal/Tests/Listeners/DrupalListener.php

and then call this done?

longwave’s picture

Status: Needs work » Needs review
StatusFileSize
new3.57 KB

Addressed #16, also took the opportunity to refactor away isTestSuite() as it seems pointless.

mondrake’s picture

Status: Needs review » Reviewed & tested by the community

Thanks.

Re #15 do you mind opening a followup to #3254723: Remove SimpletestUiPrinter?

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 17: 3211131-17.patch, failed testing. View results

mondrake’s picture

Status: Needs work » Reviewed & tested by the community

Testbot fluke

  • catch committed f555c85 on 10.0.x
    Issue #3211131 by longwave, neclimdul, mondrake: Call to an undefined...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Nice 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!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.