BrowserTestBase creates a fresh Drupal installation for each test. This takes a considerable part of test execution time and has a negative impact on developer experience.
Another performance problem is enabling modules that a test depends on. It is quite expensive operation and in some cases it may take even more time than installing Drupal.
The possible solution could be dumping a database into SQL file just before the test case get executed. So that on the next test run Drupal can be installed using predefined SQL dump which is much faster the normal installation process.
Comment | File | Size | Author |
---|---|---|---|
#71 | interdiff_61-71.txt | 560 bytes | lexbritvin |
#71 | 2900208-71.patch | 6.45 KB | lexbritvin |
#69 | interdiff_concurrency_48_69.txt | 3.37 KB | GuyPaddock |
#61 | 2900208-61.patch | 6.44 KB | _utsavsharma |
| |||
#61 | interdiff_60-61.txt | 3.33 KB | _utsavsharma |
Issue fork drupal-2900208
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
Chi CreditAttribution: Chi commentedThe performance gain of caching a database varies. While for some tests the total execution time remains almost the same on other tests it can be reduced 5 and more times.
Comment #3
Chi CreditAttribution: Chi commentedComment #5
Chi CreditAttribution: Chi commentedRerolled the patch to 8.x-5.x.
Comment #6
Anonymous (not verified) CreditAttribution: Anonymous commentedLooks awesome! 💪
Comment #7
dawehnerComment #8
borisson_I really love this as an idea, we're running daily tests for the search ecosystem on travis and I'm constantly running into the travis time limit with facets: https://travis-ci.org/mkalkbrenner/search_api_integration_tests/builds/3...
@var string
?Should be one line.
Let's do,
array_unique(sort($modules));
to make sure that when a class extends the another one and for some reason the same module is declared twice (because the base class changed or something like that).Should we worry about filename length here? This can lead to really long filenames. Some of the contributed modules that extend already existing tests can have easily 8-9 modules.
I'm not sure this is something I like. Adding an exec isn't great, I also don't think we should add the
sed
here, that would make windows systems not able to use this, I think?I'm not sure that's the best solution.
Same here.
Comment #9
dawehnerIt seems to be that the experiment @fabianx did a while ago in #2759197: [D7] Improve WebTestCase performance by 50% did not required the
mysqldump
command. Have you seen this issue? Do you think we could incorporate that strategy?Comment #10
Chi CreditAttribution: Chi commented@dawehner, I just checked it. They do not dump data to filesystem at all, but simply copy tables within the same database. I think it similar to what SimpleTest Clone does.
At this point I am not sure which approach is better.
Comment #11
dawehner@Chi
Your approach seems to for example not support pgsql, am I right?
Comment #12
Chi CreditAttribution: Chi commented@dawehner right, but technically it is possible by supplying different shell commands for each database type like
drush sql-dump
does.The approach with copying tables is cross-platform and definitely more reliable from this point.
Comment #13
borisson_In that case, I think we should close this issue and try to get #2747075: [meta] Improve WebTestCase / BrowserTestBase performance by 50% going again. At least a reroll on that issue should help. While it might not be as big an improvement as this issue, it still is an improvement and it will work for all databases.
Comment #15
jibran#2747075: [meta] Improve WebTestCase / BrowserTestBase performance by 50% is meta so I don't think we should close this one. This can be its child also the patch in #2747075 is for WTB and this is for BTB so this is more up to date.
Comment #16
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedI think this should just re-roll the latest patch from the meta issue - it was a functional port.
Comment #17
lokapujyaThis issue is awesome!
Comment #19
thtas CreditAttribution: thtas commentedI'm really liking this idea too. Functional tests are so useful, but it's such a pain how long they are taking.
Maybe instead of the exec approach, we could be doing something similar to how Drupal\FunctionalTests\Update\UpdatePathTestBase works by loading the DB from output generated from the dump script at scripts/dump-database-d8-mysql.php
Comment #20
Mile23Since it hasn't been mentioned here: Just a heads up that #2973227: Move UI helper functions from BTB into the traits was committed to support https://gitlab.com/weitzman/drupal-test-traits
That's a third-party testing framework that allows you do to functional tests against an existing site.
Comment #22
jibranCreated a reroll from scratch. Reverted
DrupalKernel
change. Addressed #8.1,2,3.Comment #23
jhedstromThis might be an over-engineering suggestion, but if this were instead implemented as a plugin, then in theory non-core DB implementations could also utilize this, and the code would be a bit cleaner as all db-specific loading/dumping logic would be encapsulated in the plugins...
Comment #24
jibranI agree the patch is little rough around the edges but before proceeding with this we should have some kind of consensus from contributor or approval from maintainers.
Comment #25
andrewbelcher CreditAttribution: andrewbelcher at FreelyGive commentedWould really love this patch! However, I hit the following error. The test has quite a lot of dependencies and I'm running in DDev, not sure if the either (or both) of those are triggering the issue:
Comment #27
mglamanFor something in contrib this would be a huge filename and probably crash some filesystems, maybe Windows which has problems with large file paths.
Perhaps a hash of the modules array is more appropriate.
Comment #28
hestenetThe topic of making testing significantly faster (and thus more affordable) has certainly become top of mind again.
I would love to help bring attention to this effort using DA channels, if that would be helpful.
It's also rather late in the game to be suggesting alternative approaches, but another suggestion from the world of containerization could be to scaffold testing using a base, preinstalled Drupal environment, build new instances on top of that base for each test, resetting to the base each time to ensure a clean environment.
Comment #31
Ruuds CreditAttribution: Ruuds at Groowup Digital Agency commentedThe patch of #22 cut down my testing time in half (35 to 16 minutes)!
Although it's not the cleanest solution as mentioned before, i've made some changes:
- The dumpFile's filename is now a md5 hash of the imploded module list
- Tests using the public and private filesystems failed
- mysqldump / mysql now takes the database hostname in account.
Comment #32
Ruuds CreditAttribution: Ruuds at Groowup Digital Agency commentedAllright.. The correct patch file here.
Comment #33
adityasingh CreditAttribution: adityasingh as a volunteer and at Material for Drupal India Association commentedFixed coding standards error.
Comment #34
oliverpolden CreditAttribution: oliverpolden commentedPatch in #33 worked for me. Brought test time down from 12.7 minutes to 56 seconds in my case.
Comment #35
alexpottHash salt is duplicated and could be set to
Crypt::randomBytesBase64(55)
which i think is more correct.Instead of doing this we can use the database dump command.
Then to install from a dump we can do:
This also needs to include the default theme and profile.
Comment #36
alexpottAlso I don't think this is useful for 99% of core browser tests - we use the testing profile and only ever install a few modules. It won't make as much difference now the install is much quicker. But this would be super super useful functionality for real projects with their own dump or install profile.
We do need test coverage to prove things actually work.
Comment #37
Chi CreditAttribution: Chi commentedThere is another much more simple approach to speed up tests which is in-memory SQLite database.
<env name="SIMPLETEST_DB" value="sqlite://localhost//dev/shm/test.sqlite"/>
Comment #38
alexpott@Chi that's good for running single core tests but if you testing a site build with a large install profile (lots of modules and config) using a dump really really helps. Also I think in-memory sqlite dbs suffer when you do testing via webdriver (or anything that uses a real browser) because of the multiple requests. Like even create image style derivatives can cause a sqlite memory db to be really slow compared to another db type.
Comment #39
tyler36 CreditAttribution: tyler36 commentedWhat level of speed improvment is being aimed for here? Is everyone testing against a common test here?
Let's establish a baseline:
Note: DemoTest.php is the simpliest / fastest functional test I could think of. I understand that functional test usually involve enabling modules, creating nodes, manipulating users. With a common baseline, it's easier to establish setups, settings, pain-points, that can be targeted or improved.
Setup:
- Copy
DemoTest.php_.test
into<drupal_root>/modules/custom/demo/tests/src/Functional
- Rename
DemoTest.php_.test
->DemoTest.php
(I can't upload a PHP file to this site)- Run PHPunit, tartgeting test.
Clean drupal install results:
- Apply
2900208-33.patch
- Run PHPunit, tartgeting test.
Clean drupal install results:
- No noticable difference but then the test isn't really doing anything other then starting up.
Comment #40
Chi CreditAttribution: Chi commentedThere is a simple benchmark in comment #3.
Comment #41
alexpott@Chi the Drupal installer has had significant performance improvements since then so any benchmark from 2017 needs to be re-run. As I've pointed out the real utility for this is on real sites where installing your real site can take quite sometime because you're dealing with way more modules and config and sometimes content. I think we should provide a test trait along the lines of https://gist.github.com/alexpott/8cd82bc75b88c01489d837cf8432451b that we document or even consider automatically using when the profile is not one of the core testing profiles.
Comment #43
GuyPaddock CreditAttribution: GuyPaddock at Inveniem commentedPerhaps the Umami test would be a better common baseline:
https://api.drupal.org/api/drupal/core%21profiles%21demo_umami%21tests%2...
Comment #45
Wim Leers@joevagyok has been using this patch at the European Commission for years, and it improves test performance ~4x!
I think this issue may have been underestimating the indirect benefits for concrete projects. Even if it's not 4x but just 1.5x faster … then it would be a huge productivity boost for many Drupal projects.
@joevagyok will push this forward tomorrow :)
Comment #47
owenbush CreditAttribution: owenbush at Lullabot for Principal Financial Group commentedWe are testing out this patch, to help with our 26min+ functional test runs, and for the most part it seems to be working as expected. However, one of our tests checks the
X-Drupal-Cache-Tags
header and we noticed that on initial runs (no DB dump yet) the tests pass, but on subsequent runs (restoring from the DB dumb) the tests fail, and that's because theX-Drupal-Cache-Tags
header is not present.This seems to be due to the fact that
FinishResponseSubscriber::debugCacheabilityHeaders
is false.In tests which run
installDrupalFromProfile()
the cacheability headers are set by calling$this->setContainerParameter('http.response.debug_cacheability_headers', TRUE);
from withininitSettings()
. That never gets called when tests runinstallDrupalFromDump()
This patch addresses that problem, with an interdiff provided too.
Comment #48
owenbush CreditAttribution: owenbush at Lullabot for Principal Financial Group commentedRe-rolled patch #47 to apply to 9.4.6
Comment #50
blazey CreditAttribution: blazey commentedHey, we had a similar case but limited in scope. Our project has only kernel tests and uses sqlite, so I wrote a module that handles that. Feel free to test it out / benchmark in your projects if that fits your case: https://www.drupal.org/project/sqlite_test_cache
Comment #51
fjgarlin CreditAttribution: fjgarlin as a volunteer and at Drupal Association commentedIs this issue still in "Needs work" or "Needs review"? I see a new patch was added at #48, just not sure if it addresses all of #35 feedback.
Comment #53
ivnish CreditAttribution: ivnish commentedPatch #48 works with Drupal 9.5
Run tests of my module without this patch: Time: 01:27.310
Run tests of my module with this patch: Time: 00:36.736
tyler36, you need to add BROWSERTEST_CACHE_DB=1 before phpunit exec. In my case this is
fin exec BROWSERTEST_CACHE_DB=1 vendor/bin/phpunit --testsuite=my_module
Comment #54
tyler36 CreditAttribution: tyler36 at Inter Quest commented@ivnish Thank you for pointing out the
BROWSERTEST_CACHE_DB
requirement.Tested #48 on Drupal 10.0.9 one existing project with mixture of unit, kernel and functional tests (32 tests, 280 assertions).
Time went from 02:28 => 1:13. Multiple runs averaged about 50 second saved.
Comment #55
tyler36 CreditAttribution: tyler36 at Inter Quest commentedI think the environmental variable should be added to
core/phpunit.xml.dist
. This allows it to be the "default" setting.Comment #56
fjgarlin CreditAttribution: fjgarlin as a volunteer and at Drupal Association commented#55 is a good suggestion. I'd add it to the list of changes requested in #35
Comment #57
ivnish CreditAttribution: ivnish commentedI have a lot of bugs with DB cache. For example "unknown schema" and "route not found". Needs to remove old dump
Comment #59
kaustavbagchi CreditAttribution: kaustavbagchi at Principal Financial Group commentedThis is an updated version of 2900208-48.patch which is now compatible with Drupal 10.1.x version.
Comment #60
kaustavbagchi CreditAttribution: kaustavbagchi at Principal Financial Group commentedThis is an updated version of 2900208-59.patch with whitespaces removed, compatible with Drupal 10.1.x version.
Comment #61
_utsavsharma CreditAttribution: _utsavsharma at OpenSense Labs for DrupalFit commentedFixed failures in #60.
Comment #62
ivnish CreditAttribution: ivnish commentedI manually compared patch #48 and patch #59 and #60. Patches #59 and #60 are garbage patches without any changes. Only code style was broken
Patch #61 added one new change
Comment #63
catchIt would be useful to see if this makes a difference on gitlab ci. We'd need an MR, with the environment variable shortcutted (or set in the gitlab YAML).
Comment #64
smustgrave CreditAttribution: smustgrave at Mobomo commentedgitlab would make sense. and #60 would be good to explain why you added a new change.
Comment #67
Dave ReidI found an issue using the latest version on 10.2 where
$connection_info['default']['driver']
is nowDrupal\mysql\Driver\Database\mysql
instead ofmysql
. Opened an official MR for better visibility and GitLab CI testing.Comment #68
Dave ReidComment #69
GuyPaddock CreditAttribution: GuyPaddock at Inveniem commentedSo far, I've been able to fix the concurrency issues and improve performance by reworking the methods as follows:
An interdiff (against #48) is attached for the changes above, but I was curious about feedback on this approach before I apply them to the MR.
Comment #70
Wim LeersIf a full install + module installs are no longer needed, but just a
drush site:install --existing-config
, then that would already speed up things considerably.Which means that #3406929: Configuration being imported by the ConfigImporter sometimes has stale original data (which just landed) should also help with this.
Comment #71
lexbritvin CreditAttribution: lexbritvin at Smile commentedThe provided patches don't consider install profile and default theme.
Here is a small enhancement to the patch.