Repository navigation
Move operator type inference to Type methods #34658
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # https://help.github.com/en/categories/automating-your-workflow-with-github-actions | |
| name: "E2E Tests" | |
| on: | |
| pull_request: | |
| paths-ignore: | |
| - 'compiler/**' | |
| - 'apigen/**' | |
| - 'changelog-generator/**' | |
| - 'issue-bot/**' | |
| push: | |
| branches: | |
| - "2.3.x" | |
| paths-ignore: | |
| - 'compiler/**' | |
| - 'apigen/**' | |
| - 'changelog-generator/**' | |
| - 'issue-bot/**' | |
| concurrency: | |
| group: e2e-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| result-cache-e2e-tests: | |
| name: "Result cache E2E tests" | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - script: | | |
| cd e2e/result-cache-1 | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| patch -b src/Bar.php < patch-1.patch | |
| cat baseline-1.neon > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| mv src/Bar.php.orig src/Bar.php | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| - script: | | |
| cd e2e/result-cache-2 | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| patch -b src/Bar.php < patch-1.patch | |
| cat baseline-1.neon > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| mv src/Bar.php.orig src/Bar.php | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| - script: | | |
| cd e2e/result-cache-3 | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| patch -b src/Baz.php < patch-1.patch | |
| cat baseline-1.neon > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| mv src/Baz.php.orig src/Baz.php | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| - script: | | |
| cd e2e/result-cache-4 | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| patch -b src/Bar.php < patch-1.patch | |
| cat baseline-1.neon > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| mv src/Bar.php.orig src/Bar.php | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| - script: | | |
| cd e2e/result-cache-5 | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| patch -b src/Baz.php < patch-1.patch | |
| cat baseline-1.neon > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| mv src/Baz.php.orig src/Baz.php | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| - script: | | |
| cd e2e/result-cache-6 | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| patch -b src/Baz.php < patch-1.patch | |
| cat baseline-1.neon > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| mv src/Baz.php.orig src/Baz.php | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| - script: | | |
| cd e2e/result-cache-7 | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| patch -b src/Bar.php < patch-1.patch | |
| cat baseline-1.neon > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| mv src/Bar.php.orig src/Bar.php | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| - script: | | |
| cd e2e/result-cache-9 | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| patch -b src/Foo.php < patch-1.patch | |
| cat baseline-1.neon > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| mv src/Foo.php.orig src/Foo.php | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| - script: | | |
| cd e2e/result-cache-10 | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| patch -b src/Foo.php < patch-1.patch | |
| cat baseline-1.neon > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| mv src/Foo.php.orig src/Foo.php | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| - script: | | |
| cd e2e/result-cache-atomic-save | |
| ../../bin/phpstan | |
| INODE=$(ls -i tmp/resultCache.php | awk '{print $1}') | |
| OUTPUT=$(../../bin/phpstan -vvv 2>&1) | |
| echo "$OUTPUT" | |
| UNCHANGED=$([ "$INODE" = "$(ls -i tmp/resultCache.php | awk '{print $1}')" ] && echo unchanged || echo replaced) | |
| ../bashunit assert equals 'unchanged' "$UNCHANGED" | |
| ../bashunit assert contains 'Result cache was not rewritten because it is unchanged.' "$OUTPUT" | |
| echo >> src/Foo.php | |
| ../../bin/phpstan | |
| # The cache is written next to its final path and renamed into place, so saving it | |
| # after a change replaces the file rather than truncating and rewriting the one the | |
| # next run reads. That is what keeps a run killed mid-save from leaving a partial | |
| # cache behind. | |
| REPLACED=$([ "$INODE" != "$(ls -i tmp/resultCache.php | awk '{print $1}')" ] && echo replaced || echo 'written in place') | |
| ../bashunit assert equals 'replaced' "$REPLACED" | |
| # A completed save leaves nothing next to the cache file. | |
| LEFTOVERS=$(ls tmp/ | grep -c '\.tmp$' || true) | |
| ../bashunit assert equals '0' "$LEFTOVERS" | |
| OUTPUT=$(../../bin/phpstan -vvv 2>&1) | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 0 files will be reanalysed.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-11 | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| patch -b src/Coll.php < patch-1.patch | |
| cat baseline-1.neon > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| mv src/Coll.php.orig src/Coll.php | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| - script: | | |
| cd e2e/result-cache-moved-tmpdir | |
| ../../bin/phpstan | |
| # Where the cache is stored cannot change what it holds, so moving it has to keep it | |
| # usable: same project, same analysis, cache read from a different tmpDir. | |
| mkdir -p tmp-b | |
| cp tmp-a/resultCache.php tmp-b/resultCache.php | |
| OUTPUT=$(../../bin/phpstan analyse -c moved.neon -vv 2>&1) | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 0 files will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert not_contains 'metadata do not match' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-truncated | |
| ../../bin/phpstan -vvv | |
| # Cut inside the first section's payload: the cache is damaged, not merely stale, so | |
| # it has to be discarded rather than read as far as it goes. | |
| php truncate.php | |
| OUTPUT=$(../../bin/phpstan -vvv 2>&1) | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'an error occurred while loading the cache file' "$OUTPUT" | |
| ../bashunit assert contains 'Result cache is saved.' "$OUTPUT" | |
| # And once more inside a section that is read lazily, which is a different code path. | |
| php truncate-lazy.php | |
| OUTPUT=$(../../bin/phpstan -vvv 2>&1) | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'is truncated at entry' "$OUTPUT" | |
| OUTPUT=$(../../bin/phpstan -vvv 2>&1) | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-stub-files-errors | |
| # The stub file is validated on the full analysis, and its error is kept in the result cache. | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'test.stub:8:Method StubFilesResultCache\Foo::doFoo() has parameter $a with no type specified.' "$OUTPUT" | |
| ../bashunit assert not_contains 'return statement is missing' "$OUTPUT" | |
| # A new file makes the files with errors re-analysed. The stub file is not one of them, | |
| # otherwise the rules for analysed files would report that doFoo() does not return. | |
| echo '<?php function doBaz(): void {}' > src/Baz.php | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored.' "$OUTPUT" | |
| ../bashunit assert contains 'test.stub:8:Method StubFilesResultCache\Foo::doFoo() has parameter $a with no type specified.' "$OUTPUT" | |
| ../bashunit assert not_contains 'return statement is missing' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-worker-count | |
| OUTPUT=$(../../bin/phpstan -vvv 2>&1) | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'largest of 2' "$OUTPUT" | |
| # Only some files are analysed again, so the result is rebuilt from the cache | |
| # and must still report the peak memory of the workers. | |
| echo '// changed' >> src/Foo.php | |
| echo '// changed' >> src/Bar.php | |
| OUTPUT=$(../../bin/phpstan -vvv 2>&1) | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 2 files will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'largest of 2' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-stale-objects | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan -vvv") | |
| echo "$OUTPUT" | |
| # Renaming a property inside the cache file is what a cache written by a PHPStan whose | |
| # classes have changed since looks like: the payload does not carry the property the class | |
| # now declares, so reconstructing the object leaves it uninitialized and reading it throws. | |
| php rename-property.php | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan -vvv") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'could not be read back' "$OUTPUT" | |
| ../bashunit assert contains 'Result cache is saved.' "$OUTPUT" | |
| # The cache written in its place is usable again. | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan -vvv") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-collected-data-rules | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Caller.php:10:Call to function ResultCacheCollectedDataRules\pure() on a separate line has no effect.' "$OUTPUT" | |
| # Nothing changed, so every rule on CollectedDataNode reuses its errors from the result cache. | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Caller.php:10:Call to function ResultCacheCollectedDataRules\pure() on a separate line has no effect.' "$OUTPUT" | |
| echo "$OUTPUT" | grep -E 'Result cache reused the errors of ([0-9]+) of \1 rules on collected data\.' | |
| ../bashunit assert contains 'Result cache was not rewritten because it is unchanged.' "$OUTPUT" | |
| # With -vvv the diagnose extensions print what the rules filled while they ran, so every rule runs. | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vvv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Caller.php:10:Call to function ResultCacheCollectedDataRules\pure() on a separate line has no effect.' "$OUTPUT" | |
| ../bashunit assert not_contains 'Result cache reused the errors' "$OUTPUT" | |
| # src/Other.php is analysed again, but what it collects stays the same. | |
| echo '// changed' >> src/Other.php | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'Caller.php:10:Call to function ResultCacheCollectedDataRules\pure() on a separate line has no effect.' "$OUTPUT" | |
| echo "$OUTPUT" | grep -E 'Result cache reused the errors of ([0-9]+) of \1 rules on collected data\.' | |
| # Removing the call changes what src/Caller.php collects, so the rule runs again and the error goes away. | |
| sed -i 's/^\t\tpure();$//' src/Caller.php | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'on a separate line has no effect' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-collected-data-rules-tracking | |
| composer install | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Controller.php:15:View about not found.' "$OUTPUT" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| echo "$OUTPUT" | grep -E 'Result cache reused the errors of ([0-9]+) of \1 rules on collected data\.' | |
| # The rule tracks the views directory, so a new view runs it again. | |
| printf 'about\n' > views/about.txt | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'View about not found.' "$OUTPUT" | |
| rm views/about.txt | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Controller.php:15:View about not found.' "$OUTPUT" | |
| # The rule tracks the file of ignored views too. | |
| printf 'about\n' > ignored-views.txt | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'View about not found.' "$OUTPUT" | |
| # And the class of extra views, which is not analysed. | |
| sed -i "s/'contact'/'nothing'/" lib/ExtraViews.php | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Controller.php:20:View contact not found.' "$OUTPUT" | |
| # And a method that a class reflection extension describes from a file that is not | |
| # analysed, tracked through DeclarationDependencyTracker. No analysed file changes. | |
| printf '' > more-views.txt | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Controller.php:25:View faq not found.' "$OUTPUT" | |
| - script: | | |
| cd e2e/bug-14514 | |
| composer install | |
| ../../bin/phpstan analyze bug-14515.php | |
| # Fails until https://github.com/phpstan/phpstan/issues/14988 is fixed again: | |
| # - script: | | |
| # cd e2e/bug-14988 | |
| # composer install | |
| # ../../bin/phpstan analyse | |
| - script: | | |
| cd e2e/bug-12972b | |
| composer install | |
| ../../bin/phpstan analyze | |
| - script: | | |
| cd e2e/debug-class-loader | |
| composer install | |
| ../../bin/phpstan analyze | |
| # With --debug, the analysis runs in the main process, and PHPStan prints the | |
| # exception that DebugClassLoader threw for the unknown class. | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyze --debug") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'by a bootstrap autoloader for class DebugClassLoaderE2e\Widget: The autoloader expected class' "$OUTPUT" | |
| - script: | | |
| cd e2e/bug-12972c | |
| composer install | |
| ../../bin/phpstan analyze | |
| - script: | | |
| cd e2e/class-alias-loader | |
| composer install | |
| ../../bin/phpstan analyze | |
| - script: | | |
| cd e2e/robot-loader | |
| composer install | |
| ../../bin/phpstan analyze | |
| - script: | | |
| cd e2e/bug-15102b | |
| ../../bin/phpstan analyze | |
| - script: | | |
| cd e2e/bug-15231 | |
| ../../bin/phpstan analyze | |
| - script: | | |
| cd e2e/bug-15475 | |
| # https://github.com/phpstan/phpstan/issues/15475 | |
| # A run on other paths builds the name scopes of the scanned file from its | |
| # simplified AST, without the anonymous class declared in a function body. | |
| # A later full run must not reuse them, even with the result cache cleared. | |
| ../../bin/phpstan analyse src/usage.php | |
| ../../bin/phpstan clear-result-cache | |
| ../../bin/phpstan analyse | |
| - script: | | |
| cd e2e/bug-15482b | |
| ../../bin/phpstan analyse | |
| - script: | | |
| cd e2e/bug-15102 | |
| composer install | |
| ../../bin/phpstan analyze | |
| - script: | | |
| cd e2e/bug-15102c | |
| composer install | |
| ../../bin/phpstan analyze | |
| - script: | | |
| cd e2e/bug-15184 | |
| composer install | |
| # The crash needs OPcache: forked workers switch it off, the --debug main process and | |
| # spawned workers (pcntl_fork disabled, as on Windows) keep it on. | |
| php -r 'if (!extension_loaded("Zend OPcache")) { fwrite(STDERR, "OPcache is not loaded, so this step cannot reproduce the crash.\n"); exit(1); }' | |
| ../../bin/phpstan analyze --debug | |
| php -d disable_functions=pcntl_fork ../../bin/phpstan analyze | |
| - script: | | |
| cd e2e/bootstrap-autoloader-wrong-file | |
| # The crash needs OPcache: forked workers switch it off, the --debug main process and | |
| # spawned workers (pcntl_fork disabled, as on Windows) keep it on. | |
| php -r 'if (!extension_loaded("Zend OPcache")) { fwrite(STDERR, "OPcache is not loaded, so this step cannot reproduce the crash.\n"); exit(1); }' | |
| ../../bin/phpstan analyze --debug | |
| php -d disable_functions=pcntl_fork ../../bin/phpstan analyze | |
| - script: | | |
| cd e2e/bug-14724 | |
| composer install | |
| ../../bin/phpstan analyze app/ | |
| - script: | | |
| cd e2e/bug10449 | |
| ../../bin/phpstan analyze | |
| git apply patch.diff | |
| rm phpstan-baseline.neon | |
| mv after-phpstan-baseline.neon phpstan-baseline.neon | |
| ../../bin/phpstan analyze -vvv | |
| - script: | | |
| cd e2e/bug10449b | |
| ../../bin/phpstan analyze | |
| git apply patch.diff | |
| rm phpstan-baseline.neon | |
| mv after-phpstan-baseline.neon phpstan-baseline.neon | |
| ../../bin/phpstan analyze -vvv | |
| - script: | | |
| cd e2e/bug-9622 | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| patch -b src/Foo.php < patch-1.patch | |
| cat baseline-1.neon > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| mv src/Foo.php.orig src/Foo.php | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| - script: | | |
| cd e2e/bug-9622-trait | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| patch -b src/Foo.php < patch-1.patch | |
| cat baseline-1.neon > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| mv src/Foo.php.orig src/Foo.php | |
| echo -n > phpstan-baseline.neon | |
| ../../bin/phpstan -vvv | |
| - script: | | |
| cd e2e/env-parameter | |
| export PHPSTAN_RESULT_CACHE_PATH=/some/path | |
| ACTUAL=$(../../bin/phpstan dump-parameters -c phpstan.neon --json -l 9 | jq --raw-output '.resultCachePath') | |
| [[ "$ACTUAL" == "/some/path" ]]; | |
| - script: | | |
| cd e2e/statement-parameter | |
| PARAMETERS=$(../../bin/phpstan dump-parameters -c phpstan.neon --json -l 9) | |
| ../bashunit assert equals 'PHPSTAN' "$(echo "$PARAMETERS" | jq --raw-output '.statementParameter')" | |
| ../bashunit assert equals 'PHPSTAN/suffix' "$(echo "$PARAMETERS" | jq --raw-output '.concatenatedStatementParameter')" | |
| - script: | | |
| cd e2e/env-int-key | |
| env 1=1 ../../bin/phpstan analyse test.php | |
| - script: | | |
| cd e2e/result-cache-scanned | |
| ../../bin/phpstan | |
| # A file that is scanned but not analysed (excluded from the analysis, but living in an | |
| # analysed directory) is tracked as a file dependency: editing it re-analyses just the | |
| # files depending on it instead of invalidating the whole result cache. | |
| patch -b src/Generated/Foo.php < patch.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Scanned files with changed symbols (1); re-analysing the files affected by them.' "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'Instantiated class ResultCacheE2EGenerated\Foo not found.' "$OUTPUT" | |
| - script: | | |
| cd e2e/bug-15125 | |
| composer install | |
| # scanFiles points at vendor/autoload.php through a parent-directory segment in the | |
| # middle of an absolute path (%rootDir%/../../../vendor/autoload.php in the issue), and | |
| # Composer records every install_path in vendor/composer/installed.php the same way. | |
| # Both paths go through the cache normalized while the freshly computed ones keep their | |
| # "..", so a second run without any change must still restore the whole cache without | |
| # reporting a metadata mismatch. | |
| ../../bin/phpstan analyse | |
| OUTPUT=$(../../bin/phpstan analyse -vvv 2>&1) | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'Result cache not used because the metadata do not match' "$OUTPUT" | |
| ../bashunit assert not_contains 'Composer metadata changed but no package versions changed' "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 0 files will be reanalysed.' "$OUTPUT" | |
| # --autoload-file is recorded in executedFilesHashes under the path it was given with; | |
| # a './' segment must not keep the cache from being reused either. | |
| ../../bin/phpstan analyse -a ./vendor/autoload.php | |
| OUTPUT=$(../../bin/phpstan analyse -a ./vendor/autoload.php -vvv 2>&1) | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'Result cache not used because the metadata do not match' "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 0 files will be reanalysed.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-scanned-2 | |
| ../../bin/phpstan analyse | |
| # Editing a file from scanDirectories re-analyses only the files depending on it - | |
| # src/Unrelated.php and src/UsesExtra.php are left alone. | |
| patch -b scanned/Dep.php < patch.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:10:Method ResultCacheE2EScanned2\Foo::doFoo() should return int but returns string.' "$OUTPUT" | |
| # Undoing the edit makes the error go away again. | |
| mv scanned/Dep.php.orig scanned/Dep.php | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| # The same for a file listed in scanFiles. | |
| patch -b scan-file/Extra.php < patch-2.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'UsesExtra.php:10:Method ResultCacheE2EScanned2\UsesExtra::doUsesExtra() should return int but returns string.' "$OUTPUT" | |
| # Deleting a scanned file re-analyses the files depending on it. | |
| rm scanned/Dep.php | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:10:Call to method doDep() on an unknown class ResultCacheE2EScanned2\Dep.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-scanned-new-file | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'UsesNew.php:10:Instantiated class ResultCacheE2EScannedNewFile\NewlyScanned not found.' "$OUTPUT" | |
| # A file that newly appears in a scanDirectories directory may define a symbol that was | |
| # reported as unknown somewhere, so the files with errors are re-analysed too - the same | |
| # way a new analysed file makes them re-analysed. src/Foo.php is still left alone. | |
| mv new-file/NewlyScanned.php scanned/NewlyScanned.php | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Scanned files with changed symbols (1); re-analysing the files affected by them.' "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert not_contains 'Instantiated class ResultCacheE2EScannedNewFile\NewlyScanned not found.' "$OUTPUT" | |
| ../bashunit assert contains 'UsesNew.php:10:Method ResultCacheE2EScannedNewFile\UsesNew::doUsesNew() should return int but returns string.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-scanned-exported-nodes | |
| # src/Bar.php refers to a class that does not exist yet, so it has an error from the start. | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Bar.php:10:Instantiated class ResultCacheE2EScannedExportedNodes\NewDep not found.' "$OUTPUT" | |
| # An edit inside a method body of a scanned file changes nothing the files depending on it | |
| # can see, so nothing is re-analysed - not the file depending on it, and not the file | |
| # with the error either. The same as for an analysed file, where only a change in the | |
| # exported nodes (a signature, a PHPDoc, a visibility) reaches the dependents. | |
| patch -b scanned/Dep.php < body.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 0 files will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert not_contains 'Scanned files with changed symbols' "$OUTPUT" | |
| ../bashunit assert contains 'Result cache is saved.' "$OUTPUT" | |
| # The new contents are recorded: the next run has nothing to notice. | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'Scanned files with changed symbols' "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 0 files will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'Result cache was not rewritten because it is unchanged.' "$OUTPUT" | |
| # A changed return type is an exported node change: the file depending on it is | |
| # re-analysed. A changed signature cannot make a new symbol available, so the file with | |
| # the error is left alone. | |
| mv scanned/Dep.php.orig scanned/Dep.php | |
| patch -b scanned/Dep.php < signature.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:10:Method ResultCacheE2EScannedExportedNodes\Foo::doFoo() should return int but returns string.' "$OUTPUT" | |
| ../bashunit assert contains 'Bar.php:10:Instantiated class ResultCacheE2EScannedExportedNodes\NewDep not found.' "$OUTPUT" | |
| # A file without any exported nodes appearing in the scanned directory cannot define the | |
| # missing symbol: nothing is re-analysed. | |
| mv new-file/NoSymbols.php scanned/NoSymbols.php | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 0 files will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'Bar.php:10:Instantiated class ResultCacheE2EScannedExportedNodes\NewDep not found.' "$OUTPUT" | |
| # A file declaring a class may define a symbol reported as unknown, so the files with | |
| # errors are re-analysed - both of them - and the error about the class is gone. | |
| mv new-file/NewDep.php scanned/NewDep.php | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 2 files will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert not_contains 'Bar.php:10:Instantiated class ResultCacheE2EScannedExportedNodes\NewDep not found.' "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:10:Method ResultCacheE2EScannedExportedNodes\Foo::doFoo() should return int but returns string.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-inner-function | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Outer.php:8:Inner named functions are not supported by PHPStan.' "$OUTPUT" | |
| ../bashunit assert contains 'Other.php:10:Method ResultCacheE2EInnerFunction\Other::doOther() should return int but returns string.' "$OUTPUT" | |
| # A function declared inside another function is not an exported node, so an edit of the | |
| # file declaring it looks like no symbol appeared or disappeared: only the file itself is | |
| # re-analysed, not every file with errors. | |
| patch -b src/Outer.php < body.patch | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals 'true' "$(echo "$INFO" | jq --raw-output '.resultCacheUsed')" | |
| ../bashunit assert equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| - script: | | |
| cd e2e/result-cache-define-in-function | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Other.php:10:Method ResultCacheE2EDefineInFunction\Other::doOther() should return int but returns string.' "$OUTPUT" | |
| # A define() inside a function or method body is not an exported node, so an edit of the | |
| # file calling it looks like no symbol appeared or disappeared: only the file itself is | |
| # re-analysed, not every file with errors. | |
| patch -b src/functions.php < body.patch | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals 'true' "$(echo "$INFO" | jq --raw-output '.resultCacheUsed')" | |
| ../bashunit assert equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| - script: | | |
| cd e2e/result-cache-function-shares-method-name | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Caller.php:10:Parameter #1 $string of function strlen expects string, int given.' "$OUTPUT" | |
| # with-method.php has a method named like the function and is scanned after functions.php, | |
| # so the cached reflection of the function must still be tied to functions.php | |
| patch -b src/functions.php < changeReturnType.patch | |
| ../../bin/phpstan analyse -vv --error-format raw | |
| - script: | | |
| cd e2e/result-cache-value-dependency | |
| composer install | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'UsesCache.php:7:Service cache does not exist.' "$OUTPUT" | |
| ../bashunit assert contains "UsesLocale.php:10:Function ResultCacheE2EValueDependency\usesLocale() should return 'none' but returns 'cs'." "$OUTPUT" | |
| ../bashunit assert contains "UsesLocaleToo.php:10:Function ResultCacheE2EValueDependency\usesLocaleToo() should return 'none' but returns 'cs'." "$OUTPUT" | |
| ../bashunit assert contains 'Holder.php:17:Method ResultCacheE2EValueDependency\Holder::isDebug() should return int but returns string.' "$OUTPUT" | |
| ../bashunit assert contains 'UsesHolder.php:9:Function ResultCacheE2EValueDependency\usesHolder() should return int but returns string.' "$OUTPUT" | |
| ../bashunit assert contains 'UsesMayThrow.php:9:Dead catch - RuntimeException is never thrown in the try block.' "$OUTPUT" | |
| ../bashunit assert contains 'UsesRegion.php:7:Region eu is used.' "$OUTPUT" | |
| ../bashunit assert contains 'UsesView.php:7:View dashboard does not exist.' "$OUTPUT" | |
| ../bashunit assert contains 'UsesMake.php:7:Call to an undefined method object::run().' "$OUTPUT" | |
| ../bashunit assert not_contains 'UsesMailer.php' "$OUTPUT" | |
| # The extensions read container.json, which the result cache knows nothing about. A rule and a | |
| # dynamic return type extension both declare the service they ask about - it is recorded once. | |
| # A change of its value re-analyses only the file asking about it. | |
| php -r '$c = json_decode(file_get_contents("container.json"), true); $c["services"]["mailer"] = "ResultCacheE2EValueDependency\\Logger"; file_put_contents("container.json", json_encode($c, JSON_PRETTY_PRINT));' | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'UsesMailer.php:7:Call to an undefined method ResultCacheE2EValueDependency\Logger::send().' "$OUTPUT" | |
| # Values nobody asked about do not re-analyse anything. | |
| php -r '$c = json_decode(file_get_contents("container.json"), true); $c["parameters"]["timezone"] = "UTC"; $c["services"]["router"] = "ResultCacheE2EValueDependency\\Logger"; file_put_contents("container.json", json_encode($c, JSON_PRETTY_PRINT));' | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '0' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| # A value that was missing is watched too. | |
| php -r '$c = json_decode(file_get_contents("container.json"), true); $c["services"]["cache"] = "ResultCacheE2EValueDependency\\Logger"; file_put_contents("container.json", json_encode($c, JSON_PRETTY_PRINT));' | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'Service cache does not exist.' "$OUTPUT" | |
| # Every file asking about the same value is re-analysed. | |
| php -r '$c = json_decode(file_get_contents("container.json"), true); $c["parameters"]["locale"] = "en"; file_put_contents("container.json", json_encode($c, JSON_PRETTY_PRINT));' | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '2' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains "UsesLocale.php:10:Function ResultCacheE2EValueDependency\usesLocale() should return 'none' but returns 'en'." "$OUTPUT" | |
| ../bashunit assert contains "UsesLocaleToo.php:10:Function ResultCacheE2EValueDependency\usesLocaleToo() should return 'none' but returns 'en'." "$OUTPUT" | |
| # Scope::getFile() is not the file to re-analyse when the extension runs outside the walk of the | |
| # analysed file: the type of Holder::$debug is inferred from the constructor on a scope of | |
| # src/Holder.php, whenever the property is first needed, and it is cached. So it is what | |
| # src/Holder.php declares that depends on the value: a change of it re-analyses src/Holder.php and | |
| # the files depending on it. | |
| php -r '$c = json_decode(file_get_contents("container.json"), true); $c["parameters"]["debug"] = "1"; file_put_contents("container.json", json_encode($c, JSON_PRETTY_PRINT));' | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '2' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'Holder.php' "$OUTPUT" | |
| # Inside a trait, the value is asked about in the analysis of the class using it - that's the | |
| # file re-analysed, and the error is reported in the trait. | |
| php -r '$c = json_decode(file_get_contents("container.json"), true); $c["services"]["logger"] = "ResultCacheE2EValueDependency\\Mailer"; file_put_contents("container.json", json_encode($c, JSON_PRETTY_PRINT));' | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '2' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'UsesLogger.php:7:Call to an undefined method ResultCacheE2EValueDependency\Mailer::log().' "$OUTPUT" | |
| ../bashunit assert contains 'LoggerTrait.php (in context of class ResultCacheE2EValueDependency\UsesLoggerTrait):10:Call to an undefined method ResultCacheE2EValueDependency\Mailer::log().' "$OUTPUT" | |
| # A dynamic throw type extension tracks the values it reads too. | |
| php -r '$c = json_decode(file_get_contents("container.json"), true); $c["parameters"]["throwing"] = "yes"; file_put_contents("container.json", json_encode($c, JSON_PRETTY_PRINT));' | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'Dead catch' "$OUTPUT" | |
| # So does a collector: the data it collected is collected again in the analysis of the file. | |
| php -r '$c = json_decode(file_get_contents("container.json"), true); $c["parameters"]["region"] = "us"; file_put_contents("container.json", json_encode($c, JSON_PRETTY_PRINT));' | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'UsesRegion.php:7:Region us is used.' "$OUTPUT" | |
| ../bashunit assert not_contains 'Region eu is used.' "$OUTPUT" | |
| # So does a parameter closure this extension: $this in the closure follows the parameter. | |
| php -r '$c = json_decode(file_get_contents("container.json"), true); $c["parameters"]["closureThis"] = "ResultCacheE2EValueDependency\\Logger"; file_put_contents("container.json", json_encode($c, JSON_PRETTY_PRINT));' | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'UsesWithThis.php:8:Call to an undefined method ResultCacheE2EValueDependency\Logger::send().' "$OUTPUT" | |
| # So does a type-specifying extension: the narrowed type follows the parameter. | |
| php -r '$c = json_decode(file_get_contents("container.json"), true); $c["parameters"]["allowedClass"] = "ResultCacheE2EValueDependency\\Logger"; file_put_contents("container.json", json_encode($c, JSON_PRETTY_PRINT));' | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'UsesIsAllowed.php:8:Call to an undefined method ResultCacheE2EValueDependency\Logger::send().' "$OUTPUT" | |
| # A rule tracking a directory: a file the pattern doesn't match changes nothing... | |
| echo 'notes' > views/notes.txt | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '0' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| # ...a matching file appearing re-analyses the file that looked for it. | |
| echo '<p>dashboard</p>' > views/dashboard.html | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'View dashboard does not exist.' "$OUTPUT" | |
| # ...and so does a matching file whose contents change. | |
| echo '<p>dashboard, changed</p>' > views/dashboard.html | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| ../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw" | |
| # A dynamic return type extension tracking a class named in a string: the class appearing - outside | |
| # the analysed paths, so no fallback for new files kicks in - re-analyses the file naming it... | |
| printf '<?php declare(strict_types = 1);\n\nnamespace ResultCacheE2EValueDependency\\Lib;\n\nclass Gadget extends Base\n{\n}\n' > lib/Gadget.php | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'UsesMake.php' "$OUTPUT" | |
| # ...a method body edited in its parent class does not... | |
| sed -i.bak 's/return 1;/return 2;/' lib/Base.php && rm lib/Base.php.bak | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '0' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| # ...and a signature changed there does. | |
| printf '<?php declare(strict_types = 1);\n\nnamespace ResultCacheE2EValueDependency\\Lib;\n\nclass Base\n{\n\n\tpublic function run(): string\n\t{\n\t\treturn \x27x\x27;\n\t}\n\n}\n' > lib/Base.php | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'UsesMake.php:7:Function ResultCacheE2EValueDependency\usesMake() should return int but returns string.' "$OUTPUT" | |
| # A class reflection extension tracking on the class: every file using the class is analysed again, | |
| # also one that got the magic method from the reflection cache without asking the extension. | |
| php -r '$c = json_decode(file_get_contents("container.json"), true); $c["parameters"]["greeting"] = "1"; file_put_contents("container.json", json_encode($c, JSON_PRETTY_PRINT));' | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '3' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'UsesMagic.php:7:Function ResultCacheE2EValueDependency\usesMagic() should return string but returns int.' "$OUTPUT" | |
| ../bashunit assert contains 'UsesMagicToo.php:7:Function ResultCacheE2EValueDependency\usesMagicToo() should return string but returns int.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-file-dependency | |
| composer install | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains "UsesFunction.php:10:Function ResultCacheE2EFileDependency\usesFunction() should return 'none' but returns 'function-1'." "$OUTPUT" | |
| ../bashunit assert contains "UsesMethod.php:10:Function ResultCacheE2EFileDependency\usesMethod() should return 'none' but returns 'method-1'." "$OUTPUT" | |
| ../bashunit assert contains "UsesStaticMethod.php:10:Function ResultCacheE2EFileDependency\usesStaticMethod() should return 'none' but returns 'static-method-1'." "$OUTPUT" | |
| ../bashunit assert contains 'Holder.php:17:Method ResultCacheE2EFileDependency\Holder::get() should return int but returns string.' "$OUTPUT" | |
| ../bashunit assert contains 'UsesHolder.php:9:Function ResultCacheE2EFileDependency\usesHolder() should return int but returns string.' "$OUTPUT" | |
| ../bashunit assert contains 'UsesDeprecatedConfig.php:7:Deprecated config asks for an error.' "$OUTPUT" | |
| ../bashunit assert not_contains 'Config asks for an error.' "$OUTPUT" | |
| # The paths declared with the deprecated RuleErrorBuilder::fileDependency() are declared on the | |
| # scope of the rule's node. | |
| printf 'quiet\n' > data/deprecated.txt | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'Deprecated config asks for an error.' "$OUTPUT" | |
| # A rule declares its dependency on src/Config.php whether it reports an error or not. The file | |
| # declares no symbols, so no change of it reaches its exported nodes - any change re-analyses the | |
| # files of the rule's nodes anyway, next to the changed file itself: src/UsesConfig.php and | |
| # src/ConfigHolder.php. Not src/UsesConfigHolder.php depending on src/ConfigHolder.php - what | |
| # src/ConfigHolder.php declares did not change. | |
| printf "<?php\n\nreturn 'report';\n" > src/Config.php | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '3' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'UsesConfig.php:7:Config asks for an error.' "$OUTPUT" | |
| ../bashunit assert contains 'ConfigHolder.php:10:Config asks for an error.' "$OUTPUT" | |
| printf "<?php\n\n// a change that keeps the error\nreturn 'report';\n" > src/Config.php | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '3' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'UsesConfig.php:7:Config asks for an error.' "$OUTPUT" | |
| # Dynamic return type extensions of a function, a method and a static method declared in | |
| # src/functions.php and src/Repository.php read a data file each. A change of one re-analyses the | |
| # file with the call - not the one declaring the function or the method. | |
| printf 'function-2\n' > data/function.txt | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains "UsesFunction.php:10:Function ResultCacheE2EFileDependency\usesFunction() should return 'none' but returns 'function-2'." "$OUTPUT" | |
| printf 'method-2\n' > data/method.txt | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains "UsesMethod.php:10:Function ResultCacheE2EFileDependency\usesMethod() should return 'none' but returns 'method-2'." "$OUTPUT" | |
| printf 'static-method-2\n' > data/static-method.txt | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains "UsesStaticMethod.php:10:Function ResultCacheE2EFileDependency\usesStaticMethod() should return 'none' but returns 'static-method-2'." "$OUTPUT" | |
| # Scope::getFile() is not the file to re-analyse when the extension runs outside the walk of the | |
| # analysed file: the type of Holder::$value is inferred from the constructor on a scope of | |
| # src/Holder.php, whenever the property is first needed - in the analysis of src/Holder.php, or of | |
| # src/UsesHolder.php reading it - and it is cached. So it is what src/Holder.php declares that | |
| # depends on data/holder.txt: a change of it re-analyses src/Holder.php and the files depending on it. | |
| printf 'int\n' > data/holder.txt | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '2' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'Holder.php:17:Method ResultCacheE2EFileDependency\Holder::get() should return int but returns string.' "$OUTPUT" | |
| ../bashunit assert not_contains 'UsesHolder.php:9:Function ResultCacheE2EFileDependency\usesHolder() should return int but returns string.' "$OUTPUT" | |
| # Deleting a declared file re-analyses the files depending on it too, and it stays watched while | |
| # it is missing: creating it again re-analyses them again. | |
| rm data/function.txt | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains "UsesFunction.php:10:Function ResultCacheE2EFileDependency\usesFunction() should return 'none' but returns 'missing'." "$OUTPUT" | |
| printf 'function-3\n' > data/function.txt | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains "UsesFunction.php:10:Function ResultCacheE2EFileDependency\usesFunction() should return 'none' but returns 'function-3'." "$OUTPUT" | |
| # The same for what src/Holder.php declares. | |
| rm data/holder.txt | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '2' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Holder.php:17:Method ResultCacheE2EFileDependency\Holder::get() should return int but returns string.' "$OUTPUT" | |
| ../bashunit assert contains 'UsesHolder.php:9:Function ResultCacheE2EFileDependency\usesHolder() should return int but returns string.' "$OUTPUT" | |
| printf 'int\n' > data/holder.txt | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '2' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'Holder.php:17:Method ResultCacheE2EFileDependency\Holder::get() should return int but returns string.' "$OUTPUT" | |
| ../bashunit assert not_contains 'UsesHolder.php:9:Function ResultCacheE2EFileDependency\usesHolder() should return int but returns string.' "$OUTPUT" | |
| # And for a declared file that is analysed itself: deleted, it is no longer analysed, and the files | |
| # of the rule's nodes are re-analysed; created again, all three are - and as with any new file, so | |
| # are the three files with errors. | |
| rm src/Config.php | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '2' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'Config asks for an error.' "$OUTPUT" | |
| printf "<?php\n\nreturn 'report';\n" > src/Config.php | |
| INFO=$(../../bin/phpstan result-cache-info --json) | |
| echo "$INFO" | |
| ../bashunit assert equals '6' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'UsesConfig.php:7:Config asks for an error.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-scanned-trait | |
| ../../bin/phpstan analyse | |
| # A trait living in a scanned file is tracked the same way, including through the | |
| # used-trait dependencies. | |
| patch -b scanned/DepTrait.php < patch.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'UsesTrait.php:12:Method ResultCacheE2EScannedTrait\UsesTrait::doIt() should return int but returns string.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-autoloaded | |
| composer install | |
| ../../bin/phpstan analyse | |
| # A project file that is not analysed and not scanned either - it is only reachable | |
| # through the Composer autoloader - is tracked as a file dependency too, so editing it | |
| # re-analyses the files depending on it instead of leaving stale results behind. | |
| patch -b other/Dep.php < patch.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:12:Method ResultCacheE2EAutoloaded\Foo::doFoo() should return int but returns string.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-autoloaded-constant | |
| composer install | |
| # The same sequence on a project file that is neither analysed nor scanned - it is only | |
| # reachable through the Composer autoloader, so it is tracked through the dependency graph | |
| # alone. | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:10:Dumped type: 1' "$OUTPUT" | |
| # The constant is renamed to one the class already declares. The file is invalid PHP, but | |
| # the class still reflects, so the file using the constant is re-analysed through its | |
| # dependency edge. | |
| patch -b other/Dep.php < duplicate.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:10:Access to undefined constant ResultCacheE2EAutoloadedConstant\Dep::CON.' "$OUTPUT" | |
| # Renamed to something else instead. | |
| mv other/Dep.php.orig other/Dep.php | |
| patch -b other/Dep.php < renamed.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:10:Access to undefined constant ResultCacheE2EAutoloadedConstant\Dep::CON.' "$OUTPUT" | |
| # Broken the same way again. | |
| mv other/Dep.php.orig other/Dep.php | |
| patch -b other/Dep.php < duplicate.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:10:Access to undefined constant ResultCacheE2EAutoloadedConstant\Dep::CON.' "$OUTPUT" | |
| # And put back the way it was. | |
| mv other/Dep.php.orig other/Dep.php | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:10:Dumped type: 1' "$OUTPUT" | |
| # A syntax error makes the class disappear altogether: the file using it reports an unknown | |
| # class and records no dependency edge to the file, so nothing but the file being fixed can | |
| # bring the file using it back - it must be re-analysed once the class is there again. | |
| patch -b other/Dep.php < broken.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:10:Access to constant CON on an unknown class ResultCacheE2EAutoloadedConstant\Dep.' "$OUTPUT" | |
| mv other/Dep.php.orig other/Dep.php | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:10:Dumped type: 1' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-scanned-vendor | |
| composer install | |
| ../../bin/phpstan analyse | |
| # A scanned file that belongs to an installed Composer package is not tracked as a file | |
| # dependency - packages are tracked at package granularity - so editing it still has to | |
| # invalidate the whole result cache instead of leaving stale results behind. | |
| patch -b vendor/test/scanned-lib/src/Dep.php < patch.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'changed and belongs to a Composer package.' "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:12:Method ResultCacheE2EScannedVendor\Foo::doFoo() should return int but returns string.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-bootstrap | |
| ../../bin/phpstan analyse | |
| # A bootstrapFile is executed, not just read, so it can change anything about the | |
| # analysis. It stays part of the executedFilesHashes metadata and keeps invalidating the | |
| # whole result cache - the scanned file tracking does not apply to it. | |
| patch -b bootstrap.php < patch.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache not used because the metadata do not match: executedFilesHashes' "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:10:Method ResultCacheE2EBootstrap\Foo::doFoo() should return int but returns string.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-traits | |
| ../../bin/phpstan analyse | |
| patch -b src/FooTrait.php < renameFooTraitMethod.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'ClassMentioningClassUsingBarTrait.php:10:Call to an undefined method ResultCacheE2ETraits\ClassUsingBarTrait::doFooTrait(). [identifier=method.notFound]' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-trait-alongside-class | |
| ../../bin/phpstan analyse | |
| patch -b src/FooTrait.php < changeFooTraitBody.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'FooTrait.php (in context of class ResultCacheE2ETraitAlongsideClass\ClassUsingFooTrait):10:Method ResultCacheE2ETraitAlongsideClass\ClassUsingFooTrait::doFooTrait() should return stdClass but returns Exception. [identifier=return.type]' "$OUTPUT" | |
| - script: | | |
| cd e2e/fix | |
| composer install | |
| rm -rf src cycle tmp && cp -r original src && cp -r cycle-original cycle && cp ignore-original.neon ignore.neon | |
| OUTPUT=$(AI_AGENT=1 ../bashunit assert exit_code "1" "../../bin/phpstan analyse --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains '7 of the errors can be fixed automatically. Run PHPStan again with --fix' "$OUTPUT" | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse --fix") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Analysing fixed code (round 4)...' "$OUTPUT" | |
| ../bashunit assert not_contains 'round 5' "$OUTPUT" | |
| ../bashunit assert contains 'Round 2: fixed 3 errors in 1 file' "$OUTPUT" | |
| ../bashunit assert contains '[OK] 10 errors fixed in 2 files' "$OUTPUT" | |
| diff -u expected/Foo.php src/Foo.php | |
| diff -u expected/Ignored-fix.php src/Ignored.php | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse --fix-ignored") | |
| echo "$OUTPUT" | |
| # the last round removes the ignoreErrors entry, it does not match anything after the fixes | |
| ../bashunit assert contains '[OK] 3 errors fixed in 2 files' "$OUTPUT" | |
| diff -u expected/Foo.php src/Foo.php | |
| diff -u expected/Ignored-fix-ignored.php src/Ignored.php | |
| diff -u ignore-expected.neon ignore.neon | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --fix --fix-ignored") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'No fixable errors found' "$OUTPUT" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -c cycle.neon --fix") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Fixing stopped after 10 rounds' "$OUTPUT" | |
| - script: | | |
| cd e2e/fix-baseline | |
| composer install | |
| mkdir -p vendor/acme/rules && cp vendor-baseline.php vendor/acme/rules/baseline.php | |
| rm -rf tmp && cp baseline-original.php baseline.php && cp baseline-original.neon baseline.neon | |
| OUTPUT=$(AI_AGENT=1 ../bashunit assert exit_code "1" "../../bin/phpstan analyse --error-format raw") | |
| echo "$OUTPUT" | |
| # two unmatched messages, an unmatched entry, and a count higher than the occurrences in the PHP baseline, | |
| # an unmatched message, an unmatched entry, and a count higher than the occurrences in the NEON baseline; | |
| # and two entries without a path in the NEON baseline, reported in it; | |
| # not the entries in vendor/, and not a count lower than the occurrences (--generate-baseline does that) | |
| ../bashunit assert contains '9 of the errors can be fixed automatically. Run PHPStan again with --fix' "$OUTPUT" | |
| ../bashunit assert contains 'baseline.neon:21:Ignored error pattern #^Unmatched without a path$# was not matched' "$OUTPUT" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --fix --generate-baseline") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Errors cannot be fixed when generating the baseline.' "$OUTPUT" | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse --fix") | |
| echo "$OUTPUT" | |
| # removing the last two messages of the PHP list conflicts, and so does removing the NEON entry | |
| # and the first message of the entry after it, and so does removing the two neighbouring entries without a path, | |
| # the second one goes in the next round | |
| ../bashunit assert contains 'Round 2: fixed 3 errors in 2 files' "$OUTPUT" | |
| ../bashunit assert contains '[OK] 9 errors fixed in 2 files' "$OUTPUT" | |
| diff -u baseline-expected.php baseline.php | |
| diff -u baseline-expected.neon baseline.neon | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Ignored error pattern #^Unmatched in vendor$#' "$OUTPUT" | |
| ../bashunit assert contains 'Ignored error pattern #^Unmatched in vendor without a path$# was not matched' "$OUTPUT" | |
| ../bashunit assert contains 'is expected to occur 1 time, but occurred 2 times.' "$OUTPUT" | |
| ../bashunit assert not_contains 'Unmatched one' "$OUTPUT" | |
| ../bashunit assert not_contains 'NEON' "$OUTPUT" | |
| ../bashunit assert not_contains 'occurred only' "$OUTPUT" | |
| - script: | | |
| cd e2e/fix-ignored-entries | |
| composer install | |
| rm -rf src tmp && cp -r original src && cp baseline-original.php baseline.php && cp baseline-original.neon baseline.neon | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse --fix-ignored") | |
| echo "$OUTPUT" | |
| # the ignored errors in the first round, the ignores that matched them in the second round: | |
| # the PHP baseline entry, the too high count of the NEON baseline entry and the @phpstan-ignore comment | |
| ../bashunit assert contains 'Round 1: fixed 5 errors in 3 files' "$OUTPUT" | |
| ../bashunit assert contains 'Round 2: fixed 3 errors in 3 files' "$OUTPUT" | |
| ../bashunit assert contains '[OK] 8 errors fixed in 5 files' "$OUTPUT" | |
| for file in InComment.php InNeonBaseline.php InPhpBaseline.php; do diff -u expected/$file src/$file; done | |
| diff -u expected/baseline.php baseline.php | |
| diff -u expected/baseline.neon baseline.neon | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --error-format raw") | |
| echo "$OUTPUT" | |
| # the entry that was stale before, in a file --fix-ignored did not change, is left alone | |
| ../bashunit assert contains 'Untouched.php:?:Ignored error pattern #^Stale$#' "$OUTPUT" | |
| ../bashunit assert not_contains 'src/In' "$OUTPUT" | |
| - script: | | |
| cd e2e/editor-mode | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Bar.php:10:Parameter #1 $s of method EditorModeE2E\Bar::requireString() expects string, int given. [identifier=argument.type]' "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:10:Method EditorModeE2E\Foo::doFoo() should return int but returns string. [identifier=return.type]' "$OUTPUT" | |
| ../bashunit assert contains 'Result cache is saved.' "$OUTPUT" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw --tmp-file differentFoo.php --instead-of src/Foo.php") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:10:Method EditorModeE2E\Foo::doFoo() should return float but returns string. [identifier=return.type]' "$OUTPUT" | |
| ../bashunit assert not_contains 'Foo.php:10:Method EditorModeE2E\Foo::doFoo() should return int but returns string. [identifier=return.type]' "$OUTPUT" | |
| ../bashunit assert not_contains 'differentFoo.php' "$OUTPUT" | |
| ../bashunit assert contains 'Bar.php:10:Parameter #1 $s of method EditorModeE2E\Bar::requireString() expects string, float given. [identifier=argument.type]' "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 2 files will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'Result cache was not saved because of --tmp-file and --instead-of CLI options passed (editor mode).' "$OUTPUT" | |
| - script: | | |
| cd e2e/trait-caching | |
| ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ | |
| ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ | |
| - script: | | |
| cd e2e/trait-caching | |
| ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ | |
| patch -b data/TraitOne.php < TraitOne.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/") | |
| echo "$OUTPUT" | |
| ../bashunit assert line_count 2 "$OUTPUT" | |
| ../bashunit assert matches "Note: Using configuration file .+phpstan.neon." "$OUTPUT" | |
| ../bashunit assert contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" | |
| - script: | | |
| cd e2e/trait-caching | |
| ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ | |
| patch -b data/TraitTwo.php < TraitTwo.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/") | |
| echo "$OUTPUT" | |
| ../bashunit assert line_count 2 "$OUTPUT" | |
| ../bashunit assert matches "Note: Using configuration file .+phpstan.neon." "$OUTPUT" | |
| ../bashunit assert contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" | |
| - script: | | |
| cd e2e/trait-caching | |
| ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ | |
| patch -b data/TraitOne.php < TraitOne.patch | |
| patch -b data/TraitTwo.php < TraitTwo.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/") | |
| echo "$OUTPUT" | |
| ../bashunit assert line_count 3 "$OUTPUT" | |
| ../bashunit assert matches "Note: Using configuration file .+phpstan.neon." "$OUTPUT" | |
| ../bashunit assert contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" | |
| ../bashunit assert contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" | |
| - script: | | |
| cd e2e/bad-exclude-paths | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -c ignore.neon") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Invalid entry in ignoreErrors' "$OUTPUT" | |
| ../bashunit assert contains 'tests" is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT" | |
| - script: | | |
| cd e2e/bad-exclude-paths | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -c phpneon.php") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Invalid entry in ignoreErrors' "$OUTPUT" | |
| ../bashunit assert contains '"src/test.php" is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT" | |
| - script: | | |
| cd e2e/bad-exclude-paths | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -c excludePaths.neon") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Invalid entry in excludePaths' "$OUTPUT" | |
| ../bashunit assert contains 'tests" is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT" | |
| - script: | | |
| cd e2e/bad-exclude-paths | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -c phpneon2.php") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Invalid entry in excludePaths' "$OUTPUT" | |
| ../bashunit assert contains '"src/test.php" is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT" | |
| - script: | | |
| cd e2e/bad-exclude-paths | |
| OUTPUT=$(../../bin/phpstan analyse -c ignoreNonexistentExcludePath.neon) | |
| echo "$OUTPUT" | |
| - script: | | |
| cd e2e/bad-exclude-paths | |
| cp -r tmp-node-modules node_modules | |
| OUTPUT=$(../../bin/phpstan analyse -c ignoreNonexistentExcludePath.neon) | |
| echo "$OUTPUT" | |
| - script: | | |
| cd e2e/bad-exclude-paths | |
| OUTPUT=$(../../bin/phpstan analyse -c ignoreReportUnmatchedFalse.neon) | |
| echo "$OUTPUT" | |
| - script: | | |
| cd e2e/bug-11826 | |
| composer install | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Child process error (exit code 255): PHP Fatal error' "$OUTPUT" | |
| ../bashunit assert contains 'Result is incomplete because of severe errors.' "$OUTPUT" | |
| - script: | | |
| cd e2e/oom-forked-worker | |
| composer install | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --memory-limit=300M") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Child process error: PHPStan process crashed' "$OUTPUT" | |
| ../bashunit assert contains 'Result is incomplete because of severe errors.' "$OUTPUT" | |
| - script: | | |
| cd e2e/bug-11857 | |
| composer install | |
| ../../bin/phpstan | |
| - script: | | |
| cd e2e/in-trait | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan --error-format=raw") | |
| ../bashunit assert contains 'FooTrait.php:10:Strict comparison using === between int<0, max> and false will always evaluate to false.' "$OUTPUT" | |
| ../bashunit assert contains 'FooTrait.php (in context of class E2EInTrait\Bar):18:Strict comparison using === between E2EInTrait\Bar and null will always evaluate to false.' "$OUTPUT" | |
| ../bashunit assert contains 'FooTrait.php (in context of class E2EInTrait\Foo):18:Strict comparison using === between E2EInTrait\Foo and null will always evaluate to false.' "$OUTPUT" | |
| - script: | | |
| cd e2e/bug-14718 | |
| # https://github.com/phpstan/phpstan/issues/14718 | |
| # An inline @phpstan-ignore on an error deduplicated directly into the trait must | |
| # suppress it, consistently with both an empty and a primed result cache. | |
| ../../bin/phpstan clear-result-cache | |
| ../bashunit assert exit_code "0" "../../bin/phpstan --error-format=raw" | |
| ../bashunit assert exit_code "0" "../../bin/phpstan --error-format=raw" | |
| # With the ignore removed, the error is reported directly in the trait file. | |
| sed -i 's# // @phpstan-ignore identical.alwaysFalse##' src/FooTrait.php | |
| ../../bin/phpstan clear-result-cache | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan --error-format=raw") | |
| ../bashunit assert contains 'FooTrait.php:10:Strict comparison using === between int<0, max> and false will always evaluate to false.' "$OUTPUT" | |
| # An ignoreErrors path may target the error by the trait file (hides it for | |
| # all using classes) or by a using-class file - a class-pathed entry accounts | |
| # only for its own class's context, so the other using class's context is | |
| # still reported (see also the e2e/bug-14932 script). | |
| ../../bin/phpstan clear-result-cache | |
| ../bashunit assert exit_code "0" "../../bin/phpstan analyse --error-format=raw -c ignore-trait-path.neon" | |
| ../../bin/phpstan clear-result-cache | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --error-format=raw -c ignore-class-path.neon") | |
| ../bashunit assert contains 'FooTrait.php (in context of class Bug14718\Bar):10:Strict comparison using === between int<0, max> and false will always evaluate to false.' "$OUTPUT" | |
| ../bashunit assert not_contains 'was not matched' "$OUTPUT" | |
| # A generated baseline suppresses the error on re-run, with both an empty and a | |
| # primed result cache (the issue reported baseline generation as impossible). | |
| ../../bin/phpstan --generate-baseline=baseline.neon | |
| ../../bin/phpstan clear-result-cache | |
| ../bashunit assert exit_code "0" "../../bin/phpstan analyse --error-format=raw -c with-baseline.neon" | |
| ../bashunit assert exit_code "0" "../../bin/phpstan analyse --error-format=raw -c with-baseline.neon" | |
| - script: | | |
| cd e2e/bug-14932 | |
| # https://github.com/phpstan/phpstan/issues/14932 | |
| # An error deduplicated directly into the trait must behave deterministically: | |
| # the same report, the same ignore matching and the same generated baseline on | |
| # every run, independent of result-cache state (cold/warm/partial) and of | |
| # worker arrival order. | |
| cp AMailer.php.dist src/Mailers/AMailer.php | |
| # With no ignores: the deduplicated error is reported once, directly in the | |
| # trait file (no "in context of"); errors whose message differs per using | |
| # class stay reported in each class context. | |
| ../../bin/phpstan clear-result-cache | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --error-format=raw") | |
| ../bashunit assert contains 'SomeTrait.php:17:Right side of && is always true.' "$OUTPUT" | |
| ../bashunit assert not_contains 'SomeTrait.php (in context of' "$OUTPUT" | |
| ../bashunit assert contains 'DiffMessageTrait.php (in context of class Bug14932\AMailer):10:Strict comparison' "$OUTPUT" | |
| ../bashunit assert contains 'DiffMessageTrait.php (in context of class Bug14932\BMailer):10:Strict comparison' "$OUTPUT" | |
| # An ignoreErrors path targeting the trait file hides the error from all | |
| # using classes - with both an empty and a primed result cache. | |
| ../../bin/phpstan clear-result-cache | |
| ../bashunit assert exit_code "0" "../../bin/phpstan analyse --error-format=raw -c ignore-trait-path.neon" | |
| ../bashunit assert exit_code "0" "../../bin/phpstan analyse --error-format=raw -c ignore-trait-path.neon" | |
| # An ignoreErrors path targeting one using class accounts only for that | |
| # class's context: the entry matches (no ignore.unmatched), and the error is | |
| # still reported in the context of the other class - deterministically, with | |
| # both an empty and a primed result cache. | |
| ../../bin/phpstan clear-result-cache | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --error-format=raw -c ignore-class-a-path.neon") | |
| ../bashunit assert contains 'SomeTrait.php (in context of class Bug14932\BMailer):17:Right side of && is always true.' "$OUTPUT" | |
| ../bashunit assert not_contains 'was not matched' "$OUTPUT" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --error-format=raw -c ignore-class-a-path.neon") | |
| ../bashunit assert contains 'SomeTrait.php (in context of class Bug14932\BMailer):17:Right side of && is always true.' "$OUTPUT" | |
| ../../bin/phpstan clear-result-cache | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --error-format=raw -c ignore-class-b-path.neon") | |
| ../bashunit assert contains 'SomeTrait.php (in context of class Bug14932\AMailer):17:Right side of && is always true.' "$OUTPUT" | |
| ../bashunit assert not_contains 'was not matched' "$OUTPUT" | |
| # Entries covering every using class hide the error completely - this is what | |
| # a baseline generated by PHPStan <= 2.1 recorded (one entry per class | |
| # context), so such baselines keep working with no unmatched entries. | |
| ../../bin/phpstan clear-result-cache | |
| ../bashunit assert exit_code "0" "../../bin/phpstan analyse --error-format=raw -c ignore-both-class-paths.neon" | |
| ../bashunit assert exit_code "0" "../../bin/phpstan analyse --error-format=raw -c ignore-both-class-paths.neon" | |
| # A directory path covering the using classes (but not the trait file) accounts | |
| # per-context too: without count it hides all contexts... | |
| ../../bin/phpstan clear-result-cache | |
| ../bashunit assert exit_code "0" "../../bin/phpstan analyse --error-format=raw -c ignore-dir-path.neon" | |
| # ...and with count: 1 it covers one of the two contexts: the other is reported | |
| # in its context, and the count mismatch reports the true occurrence count. | |
| ../../bin/phpstan clear-result-cache | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --error-format=raw -c ignore-dir-count.neon") | |
| ../bashunit assert contains 'SomeTrait.php (in context of class Bug14932\BMailer):17:Right side of && is always true.' "$OUTPUT" | |
| ../bashunit assert contains 'is expected to occur 1 time, but occurred 2 times' "$OUTPUT" | |
| # The add-a-using-class flicker from the issue: prime the result cache with | |
| # BMailer as the trait's only user (the class-pathed entry covers the only | |
| # context), then add AMailer. The AMailer context is reported - on the | |
| # partial run and identically on every unchanged run after it. | |
| rm src/Mailers/AMailer.php | |
| ../../bin/phpstan clear-result-cache | |
| ../bashunit assert exit_code "0" "../../bin/phpstan analyse --error-format=raw -c ignore-class-b-path.neon" | |
| cp AMailer.php.dist src/Mailers/AMailer.php | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --error-format=raw -c ignore-class-b-path.neon") | |
| ../bashunit assert contains 'SomeTrait.php (in context of class Bug14932\AMailer):17:Right side of && is always true.' "$OUTPUT" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --error-format=raw -c ignore-class-b-path.neon") | |
| ../bashunit assert contains 'SomeTrait.php (in context of class Bug14932\AMailer):17:Right side of && is always true.' "$OUTPUT" | |
| # --generate-baseline attributes the fully deduplicated error to the trait | |
| # file, and the baseline holds on re-run with an empty and a primed cache. | |
| ../../bin/phpstan clear-result-cache | |
| ../../bin/phpstan analyse -c generate-only.neon --generate-baseline=baseline.neon | |
| ../bashunit assert contains 'path: src/SomeTrait.php' "$(cat baseline.neon)" | |
| ../bashunit assert not_contains 'AMailer.php' "$(cat baseline.neon)" | |
| ../bashunit assert not_contains 'BMailer.php' "$(cat baseline.neon)" | |
| ../../bin/phpstan clear-result-cache | |
| ../bashunit assert exit_code "0" "../../bin/phpstan analyse --error-format=raw -c with-baseline.neon" | |
| ../bashunit assert exit_code "0" "../../bin/phpstan analyse --error-format=raw -c with-baseline.neon" | |
| # Generating a baseline on top of a one-class ignore records the surviving | |
| # context under its class path, and the combination holds on re-run. | |
| ../../bin/phpstan clear-result-cache | |
| ../../bin/phpstan analyse -c ignore-class-a-path.neon --generate-baseline=partial-baseline.neon | |
| ../bashunit assert contains 'path: src/Mailers/BMailer.php' "$(cat partial-baseline.neon)" | |
| ../bashunit assert not_contains 'SomeTrait.php' "$(cat partial-baseline.neon)" | |
| ../../bin/phpstan clear-result-cache | |
| ../bashunit assert exit_code "0" "../../bin/phpstan analyse --error-format=raw -c with-partial-baseline.neon" | |
| ../bashunit assert exit_code "0" "../../bin/phpstan analyse --error-format=raw -c with-partial-baseline.neon" | |
| # A full generated baseline (trait path for the deduplicated error, class | |
| # paths for the per-context errors) holds on re-run as well. | |
| ../../bin/phpstan clear-result-cache | |
| ../../bin/phpstan --generate-baseline=full-baseline.neon | |
| ../bashunit assert contains 'path: src/Mailers/AMailer.php' "$(cat full-baseline.neon)" | |
| ../bashunit assert contains 'path: src/Mailers/BMailer.php' "$(cat full-baseline.neon)" | |
| ../bashunit assert contains 'path: src/SomeTrait.php' "$(cat full-baseline.neon)" | |
| ../../bin/phpstan clear-result-cache | |
| ../bashunit assert exit_code "0" "../../bin/phpstan analyse --error-format=raw -c with-full-baseline.neon" | |
| ../bashunit assert exit_code "0" "../../bin/phpstan analyse --error-format=raw -c with-full-baseline.neon" | |
| - script: | | |
| cd e2e/result-cache-meta-extension | |
| composer install | |
| ../../bin/phpstan -vvv | |
| ../../bin/phpstan -vvv --fail-without-result-cache | |
| echo 'modified-hash' > hash.txt | |
| OUTPUT=$(../bashunit assert exit_code "2" "../../bin/phpstan -vvv --fail-without-result-cache") | |
| echo "$OUTPUT" | |
| ../bashunit assert matches "Note: Using configuration file .+phpstan.neon." "$OUTPUT" | |
| ../bashunit assert contains 'Result cache not used because the metadata do not match: metaExtensions' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-meta-extension-throw | |
| composer install | |
| # https://github.com/phpstan/phpstan/issues/14805 | |
| # An exception thrown from ResultCacheMetaExtension::getHash() during result cache | |
| # restore must fail the run with a non-zero exit code - even when a bootstrap file | |
| # installs a global exception handler that would otherwise swallow it into exit 0. | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan --error-format=raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'boom from getHash' "$OUTPUT" | |
| ../bashunit assert not_contains 'Swallowed by global exception handler' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-restore-without-reflection | |
| composer install | |
| ../../bin/phpstan -vvv | |
| ../../bin/phpstan -vvv | |
| - script: | | |
| cd e2e/result-cache-constants | |
| ../../bin/phpstan analyse | |
| patch -b src/Constants.php < changeConstantValue.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'ClassUsingConstant.php:13:Method ResultCacheE2EConstants\ClassUsingConstant::getMode() should return int<1, 3> but returns 5. [identifier=return.type]' "$OUTPUT" | |
| mv src/Constants.php.orig src/Constants.php | |
| ../../bin/phpstan analyse | |
| patch -b src/Constants.php < renameConstant.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'ClassUsingConstant.php:13:Constant SOME_MODE not found. [identifier=constant.notFound]' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-define | |
| ../../bin/phpstan analyse | |
| patch -b src/define.php < changeDefineValue.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'ClassUsingDefine.php:13:Method ResultCacheE2EDefine\ClassUsingDefine::getMode() should return int<1, 3> but returns 5. [identifier=return.type]' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-composer-lock | |
| composer install | |
| ../../bin/phpstan analyse | |
| # Change composer.lock without changing any installed package version/reference | |
| # (vendor/composer/installed.php is left untouched), like a regenerated lock in CI | |
| # when composer.lock is not committed. The result cache must be kept, not discarded. | |
| jq '.["content-hash"]="0000000000000000000000000000000000000000"' composer.lock > composer.lock.new | |
| mv composer.lock.new composer.lock | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Composer metadata changed but no package versions changed; keeping the result cache.' "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 0 files will be reanalysed.' "$OUTPUT" | |
| # Change the content hash again AND edit a single source file: the cache is still kept | |
| # (no package changed), and only the one changed file is reanalysed. | |
| printf '\n' >> src/Foo.php | |
| jq '.["content-hash"]="1111111111111111111111111111111111111111"' composer.lock > composer.lock.new | |
| mv composer.lock.new composer.lock | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Composer metadata changed but no package versions changed; keeping the result cache.' "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-phar-bootstrap | |
| # A bootstrapFile living inside a phar is hashed into executedFilesHashes under its | |
| # phar:// URL, and PHPStan registers its own runtime stubs exactly like that inside | |
| # phpstan.phar. Only the path after the scheme may be relativized: dropping the scheme | |
| # leaves absolutizePath() unable to reconstruct the URL, so the restored key never | |
| # matches and the cache is discarded on every run. | |
| php -d phar.readonly=0 build-boot-phar.php | |
| ../../bin/phpstan analyse | |
| # Every stored reference to the bootstrap file must keep its scheme. Grepping for | |
| # 'phar://' alone would also match the Neon-encoded projectConfig, so assert the | |
| # negative: no scheme-less key pointing at boot.phar/boot.php. | |
| if grep -oE "'[^']*boot\.phar/boot\.php'" tmp/resultCache.php | grep -v 'phar://'; then | |
| echo 'bootstrap path stored without its phar:// scheme'; exit 1 | |
| fi | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 0 files will be reanalysed.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-relative-path | |
| # Cold run: paths are stored relative to the phpstan install (the anchor), so the cache | |
| # no longer embeds the absolute checkout path anywhere - not in the error/dependency | |
| # sections and not inside collected data either, which is only reachable through the | |
| # collector that produced it (CollectorWithPaths). Assert the whole file is free of the | |
| # checkout prefix rather than just the analysed file's own key. | |
| ../../bin/phpstan analyse | |
| if grep -q "$(cd ../.. && pwd)/" tmp/resultCache.php; then | |
| echo 'cache still holds absolute paths under the checkout:' | |
| grep -o "$(cd ../.. && pwd)/[^'\"]*" tmp/resultCache.php | sort -u | head -20 | |
| exit 1 | |
| fi | |
| # Warm run: the relative cache re-absolutizes against the current anchor and is fully reused. | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 0 files will be reanalysed.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-relative-path | |
| # An optional excludePath must reach the cache the way a required one does. It used to | |
| # be absolutized whatever the loader was asked to expand, so it landed in the metadata | |
| # as an absolute path and a checkout at another path discarded the whole cache. | |
| ../../bin/phpstan analyse -c phpstan-optional-path.neon | |
| if grep -q "$(cd ../.. && pwd)/" tmp-optional-path/resultCache.php; then | |
| echo 'cache still holds absolute paths under the checkout:' | |
| grep -o "$(cd ../.. && pwd)/[^'\"]*" tmp-optional-path/resultCache.php | sort -u | head -20 | |
| exit 1 | |
| fi | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -c phpstan-optional-path.neon -vv") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 0 files will be reanalysed.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-relative-path | |
| MAIN="$(pwd)" | |
| # Warm the cache in this checkout; paths are stored relative to the phpstan install, so the | |
| # cache is portable to another checkout with the same layout. | |
| ../../bin/phpstan analyse | |
| # A git worktree is a second checkout of the same repo at a different absolute path. Give it | |
| # its own phpstan install (vendor) so %rootDir% points at the worktree, and carry the warm | |
| # cache across. The copy stands in for cache discovery, which is a follow-up and not part of | |
| # this PR; a real setup would CoW-clone the checkout or share the tmpDir. | |
| WORKTREE="$(mktemp -d)/phpstan" | |
| git -C ../.. worktree add --detach "$WORKTREE" HEAD | |
| cp -al ../../vendor "$WORKTREE/vendor" | |
| cp -R tmp "$WORKTREE/e2e/result-cache-relative-path/tmp" | |
| rm -rf "$WORKTREE/e2e/result-cache-relative-path/tmp/cache" | |
| # Running in the worktree (a different absolute prefix) must re-absolutize the relative cache | |
| # against the worktree and reuse it, with 0 files reanalysed. | |
| cd "$WORKTREE/e2e/result-cache-relative-path" | |
| # -vv progress (incl. "Result cache restored") goes to stderr, so capture both streams | |
| OUTPUT=$(../../bin/phpstan analyse -vv 2>&1) | |
| echo "$OUTPUT" | |
| echo "$OUTPUT" | grep -q 'Result cache restored. 0 files will be reanalysed.' || { echo 'result cache was not reused in the git worktree'; exit 1; } | |
| # Remove the worktree and confirm the original checkout still reuses its own cache, i.e. | |
| # running phpstan in a different worktree in between did not disturb it. | |
| cd "$MAIN" | |
| git -C ../.. worktree remove --force "$WORKTREE" | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 0 files will be reanalysed.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-package-update | |
| composer install | |
| ../../bin/phpstan analyse | |
| # Update a single Composer package by bumping its version with a patch and letting Composer | |
| # reinstall it, which changes its recorded reference in vendor/composer/installed.php. The | |
| # cache must be invalidated only for files depending on that package (src/Foo.php uses | |
| # test/logger), not for the whole project. | |
| patch -p1 < updatePackage.patch | |
| composer update test/logger --no-cache | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Composer packages changed (test/logger); re-analysing the files depending on them and the files with errors.' "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-path-repository | |
| composer install | |
| ../../bin/phpstan analyse | |
| # Updating an installed package re-analyses only the files depending on it. | |
| patch -p0 < updatePsrLog.patch | |
| composer update psr/log --no-cache | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Composer packages changed (psr/log); re-analysing the files depending on them and the files with errors.' "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| # Editing a file of an installed package is not a valid thing to do - the next Composer | |
| # command overwrites it - so PHPStan does not track it and keeps the cached results, even | |
| # though this edit takes away a method the analysed code calls. | |
| php -r '$f = "vendor/psr/log/src/LoggerInterface.php"; file_put_contents($f, str_replace("public function info(", "public function renamedInfo(", file_get_contents($f)));' | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 0 files will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert not_contains 'Call to an undefined method' "$OUTPUT" | |
| # A package from a path repository is the opposite case: it is the project's own code, | |
| # edited in place without any Composer command noticing, so its files are tracked like the | |
| # project's other non-analysed files. Symlinked, the installed package is the source | |
| # directory - an edit inside a method body changes nothing the files using it can see. | |
| patch -b -p0 < symlinked-body.patch | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 0 files will be reanalysed.' "$OUTPUT" | |
| # A changed return type does reach them. | |
| mv symlinked-lib/src/SymlinkedDep.php.orig symlinked-lib/src/SymlinkedDep.php | |
| patch -b -p0 < symlinked-signature.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'UsesSymlinked.php:16:Method ResultCacheE2EPathRepository\UsesSymlinked::doUsesSymlinked() should return int but returns string.' "$OUTPUT" | |
| # Undoing it clears the error again. | |
| mv symlinked-lib/src/SymlinkedDep.php.orig symlinked-lib/src/SymlinkedDep.php | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| # Copied instead of symlinked, the analysis reads the installed copy, so editing the | |
| # directory Composer copied from is not something PHPStan can see at all. | |
| patch -b -p0 < copied-source-signature.patch | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 0 files will be reanalysed.' "$OUTPUT" | |
| mv copied-lib/src/CopiedDep.php.orig copied-lib/src/CopiedDep.php | |
| # The installed copy is tracked the same way the symlinked package is. | |
| patch -b -p0 < copied-body.patch | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 0 files will be reanalysed.' "$OUTPUT" | |
| mv vendor/test/copied-lib/src/CopiedDep.php.orig vendor/test/copied-lib/src/CopiedDep.php | |
| patch -b -p0 < copied-signature.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'UsesCopied.php:16:Method ResultCacheE2EPathRepository\UsesCopied::doUsesCopied() should return int but returns string.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-composer-json | |
| composer install | |
| ../../bin/phpstan analyse | |
| # composer.json decides which PHP version the analysis targets (config.platform.php) and | |
| # which symbols the autoloader can reach, but its contents are hashed nowhere in the cache | |
| # metadata - composerLocks and composerInstalled cover composer.lock and installed.php, and | |
| # neither of those moves when only composer.json changes. | |
| patch -p0 < platform.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:10:Function str_contains not found.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-empty-file-gains-symbol | |
| # src/script.php declares no symbol, so the cache holds no exported nodes entry for it at | |
| # all, and src/user.php uses a class that does not exist yet. | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'user.php:7:Instantiated class ResultCacheE2EEmptyFileGainsSymbol\Thing not found.' "$OUTPUT" | |
| # That file now declares a class. A root symbol appeared, so the files with errors have to | |
| # be re-analysed, the same way a brand new file makes them re-analysed - the missing | |
| # exported nodes entry must not be read as "nothing changed". | |
| mv gains/script.php src/script.php | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'Instantiated class ResultCacheE2EEmptyFileGainsSymbol\Thing not found.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-parse-error | |
| # src/Thing.php has a parse error, and src/User.php uses the class it would declare. The parse | |
| # error carries an exception, so src/Thing.php is left out of the result cache, but the rest | |
| # of it is saved. | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vvv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Thing.php:12:Syntax error, unexpected EOF on line 12' "$OUTPUT" | |
| ../bashunit assert contains 'Result cache is saved.' "$OUTPUT" | |
| test -f tmp/resultCache.php | |
| # Only src/Thing.php is analysed again, and the result cache is not rewritten. | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vvv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Thing.php:12:Syntax error, unexpected EOF on line 12' "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'Result cache was not rewritten because it is unchanged.' "$OUTPUT" | |
| # Fixing the parse error declares Thing, so the error that src/User.php had in the result | |
| # cache goes away. | |
| cp fixed/Thing.php src/Thing.php | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vvv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'not found' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-internal-error | |
| composer dump-autoload | |
| # A rule that throws is an internal error. The result cache is not saved. | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vvv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Internal error: The rule crashed.' "$OUTPUT" | |
| ../bashunit assert contains 'Result cache was not saved' "$OUTPUT" | |
| test ! -f tmp/resultCache.php | |
| # An exception about the analysed code other than a parse error, here a class that is not | |
| # found, also keeps the result cache from being saved. | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -c class-not-found.neon -vvv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache was not saved because of non-ignorable exception: Class ResultCacheE2EInternalError\Missing was not found' "$OUTPUT" | |
| test ! -f tmp/resultCache.php | |
| - script: | | |
| cd e2e/result-cache-string-class | |
| ../../bin/phpstan analyse | |
| # The instantiated class is named by a constant string, so it is resolved through the type | |
| # system rather than through a name node, and the dependency graph records no edge from | |
| # Thing.php to Factory.php. Deleting the class still has to be noticed. | |
| rm src/Thing.php | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Factory.php:12:Instantiated class ResultCacheE2EStringClass\Thing not found.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-import-type-deleted | |
| ../../bin/phpstan analyse | |
| # Deleting the class an imported type alias comes from makes the PHPDoc of Base.php | |
| # unresolvable, and a file whose PHPDoc cannot be resolved records no dependencies at all - | |
| # the edge to User.php is dropped. The next edit of Base.php must still reach User.php. | |
| rm src/Aliases.php | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| patch -b -p0 < signature.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'User.php:10:Method ResultCacheE2EImportType\User::doUser() should return int but returns string.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-require-deleted | |
| ../../bin/phpstan analyse | |
| # The file named in require() is not analysed, not scanned and not autoloadable, so no hash | |
| # of it exists anywhere in the cache. Deleting it has to be noticed all the same: the rule | |
| # that checks the path reports an error the moment it is gone. | |
| rm config/app.php | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:10:Path in require() "config/app.php" is not a file or it does not exist.' "$OUTPUT" | |
| # And putting it back has to clear the error again. | |
| printf '<?php declare(strict_types = 1);\n\nreturn [];\n' > config/app.php | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'is not a file or it does not exist' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-vendor-class-appears | |
| composer install | |
| # The class does not exist yet, and an error about a class that does not exist records no | |
| # dependency on anything - there is no file to point at. | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:10:Parameter $logger of method ResultCacheE2EVendorClassAppears\Foo::doFoo() has invalid type Psr\Log\PluginProvidedLogger.' "$OUTPUT" | |
| # Composer updates the package, and a plugin writes the class into it on the way - which is | |
| # what phpstan/phpstan#15164 is about. Nothing the cache knows about points at that file, | |
| # so the files with errors have to be re-analysed on a package change; otherwise the error | |
| # stands while a run from a cleared cache reports nothing. | |
| patch -p0 < updatePsrLog.patch | |
| composer update psr/log --no-cache | |
| cp plugin-writes/PluginProvidedLogger.php vendor/psr/log/src/PluginProvidedLogger.php | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Composer packages changed (psr/log); re-analysing the files depending on them and the files with errors.' "$OUTPUT" | |
| ../bashunit assert not_contains 'has invalid type Psr\Log\PluginProvidedLogger' "$OUTPUT" | |
| - script: | | |
| cd e2e/ext-ds-version | |
| cp composer-json/v1-platform.json composer.json | |
| composer install --ignore-platform-reqs | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:12:Instantiated class Ds\Seq not found.' "$OUTPUT" | |
| # Only composer.json changes - composer.lock and installed.php stay as they are - and it now | |
| # selects the ext-ds 2 stubs. src/Bar.php has no error with ext-ds 1, so only its dependency on | |
| # ext-ds re-analyses it; src/Baz.php does not use ext-ds and stays cached. | |
| cp composer-json/v2-platform.json composer.json | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'PHP extension versions changed (ext-ds); re-analysing the files depending on them and the files with errors.' "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 2 files will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:11:Instantiated class Ds\Vector not found.' "$OUTPUT" | |
| ../bashunit assert contains 'Bar.php:15:Call to an undefined method Ds\Pair<string, int>::copy().' "$OUTPUT" | |
| ../bashunit assert not_contains 'Ds\Seq' "$OUTPUT" | |
| cp composer-json/v1-platform.json composer.json | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'PHP extension versions changed (ext-ds); re-analysing the files depending on them and the files with errors.' "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 2 files will be reanalysed.' "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:12:Instantiated class Ds\Seq not found.' "$OUTPUT" | |
| ../bashunit assert not_contains 'Ds\Vector' "$OUTPUT" | |
| ../bashunit assert not_contains 'Bar.php' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-extension-path-package | |
| composer install | |
| # The rule comes from a package Composer symlinked out of the project, so the file the | |
| # class reflects to is the source directory, outside vendor/ and outside the analysed | |
| # paths. Editing it has to invalidate the cache: the rule decides what every file reports. | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'the rule says one thing' "$OUTPUT" | |
| patch -p0 < patch.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache not used because extension file' "$OUTPUT" | |
| ../bashunit assert contains 'the rule says another thing' "$OUTPUT" | |
| ../bashunit assert not_contains 'the rule says one thing' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-extension-autoload-dev | |
| composer install | |
| # The rule lives in a directory that is only in autoload-dev and is not analysed, so | |
| # nothing in the dependency graph points at it. Editing it still has to invalidate. | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'the rule says one thing' "$OUTPUT" | |
| patch -p0 < patch.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache not used because extension file' "$OUTPUT" | |
| ../bashunit assert contains 'the rule says another thing' "$OUTPUT" | |
| ../bashunit assert not_contains 'the rule says one thing' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-extension-attribute-services | |
| composer install | |
| # The rule is registered through the attributeServicesDirectories section, not in services:. | |
| # Editing it has to invalidate the cache, editing another class in the same directory does not. | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'the rule says one thing' "$OUTPUT" | |
| patch -p0 < foo.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT" | |
| patch -p0 < rule.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache not used because extension file' "$OUTPUT" | |
| ../bashunit assert contains 'the rule says another thing' "$OUTPUT" | |
| ../bashunit assert not_contains 'the rule says one thing' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-extension-copied-package | |
| composer install | |
| # The rule comes from a path repository Composer copies rather than symlinks, so the | |
| # installed file really is inside vendor/. It is still the project's own code, edited in | |
| # place - unlike a downloaded package, which only ever changes through Composer. | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'the rule says one thing' "$OUTPUT" | |
| patch -p0 < patch.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache not used because extension file' "$OUTPUT" | |
| ../bashunit assert contains 'the rule says another thing' "$OUTPUT" | |
| ../bashunit assert not_contains 'the rule says one thing' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-extension-path-package-update | |
| composer install | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'the rule says one thing' "$OUTPUT" | |
| # Only the package's version moves - the rule file stays byte-identical, so its hash says | |
| # nothing. The package is symlinked, so the rule's class reflects to the source directory | |
| # rather than to anything under vendor/; the package it belongs to has to be recognised | |
| # through the link, or a new version of an extension goes unnoticed. | |
| patch -p0 < updatePackage.patch | |
| composer update test/rule-lib --no-cache | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'register a class in the container; re-analysing everything.' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-collector-sweep | |
| # Two errors: one from an ordinary rule, one from a rule built on collected data. | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'PlainCaller.php:8:Method ResultCacheE2ECollectorSweep\PlainCaller::doPlainCaller() has invalid return type ResultCacheE2ECollectorSweep\Missing.' "$OUTPUT" | |
| ../bashunit assert contains 'Caller.php:7:Call to function resultCacheE2ECollectorHelper() on a separate line has no effect.' "$OUTPUT" | |
| # One new file defines both missing symbols. The files with errors are re-analysed - including | |
| # the file holding the error of the collector-backed rule, which is not in the cached error map. | |
| cp appears/nsfns.php src/nsfns.php | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'has invalid return type' "$OUTPUT" | |
| ../bashunit assert not_contains 'on a separate line has no effect' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-extension-update | |
| composer install | |
| # cold run populates the cache; the shipped rule reports an error, so ignore the exit code | |
| ../../bin/phpstan analyse || true | |
| # Update the package that registers the rule: bump its version and change the rule, then let | |
| # Composer reinstall it. Because the package registers a class in the container, the whole | |
| # cache must be invalidated, not just the files depending on the package. | |
| patch -p1 < updatePackage.patch | |
| composer update test/rule-ext --no-cache | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'and register a class in the container; re-analysing everything.' "$OUTPUT" | |
| ../bashunit assert contains 'Subject.php:5:extension rule version 2' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-extension-installer | |
| composer install | |
| # cold run populates the cache; the shipped rule reports an error, so ignore the exit code | |
| ../../bin/phpstan analyse || true | |
| # Same as result-cache-extension-update, but the rule package registers its extension through | |
| # phpstan/extension-installer (no includes: in the project config), which is the common case. | |
| patch -p1 < updatePackage.patch | |
| composer update test/installer-rule --no-cache | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'and register a class in the container; re-analysing everything.' "$OUTPUT" | |
| ../bashunit assert contains 'Subject.php:5:installer rule version 2' "$OUTPUT" | |
| - script: | | |
| cd e2e/result-cache-ci-notification | |
| # https://github.com/phpstan/phpstan/issues/14881 | |
| # A slow full analysis in CI without a pre-existing result cache should | |
| # hint the user to persist the result cache directory between runs. | |
| # PHPSTAN_E2E_SLOW_ANALYSIS makes bootstrap.php sleep past the | |
| # elapsed-time limit the notification requires. | |
| ../../bin/phpstan clear-result-cache | |
| OUTPUT=$(PHPSTAN_E2E_SLOW_ANALYSIS=1 ../../bin/phpstan analyse 2>&1) | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Persist PHPStan' "$OUTPUT" | |
| ../bashunit assert contains 'to make your pipeline dramatically faster' "$OUTPUT" | |
| # The second run is just as slow, but finds the cache present, | |
| # so the hint is not shown again. | |
| OUTPUT=$(PHPSTAN_E2E_SLOW_ANALYSIS=1 ../../bin/phpstan analyse 2>&1) | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'to make your pipeline dramatically faster' "$OUTPUT" | |
| # A fast run without any result cache does not show the hint either. | |
| ../../bin/phpstan clear-result-cache | |
| OUTPUT=$(../../bin/phpstan analyse 2>&1) | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'to make your pipeline dramatically faster' "$OUTPUT" | |
| - script: | | |
| cd e2e/bug-12606 | |
| export CONFIGTEST=test | |
| ../../bin/phpstan | |
| - script: | | |
| cd e2e/ignore-error-extension | |
| composer install | |
| ../../bin/phpstan | |
| - script: | | |
| cd e2e/bug-10483 | |
| ../../bin/phpstan | |
| - script: | | |
| cd e2e/stub-files-errors | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Method StubFiles\Foo::doFoo() has no return type specified.' "$OUTPUT" | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Method StubFiles\Foo::doFoo() has no return type specified.' "$OUTPUT" | |
| - script: | | |
| cd e2e/bug-15120 | |
| composer install | |
| ../../bin/phpstan analyse -vvv | |
| ../../bin/phpstan analyse -vvv | |
| patch -b stubs/Foo.stub < stub-change.patch | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --error-format=raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'should return non-empty-string but returns string' "$OUTPUT" | |
| mv stubs/Foo.stub.orig stubs/Foo.stub | |
| ../../bin/phpstan analyse -vvv | |
| patch -b stub-files.neon < config-change.patch | |
| OUTPUT=$(../../bin/phpstan analyse -vvv 2>&1) | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Result cache not used because the metadata do not match' "$OUTPUT" | |
| ../bashunit assert contains 'configStubFiles' "$OUTPUT" | |
| mv stub-files.neon.orig stub-files.neon | |
| ../../bin/phpstan analyse -vvv | |
| - script: | | |
| # Restoring the result cache fetches the exported nodes of every changed file, | |
| # and resolving their PHPDocs asks the ReflectionProvider for classes. That | |
| # reaches StubPhpDocProvider, which runs the StubFilesExtensions - all in the | |
| # main process, before the deferred bootstrapFiles have run. | |
| cd e2e/bug-15120b | |
| composer install | |
| ../../bin/phpstan analyse -vvv | |
| # a changed file using a trait - the name scope map reflects the trait | |
| patch -b src/Consumer.php < source-change.patch | |
| OUTPUT=$(../../bin/phpstan analyse -vvv 2>&1 || true) | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'Undefined constant' "$OUTPUT" | |
| ../bashunit assert contains 'No errors' "$OUTPUT" | |
| mv src/Consumer.php.orig src/Consumer.php | |
| ../../bin/phpstan analyse -vvv | |
| # a changed file with a generic @template bound - resolving it reflects the | |
| # bound's class to learn whether it is generic | |
| patch -b src/Repository.php < generic-change.patch | |
| OUTPUT=$(../../bin/phpstan analyse -vvv 2>&1 || true) | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'Undefined constant' "$OUTPUT" | |
| ../bashunit assert contains 'No errors' "$OUTPUT" | |
| mv src/Repository.php.orig src/Repository.php | |
| ../../bin/phpstan analyse -vvv | |
| # a changed file with a non-private property - exporting it asks whether the | |
| # property is virtual | |
| patch -b src/Entity.php < property-change.patch | |
| OUTPUT=$(../../bin/phpstan analyse -vvv 2>&1 || true) | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'Undefined constant' "$OUTPUT" | |
| ../bashunit assert contains 'No errors' "$OUTPUT" | |
| mv src/Entity.php.orig src/Entity.php | |
| ../../bin/phpstan analyse -vvv | |
| - script: | | |
| cd e2e/nested-trait-use | |
| ../../bin/phpstan analyze | |
| - script: | | |
| cd e2e/report-ignores-without-comments | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan --error-format=raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'test.php:4:Ignore with identifier variable.undefined has no comment.' "$OUTPUT" | |
| ../bashunit assert contains 'test.php:4:Variable $undefined might not be defined.' "$OUTPUT" | |
| ../bashunit assert not_contains 'test.php:7' "$OUTPUT" | |
| ../bashunit assert contains 'test.php:10:Ignoring all errors on a line is not allowed.' "$OUTPUT" | |
| ../bashunit assert contains 'test.php:10:Variable $yetAnotherUndefined might not be defined.' "$OUTPUT" | |
| ../bashunit assert contains 'test.php:12:Ignoring all errors on a line is not allowed.' "$OUTPUT" | |
| ../bashunit assert contains 'test.php:12:Variable $yetYetAnotherUndefined might not be defined.' "$OUTPUT" | |
| - script: | | |
| # The reflection cache for PHP built-in symbols stores absolute paths to the | |
| # PhpStorm stub files, but its cache key contains only package versions. The | |
| # cache in sys_get_temp_dir() survives moving the PHPStan installation (for | |
| # example when the project with PHPStan in vendor/ is moved or the CI | |
| # workspace path changes), so the next run must not crash importing | |
| # reflections that point to the previous installation path. | |
| cp -R . /tmp/phpstan-install-1 | |
| cd e2e/moved-phpstan-installation | |
| /tmp/phpstan-install-1/bin/phpstan analyse -l 8 -vvv test.php | |
| mv /tmp/phpstan-install-1 /tmp/phpstan-install-2 | |
| /tmp/phpstan-install-2/bin/phpstan analyse -l 8 -vvv test.php | |
| - script: | | |
| # PHPStan bundles symfony/polyfill-php85 for its own runtime, so array_first() | |
| # and other PHP 8.5 symbols are declared in the PHPStan process. A project | |
| # analysed with PHP 8.4 must not see them | |
| # (https://github.com/phpstan/phpstan/issues/15150). | |
| cd e2e/bug-15150 | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --error-format=raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'test.php:11:Function array_first not found.' "$OUTPUT" | |
| ../bashunit assert contains 'test.php:12:Function array_last not found.' "$OUTPUT" | |
| ../bashunit assert contains 'test.php:21:Instantiated class Filter\FilterException not found.' "$OUTPUT" | |
| # array_find() exists in the analysed PHP version, PhpStorm stubs describe it | |
| ../bashunit assert not_contains 'array_find' "$OUTPUT" | |
| steps: | |
| - name: Harden the runner (Audit all outbound calls) | |
| uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 | |
| with: | |
| egress-policy: audit | |
| - name: "Checkout" | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: "Install PHP" | |
| uses: "shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962" # 2.40.0 | |
| with: | |
| coverage: "none" | |
| php-version: "8.2" | |
| extensions: mbstring | |
| ini-values: memory_limit=256M | |
| - uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0 | |
| - name: "Patch PHPStan" | |
| run: "patch src/Analyser/Error.php < e2e/PHPStanErrorPatch.patch" | |
| - name: "Install bashunit" | |
| uses: "TypedDevs/bashunit@8d4f357f52df7596f92ece2d6c049d91d45517a2" # 0.52.0 | |
| with: | |
| directory: "e2e" | |
| - name: "Test" | |
| run: "${{ matrix.script }}" | |
| result-cache-e2e-tests-windows: | |
| name: "Result cache E2E tests on Windows (${{ matrix.name }})" | |
| # https://github.com/phpstan/phpstan/issues/15152: since 2.2.9 the result cache stores its | |
| # paths relative to the PHPStan install, and on Windows it is reported to be discarded on | |
| # every run ("metadata do not match: ... executedFilesHashes"). Each scenario analyses an | |
| # unchanged project twice and asserts the warm run restores the cache. | |
| runs-on: windows-latest | |
| timeout-minutes: 30 | |
| defaults: | |
| run: | |
| shell: bash | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - name: "plain project" | |
| script: | | |
| cd e2e/result-cache-1 | |
| echo -n > phpstan-baseline.neon | |
| php ../../bin/phpstan analyse --no-ansi | |
| php ../../bin/phpstan analyse -vv --no-ansi 2>&1 | tee output.txt | |
| grep -F "Result cache restored. 0 files will be reanalysed." output.txt | |
| - name: "excluded files hashed as scannedFiles" | |
| script: | | |
| cd e2e/result-cache-scanned | |
| php ../../bin/phpstan analyse --no-ansi | |
| php ../../bin/phpstan analyse -vv --no-ansi 2>&1 | tee output.txt | |
| grep -F "Result cache restored. 0 files will be reanalysed." output.txt | |
| - name: "phar bootstrap file" | |
| script: | | |
| cd e2e/result-cache-phar-bootstrap | |
| # PHPStan registers its own runtime stubs as bootstrapFiles under their phar:// URL | |
| # in a phar install; this reproduces that without a compiled phpstan.phar. | |
| php -d phar.readonly=0 build-boot-phar.php | |
| php ../../bin/phpstan analyse --no-ansi | |
| if grep -oE "'[^']*boot\.phar/boot\.php'" tmp/resultCache.php | grep -v 'phar://'; then | |
| echo 'bootstrap path stored without its phar:// scheme'; exit 1 | |
| fi | |
| php ../../bin/phpstan analyse -vv --no-ansi 2>&1 | tee output.txt | |
| grep -F "Result cache restored. 0 files will be reanalysed." output.txt | |
| - name: "relative paths" | |
| script: | | |
| cd e2e/result-cache-relative-path | |
| php ../../bin/phpstan analyse --no-ansi | |
| # The cache stores forward slashes, so the checkout prefix to look for is the | |
| # D:/a/... spelling of the workspace. | |
| PREFIX="$(cygpath -m "$GITHUB_WORKSPACE")/" | |
| if grep -qF "$PREFIX" tmp/resultCache.php; then | |
| echo "cache still holds absolute paths under the checkout ($PREFIX):" | |
| grep -nF "$PREFIX" tmp/resultCache.php | head -20 | |
| exit 1 | |
| fi | |
| php ../../bin/phpstan analyse -vv --no-ansi 2>&1 | tee output.txt | |
| grep -F "Result cache restored. 0 files will be reanalysed." output.txt | |
| - name: "paths through the currentWorkingDirectory placeholder" | |
| script: | | |
| cd e2e/result-cache-placeholder-paths | |
| # paths, scanFiles and bootstrapFiles written with %currentWorkingDirectory% - the | |
| # three meta keys the issue reports as differing (analysedPaths, scannedFiles, | |
| # executedFilesHashes). Values carrying a placeholder skip the path normalisation | |
| # the Neon loader applies to plain relative paths. | |
| php ../../bin/phpstan analyse --no-ansi | |
| php ../../bin/phpstan analyse -vv --no-ansi 2>&1 | tee output.txt | |
| grep -F "Result cache restored. 0 files will be reanalysed." output.txt | |
| steps: | |
| - name: "Checkout" | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: "Install PHP" | |
| uses: "shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962" # 2.40.0 | |
| with: | |
| coverage: "none" | |
| php-version: "8.2" | |
| extensions: mbstring | |
| ini-values: memory_limit=256M | |
| - uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0 | |
| - name: "Test" | |
| run: "${{ matrix.script }}" | |
| e2e-tests: | |
| name: "E2E tests" | |
| runs-on: "ubuntu-latest" | |
| timeout-minutes: 60 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - script: "bin/phpstan analyse -l 8 -a tests/e2e/data/timecop.php -c tests/e2e/data/empty.neon tests/e2e/data/timecop.php" | |
| tools: "pecl" | |
| extensions: "timecop-beta" | |
| - script: "bin/phpstan analyse -l 8 -a tests/e2e/data/soap.php -c tests/e2e/data/empty.neon tests/e2e/data/soap.php" | |
| extensions: "soap" | |
| - script: "bin/phpstan analyse -l 8 -a tests/e2e/data/soap.php -c tests/e2e/data/empty.neon tests/e2e/data/soap.php" | |
| extensions: "" | |
| - script: "bin/phpstan analyse -l 8 -c tests/e2e/data/empty.neon tests/e2e/data/curl_setopt.php" | |
| extensions: ":curl" | |
| - script: "bin/phpstan analyse -l 8 tests/e2e/anon-class/Granularity.php" | |
| extensions: "" | |
| - script: | | |
| cd e2e/bug-11359 | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --no-progress --error-format raw --level=8 bug-11359.php") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'e2e/bug-11359/bug-11359.php:3:Function mb_ucfirst not found.' "$OUTPUT" | |
| php-version: "8.2" | |
| extensions: mbstring | |
| - script: | | |
| cd e2e/bug-11359 | |
| OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse --no-progress --error-format raw --level=8 bug-11359.php") | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'Function mb_ucfirst not found.' "$OUTPUT" | |
| php-version: "8.4" | |
| extensions: mbstring | |
| - script: "bin/phpstan analyse -l 8 e2e/phpstan-phpunit-190/test.php -c e2e/phpstan-phpunit-190/test.neon" | |
| extensions: "" | |
| - script: "bin/phpstan analyse e2e/only-files-not-analysed-trait/src -c e2e/only-files-not-analysed-trait/ignore.neon" | |
| extensions: "" | |
| - script: "bin/phpstan analyse e2e/only-files-not-analysed-trait/src/Foo.php e2e/only-files-not-analysed-trait/src/BarTrait.php -c e2e/only-files-not-analysed-trait/no-ignore.neon" | |
| extensions: "" | |
| - script: | | |
| cd e2e/baseline-uninit-prop-trait | |
| ../../bin/phpstan analyse --debug --configuration test-no-baseline.neon --generate-baseline test-baseline.neon | |
| ../../bin/phpstan analyse --debug --configuration test.neon | |
| - script: | | |
| cd e2e/baseline-uninit-prop-trait | |
| ../../bin/phpstan analyse --configuration test-no-baseline.neon --generate-baseline test-baseline.neon | |
| ../../bin/phpstan analyse --configuration test.neon | |
| - script: | | |
| cd e2e/discussion-11362 | |
| composer install | |
| ../../bin/phpstan | |
| - script: | | |
| cd e2e/bug-11819 | |
| ../../bin/phpstan | |
| - script: | | |
| cd e2e/bug-14305 | |
| ../../bin/phpstan | |
| - script: | | |
| cd e2e/composer-and-phpstan-version-config | |
| composer install --ignore-platform-reqs | |
| ../../bin/phpstan analyze test.php --level=0 | |
| - script: | | |
| cd e2e/composer-max-version | |
| composer install | |
| ../../bin/phpstan analyze test.php --level=0 | |
| - script: | | |
| cd e2e/composer-min-max-version | |
| composer install | |
| ../../bin/phpstan analyze test.php --level=0 | |
| - script: | | |
| cd e2e/composer-min-open-end-version | |
| composer install | |
| ../../bin/phpstan analyze test.php --level=0 | |
| - script: | | |
| cd e2e/composer-min-version-v5 | |
| composer install --ignore-platform-reqs | |
| ../../bin/phpstan analyze test.php --level=0 | |
| - script: | | |
| cd e2e/composer-min-version-v7 | |
| composer install --ignore-platform-reqs | |
| ../../bin/phpstan analyze test.php --level=0 | |
| - script: | | |
| cd e2e/composer-min-version | |
| composer install | |
| ../../bin/phpstan analyze test.php --level=0 | |
| - script: | | |
| cd e2e/composer-no-versions | |
| composer install | |
| ../../bin/phpstan analyze test.php --level=0 | |
| - script: | | |
| # Scope::getPhpVersion() has to honour the composer.json "require.php" range: | |
| # https://github.com/phpstan/phpstan/issues/15270 | |
| cd e2e/composer-php-version-range-7-and-8 | |
| composer install | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --no-progress --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains '/errors.php:11:Non-capturing catch is supported only on PHP 8.0 and later.' "$OUTPUT" | |
| ../bashunit assert contains '/errors.php:23:Named arguments are supported only on PHP 8.0 and later.' "$OUTPUT" | |
| ../bashunit assert contains '/errors.php:29:Function ComposerPhpVersionRange7And8Errors\phpdocBoolReturn() never returns false so the return type can be changed to true.' "$OUTPUT" | |
| ../bashunit assert not_contains 'no-errors.php' "$OUTPUT" | |
| ../bashunit assert not_contains 'php-version-id.php' "$OUTPUT" | |
| - script: | | |
| cd e2e/composer-php-version-range-7 | |
| composer install --ignore-platform-reqs | |
| ../../bin/phpstan analyse --no-progress | |
| - script: | | |
| cd e2e/composer-php-version-range-8-0 | |
| composer install | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --no-progress --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains '/errors.php:16:Missing parameter $j (int) in call to function ComposerPhpVersionRange80Errors\acceptsThreeInts.' "$OUTPUT" | |
| ../bashunit assert contains '/errors.php:16:Unpacked argument (...) cannot be followed by a non-unpacked argument.' "$OUTPUT" | |
| ../bashunit assert not_contains 'no-errors.php' "$OUTPUT" | |
| - script: | | |
| cd e2e/composer-php-version-range-8-5 | |
| composer install --ignore-platform-reqs | |
| ../../bin/phpstan analyse --no-progress | |
| - script: | | |
| cd e2e/composer-version-config-invalid | |
| OUTPUT=$(../bashunit assert exit_code "1" ../../bin/phpstan) | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Invalid configuration' "$OUTPUT" | |
| ../bashunit assert contains 'Invalid PHP version range: phpVersion.max should be greater or equal to phpVersion.min.' "$OUTPUT" | |
| - script: | | |
| cd e2e/composer-version-config-patch | |
| composer install --ignore-platform-reqs | |
| ../../bin/phpstan analyze test.php --level=0 | |
| - script: | | |
| cd e2e/composer-version-config | |
| composer install | |
| ../../bin/phpstan analyze test.php --level=0 | |
| - script: | | |
| cd e2e/bug13425 | |
| timeout 15 ../bashunit assert exit_code "1" "../../bin/phpstan analyze src/ plugins/" | |
| - script: | | |
| cd e2e/bug-14036 | |
| composer install | |
| ../../bin/phpstan analyze | |
| - script: | | |
| cd e2e/vvv-with-debug | |
| OUTPUT=$(../../bin/phpstan analyze -l 0 -vvv --debug test.php 2>&1) | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'Parallel processing scheduler' "$OUTPUT" | |
| - script: | | |
| cd e2e/vvv-with-debug | |
| OUTPUT=$(../../bin/phpstan analyze -l 0 -vvv --debug --configuration parallel.neon test.php 2>&1) | |
| echo "$OUTPUT" | |
| ../bashunit assert not_contains 'Parallel processing scheduler' "$OUTPUT" | |
| - script: | | |
| cd e2e/vvv-with-debug | |
| OUTPUT=$(../../bin/phpstan analyze -l 0 -vvv test.php 2>&1) | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Parallel processing scheduler' "$OUTPUT" | |
| - script: | | |
| cd e2e/bug-14093 | |
| # Use a relative sys_temp_dir so the value starts with a letter (not /), just like Windows | |
| # paths starting with a drive letter. If the sys_temp_dir value isn't correctly quoted, | |
| # PHP evaluates ~ as a bitwise NOT rather than a literal character, truncating the path. | |
| mkdir tmp~1 | |
| php -d "sys_temp_dir='tmp~1'" ../../bin/phpstan analyze | |
| if [ -d tmp ]; then | |
| echo "FAIL: tmp was created by a worker, meaning sys_temp_dir='tmp~1' was incorrectly evaluated to 'tmp'" | |
| exit 1 | |
| fi | |
| - script: | | |
| # A glibc system can carry a musl loader without being a musl system: | |
| # musl-tools, a musl cross-compilation sysroot, or anything else built | |
| # against musl installs /lib/ld-musl-<arch>.so.1 as well. PHPStan must | |
| # still pick the glibc turbo build there - picking the musl one makes | |
| # PHP refuse to load the extension at startup: "Unable to load dynamic | |
| # library '.../linux-musl-x86_64/phpstan_turbo-8.5.so' | |
| # (libc.musl-x86_64.so.1: cannot open shared object file)". | |
| sudo touch /lib/ld-musl-x86_64.so.1 | |
| OUTPUT=$(bin/phpstan diagnose -l 0 -c tests/e2e/data/empty.neon) | |
| echo "$OUTPUT" | |
| e2e/bashunit assert contains 'Turbo platform: linux-gnu-' "$OUTPUT" | |
| - script: | | |
| # The mirror image of the leg above: an actual musl system must keep | |
| # picking the musl build - both on Alpine, and (since /etc/alpine-release | |
| # is not what makes a system musl) with that file taken away. The images | |
| # ship no php.ini, so memory_limit would default to 128M, which building | |
| # the DI container already exceeds. | |
| DIAGNOSE='php -d memory_limit=-1 bin/phpstan diagnose -l 0 -c tests/e2e/data/empty.neon' | |
| OUTPUT=$(docker run --rm -v "$PWD:/repo" -w /repo php:8.2-cli-alpine sh -c "$DIAGNOSE") | |
| echo "$OUTPUT" | |
| e2e/bashunit assert contains 'Turbo platform: linux-musl-' "$OUTPUT" | |
| OUTPUT=$(docker run --rm -v "$PWD:/repo" -w /repo php:8.2-cli-alpine sh -c "rm /etc/alpine-release && $DIAGNOSE") | |
| echo "$OUTPUT" | |
| e2e/bashunit assert contains 'Turbo platform: linux-musl-' "$OUTPUT" | |
| - script: | | |
| # A parallel worker killed by a signal writes nothing of its own - | |
| # no PHP error, no shutdown function, an empty capture file. The | |
| # main process has to name the signal; treating the dead worker | |
| # like one that quit cleanly leaves the whole run with nothing but | |
| # "Some parallel worker jobs have not finished", which is what made | |
| # https://github.com/phpstan/phpstan/issues/15131 undiagnosable. | |
| cd e2e/parallel-worker-signal | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --no-progress") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'killed by signal 9 (SIGKILL)' "$OUTPUT" | |
| - script: | | |
| # config.platform selects the ext-ds 1 stubs: Ds\Vector exists, Ds\Seq does not. | |
| cd e2e/ext-ds-version | |
| cp composer-json/v1-platform.json composer.json | |
| composer install --ignore-platform-reqs | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --no-progress --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:12:Instantiated class Ds\Seq not found.' "$OUTPUT" | |
| ../bashunit assert not_contains 'Ds\Vector' "$OUTPUT" | |
| ../bashunit assert not_contains 'Bar.php' "$OUTPUT" | |
| - script: | | |
| # config.platform selects the ext-ds 2 stubs: Ds\Seq exists, Ds\Vector and Ds\Pair::copy() do not. | |
| cd e2e/ext-ds-version | |
| cp composer-json/v2-platform.json composer.json | |
| composer install --ignore-platform-reqs | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --no-progress --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:11:Instantiated class Ds\Vector not found.' "$OUTPUT" | |
| ../bashunit assert contains 'Bar.php:15:Call to an undefined method Ds\Pair<string, int>::copy().' "$OUTPUT" | |
| ../bashunit assert not_contains 'Ds\Seq' "$OUTPUT" | |
| - script: | | |
| # Without a config.platform entry, the major version of the required ext-ds is selected. | |
| cd e2e/ext-ds-version | |
| cp composer-json/v2-require.json composer.json | |
| composer install --ignore-platform-reqs | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --no-progress --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:11:Instantiated class Ds\Vector not found.' "$OUTPUT" | |
| ../bashunit assert contains 'Bar.php:15:Call to an undefined method Ds\Pair<string, int>::copy().' "$OUTPUT" | |
| ../bashunit assert not_contains 'Ds\Seq' "$OUTPUT" | |
| - script: | | |
| # A constraint whose alternatives all stay within one major version (^2.0 || ^2.1) selects that version. | |
| cd e2e/ext-ds-version | |
| cp composer-json/v2-same-major-or-require.json composer.json | |
| composer install --ignore-platform-reqs | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --no-progress --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:11:Instantiated class Ds\Vector not found.' "$OUTPUT" | |
| ../bashunit assert contains 'Bar.php:15:Call to an undefined method Ds\Pair<string, int>::copy().' "$OUTPUT" | |
| ../bashunit assert not_contains 'Ds\Seq' "$OUTPUT" | |
| - script: | | |
| # config.platform names ext-ds 3, which has no stubs, so the required ^2.0 decides instead. | |
| cd e2e/ext-ds-version | |
| cp composer-json/v2-unknown-platform.json composer.json | |
| composer install --ignore-platform-reqs | |
| OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse --no-progress --error-format raw") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Foo.php:11:Instantiated class Ds\Vector not found.' "$OUTPUT" | |
| ../bashunit assert contains 'Bar.php:15:Call to an undefined method Ds\Pair<string, int>::copy().' "$OUTPUT" | |
| ../bashunit assert not_contains 'Ds\Seq' "$OUTPUT" | |
| - script: | | |
| cd e2e/bug-15407 | |
| ../../bin/phpstan analyse --no-progress -vvv 2>&1 | tee -a output.txt | |
| ../bashunit assert file_contains output.txt "Restarting process: pcov (pcov.enabled=1)" | |
| coverage: "pcov" | |
| - script: | | |
| cd e2e/bug-15407 | |
| ../../bin/phpstan analyse --no-progress -vvv 2>&1 | tee -a output.txt | |
| ../bashunit assert file_contains output.txt "Restarting process: pcov (pcov.enabled=1)" | |
| coverage: "pcov" | |
| ini-values: "pcov.enabled=1" | |
| - script: | | |
| cd e2e/bug-15407 | |
| # the restart may still happen to activate OPcache, just not for pcov | |
| ../../bin/phpstan analyse --no-progress -vvv 2>&1 | tee -a output.txt | |
| ../bashunit assert file_not_contains output.txt "pcov (pcov.enabled" | |
| coverage: "pcov" | |
| ini-values: "pcov.enabled=0" | |
| steps: | |
| - name: Harden the runner (Audit all outbound calls) | |
| uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 | |
| with: | |
| egress-policy: audit | |
| - name: "Checkout" | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: "Install PHP" | |
| uses: "shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962" # 2.40.0 | |
| with: | |
| coverage: ${{ matrix.coverage || 'none' }} | |
| ini-values: ${{ matrix.ini-values || '' }} | |
| php-version: ${{ matrix.php-version || '8.2' }} | |
| tools: ${{ matrix.tools }} | |
| extensions: ${{ matrix.extensions }} | |
| - uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0 | |
| - name: "Install bashunit" | |
| uses: "TypedDevs/bashunit@8d4f357f52df7596f92ece2d6c049d91d45517a2" # 0.52.0 | |
| with: | |
| directory: "e2e" | |
| - name: "Test" | |
| run: ${{ matrix.script }} | |
| turbo-arena-e2e-tests: | |
| name: "Turbo arena E2E tests" | |
| runs-on: "ubuntu-latest" | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Harden the runner (Audit all outbound calls) | |
| uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 | |
| with: | |
| egress-policy: audit | |
| - name: "Checkout" | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| # the extension bakes its version from `git log -- turbo-ext/src`, and | |
| # TurboExtensionEnabler activates only the expected version - a shallow | |
| # clone hands us a "dev" build, which would skip the whole test | |
| fetch-depth: 0 | |
| - name: "Install PHP" | |
| uses: "shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962" # 2.40.0 | |
| with: | |
| coverage: "none" | |
| php-version: "8.5" | |
| - uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0 | |
| - name: "Install bashunit" | |
| uses: "TypedDevs/bashunit@8d4f357f52df7596f92ece2d6c049d91d45517a2" # 0.52.0 | |
| with: | |
| directory: "e2e" | |
| - name: "Build the turbo extension" | |
| run: make build-turbo | |
| - name: "Load the turbo extension through php.ini" | |
| # the restart re-executes the process with the loaded ini files to | |
| # activate OPcache; an extension passed as -d would not survive that, | |
| # one from the ini scan dir is loaded again | |
| run: | | |
| echo "extension=$PWD/turbo-ext/phpstan_turbo.so" | sudo tee "$(php -r 'echo PHP_CONFIG_FILE_SCAN_DIR;')/99-phpstan-turbo.ini" | |
| php -r 'exit(extension_loaded("phpstan_turbo") ? 0 : 1);' | |
| - name: "Verify the arena is actually exercised" | |
| # Without an active extension ArenaCache is the no-op PHP twin and no | |
| # shared memory exists at all; without fork the workers do not publish in | |
| # lockstep. Either would make the test below pass for the wrong reason. | |
| run: | | |
| OUTPUT=$(php bin/phpstan diagnose -c e2e/turbo-arena/phpstan.neon) | |
| echo "$OUTPUT" | |
| e2e/bashunit assert contains 'Turbo extension: enabled' "$OUTPUT" | |
| e2e/bashunit assert contains 'Mechanism: fork (pcntl_fork)' "$OUTPUT" | |
| - name: "Verify PHPSTAN_TURBO=0 does not disable the extension" | |
| # The env var was an escape hatch that users kept in their scripts long | |
| # after whatever it worked around got fixed. A loaded, version-matching | |
| # extension is now always used, so the variable has to be inert - | |
| # https://github.com/phpstan/phpstan/issues/15143 | |
| run: | | |
| OUTPUT=$(PHPSTAN_TURBO=0 php bin/phpstan diagnose -c e2e/turbo-arena/phpstan.neon) | |
| echo "$OUTPUT" | |
| e2e/bashunit assert contains 'Turbo extension: enabled' "$OUTPUT" | |
| - name: "Verify conditional types leave interned types equal to a resolved subject alone" | |
| # With the extension active TypeCombinator's results are interned, so equal | |
| # types share one instance. ConditionalType used to narrow the types in its | |
| # branches identical to the subject even once the subject had resolved to a | |
| # concrete type, and interning made that hit unrelated types - the `mixed` | |
| # value type of an `array` branch for a `mixed` subject - | |
| # https://github.com/phpstan/phpstan/issues/15151 | |
| run: | | |
| cd e2e/bug-15151 | |
| OUTPUT=$(php ../../bin/phpstan analyse --no-progress --error-format=raw 2>&1 || true) | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'reception_formulaire.php:20:Dumped type: mixed' "$OUTPUT" | |
| ../bashunit assert contains 'webservice.php:25:Dumped type: mixed' "$OUTPUT" | |
| ../bashunit assert not_contains 'Dumped type: array' "$OUTPUT" | |
| # the two dumps are the only errors: count the file:line:message lines | |
| ../bashunit assert equals '2' "$(echo "$OUTPUT" | grep -Ec '^/.+:[0-9]+:')" | |
| - name: "Verify conditional types leave memoized types equal to the argument alone" | |
| # With the extension active TypeCombinator's results are memoized, so every | |
| # `int|false` resolved from a PHPDoc is the same object. ConditionalType | |
| # narrowed the types in its branches identical to the argument, which made | |
| # `($priority is int ? bool : int|false)` lose the `int` for an `int|false` | |
| # argument - https://github.com/phpstan/phpstan/issues/15268 | |
| run: | | |
| cd e2e/bug-15268 | |
| php ../../bin/phpstan analyse --no-progress --error-format=raw | |
| - name: "Verify name scopes built from the simplified AST are not reused for an analysed file" | |
| # A run on other paths builds the name scopes of the scanned file from its | |
| # simplified AST, without the anonymous class declared in a function body. | |
| # A later full run must not reuse them, even with the result cache cleared - | |
| # https://github.com/phpstan/phpstan/issues/15475 | |
| run: | | |
| cd e2e/bug-15475 | |
| php ../../bin/phpstan analyse --no-progress src/usage.php | |
| php ../../bin/phpstan clear-result-cache | |
| php ../../bin/phpstan analyse --no-progress | |
| - name: "Test" | |
| # The arena is a per-run POSIX shared-memory object: shm_open() plus an | |
| # ftruncate() to 256 MB that tmpfs honours sparsely, committing pages on | |
| # first touch. When the backing store cannot commit one, the worker that | |
| # touches it takes SIGBUS and dies where it stands. 64 MB is what docker | |
| # gives /dev/shm by default, which is how | |
| # https://github.com/phpstan/phpstan/issues/15131 reached users; 32 MB | |
| # makes it deterministic on a 4-core runner. Running out of shared memory | |
| # must cost sharing, never results - the analysis has to come out exactly | |
| # as it does with the runner's normal /dev/shm. | |
| run: | | |
| cd e2e/turbo-arena | |
| rm -rf tmp | |
| php ../../bin/phpstan analyse --no-progress > "$RUNNER_TEMP/reference.txt" 2>&1 || true | |
| tail -5 "$RUNNER_TEMP/reference.txt" | |
| # a warm disk cache is the worst case: every worker loads and publishes | |
| # the same entries at the same moment, so duplicates pile up fastest | |
| php ../../bin/phpstan clear-result-cache | |
| sudo mount -o remount,size=32M /dev/shm | |
| df -h /dev/shm | |
| php ../../bin/phpstan analyse --no-progress > "$RUNNER_TEMP/small-shm.txt" 2>&1 || true | |
| tail -40 "$RUNNER_TEMP/small-shm.txt" | |
| # bashunit takes the haystack as a single argv entry, and execve caps | |
| # one argument at 128 KB - a few hundred KB of analysis output only | |
| # fits through the assertion once grep has narrowed it down | |
| ../bashunit assert not_contains 'parallel worker' "$(grep -F 'parallel worker' "$RUNNER_TEMP/small-shm.txt" || true)" | |
| diff "$RUNNER_TEMP/reference.txt" "$RUNNER_TEMP/small-shm.txt" | |
| turbo-fork-unsafe-extension-e2e-tests: | |
| name: "Turbo fork-unsafe extension E2E tests" | |
| runs-on: "ubuntu-latest" | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Harden the runner (Audit all outbound calls) | |
| uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 | |
| with: | |
| egress-policy: audit | |
| - name: "Checkout" | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| # the extension bakes its version from `git log -- turbo-ext/src`, and | |
| # TurboExtensionEnabler activates only the expected version - a shallow | |
| # clone hands us a "dev" build, which would skip the whole test | |
| fetch-depth: 0 | |
| - name: "Install PHP" | |
| uses: "shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962" # 2.40.0 | |
| with: | |
| coverage: "none" | |
| php-version: "8.5" | |
| - uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0 | |
| - name: "Install bashunit" | |
| uses: "TypedDevs/bashunit@8d4f357f52df7596f92ece2d6c049d91d45517a2" # 0.52.0 | |
| with: | |
| directory: "e2e" | |
| - name: "Build the turbo extension" | |
| run: make build-turbo | |
| - name: "Build the fork-unsafe extension" | |
| # a model of ext-grpc without grpc.enable_fork_support: its module | |
| # shutdown waits for a thread that a forked child never has. phpize | |
| # builds in place, so a copy keeps the build tree out of the checkout. | |
| run: | | |
| cp -r e2e/fork-unsafe-extension/ext "$RUNNER_TEMP/forkunsafe" | |
| cd "$RUNNER_TEMP/forkunsafe" | |
| phpize | |
| ./configure | |
| make -j"$(nproc)" | |
| - name: "Load both extensions through php.ini" | |
| # the restart re-executes the process with the loaded ini files to | |
| # activate OPcache; extensions passed as -d would not survive that, | |
| # ones from the ini scan dir are loaded again | |
| run: | | |
| printf 'extension=%s\nextension=%s\n' "$PWD/turbo-ext/phpstan_turbo.so" "$RUNNER_TEMP/forkunsafe/modules/forkunsafe.so" | sudo tee "$(php -r 'echo PHP_CONFIG_FILE_SCAN_DIR;')/99-phpstan-turbo.ini" | |
| php -r 'exit(extension_loaded("phpstan_turbo") && extension_loaded("forkunsafe") ? 0 : 1);' | |
| - name: "Verify the extension wedges a forked child on its own" | |
| # a plain exit() in a forked child must hang, or the test below would | |
| # pass for the wrong reason | |
| run: | | |
| set +e | |
| timeout 10 php e2e/fork-unsafe-extension/fork-and-exit.php | |
| CODE=$? | |
| set -e | |
| e2e/bashunit assert equals "124" "$CODE" | |
| - name: "Verify fork mode is actually used" | |
| run: | | |
| OUTPUT=$(php bin/phpstan diagnose -c e2e/fork-unsafe-extension/phpstan.neon) | |
| echo "$OUTPUT" | |
| e2e/bashunit assert contains 'Turbo extension: enabled' "$OUTPUT" | |
| e2e/bashunit assert contains 'Mechanism: fork (pcntl_fork)' "$OUTPUT" | |
| - name: "Test" | |
| # https://github.com/phpstan/phpstan/issues/15138 - the forked worker | |
| # delivers its result (100%) and then hangs in the inherited extension's | |
| # module shutdown; the run must finish instead | |
| run: | | |
| cd e2e/fork-unsafe-extension | |
| rm -rf tmp | |
| OUTPUT=$(../bashunit assert exit_code "0" "timeout 120 php ../../bin/phpstan analyse --no-progress -vvv") | |
| echo "$OUTPUT" | |
| ../bashunit assert contains 'Mechanism: fork (pcntl_fork)' "$OUTPUT" | |
| ../bashunit assert contains '[OK] No errors' "$OUTPUT" | |
| process-restart-e2e-tests: | |
| name: "Process restart E2E tests" | |
| runs-on: ${{ matrix.operating-system }} | |
| timeout-minutes: 30 | |
| defaults: | |
| run: | |
| shell: bash | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # pcov armed: one restart, every process - the forked worker | |
| # included, which no per-worker `-d` injection could reach - runs | |
| # disarmed. pcov stays loaded: the rows disarm rather than unload, | |
| # which keeps the extension's own symbols reflectable. setup-php | |
| # loads OPcache dormant on CLI, so the same restart activates it in | |
| # every Linux row | |
| - operating-system: "ubuntu-latest" | |
| coverage: "pcov" | |
| extensions: "" | |
| script: | | |
| php ../../bin/phpstan analyse --no-progress | |
| php check.php restarted-for=pcov,opcache pcov-armed=no pcov-loaded=yes opcache-cli-on=yes processes=2+ starts=2 | |
| # Windows has no pcntl_exec, so the restart runs as a child process | |
| # whose exit code is passed on. OPcache is left alone there: the | |
| # workers are spawned, and ProcessHelper activates it for each | |
| - operating-system: "windows-latest" | |
| coverage: "pcov" | |
| extensions: "" | |
| script: | | |
| php ../../bin/phpstan analyse --no-progress | |
| php check.php restarted-for=pcov pcov-armed=no processes=1+ starts=2 | |
| set +e | |
| php ../../bin/phpstan analyse --no-progress --configuration failing.neon | |
| EXIT_CODE=$? | |
| set -e | |
| if [ "$EXIT_CODE" != "1" ]; then | |
| echo "FAIL: the restarted process' exit code was not passed on, got $EXIT_CODE" | |
| exit 1 | |
| fi | |
| # xdebug: one restart disarms it via xdebug.mode=off, and it stays | |
| # loaded so xdebug_*() remains reflectable. No note about it being | |
| # active either | |
| - operating-system: "ubuntu-latest" | |
| coverage: "xdebug" | |
| extensions: "" | |
| script: | | |
| php ../../bin/phpstan analyse --no-progress 2>&1 | tee out.txt | |
| if grep -q 'The Xdebug PHP extension is active, but' out.txt; then | |
| echo "FAIL: xdebug survived the restart armed" | |
| exit 1 | |
| fi | |
| php check.php restarted-for=xdebug,opcache xdebug-armed=no xdebug-loaded=yes processes=2+ starts=2 | |
| # XDEBUG_MODE overrides xdebug.mode, so flagging the ini alone would | |
| # be silently overridable - the row exports the env var too | |
| - operating-system: "ubuntu-latest" | |
| coverage: "xdebug" | |
| extensions: "" | |
| script: | | |
| XDEBUG_MODE=develop,debug php ../../bin/phpstan analyse --no-progress | |
| php check.php restarted-for=xdebug,opcache xdebug-armed=no xdebug-loaded=yes processes=2+ starts=2 | |
| # --xdebug keeps xdebug armed and does not restart for it - only for | |
| # OPcache | |
| - operating-system: "ubuntu-latest" | |
| coverage: "xdebug" | |
| extensions: "" | |
| script: | | |
| php ../../bin/phpstan analyse --no-progress --xdebug 2>&1 | tee out.txt | |
| if ! grep -q 'enabled, and the Xdebug PHP extension is active' out.txt; then | |
| echo "FAIL: --xdebug did not keep xdebug" | |
| exit 1 | |
| fi | |
| php check.php restarted-for=opcache xdebug-armed=yes processes=1+ starts=2 | |
| # xdebug and pcov together: one restart handles both. Both come in | |
| # through `extensions` because setup-php's `coverage: xdebug` unloads | |
| # pcov. xdebug is armed by its default mode, pcov on the command | |
| # line - which also proves the composed ini carries runtime `-d` | |
| # overrides over without reconstructing them from $argv | |
| - operating-system: "ubuntu-latest" | |
| coverage: "" | |
| extensions: "xdebug, pcov" | |
| script: | | |
| php -d pcov.enabled=1 ../../bin/phpstan analyse --no-progress | |
| php check.php restarted-for=xdebug,pcov,opcache pcov-armed=no xdebug-armed=no processes=2+ starts=2 | |
| # --xdebug with pcov armed: xdebug stays armed, pcov is still | |
| # disarmed by the same restart - something xdebug-handler could never | |
| # express | |
| - operating-system: "ubuntu-latest" | |
| coverage: "" | |
| extensions: "xdebug, pcov" | |
| script: | | |
| php -d pcov.enabled=1 ../../bin/phpstan analyse --no-progress --xdebug | |
| php check.php restarted-for=pcov,opcache pcov-armed=no xdebug-armed=yes processes=1+ starts=2 | |
| # uopz rewrites opcodes unless uopz.disable=1 | |
| - operating-system: "ubuntu-latest" | |
| coverage: "none" | |
| extensions: "uopz" | |
| script: | | |
| php ../../bin/phpstan analyse --no-progress | |
| php check.php restarted-for=uopz,opcache uopz-armed=no processes=2+ starts=2 | |
| # OPcache is dormant on CLI by default - the restart activates it, | |
| # and the forked workers inherit the parent's warm opcode cache | |
| - operating-system: "ubuntu-latest" | |
| coverage: "none" | |
| extensions: "opcache" | |
| script: | | |
| php -d opcache.enable_cli=0 ../../bin/phpstan analyse --no-progress -vvv > out.txt 2>&1 | |
| cat out.txt | |
| php check.php restarted-for=opcache opcache-cli-on=yes processes=2+ starts=2 | |
| if ! grep -qE 'Mechanism: +fork' out.txt; then | |
| echo "FAIL: the workers were not forked under the activated OPcache" | |
| exit 1 | |
| fi | |
| # a PHP process spawned during the run - by an extension compiling | |
| # its own DI container, say - reads the composed ini through PHPRC, | |
| # so a forked worker that ends must not delete it; the rule spawns | |
| # one in the main process after every worker has ended | |
| - operating-system: "ubuntu-latest" | |
| coverage: "pcov" | |
| extensions: "" | |
| script: | | |
| set +e | |
| php ../../bin/phpstan analyse -c spawn.neon -a SpawnedProcessRule.php --no-progress --error-format=raw -vvv > out.txt 2>&1 | |
| CODE=$? | |
| set -e | |
| cat out.txt | |
| if ! grep -qE 'Mechanism: +fork' out.txt; then | |
| echo "FAIL: the workers were not forked" | |
| exit 1 | |
| fi | |
| if [ "$CODE" != "0" ]; then | |
| echo "FAIL: the analysis exited with $CODE" | |
| exit 1 | |
| fi | |
| php check.php restarted-for=pcov,opcache pcov-armed=no processes=2+ starts=2 | |
| # --debug restarts like any other run - it only makes the analysis | |
| # serial, --xdebug is what keeps xdebug | |
| - operating-system: "ubuntu-latest" | |
| coverage: "pcov" | |
| extensions: "" | |
| script: | | |
| php ../../bin/phpstan analyse --no-progress --debug | |
| php check.php restarted-for=pcov,opcache pcov-armed=no processes=1 starts=2 | |
| # diagnose never restarts either, it reports what a run would do | |
| - operating-system: "ubuntu-latest" | |
| coverage: "pcov" | |
| extensions: "" | |
| script: | | |
| php ../../bin/phpstan diagnose 2>&1 | tee out.txt | |
| if ! grep -qE 'Restart if started now: +yes - pcov' out.txt; then | |
| echo "FAIL: diagnose did not report the restart it would perform" | |
| exit 1 | |
| fi | |
| steps: | |
| - name: Harden the runner (Audit all outbound calls) | |
| uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 | |
| with: | |
| egress-policy: audit | |
| - name: "Checkout" | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: "Install PHP" | |
| uses: "shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962" # 2.40.0 | |
| with: | |
| coverage: ${{ matrix.coverage }} | |
| php-version: "8.2" | |
| extensions: ${{ matrix.extensions }} | |
| - uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0 | |
| - name: "Test" | |
| working-directory: "e2e/restart-manager" | |
| env: | |
| SCRIPT: ${{ matrix.script }} | |
| # Symfony wraps its notes to the terminal width and prefixes wrapped | |
| # lines with "!", which can split a phrase in a way no post-processing | |
| # rejoins - and would make the negative assertions pass vacuously | |
| COLUMNS: "400" | |
| run: | | |
| # count every start of the main process, the one before the restart | |
| # included - which only an ini-level hook sees | |
| LINE=$(php -r 'echo "auto_prepend_file=\"", getcwd(), DIRECTORY_SEPARATOR, "start-log.php\"";') | |
| INI=$(php -r 'echo php_ini_loaded_file();') | |
| if [ "$RUNNER_OS" = "Windows" ]; then | |
| echo "$LINE" >> "$INI" | |
| else | |
| echo "$LINE" | sudo tee -a "$INI" > /dev/null | |
| fi | |
| php ../../bin/phpstan clear-result-cache | |
| rm -f probe.log starts.log | |
| eval "$SCRIPT" |