-
Notifications
You must be signed in to change notification settings - Fork 483
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
POC: Speed optimize class-loading for levels tests #2916
Conversation
In CI it seems we save ~2 minutes with this PR in Tests / Levels tests (reduced from previously ~10 minutes). the approach of this PR is faster, because With every autoloadable symbol added via a new test to PHPStan the rule-level test gets a tiny bit slower. |
"classmap": [ | ||
- "tests/e2e", | ||
- "tests/PHPStan" | ||
+ "tests/PHPStan/Rules/Methods/data/returnTypes.php", |
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 understand the intention, but I don't love it. It's going to be really hard to maintain this list and debug problems as time goes on.
I'd be more interested in parallelizing running the tests. Basically each "topic" in LevelsTestCase could have its own GitHub Actions job.
It's not exactly the same but the idea is here: https://rias.be/blog/running-phpunit-tests-in-parallel-using-github-actions
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 have adjusted the PR. It now filters the existing tests based on class-name and builds a dynamic matrix with it.
this means we run now 40 tests in parallel (which each takes ~1m 15s) instead of one large job which runs ~10 minutes
the tests.yml
github action workflow for this PR finished in 4m 15s, while it takes ~10m 30s before this PR 🚀
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 understand the intention, but I don't love it. It's going to be really hard to maintain this list and debug problems as time goes on.
I'd be more interested in parallelizing running the tests. Basically each "topic" in LevelsTestCase could have its own GitHub Actions job.
It's not exactly the same but the idea is here: rias.be/blog/running-phpunit-tests-in-parallel-using-github-actions
This is very similar to what i did in psalm years ago:
vimeo/psalm#4985
But in psalm, each job also runs test in parallel.
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.
@bendavies the tests jobs - even before this PR - are already highly parallelized - within the github action jobs and also across several jobs.
This PR splits up one very slow part of the pre-existing tests into even more jobs because these were particular slow
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.
👍 apologies.
i couldn't see (still can't) where tests are split across jobs.
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.
apologies
no worries.
i couldn't see (still can't) where tests are split across jobs.
see the screen in my above comment. there you can see the "levels test" matrix, with several jobs.
if you compare to other existing PR, this step is just a single job.
the PR implements this by inspecting the test-suite level-tests test-cases. Because of the used dataprovider, this means the testLevels
method represents several test-cases. each of these test-cases is run in a separate github action job, because I feed this list of test-cases as a json array into the "test-levels" github action job matrix.
@@ -0,0 +1,31 @@ | |||
<?php | |||
|
|||
$testList = shell_exec('php vendor/bin/phpunit --list-tests'); |
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.
currently the --list-tests
phpunit feature does not allow filtering by test-groups.
I have opened a feature request about that, so we may can get rid/simplify this script in the future.
This pull request has been marked as ready for review. |
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 really love this! Just found a few minor things :)
|
||
if ( | ||
!str_contains($cleanedLine, 'PHPStan\Generics\GenericsIntegrationTest') && | ||
!str_contains($cleanedLine, 'PHPStan\Levels\LevelsIntegrationTest') |
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.
We should check the @group
inside the docblock instead of hardcoding these two classes.
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.
to make this work based on the phpdoc, I would have needed to reflect on classes and read all class-comments.
instead I updated phpunit to 10.x for the matrix job and used --list-tests-xml
which returns a xml containing group information. its a option which does not exist in phpunit 9.x
@@ -0,0 +1,33 @@ | |||
<?php |
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.
declare strict types please
.github/workflows/tests.yml
Outdated
uses: "shivammathur/setup-php@v2" | ||
with: | ||
coverage: "none" | ||
php-version: "8.1" |
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.
Let's do 8.3 here :)
.github/workflows/tests.yml
Outdated
@@ -121,7 +155,7 @@ jobs: | |||
run: "composer install --no-interaction --no-progress" | |||
|
|||
- name: "Tests" | |||
run: "make tests-levels" | |||
run: "php vendor/bin/phpunit --filter '${{ matrix.test-filter }}' --group levels" |
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 just did some local testing and in this format PHPUnit will still execute all data providers from all tests. It would be good to also add the file in which the filtered test is. I think we can get even faster that way.
See the comparison:
time vendor/bin/phpunit --filter 'PHPStan\\Type\\UnionTypeTest::testSorting'
15.7s
time vendor/bin/phpunit tests/PHPStan/Type/UnionTypeTest.php --filter 'PHPStan\\Type\\UnionTypeTest::testSorting'
1.1s
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.
|
||
$filter = str_replace('\\', '\\\\', $testCaseName); | ||
|
||
$testFilters[] = sprintf("%s --filter '%s'", $fileName, $filter); |
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.
Why is doubling backslashes needed? I’d say that instead of that we need escapeshellarg() for both fileName and filter.
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.
Running the plain command on the CLI only works only with double backslashes (in the filter arg)
See your command in #2916 (comment)
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 guess we can add escapeshellarg(), but we still need this manual escaping
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.
lets give it a try and we will see
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.
https://github.com/phpstan/phpstan-src/actions/runs/7944377066/job/21689826256 shows that it doesn't work when we only use escapeshellarg
(no tests executed).
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.
Why is that? I noticed it doesn't work but I don't know what eats the characters, whether it's bash or PHP. Doubling the backslashes seems very peculiar to me, maybe there are other things we need to fix too but we're currently unaware of that.
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.
Phpunit examples in docs also mention double backslashes
https://docs.phpunit.de/en/9.6/textui.html#textui-examples-filter-patterns
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.
Its interpreted as regex. Let mit try preg_quote()
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 know. It's probably a bash thing.
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.
preg_quote would escape the following characters: . \ + * ? [ ^ ] $ ( ) { } = ! < > | : - #
the only one of these which can show up in a class/method name is \
. I think we can use preg_quote
if you prefer but it should be enough to escape the backslash IMO
edit: I had an idea
Thank you. |
level tests are the slowest part of the CI process and take a considerable amount of time.
they usually take arround something like 10 minutes
locally I have reduced rule level tests to a small subset to easy reproducing the problem.
my reduced set of rule-level tests before this PR run like
after the change in autoload rules it runs like
which is ~37% faster.
lets see wheter this speedup reproduces in CI and the change itself is acceptable
side-note: the PR as is currently yields to 2 notices when running phpunit, which obviously should be fixedfixed by 11ffaa8