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
[Performance] Improve Infection performance executed against slow test suites #1539
Conversation
Suggestion: if we can know a certain test suite contains this much tests (a thousand) which take this much seconds to run, we can make an educated guess as to whenever to enable this new option. |
thousand of tests does not mean they are slow, to be honest. 1000 unit tests can take less than a second, while 1000 functional tests can take minutes. Probably we need to calculate the total time of the covering tests and compare with some constant (precalculated value). I tried it, and enabled |
{ | ||
$options = $this->buildForInitialTestsRun($configPath, $extraOptions); | ||
|
||
if (count($tests) > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we can take just the number of tests, and test suite time wouldn't necessary give the whole picture. I'd say median test time multiplied on the number of tests will give a better estimate to the time measure because median naturally filters outliers. Or we can go full with three standard deviations of the mean, which should give possibly even better estimate.
$filterString .= escapeshellcmd($testCaseString) . '|'; | ||
} | ||
|
||
$filterString = rtrim($filterString, '|') . '/'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we sure we absolutely need this computation? I mean, is there a chance we won't run this mutant ever? And what about mutants on the same line of the same file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
didn't get you, could you please clarify what do you mean?
This code just builds '/App\\\\Test::test_case1|App\\\\Test::test_case2/'
string, meaning to run only 2 test cases of X. Those tests cases - is 100% of all the test cases that cover particular mutated line (Mutant)
@maks-rafalko I'm re-reading the description of this issue, and I'm trying to make sense of it. Just because I'm trying to understand, if I have a test like following: class MyTest ... {
function test1() { ... }
function test2() { ... }
} Does this patch make sure that only either of |
@Ocramius Exactly. Currently (in <?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/schema/9.2.xsd" bootstrap="/infection/tests/e2e/PestTestFramework/./infection/interceptor.autoload.740af9ed4589f3494f0c14ed544eec29.infection.php" colors="false" stopOnFailure="true" cacheResult="false" stderr="false">
<testsuites>
<testsuite name="Infection testsuite with filtered tests">
<file>/infection/tests/e2e/PestTestFramework/tests/CalculatorPhpUnitTest.php</file>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>/infection/tests/e2e/PestTestFramework/src/</directory>
</whitelist>
</filter>
</phpunit> ^ as I described in the description, PHPunit has no ability to configure which test cases to run in |
Excellent++! This is gonna be literal HOURS of mutation test runs saved. For me, the largest mutation test suite (integration tests) takes 2h :D |
Yes, this is what I'm experiencing on my test suite as well with 2000+ functional tests ;) |
Yes, it is all not that simple, unfortunately. I think this is worthy a dedicated research project, and an option toggle like |
Previously, we ran **files** that contains tests that cover mutated line. In `phpunit.xml` this is all we can do. To run **test cases** that cover mutated line from that files, we can only use `--filter` options. This is what this change does
… tests run and for mutant
…on-unique test cases and using `foreach`
…y covering test cases By default, Infection runs all tests cases from **files** that contain at least 1 test case that cover mutated line Using `--only-covering-test-cases`, we can enable filtering and run only those test cases from files, that cover mutated line. For very fast test suites this decreases performance (see #1539). For slow test suites (like functional / integration tests) - this can dramatically improve performance of Mutation Testing / Infection
a4c5b0f
to
e81e0de
Compare
…y covering test cases By default, Infection runs all tests cases from **files** that contain at least 1 test case that cover mutated line Using `--only-covering-test-cases`, we can enable filtering and run only those test cases from files, that cover mutated line. For very fast test suites this decreases performance (see #1539). For slow test suites (like functional / integration tests) - this can dramatically improve performance of Mutation Testing / Infection
e81e0de
to
b185ad9
Compare
…e a regular expression
@sanmai ready for review. I've added |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
…y covering test cases By default, Infection runs all tests cases from **files** that contain at least 1 test case that cover mutated line Using `--only-covering-test-cases`, we can enable filtering and run only those test cases from files, that cover mutated line. For very fast test suites this decreases performance (see #1539). For slow test suites (like functional / integration tests) - this can dramatically improve performance of Mutation Testing / Infection
Thank you @sanmai for the review |
Q: can the performance degradation on the "infection on infection" usecase also be influenced by simply having more tests in this branch? Or did you correct for that in some way? |
I avoided the impact of mutating the new code and tests by adding The most impact is really because of building the very long strings for each mutant where running all unit tests is faster than creating this filter |
This PR dramatically improves performance for Infection executed against functional (read - slow) tests.
for one of the PR in my daily job's project.
Problem
As you know, we always said that Infection runs only those tests that cover mutated line, not the all tests from an application.
It is partially true, but not exactly. PHPUnit's XML file allows us to only filter original set of tests by
<directory>
or<file>
.And this is what we do starting from 2017.
Example of a `phpunit.xml` file generated by Infection for one of the Mutant (click me)
Notice in the example above, that we tell PHPUnit to run tests only from
CalculatorPhpUnitTest.php
. But this file can contain 1 test that covers mutated line, and hundreds of other tests that still will be executed for Mutant.It wasn't always noticeable because we (ok, at least I am) used Infection for unit tests, which are fast enough.
However, since I'm currently working on a project with 2000+ functional tests and run Infection against this huge functional test suite, this became a bottleneck.
Imagine there is a file
ProductApiTest.php
that covers all the API endpoints with functional tests (Symfony WebTestCase).So, with Infection 0.23.0 (current
master
), instead of running 1 test case for 1s, we run all the tests fromProductApiTest.php
for 1+15=16s.Solution
With this PR, for the example explained above, we run only 1 test case for 1s, saving 15 from 16 seconds.
Technical details
Since PHPUnit doesn't allow us to provide test cases on
phpunit.xml
file level, we can only do it on a command line level, using--filter
optionmaster
:'phpunit' '--configuration' '/path-to/phpunitConfiguration.d3b360aa95bfe11dadbe87dbd49b70c4.infection.xml'
'phpunit' '--configuration' '/path-to/phpunitConfiguration.d3b360aa95bfe11dadbe87dbd49b70c4.infection.xml' '--filter' '/App\\Test::test_case1|App\\Test::test_case2/'
Instead of 1000 words, here is an example of the logs for the escaped mutant:
master
:and this PR:
So it is 192 executed tests (in this branch) instead of 1166 (in
master
).How it impacts unit (fast) test suites
I tried to understand the impact for fast tests, running
master
branch and this PR against Infection itsetlf:From my tests, running Infection against Infection becomes slower with this PR.
--filter
in PHPUnit itself adds some penalty as I understand, because this option is matched usingpreg_match()
inside PHPUnit. But this adds only 5 extra seconds with the "Infection for Infection" example.Keeping that in mind, probably it's better to enable this feature by some option? If so, what the name of this new option you can recommend?
--only-covering-test-cases
--slow-tests
(probably it will be some common option to enable not just this feature)I don't know.
Conclusion
The slower the tests, the more benefit / performance boost this PR will give.
--only-covering-test-cases
option docs site#221