Problem/Motivation
Currently the session is only available from the request in tests running in the child site. Neither BrowserTestBase
nor KernelTestBase
add a session to the request when populating the request stack in preparation of a test-run.
As a result, production code has to guard access to the session using $request->hasSession()
, but only in some cases. This leads to an inconsistent call patterns into the session API throughout the code base.
If production code could rely on the presence of some session on ever request, issues like #3109600: Convert uses of $_SESSION in language module and #3109971: Use $this->request in ViewsExecutable::getExposedInput() consistently could be implemented in a simple and straight forward way.
Proposed resolution
Add a session backed by symfony MockArraySessionStorage unconditionally to every request in DrupalKernel::preHandle()
As a result, a session is automatically available on every request in the following environments:
- All tests inheriting from KernelTestBase.
- All tests using FunctionalTestSetupTrait::initKernel().
- All drush commands (since bootstrapDrupalFull() calls
DrupalKernel::preHandle()
. - All drupal console commands (since boot() calls
DrupalKernel::preHandle()
.
With MR 3320, the following drush script snippet prints bool(TRUE)
:
echo 'var_dump(\Drupal::request()->getSession()->isStarted());' | drush php:script -
Hence, it is now possible to invoke code from drush, from drupal console, from kernel tests and from the parent process in browser tests which is relying on the presence of a session somewhere deeply nested inside the call graph.
Remaining tasks
Implement- Review
- Commit
User interface changes
None.
API changes
- A session should be added to every
Request
- whether it is constructed by Drupal core, inside some test case or by contrib / custom code. In order to communicate that requirement, therequest_stack
is examined duringtearDown()
and violations of that requirement are reported via a deprecation warning.
Comment | File | Size | Author |
---|
Issue fork drupal-2484991
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:
- 2484991-ktb-mock-session changes, plain diff MR !3320
- 2484991-add-the-session changes, plain diff MR !3317
Comments
Comment #1
dawehnerSo should we setup a request by default with an empty session object inside $request?
@znerol
It would be great if you could explain why a blocker for a normal is critical.
Comment #2
znerol CreditAttribution: znerol commentedWe already populate the request stack by default. We should probably try to just retrieve the session from the container and put it on the request.
Oh, that was not intended.
Comment #3
andypostdebuggin #2478167-18: Generate proper value for sessionName property in BrowserTestBase I found that child site reports incremental amount of cookies which grows with every next test* method
Comment #4
larowlanOver in #2421503: SA-CORE-2014-002 forward port only checks internal cache we're trying to replace key/value storage of form state with session based and found that tests run from run-tests.sh don't always have the session on the request object (because php is cli) - would this help that?
Comment #5
andypostfor #3
Comment #6
Crell CreditAttribution: Crell at Palantir.net commentedCrazy idea: KeyValue is an interface, so for normal requests have the form state backed by a session-based key-value, while in test use a disk key/value. Interfaces are fun like that.
Comment #13
andypostMaybe that will be enough, let's see how many tests will fail
Comment #15
znerol CreditAttribution: znerol commentedAdds the session to
KernelTestBase
. Extracted from #2473875: Convert uses of $_SESSION to symfony session retrieved from the request.Comment #16
andypostIt looks ready to go!
Comment #17
jibranWhat about
WebTestBase
as per IS and title? The patch only covers KTBComment #18
znerol CreditAttribution: znerol commentedI think we should leave alone
WebTestBase
(that's the old thing fromsimpletest
). Because that one is deprecated and will be removed from Drupal 9 anyway.I guess I need to step through
BrowserTestBase
once in order to figure out how that thing is working. Depending on whether we still have the parent-site (runner) vs child-site (system-under-test) relationship or not, we might need different approaches on how the session needs to be wired into those components.Comment #19
znerol CreditAttribution: znerol commentedNow that KTB populates its request stack with a request pointing to a session associated with the in memory session manager, that request should now be used in test cases.
Note that it seems to be common practice to fabricate and use a separate request in some test cases. However, this can lead to weird results when some parts of the system-under-test fetch the request from the stack and some other parts work with the one passed in from the test case. This is not something which is changed with this patch. But as we convert more and more of the code base to use the symfony session exclusively and unconditionally, I expect that we will uncover quite some of those cases.
Hunk taken over and adapted from #2473875: Convert uses of $_SESSION to symfony session retrieved from the request, Use
\Drupal::request()
instead of a custom one inReplicaKillSwitchTest
.Comment #20
znerol CreditAttribution: znerol commentedIf I'm not totally mistaken, browser tests have the same architecture like good old simpletest but with modern tools. The SUT runs as a separate Drupal instance which is newly installed for every test class. A test runner (which is either the browser or a test-script) then executes the test cases by running HTTP requests against the SUT and checking the results.
Since the SUT is a fully blown Drupal instance, the session is already available there. No change necessary.
Now the question is whether we need the session in the test-runner as well. It looks like the old
Drupal\simpletest\TestBase
had some code in place which prevented$_SESSION
data set while running test cases from bleeding over into the test-runner itself. I have not found evidence of such protection anywhere insidecore/tests
directory. On the other hand I found thatDrupal/Tests/UiHelperTrait.php
provides a methoddrupalUserIsLoggedIn()
. That one is calling directly into thesession_handler.storage
in order to determine whether there is an active session for a given user.From the comments above it looks like people expect that the session is on the request in the test runner. I guess we should try to add a session associated with a mock session manager to the test-runner part of browser tests.
Comment #21
znerol CreditAttribution: znerol commentedIn
BrowserTestBase
the parent (or test-runner) shares the container with the child (SUT). Both parts share the exact same set of services. Hence it is not possible to mock thesession_manager
in the test runner.Since
SessionManager
protects against calls into PHP session subsystem when run under CLI, it should be possible to make sessions available on requests in the test runner nevertheless.Comment #22
znerol CreditAttribution: znerol commentedComment #23
znerol CreditAttribution: znerol commentedFix wrong variable name.
Comment #24
znerol CreditAttribution: znerol commentedComment #25
znerol CreditAttribution: znerol commentedOver in #3110291: Test-only issue for session on request in BrowserTestBase I realized, that there are quite some tests which fabricate their own request objects. Some of them put it on the stack others don't. Problem is that we need to populate the session on those requests as well. Thus let's introduce a helper Trait providing a function to retrieve the session from the current request on the request stack. We can reuse this method in KTB and BTB based tests.
Comment #26
znerol CreditAttribution: znerol commentedEnsure that the session is restored in KTB and BTB base and test classes. Not touching unit tests (those should probably better mock the session object if necessary).
Comment #28
znerol CreditAttribution: znerol commentedFix copy paste / spelling problem.
Comment #29
znerol CreditAttribution: znerol commentedCosmetic fixes. Issue summary update.
In my opinion this patch is now ready. I was tempted to also merge #3109600: Convert uses of $_SESSION in language module and #3109971: Use $this->request in ViewsExecutable::getExposedInput() consistently into this issue here. #3110291-12: Test-only issue for session on request in BrowserTestBase shows that this patch here fixes the remaining cases in views and language modules. However, the test-only issue also shows that in the case of the views issue, some non-trivial changes to the
AreaDisplayLinkTest
are necessary. Thus I think it is better to commit them separately.Comment #30
jibranGreat! Let's ping @Berdir or @alexpott to review this.
Comment #31
alexpottSo there's plenty of change to all the tests here that's not truly necessary.
If I remove this from the patch then the test is not going to fail. I have two concerns here which are kind of in opposition:
I think what I'm driving at is the why of
from #25 is not answered.
Also this patch needs to apply to 9.0.x and it doesn't.
Comment #32
znerol CreditAttribution: znerol commentedI share your concerns. If you look at how it is currently used in non-test code, you find occurrences where a call to
$request->getSession()
is conditional and other parts where the session is assumed to be always on the request.Example command to asses session use in production code:
Unless someone alters away the
Session
middleware, the session is always on the request during the whole request-response cycle in production code. However, since this is not the case in KTB and the test-runner part of BTB (and in the early installer), some lower level code still must check for the presence of the session in order to avoid blowing up during test-runs.If production code needs needs to conditionally check for the session only because it is not available in some parts of the test-system, then I do consider this as bug in the test-system.
Tracking down all cases where Requests are created during KTB und BTB was quite a tedious process. Reviewing these changes ist kind of boring as will. However, I think it is still the right thing to do. It clearly communicates the direction we are moving and shows the pattern we need to follow in the future. Also it shows how the new trait should be used by core and contrib test cases.
The alternative would be to only introduce the infrastructure (changes in KTB and BTB) (something like #23). If we do that, we either have to fix the test-cases in a follow-up or introduce the session only if required by a certain test (e.g., in [#3109971). Both approaches will result in an inconsistent state. Less inconsistent than now though.
Thus this is ultimately a question on whether we want to minimize changes or minimize inconsistency. Both approaches work for me.
Comment #33
znerol CreditAttribution: znerol commentedReroll.
Comment #34
znerol CreditAttribution: znerol commentedDear Mr./Ms. Jenkins. Why do try to apply this patch to 8.9.x? I kindly request you to checkout 9.0.x from now on.
Comment #36
andypostReroll for 9.1.x and clean-up comment
Comment #39
andypostComment #40
anmolgoyal74 CreditAttribution: anmolgoyal74 at OpenSense Labs for DrupalFit commentedRe-rolled for 9.2.x
Comment #41
markdorisonThe patch in #40 is failing due to a
cspell
check on the use ofMOCKSESSID
inMockSessionManager
. This could be resolved by either changin theMOCKSESSID
string to pass the checks or by addingMOCKSESSID
to cspell'sdictionary.txt
. I chose the latter sincedictionary.txt
already contains two similar strings:sess
andssess
and modifying the string would require additional changes elsewhere. Patch and inter diff attached.Comment #42
andypostComment #43
ankithashettyRerolled the patch in #41 and fixed custom command error. Thanks!
Comment #44
alexpottHow about using proper words here. ie.
MOCK_SESSION_ID
then we don't need to add something to the dictionary.Comment #46
anmolgoyal74 CreditAttribution: anmolgoyal74 at OpenSense Labs for DrupalFit commentedChanged
MOCKSESSID
toMOCK_SESSION_ID
Comment #48
znerol CreditAttribution: znerol commentedThis is a fresh reroll from #29. I suspected that maybe some reroll between the last green patch and now went wrong. The only thing I found is
ReplicaKillSwitchTest
where a conflict was resolved incorrectly (seeinterdiff.txt
). Other than that the patch is logically equivalent as #46. Thus, going to look at what changed between 8.9.x and 9.0.x wrt to functional tests.Comment #50
znerol CreditAttribution: znerol commentedInterestingly I was not able to reproduce the successful run from #29 in 8.9.x. However, since I'm running PHP 7.3 locally, I had to update phpunit to 7 (Drupal 8.9.x at the time of the last successful test run still was on phpunit 6).
Drupal 9 uses phpunit 8. Since there were no other big changes to the test infrastructure on the Drupal side, I assume that changes in phpunit are causing the weird test failures. What I found is that a call
@trigger_error("...", E_USER_ERROR)
will lead to a situation where the phpunit subprocess fails to report anything (i.e.,stdout
remains empty).Hence, let's change the
trigger_error
call into aE_USER_WARNING
for now.Comment #52
znerol CreditAttribution: znerol commentedFix a typo.
Also fix
SessionManagerDestroyNoCliCheckTest
. This test needs to operate on the productionsession_manager
. Thus move it to another id beforeKernelTestBase::register()
installs the mock session manager.Comment #53
andypost$this->container->get('session')
the only trickery prevents me from rtbc but I see no other way (except some request factory)Comment #54
znerol CreditAttribution: znerol commentedDo you mind elaborating on that? Maybe there is a better way.
Comment #55
andypostI found no other way
Comment #57
benjifisherPlease do not ask the testbot to try again until #3207086: [HEAD BROKEN] Consistent failure in MonthDatePluginTest is fixed.
Comment #58
alexpott#3207086: [HEAD BROKEN] Consistent failure in MonthDatePluginTest is fixed. I have not reviewed the code.
Comment #59
alexpottI still have the concerns I outlined in #31. IN #32 @znerol addresses this concerns directly and I agree that if production code only has to check for session's existence due to test code then that would be a bug but what about CLIs like drush and console. They don't have a session either so are we in danger of making things more difficult for them?
Is this likely to cause contrib / custom tests to start failing? Do we want this or should we deprecate and then start failing in D10?
Comment #63
andypost+1 to deprecate it in 9.5 and fail in 10.1
Comment #65
andypostRe-roll and looks mostly no changes needed for tests nowadays
PS: see interdiff - few tests removed
$request->setSession($this->getSessionFromRequest());
but passedComment #66
andypostbit more clean-up
Comment #67
andypostFix CS
Comment #68
andypostand one more
Comment #69
andypostrevert last useless test change
Comment #70
znerol CreditAttribution: znerol commentedRe #59
We could add a session object backed by an in-memory store in
DrupalKernel::preHandle()
. This is sufficient for command line tools and tests. Web requests will be processed by\Drupal\Core\StackMiddleware\Session
where the mock session is replaced by a session object backed with persistent storage and a real session handler.This works with current
drush php:script
and the following snipped:Comment #72
znerol CreditAttribution: znerol commentedSession stack middleware should avoid attempting to save a session it didn't start.
Comment #73
znerol CreditAttribution: znerol commentedRe #65
That is expected. All those calls were added for consistency reasons. The goal in this issue is that every
Request
in core has aSession
. We only can hope that people start copying the$request->setSession($this->getSessionFromRequest());
pattern if it is applied consistently throughout core.Comment #74
znerol CreditAttribution: znerol commentedSwitching this issue to MR workflow. I'm probably going to test another idea in a separate branch as well.
Comment #78
andypostLeft few comments, primary is to fix deprecation message according standard https://www.drupal.org/about/core/policies/core-change-policies/drupal-d...
43 failed tests! Great job!
Comment #79
znerol CreditAttribution: znerol commentedUpdated the issue summary in order to reflect the new approach.
Comment #81
andypostThanks, 👍 looks ready
Comment #82
volegerI set it to Needs work due to unresolved threads in MR.
Comment #83
znerol CreditAttribution: znerol commentedThanks. Extracted two methods for session initialization in order to better document their responsibility and purpose:
DrupalKernel::initializeEphemeralSession()
StackMiddleware\Session::initializePersistentSession()
Comment #84
volegerAll review comments are addressed.
Comment #85
volegerbump
As soon as will be merged, that will unblock #2529170: Remove DrupalKernel::initializeRequestGlobals and replace base_root, base_url and base_path with a service
Comment #86
volegerbump
Comment #87
quietone CreditAttribution: quietone at PreviousNext commentedThe change record for this issue has no text. Adding tag and setting to NW.
Comment #88
znerol CreditAttribution: znerol commentedAdded the CR.
Comment #89
quietone CreditAttribution: quietone at PreviousNext commentedApologies to all for my rather terse comment.
@znerol, thanks for updating the CR.
Comment #90
quietone CreditAttribution: quietone at PreviousNext commentedSetting this to NW was unintentional. Sorry for the noise.
Comment #91
andypostCR looks good, thank you
Comment #92
znerol CreditAttribution: znerol commentedAll good @quietone, change records are essential! At least I hardly could keep up with core without them.
Comment #93
dwwMAINTAINERS.txt has this for the "base system" "subsystem" [sic]:
Comment #94
dwwDoing a review of the MR. Found some issues. Will save that soon so they appear here, but downgrading to NW now...
Comment #95
dwwPushed a commit to fix all my own nits and the deprecation message typo. Also edited the CR to fix the same typo there. 😅 I'd resolve most of the threads I just opened (if I could). The two that should remain open until addressed are:
Comment #96
dwwWHOOPS! Holy cross-posting and cross-pushing, batman. 😢 Sorry, @andypost! Was trying to resolve my own stuff, didn't see you were already responding. Yikes. I'll stop now and let the dust settle before I do anything else... 🙏
Comment #97
andypostNP, As I see the only debatable moment left is https://git.drupalcode.org/project/drupal/-/merge_requests/3320#note_168867
I think it could be solved with #2613044: Requests are pushed onto the request stack twice, popped once
Comment #98
dwwPer Slack chat w/ @andypost, we agreed a
@todo/@see
comment pointing to #2613044: Requests are pushed onto the request stack twice, popped once was worth adding to justify the switch torebuildContainer()
...All new threads should now be resolved. This is ready for framework manager review, although I can't re-RTBC it now. 😅
Comment #99
dwwPosted in #needs-review-queue-initiative to solicit the framework manager review this needs, so tagging for that. 😉
Comment #100
dwwRe-queue'ed PHP 8.1 + MySQL 5.7 - random fail in
.
Comment #101
dwwNeeds followup: Opened #3355243: cspell does not flag "ist" as a typo to deal with the cspell vs. "ist" weirdness.Comment #102
andypostI think it ready and now more polished
Comment #104
volegerNeeds to change branch in MR, and update versions in deprecation messages
10.1.0
->10.2.0
Comment #105
znerol CreditAttribution: znerol commentedRebased on 11.x and adapted the deprecation messages.
Comment #106
daffie CreditAttribution: daffie at Finalist commentedLooks good to me.
The MR does what the IS states.
For me it is RTBC.
Comment #107
daffie CreditAttribution: daffie at Finalist commentedComment #108
daffie CreditAttribution: daffie at Finalist commentedI forgot to change the status.
Comment #109
voleger#3355243: cspell does not flag "ist" as a typo is merged in 11.x
Comment #110
Wim LeersThis looks awesome. 👏 I could only find 3 language nits, for which I left suggestions.
Comment #111
volegerapplied suggestions. thanks
Comment #112
quietone CreditAttribution: quietone at PreviousNext commentedSorry folks, The change record has an an incorrect branch, version and example deprecation message. And the deprecation message in the MR needs a change to conform to coding standards. I have left suggestions in the MR.
Comment #113
volegerAddressed #112
Comment #114
andypostMR is green, text is fixed
Comment #115
znerol CreditAttribution: znerol commentedRerolled.
Comment #116
xjmRaised this in committer Slack to hopefully get that needed FM feedback.
Comment #117
larowlanBoth
Request::create
andRequest::createFromGlobals
end up callingRequest::createFromFactory
I wonder if our test setup trait could call
\Symfony\Component\HttpFoundation\Request::setFactory
which would add a factory method that could do the session setup for all of those instances. It won't helpnew Request
but those should probably be using::create
or::createFromGlobals
anyway.It would mean the size of the change here is much smaller, and the need for contrib/custom projects to make chances would be reduced.
We could mention in the CR that in most cases they should switch to
::create
or::createFromGlobals
instead of usingnew Request
and setting the session.Comment #118
znerol CreditAttribution: znerol commentedWhile maybe convenient, I think that #117 will create more problems than it solves.
In Drupal 7, application code modified the
$_SESSION
superglobal directly. Every piece of code which accessed it was guaranteed to work with the same data. However, using the$_SESSION
directly breaks encapsulation and makes testing difficult. Missing test coverage was probably one of the reasons why #3260652: Feature "Remember the last selection" for views exposed filters doesn't work anymore went unnoticed for quite a long time.In times where
$_SESSION
was used exclusively, the responsibility to populate and store its contents wasSessionManager.php
alone (orsession.inc
in D7). Now that almost all core code is refactored to use theRequest::getSession()
, there are additional code paths involved in passing data form the PHP session to the places where its used and back again.Basically every piece of code which creates, clones, duplicates a request also has the responsibility to copy the session. It is true that many of those are located in test cases, but some can be found in production code paths as well. The latter have the potential to introduce subtle bugs if they do not propagate the session. When everybody used
$_SESSION
this wasn't an issue. Nowadays it is.I fear that installing a request factory in the test system will hide those potential bugs. Code which fabricates a request in order to pass that into non-trivial code paths without propagating the session will seemingly behave correctly in tests (because of the request factory) and will produce invalid requests (i.e., lacking a session) in production.
Comment #119
needs-review-queue-bot CreditAttribution: needs-review-queue-bot as a volunteer commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
This does not mean that the patch needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #120
znerol CreditAttribution: znerol commentedComment #121
larowlanThank you @znerol that is a very thorough and eloquent explanation of why the disruption is actually better.
I think we probably want to target 10.3.0 with this now, which will mean we need to update the deprecation message. I'll confirm with release managers.
Comment #122
daffie CreditAttribution: daffie at Finalist commentedThe MR needs to be updated for that this will go in 10.3 instead of 10.2
Comment #123
volegerAddressed #122
Comment #124
daffie CreditAttribution: daffie at Finalist commentedBack to RTBC.
Comment #125
needs-review-queue-bot CreditAttribution: needs-review-queue-bot as a volunteer commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
This does not mean that the patch needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #126
dwwHah, whoops, cross rebase. 😅 But no harm done.
Comment #127
smustgrave CreditAttribution: smustgrave at Mobomo commentedAppear all threads have been addressed and no tests appear to be breaking.
CR is full of detail (branch versions need to be bumped)
Comment #128
dwwEdited the CR to bump the versions to 10.3.0.
Comment #130
larowlanWent through this again, only found one out of scope change, that I reverted locally before committing.
@alexpott's concerns in #31, #32 and #59 were addressed by @znerol by a change of approach.
My point in #117 was addressed in #118
We know this could be disruptive so I think getting it in early in the 10.3 cycle is the best approach.
So on that basis, committed and pushed to 11.x
Published the change record.
Thanks everyone for the mammoth effort over a prolonged period here.
Special thanks to @znerol for your patient and thorough explanations when required.
Comment #134
larowlanSorry folks, I had to revert this as it caused failure in HEAD
Looks like there's a new test that creates a Request but doesn't push a session
See \Drupal\Tests\file\Kernel\RequirementsTest::setServerSoftware
Fine to self RTBC
I'll keep an eye out for changes
Comment #135
znerol CreditAttribution: znerol commentedThanks @larowlan. Done.
Comment #136
znerol CreditAttribution: znerol commentedOh, I might have overwritten work done by @voleger and @dww by basing off my changes on an outdated local branch. Let me fix that.
Comment #137
znerol CreditAttribution: znerol commentedMR should be up-to-date now.
Comment #140
larowlanThanks, compared the two patches locally and confirmed the only difference was this
Committed and pushed to 11.x, re-published the change record.
Thanks for the quick turnaround
Comment #141
andypostThank you! Removal of global session remains now unblocked #3109970: Convert uses of $_SESSION in forms and batch