Problem/Motivation
PHPUnit 8 fails somewhat aggressively for its own deprecations, with TestCase::addWarning()
. New methods that don't exist for PHPUnit 6 are required for tests to pass on PHPUnit 8. To work around this, Drupal 9.0 overrode TestCase::addWarning()
to skip warnings for all new deprecation in PHPUnit 8, in \Drupal\Tests\Traits\PHPUnit8Warnings
. This works differently than the suppression of deprecation failures from the Symfony PHPUnit bridge.
As we cleaned up core's use of the deprecated methods, we removed each warning from the list of silenced warnings. This has mostly happened during Drupal 9.0's beta, while 8.7 is still supported and 9.1.x is open for new development..
Because the warnings are being raised by PHPUnit itself and not the PHPUnit bridge, this means marking tests @legacy
or using --suppress-deprecations
on Drupal CI is not sufficient to avoid a test failure. It also means that tests that need to assert things related to these assertions can't be compatible with 8.7 and 9.0's beta at the same time, because even though we backported foward-compatibility shims all the way into 8.8 during its stable release, we can't and won't backport new APIs to the security-support-only branch. (Furthermore, a couple methods could not be backported to Drupal 8 because they would not be straightforward to implement on PHP 7.0.)
Proposed resolution
Normally, we don't add disruptive changes like new deprecations to a branch after it's in beta. Disruptive changes should only be going into 9.1.x at the moment. Unsilencing a deprecation is a disruptive change.
So, restore the full list of PHPUnit8 warnings to ignore in 9.0.x only. That way, 9.0 compatibility doesn't require dealing with the full gamut of "new" deprecations from PHPUnit 8, and more module can use 8^ || 9^
compatibility rather than ^8.8. || ^9
. 9.1.x will still fail if we accidentally reintroduce usage of one of the deprecated methods, and since the backport policy requires things be committed to 9.1.x first, that means we're unlikely to regress 9.0 either. (Both 9.0 and 8.9 will need to be compatible with PHP 8, and therefore PHPUnit 9.)
Remaining tasks
Decide if this is actually the best way to go, or if we do want these fails to force contrib to be forward-compatible with PHPUnit 9 (and therefore PHP 8).
User interface changes
API changes
Data model changes
Release notes snippet
Comment | File | Size | Author |
---|---|---|---|
#81 | 3142267-2-81.patch | 15.26 KB | alexpott |
#81 | 72-81-interdiff.txt | 671 bytes | alexpott |
#72 | 3142267-72.patch | 15.26 KB | jungle |
#72 | raw-interdiff-66-72.txt | 182 bytes | jungle |
#72 | raw-interdiff-53-72.txt | 416 bytes | jungle |
Comments
Comment #2
xjmComment #3
xjmComment #4
jungleAdding a related issue #3027952: [Plan] Remove the usage of deprecated methods in tests which might get affected a little.
Comment #5
xjmOops, betting this will cause a fail. Removing it wasn't wholly intentional. (I just checked out the original version of the trait.)
Comment #6
xjmComment #8
alexpottHow about we put then in a new list of warnings and use the SYMFONY_DEPRECATIONS_HELPER flag and ignore them if it is equal to disabled or the test has the legacy group?
That way a project can move forward quickly without having to fix these things. And it'll make it easier to prep 8.9.x for PHPUnit 9.
Comment #9
xjmSomething like this?
We could discuss whether to also commit this to 9.1.x or to raise hard failures there.
Comment #10
longwaveDouble negative here is a bit hard to read, this can just be === I think?
Otherwise this seems like a good approach.
Comment #11
xjm@longwave Ah yep, thanks I actually hemmed and hawed a bit about the negation and the strict comparison, but that was before I moved stuff around from a previous iteration.
Comment #12
xjmAddresses #10 and also adds 3/4 the test coverage.
Comment #13
xjmThis use statement is currently unused because...
...I thought I'd figured out how PHPUnit warnings worked, but after reading it again I think this may be a thing where it converts PHP warnings to exceptions, not its own warnings being handled as exceptions. Couldn't work out how to treat the warning as expected, but maybe there isn't a way (because if there were a straightforward way to do that then we probably wouldn't have to have overridden
addWarning()
in the first place).If anyone has a notion how to test for case 4, feel free.
Comment #14
xjmEr, and it's a test warning obviously, not a test assertion.
Comment #15
catchThe proposed resolution seems good here, although definitely agreed this should only happen in 9.0.x so that they start getting surfaced again in 9.1.x
Comment #16
xjm@catch Well, a module could legitimately decide it doesn't care about PHPUnit 9 support (and therefore PHP 8 support), so respecting the behavior of
--suppress-deprecations
could be useful for all branches. It's only an accident of how PHPUnit has chosen to handle these deprecations that tests are failing.And it's reasonably possible we'll run into the same thing again with PHPUnit 10 if they continue to use
addWarning()
to raise a deprecation; even if we make 9.0 play nice, we'll still have to jump through hoops to support the multiple major versions at once again later.Comment #17
xjmComment #18
xjmJust removing the @todo code from the test so that this can be NR, and also making the inline docs in both methods consistent. I'm not sure testing the last case is possible without further overriding
addWarning()
with API to test overridingaddWarning()
, which feels very self-referential.Comment #19
xjmWell, the docs shouldn't be completely consistent.
Comment #20
alexpottRe #18 yeah doing that feels wrong. We could also use reflection to inspect the warning private and then reset it which is also wrong. It is testable but we'd need to test in the same way that PHPUnit tests itself for end-to-end testing. To do this we'd need to support .phpt tests. Then we could make the test something like:
And then add a file called core/tests/Drupal/Tests/SkippedDeprecationTest.php which contains something like:
I'll open a follow-up to discuss adding support for this type of test. PHPunit will run .phpt tests but our \Drupal\Core\Test\TestDiscovery very much only works with classes and makes a series of assumptions.
Comment #21
alexpottThis looks RTBC to me apart from a minor nit - and something that might need a followup issue with the coder module.
Unfortunately this fails our PHPCS check with
I considered whether we should add something like
To detect whether the warning is occurring in a legacy test but I think only using the SYMFONY_DEPRECATIONS_HELPER is the way to go here. As I'm not sure what we should do in this case. Like should we be converting the warning into an @trigger_error? Feels like extra complexity. And this change has the desired effect. Contrib and custom can test without this tripping them up and the y can also test and see the warning - all with the convenience of using the same flag that we already use to surface deprecations in the test system.
Actually maybe that suggests a neater way of approaching the whole issue. We could convert the entire original list into deprecation errors (i.e. @trigger_error('blah', E_USER_DEPRECATED)) and then in \Drupal\Tests\Listeners\DeprecationListenerTrait::getSkippedDeprecations() skip the one core still triggers. That's still allow contrib/custom to ignore these with SYMFONY_DEPRECATIONS_HELPER but they'd be handled like all the other deprecations we have. It would also mean less code paths and simpler testing.
Comment #22
xjm@catch and I agreed that this can go into 9.1.x as well with the current approach being configurable anyway.
I'm not totally sure we should do our own
@trigger_error()
since these are PHPUnit's errors. It's one thing to let them not fail tests, but it seems like if the tests do fail they should still do it with the PHPUnit warning?Comment #23
alexpottWell the advantage of using @trigger_error is that we have a single logic path so we don't have to add
For testing purposes because the list will never change as we won't need to change it. Also it means that all code paths are testable with our current infrastructure. I think less logic and more testable is a good reason to do something.
Comment #24
xjmThe PHPUnit warnings fail like:
Whereas deprecations fail like:
Comment #25
alexpottAnother advantage of converting to @trigger_error() is support for ignoring the deprecations where you are doing deprecations in legacy tests.
Comment #26
mondrake+1 in the idea to convert the warning into a @trigger_error.
Also I think that we should not override addWarning in the trait, since it’s a PHPUnit internal: https://github.com/sebastianbergmann/phpunit/blob/4dc5717d74120d27d7dc37...
IMHO the correct way should be somehow to intercept the added warning in a test listener ... but I have not checked if possible :)
Comment #27
alexpott@mondrake I don't think
is possible. The way warnings work in PHPUnit occurs outside anything that could catch the exception it results in. See \PHPUnit\Framework\TestCase::runBare() and once $listener->addWarning() is being called in \PHPUnit\Framework\TestResult::addWarning() the test is already marked as failing.
Comment #28
mondrake@alexpott thanks for feedback. It's a pity, though. Need to keep a watch on what PHPUnit 10 will bring in terms of replacing the listeners.
Comment #29
xjmI care more about getting this in to stop the bleeding than I do about the exact fails, but I still think it is a wrong choice architecturally to "demote" PHPUnit's own warnings from an actual warning to an "OK" message, especially since that will also change the results on the command line when the tests are run locally.
I'll provide a patch that implements the other way and then get a third committer opinion.
Comment #30
xjmHere's a patch that nerfs the warnings into
E_USER_DEPRECATED
. Note that #23 is not correct, we still have to add the test messages. That's also whatgetSkippedDeprecations()
already does. In fact, we now have to add one of the entries in two places plus the test, rather than only one place plus the test.It is one less code path in the method itself, and it's nicer to not have to fiddle environment variables in a test, but I still think actually converting PHPUnit's version of deprecations into our own is not the right choice.
Comment #31
xjmForgot the test-only patch.
Comment #33
xjmExpected fail.
Comment #34
longwaveOne thing I don't get is why these switches exist:
It seems like fail-on-warning is happening by default, but I can't see where, or how we can turn it off again.
@see should be fully qualified, ie. with a leading backslash.
Comment #35
xjmIndeed it should.
Comment #36
jungleJust FYI, not sure if it's relevant here:
\Drupal\Tests\Listeners\DrupalListener
implements a deprecated interfaceTestListener
And a link: Make TestHook interfaces as easy to use as TestListener #3390
Comment #37
alexpottAnother advantage of changing to deprecations is that we can actually use the deprecated in a method in a test rather than adding
We don't need to test skipping deprecations as that functionality is not part of this change. And if we actually call the deprecated methods then we don't need the other string.
The patch attached contains a test of the each deprecated method and shows that we're triggering the expected deprecations and more importantly that PHPUnit is not triggering other warnings. This means that if PHPUnit 8 adds any new deprecations in the meantime that effect these methods then this test will catch it. And on the issue that fixes the
expectExceptionMessageRegExp() is deprecated in PHPUnit 8 and will be removed in PHPUnit 9.
deprecation we can add another test to PHPUnit8WarningsTest.It also means that if there other deprecated PHPUnit 8 methods that contrib is leveraging then we can add them to the list and test the deprecation in core too.
If we make Drupal compatible with PHPUnit 9 then we'll need to skip this test there but that's okay too.q
Comment #38
alexpottOops forgot to revert the changes to PhpUnitBridgeTest.
Comment #40
mondrakeCan't we find a way not to have this in two places, but just in one, and have both DeprecationListenerTrait and PHPUnit8Warnings implementations access it? Reduce c/p carpal tunnel syndrome symptoms.
Comment #41
alexpott@mondrake I don't think so. The point is that we need to do the work to remove it from the skipped deprecations and then it remains in the PhpUnit8Warnings trait. The skipped deprecation list is a list of technical debt that we have. We can easily remove it from the list so we have it in one place - we do the work in core to stop calling the method. Created #3143235: Convert ::expectExceptionMessageRegExp to ::expectExceptionMessageMatches to handle this.
Comment #42
alexpottSo if we land #3143235-4: Convert ::expectExceptionMessageRegExp to ::expectExceptionMessageMatches in 9.0.x and 9.1.x then we don't have to add this to the list of skipped deprecations and can add a test for expectExceptionMessageRegExp triggering a deprecation.
Comment #43
alexpottNow that #3143235: Convert ::expectExceptionMessageRegExp to ::expectExceptionMessageMatches has landed we don't need to skip it.
Comment #44
xjmThis is removing a lot of test coverage; why are we doing that?
Comment #45
xjmIt is part of the change to validate that the PHPUnit warnings behave the same way both skipped and un-skipped as the deprecation warnings. We can't just assume that's the case because that's what the API does -- it needs to actually be tested as well.
Comment #46
mondrakeIn #3127141: Use PHPUnit 9 for PHP 7.4+, while keeping support for PHPUnit 8.4 in PHP 7.3 we are suggesting to replace
PHPUnit8Warnings
with a more generic class name - since it's pretty clear this is there to stay in later PHPUnit versions. PHPUnit 9 already has warnings triggered for methods that will be remoed in PHPUnit 10.I wonder whether this is the right time to do the renaming in this issue, since the other issue will take longer probably.
Comment #47
xjmAlso I don't see why we need to duplicate the test coverage that PHPUnit hopefully already has for these? They're not our APIs.
Comment #48
xjm@mondrake re: #46 I was actually thinking the same thing while working on this, but I just wanted to get it in to stop the pain for people porting their modules to D9 during the porting sprint and through release. However it's been taken out of my hands now apparently to get a simple mitigation in and we're scope-creeping things to provide test coverage for PHPUnit etc. so honestly renaming the trait here seems fine.
However, that means it's becoming even more of an actual API, which, again, I really think deserves independent, decoupled test coverage for the functionality rather than test coverage coupled to the specific current list and to PHPUnit itself.
Comment #49
alexpottRe #44 because this does not add or change the logic for skipping deprecations that is tested elsewhere. PHPUnit adds deprecations mid cycle so if we want to support these calls we need to be sure that future versions of these asserts do not trigger other warnings. For example assertAttributeEquals currently triggers 3 warnings. If PHPUnit deprecates something else used in any of the calls then we need to know.
The point about the list changing and therefore we should add our own is a good one. Added that back in.
Renamed the class as #48.
Comment #50
xjmIt doesn't change the logic for skipping deprecations, but the logic for skipping deprecations could change. This is a new API that triggers the deprecations in a new way, and therefore it could behave differently.
Still also dunno why we are adding deprecation tests of PHPUnit's own code. Do we think PHPUnit lacks sufficient internal test coverage, or what?
Comment #51
longwave> Still also dunno why we are adding deprecation tests of PHPUnit's own code
If PHPUnit changes any of the warning messages we will no longer convert them to deprecations, these tests will catch that.
Comment #52
xjmProbably should update this text as well: not necessarily just Drupal 8 vs. 9, and generally "multiple versions of PHPUnit" also. Because PHPUnit 9 and 10 and etc. are mostly a D9-only problem; by the time we adopt them, D9's API will be onto at least one new minor and therefore not necessarily compatible with 8.9 anymore.
Comment #53
alexpottTried to update the documentation to account for #52.
I'm not sure it is triggering deprecations in a new way. It's using the same
@trigger_error(message, E_USER_DEPRECATION)
as everywhere else. And the skipping of such deprecations is tested already by \Drupal\Tests\SkippedDeprecationTestComment #54
xjmOK, I guess that makes sense. Note though that we could also provide the same kind of coverage even if they were still warnings. It wouldn't be as slick as an
@expectedDeprecation
, sure, but it's also 100% possible to test by having a@legacy
test that just calls every deprecated assertion/usage pattern. If PHPUnit changed the message then, we'd start getting warnings again and the test would fail.So, given that: I still don't think there's a substantive difference in the testability aside from the one case where we want to expect a warning. For that, I guess that one case is what @longwave's question in #34 would help with. I also couldn't get it to change what happened on the CLI at all.
It does sound like it's supposed to be configurable via the XML configuration file:
https://phpunit.readthedocs.io/en/9.0/configuration.html?highlight=--sto...
So if we did this:
...then we would no longer fail on warnings. Obviously we don't want to change the whole setting because other warnings should legitimately fail. However, if we could change it conditionally in the one test... This is way outside my expertise though. So maybe there is some way to do that, although it could be a hacky one.
Still not super thrilled about losing the test coverage that's independent of existing deprecation messages, though, or the general approach of turning them into deprecations in the first place. For me seeing the yellow
W
on the command line when I'm running tests gives a much clearer signal that this is a direct problem with the test itself instead of with the code that's being tested. I wonder if or how this might affect various build pipelines as well.I did reach out to both @catch and @larowlan and... neither had a strong/clear opinion on which approach to take.
Comment #55
xjmOh and re:
Internally within the method, yes. But that's an implementation detail. We shouldn't only test what we know to be the current implementation of the thing and trust that the current implementation will behave the way we expect in all four scenarios just because we wrote what we thought would work that way. That's not robust. I honestly don't know why this is a point of controversy.
Comment #56
alexpottRe #54.3 and #55 - #49 added a test that's independent of existing deprecation messages.
[Edit: originally point to #53 as where the test was added but I was wrong]
Comment #57
mondrakeComment #58
mondrakeI think this should go in 9.1.x first, without the hunks below, to allow consistently renaming of the trait across branches. We should not have the hunks below in 9.1.x because if at any point we will support PHPUnit 9, they will simply fail (unless we put BC shims around).
1.
2.
Comment #59
alexpottIn order to allow PHPUnit 9 on Drupal 9 we're going to need the forward compatible shims so I disagree about removing the tests. I think that's one of the reasons why we need the tests - we need to ensure our forward compatible shims work as we want them to.
Comment #60
mondrakeThen this one is good to go IMHO.
Comment #61
mondrakeBTW, BC shims would be relatively easy for these methods that are no longer present in PHPUnit 9
but where PHPUnit 9 keeps the method but changes the signature, things will be more complicated.
assertContains
on strings will probably require rewriting PHPUnit's Assert class.Comment #62
alexpott@mondrake that's a good point. I think we're in the "we'll do our best" with respect to forward compatibility here. But at least this test keeps us honest and if we have to skip the one where the method if changed on PHPUnit9 then so be it - but at least we'll have it documented and continued to be tested on PHPUnit8. There, is of course an on the other hand, if we leave the warnings as they - they'll get surfaced in contrib / custom projects quicker and therefore resolved quicker and therefore it'll be easier for these projects to support both PHPUnit 8 and 9 when core does.
Comment #63
mohrerao CreditAttribution: mohrerao as a volunteer and at Srijan | A Material+ Company for Drupal India Association commentedComment #64
mohrerao CreditAttribution: mohrerao as a volunteer and at Srijan | A Material+ Company for Drupal India Association commentedRerolled #53
Comment #65
mondrakeTests are failing.
Comment #66
mohrerao CreditAttribution: mohrerao as a volunteer and at Srijan | A Material+ Company for Drupal India Association commentedFixed previous CI failure.
Comment #67
alexpott@mohrerao please post interdiffs when you make a change to a patch. Here's one for #66.
Comment #69
mohrerao CreditAttribution: mohrerao as a volunteer and at Srijan | A Material+ Company for Drupal India Association commented@alexpott, #67 was a reroll #53 which failed in #64. Thats why i didnt add interdiff.
Looking into failed tests now.
Comment #70
mohrerao CreditAttribution: mohrerao as a volunteer and at Srijan | A Material+ Company for Drupal India Association commentedTried running tests locally after applying patch and tests are passing. Can some one explain why these failures are coming with Drupal CI?
Comment #71
mondrake@mohrerao take a look at the console log of the DrupalCI test run: https://dispatcher.drupalci.org/job/drupal_patches/56228/consoleFull
the error is in the build tests phase:
Fatal error: Trait 'Drupal\Tests\Traits\PHPUnitWarnings' not found in /var/www/html/core/tests/Drupal/BuildTests/Framework/BuildTestBase.php on line 52
probably an
use
statement is missing somewhereComment #72
jungleRe #71,
Drupal\Tests\Traits\PHPUnitWarning
is there. But I can not figure it out why #66 did not pass. So rerolled from #53. But attached a raw-interdiff from #66 as well.Comment #73
mondrakeStrange, it looks like a case sensitivity issue, given interdiff vs #66
Comment #75
dwwDrupal\Tests\media_library\FunctionalJavascript\WidgetUploadTest strikes again. :(
Comment #76
mondrakeBumping to Critical since this is in the critical path to PHP 8.
Comment #77
jungleYes, you're right.
PHP
UnitWarnings vsPhp
UnitWarningsComment #78
larowlanWas there an issue opened with phpunit to split addWarning into addDeprecation/addWarning as that seems like a longer-term solution here? The use of one method for two concepts is how we got here.
Looking at these two approaches again, I think the trigger_error approach is more consistent with how we handle deprecation elsewhere.
Comment #79
andypostIt's blocker for #3127141: Use PHPUnit 9 for PHP 7.4+, while keeping support for PHPUnit 8.4 in PHP 7.3
Re #78 idea with addDeprecation vs addWarning interesting but looks huge api change, it could need to rename trait too, but I lack of reason to introduce this change... re-using deprecation listener for that also could have wrapper
Comment #80
alexpott@larowlan I know there has been some discussion between the Symfony project and PHPUnit project about functionality in PHPUnit bridge. However I couldn't find anything in the PHPUnit issue tracker so I created https://github.com/sebastianbergmann/phpunit/issues/4461
Comment #81
alexpottPatch attached fixes this.
Comment #83
catchOK I still don't have a preference between the approaches, but @larowlan in #78 slightly tips it in favour of the current patch, so...
Committed ab58ca4 and pushed to 9.1.x. Thanks!