Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem
- When running a web test via the Simpletest UI (not
run-tests.sh
), then the session of the test runner leaks into the test.
Proposed solution
-
Write and close the session in
TestBase::prepareEnvironment()
. -
Back up the current session data in
TestBase::prepareEnvironment()
. -
Restore the original session data in
TestBase::restoreEnvironment()
. -
Stop disabling sessions for tests.
Comment | File | Size | Author |
---|---|---|---|
#54 | 51-54-interdiff.txt | 654 bytes | alexpott |
#54 | 2239969-new.54.patch | 9.71 KB | alexpott |
#51 | 2239969-new.51.patch | 9.82 KB | alexpott |
#51 | 50-51-interdiff.txt | 1.69 KB | alexpott |
#50 | 2239969-new.50.patch | 9.11 KB | alexpott |
Comments
Comment #1
sunComment #3
sunBlocked on #2239181: SessionManager::isStarted() returns TRUE even after session_write_close() has been called
Comment #4
sun1: test.session.1.patch queued for re-testing.
Comment #6
znerol CreditAttribution: znerol commentedLet's try a different approach: Prevent that session data is leaked into the UI test runner by closing (saving) the session in
TestBase::prepareEnvironment()
and then setting the session-name (i.e. the name of the session cookie) to a random value. If a test starts a new session, then it will be associated with a different session-name. After the test-run (TestBase::restoreEnvironment()
it can be safely destroyed.Another benefit of not using
SessionManager::disable()
in the test-runner is that we can implement the temporary session write-protection in a better way. I would like to remove that responsibility from the session-manager and instead implement it directly in the session-handler (or even better, a session-handler proxy).Comment #7
sunHm. This patch looked fine until I saw this ;-)
All of the original* properties are supposed to be private and not supposed to be available/reachable within tests; cf. #2177079: Test classes have access to TestBase::$original* properties
I first wanted to mention that the method is missing a 'get' prefix and should also contain "Original" in its name...
But when doing so, we'd only "officially accept/endorse" that the session does leak into tests... which doesn't really resolve the stated problem of this issue :-/
TBH, I don't know why the cURL code takes over the session name of the test runner by default. That clearly seems to be one part of the root cause, and shouldn't be required, since the CLI test runner has no session to begin with. (Although it has a
session_name()
, since that is set up indrupal_environment_initialize()
already, but that should be irrelevant for tests)Comment #8
znerol CreditAttribution: znerol commentedAll that the patch is supposed to do is to ensure that test code (DUTB as well as the parent site when running WTB) has no way to clobber a) in-memory session data (
$_SESSION
) and b) on-disk session data of the execution environment.It is indeed unfortunate that the WTB child site also reuses the session-name of the execution environment, but that is a different problem. We can solve that by making the session-name configurable from within
settings.php
. Given that this will involve changing non-test code, I'd prefer to not extend the scope of this issue here.Fixed. In order to keep track of changes to the session, the WTB parent site needs to know the session name of the child site.
Also I found that we need to make sure that a session cookie set during test-execution is deleted when the session is destroyed.
Comment #9
sunOK, this is definitely an improvement over status quo, so I think it's fine to move forward here. (It also sounds as if this fix would help us to move forward on session system refactoring?)
Normally, this would need explicit test coverage in
SimpleTestTest
to prove that the regression is fixed (i.e., session data of the test runner does not leak into a test and session data of a test does not leak into the test runner). The existing test coverage only ensures that a logged-in user can execute the UI test runner, which doesn't cover the actual session data.Regarding a test-specific session name, I assume that's probably as simple as checking
drupal_valid_test_ua()
to conditionally add a "TESTING" prefix to the default session name indrupal_environment_initialize()
.Comment #11
znerol CreditAttribution: znerol commented8: 2239969-fix-session-leak-8.diff queued for re-testing.
Comment #13
XanoWhat are the symptoms of this bug? I have a feeling this may be related to session issues I have been having since #1289536: Switch Watchdog to a PSR-3 logging framework was committed. That one causes SQL errors in the installer and maybe
LogicException: Cannot change the ID of an active session in Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy->setId()
as well.Comment #14
znerol CreditAttribution: znerol commentedThis fixes the
LogicException: Cannot change the ID of an active session
in most if not all cases. Setting the account on thecurrent_user
service is in fact a noop here (because$this->container
is set toDrupal::getContainer()
a couple of lines before). However since #2238087: Rebase SessionManager onto Symfony NativeSessionStorage it may cause side-effects.Other than that, this patch only affects testing through the UI (command line testing is not affected).
Comment #15
znerol CreditAttribution: znerol commented8: 2239969-fix-session-leak-8.diff queued for re-testing.
Comment #16
XanoI haven't dug into this a lot, but removing that line from WebTestBase does not prevent the errors in my particular case.
Comment #17
znerol CreditAttribution: znerol commentedBack to RTBC, test failures from #10 and #12 were bogus.
Comment #18
alexpottNice - this fixes a bug in KernelTestBase tests (eg TextPlainUnitTest) that could not be run through the UI.
Committed 5a3ef30 and pushed to 8.x. Thanks!
Comment #20
xjmThis causes:
Reverting it resolved the failure for me locally.
Comment #22
webchickReverted 5a3ef30 for now.
Comment #23
webchickComment #24
xjm8: 2239969-fix-session-leak-8.diff queued for re-testing.
Comment #25
alexpottAnd actually whilst it fixes KernelTestBase tests being run from the UI, in fact all WebTestBase tests appear to by broken with this patch applied.
Comment #27
znerol CreditAttribution: znerol commentedOh, fun. Seems like this is in fact incompatible with #1289536: Switch Watchdog to a PSR-3 logging framework. #8 only green turned green again because the watchdog patch was reverted intermittently.
Comment #28
znerol CreditAttribution: znerol commentedI see two options on how this could be fixed.
session_manager
service. This means we'd need to properly close/write the session before constructing a new container and restart it afterwards (if appropriate), similar to how the request is handled inDrupalKernel::initializeContainer()
Option 1 would be easy to implement, but I fear we'd just hide the problem. Option 2 could be tricky but it would also help with #2256257: Move token seed in SessionManager in isSessionObsolete() and regenerate() to the MetadataBag I think.
Comment #29
xjmUh. How on earth do we not have test coverage for that?
Comment #30
alexpottSo this is yet another example of why persisted services are a bad idea. +1 for option 2
I've opened #2272879: Can not run a WebTestBase or KernelTest base tests from inside a simpletest to test running tests through the UI
Comment #31
alexpottI think we should postpone this on #2272879: Can not run a WebTestBase or KernelTest base tests from inside a simpletest as that contains a commented out test for KernelTestBase and an actual test and fix for WebTestBase - so we can commit this patch knowing we have not broken anything and proving that we're fixing stuff :)
Comment #32
sunBy now, we're trying to cope with unmanageable effects and symptoms. The root cause seems pretty clear, and even though I hate to say it, we most likely have to revert #1289536: Switch Watchdog to a PSR-3 logging framework a second time.
Comment #33
znerol CreditAttribution: znerol commentedFiled #2272987: Do not persist session manager
Comment #34
alexpottre #32 I'm not sure about that since the errors are caused by not having a clear separation of sessions between the test runner and the test.
Comment #35
znerol CreditAttribution: znerol commentedJust a quick note: with #2272987-9: Do not persist session manager and #19 reapplied, both of the following tests pass on the command line as well as in the UI test-runner:
Formatter\TextPlainUnitTest
InstallationProfileModuleTestsTest
Comment #36
alexpottWe should be testing our UI test runner better... see #2272879: Can not run a WebTestBase or KernelTest base tests from inside a simpletest
Comment #37
znerol CreditAttribution: znerol commentedSimple reroll. Should fail the same way as #8.
Comment #39
neclimdulThe failing test creates a site with the testing profile. It then logs in and submits the web test runner with a test provide by a module in that profile. The error I got was in the test results of the test:
The executed test doesn't do anything strange. just install a minimal profile and a single test module and then execute:
Its all a pretty strange thing to be the only failure.
Comment #40
neclimdulWow, stepped through this and it is nasty. Sort of in the scope of what we're trying to fix though.
So the big problem is how we run through the installer which is working hard on the session and expecting multiple request from inside the setUp method, all in one request.
First, our session initialize() method gets called repeatedly through watchdog calls in the installer. This turns out is fine because the session is shut down.
Then we hit this code in the instaler:
This logs the admin user in, or at least it should. It also calls
SessionStorageInterface::regenerate()
which actually triggers the session to be started. This is also where the first actual weird thing happens because the actual session handler registered with php has a reference to the parent site request stack which doesn't see simpletest session name cookie so when we hit SessionHandler::read() actually logs out the user back out.Now, we hit initialize() from another watchdog request. This time from webtest installing extra modules (not that it really matters where) and we have a logged out user so we skip to the lazy anon block where we indescriminately call setId() and boom, and exception is thrown because the session is already started.
So the easy fix is to close the session after the installer runs. It seems like we should probably push a request on to the stack and maybe log uid 1 out.
Comment #41
neclimdulTake a look test bot. see how we're doing.
Comment #42
znerol CreditAttribution: znerol commentedThis is exactly the problem with
persist
-services. #2272987: Do not persist session manager will fix that problem, see #35.Comment #43
neclimdulI guess it isn't clear but I'm actually okay rtbc'ing this.
Comment #44
znerol CreditAttribution: znerol commentedReuploading #37 after #2272987: Do not persist session manager went in.
Comment #45
sunCan we use strict operators for both comparisons?
Can be shortened to just one line, since
session_name()
returns the current/previous name.*Returns
"The test child site uses the same session name as the test runner."
The only non-minor issue in this patch:
This reassignment is confusing, because it does not change the actual
session_name()
within the test runner.I.e.:
$this->session_name != session_name()
Given that TestBase already closes/destroys the session as necessary, do we actually need the additional back and forth with the session name…?
Comment #47
znerol CreditAttribution: znerol commentedTurns out that I forgot one hunk when rerolling #2272987: Do not persist session manager after the bootstrap-patch landed. Let's see what happens when it is restored, will #45 afterwards.
One note though:
I guess the intention of rebuilding the kernel before preparing it was to avoid building the container repeatedly. However, the result was that the container still was compiled two times (once by
prepareLegacyRequest() -> boot() -> initializeContainer()
and once byrebuildContainer()
). Additionally that sequence of calls somehow did lead to the session-manager having injected an empty request-stack service. Swapping the lines results in a) the container being compiled only once and b) the proper request-stack service injected into the session-manager.Comment #48
neclimdulI think it forced the rebuild first to ensure we didn't load the parent site container at all. We may have fixed that since then through other changes.
Comment #49
znerol CreditAttribution: znerol commentedAddresses #45. Did not include 2 because that would stretch the line over 80 chars, also I like the explicitness of recording the old value vs. assigning the new one. Regarding 4..5, reworded the comment a little bit.
This patch essentially introduces throw-away sessions during execution of test-methods. That makes it impossible to access the proper session name (which is required for
SessionTest
andSessionHttpsTest
) viasession_name()
from within a test-method.Comment #50
alexpottI think we can simplify the code in WebTestBase to just close the session rather than restarting it.
Comment #51
alexpottMaybe we can simplify even more - the only reason we have a session to close is the user login in install_finished() which only really makes sense for interactive installs.
Comment #52
sunWorks for me. The non-interactive installer should not start a session or perform any other UI related operations.
I can't think of a non-installer & non-tests scenario in which we'd encounter this problem space, so this "API usage workaround" in the installer code looks sufficient to me.
Either here or in a separate issue:
Let's consider to change
DrupalKernel::initializeCookieGlobals()
to conditionally use the simpletest UA prefix assession_name()
when being booted in a test child site.That way, we could unconditionally set a new test-site-specific session name once in
prepareEnvironment()
and be done with it.Comment #53
neclimdulI started to comment on this yesterday but it seems to have gotten lost.
The only thing it _might_ affect is drush but clearly not significantly because Testbot ran fine.
Lets move the UA stuff into another issue. I have strong feelings about adding testing code to the kernel and I don't want to block this.
Is this really needed? Otherwise looks rtbc.
Comment #54
alexpottOk let's remove the @todo - it't not properly formatted and really should have an existing issue.
Comment #55
neclimdulSkimmed again and still looks good.
Comment #56
Dries CreditAttribution: Dries commentedLooks good to me. Committed to 8.x.