Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Look into ways to speed up PHPunit tests #507

Closed
joeparsons opened this issue Feb 11, 2021 · 18 comments
Closed

Look into ways to speed up PHPunit tests #507

joeparsons opened this issue Feb 11, 2021 · 18 comments
Assignees
Labels
contractor Work to be done by an outside source spike Research required

Comments

@joeparsons
Copy link
Member

joeparsons commented Feb 11, 2021

What is the problem that we want to solve?

The amount of time it takes to run our suite of PHPunit tests is getting pretty long.

During the 2021-02-10 workshop @tadean suggested we might be able to speed up the execution of our tests if we apply the same group name to more of our tests to reduce the number of times Drupal is installed (as long as we don't group any tests that would conflict with each other together).

@joeparsons joeparsons added the spike Research required label Feb 11, 2021
@joeparsons joeparsons self-assigned this Feb 16, 2021
@danahertzberg danahertzberg added the contractor Work to be done by an outside source label Mar 11, 2021
@joeparsons
Copy link
Member Author

Another possibility would be to look into whether any of our existing functional browser tests might be able to be converted into Kernel tests:
https://mglaman.dev/blog/do-you-need-functional-test

@michaelhagedon michaelhagedon self-assigned this Mar 24, 2021
@michaelhagedon
Copy link
Contributor

Just as a baseline... I see your point.

PHPUnit 8.5.8 by Sebastian Bergmann and contributors.

..........                                                        10 / 10 (100%)

Time: 18.31 minutes, Memory: 8.00 MB

OK (10 tests, 127 assertions)

@michaelhagedon
Copy link
Contributor

michaelhagedon commented Mar 24, 2021

(Anyone else take notes for themselves and others on tickets? Sometimes I leave a novel... let me know if I should do this some other way.)

Just one test is about 1/10 the time. Makes sense:

$ lando phpunit web/profiles/custom/az_quickstart/modules/custom/az_demo/tests/src/Functional/AZDemoContentTest.php

.                                                                   1 / 1 (100%)

Time: 1.76 minutes, Memory: 6.00 MB

And two tests takes about 20% percent of the time. Maybe obvious, but this demonstrates that the testing time scales linearly with the number of full Drupal bootstraps. Makes perfect sense, just helpful to validate since I haven't looked at this test suite yet:

$ lando phpunit --group az_demo,az_paragraphs --verbose

..                                                                  2 / 2 (100%)

Time: 3.7 minutes, Memory: 8.00 MB

OK (2 tests, 17 assertions)

Changing the group of AZParagraphsTest to az_demo runs two tests, but does not reduce the time:

$ lando phpunit --group az_demo

..                                                                  2 / 2 (100%)

Time: 3.63 minutes, Memory: 8.00 MB

This is because AZParagraphsTest::setUp() correctly still calls BrowserTestCase::setUp(). Removing that call crashes the paragraphs test immediately:

$ lando phpunit --group az_demo

.E                                                                  2 / 2 (100%)

Time: 1.78 minutes, Memory: 8.00 MB

There was 1 error:

1) Drupal\Tests\az_paragraphs\Functional\AZParagraphsTest::testParagraphs
Drupal\Core\DependencyInjection\ContainerNotInitializedException: \Drupal::$container is not initialized yet. \Drupal::setContainer() must be called with a real container.

/app/web/core/lib/Drupal.php:175
/app/web/core/lib/Drupal.php:203
/app/web/core/modules/user/tests/src/Traits/UserCreationTrait.php:305
/app/web/core/modules/user/tests/src/Traits/UserCreationTrait.php:261
/app/web/core/modules/user/tests/src/Traits/UserCreationTrait.php:164
/usr/local/quickstart-install-profile/modules/custom/az_paragraphs/tests/src/Functional/AZParagraphsTest.php:61
/app/vendor/phpunit/phpunit/src/Framework/TestResult.php:691

ERRORS!
Tests: 2, Assertions: 8, Errors: 1.

So Drupal installation state doesn't automatically stay across tests. Not surprising... I might be concerned if it did!

@michaelhagedon
Copy link
Contributor

Oops, closed accidentally.

@michaelhagedon
Copy link
Contributor

I'm open to hearing more about the test grouping idea but, as implied in the description

(as long as we don't group any tests that would conflict with each other together)

this method will increase coupling between what should otherwise be independent tests, which may increase the difficulty of writing tests.

I'm curious about the Functional->Kernel test conversion idea and will head there next. It's more in line with the idea of a "testing pyramid".

@michaelhagedon
Copy link
Contributor

I haven't gone down the Kernel test route yet, mainly because I think it will make writing tests harder, if any of them are good fits for Kernel tests in the first place. Some are not, I think.

I did look into test parallelization using https://github.com/liuggio/fastest, and that seems to be helping, though I only just started down the path of making sure tests don't collide in the database.

Instead of 18 minutes, the tests took half that on my 4-core laptop:

- 8 shuffled test classes into the queue.
- Will be consumed by 4 parallel Processes.
2       1/8     ✔       3 min 99 ms     web/profiles/custom/az_quickstart/modules/custom/az_demo/tests/src/Functional/AZDemoContentTest.php
4       2/8     ✔       3 min 8 s 733 ms        web/profiles/custom/az_quickstart/modules/custom/az_core/tests/src/Functional/ClearCacheTest.php
3       3/8     ✔       3 min 9 s 921 ms        web/profiles/custom/az_quickstart/modules/custom/az_cas/tests/src/Functional/AzCasTest.php
1       4/8     ✔       3 min 16 s 311 ms       web/profiles/custom/az_quickstart/modules/custom/az_core/tests/src/Functional/MonitoringPageTest.php
3       5/8     ✔       2 min 54 s 923 ms       web/profiles/custom/az_quickstart/modules/custom/az_core/tests/src/Functional/ConfigOverrideTest.php
1       6/8     ✔       2 min 57 s 246 ms       web/profiles/custom/az_quickstart/modules/custom/az_paragraphs/tests/src/Functional/AZParagraphsTest.php
4       7/8     ✔       5 min 13 s 85 ms        web/profiles/custom/az_quickstart/themes/custom/az_barrio/tests/src/Functional/AzBarrioTest.php
2       8/8     ✔       5 min 23 s 364 ms       web/profiles/custom/az_quickstart/modules/custom/az_cas/tests/src/Functional/AzCasAdminSettingsTest.php

    ✔ You are great!
    Time: 8 minutes 23 seconds 493 milliseconds, Memory: 4.00 MiB

I'm not sure if this is really a viable approach, and I'm not sure if this will help Probo. I can clean this up soon and attempt a Probo build to see.

@joeparsons @tadean or anyone else: Does it seem like this approach could help?

@joeparsons
Copy link
Member Author

@michaelhagedon liuggio/fastest looks pretty interesting and those benchmarks definitely look promising! I'd be interested in seeing if we can make that work without causing database collisions, etc. I think it's probably worth a PR to see if it works on Probo. What do you think @tadean?

@michaelhagedon
Copy link
Contributor

I didn't realize that BrowserTestBase creates separate prefixed tables for each test, so that should minimize issues:
image

If someone wants to see how that works, it starts in BrowserTestBase::setUp(). Then FunctionalTestSetupTrait::prepareEnvironment() -> TestSetupTrait::prepareDatabasePrefix() -> TestDatabase::getDatabasePrefix(). The random prefix is ultimately created by TestDatabase::getTestLock().

michaelhagedon added a commit that referenced this issue Apr 2, 2021
If this runs at all the first time, we'll see how many cores Probo dedicates.
@michaelhagedon
Copy link
Contributor

Pushed a Probo modification to use liuggio/fastest. It didn't work because I don't really know where things go with the scaffolding repo and whatnot, but I learned I could log straight into the container and test stuff. So here's what happened when I ran PHPUnit in parallel manually in Probo:

- 8 shuffled test classes into the queue.
- Will be consumed by 40 parallel Processes.
4       1/8     ✔       1 min 29 s 246 ms       /src/az-quickstart-scaffolding/web/profiles/custom/az_quickstart/modules/custom/az_demo/tests/src/Functional/AZDemoContentTest.php
6       2/8     ✔       1 min 30 s 776 ms       /src/az-quickstart-scaffolding/web/profiles/custom/az_quickstart/modules/custom/az_core/tests/src/Functional/ConfigOverrideTest.php
1       3/8     ✔       1 min 33 s 979 ms       /src/az-quickstart-scaffolding/web/profiles/custom/az_quickstart/modules/custom/az_core/tests/src/Functional/ClearCacheTest.php
2       4/8     ✔       1 min 34 s 749 ms       /src/az-quickstart-scaffolding/web/profiles/custom/az_quickstart/modules/custom/az_core/tests/src/Functional/MonitoringPageTest.php
7       5/8     ✔       1 min 35 s 605 ms       /src/az-quickstart-scaffolding/web/profiles/custom/az_quickstart/modules/custom/az_cas/tests/src/Functional/AzCasTest.php
8       6/8     ✔       1 min 36 s 437 ms       /src/az-quickstart-scaffolding/web/profiles/custom/az_quickstart/modules/custom/az_paragraphs/tests/src/Functional/AZParagraphsTest.php
3       7/8     ✔       2 min 52 s 525 ms       /src/az-quickstart-scaffolding/web/profiles/custom/az_quickstart/modules/custom/az_cas/tests/src/Functional/AzCasAdminSettingsTest.php
5       8/8     ✔       2 min 52 s 771 ms       /src/az-quickstart-scaffolding/web/profiles/custom/az_quickstart/themes/custom/az_barrio/tests/src/Functional/AzBarrioTest.php
    ✔ You are great!
    Time: 2 minutes 52 seconds 785 milliseconds, Memory: 6.00 MiB

40 cores possible, and 3 minutes run time... encouraging!

@michaelhagedon
Copy link
Contributor

Well, first almost-good run failed a filesystem issue. Traced it to Drupal\Core\Test\TestRunnerKernel::boot() line 91 where it tries to create the simpletest directory. I wonder two parallel tests entered that condition at the same time and tried to create it and one lost.

I see we're creating a simpletest directory ahead of time in the probo config at /var/www/html/sites/simpletest. Maybe that's not the right place?

20 3	3/8	✘	1 min 38 s 975 ms	/src/az-quickstart-scaffolding/web/profiles/custom/az_quickstart/modules/custom/az_cas/tests/src/Functional/AzCasAdminSettingsTest.php[3] /src/az-quickstart-scaffolding/web/profiles/custom/az_quickstart/modules/custom/az_cas/tests/src/Functional/AzCasAdminSettingsTest.phpPHPUnit 8.5.8 by Sebastian Bergmann and contributors.
21
22 Runtime:       PHP 7.4.15
23 Configuration: /src/az-quickstart-scaffolding/web/profiles/custom/az_quickstart/phpunit.xml.dist
24
25 E.                                                                  2 / 2 (100%)
26
27 Time: 1.64 minutes, Memory: 8.00 MB
28
29 There was 1 error:
30
31 1) Drupal\Tests\az_cas\Functional\AzCasAdminSettingsTest::testPasswordResetBehavior with data set #0 (false)
32 mkdir(): File exists
33
34 /src/az-quickstart-scaffolding/web/core/lib/Drupal/Core/File/FileSystem.php:247
35 /src/az-quickstart-scaffolding/web/core/lib/Drupal/Core/File/FileSystem.php:232
36 /src/az-quickstart-scaffolding/web/core/lib/Drupal/Core/StreamWrapper/LocalStream.php:459
37 /src/az-quickstart-scaffolding/web/core/lib/Drupal/Core/Test/TestRunnerKernel.php:91
38 /src/az-quickstart-scaffolding/web/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php:619
39 /src/az-quickstart-scaffolding/web/core/tests/Drupal/Tests/BrowserTestBase.php:402
40 /src/az-quickstart-scaffolding/web/profiles/custom/az_quickstart/modules/custom/az_cas/tests/src/Functional/AzCasAdminSettingsTest.php:46
41 /src/az-quickstart-scaffolding/vendor/phpunit/phpunit/src/Framework/TestResult.php:691
42
43 ERRORS!
44 Tests: 2, Assertions: 10, Errors: 1.
45 PHP Warning:  file_put_contents(/src/az-quickstart-scaffolding/web/profiles/custom/az_quickstart/.phpunit.result.cache): failed to open stream: Permission denied in /src/az-quickstart-scaffolding/vendor/phpunit/phpunit/src/Runner/DefaultTestResultCache.php on line 108

@michaelhagedon
Copy link
Contributor

Well, I ran a rebuild and it worked! 3 min 5 sec!

But that first failure smells of non-determinism. I do see that the simpletest directory we're creating does have stuff in it during the run, but I'm thinking Drupal also created this one:

/var/www/html/sites/default/files/simpletest# ls -al
total 8
drwxrwxrwx 2 www-data www-data 4096 Apr  2 06:05 .
drwxrwxrwx 6 www-data www-data 4096 Apr  2 06:05 ..

Drupal\Core\Test\TestRunnerKernel::boot() was trying to create public://simpletest/ which would seem to line up with sites/default/files better. Not sure why one is used and not the other.

michaelhagedon added a commit that referenced this issue Apr 2, 2021
@michaelhagedon
Copy link
Contributor

I decided to preemptively create that other simpletest directory, though I'm not sure how to prove that I need to do that. Next build was just under 3 minutes!

@michaelhagedon
Copy link
Contributor

michaelhagedon commented Apr 2, 2021

If this is on a good path and we don't find red flags, I'm certain there's some refactoring needed. This new dependency should probably be in the az-quickstart-dev for one thing.

Also I have an extra lando command too that runs phpunit this way.

@joeparsons
Copy link
Member Author

@michaelhagedon We recently added a unit test that uses PHPunit data providers. I saw this in the liuggio/fastest issue queue that makes me wonder if it will be able to run our new test: liuggio/fastest#8

Any idea?

@michaelhagedon
Copy link
Contributor

@joeparsons Interesting find! I'm not 100% sure, but I think that issue is referring to parallelizing the run of each individual array element input from the data provider. liuggio/fastest will indeed not do that because it only parallelizes classes.

But in our case, I think it doesn't matter. This new test is hundreds of times faster than the browser tests, so if the inputs from the data provider all run in sequence, that's fine. Here's what happens if I run all the tests now:

- 9 shuffled test classes into the queue.
- Will be consumed by 4 parallel Processes.
1       1/9     ✔       4 min 5 s 513 ms        web/profiles/custom/az_quickstart/modules/custom/az_demo/tests/src/Functional/AZDemoContentTest.php
4       2/9     ✔       4 min 10 s 815 ms       web/profiles/custom/az_quickstart/modules/custom/az_core/tests/src/Functional/ClearCacheTest.php
1       3/9     ✔       3 min 36 s 86 ms        web/profiles/custom/az_quickstart/modules/custom/az_core/tests/src/Functional/ConfigOverrideTest.php
3       4/9     ✔       7 min 54 s 264 ms       web/profiles/custom/az_quickstart/themes/custom/az_barrio/tests/src/Functional/AzBarrioTest.php
3       5/9     ✔       777 ms          web/profiles/custom/az_quickstart/modules/custom/az_paragraphs/tests/src/Unit/AZVideoEmbedHelperTest.php
2       6/9     ✔       8 min 2 s 307 ms        web/profiles/custom/az_quickstart/modules/custom/az_cas/tests/src/Functional/AzCasAdminSettingsTest.php
4       7/9     ✔       3 min 52 s 592 ms       web/profiles/custom/az_quickstart/modules/custom/az_cas/tests/src/Functional/AzCasTest.php
1       8/9     ✔       2 min 28 s 790 ms       web/profiles/custom/az_quickstart/modules/custom/az_paragraphs/tests/src/Functional/AZParagraphsTest.php
3       9/9     ✔       2 min 26 s 145 ms       web/profiles/custom/az_quickstart/modules/custom/az_core/tests/src/Functional/MonitoringPageTest.php


    ✔ You are great!
    Time: 10 minutes 21 seconds 201 milliseconds, Memory: 4.00 MiB

The new one seems to work fine. I also tried intentionally braking everything in the data provider by adding a character to all the $expected_ids and they all ran and failed in order, 11 times, consistently. They only used one core on their own, which is expected if fastest is only parallelizing classes.

So I think it's OK. Glad you checked!

@trackleft
Copy link
Member

trackleft commented Apr 16, 2021

10 minutes 21 seconds 201 milliseconds. time for a new computer, you need 40 cores

@michaelhagedon
Copy link
Contributor

@trackleft I should definitely put 40 cores as a requirement on my next laptop request!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
contractor Work to be done by an outside source spike Research required
Projects
None yet
Development

No branches or pull requests

4 participants