Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed PHPUnit 8.3 incompatibility: method handleError was renamed to __invoke #32890

Closed
wants to merge 1 commit into from

Conversation

karser
Copy link
Contributor

@karser karser commented Aug 2, 2019

Q A
Branch? 4.3
Bug fix? yes
New feature? no
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets #32879
License MIT

The PHPUnit method handleError was renamed to __invoke in v8.3.

So we should check in Symfony DeprecationErrorHandler if method handleError exists, otherwise call __invoke

It works with phpunit v8.2.5 and 8.3.2.
The PHPUnit handler is called when I trigger some error, e.g iconv('fdsfs', 'fsdfds', '');

@smoench
Copy link
Contributor

smoench commented Aug 2, 2019

@karser On DeprecationErrorHandler:L111 there is also a ::handleError-call

@karser
Copy link
Contributor Author

karser commented Aug 2, 2019

@karser On DeprecationErrorHandler:L111 there is also a ::handleError-call

True. Do we need this autoload juggling? Didn't we already define which ErrorHandler class to use on line 76?

static $autoload = true;

$ErrorHandler = class_exists('PHPUnit_Util_ErrorHandler', $autoload) ? 'PHPUnit_Util_ErrorHandler' : 'PHPUnit\Util\ErrorHandler';
$autoload = false;

@karser karser force-pushed the phpunit83-errorhandler-fix branch from 0865fc4 to 4e9e3d7 Compare August 2, 2019 20:20
@karser
Copy link
Contributor Author

karser commented Aug 2, 2019

@smoench Fixed. callPhpUnitErrorHandler is now static so it can be called from set_error_handler closure.

@nicolas-grekas nicolas-grekas added this to the 4.3 milestone Aug 2, 2019
Copy link
Contributor

@dominikhajduk dominikhajduk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested and it solves issue. Thanks!

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Aug 3, 2019

Not sure how to deal with these booleans. In TestResult class they are all true by default

this is a critical blocker :(

we need to completely redesign the way we hook there to collect deprecations.
until 8.2, phpunit used global state and it was easy to hook, albeit not than clean I realize

now we need to do this in our listener: when TestResult calls startTest in its run method, we need to register phpunit's error handler (duplicating what it does just after the call to startTest) and then add our collector. Then disable at endTest.

Willing to dive there @karser? Maybe with help from @greg0ire too?

Note that we need to do this for 3.4 and for 4.3 (since our code changed so much between the two)

{
$ErrorHandler = self::$utilPrefix.'ErrorHandler';
if (self::$isHandlerInvokable) {
$object = new $ErrorHandler($convertDeprecationsToExceptions = true, $convertErrorsToExceptions = true, $convertNoticesToExceptions = true, $convertWarningsToExceptions = true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

variables $convertDeprecationsToExceptions, $convertErrorsToExceptions are not used and should be omit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was done on purpose because (true, ture, true, true) is not readable. FWIW @nicolas-grekas suggests to move this code to phpunit listener.


$handler = new self();
$oldErrorHandler = set_error_handler([$handler, 'handleError']);

if (null !== $oldErrorHandler) {
restore_error_handler();

if ([self::$utilPrefix.'ErrorHandler', 'handleError'] === $oldErrorHandler) {
$handlerMethod = self::$isHandlerInvokable ? '__invoke' : 'handleError';
if ([self::$utilPrefix.'ErrorHandler', $handlerMethod] === $oldErrorHandler) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the previous handler is the PhpUnit one, then $oldErrorHandler will contains an instance of PHPUnit\Util\ErrorHandler

@karser
Copy link
Contributor Author

karser commented Aug 3, 2019

@nicolas-grekas I'm not fully following what happens in phpunit bridge, so I'll ask some stupid questions:

  • Does DeprecationErrorHandler do anything other than collecting deprecations? So it means that DeprecationErrorHandler will be entirely moved to the phpunit listener?
  • There is Symfony\Bridge\PhpUnit\SymfonyTestsListener, so should we use this listener?
  • TestListener interface is deprecated (still works though) in phpunit 8. Should we consider TestHook interface?

@nicolas-grekas
Copy link
Member

Does DeprecationErrorHandler do anything other than collecting deprecations?

it copes only with that responsbility yes

So it means that DeprecationErrorHandler will be entirely moved to the phpunit listener?

It looks like so. Currently, it doesn't need to be registered as a listener, but it looks like phpunit 8 changes that.

There is Symfony\Bridge\PhpUnit\SymfonyTestsListener, so should we use this listener?

I'd say yes, one listener is easier to setup for users

TestListener interface is deprecated (still works though) in phpunit 8. Should we consider TestHook interface?

yes, although it could be done in another PR if that's easier?

@karser
Copy link
Contributor Author

karser commented Aug 3, 2019

@nicolas-grekas Ok, I'll try to do what you described, let's see how far I can get.
Sure, I'll do that in another PR, because this one already solves the problem, though not prefect.

@nicolas-grekas
Copy link
Member

Another alternative might be to check how $convertDeprecationsToExceptions is defined, and maybe duplicate the logic that turns it to true/false. That would be the easiest, if doable.

As of now, I wouldn't recommend merging the PR, as putting all these settings at true is a bug that someone will report sooner than later.

@karser
Copy link
Contributor Author

karser commented Aug 3, 2019

I'll do the research first. Looks like these variables were introduced only in 8.3 and didn't exist in 8.2

@nicolas-grekas
Copy link
Member

Maybe we can have a look at the stack trace and read the properties from there? It looks like we might find the TestResult instance there, with all the state we need?

@karser
Copy link
Contributor Author

karser commented Aug 3, 2019

TestResult is created by TestRunner which passes $arguments which contain the neccessary parameters.

image

$arguments are created by TestRunner::handleConfiguration. Here is simpllified function which does exactly what we need:

    private function handleConfiguration(array &$arguments): void
    {
        if (isset($arguments['configuration']) &&
            !$arguments['configuration'] instanceof Configuration) {
            $arguments['configuration'] = Configuration::getInstance(
                $arguments['configuration']
            );
        }

        if (isset($arguments['configuration'])) {
            $arguments['configuration']->handlePHPConfiguration();

            $phpunitConfiguration = $arguments['configuration']->getPHPUnitConfiguration();

            if (isset($phpunitConfiguration['convertDeprecationsToExceptions']) && !isset($arguments['convertDeprecationsToExceptions'])) {
                $arguments['convertDeprecationsToExceptions'] = $phpunitConfiguration['convertDeprecationsToExceptions'];
            }

            if (isset($phpunitConfiguration['convertErrorsToExceptions']) && !isset($arguments['convertErrorsToExceptions'])) {
                $arguments['convertErrorsToExceptions'] = $phpunitConfiguration['convertErrorsToExceptions'];
            }

            if (isset($phpunitConfiguration['convertNoticesToExceptions']) && !isset($arguments['convertNoticesToExceptions'])) {
                $arguments['convertNoticesToExceptions'] = $phpunitConfiguration['convertNoticesToExceptions'];
            }

            if (isset($phpunitConfiguration['convertWarningsToExceptions']) && !isset($arguments['convertWarningsToExceptions'])) {
                $arguments['convertWarningsToExceptions'] = $phpunitConfiguration['convertWarningsToExceptions'];
            }
        }
        $arguments['convertDeprecationsToExceptions']                     = $arguments['convertDeprecationsToExceptions'] ?? true;
        $arguments['convertErrorsToExceptions']                           = $arguments['convertErrorsToExceptions'] ?? true;
        $arguments['convertNoticesToExceptions']                          = $arguments['convertNoticesToExceptions'] ?? true;
        $arguments['convertWarningsToExceptions']                         = $arguments['convertWarningsToExceptions'] ?? true;
    }

@karser karser force-pushed the phpunit83-errorhandler-fix branch 2 times, most recently from c32c4a4 to ede0976 Compare August 3, 2019 21:56
@karser karser force-pushed the phpunit83-errorhandler-fix branch from ede0976 to 12717c9 Compare August 3, 2019 22:01
@karser
Copy link
Contributor Author

karser commented Aug 4, 2019

I extracted this logic into ErrorHandlerCallerV83. It obtains the necessary variables the way PHPUnit does and instantiates ErrorHandler. I made sure that these variables are changed when I modify them in phpunit.xml. it works with both v8.2 and v8.3.
@nicolas-grekas WDYT?

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Aug 4, 2019

Maybe in another PR to keep this one as is: could you please give a try to the stacktrace-based approach? I feel like it could be simpler and more robust.

@karser
Copy link
Contributor Author

karser commented Aug 4, 2019

Indeed, using debug_backtrace is MUCH easier approach. I didn't know that it's possible to get to TestResult object this way. The only issue - what to do if TestResult instance is missing in the stacktrace? Throw an exception then?
image

@nicolas-grekas
Copy link
Member

We should then call the previous handler if one was set, or return false

@karser
Copy link
Contributor Author

karser commented Aug 4, 2019

Here is stacktrace-based PR #32933

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants