Problem/Motivation
- run-tests.sh is hard to maintain code
- Its a wrapper around phpunit, which caused many bugs in the past
Proposed resolution
Try to run https://github.com/brianium/paratest with kernel tests and browser tests. It could be also worth trying to find out whether running unit tests in parallel would be worth for itself.
Note: This is an exploration issue.
Remaining tasks
User interface changes
API changes
Data model changes
| Comment | File | Size | Author |
|---|---|---|---|
| #12 | 2781123_12.patch | 56.46 KB | mile23 |
| #2 | 2781123-2.patch | 84.75 KB | dawehner |
Issue fork drupal-2781123
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
dawehnerSo far some progress.
For testing purposes I used the following command:
./vendor/bin/paratest -c core/phpunit.xml --testsuite=unit -fNote: It fatals at the moment ...
Comment #3
deviantintegral commentedI was able to get -f working by avoiding the test suites and using paths instead.
Comment #4
dawehnerInteresting, nice experiment!
Comment #7
mondrakeTried this
vendor/bin/paratest -p4 --colors -c core/phpunit.xml.dist --group Database corein a TravisCI build on PHP 7.2, in combination with the patch in #2927806: Use PHPUnit 6 for testing when PHP version >= 7.2, that allows upping the PHPUnit version to 6.5.5.
Seems to work fine, see https://travis-ci.org/mondrake/d8-php72/builds/318704239?utm_source=gith..., even if it looks like the parallel is actually slower - maybe there's some overhead in repeating the test group discovery process.
Comment #8
mondrakeAlso tried this
../vendor/bin/paratest -p4 --colors --group Database testsfrom within the core subirectory, see https://travis-ci.org/mondrake/d8-php72/builds/318709056
that seems much faster, but skips the tests in the modules
Comment #10
mile23Under new management: https://github.com/paratestphp/paratest
Comment #12
mile23Some things I found:
#3021654-10: Deprecate file_delete() and file_delete_multiple() in file.inc
We have to use paratest ~1.0 because 2.0+ requires PHPUnit 7.
We can't use paratest with PHP 5, because it requires PHP 7.
paratest does not populate its testsuites using
BaseTestRunner::SUITE_METHODNAME, which means we can't use--testsuites. To use our testsuites would mean some upstream changes, and we'd have to ask for them to be backported to 1.0. This also means we're back to square one with how to config PHPUnit for running tests in contrib.paratest doesn't seem to know that if you tell it a testsuite you shouldn't have to tell it a directory.
Using the patch I was able to get a test run started but there were a lot of errors where Drupal was saying you need to install your site, like this:
Here's a test of components with JUnit log file:
That's about right. Resulting JUnit file has 8112 lines, which tracks with the number of tests.
This patch includes the lock file and a change to drupalci.yml to try and run everything. Let's see what happens.
Comment #18
volegerLooks like paratest was updated and now we can skip unsuported versions of the PHP.
Comment #19
andypostMoreover PHP 8 enables random port selection for internal webserver so less conflicts possible
Ref https://php.watch/versions/8.0/built-in-server-port-zero
Comment #21
beakerboyOne disadvantage of the run-tests script is that each test is run as a separate command. PHPUnit has no means to collect coverage reports from multiple separate runs and combine them into one report. I've begun measuring code coverage of the sqlsrv database driver by running a single phpunit command using unit, kernel, and the install functional tests and outputting the results in html fomat. This produces a series of web pages that display the lines covered, and coverage metrics. This has then led me to developing test cases in the driver module for those places that core is not testing. Another project that I work on has a rule that all pull requests must not decrease the overall coverage percentage. This ensures all new pull requests include tests that cover all execution paths of the new code.
Comment #22
mile23Definitely check out #2626986: [meta] Improvements to run-tests.sh
Paratest also runs tests as different processes, so it's difficult to gather coverage reports.
I haven't looked lately, but I'd wager someone has solved this by merging coverage data in some way, or that there's a better parallel testing script someone came up with.
I'd also wager that the info in #12 above is out of date.
Edit: Ah, I knew I'd find it. #2974911: Allow run-tests.sh to generate coverage reports
Comment #23
andypostlinking blocker #2974911: Allow run-tests.sh to generate coverage reports btw it lives in simpletest module for some reason
Comment #27
fjgarlin commentedI was investigating the usage of "paratest" for (initially for contrib) via GitLabCI (https://git.drupalcode.org/project/gitlab_templates).
One of the biggest differences I saw was that we cannot specify the "--printer" option, which Drupal uses to generate the html in the "browser_output" folder. This option is currently not supported in any of the versions (currently 6.x and 7.x).
I managed to get _some_ improvements, going from around 30 min to around 20 min to run the unit tests of the module, getting the exact same results, but as mentioned before, not having the "browser_output" folder.
I created this issue in the "gitlab_templates": #3370952: Run phpunit tests from a single job in parallel, using paratest
Comment #28
mstrelan commented#27 FWIW once we have #3217904: [meta] Support PHPUnit 10 in Drupal 11 we will not need the
--printeroption.Comment #29
catchSome of our test classes take anything up to 5-6 minutes to complete.
paratest theoretically would help with that, by allowing multi-method classes to be run in parallel.
However, for that to be an overall benefit, we'd need to still be able to affect which tests are run by which runner and which order, see #3388365: Distribute @group #slow tests between test runners and mark more tests for the run-tests version of this. If we do that, we'd start the slowest methods first, so that if an individual method takes 5 minutes (and some do), it starts as early as possible.
Comment #30
wim leers#29 sounds totally sensible!
Comment #31
wim leersPer #3388505-5: CKEditor 5's Functional JS tests take a long time to run, this is now really a blocker.
Comment #32
fjgarlin commented#3374070: Experiment with concurrency package
I investigated briefly the usage of this tool for core and gitlabci.
Comment #33
deviantintegral commented+1 to the idea of starting slowest tests first!
It's been a long time since I looked at this, but I've since been working on a project where we set up functional tests with playwright, fully supporting parallel tests with separate databases. The project starts from an install profile, so it's not that different than what core does. I would like to refactor this to be public, but in lieu of sharing code I'll share some lessons we learned:
cpcall to copy the pristine sqlite database into a test-specific instance. While core needs to test with a variety of database backends, perhaps tests could be marked as either requiring tests against all database types, or as not requiring any specific database backend to allow greater use of sqlite in tests.Comment #34
catch@fgarlin looked at paratest in #3374070: Experiment with concurrency package and didn't seen an improvement in performance, in fact saw a performance hit.
I had a quick play with paratest locally, only got so far because I had trouble getting it to do 'run all tests from a specific test suite in parallel', it seems to always pick up all of the tests, probably missing an argument somewhere.
Paratest has broadly two modes:
1. Run test classes in parallel (this is what run-tests.sh does)
2. Run test methods in parallel
Run tests methods in parallel is interesting because it would mean an 18-method functional test isn't inherently slow any more, and those are our longest running tests at the moment. However, I think we should make an explicit decision for now, that we won't rely on per-method parallelism for optimisation of core tests, for the following reasons:
1. Our tests fail with per-method parallelism according to @fgarlin's investigations, so we'd have to do a lot of work just to get compatible at all
2. per-method parallelism at the runner level precludes (or at least makes a lot more complicated) implementing other optimisations like #2900208: Improve performance of functional tests by caching Drupal installations
3. We'd then be locked into paratest
If we decide to defer that for now, we can optimise for per-class parallelism for now, without waiting on something that doesn't exist yet, and see how far we get.
We could still switch to paratest for per-class concurrency if we want to move away from run-tests.sh, but then it's a case of exchanging somewhat 1-1 not a completely different approach.
Note also that HEAD adds support for gitlab parallel runners on top of concurrency, which is the difference between 30 minute and 17 minute test runs. And and #3388365: Distribute @group #slow tests between test runners and mark more tests adds support for distributing known-slow tests between runners at the start of each job which is the difference between 17 minute and 11 minute test runs. We'd need to replicate those features via paratest somehow in order to keep the same performance.
Comment #35
bbralaI'd say we shouldn't use paratest. Since it is not a drop in replacement this will probably surface weird issues in the testsuite. Combined with blocking other innovations like the install caching it seems like we should not do this in the foreseeable future. There is enough going on in testland right now.
Comment #36
wim leersWhy?
And: is this true for all types of tests?
run-tests.shis notoriously difficult to modify, update and, frankly, use (for example: for testing MySQL one needs to pass in--sqlite … --dburl …😳 — being clarified at #2890023: clarify db settings for run-tests.sh example command).Wouldn't a good middle ground be to start adopting
paratestfor those types of test where there's no concurrency issues? IOW: wouldn't it be a good middle ground to haverun-tests.shstart usingparatestitself for types of tests where it makes sense, to allow us to gradually move away from it?Could you explain (also for posterity sake) how those (awesome!) performance benefits are easy to achieve with
run-tests.shbut difficult usingparatest? 🙏Comment #37
bbralaWell, the issue is pretty much this:
https://github.com/paratestphp/paratest/issues/532
Paratest is erunning in parallel based on a single command, but does not allow chunking. So a lot of the control you have right now through run-tests.sh (using offset+limit) and also ordering tests based on group (#slow first) is something that is not trivial if we add paratest to the mix.
To clarify:
There are 2 kinds of concurrency on gitlab + drupal tests right now.
PARALLEL in Gitlab: running commands on multiple runners.
CONCURRENCY in run-test: running multiple processes on a single runner in gitlab.
This makes this discussion a little harder. We are currently running multiple runners in PARALLEL while running tests CONCURRENT on those runners.
We have to many dials right now imo, but that is a different discussion i think :)
Comment #38
moshe weitzman commentedThanks for giving us some better vocab. IMO, virtually no contrib module needs CONCURRENCY. Thats why I favor straight phpunit for the default implementation. Core and a few contrib projects can override from there.
Comment #39
catchGenerally only gitlab, or people working on gitlab integration, would use run-tests.sh - I always use phpunit locally because I can never get the cli arguments right, better to have phpunit.xml and then use phpunit with no arguments at all.
From gitlab experimentation in #3386474: [omnibus] Speed up gitlab ci runs parallel jobs make a massive different for functional, functional javascript, and kernel tests. This is the difference between 10-15 minute test runs vs. 30-35 minute test runs. What we're doing is running ~180 functional tests at a time.
We may end up using parallel jobs even more assuming we downsize the runners from 32 cpu AWS instances too. I think I have things (with about 10 MRs applied) where we can tweak concurrency vs parallelism vs. CPUs and have a pretty good idea of the effects, however like @bbrala says there are so many variables it gets very complicated/overwhelming very fast. If we only use paratest for unit and build tests, we haven't really gained anything except a new dependency (and possibly more edge case failures) though.
I'm not sure about this. If a contrib module has one unit test, one kernel test, and one functional test, then concurrency gets them nothing at all because the separate jobs by test type run each suite in parallel.
However, if they have 10 functional tests, running them at 10 concurrency will get the results back approximately ten times as fast. This still might only be a minute or two faster for a developer, which is not a huge difference, but for other test runs, if the runner is only needed for say 2 minutes instead of 4 minutes, we can run double the number of contrib tests with the same overall amount of runner time. But this might not turn out to be an issue if we only allocate say 2 CPUs to a runner and eight or sixteen of them can co-exist on an instance. Right now though we'd be running 10 tests in serial on a 32 cpu AWS instance which seems overkill.
IMO our best option for maintainability is dropping the --group and etc. arguments from run-tests.sh and slim it down to only what we need for gitlab-ci. That gives us less run-tests.sh code to maintain and makes it clearer what we'd need to port to any replacement.
This is the relevant bits from run-tests.sh
What this does:
1. .gitlab-ci/pipeline.yml sets certain jobs to run in parallel
2. When jobs run in parallel, gitlab sets index and total environment variables so you know you're being run in parallel and which of the parallel jobs you are.
3. In run-tests.sh we check those variables, and then slice up the jobs based on the total and index. Additionally, via
@group #slowwe've got a list of the slower/est tests and we distribute those evenly to run at the very beginning of each parallel job.paratest doesn't understand gitlab index and total. So far the only idea I had to potentially recreate that would be to embed those directly into test discovery itself (so that if the environment variables are set, we only return the slice, and also interleave the corresponding slow tests at the beginning of the list) - that might be doable but it's not a 1-1 swap.
Comment #40
mile23See this meta: #2626986: [meta] Improvements to run-tests.sh
I generally support @catch's idea of culling out stuff from run-tests.sh that isn't in use by CI processes. But I also think it's not a huge lift to make it more maintainable, minimized or not, as per #2624926: Refactor run-tests.sh for Console component. (which won't be getting a re-roll by me until there's some consensus). It should be even easier after the work for #3057420: [meta] How to deprecate Simpletest with minimal disruption
The question here is not that there are tradeoffs between paratest and run-tests.sh, and which will we choose? The problem is that no one's really maintaining run-tests.sh, for fearful reasons rather than engineering ones.
Put it into a component, and for realsies, run-tests could be the coolest thing we export to the PHP world at large, but instead it languishes in a fake shell script.
Comment #41
alexpottLast time I tried to use paratest on a client project it did not play nice at all with Symfony deprecation reporting.