Problem/Motivation
Currently we only run phpstan on changed files. This was decided because it saves on costs one DrupalCI (adding the full analysis takes 1,5 minutes).
However failing early also saves costs, and we saw issues in deprecating modules where we would benefit from this.
Also there are some recent improvements in test runs, saving time in test run.
It However it is adviced to always run this on the entire project.
https://phpstan.org/blog/why-you-should-always-analyse-whole-project
Running the full analysis when not on DrupalCi requires some larger resources (or time, can take up to 10-15 minutes). Which makes this inpractical for core committers. Therefore it was agreed that when not on drupalCI we keep only the partial analysis.
Proposed resolution
Adjust script, to always run full analysis on DrupalCI. When not on drupalCi the commit-code script only runs a partial check. (To make sure that locally not on every commit a full phpstan analysis needs to be done).
Remaining tasks
Script adjusted.
Issue fork drupal-3259355
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
mondrakeUnfortunately this in phpstan-partial.neon
also prevents reporting items no longer in the baseline, not just the general ignores.
This means that the baseline will get out-of-sync if issues solve baseline items without also removing those form the baseline, and developers will not be notified.
See #3112290: Replace REQUEST_TIME in procedural code for example.
I think this is another element on the side of always running full check.
Comment #3
mallezieI think i just saw a problem with only scanning a list of files.
From #2316941: Use the builder pattern to make it easier to create render arrays
There is following patch:
https://www.drupal.org/files/issues/2022-01-21/core-builder-pattern-2316...
This gives below result on phpstan.
https://www.drupal.org/pift-ci-job/2300923
If we take this change as an example:
This results in following phpstan error:
The reason the class is unknown is because it's added in this patch. But phpstan only scans the changed files, and does not scan the Image class, so it cannot see that this class exists.
Comment #5
longwave#3 makes sense, I don't see how we can work around this any other way.
Comment #6
mallezieTo add, on the testbot this takes 1'13" (if we start running it now, without the possible cache improvements.)
15:04:43 Running PHPStan on *all* files.
15:05:56
15:05:56 [OK] No errors
15:05:56
15:05:56
15:05:56 PHPStan: passed
Comment #7
longwaveAnyone concerned about the extra time needed on DrupalCI is welcome to review #3212346: Avoid hundreds of unnecessary installs when testing config entities which takes 4 minutes off a testbot run last time I checked.
Comment #8
mallezieWhat might be still open is to see if we only need to run phpstan on gitlab CI. If this would cause locally a large delay when running on a local computer of all people running this script.
Comment #9
daffie CreditAttribution: daffie commentedCan we link to the CR: https://www.drupal.org/node/3258232 and update it for this issue.
Comment #10
mondrakeI looked at the case in #3 but I cannot find anywhere the class that PHPStan considers missing - not in the patch, at least, so I'd tend to say that PHPStan is right...
PHPStan will work out of a fully built codebase, with the patch included, so even if changed files reference classes that are not part of the patch, still PHPStan would detect them AFAICS.
Comment #11
daffie CreditAttribution: daffie commentedAll code changes look good to me.
PHPStan does now a full code scan on all patches.
I leave it up to the committer to decide if the extra testbot run costs are acceptable.
Lets wait with updating the CR until after commit.
For me it is RTBC.
Comment #12
mallezie@mondrake when rechecking my initial analysis was indeed incorrect, although the problem still stands.
The patch does the following:
Add a reference to Image class and use a (new) method getBuilder of the Image class. But indeed PHPstan (with the limited files) is correct that the image Class does not have this method.
However this method is added further in the patch.
To the RenderElement class. But since Image is not part of the items PHPStan scans, phpstan does not know that actualle Image extends RenderElement and thus that the function can be found in the RenderElement class.
It's that combination of issues what causes this IMO.
Comment #13
mondrake#13 but
Drupal\Core\Render\Element\Image
is neither in the patch nor in the existing code, no? So how can youuse Drupal\Core\Render\Element\Image;
In other words, what happens to that patch if you remove the PHPStan checks? Wouldn't it fail in PHPUnit anyway?
Maybe I'm missing something here, sorry
Comment #14
mallezie@mondrake Ow damn! you're right. PHPStan is correct here. Those two classes (Image and TextElement) are not existing.
Sorry for all the noise here. (At least we got a patch ready if we would decide to do a full scan). But the #3 argument off course is invalid in doing so.
Comment #15
mglamanI found another reason why we need to run a full scan on all changes. I discovered #3260707: QuickEditJavascriptTestBase must be declared abstract this fix for a test base that is not abstract. Removing the class would cause the baseline to be out of date and PHPStan error (removing a baseline rule)
Comment #16
mallezieWouldn't we solve the above by removing the phpstan partial file?
Mostly isn't this the guilty line here
reportUnmatchedIgnoredErrors: false
(I still agree on the full run, but above might be a good interim step?)
Comment #17
mondrake#16 unfortunately it won’t work because in that case if any of the ignored errors (the general ones, not those in the baseline) does not occur in the changed files, it will be reported thus failing the scan.
Comment #18
mallezieForgot about that. Thx for explaining.
An option (although i think it's perhaps far fetched and not optimal) might be to do some baseline file scanning in the commit code script and see there if a changed file has an entry in the baseline file and flag it.
Say i changed file A, and file A is in the baseline, mention / flag it in the commit code script.
Although that would not be perfect. Say i have line 1 of file A in the baseline (line nrs are not in the baseline) and my patch touches lines 5-10 of that file, then i would need to fix the line 1 error in that file also while actually being not in scope for that change.
Comment #19
mglamanThat would be interesting! If the filename is found in the baseline via grep, run phpstan. I think that'd be a great compromise.
Comment #20
mondrake#19 that's only going to work for baseline ignores though, not for the ones in
phpstan.neon.dist
, no?Comment #21
catchRunning a full phpstan takes several minutes on my local machine via core/scripts/commit-code-check.sh, this is on a lenovo x1 carbon running ubuntu. Each thread growing up to 1gb+ memory usage. I have 16gb of ram and even starting with 12gb of ram free, it will still get down to swap by the end unless I hack phpstan.dist to reduce the maximum threads. Running phpstan with --debug -vvv it shows no particular file taking loads of memory, maybe one or two at 20mb each, but just a cumulative increase in memory usage as more files are parsed. Found a couple of upstream bugs that look similar (https://github.com/phpstan/phpstan/issues/3702 and https://github.com/phpstan/phpstan/issues/4072) - nothing very conclusive on those, and I didn't look into it further yet.
We run commit-code-check on every local core commit, so either caching or fixing the memory leak is necessary before adding this to core, otherwise it's going to make committing anything else at all quite tricky without workarounds. Maybe we could just enable the cache for the code check even though it'll be thrown away on DrupalCI, so that core committers can rely on it.
Comment #22
mallezieDid some tests on locally running a full phpstan, and i do confirm this can take some time.
(My laptop is even slower then what catch describes here).
A cached one is luckily enough much faster. And in most cases this only takes changed files into account.
There are also a lot of possibilities when PHPStan invalidates the cache, so it runs a full scan again.
https://phpstan.org/user-guide/result-cache
Note: also when running with --debug the cache is not used.
So committers will sometimes have a full non-cached phpstan run. Question is, if this is acceptable for core committers, or do we need to move the phpstan analysis out of the core-commit script, and only run on CI?
By default the cache is stored at %tmpDir%/resultCache.php (/tmp/phpstan on my machine) so that needs to be writable i think.
Comment #23
catchOn my machine, I ended up with a PHP fatal error at least twice, and eventually ended up forcing it to use less threads via hacking phpstan.neon.dist in order to be able to continue. It's not acceptable to make it impossible to commit a patch without hacking core...
I thought about a core patch to force the lower number of threads in phpstan.neon.dist for everyone, but... if you have 64g of ram instead of 16g on your laptop, you can probably afford more threads and get a quicker result, so that's not necessarily great either.
The advantage of running things in the commit hooks (as we do for cspell etc.) is that we're less likely to introduce regressions when multiple patches are committed between DrupalCI test runs. However we obviously don't do that for full test runs, so it might not be the end of the world - but then I think a partial phpstan run would be better than nothing for the commit hooks.
Comment #24
mallezieRebased the branch, and adjusted to run a full scan on CI, and a partial one otherwise.
Comment #25
mondrakeI think this is the best compromise possible ATM, thanks!
Comment #26
catch#3261539: Don't scan gzips.
Comment #27
mondrakeCritical, because as per #3261539-13: Don't scan gzips we are now not able to limit scanning to specific file extensions.
Comment #28
longwave+1, because without this I think modifying LinkItemTest in a patch is not possible now, because it will cause PHPStan and in turn the script to crash.
Comment #29
mglamanAfter reading #3261539-10: Don't scan gzips I see why this matters so much 😬, beyond stale concerns. So gzips were skipped but forced to be scanned due to how we passed files into PHPStan, bypassing exclusion?
Comment #30
mondrake#29 that's my reading, yes
Comment #31
longwaveI think we have a case over in #3251754: Use sniff DrupalPractice.CodeAnalysis.VariableAnalysis on */tests/* that requires this change. PHPStan failed with:
As far as I can see this is because the test in question does
but PHPStan hasn't scanned that fixture (as it didn't change) and doesn't know that the class is located there.
Comment #32
mallezieNeed to test this, but at first sight @longwave that analysis looks correct.
However this will also fail the commit code script (when not run on ci) when a committer want's to commit that patch. In this case the only option for a committer would be to know to commit although the script fails, or run a full scan locally (which would require a committer to do manually, or hack the script). Not sure if this is really a problem.
Comment #33
mallezieNot related to phpstan running partial or full. (both give same result).
In our phpstan config we have
excludePaths:
# Skip test fixtures.
- */tests/fixtures/*.php
This skips the AssertHelperTestClass class.
Comment #34
longwaveOh, that is interesting, thanks for debugging that. I guess we need to be more specific in our exclude path settings?
Comment #35
catchAnother odd one #3262937: PHPStan fails when @legacy tests call deprecated code.
Comment #36
catch#3262937: PHPStan fails when @legacy tests call deprecated code.
Comment #37
mondrakeComment #38
mglamanI know one of the concerns was performance. phpstan-drupal has a performance improvement in dev-main waiting to be released: https://github.com/mglaman/phpstan-drupal/issues/318. It's not much but should improve reflection usage when the BrowserTestBaseDefaultThemeRule is checked (namespace preflight checks before checking ancestors to see if the class extends BrowserTestBase.) It's not tagged. But it's available if someone would like to try it locally.
Comment #39
mondrakeSee #3264061-8: Remove deprecated functions from image module
Comment #40
mondrakeComment #41
alexpottThis is no longer critical.
Comment #42
xjmCan we refactor the commit checks so that the full static analysis doesn't run on every commit, but the rest still do?
See:
https://github.com/alexpott/d8githooks/commit/abfe885830597f9a93be862658...
Comment #43
mallezie@xjm not fully sure what you mean.
Phpstan here does a partial scan when not on DrupalCI (like it is now) and only does a full scan on DrupalCI.
Or would you like the partial phpstan scan also removed when not on DrupalCI.
Comment #44
xjm@mallezie, if so, then the issue title and issue summary are out of date. :-) Thanks!
Edit: We also need those change record updates.
Comment #45
mallezieComment #46
mallezieTried to put issue summary back up to date.
For the change record https://www.drupal.org/node/3258232
Nothing needs to change here, this is still correct. This issue only adjust the core-commit-code script.
Comment #47
mallezieAdded some extra info the the previous change record.
Comment #48
mallezieRebased. This will probably fail now until #3278782: PHPStan baseline is out of sync is committed.
Comment #49
mallezieComment #50
mallezieStill hoping to get this in, since it would fix some phpstan-baseline chasing issues.
There seems to be some performance improvements in the latest mglaman/phpstan-drupal version. So if we can update that one prior we can see a full scan takes much less time.
There do seem to be some quite good performance improvement in this upgrade.
After the update a full scan takes 1:15
While in #3278916: Update phpstan/phpstan to latest version a full scan takes 2:27
So if we can get #3279840: Update mglaman/phpstan-drupal (and perhaps also #3278916: Update phpstan/phpstan to latest version) this might be acceptable as for the longer DrupalCI runtime (and thus costs).
Comment #51
mallezieNow that the updates to phpstan and phpstan-drupal are in. I've rebased this, to let the bot run again.
From my side this is ready now. Giving in mind the performance improvements of phpstan-drupal, this might now be acceptable.
Comment #52
mallezieComment #53
alexpottI think this change should leave in place the fullscan logic if phpstan files have changed. It makes the commit-code-check.sh logic better for committers like me. It makes this change smaller... basically do this instead:
Comment #54
mondrakeNW for #53
Comment #55
mallezieThis was in place to then do a full scan on changes in both core/phpstan-baseline.neon and core/phpstan.neon.dist.
For the phpstan.neon.dist file, the configuration i think this makes sense. Do you also wan't this for 'each' baseline change? Since we committed a lot of items to the baseline (to stop introducing new ones), this might make it run locally more often than you would like?
If that's no issue, we might also do this for changes to the composer.lock files, and add that file to the list as well?
Comment #56
alexpott@mallezie I would leave the logic for detecting whether to do a full run alone - it's fine as is. Most MRs do not change the baseline or the phpstan config.
Comment #57
mallezieAdjusted as requested.
Comment #58
mondrakeThis will allow us to run full PHPStan analysis on all DrupalCI runs, while leaving it partial locally. Will save a lot of time in chasing baseline out-of-syncs, that happened already a few times since PHPStan was introduced. RTBC.
Comment #59
alexpottI think we need to give @Mixologic the chance to chime in here. Maybe there is something we can set the the cache dir to that would allow Drupal CI to benefit from it.
I'm cautious about committing this because this means we'll be doing a full phpstan analysis on every test run even when non php code is changing. With a cache this could be near instantaneous but there are quite a few questions about how to generate that cache. Maybe this is something that'll be easier to do once with have everything running on gitlab and we can configure a daily job to generate the phpstan cache for each development branch that has phpstan.
Comment #60
mallezieI can see the cautiousness.
As an intermediate step we might do #3259353: Run full phpstan on composer change first. When checking the issues where baseline problems were introduced. I saw following reasons.
* Removal of modules (which can be caught in the composer.lock change).
* Updates of phpstan and drupal-phpstan (which would also be caught)
* Some cases where issues ran against 9.x ci, but not to 10.x, that's something sitll to look out for.
Another option would be (but not sure how feasible) is to check if we can distinguish between a patch run on CI, and a nightly run, then we could do a full scan on the scheduled run. That might also help finding baseline issues earlier.
Comment #61
MixologicDrupalci testbots are ephemeral so they cannot benefit from any caches. Im not sure whether or not gitlabci is going to preserve caches since we will have a similar setup running there, with testrunners being created and destroyed on demand. Gitlab may keep caches on the main server (which, if it does means disk space we may need to plan for).
It's not ideal to have this add 2.5 minutes to every test run, mostly for the fact that we cant have this job run alongside other jobs in the pipeline, which we *will* get on gitlabci.
From my perspective, we can make this change now, and just take the cost hit until gitlabci, and then find ways to optimize later. Developer time $$ > testbot cost $$.
Comment #62
alexpottFunnily enough I think once the baseline is empty we could go back to running partial on CI because the only thing that has got out of date or been problematic has been the baseline. It definitely bugs me that we will run a scan when only js or css or txt or yml files are changing - partial scans are great for dealing with that case because they are so quick when all the files are excluded.
FWIW I think the "Developer time $$ > testbot cost $$" is not actually that clear here. By running PHPStan on every test run we make it something that every developer has to wait and care about when it fails.
That said @Mixologic has +1'd so let's go ahead because I don't have the will to discuss this anymore as that is wasting time too and I'm not sure how much it actually matters. FWIW all the linked issues that partial scans caused that were "critical" were actually solved or not down to partial scans.
Committed 6a27d76 and pushed to 10.0.x.
Comment #64
neclimdulMaybe. I'm sure that's the case though because one of the reasons for a full run is to catch how change affects unmodified files. Like removing an interface might not break the changed file but have drastic impacts on untouched files.
I agree it complicated. Its surely nice skipping things when no php is modified but also super frustrating and a waste of other developers when pipelines break or chasing broken code testbot could have caught.
That said you mentioned commits that only touch js/css.Maybe we can do something with that in gitlab using the rules logic. Also, maybe the commit hook could do something similar as well looking for composer files, modules files, and php files and only running certain tests if those are modified?