Problem/Motivation
__PHPUNIT_BOOTSTRAP was removed along with all globals in PHPUnit 10. DependencySerializationTrait relies on it to "fix" tests.
This was added as part of #2553655: Convert ViewKernelTestBase to use KernelTestBaseTNG and then modified by #2909164: Fatal error with stub container in DependencySerializationTrait::__wakeup() as a way to deal with hard to understand test failures.
Steps to reproduce
This is exposed by this failure running tests with PHPUnit 10:
4) Drupal\Tests\views_ui\Unit\ViewUIObjectTest::testSerialization
Drupal\Core\DependencyInjection\ContainerNotInitializedException: \Drupal::$container is not initialized yet. \Drupal::setContainer() must be called with a real container.
/app/core/lib/Drupal.php:169
/app/core/lib/Drupal/Core/DependencyInjection/DependencySerializationTrait.php:80
/app/core/modules/views_ui/tests/src/Unit/ViewUIObjectTest.php:134
/app/vendor/phpunit/phpunit/src/Framework/TestRunner.php:103
/app/vendor/phpunit/phpunit/src/Framework/TestSuite.php:340
Additionally here is the change in PHPUnit where this was removed:
https://github.com/sebastianbergmann/phpunit/commit/91fa2f8256eba9ed40a34b87aab8b869ee836099
Proposed resolution
Remove PHPUNIT hack in DependencySerializationTrait.
Remaining tasks
User interface changes
NA
API changes
None
Data model changes
NA
Release notes snippet
NA
Issue fork drupal-3406024
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 #2
neclimdulI assume this also triggers kernel test failures but I haven't gotten into testing Kernel tests yet.
Interestingly, the Unit test suite failure in ViewUIObjectTest::testSerialization exposes a test that isn't actually testing what it thinks it is. The test is trying to test that the object can safely serialize and unserialize. But because of this hack in DependencySerializationTrait, the unserialize component of the test "succeed" with a broken object because the hack allows the code to continue and silently unserialize without the container to populate the code.
This exposes that we can never really trust any tests of this code because they'll always behave differently in a test environment from a real Drupal site which is very bad.
This and the fact that we're embedding testing logic in production code which has its own code smell for performance and complexity reasons leads me to wonder if the entire hack should be removed in favor of addressing the problem somewhere else. Don't know if that's possible but it seems like it would likely be a better solution.
Comment #3
longwaveMaybe we should try removing this hack entirely on PHPUnit 9 and seeing how badly things break?
Comment #5
neclimdulWow! Not nearly as bad as I expected with all the dire warnings in the trait. Only the known views test is failing. Let me move that over to a kernel test and we'll at least have a green merge request.
Comment #6
mondrakeNeeds rebase and understanding why the tests failed
Comment #8
mondrakeTests were failing due to missing
@groupannotation on the newly introduced test.Comment #9
smustgrave commentedWonder if IS could be updated to included proposed solution. Not sure if this counts as API change?
Comment #10
neclimdulUpdated IS
Since the changes should only affect testing so I don't think this counts as an "API Change" so captured that in the IS.
Comment #11
smustgrave commentedSmall comments on MR. Probably good to self RTBC after.
Comment #12
mondrakedone
Comment #13
smustgrave commentedBelieve this is is good now
Comment #16
longwaveDifficult to decide whether to backport this one or whether we need a change record. This seems somewhat unlikely to affect contrib or custom tests unless they are specifically checking dependency serialization so decided to backport to 10.3.x to keep things in sync, but not 10.2.x, and that we don't need a change record as it is an edge case for tests only; testing serialization of dependencies should have been done in a kernel test anyway.
Committed and pushed 24f8f3c961 to 11.x and dfa7222ae5 to 10.3.x. Thanks!
Comment #20
longwaveReverted, after commit this broke
DrupalDateTimeTest::testSleep.Comment #22
longwaveThis was added recently in #3187004: DrupalDateTime serialization issue
Comment #23
spokjeMoved DateTimeTest::testSleep from UnitTest to KernelTest.
Comment #24
smustgrave commentedTests appear green again so remarking.
Comment #25
longwaveGiven that I broke HEAD once here, changed my mind on #16. Let's add a short change record that explains that if you were testing dependency serialization in a unit test you will need to switch to a kernel test.
Comment #26
longwaveFor review: https://www.drupal.org/node/3425462
Comment #27
smustgrave commentedCR reads fine. So this change is for 11.x only correct?
Comment #28
longwaveTechnically, yes, but we can also make it in 10.3.x if we want, with the minor risk of breaking some contrib or custom code tests - but as there are only two in core that are affected the chances of any others feels quite slim.
Comment #29
smustgrave commentedGotcha, thanks for that.
Comment #30
alexpottIf I have to work this out when a contrib or custom test suddenly fails on me I'm going to be a little bit frustrated. This change will break someone's contrib or custom tests somewhere. In fact I think it's more like than core as some contrib and custom much prefer to wirte unit tests over other types of tests because they are lighter and easier to manage.
An alternate fix would be use the constant PHPUNIT_COMPOSER_INSTALL - we ensure it is set it in core/tests/bootstrap.php and it is still used in PHPUnit 11 - see https://github.com/sebastianbergmann/phpunit/blob/2a6d51d23035bfcd0fe6c4...
So if do the one line change
Then everything continues to work and you can unit test serialization of objects that use the DependencySerializationTrait
Comment #32
longwaveThe problem is that we are calling \Drupal::getContainer() when there is no container. But also in the unit test case there are no service IDs to restore in wakeup either, so we can just skip calling getContainer() in that case. MR!6914 does this and passes locally without changing any tests.
Comment #33
alexpottSo according to #2553655: Convert ViewKernelTestBase to use KernelTestBaseTNG we added this code largely to prevent deserialization errors in PHPUnit on failure.
I've tested @longwave's new approach with \Drupal\Tests\user\Kernel\Condition\UserRoleConditionTest::testConditions and made it fail. We don't get serialisations issues anymore! Yay! We think this is due to changes in PHPUnit include the golden oldie https://github.com/sebastianbergmann/phpunit/issues/4983
Comment #35
longwaveUpdated the IS and unpublished the change record as we no longer need it.
Comment #39
catchLet's try again. Committed/pushed to 11.x and cherry-picked to 10.3.x, thanks!
Comment #40
neclimdulAt some point this changed from using \Drupal::container to Drupal::service() in a loop. Could that potentially add a bunch of extra function calls to a wakeup?
Comment #41
neclimdulOh and it removed all the other changes. So, I think the failure was probably important. Wasn't it saying "hey, you're not testing what you think you're testing. There's not actually a container available."
Comment #42
catchhmm but isn't that why it was moved into the loop - so that it only runs if there are actual service IDs to operate on, so it doesn't run when there's no container.
But... we could probably check if _serviceIds has anything in/whether there's a container, before getting the container to achieve the same thing.
Comment #43
neclimdulThat makes sense as an optimization.
I guess I'm worried that the tests we where changing where explicitly testing container aware serialization without a container. That's kinda a red flag to me but maybe we can't force better testing with logic outside the test suite.
Comment #44
longwave> \Drupal::container to Drupal::service() in a loop. Could that potentially add a bunch of extra function calls to a wakeup?
To me this is a micro-optimisation and not worth worrying about; we are adding one extra function call for each service that is being reinitialised on wakeup.
The tests were already testing object serialization without a container, and we aren't changing that here; they aren't testing the trait itself, just other features of their objects.
Comment #46
neclimdulWe worry about function calls in a ton of other circumstances that might be hot code paths. Not sure why this would be different so follow up MR posted with the approach Catch mentioned.
As far as the tests, they are clearly not testing what they think they are. The only reason to test serialization on them is to assert they're correctly interacting with dependency serialization which they're bypassing so there's definitely a bug. If we're not fixing it here, then we need a follow up.
I wish we could help surface this sort of trap to developers because its obviously easy to fall in. But like I said, I guess we can't build test suite logic into things outside the test suite. That sort of code is what we where removing from this trait to start with its just unfortunate.
Comment #47
catchLooks good to me now!
Comment #48
longwaveCommitted and pushed 1707169f2b to 11.x and a20d399646 to 10.3.x. Thanks!