Problem/Motivation
According to the change record Updated to PHPUnit 9:
Drupal 9.1.x has updated to PHPUnit 9. Installs on PHP 7.3 will continue to use PHPUnit 8.4 for compatibility reasons.
As of PHPUnit 8, \PHPUnit\Framework\TestCase
added a void
return type hint to the following methods:
protected function setUp(): void
protected function tearDown(): void
public static function setUpBeforeClass(): void
protected function assertPostConditions(): void
protected function assertPreConditions(): void
(not currently used by core Drupal)public static function tearDownAfterClass(): void
(not currently used by core Drupal)protected function onNotSuccessfulTest(): void
(not currently used by core Drupal)
See https://phpunit.de/announcements/phpunit-8.html
This issue is to correct the signature used for these methods in core, for all classes that extend \PHPUnit\Framework\TestCase
, which includes classes that extend \Drupal\Tests\UnitTestCase
, \Drupal\KernelTests\KernelTestBase
, and \Drupal\Tests\BrowserTestBase
.
Specifically, this issue is to ensure that the void return type hint is used and that the protected
visibility is declared properly. The protected
visibility mainly affects core's setUp()
and tearDown()
methods because there are still a large number of cases where public
has been improperly used.
User interface changes
None.
API changes
The void
return type hint technically constitutes an API change, but the protected
visibility does not - setUp()
and tearDown()
have been declared as protected in core Drupal since early in Drupal 7. Fixing visibility for these methods is just adhering to our existing API.
Data model changes
None.
Original issue summary:
On Drupal 9.0.7, I tried to run Upgrade Rector, and got the following fatal error:
PHP Fatal error: Declaration of Drupal\Tests\BrowserTestBase::setUp() must be compatible with PHPUnit\Framework\TestCase::setUp(): void in /Users/dan/coding/drupal-ch/web/core/tests/Drupal/Tests/BrowserTestBase.php on line 388
I see that we applied fixes in #3114640: Declaration of Drupal\Tests\UnitTestCase::setUp() must be compatible with PHPUnit\Framework\TestCase::setUp(): void and #3114041: PhpUnit 8 tests breaking because of compatibility issue with setUp() but it looks like we missed this file and many others.
When I do a search for setUp() { I get 86 results in 86 files, do we want to patch all of them in this one issue?
The fix is to simply add the return type of void:
- protected function setUp() {
+ protected function setUp(): void {
Comment | File | Size | Author |
---|---|---|---|
#40 | 3182103-40.patch | 127.77 KB | gidel |
#39 | drupal-3182103-0905-39-phpunit-typehints.patch | 127.74 KB | Sweetchuck |
#38 | 3182103-38.patch | 139.8 KB | Grimreaper |
#37 | 3182103-37-9.4.x.patch | 139.95 KB | Grimreaper |
#35 | 3182103-35-UpdateUploaderTestBase-left-out.patch | 127.57 KB | Balu Ertl |
Issue fork drupal-3182103
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
dan2k3k4 CreditAttribution: dan2k3k4 at Amazee Labs commentedAdded a merge request with all of the files above being patched:
https://git.drupalcode.org/project/drupal/-/merge_requests/28
Comment #4
dan2k3k4 CreditAttribution: dan2k3k4 at Amazee Labs commentedOh it looks like tearDown is also needed to be updated in the same way:
PHP Fatal error: Declaration of Drupal\Tests\BrowserTestBase::tearDown() must be compatible with PHPUnit\Framework\TestCase::tearDown(): void in /Users/dan/coding/drupal-ch/web/core/tests/Drupal/Tests/BrowserTestBase.php on line 468
There were only 8 files that needed to be tweaked. I pushed / updated my merge request.
Comment #5
mondrakeHi, these are all BASE test classes and were left out on purpose in #3107732: Add return typehints to setUp/tearDown methods in concrete test classes. AFAICS we will be converting these classes too in Drupal 10
Comment #6
dan2k3k4 CreditAttribution: dan2k3k4 at Amazee Labs commentedAh, thanks @mondrake - I seem to remember @mgalman or @catch mentioning to ignore tests when running Rector... I guess I need to update my rector.yml file to ignore test directories.
Comment #7
GrimreaperHello @dan2k3k4,
Thanks a lot for the MR. I had been able to apply the patch from the MR on Drupal 9.1.0. Rector working again! Thanks!
Comment #8
Kristen PolThanks for the patch. Moving back to needs work based on:
1) Was able to apply the patch cleanly to 9.2.
https://git.drupalcode.org/project/drupal/-/merge_requests/28.diff
2) After applying patch, all
setUp
andtearDown
methods includevoid
.3) There were some
setUpBeforeClass
andassertPostConditions
methods changed as well.4) Based on 3, there are two methods that still need adjusting:
5) The title and issue summary also need adjusting to match the new scope. I took a stab at a new title but it might be too specific.
Comment #9
SweetchuckThe current state of this issue is little bit confusing.
This issue belongs to Drupal core version 10.0.x.dev.
MR28 Request to merge issue:3182103-browsertestbasesetup-must-be into 9.0.x
In "drupal/core:9.2.3" the \Drupal\Tests\UnitTestCase::setUpBeforeClass still throws an error (Attached patch fixes this problem).
Patch from MR28 is not applicable to 9.2.x.
Comment #10
mondrakeWhen we'll do this, we also need to remove the BC layer.
Comment #11
mondrakeA few methods were missing the typehint.
Comment #12
TR CreditAttribution: TR commentedThe changes in #11 to
core/tests/Drupal/TestSite/TestSiteInstallTestScript.php
andcore/tests/Drupal/TestSite/TestSiteMultilingualInstallTestScript.php
are wrong.These classes do NOT extend either
BrowserTestBase
orKernelTestBase
.Instead, these classes implement
\Drupal\TestSite\TestSetupInterface
, which defines a (lowercase)public function setup();
method with no capital 'U' and no void return type hint.I would also argue that as long as we're fixing the signature of
setUp()
, let's do it right and finish the job by making all instancesprotected
instead ofpublic
. The test base classesBrowserTestBase
andKernelTestBase
both define this function asprotected function setUp(): void;
, and classes which extend these bases should not be redefining the visibility. I did not make this change in the patch - waiting to hear if anyone else considers this in-scope. (There are 98 uses ofpublic function setUp()
and 4 uses ofpublic function tearDown()
that should be corrected to beprotected
.)Comment #13
TR CreditAttribution: TR commentedBTW, it's really discouraging to fix this stuff time and time again only to have it pop back up and be pushed off to future versions of core. Here are just a few of the issues that "fixed" this: #2322889: Various setUp() and tearDown() methods are not protected, #2642236: Various setUp() and tearDown() methods are not protected (the sequel), #2858646: Methods called with incorrect case.
The signature of these methods was frequently wrong in core Drupal 7 and in core Drupal 8, and now the signatures are wrong again in Drupal 9. Contrib has been copying these core mistakes for more than 10 years, serving as a bad example for people who contribute core patches as well. Contrib can't use the correct "protected" visibility until core gets its act together and fixes core, and it's a real pain to maintain a mixture of public/protected in contrib simply to keep the tests from failing.
It would be really great if we fixed this in Drupal 9 instead of putting it off until Drupal 10. Breaking changes have been made for other reasons in D9, but unlike most of those other breaking changes this one (changing signatures on test functions) only affects tests. And in fact this isn't even a breaking change IMO because changing public to protected in core doesn't break contrib tests that use a public visibility. Likewise, adding :void in contrib is a minimal change that shouldn't inconvenience anyone for more than a few minutes.
Comment #14
mondrake+1. PHP allows to change a protected method to public in extending classes, https://3v4l.org/JFZgY (not viceversa, https://3v4l.org/qqTJS). So it is up to us to ensure consistent visibility signature. We could also implement a check in
Drupal\Tests\Listeners\DrupalListener::startTest()
similarly to what's being done already for the$modules
property.EDIT - FWIW, I think the use of
public
visibility is a leftover of Simpletest times.Comment #15
TR CreditAttribution: TR commentedFun fact: setUp() was changed from
function setUp()
(public by default) toprotected function setUp()
in Drupal 7 by:This was later even backported to the D6 contributed Simpletest module for consistency with core D7 ... so this has been broken for a really long time.
Here's patch #12 with the additional change of public -> protected for all
setUp()
andtearDown()
methods that override\PHPUnit\Framework\TestCase
,\Drupal\Tests\UnitTestCase
,\Drupal\KernelTests\KernelTestBase
, or\Drupal\Tests\BrowserTestBase
.Comment #16
TR CreditAttribution: TR commentedRemoved the 400+ lines of code diff from the issue summary. That's not helpful to understanding the issue and it was out-of-date.
Comment #17
TR CreditAttribution: TR commentedI've read through #3107732: Add return typehints to setUp/tearDown methods in concrete test classes and the sense I get is that the big concern was BC compatibility with D8, which is why the base classes were excluded from that patch.
When D8 officially becomes unsupported a few weeks from now (2 Nov 21), this will no longer be a concern. PHP 7.3 will then be the minimum supported version of PHP so return type hinting will be allowed for all supported versions of PHP, unlike with D8.
Likewise, we use PHPUnit 9 in all supported versions of Drupal 9 (D9.1 is the minimum-supported version at this time). The void return type hint was added in PHPUnit 8.
We have a BC layer that was added in Overridden test methods require void return type hints, so contrib has already been moving to :void over the past 18 months - this deprecation has been reported by all our standard tools since the BC layer went in.
I really don't see any need to put this off until D10.
Comment #18
TR CreditAttribution: TR commentedThe tests in #15 failed because of the change to
core/tests/Drupal/Tests/Composer/Plugin/Scaffold/Fixtures.php
.Again, this class does NOT extend
UnitTest
,BrowserTestBase
, orKernelTestBase
. It is a utility class that defines its own tearDown() method - the signature on this method should not be changed (and indeed cannot be changed without breaking the tests ...). Here's a new patch.Comment #19
TR CreditAttribution: TR commentedUpdated issue summary.
Comment #20
TR CreditAttribution: TR commentedComment #21
TR CreditAttribution: TR commentedComment #22
longwaveThis is not quite true, for historical reasons we support both PHPUnit 8 and 9, and PHPUnit 8 is still used if you are on PHP 7.3 even in Drupal 9.3.
We promised to contrib that we would not break this on them until D10, and we keep our BC promises elsewhere. The issue is that contrib may extend any of these test base classes, and if they haven't yet added typehints then their tests will break if we add typehints first.
To me this is currently working as designed and is a task that can be committed when 10.0.x opens.
Comment #23
mondrake#22, I agree.
NW because I think it would make sense to do #14 too
Comment #24
mondrakeComment #25
TR CreditAttribution: TR commented@longwave What is the purpose of pointing out my inexact language in one small sentence of one comment? It's completely irrelevant to the argument and only serves to derail the issue. You also took this statement out of context. The full statement was:
Read that in full. I said the void return type hint was added in PHPUnit 8. It doesn't matter that we use PHPUnit 8 in some configurations and use PHPUnit 9 in some configurations - they both use the void return type hint.
Note that I was precise when I re-wrote the issue summary and quoted the change record as saying:
I also cited the PHPUnit change record for PHPUnit 8.
And while it would have been better if I had written:
THAT DOES NOT CHANGE THE REASONING in any of my posts, because the void return type was added in PHPUnit 8, which we use for ALL supported versions of Drupal 9.
So can we get back to the work of trying to fix core?
There are two parts of this issue:
Regarding point 1: There's absolutely no reason that anyone has articulated that would prevent us from fixing the method visibilities right now, to agree with the published Drupal API that has been in effect for more than 10 years. And we already fixed this for the $modules property. Not a single reason to delay this any longer.
Regarding point 2: The "promise" that was made in the change record Overridden test methods require void return type hints was that the backwards compatibility shim would persist until Drupal 10. If you want to keep this "promise" then simply add back the shim that was removed from the patch by @mondrake in #10. No reason we can't keep the shim, if that is your concern. I have added that back into the patch.
Note we have already added void return type hints for
UnitTestCase
,KernelTestBase
, andBrowserTestBase
- these have been in Drupal core since April 2020. There is no good reason to delay enforcing these in core - enforcing it now will make the transition for contrib easier (and the change need in contrib is already pretty trivial). Right now core is making it harder for contrib because core forces contrib to have all sorts of special-case situations, due to core not following its own rules. Remember the entity manager? Core changed that pre 8.0 yet core continued to use it until 8.8, causing all sorts of cascading problems that still arise even today. The way to avoid those situations is for core to live by its own rules - when core changes the signature of a method, then we must insist that ALL of core changes so that the legacy examples won't persist and be copied into contrib and back into core etc. It's the double standard that causes problems for contrib, not necessarily the change itself.Remember, we're talking about test cases and only test cases here. Even in the worst case scenario this won't break any code, and might break a test case only temporarily. Worst case. If you've kept up maintenance of your contrib module there will be no effect at all. The changes needed in contrib in any case will be extremely small - certainly less that the current disruption of having to maintain both public and protected for the same methods, certainly less than having to use :void for the methods in some places and not others. By delaying this you're forcing contrib maintainers to deal with the disruption twice, spread out over two years, rather than just fixing it and getting on with our lives.
I'm speaking from the point of view of a maintainer of contrib projects when I say that you're not doing me a favor by delaying this, you're just making it harder to maintain my projects.
@mondrake: I won't go switching the version again, but I would appreciate if you would reconsider. At a minimum, let's reduce the scope of this issue and at least fix the method visibility in D9 - don't put that off yet again. That's been broken and causing problems in contrib since D7.
Here's a new patch rerolled against current D9 HEAD, including the check in
DrupalListener::startTest()
like you suggested and with the BC shim for :void restored, as mentioned above.Comment #26
longwaveThere are no void return types in any of our test base classes yet (and in fact your patch adds these return types to these three base classes). PHPUnit 8/9 does declare void return types - but we patch this out at runtime, precisely so downstream users don't have to add the return types just yet. See
Drupal\TestTools\PhpUnitCompatibility\PhpUnit8\ClassWriter::alterTestCase()
. This allows us to trigger the deprecation inDrupalListener::startTest()
without a fatal error before we even get to that point.If you use contrib search at http://grep.xnddx.ru/search?text=function+setUp&filename= a random sample shows there are many modules that are D9 compatible but that have not yet added void return types to their
setUp
method. If we commit #25 now then these tests will no longer run: https://3v4l.org/uHi7QCan you give an example of a special case that is needed here? All contrib and other downstream tests can add void return types to all methods right now, as the deprecation message states, so they can seamlessly upgrade from Drupal 9 to 10 later on. If there is a case where this is not possible I'd like to help solve it.
I do agree that the method visibilities can and should be fixed; I suggest that it should be done separately from adding the return types.
Comment #27
mondrakeComment #28
longwaveRerolled #25 for 10.0.x.
Comment #29
mondrakeI think we should also do this from #11 here to prevent accidental regression.
Is that all? Do we also need to check
- 'setUpBeforeClass',
- 'assertPreConditions',
- 'assertPostConditions',
- 'tearDownAfterClass',
- 'onNotSuccessfulTest',
? Also, isn't this something PHPCS or PHPStan would be better at?
Comment #30
longwaveAddressed #29. Also removed another chunk of code that checked for the void return methods, they all need to be void return now or the classes won't load in the first place.
> isn't this something PHPCS or PHPStan would be better at?
I realise we have the check for the
$modules
property immediately above this, but adding more checks is a slippery slope. - this definitely feels like a job for static analysis instead of runtime checks, so removed this for now. Also this doesn't technically cause any issues if a subclass has the wrong visibility, and it seems pretty unlikely someone will instantiate a different test class and try to call one of these methods from outside.Comment #31
longwaveTry again, missed a line.
Comment #32
mondrakeLooks good, RTBC. See also Slack conversation https://drupal.slack.com/archives/C1BMUQ9U6/p1639474615373900.
Comment #34
catch@mondrake asked in slack if this was eligible for alpha1 (which is strictly dependency updates with Symfony 5.4, no Drupal deprecation removals yet). Since this is removing a phpunit bc/fc compatibility layer and allowing us to 'properly' update to phpunit 9 I think it's in scope to commit pre-alpha. Although it's not as high a priority as hard blockers it's also been ready to go for quite some time.
Committed cfd8517 and pushed to 10.0.x. Thanks!
Comment #35
Balu ErtlFor those rare fellows in need of kinda “backporting” this patch to 9.3.x branch, here's a slightly modified one: as
UpdateUploaderTestBase
was introduced on the 10.0.x branch, therefore the following part blocks the patch from applying:Comment #37
GrimreaperFor people who wants to be able to apply patch on 9.4.0.
I took patch from comment 35, applied on 9.3.x, then cheery-pick on 9.4.x.
Comment #38
GrimreaperRebased patch from comment 37 to be able to apply on core 9.4.8.
Comment #39
SweetchuckPatch for 9.5.x (2022-12-25 f385639b85d7b5eb719cbb79bc394534a67e3796)
Comment #40
gidel CreditAttribution: gidel at Smile commentedHere's a patch made for the 9.5.3 release (From 2023-01-31 - 11:33pm 0e78230972c599be926cbb7baa5a77a2afa362a2).
Comment #41
gidel CreditAttribution: gidel at Smile commented