Repository navigation
2739 lines (2663 loc) · 176 KB
/
Copy pathe2e-tests.yml
File metadata and controls
2739 lines (2663 loc) · 176 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# 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