Problem/Motivation
Similarly to #2950132: Support PHPUnit 7 optionally in Drupal 8, while keeping support for ^6.5, this issue is about enabling testing with PHPUnit 8 in Drupal 8.
PHPUnit 6 is out of support since Feb 1, 2019
PHPUnit 7 will be end of support Feb 7, 2020
PHPUnit 8 will be supported until Feb 5, 2021
PHPUnit 9 will be released Feb 7, 2020 - #3110543: [meta] Support PHPUnit 9 in Drupal 9
The big problem with PHPUnit 8 is that it is changing the signature of the main test methods that test classes extend - above all setUp() and tearDown.
This means that ALL the 1700+ implementations of setUp() and the 30 of tearDown() in Drupal test code will have to be touched one way or another, sooner or later.
Also, in PHPUnit8 several assertions methods are deprecated for removal in PHPUnit9, see #3110543: [meta] Support PHPUnit 9 in Drupal 9.
Spun off issues
- #3094151: ExpectDeprecationTrait is not compatible with PHPUnit 8
- #3107732: Add return typehints to setUp/tearDown methods in concrete test classes
Done
- #3106918: Drop support of PHPUnit 6 in Drupal 9 because it will never get used anyway
- #3097822: Drupal\migrate_drupal\Tests\StubTestTrait is not compatible with PHPUnit 8
MigrateExecutableMemoryExceededTest- it has mock expectation fails in PHPUnit8 due to inconsistent matches(). Added a @todo and commented out the failing tests. #3102903: MigrateExecutableMemoryExceededTest has mismatched argument type mock expectations (and fails in PHPUnit8)ViewExecutableTest- it has mock expectation fails in PHPUnit8 due to callback return type should be bool and not string. Added a @todo and commented out the failing tests. #3102899: ViewExecutableTest uses a mocked argument callback wrongly (and fails in PHPUnit8)
Proposed resolution
There seems to be three options, we settled on option #3.
3
3. Upgrade to PHPUnit 8 in Drupal 9 but rewrite TestCase
Symfony's simple-phpunit rewrites the phpunit TestCase class to not do the void return types. We can do something similar and load the rewritten class before the autoloader would thereby replacing the PHPUnit class. This would allow us to not change our test base classes so making it easy to maintain tests that work on 8.8, 8.9 and 9.0. See #58 for an implementation of this.
- We would anyway add
voidreturn typehint for concrete (non-base) test classes, #3107732: Add return typehints to setUp/tearDown methods in concrete test classes - We would still need to fix #3094151: ExpectDeprecationTrait is not compatible with PHPUnit 8 in 8.8.x and 8.9.x
- Introduce a deprecation error when a class relies on the rewriting
- Try to maintain the rewrite all the way to 10.0.x, when we would update our base classes and remove the rewrite altogether. However if we run into insurmountable compatibility issues with a later version of phpunit re-assess
Options 1 and 2 for posterity:
1. Change the signatures of the methods in Drupal test code according to PHPUnit 8
This will have the impact of forcing testing only on PHP 7.1+ (the minimum that allows specifying a void return typehint to methods), and from the day such a change will be in core, all contrib module will have to comply or fail tests.
2. Introduce a compatibility layer
- A new
Drupal\Tests\BaseTestClass, extending fromPHPUnit\Framework\TestCase, is the new base class for ANY Drupal test, being Unit, Kernel, Functional, etc - The
Drupal\Tests\BaseTestClassis actually aliased to aDrupal\TestTools\PhpUnitCompatibility\PhpUnit#\BaseTestCaseclass, one for each PHPUnit version, using the compatibility layer mechanism put in place to support PHPUnit 7. These aliased classes hold the override of the::setUp(),::tearDown(), etc methods, type hinted according to the specific PHPUnit version. - Each overridden method calls a corresponding proxy in the test class so
::setUp()->::xxxSetup(). The naming tbd. - ALL test classes in Drupal have to rename their PHPUnit overriding methods to the proxies, and any call to
parent::method()to the parent proxy, too i.e.parent::setUp()-->parent::xxSetUp()
Remaining tasks
Review.
User interface changes
None
API changes
Some
Data model changes
None
Release notes snippet
Updated to PHPUnit 8.
| Comment | File | Size | Author |
|---|---|---|---|
| #92 | 3063887-92.patch | 1.29 KB | mondrake |
| #81 | 3063887-81.patch | 28.54 KB | alexpott |
| #81 | 73-81-interdiff.txt | 1.4 KB | alexpott |
| #73 | 3063887-73.patch | 30.04 KB | alexpott |
| #73 | 66-73-interdiff.txt | 957 bytes | alexpott |
Comments
Comment #2
mondrakeThis patch is buidling on the latest #2950132: Support PHPUnit 7 optionally in Drupal 8, while keeping support for ^6.5 patch to extend support to PHPUnit 8, implementing option 2 from the IS. Just focusing on Unit tests to keep the patch (relatively :)) smallish. PHPunit's methods are implemented in the compatibility traits, and their test class version-independent equivalents renamed to xxxMethod.
Comment #3
mondrakeComment #4
wim leers😨
Comment #5
mondrakeComment #7
mondrakeComment #8
mondrakeSo, here's a new start.
The proposal is:
Drupal\Tests\BaseTestClass, extending fromPHPUnit\Framework\TestCase, is the new base class for ANY Drupal test, being Unit, Kernel, Functional, etcDrupal\Tests\BaseTestClassis actually aliased to aDrupal\TestTools\PhpUnitCompatibility\PhpUnit#\BaseTestCaseclass, one for each PHPUnit version, using the compatibility layer mechanism put in place to support PHPUnit 7. These aliased classes hold the override of the::setUp(),::tearDown(), etc methods, type hinted according to the specific PHPUnit version.::setUp()->::xxxSetup(). The naming tbd.parent::method()to the parent proxy, too i.e.parent::setUp()-->parent::xxSetUp()The patch here is introducing the thing and converting Unit and Build tests, limiting testing to these test types only in DrupalCI. The size is huge just because it's an all or nothing thing... it's tedious but not complicated.
The patch is also lowering the PHPUnit upgrade from PHP 7.3 to PHP 7.1 so we can test with PHPUnit 6, 7 and 8.
Open problems:
ExpectDeprecationTrait- its version in HEAD is not compatible with PHPUnit 8 since PHPUnit 8 has a ::expectDeprecation() method with a different signature than the one in the trait; also, looks like the overall logic underlying is different.MigrateExecutableMemoryExceededTest- it has mock expectation fails in PHPUnit8 due to inconsistent matches(). Added a @todo and commented out the failing tests.ViewExecutableTest- it has mock expectation fails in PHPUnit8 due to callback return type should be bool and not string. Added a @todo and commented out the failing tests.Comment #9
mondrakethe patch
Comment #10
mondrakeComment #11
mondrakeIn #8, I misinterpreted the logic behind ExpectDeprecationTrait - its purpose is different than PHPUnit8 expectDeprecation, but we have a method name collision that we need to solve. Here, renaming to xxxExpectDeprecation.
Comment #12
mondrakeComment #13
mondrakeSpinned off #3094151: ExpectDeprecationTrait is not compatible with PHPUnit 8.
Comment #14
mondrakeRerolled and added protection to prevent circular referencing of main/proxied methods that claims all memory and then fails - added an excpetion instead taht will call to action to rename the main methods in the test classes to their proxies.
Comment #15
mondrakeComment #16
mondrakeComment #17
mondrakeComment #18
mondrakeHere's a megapatch converting ALL Drupal tests, including Kernel/Functional/JavaScript.
Comment #19
mondrakeHere's a full patch rerolled, and a patch for review with just the key changes from HEAD - the changes are relatively small, the size of the full patch is due to thousands of renames.
Comment #20
mondrakeNote: patches in #18 and #19 run as follows:
PHP 7 -> PHPUnit 6.5.14
PHP 7.1 -> PHPUnit 7.5.17
PHP 7.2 and 7.3 -> PHPUnit 8.4.3
Comment #21
chi commentedIs there any reason we don't use PHPUnit 8 in Drupal 9 yet? At this moment PHPUnit 8 is the only version that supports PHP 7.4
https://phpunit.de/supported-versions.html
Comment #22
longwaveThinking out loud but instead of renaming methods is it possible to implement __call() etc and proxy through to the base class that way rather than directly extending it?
Comment #23
longwaveTried a quick hack to see if #22 is viable but PHPUnit's StandardTestSuiteLoader requires test classes to extend TestCase, so we would need to override the loader as well if we go this route.
Comment #24
longwaveReturn type signatures were introduced in PHP 7.0 so I think if we were to add these now then it would work on all supported versions. Overridden methods can specify a return type even if the parent method doesn't: https://3v4l.org/KXAWl
However as you say this is still problematic for anyone in contrib that is extending these tests. Can we announce there will be a BC break in tests in Drupal 9, or 9.1? Contrib can get ready now by adding the return type in preparation, the tests will still work on earlier versions.
Or could we introduce a parallel set of base classes that add the return type? This still runs into problems where contrib extends a concrete test class from core - are there many cases of this, though?
We should have heeded the warnings from PHPUnit earlier, see the Looking Forward section of https://phpunit.de/announcements/phpunit-7.html
Comment #25
mondrake@longwave re #24
voidreturn types were introduced only in PHP 7.1, so adding the return typehint in Drupal 8, that still supports PHP 7.0, would lead to failure (see https://3v4l.org/gvBoi).This issue is for D8. For D9, where min PHP is 7.2, that could be a good idea. But I think in that case we may want to drop PHPUnit 6 first to reduce the compatibility layer, and make PHPUnit 7 the default.
Looks like it will have to be, one way or another. Adding framework and release manager review tags to flag the issue.
Comment #26
chi commentedI suppose we have to use PHPUnit 8 because PHPUnit 7 does not work with PHP 7.4, and its support ends on February 7, 2020.
https://phpunit.de/supported-versions.html
Comment #27
mondrakeRerolled, after #3082340: Replace assertEquals() overrides in PHPUnit Kernel, Functional and FunctionalJavascript tests with a MarkupInterfaceComparator implementation
Comment #28
mondrakeComment #29
mondrakeComment #30
mondrakeReroll after commit of #3102899: ViewExecutableTest uses a mocked argument callback wrongly (and fails in PHPUnit8).
Comment #31
mondrakeComment #32
mondrakeComment #33
mondrakeRe-roll.
Comment #34
mondrakeTrying a different approach, allowing BC, for #3094151: ExpectDeprecationTrait is not compatible with PHPUnit 8.
Comment #35
mondrakeComment #37
mondrakeDisregard #34 and #35. Will re-roll from #33.
Comment #38
mondrakeRerolled from #33.
Comment #39
xjmRegarding:
Given this statement from the IS:
If anything I'd want to make the break in 9.0 rather than 9.1 because that's a massive change.
In general, BC breaks due to dependency major version updates are allowable in 9.0, but ideally we would have had deprecations in 8.8 for BC breaks in core test APIs. In https://www.drupal.org/node/2957906 it looks like we provided BC in our test base classes for less drastic differences.
(And similar for other methods.)
At first I thought the
xxxprefix was debug code until I read theBaseTestCase. Is thexxxnaming we're inheriting from upstream? If not I'd prefer something descriptive.Comment #40
mondrake#39: no
xxxis just a placeholder for "something descriptive" that I did not take care to think of, see IS.What's happening upstream is only that some of the methods get a
voidreturn typehint in PHPUnit 8:and this 'forces' any extending class to comply to the typehint.
The patch here implements option 2 from the IS, which is, to some extent, black magic... TBH I would definitely prefer option 1 i.e. just add the return typehints, thus avoiding any PHPUnit dependent
BaseTestCaseclass and thexxxprefixed methods which would be confusing for developers.I do not see a problem for D9 core to move in that direction - since it is already only supporting PHP 7.2 and min PHP 7.3 support is on the way. The problem would be, though, in contrib, because all tests implementing the methods above would have to be adjusted for the return type hint which is only supported in PHP 7.1+ (see #25), so modules supporting both D8 and D9 would only be testable on PHP 7.1+.
Comment #41
longwaveElsewhere we use "do" as a prefix for wrapped methods that are expected to be overridden, so perhaps
doSetUpwould work if we have go down the option 2 route.I would strongly prefer the option 1 route however, as we have to do that eventually, unless we want to maintain this compatibility layer forever?
Comment #42
mondrakeIMHO we'll have to be pragmatic and accept that 'some' level of compatibility layer we will have to maintain anyway moving forward - for example,
assertInternalType()will be removed from PHPUnit 9, and is deprecated in PHPUnit 8 - so we may want to have a FC shim in D9 while calls toassertInternalType()are converted to the recommended PHPUnit methods.But here, yes, the impact of this return typehinting is huge and so far we could not find a softer BC solution.
Comment #43
longwaveAs Drupal 9 requires PHP >7.1 I wonder if we can:
As this only affects tests and is inherently only an issue that affects developers rather than end users, perhaps we can get away with this?
Comment #44
mondrakeFWIW, I tried to #3106802: Add void return typehint to test setUp methods in a contrib module, and results are as expected - pass on D8.8, D8.9 and D9.0 with PHP 7.1+, fail for PHP 7.0.
Comment #45
mondrakeUpdated IS with the fact that option #1 can also work for PHP 7.1.
Comment #46
mondrakeSpinned off #3107732: Add return typehints to setUp/tearDown methods in concrete test classes to discuss option 1 for Drupal 9 in isolation.
Comment #47
mondrakeSo based on latest comments in #3107732: Add return typehints to setUp/tearDown methods in concrete test classes and #3106918: Drop support of PHPUnit 6 in Drupal 9 because it will never get used anyway I think we should stop targeting D8 and focus on D9 instead.
Postponed on #3107732: Add return typehints to setUp/tearDown methods in concrete test classes.
Comment #48
mondrakeComment #49
mondrakeComment #50
alexpottHere's an alternative idea based on what simple-phpunit does but customising it for the Drupal. This would allow us to trigger deprecations at some point in the future to add the void typehint and thereby make it much simpler for contrib. We can add these deprecations using reflection in KernelTestBase and BrowserTestBase.
The idea is based on simple-phpunit's re-writing of TestCase.php. Instead of rewriting it in place we re-write it to sites/simpletest and include it before the autoloader has a change to include it. Therefore everything uses our copy.
Comment #51
alexpottAt the moment the patch in #50 updates everything to PHPUnit 8 but I think that keeping PHPUnit 7 around will be valuable for keeping things simple 8.9.x and 9.0.x fixes. But to do that with testing we need to be able to test PHP 7.3 of PHPUnit7 and PHP7.4 on PHPUnit8 - but unfortunately we're not yet green on PHPUnit8.
Comment #52
alexpottOf course run-tests.sh is special :)
Comment #53
alexpottAnd now we need to resolve the deprecations. Let's skip them for now.
Comment #54
alexpottIncluding some of #3094151: ExpectDeprecationTrait is not compatible with PHPUnit 8
Comment #55
alexpottThis is not the right place for this but I think writing the TestCase all the time is breaking things. So I've tried to stop that.
Comment #56
alexpottFixes some more tests.
Comment #58
alexpottI think this fixes the remaining fails.
Comment #59
mondrake@alexpott kudos - at some point I fantasized about dynamically rewriting the base test class, but looking at your code I realise I would have never been able to. I guess this should be an option 3 in the IS, so flagging for IS update.
Couple of questions -
1) shall we need to emit a deprecation when the actual class being tested is missing the
voidreturn type from the relevant methods? Maybe a point for #3107732: Add return typehints to setUp/tearDown methods in concrete test classes, that would take a different path then.2) PHPUnit 7 EOL is Feb 7, 2020, in 9 days, when PHPUnit 9 will be released. Would it make sense to jump to PHPUnit 8 (meant to support PHP 7.2, 7.3 and 7.4) directly and drop PHPUnit7 straight away?
Comment #60
alexpottUpdated the issue summary to include this new option.
Re #59.
1. Yes but we can do that in 9.1.x or 9.2.x or some minor in the future - when we're less concerned about 8 and 9 co-compatibilty and support for PHP 7.0
2. When I started out i was thinking there was value in supporting PHPUnit7 in Drupal 9 but now I think option 3 allows to move to PHPUnit8 right now - less moving parts for the win!
Comment #61
longwaveYeah, this is a neat approach. When this issue was first opened I tried something vaguely similar with class aliases and messing about with PHPUnit's config and couldn't get it to work, but didn't think of intercepting it at the class loader level.
Agree with moving to PHPUnit 8 now if we can, we might as well as it saves us from having to do it later!
Comment #62
mondrakeBumped #3094151: ExpectDeprecationTrait is not compatible with PHPUnit 8 to critical as it's become a blocker for this issue.
Comment #63
alexpottRemoving the PHPUnit7 stuff from the patch because it is unused and moving the code that rewrites the TestCase class to somewhere better.
Comment #64
alexpottI'm not sure that #62 blocks this issue per se. I think it becomes a very nice thing to have for 8.9.x (and if we're nice 8.8.x) once this lands.
Comment #65
mondrakeAdd a @todo referencing #3075490: Move simpletest module to contrib?
compatbile/compatible
Add a @todo and/or a followup?
shoudn't this be removed from RunnerVersion then?
Comment #66
alexpottThanks for the review @mondrake.
Re #65
Comment #67
mondrakeI'd be +1 on RTBCing this.
Comment #68
catchShould this be 'before Drupal 10'? I'd assume if we're going to add the bc layer we'd keep it throughout 9.x
I would have been fine with the hard break in 9.0.0, but +1 to adding the bc layer here now it's been figured out, one less thing for modules to do in the next couple of months.
However, I would assume we'll still want to do #3107732: Add return typehints to setUp/tearDown methods in concrete test classes in 9.0.x so that core's not relying on the bc layer for its own test coverage.
Comment #69
alexpott@catch I don't think we need to do #3107732: Add return typehints to setUp/tearDown methods in concrete test classes now. Firstly because if we change the abstract classes like BrowserTestBase then contrib is back in the same place. I think in 9.1.x or whenever (maybe even now but that's going to create a lot of noise for contrib that I don't think we should make) we should deprecate implementations of
That don't have a return type. But this should be a special deprecation - one that ends mid cycle. Once that deprecation is over then we should add return types our abstract classes and remove this hack.
Comment #70
alexpottIf we do the hard break in 9.0.0 then modules that are currently passing on all PHP versions we currently support on Drupal 8.8 cannot also be Drupal 9 compatible. And pretty much every module that has already fixed their deprecations and has test coverage has another round of work to do.
Comment #71
mondrake#70 +1000 from a contrib maintainer pov. This patch allow modules currently specifying
core_version_requirement: ^8.8 || ^9to test on PHP 7.0 through to PHP 7.4 with no major headache.
Comment #72
mondrakeI was also experiencing the SQLite failure like in #66, in #30, and I think this boils down to the SQLite's Connection destructor
I was thinking to
@silence theunlinkcall that should not impact anything at that point.Comment #73
alexpottThis happens because we have multiple processes with database objects constructed. PhpUnit8 is destructing the TestCase at a different time that PHPUnit 7 - this causes the source database connection attached to the MigrateTestBase to be destructed at a different time. The fix is to check whether the file exists. No need to silence errors.
Comment #74
mondrakeComment #75
alexpottDiscussed this issue with @catch. One of @catch's concerns is the proposal to drop the hack mid 9.x. As @catch points out:
and
Therefore we agreed that:
Our proposal is to:
Doing the above means that
Comment #76
catchFor extra background, since PHPUNIT 6 the releases have been annual in February. So given PHPUNIT 9 is on its way out this month, we're most likely to run into a problem with this in either February 2021 (PHPUNIT 10) or February 2022 (PHPUNIT 11), or when PHP 8 comes out whenever that is. Unless something changes with their development cycle.
Symfony 4.4 stops getting bugfixes in November 2022 and EOL is November 2023, and we're probably trying to release Drupal 10 mid-2022 per #3018653: Decide on Drupal 9's EOL date range (and therefore, Drupal 10's release date range) to keep up.
When PHPUNIT 11 comes out, we'll already be actively retiring this bc layer in the Drupal 10 branch. That makes me think we have a decent chance of being able to maintain this until Drupal 10 without too much hassle since there are not all that many releases to get past before we get there - at least compared to how many we've had to span with Drupal 8.
Comment #77
mondrakeFiled #3111044: Deprecate setUp/tearDown concrete test methods that do not specify void return type as a follow-up.
Comment #78
mondrakeComment #79
alexpottWe need to update this with the agreement between me and catch to support this for D9 unless PHP8 or PHPUnit forces our hand.
Comment #80
mondrakeComment #81
alexpottRerolled now that Simpletest has been removed and updated comment with agreement outlined in #75 / #76
Comment #82
mondrakeTime to RTBC this? I cannot, really...
Comment #83
alexpott@mondrake I think we might want to do #3094151: ExpectDeprecationTrait is not compatible with PHPUnit 8 first - because we have to backport that to 8.9.x and 8.8.x too.
Comment #84
alexpottBut at the same time we could do this and then do that only in 8.9.x and 8.8.x
Comment #85
mondrake+1 on #84, IMHO it wouldn't make sense to introduce more deprecation triggers in 9.0.x right now for removal before beta
Comment #86
longwaveThis approach looks good, let's get this in and then try to get to PHPUnit 9 if we can?!
Comment #87
catchI added a change record at https://www.drupal.org/node/3113653. There are no actual changes here to make as such, but we can re-use that change record for PHPUnit 9 and any deprecations we add in subsequent issues. Already added the obvious issues to the change record too.
Also updated the issue summary to make it clearer what the current plan is.
Untagging for release manager review. Both @xjm and me would prefer to make the hard break in a major release, but the approach here allows that release to be Drupal 10 with a deprecation added beforehand, instead of Drupal 9 with none. If PHPUnit 9 has any nasty surprises we'll find out before release. PHPUnit 10 or PHPUnit 11 might break our bc layer, but if they do we can deal with that when it happens.
Committed 5d457e3 and pushed to 9.0.x. Thanks!
Comment #88
catchNot sure why the commit didn't auto-post, but it does show up here: https://git.drupalcode.org/project/drupal/commit/5d4
Comment #89
mondrakeAdded a couple more issues to the CR.
Comment #91
mondrakeTrying running the tests directky from PHPUnit on TravisCI leads to errors like
maybe the problem is the following piece of code
is not creating the simpletest directory if it's missing?
Comment #92
mondrakeComment #93
mondrake#92 fixes #91 for me.
Out of curiosity - PHPUnit manages its deprecations as (W)arnings in its CLI run. We do not see those in DrupalCI because they're not captured (#2905007: Allow run-tests.sh to report skipped/incomplete PHPUnit tests would address that).
As an example, running the test suite for --group=Database, we get
Comment #94
alexpott@mondrake let’s not re-open this issue for travis come stuff. Let’s discuss that in a separate issue.
Comment #95
pasquallethank you
Comment #97
xjm