mirror of
https://github.com/pestphp/pest.git
synced 2026-09-05 14:23:34 +02:00
Compare commits
1318 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 375b92e93b | |||
| e0e00ca748 | |||
| d70ecb873e | |||
| 638932ddb5 | |||
| ac48185736 | |||
| 2e4f8ad56f | |||
| 8c554a81e2 | |||
| 42d9b777bf | |||
| eb88513baf | |||
| 7b6415ccd0 | |||
| a5d726b678 | |||
| 93223caa56 | |||
| d0572ce138 | |||
| b0d1fc589d | |||
| 5a4f124199 | |||
| c3ceb7d0c7 | |||
| 16d1208344 | |||
| 1cebc84c2d | |||
| 1f79660add | |||
| 37bdf60a1c | |||
| cc7acfe485 | |||
| 7bfb2a6185 | |||
| ca56ca5216 | |||
| b49ba062d5 | |||
| 09699847a2 | |||
| 545d0c2784 | |||
| b795af3b1b | |||
| a40cc6bc0e | |||
| 4d3d0105b7 | |||
| e90e4f70fc | |||
| d112582857 | |||
| 9f3c4e1e82 | |||
| 71f39366c2 | |||
| bfd5b75677 | |||
| 411b9954b5 | |||
| db70017cb2 | |||
| 86adaedbbd | |||
| 92c7677c6e | |||
| 872f0a50c2 | |||
| 668809bc75 | |||
| 8d8f45c843 | |||
| 086b3e9107 | |||
| 43fe26f324 | |||
| 865c1e5113 | |||
| 19eed8d581 | |||
| ad1850b110 | |||
| 585de259a2 | |||
| 953664dce0 | |||
| 1dd959848c | |||
| 047753c836 | |||
| b63626a94d | |||
| ae080becd6 | |||
| aa86304794 | |||
| 75963ea973 | |||
| 5d0ceb1a5d | |||
| 6f0c0536a4 | |||
| 6b2cd358e8 | |||
| e09e1be76d | |||
| 759ac63139 | |||
| 333f469efb | |||
| c4ec925aa5 | |||
| 5c8cfed483 | |||
| 962510b7e3 | |||
| e9c268a584 | |||
| fa91fcf744 | |||
| c6073511a2 | |||
| b5e1b83bb4 | |||
| 1c11595504 | |||
| cd0e921158 | |||
| f8c4c061f8 | |||
| 81eacd79fd | |||
| 1ef680c75d | |||
| 820fa08313 | |||
| 9e79e491e2 | |||
| 1b83b8fcda | |||
| ff4aba2ffa | |||
| feaac1ab7e | |||
| bbc6ada21e | |||
| 3927dbfcdf | |||
| b52bbb4cca | |||
| 52819501eb | |||
| 7b18287374 | |||
| 33e3bf2888 | |||
| 72ab506d7b | |||
| 5dc49a71d6 | |||
| 1adb484d3f | |||
| aadf7e92a6 | |||
| df42b9ed7b | |||
| 632ae79c8b | |||
| b5fed82883 | |||
| 145e20db75 | |||
| ee2e97e932 | |||
| b561236325 | |||
| e685179b7e | |||
| ac8feafdcc | |||
| b49cd150c9 | |||
| 5cfb4133bf | |||
| 3c8bae5f05 | |||
| b2998bc69e | |||
| 3876093cd2 | |||
| 932f8bcc07 | |||
| d393799d2a | |||
| 0d7814ca16 | |||
| 8467c64c22 | |||
| 97714a7088 | |||
| afb582616d | |||
| 15e9b6a507 | |||
| 520ce29376 | |||
| 87882a8561 | |||
| 77ef7e0df1 | |||
| 12100dd901 | |||
| 20c12d006e | |||
| 3aab02d2eb | |||
| 553aac65e6 | |||
| be49a3ce18 | |||
| 774a340400 | |||
| 04c9d41895 | |||
| dfb7b870af | |||
| 37821e1108 | |||
| cd711a25d5 | |||
| eee60c9e11 | |||
| c40c8dbc24 | |||
| 2d93c9c373 | |||
| 3d5bba93f8 | |||
| e5ab3af05b | |||
| 79bc7a8257 | |||
| 40b88b62ef | |||
| e3361bc321 | |||
| fc48c1bd1e | |||
| 92e76eb5ab | |||
| da726beffc | |||
| 4ef12b9aac | |||
| bd22f478b8 | |||
| eeaac34cf6 | |||
| b9b07d8983 | |||
| 6aa7d2f891 | |||
| 4d550cecfd | |||
| 1c21a7647a | |||
| d649de1988 | |||
| 783ca4bcd6 | |||
| ba07497219 | |||
| 34695843b3 | |||
| 1ca021dea6 | |||
| d17be9decd | |||
| b828ddcec7 | |||
| f859bb179d | |||
| 2fc75cfcf0 | |||
| 6cc48f63f8 | |||
| e0419d1328 | |||
| faa6988801 | |||
| c12247fafd | |||
| 29b4452443 | |||
| 1b168aba1c | |||
| 6aabd977cd | |||
| a882543c53 | |||
| c250b9da4f | |||
| 46bc3dc628 | |||
| d3ce498b8a | |||
| e1a4b98b71 | |||
| 9afbcd5c18 | |||
| 75593b6454 | |||
| 89590d6120 | |||
| fb0978c9bf | |||
| a3796daa42 | |||
| e3004db666 | |||
| 99cc4e0146 | |||
| a47e6f8fef | |||
| 536d79f765 | |||
| 65c0fbc528 | |||
| 9e4cf4b665 | |||
| 7bea819978 | |||
| 4280233b40 | |||
| d6db3a8a20 | |||
| 51c8ce4df6 | |||
| 5b8393b925 | |||
| e4d9b61fdf | |||
| e2d940cd53 | |||
| 380ccd30b4 | |||
| 31c200716d | |||
| 6add4da543 | |||
| 8ddcd3e853 | |||
| e3e178fd94 | |||
| 7b1ec9f003 | |||
| 1e48c5d473 | |||
| d00ec95dd9 | |||
| 89f3d6cb39 | |||
| a07a2e512a | |||
| 57eecb2b3d | |||
| 9f804dc954 | |||
| 7cbad4c589 | |||
| 5cae93b059 | |||
| df829ad19d | |||
| 635460653c | |||
| 1aa80dc398 | |||
| 8a14056111 | |||
| f247dd8e7b | |||
| 1c7c9754fd | |||
| 5f37939fda | |||
| 28305fcb7a | |||
| 5242803694 | |||
| 925935a7e8 | |||
| 460401c379 | |||
| 348b439172 | |||
| a4e77766c5 | |||
| 4a8c2d7d78 | |||
| 7d51601120 | |||
| 631bbe318b | |||
| 9b7c15d5b6 | |||
| 872796bd9b | |||
| c38d32ae86 | |||
| 6407c4f78f | |||
| 6e1bf63f6a | |||
| 1d3e8bb5dd | |||
| 3cc9b169e3 | |||
| c4911d046b | |||
| d0295f6168 | |||
| 21efbc3107 | |||
| e59b99cd73 | |||
| bf48e20880 | |||
| 53db68e005 | |||
| 34f1e9a7f2 | |||
| 57fd5ce042 | |||
| 3bcabfb63b | |||
| aa3a7c303a | |||
| 5c08a135f7 | |||
| 6e0e030d71 | |||
| b2c07561e7 | |||
| 97600b6f0b | |||
| 8a51f15d65 | |||
| a349f53964 | |||
| a725e774c0 | |||
| bed5e5b54a | |||
| 45b1d4ce20 | |||
| d106b70766 | |||
| 6ac6c1518e | |||
| fda515a17f | |||
| 0a97d3a288 | |||
| 3802fa80e6 | |||
| 5c3cbc14d2 | |||
| 6b9c768172 | |||
| 4a2fc179ae | |||
| b5bb2139dc | |||
| 07416a3c61 | |||
| 30b94e3034 | |||
| be34eecb2f | |||
| 5d9f95f8d4 | |||
| 48b70a03d5 | |||
| 4b8642b972 | |||
| 8711d51eac | |||
| 58dfb6da64 | |||
| d7735d1faa | |||
| 6b59166f3c | |||
| 3a26028d17 | |||
| 3c91bf4ad2 | |||
| 6a434be0f6 | |||
| f355b99bbf | |||
| 95a00341e9 | |||
| 466259646d | |||
| 00f8d56083 | |||
| ca2dca592d | |||
| 405d8d4406 | |||
| b944ee5841 | |||
| f4e22dcafe | |||
| 339c1e8cac | |||
| d4c7362132 | |||
| 81bfdbf8fe | |||
| f45cbf43c5 | |||
| b9088d23fb | |||
| 7250185423 | |||
| e457eb0e9c | |||
| 48357c6f30 | |||
| b46f051550 | |||
| 3d3c5d41ac | |||
| caabebf2a1 | |||
| 470a5833d4 | |||
| c1feefbb9e | |||
| e876dba8ba | |||
| d9c18f9c02 | |||
| 660b57b365 | |||
| 68527c996f | |||
| c6a42a2b28 | |||
| fcf5c27914 | |||
| 856a370032 | |||
| e24882c486 | |||
| 51fc380789 | |||
| f6609f4039 | |||
| 2941f9821f | |||
| ed399af43e | |||
| 0d66dc4322 | |||
| 7e4280bf83 | |||
| a5915b16ab | |||
| 1476b529a1 | |||
| 2892341c28 | |||
| 59e781e77b | |||
| 55a3394f8c | |||
| 0d99c33c4e | |||
| adc5aae6f8 | |||
| 980667e845 | |||
| 8c849c5f40 | |||
| 47f1fc2d94 | |||
| 9c8033d60c | |||
| 42d1092a9e | |||
| c7e32f5d33 | |||
| d379128cc4 | |||
| f09d6f2064 | |||
| 494cc6e2a4 | |||
| f52a455773 | |||
| 184f5d2742 | |||
| 1d81069a2a | |||
| 4b9bb77b54 | |||
| c440031e28 | |||
| 18bbca748f | |||
| bff44562a9 | |||
| 9ebb990f96 | |||
| f142aad8ad | |||
| cabff738f7 | |||
| 0746173a32 | |||
| 74a28d4f5e | |||
| 6053e15d00 | |||
| 87db0b4847 | |||
| 6ba373a772 | |||
| 945d476409 | |||
| a8cf0fe2cb | |||
| 2ae072bb95 | |||
| 59d066950c | |||
| 0dd1aa72ef | |||
| 4e03cd3edb | |||
| eeab24e2bb | |||
| 9b64d5425a | |||
| 0acab1cbb4 | |||
| e616eab9fb | |||
| 7cbb1fcdb2 | |||
| cb5f6e1bd2 | |||
| 985dadd934 | |||
| 10aee6045c | |||
| 4ac14b2528 | |||
| 2d649d765f | |||
| 4fb4908570 | |||
| 13c322bab3 | |||
| 3855249ce9 | |||
| f528bd8427 | |||
| acd8aafa63 | |||
| e8d630e774 | |||
| b6385dc865 | |||
| 02dc8d7bcc | |||
| 729f18a152 | |||
| bdf60cea91 | |||
| 3a8ee8291c | |||
| 654cb726c9 | |||
| bce26aeaad | |||
| 5948bcd71e | |||
| 89006d83a9 | |||
| a8e974d64a | |||
| 617b074049 | |||
| 2eea71a664 | |||
| 4b5374d507 | |||
| 9085561ece | |||
| b71bfc513a | |||
| 75938ac9eb | |||
| e766825f5b | |||
| 8a83a1a1a9 | |||
| e63a886f98 | |||
| 109bb22c5e | |||
| 89dd212d84 | |||
| cd07c6d966 | |||
| 8dddb47ad5 | |||
| 3a6c2fab37 | |||
| 281dbf6cf4 | |||
| 40c8429058 | |||
| 8dd650fd05 | |||
| d9d46c73f8 | |||
| fbca346d7c | |||
| 3f13bca0f7 | |||
| d3acb1c56a | |||
| e601e6df31 | |||
| 6fdbca1226 | |||
| 54359b895f | |||
| e44c554a0b | |||
| 44c04bfce1 | |||
| 271c680d3c | |||
| 9797a71dbc | |||
| c1a54df233 | |||
| 4a1d8d27b8 | |||
| 0f6924984c | |||
| ce05ee9aad | |||
| 3d2ebdb273 | |||
| 668ca9f5de | |||
| f47b74445b | |||
| 6c42e7f4ea | |||
| be3ff37517 | |||
| a087555383 | |||
| 4b50cb486d | |||
| f7175ecfd7 | |||
| 07737bc0b2 | |||
| f659a45311 | |||
| e6ab897594 | |||
| a753b41409 | |||
| 1a4c06bd6e | |||
| 12c1da29ee | |||
| 5d42e8fe3a | |||
| 9d17b872dd | |||
| 2a80101f42 | |||
| f7015fe59c | |||
| 7281e0ded7 | |||
| 1675dd1d41 | |||
| fa27c8daef | |||
| f0a08f0503 | |||
| 2c040c5b1f | |||
| a9ce1fd739 | |||
| 3533356262 | |||
| 4aa41d0b14 | |||
| e4ed60085c | |||
| e2b119655d | |||
| fcf5baf0a9 | |||
| df7b6c8454 | |||
| 5de8693e3b | |||
| 7d80f1d20e | |||
| b3119cc120 | |||
| 4e294edf76 | |||
| f96a1b2786 | |||
| a49cf7edc5 | |||
| b0f6a74cb6 | |||
| aaa226f6a6 | |||
| 69cb752d02 | |||
| cf00e58b7d | |||
| 1f39b28e2c | |||
| 9fcbca69d4 | |||
| b081584ab6 | |||
| 6966802afc | |||
| c61dcad42b | |||
| ec3e0b2d33 | |||
| c3620840b4 | |||
| 10a19f16ba | |||
| a956de5446 | |||
| 3a4329ddc7 | |||
| e6f511302b | |||
| dd01229d7b | |||
| c7e4efcea4 | |||
| df3205e814 | |||
| bc57a84e77 | |||
| bc39830d8a | |||
| 3a566b100e | |||
| 9fe61e0e56 | |||
| e86bec3e68 | |||
| 58b8f3cc5d | |||
| c157b661f2 | |||
| be90610f17 | |||
| 1701a306c3 | |||
| 064ab3fc2e | |||
| 44e315df98 | |||
| 62694c14b9 | |||
| 7c43c1c583 | |||
| 6a96aed654 | |||
| b1c997a869 | |||
| b4172e2c2e | |||
| ae419afd36 | |||
| 27aa305897 | |||
| 0e7c2abe8b | |||
| f5820bd670 | |||
| 41fd831153 | |||
| 51340439e8 | |||
| 1a39826935 | |||
| bd5fed9e12 | |||
| 26345fd9f4 | |||
| ae1da79ac1 | |||
| 00990efc97 | |||
| 477d20a54f | |||
| b7b16096db | |||
| 4105e33c39 | |||
| 08b09f2e98 | |||
| b0fab7e437 | |||
| 8e3444e1db | |||
| dc9a1e8ace | |||
| fc7a4182b5 | |||
| b7406938ac | |||
| 314caabd1d | |||
| 65cabf91b1 | |||
| f91c6c1e1e | |||
| 843dbbf18a | |||
| 47fb1d7763 | |||
| 639df4cb43 | |||
| e54e4a0178 | |||
| 7749775f50 | |||
| f11f3aa0a4 | |||
| 33817013fe | |||
| 7f11ace329 | |||
| 3d776f1f20 | |||
| d5ced0a5ca | |||
| af1e214be4 | |||
| 7f9b50974a | |||
| cd5272d8cc | |||
| a7b2039175 | |||
| 72cf695554 | |||
| 50960a96e9 | |||
| 507df757a1 | |||
| 8722b3fc3c | |||
| 19eca6e338 | |||
| 6b523d6963 | |||
| a350545803 | |||
| 71c2e97c9f | |||
| 98a12012bf | |||
| 027f4e4832 | |||
| 92523a6f39 | |||
| ed38fb644f | |||
| 39b66bf01d | |||
| 165c879fe6 | |||
| 4c8bf4b2fd | |||
| 1ee36f584d | |||
| 1b0a846a81 | |||
| e3e518747f | |||
| 0b96b8f630 | |||
| 711a60c2db | |||
| e7132fa012 | |||
| 3b72bbd7fe | |||
| 273edb864c | |||
| fcb60f3c4a | |||
| 91bb7589e2 | |||
| e524bf5f73 | |||
| 27414ce19f | |||
| fbc9e704e2 | |||
| ee6b3ed062 | |||
| 4c88590b89 | |||
| 66e59efec6 | |||
| f692be3637 | |||
| 127ad618d3 | |||
| da04ba62a8 | |||
| d187566e63 | |||
| 3e86e158b2 | |||
| d6c6489e93 | |||
| ee70a3cfea | |||
| 7a6f33f139 | |||
| 55218bcf78 | |||
| e29302300f | |||
| 2a47b514ec | |||
| 222ed174bc | |||
| 2aa32569f0 | |||
| 1d8d1a046f | |||
| 3d9ceb1cf2 | |||
| 520a5fe29d | |||
| de4409e368 | |||
| 6d6e4e040f | |||
| aac08629f7 | |||
| fe27012bbc | |||
| f9901245f1 | |||
| 21e22decf3 | |||
| e513f76ea9 | |||
| be9c95e3bc | |||
| 9172721ce8 | |||
| 924dc016cc | |||
| f49b91ec0d | |||
| 516ace85b4 | |||
| f9814793dd | |||
| 00572f5f8e | |||
| fb282b184e | |||
| e0695a13cb | |||
| 8f810bf2a2 | |||
| 84636cee96 | |||
| 0355119afc | |||
| 9d0410ee0b | |||
| 0d148c2a67 | |||
| 0f1e87c726 | |||
| 73bf579da3 | |||
| 5def62018b | |||
| d8e1b27491 | |||
| 2ff4713968 | |||
| 3f27352560 | |||
| af3fdceddb | |||
| 3faeede1ef | |||
| 0bc3219a2b | |||
| a22013a7d3 | |||
| 7fc69033f8 | |||
| ef76c04dbe | |||
| 7d77bbf1bb | |||
| 163479ae60 | |||
| c3bfdf130e | |||
| 8c403a57c2 | |||
| 97c136cd94 | |||
| d6cbd12d8b | |||
| 49bf00024f | |||
| dd44ac4195 | |||
| 5d2aafd2a3 | |||
| 0fc9d4dfe0 | |||
| 02b1ffb334 | |||
| c62cc3fef0 | |||
| 909d778da3 | |||
| 7711a52fe9 | |||
| 99c9f4e5d8 | |||
| a310796165 | |||
| db9243ca2e | |||
| 635e3b4c41 | |||
| 791734a29c | |||
| 8cfb0acf46 | |||
| bf67407ba5 | |||
| efdc84e115 | |||
| d1608bf33d | |||
| 4f6140fdb1 | |||
| 442a58d07f | |||
| 19e9267021 | |||
| c6244a8712 | |||
| eed68f2840 | |||
| 6080f51a0b | |||
| e0f07be017 | |||
| 42e1b9f17f | |||
| 0171617c1d | |||
| 2e11e9e65d | |||
| 4969526ef2 | |||
| d7b1c36fdd | |||
| 003fc96e8f | |||
| f68d11ccae | |||
| e46d499384 | |||
| 490f321a0d | |||
| 174645caa2 | |||
| ed70c9dc2b | |||
| 157a753d87 | |||
| 2c3a53f6cd | |||
| 0bdaef29e9 | |||
| 1ad30a97b3 | |||
| a5317c5640 | |||
| 66ceb64faa | |||
| fa4098db8d | |||
| 4a987d3d5c | |||
| 4079a08f5f | |||
| e4aab77a34 | |||
| 50ff347b59 | |||
| b5b8fab09b | |||
| c4c9e915f4 | |||
| e834527db2 | |||
| 23f130b0f9 | |||
| 0cb8c42497 | |||
| fe4b5e5e1f | |||
| 8ee9d66d80 | |||
| 7760d945bb | |||
| 709ecb1ba2 | |||
| 6afb36519d | |||
| 150bb9478d | |||
| bf3178473d | |||
| d2eb94d723 | |||
| 9688b83a3d | |||
| 675372c794 | |||
| c18636b3d5 | |||
| 1ac594bdf0 | |||
| 145294a4a3 | |||
| c2cabaeae6 | |||
| 918a8fc169 | |||
| 5d32dd0641 | |||
| 982353fb38 | |||
| 2eefa8b88d | |||
| 787d5492ac | |||
| 06a0bd9b0b | |||
| 5331b44a18 | |||
| 91afc81222 | |||
| 179d46ce97 | |||
| fa2bc1e536 | |||
| eaeb133c77 | |||
| cf57ea1f94 | |||
| 0b7f4f2384 | |||
| 2903a7e621 | |||
| b8964375c7 | |||
| bdcb883829 | |||
| 8a7e7f39ef | |||
| 53c94600cb | |||
| 67f217852c | |||
| 1bad148487 | |||
| e24f137b8e | |||
| 6d9189f3f5 | |||
| 6968094e2b | |||
| 9510d4a2f9 | |||
| cd2eb3504b | |||
| 7c639cdbbd | |||
| 1513ede73b | |||
| 8c65197881 | |||
| a6cd83665c | |||
| 0c57142c03 | |||
| 3f65af9fdf | |||
| 42d89814e3 | |||
| 1e3156a5b6 | |||
| 97713c0832 | |||
| 62b0e3c9df | |||
| 647de2f1cf | |||
| 0a7bff0d24 | |||
| 7618434580 | |||
| dd7d150caa | |||
| 1e0bb88b73 | |||
| 83b76d7c2e | |||
| 5a870b3940 | |||
| 1115c64186 | |||
| e38a271ca2 | |||
| 43703ab40a | |||
| 86452765a4 | |||
| b8a1b7e5cc | |||
| 5fe79d9c18 | |||
| 2744da4292 | |||
| 87f4e5e7b3 | |||
| bb3decf3cc | |||
| 4e2987d438 | |||
| a25158bce8 | |||
| 49e77b1d4c | |||
| 989e43d1a0 | |||
| 7cd42aafd8 | |||
| 48a1de273f | |||
| 970e16e949 | |||
| 432ff221c6 | |||
| a55da85dd2 | |||
| f291cd1603 | |||
| 5de0c2254a | |||
| b98ce0ced3 | |||
| 28772c2609 | |||
| 452ffaf8df | |||
| e8338405b5 | |||
| 1b014e4b18 | |||
| 034715e8b1 | |||
| 09eff785c4 | |||
| 22cc7805d7 | |||
| 669dc0da71 | |||
| 689da4ed4e | |||
| 92bc1decd9 | |||
| e3bfcbe5f1 | |||
| 2f15861b0d | |||
| ba7eb70a5d | |||
| 74ff3b8cd9 | |||
| ab0b4a1b4e | |||
| 169b76458e | |||
| 668685498f | |||
| bab193e7e1 | |||
| f720be862e | |||
| 0d50d35b5e | |||
| ce61ced8e1 | |||
| 7227d24611 | |||
| 45f16484d5 | |||
| b16e8650da | |||
| c2f30e0148 | |||
| 47ce45de56 | |||
| 32881774d2 | |||
| ea72461f1b | |||
| 49f15521e0 | |||
| 95c5394b66 | |||
| 8de30cc8b7 | |||
| b17feef3f4 | |||
| d8e4a405ad | |||
| 04af21183a | |||
| 09edaa9c2d | |||
| fa41a67be9 | |||
| 1a8f7025fa | |||
| 6afd2ec9df | |||
| d772069db2 | |||
| bb1a0b5e79 | |||
| 3c333ebbb8 | |||
| 868ac1840f | |||
| f857b4889c | |||
| c6b81e6e12 | |||
| c78d33b69e | |||
| bfd351783e | |||
| 526af2a75e | |||
| bf9d011045 | |||
| aaee0e420b | |||
| 772448db80 | |||
| e22fb2e6c0 | |||
| 49aa44c470 | |||
| 1cae035887 | |||
| 15183c4145 | |||
| ae288d1123 | |||
| 2d80ff19ec | |||
| c82f77ea75 | |||
| 5050ae304f | |||
| 98e947e0cc | |||
| 68785986a0 | |||
| 8c078087ff | |||
| 65f74f620c | |||
| dd20323ca7 | |||
| a7ca7afe4e | |||
| baf764f286 | |||
| 3a907c886b | |||
| e6823679dd | |||
| a021b5b8c3 | |||
| e2d360b1b5 | |||
| 8920b850e1 | |||
| 509074b3fa | |||
| 6f9ea14c68 | |||
| 60dcfb36a8 | |||
| ca25d5b13f | |||
| 5cba63e2ba | |||
| dd45a5c655 | |||
| dde943b993 | |||
| bb8677549a | |||
| 5ae5ac9a54 | |||
| cc6f1b43f6 | |||
| 88197fe1d5 | |||
| f53f855e9c | |||
| df69b0b791 | |||
| ecdbe7a472 | |||
| 2c6c3119d2 | |||
| 9ceb0834ae | |||
| 86d2191cae | |||
| 748beb17d5 | |||
| 7ba235f61a | |||
| 700bd517f4 | |||
| cbcfa2c5e2 | |||
| 243e45a551 | |||
| 9b11857ae6 | |||
| bb29e97200 | |||
| 8fe2698c28 | |||
| 823c3d4b17 | |||
| 39c9b15bc0 | |||
| b13acb630d | |||
| 172d94c0ca | |||
| f72d6f2278 | |||
| 71811d6e3a | |||
| dfdbd357e9 | |||
| 4d9ed8768c | |||
| 6638d279e1 | |||
| b5cd0ffb65 | |||
| 7ef40760c2 | |||
| ce4495b093 | |||
| 868547114f | |||
| 9c07dd9990 | |||
| 09beb812d4 | |||
| 4e98dfe3c3 | |||
| ad6dca94fa | |||
| 86f46c2efd | |||
| ccfd4fd77a | |||
| e4d2dac354 | |||
| 7e4c51e13d | |||
| aacd874ebe | |||
| 1c236aab26 | |||
| b6bf01148f | |||
| 347bcfd8a8 | |||
| 0ced3171b0 | |||
| 38638e865f | |||
| adbc6b4a89 | |||
| 9353015691 | |||
| 17058d1709 | |||
| 8ffa66dc7c | |||
| 0697555dc2 | |||
| af680ca8aa | |||
| 651aab560c | |||
| 41e50cac05 | |||
| 6fb1133d52 | |||
| 63ba117b33 | |||
| 33d36d77cb | |||
| 4e7db91ee8 | |||
| d0ff2c8ec2 | |||
| 5e41e546a0 | |||
| 45cce6ce93 | |||
| 6a8a4f3243 | |||
| 101e26749a | |||
| b3c8c24aea | |||
| ef29b4f091 | |||
| a7553b7593 | |||
| f2691623cf | |||
| 99107544ff | |||
| 2e411893d2 | |||
| 135c8a0d46 | |||
| 1cdd7d6744 | |||
| fca0c3a10c | |||
| 0331a87be1 | |||
| ef120125e0 | |||
| 8a9a416133 | |||
| 4783334f15 | |||
| d3be6b72dd | |||
| 7d3118db65 | |||
| eac7abebcb | |||
| 6896dd486a | |||
| 1e5b399603 | |||
| ccdf43726d | |||
| 67dbce2d42 | |||
| ee32f25485 | |||
| 09ca7a1fd5 | |||
| dade84e6b6 | |||
| 1c4bc8b1dc | |||
| 0d2f3eb60e | |||
| 29787d1ff1 | |||
| 474b9b7e17 | |||
| 5c3bf469d5 | |||
| d9252e85d6 | |||
| 0289466ce8 | |||
| 57ef989df8 | |||
| 9d02b649e2 | |||
| 00643312b7 | |||
| eac6585a2e | |||
| 04c39bae2e | |||
| c65755725d | |||
| ec58040f6e | |||
| 3fa73e40cc | |||
| c07513c6a0 | |||
| 85d91d5652 | |||
| 02bae3b649 | |||
| 3ba2b68afc | |||
| ed3ec79aab | |||
| 894dca83f7 | |||
| b873b89b62 | |||
| 1bee283d15 | |||
| 7b4dd410f6 | |||
| 4396ee2e03 | |||
| e4550c8d51 | |||
| a25cfb435c | |||
| fe4fe12df1 | |||
| 3bcc99a372 | |||
| e8f122bf47 | |||
| 9fc607a2b8 | |||
| 3ad788dddb | |||
| 2108d18be5 | |||
| aa4a5fcd15 | |||
| 1688888f15 | |||
| 40539ca720 | |||
| 7144d6dfbd | |||
| 7240250a15 | |||
| 508e42a2ff | |||
| d8156fee53 | |||
| abc245bf85 | |||
| 65dacd5647 | |||
| 917f7a64a0 | |||
| e8b09d6f8c | |||
| 0c4e6de823 | |||
| 52282cc590 | |||
| a46142d8c7 | |||
| 241dcf8f34 | |||
| 927cee609e | |||
| 98e4ebb8fd | |||
| c173e3e86b | |||
| c73655f4f9 | |||
| 4ac1c6efc6 | |||
| 2e5a308b0d | |||
| 7b8e4aec08 | |||
| 13fb66f15c | |||
| dd1bd92910 | |||
| d665b53b22 | |||
| c54b7e400e | |||
| c1e1fff0d0 | |||
| 2e4a8329a6 | |||
| 878988a02d | |||
| ceb7244b43 | |||
| 84256aa8b9 | |||
| d6b59e4e96 | |||
| 087d09120a | |||
| cc41a7f81d | |||
| bd16769b93 | |||
| 60b1e63c23 | |||
| c7bcb6eb7b | |||
| d25ec50384 | |||
| 9e27813897 | |||
| b33af71036 | |||
| 3c6c89a6ad | |||
| 55f6b5696e | |||
| ba914fa2fb | |||
| c919bb5bc4 | |||
| 8169382362 | |||
| 04b099e87c | |||
| fecdb7f572 | |||
| b611d0d444 | |||
| ac7199c96d | |||
| 7756457dc4 | |||
| 10da81eee4 | |||
| 8bbee3c1e5 | |||
| 16125df77b | |||
| 80530cb1e0 | |||
| 2070538fd3 | |||
| a2cb78710d | |||
| 335bfdb79d | |||
| cfa00da885 | |||
| f49d1e0e18 | |||
| 303f4c0113 | |||
| 35a1fcd0cf | |||
| adb2fb51df | |||
| a7a3e4240e | |||
| e4af33867b | |||
| 0c51b159a7 | |||
| c6984323c3 | |||
| 831d9bf49a | |||
| 12f6aa604c | |||
| 265f0c7da9 | |||
| 680111fb1e | |||
| aa6ff95ea4 | |||
| 863a0cc837 | |||
| 126a84a63e | |||
| 0ccbe5c8f0 | |||
| a4f8ae1a12 | |||
| 6094682158 | |||
| d519e40b95 | |||
| 6a1161ead8 | |||
| a1b3547dd6 | |||
| b9e3146a47 | |||
| ce1607cba9 | |||
| ac07bc1770 | |||
| 521a41dd10 | |||
| 1b68b340e8 | |||
| 853f6efce6 | |||
| 62a9a78ee2 | |||
| 78d9fd31d0 | |||
| 2c3234fb3d | |||
| 1b64fef7ba | |||
| a136231503 | |||
| 602b696348 | |||
| 5b0f88c227 | |||
| f31a2c3220 | |||
| e8fa98c810 | |||
| 07e314fbf5 | |||
| 4baf27911e | |||
| 12e48a14d1 | |||
| 1bc0f79508 | |||
| cb0f256791 | |||
| 7b9bae0415 | |||
| 3dffdf7cb8 | |||
| 923970a117 | |||
| b3db7dfd4c | |||
| b303f9f818 | |||
| d29997d5b0 | |||
| 13f340a742 | |||
| eeade88ad2 | |||
| 06280ef75d | |||
| aa46f73888 | |||
| 3660865e5e | |||
| 13695d597b | |||
| fab2de833f | |||
| 5b630bcdff | |||
| e70edbfa38 | |||
| b1558ddde5 | |||
| 582529377b | |||
| 88714598b6 | |||
| 5136267bbe | |||
| 19e748f0d4 | |||
| a53a9d03cf | |||
| edaa045283 | |||
| c5ce355f3c | |||
| 62d8459627 | |||
| a5bf6a3fcb | |||
| 7a46514df8 | |||
| cb1735f4d8 | |||
| 607a4906ac | |||
| 317ea0356e | |||
| 1153531104 | |||
| cfb724cd77 | |||
| 0060b6f955 | |||
| 95cd550524 | |||
| 815ae3c644 | |||
| 887bed3d45 | |||
| 79da02c500 | |||
| 0aecd5d5d7 | |||
| e95c4ee636 | |||
| 2e7fec6be5 | |||
| 4be7082de5 | |||
| fb90f778b9 | |||
| 9d58e1a77e | |||
| 9c077ed352 | |||
| 2562d36518 | |||
| 1d2fe2de2d | |||
| 2d82ee2837 | |||
| 1eee9df679 | |||
| 8c57cc1731 | |||
| 4febd8a11b | |||
| 880b003bee | |||
| e0f9d0bccf | |||
| d4853feecd | |||
| 86e812284d | |||
| 4e31973040 | |||
| f75063c420 | |||
| 6a48e9d44b | |||
| 1f8e6e4e9f | |||
| bb593846e5 | |||
| 108d181a05 | |||
| ac5d6c1f67 | |||
| 5aa3b91d56 | |||
| 9a01504b76 | |||
| 0ab636e436 | |||
| b9d2be87a2 | |||
| fef02594db | |||
| e135e2671f | |||
| 6d74965727 | |||
| 146e141b2a | |||
| 6fed7545c0 | |||
| be407ac904 | |||
| 9ce52ee7ce | |||
| 3ff41bcb68 | |||
| 5332858782 | |||
| 2b094b4188 | |||
| 3457841a9b | |||
| 5258e569c1 | |||
| abb416c2ff | |||
| dd4d5bbd4e | |||
| ab64912c70 | |||
| 1506d8bb27 | |||
| 5aa13b8e97 | |||
| b143ed7aac | |||
| 26dd5f298f | |||
| d939ee938e | |||
| 515de3972f | |||
| bf573b3cac | |||
| 53dc9ffa06 | |||
| 04d2fa5ce8 | |||
| 7764a7a162 | |||
| 727a427837 | |||
| b1c59ec2e6 | |||
| f69a3cf832 | |||
| ed0bf1786f | |||
| 2d1d8a81e1 | |||
| d515cf965e | |||
| dc1e4f040d | |||
| 5e1e701ce5 | |||
| f004591c5a | |||
| 86a96dd157 | |||
| 97dc32f9d2 | |||
| a3ab065343 | |||
| c390721ac3 | |||
| f83d758d4b | |||
| e00aba539a | |||
| 7799500d06 | |||
| c099991cd9 | |||
| e27d2e7394 | |||
| 14fb992ef2 | |||
| 4550a344d3 | |||
| 8efd25ef65 | |||
| 117694f210 | |||
| e5dc6f0ae2 | |||
| 8f738f5d49 | |||
| 1e2ca40c5b | |||
| 4522cb5dcb | |||
| 9ee4191020 | |||
| cc65009d0a | |||
| 453133d382 | |||
| dd0dddffd4 | |||
| 9a8f6e6414 | |||
| 4ece95a040 | |||
| 0cc09380bc | |||
| 809fb855de | |||
| aa14f2e200 | |||
| e319bdb6d3 | |||
| fb7340b556 | |||
| 0528fec083 | |||
| 1cbaaf6e12 | |||
| dc862f60b2 | |||
| ff04d54247 | |||
| 330cf05177 | |||
| 42b5fa914c | |||
| 3b1026b7d7 | |||
| b6151e0d01 | |||
| d6db2c13c1 | |||
| 07b6ff6c04 | |||
| ac5da9e3f7 | |||
| 90fb8c602c | |||
| 3974a65a18 | |||
| 2a54b5819d | |||
| 8be46b57a0 | |||
| 7177791f1e | |||
| c743b10a87 | |||
| 83f8de17c8 | |||
| da20a62e49 | |||
| c8d3e1a9fa | |||
| f7705fe1c1 | |||
| 4f35dbc607 | |||
| 2e01776272 | |||
| cf23dfa477 | |||
| ab4787c667 | |||
| bd6b166a62 | |||
| 17340947b3 | |||
| f235d84d95 | |||
| 3c0d780696 | |||
| 16768fca9f | |||
| 95ec0a82b2 | |||
| 15cd7187e9 | |||
| 0a680dd06e | |||
| 152892cc38 | |||
| 9aad417fb2 | |||
| b58e0cba66 | |||
| 74864c60e1 | |||
| fd4f161edd | |||
| e0939e3e99 | |||
| 2cbecd10e6 | |||
| 2cdd5e3ba0 | |||
| 811ef27ee4 | |||
| 22a7fd0656 | |||
| 698c276cbe | |||
| 6340656ece | |||
| 2d5840f947 | |||
| b8bb3684a3 | |||
| b8cd563569 | |||
| 9fb64599de | |||
| 502f37d280 | |||
| 29cfa8ec35 | |||
| 86c107ae5e | |||
| a63cd2e4f5 | |||
| 7249b59e52 | |||
| 5c94d9994e | |||
| bb0a5d8323 | |||
| b126e8e6e4 | |||
| 677129d23d | |||
| cef5c36885 | |||
| a343ba4a29 | |||
| 21b30b22a7 | |||
| 449c4b6c5e | |||
| 6513ad6ced | |||
| 12421c846e | |||
| a312cecede | |||
| 4be97ed314 | |||
| 5101b9dce3 | |||
| 67e452e9ed | |||
| ecff90da1c | |||
| 2ffafd445d | |||
| 6068ef6150 | |||
| 3ee5c29a00 | |||
| 8c0b933fcd | |||
| 991e02649a | |||
| 79f5973e5a | |||
| 37c40cb735 | |||
| 28ee2917f1 | |||
| a8b785f69e | |||
| 56610d886d | |||
| be0d9e964b | |||
| 6bc9da3fe1 | |||
| 6f54462070 | |||
| 876629b744 | |||
| 5e74e5a19d | |||
| 0d114e21fd | |||
| 95b65fe72b | |||
| bc08f2cb55 | |||
| 6c73a3d90b | |||
| c08f33638a | |||
| 6c93390c9c | |||
| b53e396aac | |||
| 8b327aa8b4 | |||
| d0c6f9bc60 | |||
| b5e066939b | |||
| 7892237408 | |||
| 74df53c72b | |||
| ee26457705 | |||
| 09e6a0944a | |||
| bdee46043a | |||
| 3e25168777 | |||
| 21b8507252 | |||
| d8e283777e | |||
| 2b0aa4b9c9 | |||
| 040eb8142d | |||
| d1aeabc9da | |||
| e4ec2b3efa | |||
| dedcc6b887 | |||
| 2b0ed2bc45 | |||
| 9c859ae7c4 | |||
| ae0a230046 | |||
| 644fade478 | |||
| c9e919dd40 | |||
| 42323e27b1 | |||
| 3927177b23 | |||
| 038fd80428 | |||
| cc6c5bf199 | |||
| b88d9e8ff2 | |||
| 0fc232bbc7 | |||
| 7dcd42d113 | |||
| e79ffc6bad | |||
| 8ea425b266 | |||
| 3a0f6a1d09 | |||
| b9b90295fa | |||
| 9dabecacbf | |||
| 04fa6b6372 | |||
| a0d2856f51 | |||
| 55b9266648 | |||
| 4313a1ef20 | |||
| 005ef03845 | |||
| bbac28c9f4 | |||
| eb56483ba2 | |||
| 5d6b717c9a | |||
| e888f3613b | |||
| e69899559d | |||
| e6fe968d44 | |||
| 678898efe7 | |||
| 6c3d8829ce | |||
| 14859a4c89 | |||
| 8a44d3f136 | |||
| be71d6918d | |||
| afb3dd459a | |||
| b6e3ffafa7 | |||
| 6c95f3d8cf | |||
| 2192373bec | |||
| dfcdaa3f8e | |||
| 79bc9e677f | |||
| 60b615ea6a | |||
| 8787481e40 | |||
| c24406259f | |||
| cbd6a65057 | |||
| 175004baf3 | |||
| 6d9c0483a6 | |||
| 2dc413cba0 | |||
| 206548af2b | |||
| af6de422e9 | |||
| 1c7b254395 | |||
| de1c721cd9 | |||
| f8dd286213 | |||
| e11337df2d | |||
| 2f90d4ccd7 | |||
| 2db15af24a | |||
| 8ea7b2b802 | |||
| c9e3932637 | |||
| d218afaf77 | |||
| 19739ff814 | |||
| 478144fb35 | |||
| 5d81cf0d4c | |||
| 0b115230f9 | |||
| 0b246f7a76 | |||
| 7914224ff7 | |||
| 997b0e9368 | |||
| a76414aeee | |||
| d2096df82a | |||
| 4951b1b0f9 | |||
| f2e31452f2 | |||
| c2985ffb31 | |||
| 492f797dd5 | |||
| 0b261ef97b | |||
| f19692a72f | |||
| 0787b37f2c | |||
| f0223b50d0 | |||
| 0263fcb2ac | |||
| c0a234317b | |||
| 72100075d2 | |||
| a7aa923241 | |||
| e012517b16 | |||
| b1dd18af8a | |||
| 398e3ff3b5 | |||
| 03648f580c | |||
| d94a6580f5 | |||
| fb75b712d3 | |||
| 6ead2a4e8b |
@@ -0,0 +1,66 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: Report an Issue or Bug with the Pest
|
||||||
|
title: "[Bug]: "
|
||||||
|
labels: ["bug"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
We're sorry to hear you have a problem. Can you help us solve it by providing the following details.
|
||||||
|
- type: textarea
|
||||||
|
id: what-happened
|
||||||
|
attributes:
|
||||||
|
label: What Happened
|
||||||
|
description: What did you expect to happen?
|
||||||
|
placeholder: When I use expect()->toBeTrue() in my tests, I get an error
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: how-to-reproduce
|
||||||
|
attributes:
|
||||||
|
label: How to Reproduce
|
||||||
|
description: How did this occur, please add any config values used and provide a set of reliable steps if possible.
|
||||||
|
placeholder: Install a fresh Laravel app, add Pest, add a test that uses expect()->toBeTrue()
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: repository-sample
|
||||||
|
attributes:
|
||||||
|
label: Sample Repository
|
||||||
|
description: If possible, please provide a sample repository that reproduces the issue.
|
||||||
|
placeholder: https://github.com.br/your-username/your-repository
|
||||||
|
- type: input
|
||||||
|
id: pest-version
|
||||||
|
attributes:
|
||||||
|
label: Pest Version
|
||||||
|
description: What version of our Package are you running? Please be as specific as possible
|
||||||
|
placeholder: 2.14.1
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: php-version
|
||||||
|
attributes:
|
||||||
|
label: PHP Version
|
||||||
|
description: What version of PHP are you running? Please be as specific as possible
|
||||||
|
placeholder: 8.1.20
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: operating-systems
|
||||||
|
attributes:
|
||||||
|
label: Operation System
|
||||||
|
description: On which operating systems does the problem occur? You can select more than one.
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- macOS
|
||||||
|
- Windows
|
||||||
|
- Linux
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: notes
|
||||||
|
attributes:
|
||||||
|
label: Notes
|
||||||
|
description: Use this field to provide any other notes that you feel might be relevant to the issue.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
@@ -1,10 +1,16 @@
|
|||||||
| Q | A
|
|
||||||
| ------------- | ---
|
|
||||||
| Bug fix? | yes/no
|
|
||||||
| New feature? | yes/no
|
|
||||||
| Fixed tickets | #... <!-- #-prefixed issue number(s), if any -->
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
- Replace this comment by a description of what your PR is solving.
|
- Fill in the form below correctly. This will help the Pest team to understand the PR and also work on it.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
### What:
|
||||||
|
|
||||||
|
- [ ] Bug Fix
|
||||||
|
- [ ] New Feature
|
||||||
|
|
||||||
|
### Description:
|
||||||
|
|
||||||
|
<!-- describe what your PR is solving -->
|
||||||
|
|
||||||
|
### Related:
|
||||||
|
|
||||||
|
<!-- link to the issue(s) your PR is solving. If it doesn't exist, remove the "Related" section. -->
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
**PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, [SEE BELOW](#reporting-a-vulnerability).**
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
If you discover a security vulnerability in Pest, please report it privately using one of the following channels:
|
||||||
|
|
||||||
|
1. **GitHub Private Vulnerability Reporting** (preferred) — go to the repository's **Security** tab and click **"Report a vulnerability"**. This creates a private advisory visible only to maintainers and provides a structured workflow for triage, fix coordination, and CVE assignment.
|
||||||
|
|
||||||
|
2. **Email** — send the details to Nuno Maduro at **enunomaduro@gmail.com**.
|
||||||
|
|
||||||
|
All security vulnerabilities will be promptly addressed.
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
cooldown:
|
||||||
|
default-days: 5
|
||||||
|
groups:
|
||||||
|
github-actions:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
name: Integration Tests
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * *'
|
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
ci:
|
|
||||||
if: github.event_name != 'schedule' || github.repository == 'pestphp/pest'
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
||||||
php: ['8.1', '8.2']
|
|
||||||
dependency-version: [prefer-lowest, prefer-stable]
|
|
||||||
|
|
||||||
name: PHP ${{ matrix.php }} - ${{ matrix.os }} - ${{ matrix.dependency-version }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Setup PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php }}
|
|
||||||
tools: composer:v2
|
|
||||||
coverage: none
|
|
||||||
|
|
||||||
- name: Setup Problem Matches
|
|
||||||
run: |
|
|
||||||
echo "::add-matcher::${{ runner.tool_cache }}/php.json"
|
|
||||||
echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
|
|
||||||
|
|
||||||
- name: Install PHP dependencies
|
|
||||||
run: composer update --${{ matrix.dependency-version }} --no-interaction --no-progress --ansi
|
|
||||||
|
|
||||||
- name: Integration Tests
|
|
||||||
run: composer test:integration
|
|
||||||
|
|
||||||
@@ -2,9 +2,17 @@ name: Static Analysis
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches: [5.x]
|
||||||
pull_request:
|
pull_request:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 0 * * *'
|
- cron: '0 9 * * *'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: static-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
static:
|
static:
|
||||||
@@ -12,23 +20,45 @@ jobs:
|
|||||||
name: Static Tests
|
name: Static Tests
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
dependency-version: [prefer-lowest, prefer-stable]
|
dependency-version: [prefer-stable]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
|
||||||
with:
|
with:
|
||||||
php-version: 8.1
|
php-version: 8.4
|
||||||
tools: composer:v2
|
tools: composer:v2
|
||||||
coverage: none
|
coverage: none
|
||||||
|
extensions: sockets
|
||||||
|
|
||||||
|
- name: Get Composer cache directory
|
||||||
|
id: composer-cache
|
||||||
|
shell: bash
|
||||||
|
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Cache Composer dependencies
|
||||||
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
|
||||||
|
with:
|
||||||
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
|
key: static-php-8.4-${{ matrix.dependency-version }}-composer-${{ hashFiles('**/composer.json', '**/composer.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
static-php-8.4-${{ matrix.dependency-version }}-composer-
|
||||||
|
static-php-8.4-composer-
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: composer update --prefer-stable --no-interaction --no-progress --ansi
|
env:
|
||||||
|
COMPOSER_ROOT_VERSION: 5.x-dev
|
||||||
|
run: composer update --${{ matrix.dependency-version }} --no-interaction --no-progress --ansi
|
||||||
|
|
||||||
|
- name: Profanity Check
|
||||||
|
run: composer test:profanity
|
||||||
|
|
||||||
- name: Type Check
|
- name: Type Check
|
||||||
run: composer test:type:check
|
run: composer test:type:check
|
||||||
@@ -36,8 +66,5 @@ jobs:
|
|||||||
- name: Type Coverage
|
- name: Type Coverage
|
||||||
run: composer test:type:coverage
|
run: composer test:type:coverage
|
||||||
|
|
||||||
- name: Refacto
|
|
||||||
run: composer test:refacto
|
|
||||||
|
|
||||||
- name: Style
|
- name: Style
|
||||||
run: composer test:lint
|
run: composer test:lint
|
||||||
|
|||||||
+44
-13
@@ -2,33 +2,59 @@ name: Tests
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches: [5.x]
|
||||||
pull_request:
|
pull_request:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 0 * * *'
|
- cron: '0 9 * * *'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: tests-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ci:
|
tests:
|
||||||
if: github.event_name != 'schedule' || github.repository == 'pestphp/pest'
|
if: github.event_name != 'schedule' || github.repository == 'pestphp/pest'
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
||||||
php: ['8.1', '8.2']
|
|
||||||
dependency-version: [prefer-lowest, prefer-stable]
|
|
||||||
|
|
||||||
name: PHP ${{ matrix.php }} - ${{ matrix.os }} - ${{ matrix.dependency-version }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 15
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, macos-latest] # windows-latest
|
||||||
|
symfony: ['8.0']
|
||||||
|
php: ['8.4', '8.5']
|
||||||
|
dependency_version: [prefer-stable]
|
||||||
|
|
||||||
|
name: PHP ${{ matrix.php }} - Symfony ^${{ matrix.symfony }} - ${{ matrix.os }} - ${{ matrix.dependency_version }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php }}
|
php-version: ${{ matrix.php }}
|
||||||
tools: composer:v2
|
tools: composer:v2
|
||||||
coverage: none
|
coverage: none
|
||||||
|
extensions: sockets
|
||||||
|
|
||||||
|
- name: Get Composer cache directory
|
||||||
|
id: composer-cache
|
||||||
|
shell: bash
|
||||||
|
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Cache Composer dependencies
|
||||||
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
|
||||||
|
with:
|
||||||
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
|
key: ${{ matrix.os }}-php-${{ matrix.php }}-symfony-${{ matrix.symfony }}-composer-${{ hashFiles('**/composer.json', '**/composer.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ matrix.os }}-php-${{ matrix.php }}-symfony-${{ matrix.symfony }}-composer-
|
||||||
|
${{ matrix.os }}-php-${{ matrix.php }}-composer-
|
||||||
|
|
||||||
- name: Setup Problem Matches
|
- name: Setup Problem Matches
|
||||||
run: |
|
run: |
|
||||||
@@ -36,11 +62,16 @@ jobs:
|
|||||||
echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
|
echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
|
||||||
|
|
||||||
- name: Install PHP dependencies
|
- name: Install PHP dependencies
|
||||||
run: composer update --${{ matrix.dependency-version }} --no-interaction --no-progress --ansi
|
shell: bash
|
||||||
|
env:
|
||||||
|
COMPOSER_ROOT_VERSION: 5.x-dev
|
||||||
|
run: composer update --${{ matrix.dependency_version }} --no-interaction --no-progress --ansi --with="symfony/console:^${{ matrix.symfony }}"
|
||||||
|
|
||||||
- name: Unit Tests
|
- name: Unit Tests
|
||||||
run: composer test:unit
|
run: composer test:unit
|
||||||
|
|
||||||
- name: Unit Tests in Parallel
|
- name: Parallel Tests
|
||||||
run: composer test:parallel
|
run: composer test:parallel
|
||||||
|
|
||||||
|
- name: Integration Tests
|
||||||
|
run: composer test:integration
|
||||||
|
|||||||
+3
-1
@@ -1,7 +1,7 @@
|
|||||||
.idea/*
|
.idea/*
|
||||||
.idea/codeStyleSettings.xml
|
.idea/codeStyleSettings.xml
|
||||||
.temp/*
|
.temp/*
|
||||||
composer.lock
|
/composer.lock
|
||||||
/vendor/
|
/vendor/
|
||||||
coverage.xml
|
coverage.xml
|
||||||
.phpunit.result.cache
|
.phpunit.result.cache
|
||||||
@@ -12,3 +12,5 @@ coverage.xml
|
|||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
.vscode/
|
.vscode/
|
||||||
|
.STREAM.md
|
||||||
|
|
||||||
|
|||||||
-222
@@ -1,222 +0,0 @@
|
|||||||
# Release Notes for 2.x
|
|
||||||
|
|
||||||
## Unreleased
|
|
||||||
|
|
||||||
## [v2.13.0 (2023-08-09)](https://github.com/pestphp/pest/compare/v2.12.2...v2.13.0)
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- `expect()->ddWhen` and `expect()->ddUnless` ([306b7eb](https://github.com/pestphp/pest/commit/306b7eb2a6a57e570d58228b46501ad9ba4062b4))
|
|
||||||
|
|
||||||
## [v2.12.2 (2023-08-07)](https://github.com/pestphp/pest/compare/v2.12.0...v2.12.2)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Running tests from `uses` parent class ([#898](https://github.com/pestphp/pest/pull/898))
|
|
||||||
|
|
||||||
## [v2.12.0 (2023-08-02)](https://github.com/pestphp/pest/compare/v2.11.0...v2.12.0)
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Allows multiple `toMatchSnapshot` per test ([#881](https://github.com/pestphp/pest/pull/881))
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Bumps PHPUnit to `^10.2.7` ([43107c1](https://github.com/pestphp/pest/commit/43107c17436e41e23018ae31705c688168c14784))
|
|
||||||
|
|
||||||
## [v2.11.0 (2023-08-01)](https://github.com/pestphp/pest/compare/v2.10.1...v2.11.0)
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- `toBeInvokable`expectation ([#891](https://github.com/pestphp/pest/pull/891))
|
|
||||||
|
|
||||||
## [v2.10.1 (2023-07-31)](https://github.com/pestphp/pest/compare/v2.10.0...v2.10.1)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- `not->toHaveSuffix` and `toHavePrefix` expectations ([#888](https://github.com/pestphp/pest/pull/888))
|
|
||||||
|
|
||||||
## [v2.10.0 (2023-07-31)](https://github.com/pestphp/pest/compare/v2.9.5...v2.10.0)
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- `repeat` feature ([f3f35a2](https://github.com/pestphp/pest/commit/f3f35a2ed119f63eefd323a8c66d3387e908df3f))
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- `-v` option ([86a6b32](https://github.com/pestphp/pest/commit/86a6b3271518742dc39761228687a5107551d279))
|
|
||||||
|
|
||||||
## [v2.9.5 (2023-07-22)](https://github.com/pestphp/pest/compare/v2.9.4...v2.9.5)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Assertions count on arch expectations ([632ffc2](https://github.com/pestphp/pest/commit/632ffc2f8e1fe45f739b12b818426ae14700079e))
|
|
||||||
|
|
||||||
## [v2.9.4 (2023-07-22)](https://github.com/pestphp/pest/compare/v2.9.3...v2.9.4)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Output on `describe` `beforeEach` failure ([5637dfa](https://github.com/pestphp/pest/commit/5637dfa75d1a331adc810935536cde7c3196af06))
|
|
||||||
|
|
||||||
## [v2.9.3 (2023-07-20)](https://github.com/pestphp/pest/compare/v2.9.2...v2.9.3)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Snapshots directory on Windows environments ([cf52752](https://github.com/pestphp/pest/commit/cf5275293fe693ec2cf4dbadbadae01daaa08169))
|
|
||||||
|
|
||||||
## [v2.9.2 (2023-07-20)](https://github.com/pestphp/pest/compare/v2.9.1...v2.9.2)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- `beforeEach` on Windows environments ([a37a3b9](https://github.com/pestphp/pest/commit/a37a3b9f9931bc1ab1ea9e1d6d38dfb55dde3f74))
|
|
||||||
|
|
||||||
## [v2.9.1 (2023-07-20)](https://github.com/pestphp/pest/compare/v2.9.0...v2.9.1)
|
|
||||||
|
|
||||||
### Chore
|
|
||||||
|
|
||||||
- Bumps PHPUnit to `^10.2.6` ([8fdb0b3](https://github.com/pestphp/pest/commit/8fdb0b3d32ce9ee12bd182f22751c2b41a53e97b))
|
|
||||||
|
|
||||||
## [v2.9.0 (2023-07-19)](https://github.com/pestphp/pest/compare/v2.8.1...v2.9.0)
|
|
||||||
|
|
||||||
> "Spicy Summer" is the codename assigned to Pest 2.9, for full details check our announcement: [https://pestphp.com/docs/pest-spicy-summer-release](https://pestphp.com/docs/pest-spicy-summer-release)
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Built-in Snapshot Testing ([c828756](https://github.com/pestphp/pest/commit/c8287567eb8c3dbea5845b2a6f70804b094b4b60))
|
|
||||||
- Describe Blocks ([c828756](https://github.com/pestphp/pest/commit/c8287567eb8c3dbea5845b2a6f70804b094b4b60))
|
|
||||||
- Architectural Testing++ ([c828756](https://github.com/pestphp/pest/commit/c8287567eb8c3dbea5845b2a6f70804b094b4b60))
|
|
||||||
- Type Coverage Plugin ([c828756](https://github.com/pestphp/pest/commit/c8287567eb8c3dbea5845b2a6f70804b094b4b60))
|
|
||||||
- Drift Plugin ([c828756](https://github.com/pestphp/pest/commit/c8287567eb8c3dbea5845b2a6f70804b094b4b60))
|
|
||||||
|
|
||||||
## [v2.8.1 (2023-06-20)](https://github.com/pestphp/pest/compare/v2.8.0...v2.8.1)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Fixes "Cannot find TestCase object on call stack" ([eb7bb34](https://github.com/pestphp/pest/commit/eb7bb348253f412e806a6ba6f0df46c0435d0dfe))
|
|
||||||
|
|
||||||
## [v2.8.0 (2023-06-19)](https://github.com/pestphp/pest/compare/v2.7.0...v2.8.0)
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- Support for `globs` in `uses` ([#829](https://github.com/pestphp/pest/pull/829))
|
|
||||||
|
|
||||||
## [v2.7.0 (2023-06-15)](https://github.com/pestphp/pest/compare/v2.6.3...v2.7.0)
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- Support for unexpected output on printer ([eb9f31e](https://github.com/pestphp/pest/commit/eb9f31edeb00a88c449874f3d48156128a00fff8))
|
|
||||||
|
|
||||||
### Chore
|
|
||||||
- Bumps PHPUnit to `^10.2.2` ([0e5470b](https://github.com/pestphp/pest/commit/0e5470b192b259ba2db7c02a50371216c98fc0a6))
|
|
||||||
|
|
||||||
## [v2.6.3 (2023-06-07)](https://github.com/pestphp/pest/compare/v2.6.2...v2.6.3)
|
|
||||||
|
|
||||||
### Chore
|
|
||||||
- Bumps PHPUnit to `^10.2.1` ([73a859e](https://github.com/pestphp/pest/commit/73a859ee563fe96944ba39b191dceca28ef703c2))
|
|
||||||
|
|
||||||
## [v2.6.2 (2023-06-02)](https://github.com/pestphp/pest/compare/v2.6.1...v2.6.2)
|
|
||||||
|
|
||||||
### Chore
|
|
||||||
- Bumps PHPUnit to `^10.2.0` ([a0041f1](https://github.com/pestphp/pest/commit/a0041f139cba94fe5d15318c38e275f2e2fb3350))
|
|
||||||
|
|
||||||
## [v2.6.1 (2023-04-12)](https://github.com/pestphp/pest/compare/v2.6.0...v2.6.1)
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
- PHPStorm issue output problem for tests throwing an exception before the first assertion ([#809](https://github.com/pestphp/pest/pull/809))
|
|
||||||
- Allow traits to be covered ([#804](https://github.com/pestphp/pest/pull/804))
|
|
||||||
|
|
||||||
### Chore
|
|
||||||
- Bumps PHPUnit to `^10.1.3` ([c993252](https://github.com/pestphp/pest/commit/c99325275acf1fd3759b487b93ec50473f706709))
|
|
||||||
|
|
||||||
## [v2.6.0 (2023-04-05)](https://github.com/pestphp/pest/compare/v2.5.2...v2.6.0)
|
|
||||||
|
|
||||||
### Adds
|
|
||||||
- Allows `toThrow` to be used against an exception instance ([#797](https://github.com/pestphp/pest/pull/797))
|
|
||||||
|
|
||||||
## [v2.5.2 (2023-04-19)](https://github.com/pestphp/pest/compare/v2.5.1...v2.5.2)
|
|
||||||
|
|
||||||
### Chore
|
|
||||||
- Removes `myclabs/php-enuma` dependency ([1a05df1](https://github.com/pestphp/pest/commit/1a05df14d0ce7d12583df26ff716807db6f81f13))
|
|
||||||
|
|
||||||
## [v2.5.1 (2023-04-18)](https://github.com/pestphp/pest/compare/v2.5.0...v2.5.1)
|
|
||||||
|
|
||||||
### Chore
|
|
||||||
- Bumps PHPUnit to `^10.1.1` ([ec6a817](https://github.com/pestphp/pest/commit/ec6a81735af19f5463d24545df97535d77697ec6))
|
|
||||||
|
|
||||||
## [v2.5.0 (2023-04-14)](https://github.com/pestphp/pest/compare/v2.4.0...v2.5.0)
|
|
||||||
|
|
||||||
### Chore
|
|
||||||
- Bumps PHPUnit to `^10.1.0` ([#780](https://github.com/pestphp/pest/pull/780))
|
|
||||||
|
|
||||||
## [v2.4.0 (2023-04-03)](https://github.com/pestphp/pest/compare/v2.3.0...v2.4.0)
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- `skipOnWindows()`, `skipOnMac()`, and `skipOnLinux()` ([#757](https://github.com/pestphp/pest/pull/757))
|
|
||||||
- source architecture testing violation ([#1](https://github.com/pestphp/pest-plugin-arch/pull/1))([8e66263](https://github.com/pestphp/pest-plugin-arch/commit/8e66263104304e99e3d6ceda25c7ed679b27fb03))
|
|
||||||
- `toHaveProperties` may now also check values ([#760](https://github.com/pestphp/pest/pull/760))
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Tests on `tests/Helpers` directory not being executed ([#753](https://github.com/pestphp/pest/pull/753))
|
|
||||||
- Teamcity count ([#747](https://github.com/pestphp/pest/pull/747))
|
|
||||||
- Parallel execution when class extends class with same name ([#748](https://github.com/pestphp/pest/pull/748))
|
|
||||||
- Wording on `uses()` hint ([#745](https://github.com/pestphp/pest/pull/745/files))
|
|
||||||
|
|
||||||
## [v2.3.0 (2023-03-28)](https://github.com/pestphp/pest/compare/v2.2.3...v2.3.0)
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- Better error handler about missing uses ([#743](https://github.com/pestphp/pest/pull/743))
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Inconsistent spelling of `dataset` ([#739](https://github.com/pestphp/pest/pull/739))
|
|
||||||
|
|
||||||
### Chore
|
|
||||||
- Bumps PHPUnit to `^10.0.19` ([3d7e621](https://github.com/pestphp/pest/commit/3d7e621b7dfc03f0b2d9dcf6eb06c26bc383f502))
|
|
||||||
|
|
||||||
## [v2.2.3 (2023-03-24)](https://github.com/pestphp/pest/compare/v2.2.2...v2.2.3)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Unnecessary dataset on dataset arguments mismatch ([#736](https://github.com/pestphp/pest/pull/736))
|
|
||||||
- Parallel arguments on plugins order ([#703](https://github.com/pestphp/pest/pull/703))
|
|
||||||
- Arch plugin runtime exceptions on bad phpdocs ([2f2b51c](https://github.com/pestphp/pest/commit/2f2b51ce3d1b000be9d6add0e785fd0044931b3b))
|
|
||||||
|
|
||||||
## [v2.2.2 (2023-03-23)](https://github.com/pestphp/pest/compare/v2.2.1...v2.2.2)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Edge case in parallel execution test description ([3ce6408](https://github.com/pestphp/pest/commit/3ce640819541ca6022b250e000f336d87c3e7889))
|
|
||||||
|
|
||||||
## [v2.2.1 (2023-03-22)](https://github.com/pestphp/pest/compare/v2.2.0...v2.2.1)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Collision between tests names with underscores ([#724](https://github.com/pestphp/pest/pull/724))
|
|
||||||
|
|
||||||
### Chore
|
|
||||||
- Bumps PHPUnit to `^10.0.18` ([1408cff](https://github.com/pestphp/pest/commit/1408cffc028690057e44f00038f9390f776e6bfb))
|
|
||||||
|
|
||||||
## [v2.2.0 (2023-03-22)](https://github.com/pestphp/pest/compare/v2.1.0...v2.2.0)
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- Improved error messages on dataset arguments mismatch ([#698](https://github.com/pestphp/pest/pull/698))
|
|
||||||
- Allows the usage of `DateTimeInterface` on multiple expectations ([#716](https://github.com/pestphp/pest/pull/716))
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- `--dirty` option on Windows environments ([#721](https://github.com/pestphp/pest/pull/721))
|
|
||||||
- Parallel exit code when `phpunit.xml` is outdated ([14dd5cb](https://github.com/pestphp/pest/commit/14dd5cb57b9432300ac4e8095f069941cb43bdb5))
|
|
||||||
|
|
||||||
## [v2.1.0 (2023-03-21)](https://github.com/pestphp/pest/compare/v2.0.2...v2.1.0)
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- `only` test case method ([bcd1503](https://github.com/pestphp/pest/commit/bcd1503cade938853a55c1283b02b6b820ea0b69))
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Issues with different characters on test names ([715](https://github.com/pestphp/pest/pull/715))
|
|
||||||
|
|
||||||
## [v2.0.2 (2023-03-20)](https://github.com/pestphp/pest/compare/v2.0.1...v2.0.2)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- `Pest.php` not being loaded in certain scenarios ([b887116](https://github.com/pestphp/pest/commit/b887116e5ce9a69403ad620cad20f0a029474eb5))
|
|
||||||
|
|
||||||
## [v2.0.1 (2023-03-20)](https://github.com/pestphp/pest/compare/v2.0.0...v2.0.1)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Wrong `version` configuration key on `composer.json` ([8f91f40](https://github.com/pestphp/pest/commit/8f91f40e8ea8b35e04b7989bed6a8f9439e2a2d6))
|
|
||||||
|
|
||||||
## [v2.0.0 (2023-03-20)](https://github.com/pestphp/pest/compare/v1.22.6...v2.0.0)
|
|
||||||
|
|
||||||
Please consult the [upgrade guide](https://pestphp.com/docs/upgrade-guide) and [release notes](https://pestphp.com/docs/announcing-pest2) in the official Pest documentation.
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
**Do not edit this file.** Agents must never add, remove, or reword anything here. If a change seems needed, say so and let a human do it.
|
||||||
|
|
||||||
|
## Ask before testing
|
||||||
|
|
||||||
|
When asked to review code or build a feature, do not run the test suite and do not write new tests. Make the change, report it, then ask the user whether tests should be added — describing the tests you have in mind — and wait for the user to confirm.
|
||||||
|
|
||||||
|
Two reasons this matters here: the suite takes minutes, and `tests/.snapshots/success.txt` plus the tally in `tests/Visual/Parallel.php` encode the whole suite's result, so a single added test breaks both.
|
||||||
|
|
||||||
|
Once the user confirms:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer test:unit # fast, excludes the visual group
|
||||||
|
composer test:integration # visual and snapshot tests
|
||||||
|
composer test # everything CI runs, in CI's order
|
||||||
|
composer update:snapshots # only when a test was added or removed
|
||||||
|
```
|
||||||
|
|
||||||
|
## TIA scenario tests
|
||||||
|
|
||||||
|
`tests/Features/Tia/*` scaffold a throwaway git project, run a real `pest` subprocess against it, and diff the TIA graph it wrote. They exist because TIA's contract is about what a run *writes* — replay, branch keys, and the COMPLETE / RESULTS-ONLY / HARD-SUPPRESSED tiers are invisible to ordinary assertions, and every case used to be measured by hand against a playground app.
|
||||||
|
|
||||||
|
Add one whenever a change touches branch resolution, replay, filtered mode, or the write tiers. How:
|
||||||
|
|
||||||
|
- `Project::make('master')` scaffolds; `seed('master')` writes a graph and sentinels every cached result (`time=9.999`, `assertions=42`) so any rewrite shows up.
|
||||||
|
- `$project->pest('--tia', …)` runs it; `$project->delta()` compares against that snapshot. `writtenCount()` is the discriminator — `0` means "replayed", not "wrote the same values". `mutateGraph()` bends one entry; overlays in `tests/Fixtures/Tia/overlays/<name>/` supply a different `tests/Pest.php`.
|
||||||
|
- Keep expectations driver-independent: a cold recording run needs pcov/xdebug and behaves differently without one. Seed a graph instead of recording one.
|
||||||
|
|
||||||
|
Run them by file (a directory argument finds nothing) or by `--filter`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php bin/pest tests/Features/Tia/PartialRunWriteTier.php
|
||||||
|
```
|
||||||
+2
-2
@@ -42,7 +42,7 @@ composer test
|
|||||||
|
|
||||||
Check types:
|
Check types:
|
||||||
```bash
|
```bash
|
||||||
composer test:types
|
composer test:type:check
|
||||||
```
|
```
|
||||||
|
|
||||||
Unit tests:
|
Unit tests:
|
||||||
@@ -69,7 +69,7 @@ If you want to check things work against a specific version of PHP, you may incl
|
|||||||
the `PHP` build argument when building the image:
|
the `PHP` build argument when building the image:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make build ARGS="--build-arg PHP=8.2"
|
make build ARGS="--build-arg PHP=8.3"
|
||||||
```
|
```
|
||||||
|
|
||||||
The default PHP version will always be the lowest version of PHP supported by Pest.
|
The default PHP version will always be the lowest version of PHP supported by Pest.
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
# Well documented Makefiles
|
|
||||||
DEFAULT_GOAL := help
|
|
||||||
help:
|
|
||||||
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-40s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
|
||||||
|
|
||||||
build: ## Build all docker images. Specify the command e.g. via make build ARGS="--build-arg PHP=8.2"
|
|
||||||
docker compose build $(ARGS)
|
|
||||||
|
|
||||||
##@ [Application]
|
|
||||||
install: ## Install the composer dependencies
|
|
||||||
docker compose run --rm composer install
|
|
||||||
|
|
||||||
test: ## Run the tests
|
|
||||||
docker compose run --rm composer test
|
|
||||||
@@ -1,39 +1,46 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://raw.githubusercontent.com/pestphp/art/master/v2/banner.png" width="600" alt="PEST">
|
<img src="https://raw.githubusercontent.com/pestphp/art/master/v5/social.png" width="600" alt="PEST">
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/pestphp/pest/actions"><img alt="GitHub Workflow Status (master)" src="https://img.shields.io/github/actions/workflow/status/pestphp/pest/tests.yml?branch=2.x&label=Tests%202.x"></a>
|
<a href="https://github.com/pestphp/pest/actions"><img alt="GitHub Workflow Status (5.x)" src="https://img.shields.io/github/actions/workflow/status/pestphp/pest/tests.yml?branch=5.x&label=Tests%205.x"></a>
|
||||||
<a href="https://packagist.org/packages/pestphp/pest"><img alt="Total Downloads" src="https://img.shields.io/packagist/dt/pestphp/pest"></a>
|
<a href="https://packagist.org/packages/pestphp/pest"><img alt="Total Downloads" src="https://img.shields.io/packagist/dt/pestphp/pest"></a>
|
||||||
<a href="https://packagist.org/packages/pestphp/pest"><img alt="Latest Version" src="https://img.shields.io/packagist/v/pestphp/pest"></a>
|
<a href="https://packagist.org/packages/pestphp/pest"><img alt="Latest Version" src="https://img.shields.io/packagist/v/pestphp/pest"></a>
|
||||||
<a href="https://packagist.org/packages/pestphp/pest"><img alt="License" src="https://img.shields.io/packagist/l/pestphp/pest"></a>
|
<a href="https://packagist.org/packages/pestphp/pest"><img alt="License" src="https://img.shields.io/packagist/l/pestphp/pest"></a>
|
||||||
|
<a href="https://whyphp.dev"><img src="https://img.shields.io/badge/Why_PHP-in_2026-7A86E8?style=flat-square&labelColor=18181b" alt="Why PHP in 2026"></a>
|
||||||
|
<a href="https://youtube.com/@nunomaduro?sub_confirmation=1"><img alt="YouTube Channel Subscribers" src="https://img.shields.io/youtube/channel/subscribers/UCO_hYZF2gb_CyG5sA7ArlGg?style=flat&label=youtube&color=brightgreen"></a>
|
||||||
</p>
|
</p>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
------
|
------
|
||||||
**Pest** is an elegant PHP testing Framework with a focus on simplicity, meticulously designed to bring back the joy of testing in PHP.
|
|
||||||
|
> Pest v5 Now Available: **[Read the announcement »](https://pestphp.com/docs/pest5-now-available)**.
|
||||||
|
|
||||||
|
**Pest** is an elegant testing framework for PHP developers and AI agents.
|
||||||
|
|
||||||
- Explore our docs at **[pestphp.com »](https://pestphp.com)**
|
- Explore our docs at **[pestphp.com »](https://pestphp.com)**
|
||||||
- Follow us on Twitter at **[@pestphp »](https://twitter.com/pestphp)**
|
- Follow the creator Nuno Maduro:
|
||||||
- Join us at **[discord.gg/kaHY6p54JH »](https://discord.gg/kaHY6p54JH)** or **[t.me/+kYH5G4d5MV83ODk0 »](https://t.me/+kYH5G4d5MV83ODk0)**
|
- YouTube: **[youtube.com/@nunomaduro](https://youtube.com/@nunomaduro)** — Videos every week
|
||||||
|
- Twitch: **[twitch.tv/nunomaduro](https://twitch.tv/nunomaduro)** — Live coding on Mondays, Wednesdays, and Fridays at 9PM UTC
|
||||||
|
- Twitter / X: **[x.com/enunomaduro](https://x.com/enunomaduro)**
|
||||||
|
- LinkedIn: **[linkedin.com/in/nunomaduro](https://www.linkedin.com/in/nunomaduro)**
|
||||||
|
- Instagram: **[instagram.com/enunomaduro](https://www.instagram.com/enunomaduro)**
|
||||||
|
- Tiktok: **[tiktok.com/@enunomaduro](https://www.tiktok.com/@enunomaduro)**
|
||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
We cannot thank our sponsors enough for their incredible support in funding Pest's development. Their contributions have been instrumental in making Pest the best it can be. For those who are interested in becoming a sponsor, please visit Nuno Maduro's Sponsor page at **[github.com/sponsors/nunomaduro](https://github.com/sponsors/nunomaduro)**.
|
We cannot thank our sponsors enough for their incredible support in funding Pest's development. Their contributions have been instrumental in making Pest the best it can be. For those who are interested in becoming a sponsor, please visit Nuno Maduro's Sponsor page at **[github.com/sponsors/nunomaduro](https://github.com/sponsors/nunomaduro)**.
|
||||||
|
|
||||||
### Platinum Sponsors
|
- **[PhpStorm](https://jb.gg/nuno)**
|
||||||
|
- **[CodeRabbit](https://coderabbit.link/nunomaduro)**
|
||||||
- **[Forge](https://forge.laravel.com)**
|
- **[SerpApi](https://serpapi.com/?ref=nunomaduro)**
|
||||||
- **[LoadForge](https://loadforge.com)**
|
- **[Bento](https://bentonow.com/?ref=nunomaduro)**
|
||||||
- **[Spatie](https://spatie.be)**
|
- **[CMS Max](https://cmsmax.com/?ref=nunomaduro)**
|
||||||
- **[Worksome](https://www.worksome.com/)**
|
- **[Redberry](https://redberry.international/laravel-development/?utm_source=pest&utm_medium=banner&utm_campaign=pest_sponsorship)**
|
||||||
|
- **[Typesense](https://typesense.org/?ref=nunomaduro)**
|
||||||
### Premium Sponsors
|
- **[Pixel](https://wearepixel.com.au/?ref=nunomaduro)**
|
||||||
|
- [TestMu AI](https://www.testmuai.com/?utm_medium=sponsor&utm_source=pest)
|
||||||
- [Akaunting](https://akaunting.com)
|
- [Zapiet](https://zapiet.com/?ref=nunomaduro)
|
||||||
- [Codecourse](https://codecourse.com/)
|
- [LoadForge](https://loadforge.com/?ref=nunomaduro)
|
||||||
- [Laracasts](https://laracasts.com/)
|
- [Route4Me](https://route4me.com/?ref=nunomaduro)
|
||||||
- [Localazy](https://localazy.com)
|
- [Nerdify](https://getnerdify.com/?ref=nunomaduro)
|
||||||
- [Fathom Analytics](https://usefathom.com/)
|
|
||||||
- [Meema](https://meema.io)
|
|
||||||
- [Zapiet](https://www.zapiet.com)
|
|
||||||
|
|
||||||
Pest is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.
|
Pest is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.
|
||||||
|
|||||||
+4
-4
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
When releasing a new version of Pest there are some checks and updates that need to be done:
|
When releasing a new version of Pest there are some checks and updates that need to be done:
|
||||||
|
|
||||||
> **For Pest v1 you should use the `1.x` branch instead.**
|
> **For Pest v4 you should use the `4.x` branch instead.**
|
||||||
|
|
||||||
- Clear your local repository with: `git add . && git reset --hard && git checkout 2.x`
|
- Clear your local repository with: `git add . && git reset --hard && git checkout 5.x`
|
||||||
- On the GitHub repository, check the contents of [github.com/pestphp/pest/compare/{latest_version}...2.x](https://github.com/pestphp/pest/compare/{latest_version}...master) and update the [changelog](CHANGELOG.md) file with the main changes for this release
|
- On the GitHub repository, check the contents of [github.com/pestphp/pest/compare/{latest_version}...5.x](https://github.com/pestphp/pest/compare/{latest_version}...5.x)
|
||||||
- Update the version number in [src/Pest.php](src/Pest.php)
|
- Update the version number in [src/Pest.php](src/Pest.php)
|
||||||
- Run the tests locally using: `composer test`
|
- Run the tests locally using: `composer test`
|
||||||
- Commit the CHANGELOG and Pest file with the message: `git commit -m "release: vX.X.X"`
|
- Commit the Pest file with the message: `git commit -m "release: vX.X.X"`
|
||||||
- Push the changes to GitHub
|
- Push the changes to GitHub
|
||||||
- Check that the CI is passing as expected: [github.com/pestphp/pest/actions](https://github.com/pestphp/pest/actions)
|
- Check that the CI is passing as expected: [github.com/pestphp/pest/actions](https://github.com/pestphp/pest/actions)
|
||||||
- Tag and push the tag with `git tag vX.X.X && git push --tags`
|
- Tag and push the tag with `git tag vX.X.X && git push --tags`
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
#!/usr/bin/env php
|
#!/usr/bin/env php
|
||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Pest\Contracts\Restarter;
|
||||||
use Pest\Kernel;
|
use Pest\Kernel;
|
||||||
use Pest\Panic;
|
use Pest\Panic;
|
||||||
|
use Pest\Support\Container;
|
||||||
use Pest\TestCaseFilters\GitDirtyTestCaseFilter;
|
use Pest\TestCaseFilters\GitDirtyTestCaseFilter;
|
||||||
|
use Pest\TestCaseMethodFilters\AssigneeTestCaseFilter;
|
||||||
|
use Pest\TestCaseMethodFilters\IssueTestCaseFilter;
|
||||||
|
use Pest\TestCaseMethodFilters\NotesTestCaseFilter;
|
||||||
|
use Pest\TestCaseMethodFilters\PrTestCaseFilter;
|
||||||
|
use Pest\TestCaseMethodFilters\FlakyTestCaseFilter;
|
||||||
use Pest\TestCaseMethodFilters\TodoTestCaseFilter;
|
use Pest\TestCaseMethodFilters\TodoTestCaseFilter;
|
||||||
use Pest\TestSuite;
|
use Pest\TestSuite;
|
||||||
use Symfony\Component\Console\Input\ArgvInput;
|
use Symfony\Component\Console\Input\ArgvInput;
|
||||||
@@ -13,39 +22,108 @@ use Symfony\Component\Console\Output\ConsoleOutput;
|
|||||||
// Ensures Collision's Printer is registered.
|
// Ensures Collision's Printer is registered.
|
||||||
$_SERVER['COLLISION_PRINTER'] = 'DefaultPrinter';
|
$_SERVER['COLLISION_PRINTER'] = 'DefaultPrinter';
|
||||||
|
|
||||||
$args = $_SERVER['argv'];
|
$arguments = $originalArguments = $_SERVER['argv'];
|
||||||
|
|
||||||
$dirty = false;
|
$dirty = false;
|
||||||
$todo = false;
|
$todo = false;
|
||||||
|
$flaky = false;
|
||||||
|
$notes = false;
|
||||||
|
|
||||||
|
foreach ($arguments as $key => $value) {
|
||||||
|
|
||||||
foreach ($args as $key => $value) {
|
|
||||||
if ($value === '--compact') {
|
if ($value === '--compact') {
|
||||||
$_SERVER['COLLISION_PRINTER_COMPACT'] = 'true';
|
$_SERVER['COLLISION_PRINTER_COMPACT'] = 'true';
|
||||||
unset($args[$key]);
|
unset($arguments[$key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value === '--profile') {
|
if ($value === '--profile') {
|
||||||
$_SERVER['COLLISION_PRINTER_PROFILE'] = 'true';
|
$_SERVER['COLLISION_PRINTER_PROFILE'] = 'true';
|
||||||
unset($args[$key]);
|
unset($arguments[$key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (str_contains($value, '--test-directory')) {
|
if (str_contains($value, '--test-directory=')) {
|
||||||
unset($args[$key]);
|
unset($arguments[$key]);
|
||||||
|
} elseif ($value === '--test-directory') {
|
||||||
|
unset($arguments[$key]);
|
||||||
|
|
||||||
|
if (isset($arguments[$key + 1])) {
|
||||||
|
unset($arguments[$key + 1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value === '--dirty') {
|
if ($value === '--dirty') {
|
||||||
$dirty = true;
|
$dirty = true;
|
||||||
unset($args[$key]);
|
unset($arguments[$key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value === '--todos') {
|
if (in_array($value, ['--todo', '--todos'], true)) {
|
||||||
$todo = true;
|
$todo = true;
|
||||||
unset($args[$key]);
|
unset($arguments[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value === '--flaky') {
|
||||||
|
$flaky = true;
|
||||||
|
unset($arguments[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value === '--notes') {
|
||||||
|
$notes = true;
|
||||||
|
unset($arguments[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_contains($value, '--assignee=')) {
|
||||||
|
unset($arguments[$key]);
|
||||||
|
} elseif ($value === '--assignee') {
|
||||||
|
unset($arguments[$key]);
|
||||||
|
|
||||||
|
if (isset($arguments[$key + 1])) {
|
||||||
|
unset($arguments[$key + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_contains($value, '--issue=')) {
|
||||||
|
unset($arguments[$key]);
|
||||||
|
} elseif ($value === '--issue') {
|
||||||
|
unset($arguments[$key]);
|
||||||
|
|
||||||
|
if (isset($arguments[$key + 1])) {
|
||||||
|
unset($arguments[$key + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_contains($value, '--ticket=')) {
|
||||||
|
unset($arguments[$key]);
|
||||||
|
} elseif ($value === '--ticket') {
|
||||||
|
unset($arguments[$key]);
|
||||||
|
|
||||||
|
if (isset($arguments[$key + 1])) {
|
||||||
|
unset($arguments[$key + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_contains($value, '--pr=')) {
|
||||||
|
unset($arguments[$key]);
|
||||||
|
} elseif ($value === '--pr') {
|
||||||
|
unset($arguments[$key]);
|
||||||
|
|
||||||
|
if (isset($arguments[$key + 1])) {
|
||||||
|
unset($arguments[$key + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_contains($value, '--pull-request=')) {
|
||||||
|
unset($arguments[$key]);
|
||||||
|
} elseif ($value === '--pull-request') {
|
||||||
|
unset($arguments[$key]);
|
||||||
|
|
||||||
|
if (isset($arguments[$key + 1])) {
|
||||||
|
unset($arguments[$key + 1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (str_contains($value, '--teamcity')) {
|
if (str_contains($value, '--teamcity')) {
|
||||||
unset($args[$key]);
|
unset($arguments[$key]);
|
||||||
$args[] = '--no-output';
|
$arguments[] = '--no-output';
|
||||||
unset($_SERVER['COLLISION_PRINTER']);
|
unset($_SERVER['COLLISION_PRINTER']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,7 +144,8 @@ use Symfony\Component\Console\Output\ConsoleOutput;
|
|||||||
|
|
||||||
// Get $rootPath based on $autoloadPath
|
// Get $rootPath based on $autoloadPath
|
||||||
$rootPath = dirname($autoloadPath, 2);
|
$rootPath = dirname($autoloadPath, 2);
|
||||||
$input = new ArgvInput();
|
|
||||||
|
$input = new ArgvInput;
|
||||||
|
|
||||||
$testSuite = TestSuite::getInstance(
|
$testSuite = TestSuite::getInstance(
|
||||||
$rootPath,
|
$rootPath,
|
||||||
@@ -78,7 +157,35 @@ use Symfony\Component\Console\Output\ConsoleOutput;
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($todo) {
|
if ($todo) {
|
||||||
$testSuite->tests->addTestCaseMethodFilter(new TodoTestCaseFilter());
|
$testSuite->tests->addTestCaseMethodFilter(new TodoTestCaseFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($flaky) {
|
||||||
|
$testSuite->tests->addTestCaseMethodFilter(new FlakyTestCaseFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($notes) {
|
||||||
|
$testSuite->tests->addTestCaseMethodFilter(new NotesTestCaseFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($assignee = $input->getParameterOption('--assignee')) {
|
||||||
|
$testSuite->tests->addTestCaseMethodFilter(new AssigneeTestCaseFilter((string) $assignee));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($issue = $input->getParameterOption('--issue')) {
|
||||||
|
$testSuite->tests->addTestCaseMethodFilter(new IssueTestCaseFilter((int) $issue));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($issue = $input->getParameterOption('--ticket')) {
|
||||||
|
$testSuite->tests->addTestCaseMethodFilter(new IssueTestCaseFilter((int) $issue));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($pr = $input->getParameterOption('--pr')) {
|
||||||
|
$testSuite->tests->addTestCaseMethodFilter(new PrTestCaseFilter((int) $pr));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($pr = $input->getParameterOption('--pull-request')) {
|
||||||
|
$testSuite->tests->addTestCaseMethodFilter(new PrTestCaseFilter((int) $pr));
|
||||||
}
|
}
|
||||||
|
|
||||||
$isDecorated = $input->getParameterOption('--colors', 'always') !== 'never';
|
$isDecorated = $input->getParameterOption('--colors', 'always') !== 'never';
|
||||||
@@ -88,9 +195,18 @@ use Symfony\Component\Console\Output\ConsoleOutput;
|
|||||||
try {
|
try {
|
||||||
$kernel = Kernel::boot($testSuite, $input, $output);
|
$kernel = Kernel::boot($testSuite, $input, $output);
|
||||||
|
|
||||||
$result = $kernel->handle($args);
|
$container = Container::getInstance();
|
||||||
|
|
||||||
$kernel->shutdown();
|
foreach (Kernel::RESTARTERS as $restarterClass) {
|
||||||
|
$restarter = $container->get($restarterClass);
|
||||||
|
assert($restarter instanceof Restarter);
|
||||||
|
|
||||||
|
$restarter->maybeRestart($rootPath, $originalArguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $kernel->handle($originalArguments, $arguments);
|
||||||
|
|
||||||
|
$kernel->terminate();
|
||||||
} catch (Throwable|Error $e) {
|
} catch (Throwable|Error $e) {
|
||||||
Panic::with($e);
|
Panic::with($e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,374 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { readdir, readFile } from 'node:fs/promises'
|
||||||
|
import { existsSync } from 'node:fs'
|
||||||
|
import { createRequire } from 'node:module'
|
||||||
|
import { resolve, relative, extname, sep, join } from 'node:path'
|
||||||
|
import { pathToFileURL } from 'node:url'
|
||||||
|
|
||||||
|
const PAGE_EXTENSIONS = new Set([
|
||||||
|
'.vue', '.svelte',
|
||||||
|
'.tsx', '.jsx',
|
||||||
|
'.ts', '.js',
|
||||||
|
'.mts', '.cts', '.mjs', '.cjs',
|
||||||
|
])
|
||||||
|
const ASSET_EXT_RE = /\.(css|scss|sass|less|styl|stylus|svg|png|jpe?g|gif|webp|avif|ico|bmp|woff2?|ttf|eot|otf|md|mdx|txt|html|mp4|webm|mp3|wav|ogg|m4a|pdf|wasm|glsl|frag|vert)$/i
|
||||||
|
const PROJECT_ROOT = resolve(process.argv[2] ?? process.cwd())
|
||||||
|
const PAGE_DIR_CANDIDATES = [
|
||||||
|
'resources/js/Pages',
|
||||||
|
'resources/js/pages',
|
||||||
|
'assets/js/Pages',
|
||||||
|
'assets/js/pages',
|
||||||
|
'assets/Pages',
|
||||||
|
'assets/pages',
|
||||||
|
]
|
||||||
|
|
||||||
|
async function loadRolldown() {
|
||||||
|
const projectRequire = createRequire(join(PROJECT_ROOT, 'package.json'))
|
||||||
|
let path = null
|
||||||
|
|
||||||
|
try { path = projectRequire.resolve('rolldown') } catch {}
|
||||||
|
|
||||||
|
if (path === null) {
|
||||||
|
// rolldown-vite installs (vite@npm:rolldown-vite) ship rolldown as a
|
||||||
|
// dependency of the vite package rather than a top-level install.
|
||||||
|
try {
|
||||||
|
const viteRequire = createRequire(projectRequire.resolve('vite/package.json'))
|
||||||
|
path = viteRequire.resolve('rolldown')
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path === null) return null
|
||||||
|
|
||||||
|
return await import(pathToFileURL(path).href)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stripJsonComments(raw) {
|
||||||
|
let out = ''
|
||||||
|
let inString = false
|
||||||
|
let quote = ''
|
||||||
|
let inLine = false
|
||||||
|
let inBlock = false
|
||||||
|
|
||||||
|
for (let i = 0; i < raw.length; i++) {
|
||||||
|
const c = raw[i]
|
||||||
|
const n = raw[i + 1]
|
||||||
|
|
||||||
|
if (inLine) {
|
||||||
|
if (c === '\n') { inLine = false; out += c }
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (inBlock) {
|
||||||
|
if (c === '*' && n === '/') { inBlock = false; i++ }
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (inString) {
|
||||||
|
out += c
|
||||||
|
if (c === '\\') { out += n ?? ''; i++; continue }
|
||||||
|
if (c === quote) inString = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (c === '"' || c === "'") { inString = true; quote = c; out += c; continue }
|
||||||
|
if (c === '/' && n === '/') { inLine = true; i++; continue }
|
||||||
|
if (c === '/' && n === '*') { inBlock = true; i++; continue }
|
||||||
|
if (c === '}' || c === ']') out = out.replace(/,\s*$/, '')
|
||||||
|
out += c
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readJsonWithComments(path) {
|
||||||
|
const raw = await readFile(path, 'utf8')
|
||||||
|
return JSON.parse(stripJsonComments(raw))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadAliasFromTsconfig(projectRoot = PROJECT_ROOT) {
|
||||||
|
const alias = {}
|
||||||
|
for (const name of ['tsconfig.json', 'jsconfig.json']) {
|
||||||
|
const p = join(projectRoot, name)
|
||||||
|
if (!existsSync(p)) continue
|
||||||
|
let cfg
|
||||||
|
try { cfg = await readJsonWithComments(p) } catch { continue }
|
||||||
|
const baseUrl = resolve(projectRoot, cfg?.compilerOptions?.baseUrl ?? '.')
|
||||||
|
const paths = cfg?.compilerOptions?.paths ?? {}
|
||||||
|
for (const [key, targets] of Object.entries(paths)) {
|
||||||
|
if (!key.endsWith('/*')) continue
|
||||||
|
const t0 = Array.isArray(targets) ? targets[0] : null
|
||||||
|
if (typeof t0 !== 'string' || !t0.endsWith('/*')) continue
|
||||||
|
const aliasKey = key.slice(0, -2)
|
||||||
|
if (alias[aliasKey] !== undefined) continue
|
||||||
|
alias[aliasKey] = resolve(baseUrl, t0.slice(0, -2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return alias
|
||||||
|
}
|
||||||
|
|
||||||
|
const VITE_CONFIG_FILES = [
|
||||||
|
'vite.config.ts',
|
||||||
|
'vite.config.js',
|
||||||
|
'vite.config.mjs',
|
||||||
|
'vite.config.cjs',
|
||||||
|
'vite.config.mts',
|
||||||
|
'vite.config.cts',
|
||||||
|
]
|
||||||
|
|
||||||
|
function resolveAliasTarget(projectRoot, target) {
|
||||||
|
if (target.startsWith('/')) {
|
||||||
|
// Vite resolves a leading slash against the project root, unless the
|
||||||
|
// config author used a genuinely absolute path.
|
||||||
|
return existsSync(target) ? target : resolve(projectRoot, '.' + target)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(projectRoot, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function usesLaravelVitePlugin(projectRoot) {
|
||||||
|
const p = join(projectRoot, 'package.json')
|
||||||
|
if (!existsSync(p)) return false
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pkg = JSON.parse(await readFile(p, 'utf8'))
|
||||||
|
return Boolean(pkg.dependencies?.['laravel-vite-plugin'] ?? pkg.devDependencies?.['laravel-vite-plugin'])
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadAliasFromViteConfig(projectRoot = PROJECT_ROOT) {
|
||||||
|
const alias = {}
|
||||||
|
let source = null
|
||||||
|
|
||||||
|
for (const name of VITE_CONFIG_FILES) {
|
||||||
|
const p = join(projectRoot, name)
|
||||||
|
if (!existsSync(p)) continue
|
||||||
|
try { source = await readFile(p, 'utf8') } catch { continue }
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source !== null) {
|
||||||
|
// The config is executable code we cannot import safely, so extract alias
|
||||||
|
// entries textually: a quoted `@…`/`~…` key, then the last quoted path in
|
||||||
|
// the value expression — covers `'@': '/resources/js'` as well as
|
||||||
|
// `'@': path.resolve(__dirname, 'resources/js')`. The value stops at a
|
||||||
|
// top-level comma; call arguments are kept via the paren group.
|
||||||
|
for (const m of source.matchAll(/['"`]([@~][\w./-]*)['"`]\s*:\s*((?:\([^)\n]*\)|[^,\n])*)/g)) {
|
||||||
|
const key = m[1]
|
||||||
|
if (alias[key] !== undefined) continue
|
||||||
|
|
||||||
|
const paths = [...m[2].matchAll(/['"`]([^'"`]+)['"`]/g)].map((p) => p[1])
|
||||||
|
const target = paths.length > 0 ? paths[paths.length - 1] : null
|
||||||
|
if (!target || target.includes('*')) continue
|
||||||
|
|
||||||
|
alias[key] = resolveAliasTarget(projectRoot, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// laravel-vite-plugin registers '@' → resources/js by default.
|
||||||
|
if (alias['@'] === undefined && (await usesLaravelVitePlugin(projectRoot))) {
|
||||||
|
alias['@'] = resolve(projectRoot, 'resources/js')
|
||||||
|
}
|
||||||
|
|
||||||
|
return alias
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listPageFiles(pagesDir) {
|
||||||
|
if (!existsSync(pagesDir)) return []
|
||||||
|
|
||||||
|
const out = []
|
||||||
|
const walk = async (dir) => {
|
||||||
|
let entries
|
||||||
|
try { entries = await readdir(dir, { withFileTypes: true }) } catch { return }
|
||||||
|
for (const entry of entries) {
|
||||||
|
const full = resolve(dir, entry.name)
|
||||||
|
if (entry.isDirectory()) { await walk(full); continue }
|
||||||
|
if (PAGE_EXTENSIONS.has(extname(entry.name))) out.push(full)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await walk(pagesDir)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
async function discoverPagesDir() {
|
||||||
|
const override = process.env.TIA_VITE_PAGES_DIR
|
||||||
|
if (override && override.length > 0) {
|
||||||
|
return resolve(PROJECT_ROOT, override.replace(/\\/g, '/'))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const rel of PAGE_DIR_CANDIDATES) {
|
||||||
|
const abs = resolve(PROJECT_ROOT, rel)
|
||||||
|
if (!existsSync(abs)) continue
|
||||||
|
const files = await listPageFiles(abs)
|
||||||
|
if (files.length > 0) return abs
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function componentNameFor(pageAbs, pagesDir) {
|
||||||
|
const rel = relative(pagesDir, pageAbs).split(sep).join('/')
|
||||||
|
const ext = extname(rel)
|
||||||
|
return rel.slice(0, rel.length - ext.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLocalSpecifier(source, aliasKeys) {
|
||||||
|
if (source.startsWith('.') || source.startsWith('/')) return true
|
||||||
|
for (const key of aliasKeys) {
|
||||||
|
if (source === key || source.startsWith(key + '/')) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const pagesDir = await discoverPagesDir()
|
||||||
|
|
||||||
|
if (pagesDir === null) {
|
||||||
|
process.stdout.write('{}')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const pages = await listPageFiles(pagesDir)
|
||||||
|
|
||||||
|
if (pages.length === 0) {
|
||||||
|
process.stdout.write('{}')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const loaded = await loadRolldown()
|
||||||
|
|
||||||
|
if (loaded === null) {
|
||||||
|
process.stdout.write('{}')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { rolldown } = loaded
|
||||||
|
// The vite config is what the dev server actually resolves with — let it win.
|
||||||
|
const alias = { ...(await loadAliasFromTsconfig()), ...(await loadAliasFromViteConfig()) }
|
||||||
|
const aliasKeys = Object.keys(alias)
|
||||||
|
|
||||||
|
const graph = new Map()
|
||||||
|
|
||||||
|
const collector = {
|
||||||
|
name: 'pest-tia-collector',
|
||||||
|
moduleParsed(info) {
|
||||||
|
const id = info.id
|
||||||
|
if (!id || id.startsWith('\0')) return
|
||||||
|
const deps = new Set()
|
||||||
|
for (const i of info.importedIds) if (i && !i.startsWith('\0')) deps.add(i)
|
||||||
|
for (const i of info.dynamicallyImportedIds) if (i && !i.startsWith('\0')) deps.add(i)
|
||||||
|
graph.set(id, deps)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const externalBare = {
|
||||||
|
name: 'pest-tia-external-bare',
|
||||||
|
resolveId(source) {
|
||||||
|
if (!source) return null
|
||||||
|
if (isLocalSpecifier(source, aliasKeys)) return null
|
||||||
|
return { id: source, external: true }
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const assetStub = {
|
||||||
|
name: 'pest-tia-asset-stub',
|
||||||
|
load(id) {
|
||||||
|
if (!id) return null
|
||||||
|
if (ASSET_EXT_RE.test(id)) {
|
||||||
|
return { code: 'export default null', moduleSideEffects: false }
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const input = Object.create(null)
|
||||||
|
for (let i = 0; i < pages.length; i++) input[`p${i}`] = pages[i]
|
||||||
|
|
||||||
|
const bundle = await rolldown({
|
||||||
|
input,
|
||||||
|
cwd: PROJECT_ROOT,
|
||||||
|
resolve: {
|
||||||
|
alias,
|
||||||
|
extensions: ['.tsx', '.ts', '.jsx', '.js', '.mts', '.cts', '.mjs', '.cjs', '.json', '.vue', '.svelte'],
|
||||||
|
},
|
||||||
|
transform: { jsx: 'preserve' },
|
||||||
|
treeshake: false,
|
||||||
|
plugins: [externalBare, assetStub, collector],
|
||||||
|
logLevel: 'silent',
|
||||||
|
onLog: () => {},
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
await bundle.generate({ format: 'esm' })
|
||||||
|
} finally {
|
||||||
|
await bundle.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
const reverse = new Map()
|
||||||
|
const transitiveCache = new Map()
|
||||||
|
|
||||||
|
const computeTransitive = (id, stack) => {
|
||||||
|
const cached = transitiveCache.get(id)
|
||||||
|
if (cached) return cached
|
||||||
|
if (stack.has(id)) return null
|
||||||
|
|
||||||
|
stack.add(id)
|
||||||
|
const acc = new Set()
|
||||||
|
// A set computed while skipping an in-stack (cyclic) dependency is missing
|
||||||
|
// that dependency's subtree and must not be memoized — the ancestor call
|
||||||
|
// completes it for the current traversal, but a cached copy would leak the
|
||||||
|
// incomplete set into other pages' traversals.
|
||||||
|
let complete = true
|
||||||
|
const deps = graph.get(id)
|
||||||
|
if (deps) {
|
||||||
|
for (const dep of deps) {
|
||||||
|
if (!dep || dep.startsWith('\0')) continue
|
||||||
|
if (dep.startsWith(PROJECT_ROOT)) {
|
||||||
|
const rel = relative(PROJECT_ROOT, dep).split(sep).join('/')
|
||||||
|
acc.add(rel)
|
||||||
|
}
|
||||||
|
if (stack.has(dep)) {
|
||||||
|
complete = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const child = computeTransitive(dep, stack)
|
||||||
|
if (child) for (const r of child) acc.add(r)
|
||||||
|
if (!transitiveCache.has(dep)) complete = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stack.delete(id)
|
||||||
|
if (complete) transitiveCache.set(id, acc)
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const page of pages) {
|
||||||
|
const pageComponent = componentNameFor(page, pagesDir)
|
||||||
|
const reachable = computeTransitive(page, new Set())
|
||||||
|
if (!reachable) continue
|
||||||
|
for (const rel of reachable) {
|
||||||
|
const bucket = reverse.get(rel) ?? new Set()
|
||||||
|
bucket.add(pageComponent)
|
||||||
|
reverse.set(rel, bucket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = Object.create(null)
|
||||||
|
const keys = [...reverse.keys()].sort()
|
||||||
|
for (const key of keys) {
|
||||||
|
payload[key] = [...reverse.get(key)].sort()
|
||||||
|
}
|
||||||
|
|
||||||
|
process.stdout.write(JSON.stringify(payload))
|
||||||
|
}
|
||||||
|
|
||||||
|
const invokedDirectly = process.argv[1] !== undefined
|
||||||
|
&& import.meta.url === pathToFileURL(process.argv[1]).href
|
||||||
|
|
||||||
|
if (invokedDirectly) {
|
||||||
|
try {
|
||||||
|
await main()
|
||||||
|
} catch (err) {
|
||||||
|
process.stderr.write(String(err?.stack ?? err ?? 'unknown error'))
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
-6
@@ -6,13 +6,14 @@ use ParaTest\WrapperRunner\ApplicationForWrapperWorker;
|
|||||||
use ParaTest\WrapperRunner\WrapperWorker;
|
use ParaTest\WrapperRunner\WrapperWorker;
|
||||||
use Pest\Kernel;
|
use Pest\Kernel;
|
||||||
use Pest\Plugins\Actions\CallsHandleArguments;
|
use Pest\Plugins\Actions\CallsHandleArguments;
|
||||||
|
use Pest\Support\Container;
|
||||||
use Pest\TestSuite;
|
use Pest\TestSuite;
|
||||||
use Symfony\Component\Console\Input\ArgvInput;
|
use Symfony\Component\Console\Input\ArgvInput;
|
||||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
$bootPest = (static function (): void {
|
$bootPest = (static function (): void {
|
||||||
$workerArgv = new ArgvInput();
|
$workerArgv = new ArgvInput;
|
||||||
|
|
||||||
$rootPath = dirname(PHPUNIT_COMPOSER_INSTALL, 2);
|
$rootPath = dirname(PHPUNIT_COMPOSER_INSTALL, 2);
|
||||||
$testSuite = TestSuite::getInstance($rootPath, $workerArgv->getParameterOption(
|
$testSuite = TestSuite::getInstance($rootPath, $workerArgv->getParameterOption(
|
||||||
@@ -20,7 +21,7 @@ $bootPest = (static function (): void {
|
|||||||
'tests'
|
'tests'
|
||||||
));
|
));
|
||||||
|
|
||||||
$input = new ArgvInput();
|
$input = new ArgvInput;
|
||||||
|
|
||||||
$output = new ConsoleOutput(OutputInterface::VERBOSITY_NORMAL, true);
|
$output = new ConsoleOutput(OutputInterface::VERBOSITY_NORMAL, true);
|
||||||
|
|
||||||
@@ -32,10 +33,13 @@ $bootPest = (static function (): void {
|
|||||||
'status-file:',
|
'status-file:',
|
||||||
'progress-file:',
|
'progress-file:',
|
||||||
'unexpected-output-file:',
|
'unexpected-output-file:',
|
||||||
'testresult-file:',
|
'test-result-file:',
|
||||||
|
'result-cache-file:',
|
||||||
'teamcity-file:',
|
'teamcity-file:',
|
||||||
'testdox-file:',
|
'testdox-file:',
|
||||||
'testdox-color',
|
'testdox-color',
|
||||||
|
'testdox-columns:',
|
||||||
|
'testdox-summary',
|
||||||
'phpunit-argv:',
|
'phpunit-argv:',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -55,13 +59,23 @@ $bootPest = (static function (): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$container = Container::getInstance();
|
||||||
|
$rootPath = dirname(PHPUNIT_COMPOSER_INSTALL, 2);
|
||||||
|
|
||||||
|
foreach (Kernel::RESTARTERS as $restarterClass) {
|
||||||
|
$restarter = $container->get($restarterClass);
|
||||||
|
|
||||||
|
$restarter->maybeRestart($rootPath, $_SERVER['argv']);
|
||||||
|
}
|
||||||
|
|
||||||
assert(isset($getopt['status-file']) && is_string($getopt['status-file']));
|
assert(isset($getopt['status-file']) && is_string($getopt['status-file']));
|
||||||
$statusFile = fopen($getopt['status-file'], 'wb');
|
$statusFile = fopen($getopt['status-file'], 'wb');
|
||||||
assert(is_resource($statusFile));
|
assert(is_resource($statusFile));
|
||||||
|
|
||||||
assert(isset($getopt['progress-file']) && is_string($getopt['progress-file']));
|
assert(isset($getopt['progress-file']) && is_string($getopt['progress-file']));
|
||||||
assert(isset($getopt['unexpected-output-file']) && is_string($getopt['unexpected-output-file']));
|
assert(isset($getopt['unexpected-output-file']) && is_string($getopt['unexpected-output-file']));
|
||||||
assert(isset($getopt['testresult-file']) && is_string($getopt['testresult-file']));
|
assert(isset($getopt['test-result-file']) && is_string($getopt['test-result-file']));
|
||||||
|
assert(! isset($getopt['result-cache-file']) || is_string($getopt['result-cache-file']));
|
||||||
assert(! isset($getopt['teamcity-file']) || is_string($getopt['teamcity-file']));
|
assert(! isset($getopt['teamcity-file']) || is_string($getopt['teamcity-file']));
|
||||||
assert(! isset($getopt['testdox-file']) || is_string($getopt['testdox-file']));
|
assert(! isset($getopt['testdox-file']) || is_string($getopt['testdox-file']));
|
||||||
|
|
||||||
@@ -77,10 +91,12 @@ $bootPest = (static function (): void {
|
|||||||
$phpunitArgv,
|
$phpunitArgv,
|
||||||
$getopt['progress-file'],
|
$getopt['progress-file'],
|
||||||
$getopt['unexpected-output-file'],
|
$getopt['unexpected-output-file'],
|
||||||
$getopt['testresult-file'],
|
$getopt['test-result-file'],
|
||||||
|
$getopt['result-cache-file'] ?? null,
|
||||||
$getopt['teamcity-file'] ?? null,
|
$getopt['teamcity-file'] ?? null,
|
||||||
$getopt['testdox-file'] ?? null,
|
$getopt['testdox-file'] ?? null,
|
||||||
isset($getopt['testdox-color']),
|
isset($getopt['testdox-color']),
|
||||||
|
(int) ($getopt['testdox-columns'] ?? null),
|
||||||
);
|
);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -95,7 +111,6 @@ $bootPest = (static function (): void {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// It must be a 1 byte string to ensure filesize() is equal to the number of tests executed
|
|
||||||
$exitCode = $application->runTest(realpath(trim($testPath)));
|
$exitCode = $application->runTest(realpath(trim($testPath)));
|
||||||
|
|
||||||
fwrite($statusFile, (string) $exitCode);
|
fwrite($statusFile, (string) $exitCode);
|
||||||
|
|||||||
+50
-23
@@ -17,17 +17,22 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.1.0",
|
"php": "^8.4",
|
||||||
"brianium/paratest": "^7.2.5",
|
"brianium/paratest": "^7.24.0",
|
||||||
"nunomaduro/collision": "^7.8.1",
|
"nunomaduro/collision": "^8.9.5",
|
||||||
"nunomaduro/termwind": "^1.15.1",
|
"nunomaduro/termwind": "^2.4.0",
|
||||||
"pestphp/pest-plugin": "^2.0.1",
|
"pestphp/pest-plugin": "^5.0.0",
|
||||||
"pestphp/pest-plugin-arch": "^2.2.3",
|
"pestphp/pest-plugin-arch": "^5.0.0",
|
||||||
"phpunit/phpunit": "^10.3.1"
|
"pestphp/pest-plugin-mutate": "^5.0.1",
|
||||||
|
"pestphp/pest-plugin-profanity": "^5.0.0",
|
||||||
|
"phpunit/phpunit": "^13.3.0",
|
||||||
|
"symfony/process": "^8.1.0"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"webmozart/assert": "<1.11.0",
|
"filp/whoops": "<2.18.3",
|
||||||
"phpunit/phpunit": ">10.3.1"
|
"phpunit/phpunit": ">13.3.0",
|
||||||
|
"sebastian/exporter": "<7.0.0",
|
||||||
|
"webmozart/assert": "<1.11.0"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
@@ -43,22 +48,30 @@
|
|||||||
"Tests\\Fixtures\\Covers\\": "tests/Fixtures/Covers",
|
"Tests\\Fixtures\\Covers\\": "tests/Fixtures/Covers",
|
||||||
"Tests\\Fixtures\\Inheritance\\": "tests/Fixtures/Inheritance",
|
"Tests\\Fixtures\\Inheritance\\": "tests/Fixtures/Inheritance",
|
||||||
"Tests\\Fixtures\\Arch\\": "tests/Fixtures/Arch",
|
"Tests\\Fixtures\\Arch\\": "tests/Fixtures/Arch",
|
||||||
|
"Tests\\Fixtures\\Tia\\": "tests/Fixtures/Tia",
|
||||||
"Tests\\": "tests/PHPUnit/"
|
"Tests\\": "tests/PHPUnit/"
|
||||||
},
|
},
|
||||||
|
"classmap": [
|
||||||
|
"tests/Fixtures/Arch/ToBeCasedCorrectly/IncorrectCasing/incorrectCasing.php"
|
||||||
|
],
|
||||||
"files": [
|
"files": [
|
||||||
"tests/Autoload.php"
|
"tests/Autoload.php"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"pestphp/pest-dev-tools": "^2.14.0",
|
"pestphp/pest-dev-tools": "^5.0.0",
|
||||||
"pestphp/pest-plugin-type-coverage": "^2.0.0",
|
"pestphp/pest-plugin-browser": "^5.0.0",
|
||||||
"symfony/process": "^6.3.2"
|
"pestphp/pest-plugin-phpstan": "^5.0.2",
|
||||||
|
"pestphp/pest-plugin-rector": "^5.0.3",
|
||||||
|
"pestphp/pest-plugin-type-coverage": "^5.0.2",
|
||||||
|
"psy/psysh": "^0.12.24"
|
||||||
},
|
},
|
||||||
"minimum-stability": "dev",
|
"minimum-stability": "dev",
|
||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
"config": {
|
"config": {
|
||||||
"sort-packages": true,
|
"sort-packages": true,
|
||||||
"preferred-install": "dist",
|
"preferred-install": "dist",
|
||||||
|
"process-timeout": 0,
|
||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
"pestphp/pest-plugin": true
|
"pestphp/pest-plugin": true
|
||||||
}
|
}
|
||||||
@@ -67,19 +80,24 @@
|
|||||||
"bin/pest"
|
"bin/pest"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"refacto": "rector",
|
"lint": [
|
||||||
"lint": "pint",
|
"rector",
|
||||||
"test:refacto": "rector --dry-run",
|
"pint --parallel"
|
||||||
"test:lint": "pint --test",
|
],
|
||||||
|
"test:lint": [
|
||||||
|
"rector --dry-run",
|
||||||
|
"pint --parallel --test"
|
||||||
|
],
|
||||||
|
"test:profanity": "php bin/pest --profanity --compact",
|
||||||
"test:type:check": "phpstan analyse --ansi --memory-limit=-1 --debug",
|
"test:type:check": "phpstan analyse --ansi --memory-limit=-1 --debug",
|
||||||
"test:type:coverage": "php bin/pest --type-coverage --min=100",
|
"test:type:coverage": "php -d memory_limit=-1 bin/pest --type-coverage --min=100",
|
||||||
"test:unit": "php bin/pest --colors=always --exclude-group=integration --compact",
|
"test:unit": "php bin/pest --exclude-group=integration --compact",
|
||||||
"test:inline": "php bin/pest --colors=always --configuration=phpunit.inline.xml",
|
"test:inline": "php bin/pest --configuration=phpunit.inline.xml",
|
||||||
"test:parallel": "php bin/pest --colors=always --exclude-group=integration --parallel --processes=10",
|
"test:parallel": "php bin/pest --exclude-group=integration --parallel --processes=3",
|
||||||
"test:integration": "php bin/pest --colors=always --group=integration",
|
"test:integration": "php bin/pest --group=integration -v",
|
||||||
"update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --colors=always --update-snapshots",
|
"test:tia": "php bin/pest --group=tia -v",
|
||||||
|
"update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --update-snapshots --exclude-group=tia",
|
||||||
"test": [
|
"test": [
|
||||||
"@test:refacto",
|
|
||||||
"@test:lint",
|
"@test:lint",
|
||||||
"@test:type:check",
|
"@test:type:check",
|
||||||
"@test:type:coverage",
|
"@test:type:coverage",
|
||||||
@@ -91,6 +109,8 @@
|
|||||||
"extra": {
|
"extra": {
|
||||||
"pest": {
|
"pest": {
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
"Pest\\Mutate\\Plugins\\Mutate",
|
||||||
|
"Pest\\Plugins\\Configuration",
|
||||||
"Pest\\Plugins\\Bail",
|
"Pest\\Plugins\\Bail",
|
||||||
"Pest\\Plugins\\Cache",
|
"Pest\\Plugins\\Cache",
|
||||||
"Pest\\Plugins\\Coverage",
|
"Pest\\Plugins\\Coverage",
|
||||||
@@ -106,8 +126,15 @@
|
|||||||
"Pest\\Plugins\\Snapshot",
|
"Pest\\Plugins\\Snapshot",
|
||||||
"Pest\\Plugins\\Verbose",
|
"Pest\\Plugins\\Verbose",
|
||||||
"Pest\\Plugins\\Version",
|
"Pest\\Plugins\\Version",
|
||||||
|
"Pest\\Plugins\\Shard",
|
||||||
|
"Pest\\Plugins\\Tia",
|
||||||
"Pest\\Plugins\\Parallel"
|
"Pest\\Plugins\\Parallel"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"phpstan": {
|
||||||
|
"includes": [
|
||||||
|
"extension.neon"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
version: "3.8"
|
|
||||||
|
|
||||||
services:
|
|
||||||
php:
|
|
||||||
build:
|
|
||||||
context: ./docker
|
|
||||||
volumes:
|
|
||||||
- .:/var/www/html
|
|
||||||
composer:
|
|
||||||
build:
|
|
||||||
context: ./docker
|
|
||||||
volumes:
|
|
||||||
- .:/var/www/html
|
|
||||||
entrypoint: ["composer"]
|
|
||||||
+4
-11
@@ -1,21 +1,14 @@
|
|||||||
ARG PHP=8.1
|
ARG PHP=8.1
|
||||||
FROM php:${PHP}-cli-alpine
|
FROM php:${PHP}-cli-alpine
|
||||||
|
|
||||||
RUN apk update \
|
RUN apk update && apk add \
|
||||||
&& apk add zip libzip-dev icu-dev git
|
zip libzip-dev icu-dev git
|
||||||
|
|
||||||
RUN docker-php-ext-configure zip
|
RUN docker-php-ext-install zip intl
|
||||||
RUN docker-php-ext-install zip
|
|
||||||
RUN docker-php-ext-enable zip
|
|
||||||
|
|
||||||
RUN docker-php-ext-configure intl
|
RUN apk add --no-cache linux-headers autoconf build-base
|
||||||
RUN docker-php-ext-install intl
|
|
||||||
RUN docker-php-ext-enable intl
|
|
||||||
|
|
||||||
RUN apk add --no-cache $PHPIZE_DEPS linux-headers
|
|
||||||
RUN pecl install xdebug
|
RUN pecl install xdebug
|
||||||
RUN docker-php-ext-enable xdebug
|
RUN docker-php-ext-enable xdebug
|
||||||
|
|
||||||
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
||||||
|
|
||||||
WORKDIR /var/www/html
|
WORKDIR /var/www/html
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
parameters:
|
||||||
|
universalObjectCratesClasses:
|
||||||
|
- Pest\Support\HigherOrderTapProxy
|
||||||
|
- Pest\Expectation
|
||||||
@@ -20,9 +20,11 @@ use PHPUnit\Util\Filter;
|
|||||||
use PHPUnit\Util\ThrowableToStringMapper;
|
use PHPUnit\Util\ThrowableToStringMapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
|
||||||
|
*
|
||||||
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
||||||
*/
|
*/
|
||||||
final class ThrowableBuilder
|
final readonly class ThrowableBuilder
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
@@ -36,7 +38,7 @@ final class ThrowableBuilder
|
|||||||
$previous = self::from($previous);
|
$previous = self::from($previous);
|
||||||
}
|
}
|
||||||
|
|
||||||
$trace = Filter::getFilteredStacktrace($t);
|
$trace = Filter::stackTraceFromThrowableAsString($t);
|
||||||
|
|
||||||
if ($t instanceof RenderableOnCollisionEditor && $frame = $t->toCollisionEditor()) {
|
if ($t instanceof RenderableOnCollisionEditor && $frame = $t->toCollisionEditor()) {
|
||||||
$file = $frame->getFile();
|
$file = $frame->getFile();
|
||||||
@@ -50,7 +52,7 @@ final class ThrowableBuilder
|
|||||||
$t->getMessage(),
|
$t->getMessage(),
|
||||||
ThrowableToStringMapper::map($t),
|
ThrowableToStringMapper::map($t),
|
||||||
$trace,
|
$trace,
|
||||||
$previous
|
$previous,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,467 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of PHPUnit.
|
||||||
|
*
|
||||||
|
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace PHPUnit\Logging\JUnit;
|
||||||
|
|
||||||
|
use DOMDocument;
|
||||||
|
use DOMElement;
|
||||||
|
use Pest\Logging\Converter;
|
||||||
|
use Pest\Support\Container;
|
||||||
|
use Pest\TestSuite;
|
||||||
|
use PHPUnit\Event\Code\Test;
|
||||||
|
use PHPUnit\Event\Code\TestMethod;
|
||||||
|
use PHPUnit\Event\EventFacadeIsSealedException;
|
||||||
|
use PHPUnit\Event\Facade;
|
||||||
|
use PHPUnit\Event\InvalidArgumentException;
|
||||||
|
use PHPUnit\Event\Telemetry\HRTime;
|
||||||
|
use PHPUnit\Event\Telemetry\Info;
|
||||||
|
use PHPUnit\Event\Test\Errored;
|
||||||
|
use PHPUnit\Event\Test\Failed;
|
||||||
|
use PHPUnit\Event\Test\Finished;
|
||||||
|
use PHPUnit\Event\Test\MarkedIncomplete;
|
||||||
|
use PHPUnit\Event\Test\PreparationStarted;
|
||||||
|
use PHPUnit\Event\Test\Prepared;
|
||||||
|
use PHPUnit\Event\Test\PrintedUnexpectedOutput;
|
||||||
|
use PHPUnit\Event\Test\Skipped;
|
||||||
|
use PHPUnit\Event\TestSuite\Started;
|
||||||
|
use PHPUnit\Event\UnknownSubscriberTypeException;
|
||||||
|
use PHPUnit\TextUI\Output\Printer;
|
||||||
|
use PHPUnit\Util\Xml;
|
||||||
|
|
||||||
|
use function assert;
|
||||||
|
use function basename;
|
||||||
|
use function is_int;
|
||||||
|
use function sprintf;
|
||||||
|
use function str_replace;
|
||||||
|
use function trim;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
|
||||||
|
*
|
||||||
|
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
||||||
|
*/
|
||||||
|
final class JunitXmlLogger
|
||||||
|
{
|
||||||
|
private readonly Printer $printer;
|
||||||
|
|
||||||
|
private readonly Converter $converter;
|
||||||
|
|
||||||
|
private DOMDocument $document;
|
||||||
|
|
||||||
|
private DOMElement $root;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var DOMElement[]
|
||||||
|
*/
|
||||||
|
private array $testSuites = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<int,int>
|
||||||
|
*/
|
||||||
|
private array $testSuiteTests = [0];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<int,int>
|
||||||
|
*/
|
||||||
|
private array $testSuiteAssertions = [0];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<int,int>
|
||||||
|
*/
|
||||||
|
private array $testSuiteErrors = [0];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<int,int>
|
||||||
|
*/
|
||||||
|
private array $testSuiteFailures = [0];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<int,int>
|
||||||
|
*/
|
||||||
|
private array $testSuiteSkipped = [0];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<int,int>
|
||||||
|
*/
|
||||||
|
private array $testSuiteTimes = [0];
|
||||||
|
|
||||||
|
private int $testSuiteLevel = 0;
|
||||||
|
|
||||||
|
private ?DOMElement $currentTestCase = null;
|
||||||
|
|
||||||
|
private ?HRTime $time = null;
|
||||||
|
|
||||||
|
private bool $prepared = false;
|
||||||
|
|
||||||
|
private bool $preparationFailed = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws EventFacadeIsSealedException
|
||||||
|
* @throws UnknownSubscriberTypeException
|
||||||
|
*/
|
||||||
|
public function __construct(Printer $printer, Facade $facade)
|
||||||
|
{
|
||||||
|
$this->printer = $printer;
|
||||||
|
$this->converter = new Converter(Container::getInstance()->get(TestSuite::class)->rootPath);
|
||||||
|
$this->registerSubscribers($facade);
|
||||||
|
$this->createDocument();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function flush(): void
|
||||||
|
{
|
||||||
|
$this->printer->print($this->document->saveXML() ?: '');
|
||||||
|
|
||||||
|
$this->printer->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSuiteStarted(Started $event): void
|
||||||
|
{
|
||||||
|
$testSuite = $this->document->createElement('testsuite');
|
||||||
|
$testSuite->setAttribute('name', $this->converter->getTestSuiteName($event->testSuite()));
|
||||||
|
if ($event->testSuite()->isForTestClass()) {
|
||||||
|
$testSuite->setAttribute('file', $this->converter->getTestSuiteLocation($event->testSuite()) ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->testSuiteLevel > 0) {
|
||||||
|
$this->testSuites[$this->testSuiteLevel]->appendChild($testSuite);
|
||||||
|
} else {
|
||||||
|
$this->root->appendChild($testSuite);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->testSuiteLevel++;
|
||||||
|
$this->testSuites[$this->testSuiteLevel] = $testSuite;
|
||||||
|
$this->testSuiteTests[$this->testSuiteLevel] = 0;
|
||||||
|
$this->testSuiteAssertions[$this->testSuiteLevel] = 0;
|
||||||
|
$this->testSuiteErrors[$this->testSuiteLevel] = 0;
|
||||||
|
$this->testSuiteFailures[$this->testSuiteLevel] = 0;
|
||||||
|
$this->testSuiteSkipped[$this->testSuiteLevel] = 0;
|
||||||
|
$this->testSuiteTimes[$this->testSuiteLevel] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSuiteFinished(): void
|
||||||
|
{
|
||||||
|
$this->testSuites[$this->testSuiteLevel]->setAttribute(
|
||||||
|
'tests',
|
||||||
|
(string) $this->testSuiteTests[$this->testSuiteLevel],
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->testSuites[$this->testSuiteLevel]->setAttribute(
|
||||||
|
'assertions',
|
||||||
|
(string) $this->testSuiteAssertions[$this->testSuiteLevel],
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->testSuites[$this->testSuiteLevel]->setAttribute(
|
||||||
|
'errors',
|
||||||
|
(string) $this->testSuiteErrors[$this->testSuiteLevel],
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->testSuites[$this->testSuiteLevel]->setAttribute(
|
||||||
|
'failures',
|
||||||
|
(string) $this->testSuiteFailures[$this->testSuiteLevel],
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->testSuites[$this->testSuiteLevel]->setAttribute(
|
||||||
|
'skipped',
|
||||||
|
(string) $this->testSuiteSkipped[$this->testSuiteLevel],
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->testSuites[$this->testSuiteLevel]->setAttribute(
|
||||||
|
'time',
|
||||||
|
sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel]),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($this->testSuiteLevel > 1) {
|
||||||
|
$this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel];
|
||||||
|
$this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel];
|
||||||
|
$this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel];
|
||||||
|
$this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel];
|
||||||
|
$this->testSuiteSkipped[$this->testSuiteLevel - 1] += $this->testSuiteSkipped[$this->testSuiteLevel];
|
||||||
|
$this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->testSuiteLevel--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testPreparationStarted(PreparationStarted $event): void
|
||||||
|
{
|
||||||
|
$this->createTestCase($event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPreparationFailed(): void
|
||||||
|
{
|
||||||
|
$this->preparationFailed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPrepared(): void
|
||||||
|
{
|
||||||
|
$this->prepared = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPrintedUnexpectedOutput(PrintedUnexpectedOutput $event): void
|
||||||
|
{
|
||||||
|
assert($this->currentTestCase !== null);
|
||||||
|
|
||||||
|
$systemOut = $this->document->createElement(
|
||||||
|
'system-out',
|
||||||
|
Xml::prepareString($event->output()),
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->currentTestCase->appendChild($systemOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testFinished(Finished $event): void
|
||||||
|
{
|
||||||
|
if (! $this->prepared || $this->preparationFailed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->handleFinish($event->telemetryInfo(), $event->numberOfAssertionsPerformed());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testMarkedIncomplete(MarkedIncomplete $event): void
|
||||||
|
{
|
||||||
|
$this->handleIncompleteOrSkipped($event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testSkipped(Skipped $event): void
|
||||||
|
{
|
||||||
|
$this->handleIncompleteOrSkipped($event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testErrored(Errored $event): void
|
||||||
|
{
|
||||||
|
$this->handleFault($event, 'error');
|
||||||
|
|
||||||
|
$this->testSuiteErrors[$this->testSuiteLevel]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testFailed(Failed $event): void
|
||||||
|
{
|
||||||
|
$this->handleFault($event, 'failure');
|
||||||
|
|
||||||
|
$this->testSuiteFailures[$this->testSuiteLevel]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
private function handleFinish(Info $telemetryInfo, int $numberOfAssertionsPerformed): void
|
||||||
|
{
|
||||||
|
assert($this->currentTestCase !== null);
|
||||||
|
assert($this->time !== null);
|
||||||
|
|
||||||
|
$time = $telemetryInfo->time()->duration($this->time)->asFloat();
|
||||||
|
|
||||||
|
$this->testSuiteAssertions[$this->testSuiteLevel] += $numberOfAssertionsPerformed;
|
||||||
|
|
||||||
|
$this->currentTestCase->setAttribute(
|
||||||
|
'assertions',
|
||||||
|
(string) $numberOfAssertionsPerformed,
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->currentTestCase->setAttribute(
|
||||||
|
'time',
|
||||||
|
sprintf('%F', $time),
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->testSuites[$this->testSuiteLevel]->appendChild(
|
||||||
|
$this->currentTestCase,
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->testSuiteTests[$this->testSuiteLevel]++;
|
||||||
|
$this->testSuiteTimes[$this->testSuiteLevel] += $time;
|
||||||
|
|
||||||
|
$this->currentTestCase = null;
|
||||||
|
$this->time = null;
|
||||||
|
$this->prepared = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws EventFacadeIsSealedException
|
||||||
|
* @throws UnknownSubscriberTypeException
|
||||||
|
*/
|
||||||
|
private function registerSubscribers(Facade $facade): void
|
||||||
|
{
|
||||||
|
$facade->registerSubscribers(
|
||||||
|
new TestSuiteStartedSubscriber($this),
|
||||||
|
new TestSuiteFinishedSubscriber($this),
|
||||||
|
new TestPreparationStartedSubscriber($this),
|
||||||
|
new TestPreparationFailedSubscriber($this),
|
||||||
|
new TestPreparedSubscriber($this),
|
||||||
|
new TestPrintedUnexpectedOutputSubscriber($this),
|
||||||
|
new TestFinishedSubscriber($this),
|
||||||
|
new TestErroredSubscriber($this),
|
||||||
|
new TestFailedSubscriber($this),
|
||||||
|
new TestMarkedIncompleteSubscriber($this),
|
||||||
|
new TestSkippedSubscriber($this),
|
||||||
|
new TestRunnerExecutionFinishedSubscriber($this),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createDocument(): void
|
||||||
|
{
|
||||||
|
$this->document = new DOMDocument('1.0', 'UTF-8');
|
||||||
|
$this->document->formatOutput = true;
|
||||||
|
|
||||||
|
$this->root = $this->document->createElement('testsuites');
|
||||||
|
$this->document->appendChild($this->root);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
private function handleFault(Errored|Failed $event, string $type): void
|
||||||
|
{
|
||||||
|
if (! $this->prepared) {
|
||||||
|
$this->createTestCase($event);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert($this->currentTestCase !== null);
|
||||||
|
|
||||||
|
$buffer = $this->converter->getTestCaseMethodName($event->test());
|
||||||
|
$throwable = $event->throwable();
|
||||||
|
$buffer .= trim(
|
||||||
|
$this->converter->getExceptionMessage($throwable).PHP_EOL.
|
||||||
|
$this->converter->getExceptionDetails($throwable),
|
||||||
|
);
|
||||||
|
|
||||||
|
$fault = $this->document->createElement(
|
||||||
|
$type,
|
||||||
|
Xml::prepareString($buffer),
|
||||||
|
);
|
||||||
|
|
||||||
|
$fault->setAttribute('type', $throwable->className());
|
||||||
|
|
||||||
|
$this->currentTestCase->appendChild($fault);
|
||||||
|
|
||||||
|
if (! $this->prepared) {
|
||||||
|
$this->handleFinish($event->telemetryInfo(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
private function handleIncompleteOrSkipped(MarkedIncomplete|Skipped $event): void
|
||||||
|
{
|
||||||
|
if (! $this->prepared) {
|
||||||
|
$this->createTestCase($event);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert($this->currentTestCase !== null);
|
||||||
|
|
||||||
|
$skipped = $this->document->createElement('skipped');
|
||||||
|
|
||||||
|
$this->currentTestCase->appendChild($skipped);
|
||||||
|
|
||||||
|
$this->testSuiteSkipped[$this->testSuiteLevel]++;
|
||||||
|
|
||||||
|
if (! $this->prepared) {
|
||||||
|
$this->handleFinish($event->telemetryInfo(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
private function testAsString(Test $test): string
|
||||||
|
{
|
||||||
|
if ($test->isPhpt()) {
|
||||||
|
return basename($test->file());
|
||||||
|
}
|
||||||
|
|
||||||
|
assert($test instanceof TestMethod);
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
'%s::%s%s',
|
||||||
|
$test->className(),
|
||||||
|
$this->name($test),
|
||||||
|
PHP_EOL,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
private function name(Test $test): string
|
||||||
|
{
|
||||||
|
if ($test->isPhpt()) {
|
||||||
|
return basename($test->file());
|
||||||
|
}
|
||||||
|
|
||||||
|
assert($test instanceof TestMethod);
|
||||||
|
|
||||||
|
if (! $test->testData()->hasDataFromDataProvider()) {
|
||||||
|
return $test->methodName();
|
||||||
|
}
|
||||||
|
|
||||||
|
$dataSetName = $test->testData()->dataFromDataProvider()->dataSetName();
|
||||||
|
|
||||||
|
if (is_int($dataSetName)) {
|
||||||
|
return sprintf(
|
||||||
|
'%s with data set #%d',
|
||||||
|
$test->methodName(),
|
||||||
|
$dataSetName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
'%s with data set "%s"',
|
||||||
|
$test->methodName(),
|
||||||
|
$dataSetName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*
|
||||||
|
* @phpstan-assert !null $this->currentTestCase
|
||||||
|
*/
|
||||||
|
private function createTestCase(Errored|Failed|MarkedIncomplete|PreparationStarted|Prepared|Skipped $event): void
|
||||||
|
{
|
||||||
|
$testCase = $this->document->createElement('testcase');
|
||||||
|
|
||||||
|
$test = $event->test();
|
||||||
|
$file = $this->converter->getTestCaseLocation($test);
|
||||||
|
$testCase->setAttribute('name', $this->converter->getTestCaseMethodName($test));
|
||||||
|
$testCase->setAttribute('file', $file);
|
||||||
|
if ($test->isTestMethod()) {
|
||||||
|
assert($test instanceof TestMethod);
|
||||||
|
|
||||||
|
$className = $this->converter->getTrimmedTestClassName($test);
|
||||||
|
$testCase->setAttribute('class', $className);
|
||||||
|
$testCase->setAttribute('classname', str_replace('\\', '.', $className));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->currentTestCase = $testCase;
|
||||||
|
$this->time = $event->telemetryInfo()->time();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ParaTest\WrapperRunner;
|
||||||
|
|
||||||
|
use PHPUnit\TextUI\Output\Printer;
|
||||||
|
|
||||||
|
use function preg_match;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final readonly class ProgressPrinterOutput implements Printer
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private Printer $progressPrinter,
|
||||||
|
private Printer $outputPrinter,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function print(string $buffer): void
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
$buffer === "\n"
|
||||||
|
|| preg_match('/^ +$/', $buffer) === 1
|
||||||
|
|| preg_match('/^ \d+ \/ \d+ \(...%\)$/', $buffer) === 1
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match ($buffer) {
|
||||||
|
'E', 'F', 'I', 'N', 'D', 'R', 'W', 'S', 'T', '.' => $this->progressPrinter->print($buffer),
|
||||||
|
default => $this->outputPrinter->print($buffer),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function flush(): void
|
||||||
|
{
|
||||||
|
$this->progressPrinter->flush();
|
||||||
|
$this->outputPrinter->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,72 +1,47 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* BSD 3-Clause License
|
* This file is part of PHPUnit.
|
||||||
*
|
*
|
||||||
* Copyright (c) 2001-2023, Sebastian Bergmann
|
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||||
* All rights reserved.
|
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* For the full copyright and license information, please view the LICENSE
|
||||||
* modification, are permitted provided that the following conditions are met:
|
* file that was distributed with this source code.
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace PHPUnit\Runner\Filter;
|
namespace PHPUnit\Runner\Filter;
|
||||||
|
|
||||||
use function end;
|
|
||||||
use Exception;
|
|
||||||
use function implode;
|
|
||||||
use Pest\Contracts\HasPrintableTestCaseName;
|
use Pest\Contracts\HasPrintableTestCaseName;
|
||||||
use PHPUnit\Framework\SelfDescribing;
|
|
||||||
use PHPUnit\Framework\Test;
|
use PHPUnit\Framework\Test;
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use PHPUnit\Framework\TestSuite;
|
use PHPUnit\Framework\TestSuite;
|
||||||
use function preg_match;
|
use PHPUnit\Runner\Phpt\TestCase as PhptTestCase;
|
||||||
use RecursiveFilterIterator;
|
use RecursiveFilterIterator;
|
||||||
use RecursiveIterator;
|
use RecursiveIterator;
|
||||||
use function sprintf;
|
|
||||||
use function str_replace;
|
use function end;
|
||||||
|
use function preg_match;
|
||||||
|
use function trim;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @extends RecursiveFilterIterator<int, Test, RecursiveIterator<int, Test>>
|
||||||
|
*
|
||||||
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
||||||
*/
|
*/
|
||||||
final class NameFilterIterator extends RecursiveFilterIterator
|
abstract class NameFilterIterator extends RecursiveFilterIterator
|
||||||
{
|
{
|
||||||
private ?string $filter = null;
|
private readonly CompiledNameFilter $filter;
|
||||||
|
|
||||||
private ?int $filterMin = null;
|
|
||||||
|
|
||||||
private ?int $filterMax = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws Exception
|
* @param RecursiveIterator<int, Test> $iterator
|
||||||
|
* @param non-empty-string $filter
|
||||||
*/
|
*/
|
||||||
public function __construct(RecursiveIterator $iterator, string $filter)
|
public function __construct(RecursiveIterator $iterator, string $filter)
|
||||||
{
|
{
|
||||||
parent::__construct($iterator);
|
parent::__construct($iterator);
|
||||||
|
|
||||||
$this->setFilter($filter);
|
$this->filter = CompiledNameFilter::from($filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function accept(): bool
|
public function accept(): bool
|
||||||
@@ -77,95 +52,27 @@ final class NameFilterIterator extends RecursiveFilterIterator
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$tmp = $this->describe($test);
|
if ($test instanceof PhptTestCase) {
|
||||||
|
return false;
|
||||||
if ($tmp[0] !== '') {
|
|
||||||
$name = implode('::', $tmp);
|
|
||||||
} else {
|
|
||||||
$name = $tmp[1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$accepted = @preg_match($this->filter, $name, $matches);
|
|
||||||
|
|
||||||
if ($accepted && isset($this->filterMax)) {
|
|
||||||
$set = end($matches);
|
|
||||||
$accepted = $set >= $this->filterMin && $set <= $this->filterMax;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (bool) $accepted;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
private function setFilter(string $filter): void
|
|
||||||
{
|
|
||||||
if (@preg_match($filter, '') === false) {
|
|
||||||
// Handles:
|
|
||||||
// * testAssertEqualsSucceeds#4
|
|
||||||
// * testAssertEqualsSucceeds#4-8
|
|
||||||
if (preg_match('/^(.*?)#(\d+)(?:-(\d+))?$/', $filter, $matches)) {
|
|
||||||
if (isset($matches[3]) && $matches[2] < $matches[3]) {
|
|
||||||
$filter = sprintf(
|
|
||||||
'%s.*with dataset #(\d+)$',
|
|
||||||
$matches[1]
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->filterMin = (int) $matches[2];
|
|
||||||
$this->filterMax = (int) $matches[3];
|
|
||||||
} else {
|
|
||||||
$filter = sprintf(
|
|
||||||
'%s.*with dataset #%s$',
|
|
||||||
$matches[1],
|
|
||||||
$matches[2]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} // Handles:
|
|
||||||
// * testDetermineJsonError@JSON_ERROR_NONE
|
|
||||||
// * testDetermineJsonError@JSON.*
|
|
||||||
elseif (preg_match('/^(.*?)@(.+)$/', $filter, $matches)) {
|
|
||||||
$filter = sprintf(
|
|
||||||
'%s.*with dataset "%s"$',
|
|
||||||
$matches[1],
|
|
||||||
$matches[2]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Escape delimiters in regular expression. Do NOT use preg_quote,
|
|
||||||
// to keep magic characters.
|
|
||||||
$filter = sprintf(
|
|
||||||
'/%s/i',
|
|
||||||
str_replace(
|
|
||||||
'/',
|
|
||||||
'\\/',
|
|
||||||
$filter
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->filter = $filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @psalm-return array{0: string, 1: string}
|
|
||||||
*/
|
|
||||||
private function describe(Test $test): array
|
|
||||||
{
|
|
||||||
if ($test instanceof HasPrintableTestCaseName) {
|
if ($test instanceof HasPrintableTestCaseName) {
|
||||||
return [
|
$name = trim(
|
||||||
$test::getPrintableTestCaseName(),
|
$test::getPrintableTestCaseName().'::'.$test->getPrintableTestCaseMethodName().$test->dataSetAsString()
|
||||||
$test->getPrintableTestCaseMethodName(),
|
);
|
||||||
];
|
} else {
|
||||||
|
$name = $test::class.'::'.$test->nameWithDataSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($test instanceof TestCase) {
|
$accepted = @preg_match($this->filter->regularExpression(), $name, $matches) === 1;
|
||||||
return [$test::class, $test->nameWithDataSet()];
|
|
||||||
|
if ($accepted && $this->filter->hasDataSetRange()) {
|
||||||
|
$set = end($matches);
|
||||||
|
$accepted = $set >= $this->filter->dataSetMinimum() && $set <= $this->filter->dataSetMaximum();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($test instanceof SelfDescribing) {
|
return $this->doAccept($accepted);
|
||||||
return ['', $test->toString()];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ['', $test::class];
|
abstract protected function doAccept(bool $result): bool;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,195 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* BSD 3-Clause License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2001-2023, Sebastian Bergmann
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of PHPUnit.
|
|
||||||
*
|
|
||||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace PHPUnit\Runner\ResultCache;
|
|
||||||
|
|
||||||
use function array_keys;
|
|
||||||
use function assert;
|
|
||||||
use const DIRECTORY_SEPARATOR;
|
|
||||||
use function dirname;
|
|
||||||
use function file_get_contents;
|
|
||||||
use function file_put_contents;
|
|
||||||
use function is_array;
|
|
||||||
use function is_dir;
|
|
||||||
use function is_file;
|
|
||||||
use function json_decode;
|
|
||||||
use function json_encode;
|
|
||||||
use function Pest\version;
|
|
||||||
use PHPUnit\Framework\TestStatus\TestStatus;
|
|
||||||
use PHPUnit\Runner\DirectoryCannotBeCreatedException;
|
|
||||||
use PHPUnit\Runner\Exception;
|
|
||||||
use PHPUnit\Util\Filesystem;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
|
||||||
*/
|
|
||||||
final class DefaultResultCache implements ResultCache
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private const DEFAULT_RESULT_CACHE_FILENAME = '.phpunit.result.cache';
|
|
||||||
|
|
||||||
private readonly string $cacheFilename;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @psalm-var array<string, TestStatus>
|
|
||||||
*/
|
|
||||||
private array $defects = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @psalm-var array<string, TestStatus>
|
|
||||||
*/
|
|
||||||
private array $currentDefects = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @psalm-var array<string, float>
|
|
||||||
*/
|
|
||||||
private array $times = [];
|
|
||||||
|
|
||||||
public function __construct(string $filepath = null)
|
|
||||||
{
|
|
||||||
if ($filepath !== null && is_dir($filepath)) {
|
|
||||||
$filepath .= DIRECTORY_SEPARATOR.self::DEFAULT_RESULT_CACHE_FILENAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->cacheFilename = $filepath ?? $_ENV['PHPUNIT_RESULT_CACHE'] ?? self::DEFAULT_RESULT_CACHE_FILENAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setStatus(string $id, TestStatus $status): void
|
|
||||||
{
|
|
||||||
if ($status->isFailure() || $status->isError()) {
|
|
||||||
$this->currentDefects[$id] = $status;
|
|
||||||
$this->defects[$id] = $status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function status(string $id): TestStatus
|
|
||||||
{
|
|
||||||
return $this->defects[$id] ?? TestStatus::unknown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setTime(string $id, float $time): void
|
|
||||||
{
|
|
||||||
if (! isset($this->currentDefects[$id])) {
|
|
||||||
unset($this->defects[$id]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->times[$id] = $time;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function time(string $id): float
|
|
||||||
{
|
|
||||||
return $this->times[$id] ?? 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function load(): void
|
|
||||||
{
|
|
||||||
if (! is_file($this->cacheFilename)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = json_decode(
|
|
||||||
file_get_contents($this->cacheFilename),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($data === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! isset($data['version'])) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($data['version'] !== $this->cacheVersion()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(isset($data['defects']) && is_array($data['defects']));
|
|
||||||
assert(isset($data['times']) && is_array($data['times']));
|
|
||||||
|
|
||||||
foreach (array_keys($data['defects']) as $test) {
|
|
||||||
$data['defects'][$test] = TestStatus::from($data['defects'][$test]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->defects = $data['defects'];
|
|
||||||
$this->times = $data['times'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function persist(): void
|
|
||||||
{
|
|
||||||
if (! Filesystem::createDirectory(dirname($this->cacheFilename))) {
|
|
||||||
throw new DirectoryCannotBeCreatedException($this->cacheFilename);
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'version' => $this->cacheVersion(),
|
|
||||||
'defects' => [],
|
|
||||||
'times' => $this->times,
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($this->defects as $test => $status) {
|
|
||||||
$data['defects'][$test] = $status->asInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
file_put_contents(
|
|
||||||
$this->cacheFilename,
|
|
||||||
json_encode($data),
|
|
||||||
LOCK_EX
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the cache version.
|
|
||||||
*/
|
|
||||||
private function cacheVersion(): string
|
|
||||||
{
|
|
||||||
return 'pest_'.version();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,302 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of PHPUnit.
|
||||||
|
*
|
||||||
|
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace PHPUnit\Runner\TestRunHistory;
|
||||||
|
|
||||||
|
use const DIRECTORY_SEPARATOR;
|
||||||
|
use const LOCK_EX;
|
||||||
|
use const LOCK_UN;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestStatus\TestStatus;
|
||||||
|
use PHPUnit\Runner\DirectoryDoesNotExistException;
|
||||||
|
use PHPUnit\Runner\Exception;
|
||||||
|
use PHPUnit\Util\Filesystem;
|
||||||
|
|
||||||
|
use function array_keys;
|
||||||
|
use function dirname;
|
||||||
|
use function fclose;
|
||||||
|
use function file_get_contents;
|
||||||
|
use function flock;
|
||||||
|
use function fopen;
|
||||||
|
use function ftruncate;
|
||||||
|
use function fwrite;
|
||||||
|
use function is_array;
|
||||||
|
use function is_dir;
|
||||||
|
use function is_file;
|
||||||
|
use function is_float;
|
||||||
|
use function is_int;
|
||||||
|
use function is_string;
|
||||||
|
use function json_decode;
|
||||||
|
use function json_encode;
|
||||||
|
use function Pest\version;
|
||||||
|
use function rewind;
|
||||||
|
use function stream_get_contents;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
||||||
|
*/
|
||||||
|
final class DefaultTestRunHistory implements TestRunHistory
|
||||||
|
{
|
||||||
|
private const string DEFAULT_FILENAME = '.phpunit.result.cache';
|
||||||
|
|
||||||
|
private readonly string $filename;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<string, TestStatus>
|
||||||
|
*/
|
||||||
|
private array $defects = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<string, float>
|
||||||
|
*/
|
||||||
|
private array $times = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<string, true>
|
||||||
|
*/
|
||||||
|
private array $changedDefects = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<string, true>
|
||||||
|
*/
|
||||||
|
private array $changedTimes = [];
|
||||||
|
|
||||||
|
public function __construct(?string $filepath = null)
|
||||||
|
{
|
||||||
|
if ($filepath !== null && is_dir($filepath)) {
|
||||||
|
$filepath .= DIRECTORY_SEPARATOR.self::DEFAULT_FILENAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->filename = $filepath ?? $_ENV['PHPUNIT_RESULT_CACHE'] ?? self::DEFAULT_FILENAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setStatus(TestRunHistoryId $id, TestStatus $status): void
|
||||||
|
{
|
||||||
|
if ($status->isSuccess()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->defects[$id->asString()] = $status;
|
||||||
|
$this->changedDefects[$id->asString()] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remove(TestRunHistoryId $id): void
|
||||||
|
{
|
||||||
|
unset($this->defects[$id->asString()]);
|
||||||
|
|
||||||
|
$this->changedDefects[$id->asString()] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function status(TestRunHistoryId $id): TestStatus
|
||||||
|
{
|
||||||
|
return $this->defects[$id->asString()] ?? TestStatus::unknown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTime(TestRunHistoryId $id, float $time): void
|
||||||
|
{
|
||||||
|
$this->times[$id->asString()] = $time;
|
||||||
|
$this->changedTimes[$id->asString()] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function time(TestRunHistoryId $id): float
|
||||||
|
{
|
||||||
|
return $this->times[$id->asString()] ?? 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mergeWith(self $other): void
|
||||||
|
{
|
||||||
|
foreach ($other->defects as $id => $defect) {
|
||||||
|
$this->defects[$id] = $defect;
|
||||||
|
$this->changedDefects[$id] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($other->times as $id => $time) {
|
||||||
|
$this->times[$id] = $time;
|
||||||
|
$this->changedTimes[$id] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function load(): void
|
||||||
|
{
|
||||||
|
if (! is_file($this->filename)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contents = file_get_contents($this->filename);
|
||||||
|
|
||||||
|
if ($contents === false) {
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
|
return;
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
$parsed = $this->parse($contents);
|
||||||
|
|
||||||
|
if ($parsed === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$this->defects, $this->times] = $parsed;
|
||||||
|
|
||||||
|
$this->changedDefects = [];
|
||||||
|
$this->changedTimes = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function persist(): void
|
||||||
|
{
|
||||||
|
$this->writeToFile(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function persistAndPrune(): void
|
||||||
|
{
|
||||||
|
$this->writeToFile(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
private function writeToFile(bool $prune): void
|
||||||
|
{
|
||||||
|
if (! Filesystem::createDirectory(dirname($this->filename))) {
|
||||||
|
throw new DirectoryDoesNotExistException(dirname($this->filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
$handle = fopen($this->filename, 'c+');
|
||||||
|
|
||||||
|
if ($handle === false) {
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
|
return;
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
flock($handle, LOCK_EX);
|
||||||
|
|
||||||
|
if ($prune) {
|
||||||
|
$defects = [];
|
||||||
|
|
||||||
|
foreach ($this->defects as $id => $status) {
|
||||||
|
if (isset($this->changedDefects[$id])) {
|
||||||
|
$defects[$id] = $status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$times = [];
|
||||||
|
|
||||||
|
foreach ($this->times as $id => $time) {
|
||||||
|
if (isset($this->changedTimes[$id]) || isset($this->changedDefects[$id])) {
|
||||||
|
$times[$id] = $time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$parsed = $this->parse((string) stream_get_contents($handle));
|
||||||
|
|
||||||
|
if ($parsed !== null) {
|
||||||
|
[$defects, $times] = $parsed;
|
||||||
|
|
||||||
|
foreach (array_keys($this->changedDefects) as $id) {
|
||||||
|
if (isset($this->defects[$id])) {
|
||||||
|
$defects[$id] = $this->defects[$id];
|
||||||
|
} else {
|
||||||
|
unset($defects[$id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->times as $id => $time) {
|
||||||
|
if (isset($this->changedTimes[$id])) {
|
||||||
|
$times[$id] = $time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$defects = $this->defects;
|
||||||
|
$times = $this->times;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'version' => $this->version(),
|
||||||
|
'defects' => [],
|
||||||
|
'times' => $times,
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($defects as $test => $status) {
|
||||||
|
$data['defects'][$test] = $status->asInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
$json = json_encode($data);
|
||||||
|
|
||||||
|
if ($json !== false) {
|
||||||
|
ftruncate($handle, 0);
|
||||||
|
rewind($handle);
|
||||||
|
fwrite($handle, $json);
|
||||||
|
}
|
||||||
|
|
||||||
|
flock($handle, LOCK_UN);
|
||||||
|
fclose($handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ?array{0: array<string, TestStatus>, 1: array<string, float>}
|
||||||
|
*/
|
||||||
|
private function parse(string $contents): ?array
|
||||||
|
{
|
||||||
|
$data = json_decode(
|
||||||
|
$contents,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (! is_array($data)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! isset($data['version']) || $data['version'] !== $this->version()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! isset($data['defects'], $data['times']) || ! is_array($data['defects']) || ! is_array($data['times'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$defects = [];
|
||||||
|
|
||||||
|
foreach ($data['defects'] as $test => $status) {
|
||||||
|
if (! is_string($test) || ! is_int($status)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$defects[$test] = TestStatus::from($status);
|
||||||
|
}
|
||||||
|
|
||||||
|
$times = [];
|
||||||
|
|
||||||
|
foreach ($data['times'] as $test => $time) {
|
||||||
|
if (! is_string($test) || (! is_float($time) && ! is_int($time))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$times[$test] = (float) $time;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$defects, $times];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function version(): string
|
||||||
|
{
|
||||||
|
return 'pest_'.version();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,53 +1,24 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/*
|
|
||||||
* BSD 3-Clause License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2001-2023, Sebastian Bergmann
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace PHPUnit\Runner;
|
namespace PHPUnit\Runner;
|
||||||
|
|
||||||
use function array_diff;
|
|
||||||
use function array_values;
|
|
||||||
use function basename;
|
|
||||||
use function class_exists;
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use function get_declared_classes;
|
|
||||||
use Pest\Contracts\HasPrintableTestCaseName;
|
use Pest\Contracts\HasPrintableTestCaseName;
|
||||||
|
use Pest\Panic;
|
||||||
use Pest\TestCases\IgnorableTestCase;
|
use Pest\TestCases\IgnorableTestCase;
|
||||||
use Pest\TestSuite;
|
use Pest\TestSuite;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use ReflectionClass;
|
use ReflectionClass;
|
||||||
use ReflectionException;
|
use ReflectionException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
use function array_diff;
|
||||||
|
use function array_values;
|
||||||
|
use function basename;
|
||||||
|
use function class_exists;
|
||||||
|
use function get_declared_classes;
|
||||||
use function substr;
|
use function substr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -85,9 +56,13 @@ final class TestSuiteLoader
|
|||||||
$suiteClassName = $this->classNameFromFileName($suiteClassFile);
|
$suiteClassName = $this->classNameFromFileName($suiteClassFile);
|
||||||
|
|
||||||
(static function () use ($suiteClassFile) {
|
(static function () use ($suiteClassFile) {
|
||||||
|
try {
|
||||||
include_once $suiteClassFile;
|
include_once $suiteClassFile;
|
||||||
|
|
||||||
TestSuite::getInstance()->tests->makeIfNeeded($suiteClassFile);
|
TestSuite::getInstance()->tests->makeIfNeeded($suiteClassFile);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
Panic::with($e);
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
$loadedClasses = array_values(
|
$loadedClasses = array_values(
|
||||||
@@ -131,7 +106,7 @@ final class TestSuiteLoader
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($class->isAbstract() || ($class->getFileName() !== $suiteClassFile)) {
|
if ($class->isAbstract() || ($suiteClassFile !== $class->getFileName())) {
|
||||||
if (! str_contains($class->getFileName(), 'TestCaseFactory.php')) {
|
if (! str_contains($class->getFileName(), 'TestCaseFactory.php')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,389 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of PHPUnit.
|
||||||
|
*
|
||||||
|
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace PHPUnit\Runner;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\DataProviderTestSuite;
|
||||||
|
use PHPUnit\Framework\IterativeTestSuite;
|
||||||
|
use PHPUnit\Framework\Reorderable;
|
||||||
|
use PHPUnit\Framework\Test;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use PHPUnit\Framework\TestSuite;
|
||||||
|
use PHPUnit\Runner\TestRunHistory\NullTestRunHistory;
|
||||||
|
use PHPUnit\Runner\TestRunHistory\TestRunHistory;
|
||||||
|
use PHPUnit\Runner\TestRunHistory\TestRunHistoryId;
|
||||||
|
|
||||||
|
use function array_diff;
|
||||||
|
use function array_merge;
|
||||||
|
use function array_reverse;
|
||||||
|
use function array_splice;
|
||||||
|
use function assert;
|
||||||
|
use function count;
|
||||||
|
use function in_array;
|
||||||
|
use function max;
|
||||||
|
use function shuffle;
|
||||||
|
use function usort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
||||||
|
*/
|
||||||
|
final class TestSuiteSorter
|
||||||
|
{
|
||||||
|
public const int ORDER_DEFAULT = 0;
|
||||||
|
|
||||||
|
public const int ORDER_RANDOMIZED = 1;
|
||||||
|
|
||||||
|
public const int ORDER_REVERSED = 2;
|
||||||
|
|
||||||
|
public const int ORDER_DEFECTS_FIRST = 3;
|
||||||
|
|
||||||
|
public const int ORDER_DURATION_ASCENDING = 4;
|
||||||
|
|
||||||
|
public const int ORDER_SIZE_ASCENDING = 5;
|
||||||
|
|
||||||
|
public const int ORDER_DURATION_DESCENDING = 6;
|
||||||
|
|
||||||
|
public const int ORDER_SIZE_DESCENDING = 7;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var non-empty-array<non-empty-string, positive-int>
|
||||||
|
*/
|
||||||
|
private const array SIZE_SORT_WEIGHT = [
|
||||||
|
'small' => 1,
|
||||||
|
'medium' => 2,
|
||||||
|
'large' => 3,
|
||||||
|
'unknown' => 4,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<string, int> Associative array of (string => DEFECT_SORT_WEIGHT) elements
|
||||||
|
*/
|
||||||
|
private array $defectSortOrder = [];
|
||||||
|
|
||||||
|
private readonly TestRunHistory $testRunHistory;
|
||||||
|
|
||||||
|
public function __construct(?TestRunHistory $testRunHistory = null)
|
||||||
|
{
|
||||||
|
$this->testRunHistory = $testRunHistory ?? new NullTestRunHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function reorderTestsInSuite(Test $suite, int $order, bool $resolveDependencies, int $orderDefects): void
|
||||||
|
{
|
||||||
|
$allowedOrders = [
|
||||||
|
self::ORDER_DEFAULT,
|
||||||
|
self::ORDER_REVERSED,
|
||||||
|
self::ORDER_RANDOMIZED,
|
||||||
|
self::ORDER_DURATION_ASCENDING,
|
||||||
|
self::ORDER_SIZE_ASCENDING,
|
||||||
|
self::ORDER_DURATION_DESCENDING,
|
||||||
|
self::ORDER_SIZE_DESCENDING,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! in_array($order, $allowedOrders, true)) {
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
|
throw new InvalidOrderException;
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowedOrderDefects = [
|
||||||
|
self::ORDER_DEFAULT,
|
||||||
|
self::ORDER_DEFECTS_FIRST,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! in_array($orderDefects, $allowedOrderDefects, true)) {
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
|
throw new InvalidOrderException;
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($suite instanceof IterativeTestSuite) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($suite instanceof TestSuite) {
|
||||||
|
foreach ($suite as $_suite) {
|
||||||
|
$this->reorderTestsInSuite($_suite, $order, $resolveDependencies, $orderDefects);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($orderDefects === self::ORDER_DEFECTS_FIRST) {
|
||||||
|
$this->addSuiteToDefectSortOrder($suite);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->sort($suite, $order, $resolveDependencies, $orderDefects);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sort(TestSuite $suite, int $order, bool $resolveDependencies, int $orderDefects): void
|
||||||
|
{
|
||||||
|
$tests = $suite->tests();
|
||||||
|
|
||||||
|
if ($tests === []) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($order === self::ORDER_REVERSED) {
|
||||||
|
$tests = $this->reverse($tests);
|
||||||
|
} elseif ($order === self::ORDER_RANDOMIZED) {
|
||||||
|
$tests = $this->randomize($tests);
|
||||||
|
} elseif ($order === self::ORDER_DURATION_ASCENDING) {
|
||||||
|
$tests = $this->sortByDuration($tests);
|
||||||
|
} elseif ($order === self::ORDER_DURATION_DESCENDING) {
|
||||||
|
$tests = $this->sortByDurationDescending($tests);
|
||||||
|
} elseif ($order === self::ORDER_SIZE_ASCENDING) {
|
||||||
|
$tests = $this->sortBySize($tests);
|
||||||
|
} elseif ($order === self::ORDER_SIZE_DESCENDING) {
|
||||||
|
$tests = $this->sortBySizeDescending($tests);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($orderDefects === self::ORDER_DEFECTS_FIRST) {
|
||||||
|
$tests = $this->sortDefectsFirst($tests);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($resolveDependencies && ! ($suite instanceof DataProviderTestSuite)) {
|
||||||
|
/** @noinspection PhpParamsInspection */
|
||||||
|
/** @phpstan-ignore argument.type */
|
||||||
|
$tests = $this->resolveDependencies($tests);
|
||||||
|
}
|
||||||
|
|
||||||
|
$suite->setTests($tests);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addSuiteToDefectSortOrder(TestSuite $suite): void
|
||||||
|
{
|
||||||
|
$max = 0;
|
||||||
|
|
||||||
|
foreach ($suite->tests() as $test) {
|
||||||
|
assert($test instanceof Reorderable);
|
||||||
|
|
||||||
|
$sortId = $test->sortId();
|
||||||
|
|
||||||
|
if (! isset($this->defectSortOrder[$sortId])) {
|
||||||
|
$this->defectSortOrder[$sortId] = $this->testRunHistory->status(TestRunHistoryId::fromReorderable($test))->sortWeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
$max = max($max, $this->defectSortOrder[$sortId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->defectSortOrder[$suite->sortId()] = $max;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<Test> $tests
|
||||||
|
* @return list<Test>
|
||||||
|
*/
|
||||||
|
private function reverse(array $tests): array
|
||||||
|
{
|
||||||
|
return array_reverse($tests);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<Test> $tests
|
||||||
|
* @return list<Test>
|
||||||
|
*/
|
||||||
|
private function randomize(array $tests): array
|
||||||
|
{
|
||||||
|
shuffle($tests);
|
||||||
|
|
||||||
|
return $tests;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<Test> $tests
|
||||||
|
* @return list<Test>
|
||||||
|
*/
|
||||||
|
private function sortDefectsFirst(array $tests): array
|
||||||
|
{
|
||||||
|
usort(
|
||||||
|
$tests,
|
||||||
|
fn (Test $left, Test $right) => $this->cmpDefectPriorityAndTime($left, $right),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $tests;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<Test> $tests
|
||||||
|
* @return list<Test>
|
||||||
|
*/
|
||||||
|
private function sortByDuration(array $tests): array
|
||||||
|
{
|
||||||
|
usort(
|
||||||
|
$tests,
|
||||||
|
fn (Test $left, Test $right) => $this->cmpDuration($left, $right),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $tests;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<Test> $tests
|
||||||
|
* @return list<Test>
|
||||||
|
*/
|
||||||
|
private function sortByDurationDescending(array $tests): array
|
||||||
|
{
|
||||||
|
usort(
|
||||||
|
$tests,
|
||||||
|
fn (Test $left, Test $right) => $this->cmpDuration($right, $left),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $tests;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<Test> $tests
|
||||||
|
* @return list<Test>
|
||||||
|
*/
|
||||||
|
private function sortBySize(array $tests): array
|
||||||
|
{
|
||||||
|
usort(
|
||||||
|
$tests,
|
||||||
|
fn (Test $left, Test $right) => $this->cmpSize($left, $right),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $tests;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<Test> $tests
|
||||||
|
* @return list<Test>
|
||||||
|
*/
|
||||||
|
private function sortBySizeDescending(array $tests): array
|
||||||
|
{
|
||||||
|
usort(
|
||||||
|
$tests,
|
||||||
|
fn (Test $left, Test $right) => $this->cmpSize($right, $left),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $tests;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cmpDefectPriorityAndTime(Test $a, Test $b): int
|
||||||
|
{
|
||||||
|
assert($a instanceof Reorderable);
|
||||||
|
assert($b instanceof Reorderable);
|
||||||
|
|
||||||
|
$priorityA = $this->defectSortOrder[$a->sortId()] ?? 0;
|
||||||
|
$priorityB = $this->defectSortOrder[$b->sortId()] ?? 0;
|
||||||
|
|
||||||
|
return $priorityB <=> $priorityA;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cmpDuration(Test $a, Test $b): int
|
||||||
|
{
|
||||||
|
return $this->durationWeight($a) <=> $this->durationWeight($b);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function durationWeight(Test $test): float
|
||||||
|
{
|
||||||
|
if ($test instanceof TestSuite) {
|
||||||
|
$sum = 0.0;
|
||||||
|
|
||||||
|
foreach ($test->tests() as $inner) {
|
||||||
|
$sum += $this->durationWeight($inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($test instanceof Reorderable) {
|
||||||
|
return $this->testRunHistory->time(TestRunHistoryId::fromReorderable($test));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cmpSize(Test $a, Test $b): int
|
||||||
|
{
|
||||||
|
return $this->sizeWeight($a) <=> $this->sizeWeight($b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return positive-int
|
||||||
|
*/
|
||||||
|
private function sizeWeight(Test $test): int
|
||||||
|
{
|
||||||
|
if ($test instanceof TestCase || $test instanceof DataProviderTestSuite) {
|
||||||
|
return self::SIZE_SORT_WEIGHT[$test->size()->asString()];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($test instanceof TestSuite) {
|
||||||
|
$max = 0;
|
||||||
|
|
||||||
|
foreach ($test->tests() as $inner) {
|
||||||
|
$weight = $this->sizeWeight($inner);
|
||||||
|
|
||||||
|
if ($weight > $max) {
|
||||||
|
$max = $weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($max > 0) {
|
||||||
|
return $max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::SIZE_SORT_WEIGHT['unknown'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<TestCase> $tests
|
||||||
|
* @return list<TestCase>
|
||||||
|
*/
|
||||||
|
private function resolveDependencies(array $tests): array
|
||||||
|
{
|
||||||
|
if (! $this->anyTestHasDependencies($tests)) {
|
||||||
|
return $tests;
|
||||||
|
}
|
||||||
|
|
||||||
|
$newTestOrder = [];
|
||||||
|
$i = 0;
|
||||||
|
$provided = [];
|
||||||
|
|
||||||
|
while ($tests !== [] && $i < count($tests)) {
|
||||||
|
if (array_diff($tests[$i]->requires(), $provided) === []) {
|
||||||
|
$provided = array_merge($provided, $tests[$i]->provides());
|
||||||
|
$newTestOrder = array_merge($newTestOrder, array_splice($tests, $i, 1));
|
||||||
|
$i = 0;
|
||||||
|
} else {
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_merge($newTestOrder, $tests);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param iterable<Test> $tests
|
||||||
|
*/
|
||||||
|
private function anyTestHasDependencies(iterable $tests): bool
|
||||||
|
{
|
||||||
|
foreach ($tests as $test) {
|
||||||
|
if ($test instanceof TestSuite) {
|
||||||
|
if ($this->anyTestHasDependencies($test->tests())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($test instanceof TestCase && $test->requires() !== []) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-39
@@ -1,37 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/*
|
|
||||||
* BSD 3-Clause License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2001-2023, Sebastian Bergmann
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -45,6 +13,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace PHPUnit\TextUI\Command;
|
namespace PHPUnit\TextUI\Command;
|
||||||
|
|
||||||
|
use const PHP_EOL;
|
||||||
|
|
||||||
use PHPUnit\TextUI\Configuration\CodeCoverageFilterRegistry;
|
use PHPUnit\TextUI\Configuration\CodeCoverageFilterRegistry;
|
||||||
use PHPUnit\TextUI\Configuration\Configuration;
|
use PHPUnit\TextUI\Configuration\Configuration;
|
||||||
use PHPUnit\TextUI\Configuration\NoCoverageCacheDirectoryException;
|
use PHPUnit\TextUI\Configuration\NoCoverageCacheDirectoryException;
|
||||||
@@ -55,11 +25,11 @@ use SebastianBergmann\Timer\Timer;
|
|||||||
/**
|
/**
|
||||||
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
||||||
*/
|
*/
|
||||||
final class WarmCodeCoverageCacheCommand implements Command
|
final readonly class WarmCodeCoverageCacheCommand implements Command
|
||||||
{
|
{
|
||||||
private readonly Configuration $configuration;
|
private Configuration $configuration;
|
||||||
|
|
||||||
private readonly CodeCoverageFilterRegistry $codeCoverageFilterRegistry;
|
private CodeCoverageFilterRegistry $codeCoverageFilterRegistry;
|
||||||
|
|
||||||
public function __construct(Configuration $configuration, CodeCoverageFilterRegistry $codeCoverageFilterRegistry)
|
public function __construct(Configuration $configuration, CodeCoverageFilterRegistry $codeCoverageFilterRegistry)
|
||||||
{
|
{
|
||||||
@@ -76,16 +46,16 @@ final class WarmCodeCoverageCacheCommand implements Command
|
|||||||
if (! $this->configuration->hasCoverageCacheDirectory()) {
|
if (! $this->configuration->hasCoverageCacheDirectory()) {
|
||||||
return Result::from(
|
return Result::from(
|
||||||
'Cache for static analysis has not been configured'.PHP_EOL,
|
'Cache for static analysis has not been configured'.PHP_EOL,
|
||||||
Result::FAILURE
|
Result::FAILURE,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->codeCoverageFilterRegistry->init($this->configuration);
|
$this->codeCoverageFilterRegistry->init($this->configuration, true);
|
||||||
|
|
||||||
if (! $this->codeCoverageFilterRegistry->configured()) {
|
if (! $this->codeCoverageFilterRegistry->configured()) {
|
||||||
return Result::from(
|
return Result::from(
|
||||||
'Filter for code coverage has not been configured'.PHP_EOL,
|
'Filter for code coverage has not been configured'.PHP_EOL,
|
||||||
Result::FAILURE
|
Result::FAILURE,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +66,7 @@ final class WarmCodeCoverageCacheCommand implements Command
|
|||||||
$this->configuration->coverageCacheDirectory(),
|
$this->configuration->coverageCacheDirectory(),
|
||||||
! $this->configuration->disableCodeCoverageIgnore(),
|
! $this->configuration->disableCodeCoverageIgnore(),
|
||||||
$this->configuration->ignoreDeprecatedCodeUnitsFromCodeCoverage(),
|
$this->configuration->ignoreDeprecatedCodeUnitsFromCodeCoverage(),
|
||||||
$this->codeCoverageFilterRegistry->get()
|
$this->codeCoverageFilterRegistry->get(),
|
||||||
);
|
);
|
||||||
|
|
||||||
return Result::from();
|
return Result::from();
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of PHPUnit.
|
||||||
|
*
|
||||||
|
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace PHPUnit\TextUI\Output\Default\ProgressPrinter;
|
||||||
|
|
||||||
|
use PHPUnit\Event\Test\Skipped;
|
||||||
|
use PHPUnit\Event\Test\SkippedSubscriber;
|
||||||
|
use ReflectionClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
||||||
|
*/
|
||||||
|
final readonly class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber
|
||||||
|
{
|
||||||
|
public function notify(Skipped $event): void
|
||||||
|
{
|
||||||
|
if ($event->message() === '__TODO__') {
|
||||||
|
$this->printTodoItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->printer()->testSkipped();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function printTodoItem(): void
|
||||||
|
{
|
||||||
|
$mirror = new ReflectionClass($this->printer());
|
||||||
|
$printProgress = $mirror->getMethod('printProgress');
|
||||||
|
$printProgress->invoke($this->printer(), 'T');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* BSD 3-Clause License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2001-2023, Sebastian Bergmann
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of PHPUnit.
|
|
||||||
*
|
|
||||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace PHPUnit\TextUI\Output\Default\ProgressPrinter;
|
|
||||||
|
|
||||||
use PHPUnit\Event\Test\Skipped;
|
|
||||||
use PHPUnit\Event\Test\SkippedSubscriber;
|
|
||||||
use ReflectionClass;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
|
||||||
*
|
|
||||||
* This file is overridden to allow Pest Parallel to show todo items in the progress output.
|
|
||||||
*/
|
|
||||||
final class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Notifies the printer that a test was skipped.
|
|
||||||
*/
|
|
||||||
public function notify(Skipped $event): void
|
|
||||||
{
|
|
||||||
if (str_contains($event->message(), '__TODO__')) {
|
|
||||||
$this->printTodoItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->printer()->testSkipped();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prints a "T" to the standard PHPUnit output to indicate a todo item.
|
|
||||||
*/
|
|
||||||
private function printTodoItem(): void
|
|
||||||
{
|
|
||||||
$mirror = new ReflectionClass($this->printer());
|
|
||||||
$printerMirror = $mirror->getMethod('printProgress');
|
|
||||||
$printerMirror->invoke($this->printer(), 'T');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/*
|
|
||||||
* BSD 3-Clause License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2001-2023, Sebastian Bergmann
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This file is part of PHPUnit.
|
* This file is part of PHPUnit.
|
||||||
*
|
*
|
||||||
@@ -45,84 +12,95 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace PHPUnit\TextUI;
|
namespace PHPUnit\TextUI;
|
||||||
|
|
||||||
use function array_map;
|
|
||||||
use Pest\Plugins\Only;
|
use Pest\Plugins\Only;
|
||||||
|
use Pest\Runner\Filter\EnsureTestCaseIsInitiatedFilter;
|
||||||
use PHPUnit\Event;
|
use PHPUnit\Event;
|
||||||
use PHPUnit\Framework\TestSuite;
|
use PHPUnit\Framework\TestSuite;
|
||||||
use PHPUnit\Runner\Filter\Factory;
|
use PHPUnit\Runner\Filter\Factory;
|
||||||
use PHPUnit\TextUI\Configuration\Configuration;
|
use PHPUnit\TextUI\Configuration\Configuration;
|
||||||
use PHPUnit\TextUI\Configuration\FilterNotConfiguredException;
|
use PHPUnit\TextUI\Configuration\FilterNotConfiguredException;
|
||||||
|
|
||||||
|
use function array_map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
||||||
*/
|
*/
|
||||||
final class TestSuiteFilterProcessor
|
final readonly class TestSuiteFilterProcessor
|
||||||
{
|
{
|
||||||
private Factory $filterFactory;
|
|
||||||
|
|
||||||
public function __construct(Factory $factory = new Factory)
|
|
||||||
{
|
|
||||||
$this->filterFactory = $factory;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws Event\RuntimeException
|
* @throws Event\RuntimeException
|
||||||
* @throws FilterNotConfiguredException
|
* @throws FilterNotConfiguredException
|
||||||
*/
|
*/
|
||||||
public function process(Configuration $configuration, TestSuite $suite): void
|
public function process(Configuration $configuration, TestSuite $suite): void
|
||||||
{
|
{
|
||||||
|
$factory = new Factory;
|
||||||
|
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
(fn () => $this->filters[] = [
|
||||||
|
'className' => EnsureTestCaseIsInitiatedFilter::class,
|
||||||
|
'argument' => '',
|
||||||
|
])->call($factory);
|
||||||
|
|
||||||
if (! $configuration->hasFilter() &&
|
if (! $configuration->hasFilter() &&
|
||||||
! $configuration->hasGroups() &&
|
! $configuration->hasGroups() &&
|
||||||
! $configuration->hasExcludeGroups() &&
|
! $configuration->hasExcludeGroups() &&
|
||||||
|
! $configuration->hasExcludeFilter() &&
|
||||||
! $configuration->hasTestsCovering() &&
|
! $configuration->hasTestsCovering() &&
|
||||||
! $configuration->hasTestsUsing() &&
|
! $configuration->hasTestsUsing() &&
|
||||||
! Only::isEnabled()
|
! Only::isEnabled()) {
|
||||||
) {
|
$suite->injectFilter($factory);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($configuration->hasExcludeGroups()) {
|
if ($configuration->hasExcludeGroups()) {
|
||||||
$this->filterFactory->addExcludeGroupFilter(
|
$factory->addExcludeGroupFilter(
|
||||||
$configuration->excludeGroups()
|
$configuration->excludeGroups(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Only::isEnabled()) {
|
if (Only::isEnabled()) {
|
||||||
$this->filterFactory->addIncludeGroupFilter(['__pest_only']);
|
$factory->addIncludeGroupFilter([Only::group()]);
|
||||||
} elseif ($configuration->hasGroups()) {
|
} elseif ($configuration->hasGroups()) {
|
||||||
$this->filterFactory->addIncludeGroupFilter(
|
$factory->addIncludeGroupFilter(
|
||||||
$configuration->groups()
|
$configuration->groups(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($configuration->hasTestsCovering()) {
|
if ($configuration->hasTestsCovering()) {
|
||||||
$this->filterFactory->addIncludeGroupFilter(
|
$factory->addIncludeGroupFilter(
|
||||||
array_map(
|
array_map(
|
||||||
static fn (string $name): string => '__phpunit_covers_'.$name,
|
static fn (string $name): string => '__phpunit_covers_'.$name,
|
||||||
$configuration->testsCovering()
|
$configuration->testsCovering(),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($configuration->hasTestsUsing()) {
|
if ($configuration->hasTestsUsing()) {
|
||||||
$this->filterFactory->addIncludeGroupFilter(
|
$factory->addIncludeGroupFilter(
|
||||||
array_map(
|
array_map(
|
||||||
static fn (string $name): string => '__phpunit_uses_'.$name,
|
static fn (string $name): string => '__phpunit_uses_'.$name,
|
||||||
$configuration->testsUsing()
|
$configuration->testsUsing(),
|
||||||
)
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($configuration->hasExcludeFilter()) {
|
||||||
|
$factory->addExcludeNameFilter(
|
||||||
|
$configuration->excludeFilter(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($configuration->hasFilter()) {
|
if ($configuration->hasFilter()) {
|
||||||
$this->filterFactory->addNameFilter(
|
$factory->addIncludeNameFilter(
|
||||||
$configuration->filter()
|
$configuration->filter(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$suite->injectFilter($this->filterFactory);
|
$suite->injectFilter($factory);
|
||||||
|
|
||||||
Event\Facade::emitter()->testSuiteFiltered(
|
Event\Facade::emitter()->testSuiteFiltered(
|
||||||
Event\TestSuite\TestSuiteBuilder::from($suite)
|
Event\TestSuite\TestSuiteBuilder::from($suite),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
parameters:
|
||||||
|
ignoreErrors:
|
||||||
|
-
|
||||||
|
message: '#^Trait Pest\\Concerns\\Expectable is used zero times and is not analysed\.$#'
|
||||||
|
identifier: trait.unused
|
||||||
|
count: 1
|
||||||
|
path: src/Concerns/Expectable.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Trait Pest\\Concerns\\Logging\\WritesToConsole is used zero times and is not analysed\.$#'
|
||||||
|
identifier: trait.unused
|
||||||
|
count: 1
|
||||||
|
path: src/Concerns/Logging/WritesToConsole.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Trait Pest\\Concerns\\Testable is used zero times and is not analysed\.$#'
|
||||||
|
identifier: trait.unused
|
||||||
|
count: 1
|
||||||
|
path: src/Concerns/Testable.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Method Pest\\Expectation\:\:and\(\) should return Pest\\Expectation\<TAndValue\> but returns \(Pest\\Expectation&TAndValue\)\|Pest\\Expectation\<TAndValue of mixed\>\.$#'
|
||||||
|
identifier: return.type
|
||||||
|
count: 1
|
||||||
|
path: src/Expectation.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^PHPDoc tag @property for property Pest\\Expectation\:\:\$each contains generic class Pest\\Expectations\\EachExpectation but does not specify its types\: TValue$#'
|
||||||
|
identifier: missingType.generics
|
||||||
|
count: 1
|
||||||
|
path: src/Expectation.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^PHPDoc tag @property for property Pest\\Expectation\:\:\$not contains generic class Pest\\Expectations\\OppositeExpectation but does not specify its types\: TValue$#'
|
||||||
|
identifier: missingType.generics
|
||||||
|
count: 1
|
||||||
|
path: src/Expectation.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Parameter \#2 \$newScope of method Closure\:\:bindTo\(\) expects ''static''\|class\-string\|object\|null, string given\.$#'
|
||||||
|
identifier: argument.type
|
||||||
|
count: 1
|
||||||
|
path: src/Expectation.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Function expect\(\) should return Pest\\Expectation\<TValue\|null\> but returns Pest\\Expectation\<TValue\|null\>\.$#'
|
||||||
|
identifier: return.type
|
||||||
|
count: 1
|
||||||
|
path: src/Functions.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Parameter \#1 \$argv of method PHPUnit\\TextUI\\Application\:\:run\(\) expects list\<string\>, array\<int, string\> given\.$#'
|
||||||
|
identifier: argument.type
|
||||||
|
count: 1
|
||||||
|
path: src/Kernel.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Call to an undefined method object&TValue of mixed\:\:__toString\(\)\.$#'
|
||||||
|
identifier: method.notFound
|
||||||
|
count: 1
|
||||||
|
path: src/Mixins/Expectation.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Call to an undefined method object&TValue of mixed\:\:toArray\(\)\.$#'
|
||||||
|
identifier: method.notFound
|
||||||
|
count: 4
|
||||||
|
path: src/Mixins/Expectation.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Call to an undefined method object&TValue of mixed\:\:toSnapshot\(\)\.$#'
|
||||||
|
identifier: method.notFound
|
||||||
|
count: 1
|
||||||
|
path: src/Mixins/Expectation.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Call to an undefined method object&TValue of mixed\:\:toString\(\)\.$#'
|
||||||
|
identifier: method.notFound
|
||||||
|
count: 1
|
||||||
|
path: src/Mixins/Expectation.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertTrue\(\) with true will always evaluate to true\.$#'
|
||||||
|
identifier: staticMethod.alreadyNarrowedType
|
||||||
|
count: 2
|
||||||
|
path: src/Mixins/Expectation.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^PHPDoc tag @var with type callable\(\)\: bool is not subtype of native type Closure\|null\.$#'
|
||||||
|
identifier: varTag.nativeType
|
||||||
|
count: 1
|
||||||
|
path: src/PendingCalls/TestCall.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Parameter \#4 \$testErroredEvents of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\<PHPUnit\\Event\\Test\\AfterLastTestMethodErrored\|PHPUnit\\Event\\Test\\BeforeFirstTestMethodErrored\|PHPUnit\\Event\\Test\\Errored\>, array given\.$#'
|
||||||
|
identifier: argument.type
|
||||||
|
count: 1
|
||||||
|
path: src/Plugins/Parallel/Paratest/WrapperRunner.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Parameter \#7 \$testSuiteSkippedEvents of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\<PHPUnit\\Event\\TestSuite\\Skipped\>, array given\.$#'
|
||||||
|
identifier: argument.type
|
||||||
|
count: 1
|
||||||
|
path: src/Plugins/Parallel/Paratest/WrapperRunner.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Parameter \#8 \$testSkippedEvents of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\<PHPUnit\\Event\\Test\\Skipped\>, array given\.$#'
|
||||||
|
identifier: argument.type
|
||||||
|
count: 1
|
||||||
|
path: src/Plugins/Parallel/Paratest/WrapperRunner.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Parameter \#9 \$testMarkedIncompleteEvents of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\<PHPUnit\\Event\\Test\\MarkedIncomplete\>, array given\.$#'
|
||||||
|
identifier: argument.type
|
||||||
|
count: 1
|
||||||
|
path: src/Plugins/Parallel/Paratest/WrapperRunner.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Property Pest\\Plugins\\Parallel\\Paratest\\WrapperRunner\:\:\$pending \(list\<non\-empty\-string\>\) does not accept array\<int, non\-empty\-string\>\.$#'
|
||||||
|
identifier: assign.propertyType
|
||||||
|
count: 1
|
||||||
|
path: src/Plugins/Parallel/Paratest/WrapperRunner.php
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
services:
|
||||||
|
-
|
||||||
|
class: Pest\PHPStan\HigherOrderExpectationTypeExtension
|
||||||
|
tags:
|
||||||
|
- phpstan.broker.expressionTypeResolverExtension
|
||||||
+5
-17
@@ -1,23 +1,11 @@
|
|||||||
includes:
|
includes:
|
||||||
- vendor/phpstan/phpstan-strict-rules/rules.neon
|
- phpstan-baseline.neon
|
||||||
- vendor/ergebnis/phpstan-rules/rules.neon
|
- phpstan-pest-extension.neon
|
||||||
- vendor/thecodingmachine/phpstan-strict-rules/phpstan-strict-rules.neon
|
- vendor/pestphp/pest-plugin-phpstan/extension.neon
|
||||||
|
|
||||||
parameters:
|
parameters:
|
||||||
level: max
|
level: 7
|
||||||
paths:
|
paths:
|
||||||
- src
|
- src
|
||||||
|
|
||||||
checkMissingIterableValueType: true
|
reportUnmatchedIgnoredErrors: false
|
||||||
reportUnmatchedIgnoredErrors: true
|
|
||||||
|
|
||||||
ignoreErrors:
|
|
||||||
- "#has a nullable return type declaration.#"
|
|
||||||
- "#Language construct isset\\(\\) should not be used.#"
|
|
||||||
- "#is not allowed to extend#"
|
|
||||||
- "#is concrete, but does not have a Test suffix#"
|
|
||||||
- "#with a nullable type declaration#"
|
|
||||||
- "#type mixed is not subtype of native#"
|
|
||||||
- "# with null as default value#"
|
|
||||||
- "#has parameter \\$closure with default value.#"
|
|
||||||
- "#has parameter \\$description with default value.#"
|
|
||||||
|
|||||||
+3
-1
@@ -16,9 +16,11 @@
|
|||||||
<testsuites>
|
<testsuites>
|
||||||
<testsuite name="default">
|
<testsuite name="default">
|
||||||
<directory suffix=".php">./tests</directory>
|
<directory suffix=".php">./tests</directory>
|
||||||
|
<directory suffix=".php">./tests-external</directory>
|
||||||
<exclude>./tests/.snapshots</exclude>
|
<exclude>./tests/.snapshots</exclude>
|
||||||
<exclude>./tests/.tests</exclude>
|
|
||||||
<exclude>./tests/Fixtures/Inheritance</exclude>
|
<exclude>./tests/Fixtures/Inheritance</exclude>
|
||||||
|
<exclude>./tests/Fixtures/Suites</exclude>
|
||||||
|
<exclude>./tests/Fixtures/Tia</exclude>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuites>
|
</testsuites>
|
||||||
<source>
|
<source>
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"exclude": [
|
||||||
|
"tests/Fixtures/Suites"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"Pint/phpdoc_type_annotations_only": true
|
||||||
|
}
|
||||||
|
}
|
||||||
+54
-23
@@ -2,30 +2,61 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector;
|
use Pest\Rector\Rules\UseToMatchArrayRector;
|
||||||
|
use Pest\Rector\Set\PestSetList;
|
||||||
|
use Rector\CodingStyle\Rector\ArrowFunction\ArrowFunctionDelegatingCallToFirstClassCallableRector;
|
||||||
use Rector\Config\RectorConfig;
|
use Rector\Config\RectorConfig;
|
||||||
use Rector\Set\ValueObject\LevelSetList;
|
use Rector\DeadCode\Rector\ClassMethod\RemoveDuplicatedReturnSelfDocblockRector;
|
||||||
use Rector\Set\ValueObject\SetList;
|
use Rector\DeadCode\Rector\ClassMethod\RemoveEmptyClassMethodRector;
|
||||||
|
use Rector\DeadCode\Rector\ClassMethod\RemoveParentDelegatingConstructorRector;
|
||||||
|
use Rector\DeadCode\Rector\ClassMethod\RemoveReturnTagIncompatibleWithNativeTypeRector;
|
||||||
|
use Rector\DeadCode\Rector\ClassMethod\RemoveUnusedConstructorParamRector;
|
||||||
|
use Rector\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector;
|
||||||
|
use Rector\DeadCode\Rector\Property\RemoveUnusedPrivatePropertyRector;
|
||||||
|
use Rector\TypeDeclaration\Rector\ArrowFunction\AddArrowFunctionReturnTypeRector;
|
||||||
|
use Rector\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector;
|
||||||
|
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnNeverTypeRector;
|
||||||
|
|
||||||
return static function (RectorConfig $rectorConfig): void {
|
return RectorConfig::configure()
|
||||||
$rectorConfig->paths([
|
->withPaths([
|
||||||
__DIR__.'/src',
|
__DIR__.'/src',
|
||||||
]);
|
__DIR__.'/tests',
|
||||||
|
])
|
||||||
$rectorConfig->rules([
|
->withSets([
|
||||||
InlineConstructorDefaultToPropertyRector::class,
|
PestSetList::CODING_STYLE,
|
||||||
]);
|
])
|
||||||
|
->withSkip([
|
||||||
$rectorConfig->skip([
|
|
||||||
__DIR__.'/src/Plugins/Parallel/Paratest/WrapperRunner.php',
|
__DIR__.'/src/Plugins/Parallel/Paratest/WrapperRunner.php',
|
||||||
]);
|
__DIR__.'/tests/Fixtures/Arch',
|
||||||
|
__DIR__.'/tests/Fixtures/Suites',
|
||||||
$rectorConfig->sets([
|
ReturnNeverTypeRector::class,
|
||||||
LevelSetList::UP_TO_PHP_81,
|
ArrowFunctionDelegatingCallToFirstClassCallableRector::class,
|
||||||
SetList::CODE_QUALITY,
|
NarrowObjectReturnTypeRector::class,
|
||||||
SetList::DEAD_CODE,
|
RemoveParentDelegatingConstructorRector::class,
|
||||||
SetList::EARLY_RETURN,
|
RemoveDuplicatedReturnSelfDocblockRector::class,
|
||||||
SetList::TYPE_DECLARATION,
|
RemoveUselessUnionReturnDocblockRector::class,
|
||||||
SetList::PRIVATIZATION,
|
RemoveReturnTagIncompatibleWithNativeTypeRector::class => [
|
||||||
]);
|
__DIR__.'/src/Expectations/HigherOrderExpectation.php',
|
||||||
};
|
],
|
||||||
|
UseToMatchArrayRector::class,
|
||||||
|
RemoveEmptyClassMethodRector::class => [
|
||||||
|
__DIR__.'/tests',
|
||||||
|
],
|
||||||
|
RemoveUnusedConstructorParamRector::class => [
|
||||||
|
__DIR__.'/tests',
|
||||||
|
],
|
||||||
|
RemoveUnusedPrivatePropertyRector::class => [
|
||||||
|
__DIR__.'/tests',
|
||||||
|
],
|
||||||
|
AddArrowFunctionReturnTypeRector::class => [
|
||||||
|
__DIR__.'/tests',
|
||||||
|
],
|
||||||
|
])
|
||||||
|
->withPreparedSets(
|
||||||
|
deadCode: true,
|
||||||
|
codeQuality: true,
|
||||||
|
typeDeclarations: true,
|
||||||
|
privatization: true,
|
||||||
|
earlyReturn: true,
|
||||||
|
)
|
||||||
|
->withPhpSets();
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||||
|
bootstrap="vendor/autoload.php"
|
||||||
|
colors="true"
|
||||||
|
>
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="Default">
|
||||||
|
<directory>tests/</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
<source>
|
||||||
|
<include>
|
||||||
|
<directory>app</directory>
|
||||||
|
<directory>src</directory>
|
||||||
|
</include>
|
||||||
|
</source>
|
||||||
|
<php>
|
||||||
|
<env name="APP_ENV" value="testing"/>
|
||||||
|
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
|
||||||
|
<env name="BCRYPT_ROUNDS" value="4"/>
|
||||||
|
<env name="CACHE_STORE" value="array"/>
|
||||||
|
<!-- <env name="DB_CONNECTION" value="sqlite"/> -->
|
||||||
|
<!-- <env name="DB_DATABASE" value=":memory:"/> -->
|
||||||
|
<env name="MAIL_MAILER" value="array"/>
|
||||||
|
<env name="PULSE_ENABLED" value="false"/>
|
||||||
|
<env name="QUEUE_CONNECTION" value="sync"/>
|
||||||
|
<env name="SESSION_DRIVER" value="array"/>
|
||||||
|
<env name="TELESCOPE_ENABLED" value="false"/>
|
||||||
|
</php>
|
||||||
|
</phpunit>
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
---
|
||||||
|
name: pest-testing
|
||||||
|
description: "Use this skill for Pest PHP testing in Laravel projects only. Trigger whenever any test is being written, edited, fixed, or refactored — including fixing tests that broke after a code change, adding assertions, converting PHPUnit to Pest, adding datasets, and TDD workflows. Always activate when the user asks how to write something in Pest, mentions test files or directories (tests/Feature, tests/Unit, tests/Browser), or needs browser testing, smoke testing multiple pages for JS errors, architecture tests, or faster test runs with Test Impact Analysis. Covers: test()/it()/expect() syntax, datasets, mocking, browser testing (visit/click/fill), smoke testing, arch(), Livewire component tests, RefreshDatabase, Tia (--tia), sharding, and all Pest 5 features. Do not use for factories, seeders, migrations, controllers, models, or non-test PHP code."
|
||||||
|
license: MIT
|
||||||
|
metadata:
|
||||||
|
author: laravel
|
||||||
|
---
|
||||||
|
@php
|
||||||
|
/** @var \Laravel\Boost\Install\GuidelineAssist $assist */
|
||||||
|
@endphp
|
||||||
|
# Pest Testing 5
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Use `search-docs` for detailed Pest 5 patterns and documentation.
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
### Creating Tests
|
||||||
|
|
||||||
|
All tests must be written using Pest. Use `{{ $assist->artisanCommand('make:test --pest {name}') }}`.
|
||||||
|
|
||||||
|
The `{name}` argument should include only the path and test name, but should not include the test suite.
|
||||||
|
- Incorrect: `{{ $assist->artisanCommand('make:test --pest Feature/SomeFeatureTest') }}` will generate `tests/Feature/Feature/SomeFeatureTest.php`
|
||||||
|
- Correct: `{{ $assist->artisanCommand('make:test --pest SomeControllerTest') }}` will generate `tests/Feature/SomeControllerTest.php`
|
||||||
|
- Incorrect: `{{ $assist->artisanCommand('make:test --pest --unit Unit/SomeServiceTest') }}` will generate `tests/Unit/Unit/SomeServiceTest.php`
|
||||||
|
- Correct: `{{ $assist->artisanCommand('make:test --pest --unit SomeServiceTest') }}` will generate `tests/Unit/SomeServiceTest.php`
|
||||||
|
|
||||||
|
### Test Organization
|
||||||
|
|
||||||
|
- Unit/Feature tests: `tests/Feature` and `tests/Unit` directories.
|
||||||
|
- Browser tests: `tests/Browser/` directory.
|
||||||
|
- Do NOT remove tests without approval - these are core application code.
|
||||||
|
|
||||||
|
### Basic Test Structure
|
||||||
|
|
||||||
|
Pest supports both `test()` and `it()` functions. Before writing new tests, check existing test files in the same directory to match the project's convention. Use `test()` if existing tests use `test()`, or `it()` if they use `it()`.
|
||||||
|
|
||||||
|
@boostsnippet("Basic Pest Test Example", "php")
|
||||||
|
it('is true', function () {
|
||||||
|
expect(true)->toBeTrue();
|
||||||
|
});
|
||||||
|
@endboostsnippet
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
- Run minimal tests with filter before finalizing: `{{ $assist->artisanCommand('test --compact --filter=testName') }}`.
|
||||||
|
- Run all tests: `{{ $assist->artisanCommand('test --compact') }}`.
|
||||||
|
- Run file: `{{ $assist->artisanCommand('test --compact tests/Feature/ExampleTest.php') }}`.
|
||||||
|
- Run only tests affected by recent changes (Tia): `./vendor/bin/pest --parallel --tia`.
|
||||||
|
|
||||||
|
## Assertions
|
||||||
|
|
||||||
|
Use specific assertions (`assertSuccessful()`, `assertNotFound()`) instead of `assertStatus()`:
|
||||||
|
|
||||||
|
@boostsnippet("Pest Response Assertion", "php")
|
||||||
|
it('returns all', function () {
|
||||||
|
$this->postJson('/api/docs', [])->assertSuccessful();
|
||||||
|
});
|
||||||
|
@endboostsnippet
|
||||||
|
|
||||||
|
| Use | Instead of |
|
||||||
|
|-----|------------|
|
||||||
|
| `assertSuccessful()` | `assertStatus(200)` |
|
||||||
|
| `assertNotFound()` | `assertStatus(404)` |
|
||||||
|
| `assertForbidden()` | `assertStatus(403)` |
|
||||||
|
|
||||||
|
## Mocking
|
||||||
|
|
||||||
|
Import mock function before use: `use function Pest\Laravel\mock;`
|
||||||
|
|
||||||
|
## Datasets
|
||||||
|
|
||||||
|
Use datasets for repetitive tests (validation rules, etc.):
|
||||||
|
|
||||||
|
@boostsnippet("Pest Dataset Example", "php")
|
||||||
|
it('has emails', function (string $email) {
|
||||||
|
expect($email)->not->toBeEmpty();
|
||||||
|
})->with([
|
||||||
|
'james' => 'james@laravel.com',
|
||||||
|
'taylor' => 'taylor@laravel.com',
|
||||||
|
]);
|
||||||
|
@endboostsnippet
|
||||||
|
|
||||||
|
## Pest 5 Features
|
||||||
|
|
||||||
|
| Feature | Purpose |
|
||||||
|
|---------|---------|
|
||||||
|
| Tia (Test Impact Analysis) | Rerun only tests affected by recent changes |
|
||||||
|
| Time-Balanced Sharding | Split tests across CI shards by execution time |
|
||||||
|
| New Validation Expectations | `toBeEmail()`, `toBeUlid()`, `toBeIpAddress()`, and more |
|
||||||
|
| Browser Testing | Full integration tests in real browsers |
|
||||||
|
| Smoke Testing | Validate multiple pages quickly |
|
||||||
|
| Visual Regression | Compare screenshots for visual changes |
|
||||||
|
| Architecture Testing | Enforce code conventions |
|
||||||
|
|
||||||
|
### Tia (Test Impact Analysis)
|
||||||
|
|
||||||
|
Tia reruns only tests affected by recent changes and replays cached results for the rest, dramatically reducing suite duration:
|
||||||
|
|
||||||
|
@boostsnippet("Tia Example", "shell")
|
||||||
|
./vendor/bin/pest --parallel --tia
|
||||||
|
@endboostsnippet
|
||||||
|
|
||||||
|
- Replayed tests are not skipped — cached tests store everything they produced, including covered lines and branches.
|
||||||
|
- Detects Laravel, Symfony, Livewire, and Inertia automatically.
|
||||||
|
|
||||||
|
### New Validation Expectations
|
||||||
|
|
||||||
|
Pest 5 ships eight new validation matchers, all supporting `.not` negation:
|
||||||
|
|
||||||
|
@boostsnippet("Pest 5 Validation Expectations", "php")
|
||||||
|
expect('nuno@pestphp.com')->toBeEmail();
|
||||||
|
expect('01ARZ3NDEKTSV4RRFFQ69G5FAV')->toBeUlid();
|
||||||
|
expect('192.168.1.1')->toBeIpAddress();
|
||||||
|
expect('00:1a:2b:3c:4d:5e')->toBeMacAddress();
|
||||||
|
expect('example.com')->toBeHostname();
|
||||||
|
expect('example.co.uk')->toBeDomain();
|
||||||
|
expect('Zm9vYmFy')->toBeBase64();
|
||||||
|
expect('deadbeef')->toBeHexadecimal();
|
||||||
|
@endboostsnippet
|
||||||
|
|
||||||
|
### Time-Balanced Sharding
|
||||||
|
|
||||||
|
Distribute tests across CI shards by execution time rather than count:
|
||||||
|
|
||||||
|
@boostsnippet("Pest Sharding Example", "shell")
|
||||||
|
./vendor/bin/pest --update-shards
|
||||||
|
./vendor/bin/pest --shard=1/4
|
||||||
|
@endboostsnippet
|
||||||
|
|
||||||
|
Commit `tests/.pest/shards.json` to the repository so CI shards stay consistent.
|
||||||
|
|
||||||
|
### Browser Test Example
|
||||||
|
|
||||||
|
Browser tests run in real browsers for full integration testing:
|
||||||
|
|
||||||
|
- Browser tests live in `tests/Browser/`.
|
||||||
|
- Use Laravel features like `Event::fake()`, `assertAuthenticated()`, and model factories.
|
||||||
|
- Use `RefreshDatabase` for clean state per test.
|
||||||
|
- Interact with page: click, type, scroll, select, submit, drag-and-drop, touch gestures.
|
||||||
|
- Test on multiple browsers (Chrome, Firefox, Safari) if requested.
|
||||||
|
- Test on different devices/viewports (iPhone 14 Pro, tablets) if requested.
|
||||||
|
- Switch color schemes (light/dark mode) when appropriate.
|
||||||
|
- Take screenshots or pause tests for debugging.
|
||||||
|
|
||||||
|
@boostsnippet("Pest Browser Test Example", "php")
|
||||||
|
it('may reset the password', function () {
|
||||||
|
Notification::fake();
|
||||||
|
|
||||||
|
$this->actingAs(User::factory()->create());
|
||||||
|
|
||||||
|
$page = visit('/sign-in');
|
||||||
|
|
||||||
|
$page->assertSee('Sign In')
|
||||||
|
->assertNoJavaScriptErrors()
|
||||||
|
->click('Forgot Password?')
|
||||||
|
->fill('email', 'nuno@laravel.com')
|
||||||
|
->click('Send Reset Link')
|
||||||
|
->assertSee('We have emailed your password reset link!');
|
||||||
|
|
||||||
|
Notification::assertSent(ResetPassword::class);
|
||||||
|
});
|
||||||
|
@endboostsnippet
|
||||||
|
|
||||||
|
### Smoke Testing
|
||||||
|
|
||||||
|
Quickly validate multiple pages have no JavaScript errors:
|
||||||
|
|
||||||
|
@boostsnippet("Pest Smoke Testing Example", "php")
|
||||||
|
$pages = visit(['/', '/about', '/contact']);
|
||||||
|
|
||||||
|
$pages->assertNoJavaScriptErrors()->assertNoConsoleLogs();
|
||||||
|
@endboostsnippet
|
||||||
|
|
||||||
|
### Visual Regression Testing
|
||||||
|
|
||||||
|
Capture and compare screenshots to detect visual changes.
|
||||||
|
|
||||||
|
### Architecture Testing
|
||||||
|
|
||||||
|
@boostsnippet("Architecture Test Example", "php")
|
||||||
|
arch('controllers')
|
||||||
|
->expect('App\Http\Controllers')
|
||||||
|
->toExtendNothing()
|
||||||
|
->toHaveSuffix('Controller');
|
||||||
|
@endboostsnippet
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
- Not importing `use function Pest\Laravel\mock;` before using mock
|
||||||
|
- Using `assertStatus(200)` instead of `assertSuccessful()`
|
||||||
|
- Forgetting datasets for repetitive validation tests
|
||||||
|
- Deleting tests without approval
|
||||||
|
- Forgetting `assertNoJavaScriptErrors()` in browser tests
|
||||||
|
- Prefixing `Feature/` or `Unit/` in `{name}` when using `make:test`
|
||||||
@@ -5,12 +5,14 @@
|
|||||||
[$bgBadgeColor, $bgBadgeText] = match ($type) {
|
[$bgBadgeColor, $bgBadgeText] = match ($type) {
|
||||||
'INFO' => ['blue', 'INFO'],
|
'INFO' => ['blue', 'INFO'],
|
||||||
'ERROR' => ['red', 'ERROR'],
|
'ERROR' => ['red', 'ERROR'],
|
||||||
|
'WARN' => ['yellow', 'WARN'],
|
||||||
|
'SUCCESS' => ['green', 'SUCCESS'],
|
||||||
};
|
};
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="my-1">
|
<div class="my-1">
|
||||||
<span class="ml-2 px-1 bg-<?php echo $bgBadgeColor ?>-600 font-bold"><?php echo htmlspecialchars($bgBadgeText) ?></span>
|
<span class="ml-2 px-1 bg-<?php echo $bgBadgeColor ?> font-bold"><?php echo htmlspecialchars($bgBadgeText) ?></span>
|
||||||
<span class="ml-1">
|
<span class="ml-1">
|
||||||
<?php echo htmlspecialchars($content) ?>
|
<?php echo htmlspecialchars($content) ?>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<div class="mx-2 mb-1">
|
||||||
|
<p>
|
||||||
|
<span>Using the <span class="text-yellow font-bold">visit()</span> function requires the Pest Plugin Browser to be installed.</span>
|
||||||
|
|
||||||
|
<span class="ml-1 text-yellow font-bold">Run:</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span class="text-gray mr-1">- </span>
|
||||||
|
<span>composer require pestphp/pest-plugin-browser:^5.0 --dev</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span class="text-gray mr-1">- </span>
|
||||||
|
<span>npm install playwright@latest</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span class="text-gray mr-1">- </span>
|
||||||
|
<span>npx playwright install</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\ArchPresets;
|
||||||
|
|
||||||
|
use Pest\Arch\Contracts\ArchExpectation;
|
||||||
|
use Pest\Expectation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
abstract class AbstractPreset // @pest-arch-ignore-line
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array<int, Expectation<mixed>|ArchExpectation>
|
||||||
|
*/
|
||||||
|
protected array $expectations = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, string> $userNamespaces
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
private readonly array $userNamespaces,
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
abstract public function execute(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, string>|string $targetsOrDependencies
|
||||||
|
*/
|
||||||
|
final public function ignoring(array|string $targetsOrDependencies): void
|
||||||
|
{
|
||||||
|
$this->expectations = array_map(
|
||||||
|
fn (ArchExpectation|Expectation $expectation): Expectation|ArchExpectation => $expectation instanceof ArchExpectation ? $expectation->ignoring($targetsOrDependencies) : $expectation,
|
||||||
|
$this->expectations,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable(Expectation<string>): ArchExpectation ...$callbacks
|
||||||
|
*/
|
||||||
|
final public function eachUserNamespace(callable ...$callbacks): void
|
||||||
|
{
|
||||||
|
foreach ($this->userNamespaces as $namespace) {
|
||||||
|
foreach ($callbacks as $callback) {
|
||||||
|
$this->expectations[] = $callback(expect($namespace));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final public function flush(): void
|
||||||
|
{
|
||||||
|
$this->expectations = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\ArchPresets;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Pest\Arch\Contracts\ArchExpectation;
|
||||||
|
use Pest\Expectation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class Custom extends AbstractPreset
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param array<int, string> $userNamespaces
|
||||||
|
* @param Closure(array<int, string>): array<Expectation<mixed>|ArchExpectation> $execute
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
private readonly array $userNamespaces,
|
||||||
|
private readonly string $name,
|
||||||
|
private readonly Closure $execute,
|
||||||
|
) {
|
||||||
|
parent::__construct($userNamespaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function name(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(): void
|
||||||
|
{
|
||||||
|
$this->expectations = ($this->execute)($this->userNamespaces);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\ArchPresets;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class Laravel extends AbstractPreset
|
||||||
|
{
|
||||||
|
public function execute(): void
|
||||||
|
{
|
||||||
|
$this->expectations[] = expect('App\Traits')
|
||||||
|
->toBeTraits();
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Concerns')
|
||||||
|
->toBeTraits();
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App')
|
||||||
|
->not->toBeEnums()
|
||||||
|
->ignoring('App\Enums');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Enums')
|
||||||
|
->toBeEnums()
|
||||||
|
->ignoring('App\Enums\Concerns');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Features')
|
||||||
|
->toBeClasses()
|
||||||
|
->ignoring('App\Features\Concerns');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Features')
|
||||||
|
->toHaveMethod('resolve')
|
||||||
|
->ignoring('App\Features\Concerns');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Exceptions')
|
||||||
|
->classes()
|
||||||
|
->toImplement('Throwable')
|
||||||
|
->ignoring('App\Exceptions\Handler');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App')
|
||||||
|
->not->toImplement(Throwable::class)
|
||||||
|
->ignoring('App\Exceptions');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Http\Middleware')
|
||||||
|
->classes()
|
||||||
|
->toHaveMethod('handle');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Models')
|
||||||
|
->classes()
|
||||||
|
->toExtend('Illuminate\Database\Eloquent\Model')
|
||||||
|
->ignoring('App\Models\Scopes');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Models')
|
||||||
|
->classes()
|
||||||
|
->not->toHaveSuffix('Model');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App')
|
||||||
|
->not->toExtend('Illuminate\Database\Eloquent\Model')
|
||||||
|
->ignoring('App\Models');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Http\Requests')
|
||||||
|
->classes()
|
||||||
|
->toHaveSuffix('Request');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Http\Requests')
|
||||||
|
->classes()
|
||||||
|
->toExtend('Illuminate\Foundation\Http\FormRequest');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Http\Requests')
|
||||||
|
->toHaveMethod('rules');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App')
|
||||||
|
->not->toExtend('Illuminate\Foundation\Http\FormRequest')
|
||||||
|
->ignoring('App\Http\Requests');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Console\Commands')
|
||||||
|
->classes()
|
||||||
|
->toHaveSuffix('Command');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Console\Commands')
|
||||||
|
->classes()
|
||||||
|
->toExtend('Illuminate\Console\Command');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Console\Commands')
|
||||||
|
->classes()
|
||||||
|
->toHaveMethod('handle');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App')
|
||||||
|
->not->toExtend('Illuminate\Console\Command')
|
||||||
|
->ignoring('App\Console\Commands');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Mail')
|
||||||
|
->classes()
|
||||||
|
->toExtend('Illuminate\Mail\Mailable');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Mail')
|
||||||
|
->classes()
|
||||||
|
->toImplement('Illuminate\Contracts\Queue\ShouldQueue');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App')
|
||||||
|
->not->toExtend('Illuminate\Mail\Mailable')
|
||||||
|
->ignoring('App\Mail');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Jobs')
|
||||||
|
->classes()
|
||||||
|
->toImplement('Illuminate\Contracts\Queue\ShouldQueue');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Jobs')
|
||||||
|
->classes()
|
||||||
|
->toHaveMethod('handle');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Listeners')
|
||||||
|
->toHaveMethod('handle');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Notifications')
|
||||||
|
->classes()
|
||||||
|
->toExtend('Illuminate\Notifications\Notification');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App')
|
||||||
|
->not->toExtend('Illuminate\Notifications\Notification')
|
||||||
|
->ignoring('App\Notifications');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Providers')
|
||||||
|
->toHaveSuffix('ServiceProvider');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Providers')
|
||||||
|
->classes()
|
||||||
|
->toExtend('Illuminate\Support\ServiceProvider');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Providers')
|
||||||
|
->not->toBeUsed();
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App')
|
||||||
|
->not->toExtend('Illuminate\Support\ServiceProvider')
|
||||||
|
->ignoring('App\Providers');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App')
|
||||||
|
->not->toHaveSuffix('ServiceProvider')
|
||||||
|
->ignoring('App\Providers');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App')
|
||||||
|
->not->toHaveSuffix('Controller')
|
||||||
|
->ignoring('App\Http\Controllers');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Http\Controllers')
|
||||||
|
->classes()
|
||||||
|
->toHaveSuffix('Controller');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Http')
|
||||||
|
->toOnlyBeUsedIn(['App\Http', 'App\Providers']);
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Http\Controllers')
|
||||||
|
->not->toHavePublicMethodsBesides(['__construct', '__invoke', 'index', 'show', 'create', 'store', 'edit', 'update', 'destroy', 'middleware']);
|
||||||
|
|
||||||
|
$this->expectations[] = expect([
|
||||||
|
'dd',
|
||||||
|
'ddd',
|
||||||
|
'dump',
|
||||||
|
'env',
|
||||||
|
'exit',
|
||||||
|
'ray',
|
||||||
|
])->not->toBeUsed();
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Policies')
|
||||||
|
->classes()
|
||||||
|
->toHaveSuffix('Policy');
|
||||||
|
|
||||||
|
$this->expectations[] = expect('App\Attributes')
|
||||||
|
->classes()
|
||||||
|
->toImplement('Illuminate\Contracts\Container\ContextualAttribute')
|
||||||
|
->toHaveAttribute('Attribute')
|
||||||
|
->toHaveMethod('resolve');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\ArchPresets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class Php extends AbstractPreset
|
||||||
|
{
|
||||||
|
public function execute(): void
|
||||||
|
{
|
||||||
|
$this->expectations[] = expect([
|
||||||
|
'debug_zval_dump',
|
||||||
|
'debug_backtrace',
|
||||||
|
'debug_print_backtrace',
|
||||||
|
'dump',
|
||||||
|
'ray',
|
||||||
|
'ds',
|
||||||
|
'die',
|
||||||
|
'goto',
|
||||||
|
'global',
|
||||||
|
'var_dump',
|
||||||
|
'phpinfo',
|
||||||
|
'echo',
|
||||||
|
'ereg',
|
||||||
|
'eregi',
|
||||||
|
'mysql_connect',
|
||||||
|
'mysql_pconnect',
|
||||||
|
'mysql_query',
|
||||||
|
'mysql_select_db',
|
||||||
|
'mysql_fetch_array',
|
||||||
|
'mysql_fetch_assoc',
|
||||||
|
'mysql_fetch_object',
|
||||||
|
'mysql_fetch_row',
|
||||||
|
'mysql_num_rows',
|
||||||
|
'mysql_affected_rows',
|
||||||
|
'mysql_free_result',
|
||||||
|
'mysql_insert_id',
|
||||||
|
'mysql_error',
|
||||||
|
'mysql_real_escape_string',
|
||||||
|
'print',
|
||||||
|
'print_r',
|
||||||
|
'var_export',
|
||||||
|
'xdebug_break',
|
||||||
|
'xdebug_call_class',
|
||||||
|
'xdebug_call_file',
|
||||||
|
'xdebug_call_int',
|
||||||
|
'xdebug_call_line',
|
||||||
|
'xdebug_code_coverage_started',
|
||||||
|
'xdebug_connect_to_client',
|
||||||
|
'xdebug_debug_zval',
|
||||||
|
'xdebug_debug_zval_stdout',
|
||||||
|
'xdebug_dump_superglobals',
|
||||||
|
'xdebug_get_code_coverage',
|
||||||
|
'xdebug_get_collected_errors',
|
||||||
|
'xdebug_get_function_count',
|
||||||
|
'xdebug_get_function_stack',
|
||||||
|
'xdebug_get_gc_run_count',
|
||||||
|
'xdebug_get_gc_total_collected_roots',
|
||||||
|
'xdebug_get_gcstats_filename',
|
||||||
|
'xdebug_get_headers',
|
||||||
|
'xdebug_get_monitored_functions',
|
||||||
|
'xdebug_get_profiler_filename',
|
||||||
|
'xdebug_get_stack_depth',
|
||||||
|
'xdebug_get_tracefile_name',
|
||||||
|
'xdebug_info',
|
||||||
|
'xdebug_is_debugger_active',
|
||||||
|
'xdebug_memory_usage',
|
||||||
|
'xdebug_notify',
|
||||||
|
'xdebug_peak_memory_usage',
|
||||||
|
'xdebug_print_function_stack',
|
||||||
|
'xdebug_set_filter',
|
||||||
|
'xdebug_start_code_coverage',
|
||||||
|
'xdebug_start_error_collection',
|
||||||
|
'xdebug_start_function_monitor',
|
||||||
|
'xdebug_start_gcstats',
|
||||||
|
'xdebug_start_trace',
|
||||||
|
'xdebug_stop_code_coverage',
|
||||||
|
'xdebug_stop_error_collection',
|
||||||
|
'xdebug_stop_function_monitor',
|
||||||
|
'xdebug_stop_gcstats',
|
||||||
|
'xdebug_stop_trace',
|
||||||
|
'xdebug_time_index',
|
||||||
|
'xdebug_var_dump',
|
||||||
|
'trap',
|
||||||
|
])->not->toBeUsed();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\ArchPresets;
|
||||||
|
|
||||||
|
use Pest\Arch\Contracts\ArchExpectation;
|
||||||
|
use Pest\Expectation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class Relaxed extends AbstractPreset
|
||||||
|
{
|
||||||
|
public function execute(): void
|
||||||
|
{
|
||||||
|
$this->eachUserNamespace(
|
||||||
|
fn (Expectation $namespace): ArchExpectation => $namespace->not->toUseStrictTypes(),
|
||||||
|
fn (Expectation $namespace): ArchExpectation => $namespace->classes()->not->toBeFinal(),
|
||||||
|
fn (Expectation $namespace): ArchExpectation => $namespace->classes()->not->toHavePrivateMethods(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\ArchPresets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class Security extends AbstractPreset
|
||||||
|
{
|
||||||
|
public function execute(): void
|
||||||
|
{
|
||||||
|
$this->expectations[] = expect([
|
||||||
|
'md5',
|
||||||
|
'sha1',
|
||||||
|
'uniqid',
|
||||||
|
'rand',
|
||||||
|
'mt_rand',
|
||||||
|
'tempnam',
|
||||||
|
'str_shuffle',
|
||||||
|
'shuffle',
|
||||||
|
'array_rand',
|
||||||
|
'eval',
|
||||||
|
'exec',
|
||||||
|
'shell_exec',
|
||||||
|
'system',
|
||||||
|
'passthru',
|
||||||
|
'create_function',
|
||||||
|
'unserialize',
|
||||||
|
'extract',
|
||||||
|
'mb_parse_str',
|
||||||
|
'dl',
|
||||||
|
'assert',
|
||||||
|
])->not->toBeUsed();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\ArchPresets;
|
||||||
|
|
||||||
|
use Pest\Arch\Contracts\ArchExpectation;
|
||||||
|
use Pest\Expectation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class Strict extends AbstractPreset
|
||||||
|
{
|
||||||
|
public function execute(): void
|
||||||
|
{
|
||||||
|
$this->eachUserNamespace(
|
||||||
|
fn (Expectation $namespace): ArchExpectation => $namespace->classes()->not->toHaveProtectedMethods(),
|
||||||
|
fn (Expectation $namespace): ArchExpectation => $namespace->classes()->not->toBeAbstract(),
|
||||||
|
fn (Expectation $namespace): ArchExpectation => $namespace->toUseStrictTypes(),
|
||||||
|
fn (Expectation $namespace): ArchExpectation => $namespace->toUseStrictEquality(),
|
||||||
|
fn (Expectation $namespace): ArchExpectation => $namespace->classes()->toBeFinal(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->expectations[] = expect([
|
||||||
|
'sleep',
|
||||||
|
'usleep',
|
||||||
|
])->not->toBeUsed();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,11 +13,9 @@ use PHPUnit\Util\ExcludeList;
|
|||||||
final class BootExcludeList implements Bootstrapper
|
final class BootExcludeList implements Bootstrapper
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The directories to exclude.
|
|
||||||
*
|
|
||||||
* @var array<int, non-empty-string>
|
* @var array<int, non-empty-string>
|
||||||
*/
|
*/
|
||||||
private const EXCLUDE_LIST = [
|
private const array EXCLUDE_LIST = [
|
||||||
'bin',
|
'bin',
|
||||||
'overrides',
|
'overrides',
|
||||||
'resources',
|
'resources',
|
||||||
@@ -25,9 +23,6 @@ final class BootExcludeList implements Bootstrapper
|
|||||||
'stubs',
|
'stubs',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Boots the "exclude list" for PHPUnit to ignore Pest files.
|
|
||||||
*/
|
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
$baseDirectory = dirname(__DIR__, 2);
|
$baseDirectory = dirname(__DIR__, 2);
|
||||||
|
|||||||
@@ -5,25 +5,25 @@ declare(strict_types=1);
|
|||||||
namespace Pest\Bootstrappers;
|
namespace Pest\Bootstrappers;
|
||||||
|
|
||||||
use Pest\Contracts\Bootstrapper;
|
use Pest\Contracts\Bootstrapper;
|
||||||
|
use Pest\Exceptions\FatalException;
|
||||||
use Pest\Support\DatasetInfo;
|
use Pest\Support\DatasetInfo;
|
||||||
use Pest\Support\Str;
|
use Pest\Support\Str;
|
||||||
use function Pest\testDirectory;
|
|
||||||
use Pest\TestSuite;
|
use Pest\TestSuite;
|
||||||
use RecursiveDirectoryIterator;
|
use RecursiveDirectoryIterator;
|
||||||
use RecursiveIteratorIterator;
|
use RecursiveIteratorIterator;
|
||||||
use SebastianBergmann\FileIterator\Facade as PhpUnitFileIterator;
|
use SebastianBergmann\FileIterator\Facade as PhpUnitFileIterator;
|
||||||
|
|
||||||
|
use function Pest\testDirectory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
final class BootFiles implements Bootstrapper
|
final class BootFiles implements Bootstrapper
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The structure of the tests directory.
|
|
||||||
*
|
|
||||||
* @var array<int, string>
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
private const STRUCTURE = [
|
private const array STRUCTURE = [
|
||||||
'Expectations',
|
'Expectations',
|
||||||
'Expectations.php',
|
'Expectations.php',
|
||||||
'Helpers',
|
'Helpers',
|
||||||
@@ -31,14 +31,15 @@ final class BootFiles implements Bootstrapper
|
|||||||
'Pest.php',
|
'Pest.php',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Boots the structure of the tests directory.
|
|
||||||
*/
|
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
$rootPath = TestSuite::getInstance()->rootPath;
|
$rootPath = TestSuite::getInstance()->rootPath;
|
||||||
$testsPath = $rootPath.DIRECTORY_SEPARATOR.testDirectory();
|
$testsPath = $rootPath.DIRECTORY_SEPARATOR.testDirectory();
|
||||||
|
|
||||||
|
if (! is_dir($testsPath)) {
|
||||||
|
throw new FatalException(sprintf('The test directory [%s] does not exist.', $testsPath));
|
||||||
|
}
|
||||||
|
|
||||||
foreach (self::STRUCTURE as $filename) {
|
foreach (self::STRUCTURE as $filename) {
|
||||||
$filename = sprintf('%s%s%s', $testsPath, DIRECTORY_SEPARATOR, $filename);
|
$filename = sprintf('%s%s%s', $testsPath, DIRECTORY_SEPARATOR, $filename);
|
||||||
|
|
||||||
@@ -61,9 +62,6 @@ final class BootFiles implements Bootstrapper
|
|||||||
$this->bootDatasets($testsPath);
|
$this->bootDatasets($testsPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads, if possible, the given file.
|
|
||||||
*/
|
|
||||||
private function load(string $filename): void
|
private function load(string $filename): void
|
||||||
{
|
{
|
||||||
if (! Str::endsWith($filename, '.php')) {
|
if (! Str::endsWith($filename, '.php')) {
|
||||||
@@ -77,6 +75,8 @@ final class BootFiles implements Bootstrapper
|
|||||||
|
|
||||||
private function bootDatasets(string $testsPath): void
|
private function bootDatasets(string $testsPath): void
|
||||||
{
|
{
|
||||||
|
assert($testsPath !== '');
|
||||||
|
|
||||||
$files = (new PhpUnitFileIterator)->getFilesAsArray($testsPath, '.php');
|
$files = (new PhpUnitFileIterator)->getFilesAsArray($testsPath, '.php');
|
||||||
|
|
||||||
foreach ($files as $file) {
|
foreach ($files as $file) {
|
||||||
|
|||||||
@@ -12,20 +12,14 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
final class BootKernelDump implements Bootstrapper
|
final readonly class BootKernelDump implements Bootstrapper
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Boot Kernel Dump instance.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly OutputInterface $output,
|
private OutputInterface $output,
|
||||||
) {
|
) {
|
||||||
// ...
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Boots the kernel dump.
|
|
||||||
*/
|
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
Container::getInstance()->add(KernelDump::class, $kernelDump = new KernelDump(
|
Container::getInstance()->add(KernelDump::class, $kernelDump = new KernelDump(
|
||||||
|
|||||||
@@ -13,23 +13,21 @@ use Pest\Exceptions\ShouldNotHappen;
|
|||||||
final class BootOverrides implements Bootstrapper
|
final class BootOverrides implements Bootstrapper
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The list of files to be overridden.
|
|
||||||
*
|
|
||||||
* @var array<int, string>
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
private const FILES = [
|
public const array FILES = [
|
||||||
|
'ParaTest/WrapperRunner/ProgressPrinterOutput.php',
|
||||||
'Runner/Filter/NameFilterIterator.php',
|
'Runner/Filter/NameFilterIterator.php',
|
||||||
'Runner/ResultCache/DefaultResultCache.php',
|
'Runner/TestRunHistory/DefaultTestRunHistory.php',
|
||||||
'Runner/TestSuiteLoader.php',
|
'Runner/TestSuiteLoader.php',
|
||||||
'TextUI/Command/WarmCodeCoverageCacheCommand.php',
|
'Runner/TestSuiteSorter.php',
|
||||||
'TextUI/Output/Default/ProgressPrinter/TestSkippedSubscriber.php',
|
'TextUI/Command/Commands/WarmCodeCoverageCacheCommand.php',
|
||||||
|
'TextUI/Output/Default/ProgressPrinter/Subscriber/TestSkippedSubscriber.php',
|
||||||
'TextUI/TestSuiteFilterProcessor.php',
|
'TextUI/TestSuiteFilterProcessor.php',
|
||||||
'Event/Value/ThrowableBuilder.php',
|
'Event/Value/ThrowableBuilder.php',
|
||||||
|
'Logging/JUnit/JunitXmlLogger.php',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Boots the list of files to be overridden.
|
|
||||||
*/
|
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
foreach (self::FILES as $file) {
|
foreach (self::FILES as $file) {
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Bootstrappers;
|
||||||
|
|
||||||
|
use Pest\Contracts\Bootstrapper;
|
||||||
|
use PHPUnit\TextUI\Configuration\Builder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class BootPhpUnitConfiguration implements Bootstrapper
|
||||||
|
{
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
(new Builder)->build(['pest']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,31 +13,39 @@ use PHPUnit\Event\Subscriber;
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
final class BootSubscribers implements Bootstrapper
|
final readonly class BootSubscribers implements Bootstrapper
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The list of Subscribers.
|
|
||||||
*
|
|
||||||
* @var array<int, class-string<Subscriber>>
|
* @var array<int, class-string<Subscriber>>
|
||||||
*/
|
*/
|
||||||
private const SUBSCRIBERS = [
|
private const array SUBSCRIBERS = [
|
||||||
Subscribers\EnsureConfigurationIsAvailable::class,
|
Subscribers\EnsureConfigurationIsAvailable::class,
|
||||||
Subscribers\EnsureIgnorableTestCasesAreIgnored::class,
|
Subscribers\EnsureIgnorableTestCasesAreIgnored::class,
|
||||||
Subscribers\EnsureKernelDumpIsFlushed::class,
|
Subscribers\EnsureKernelDumpIsFlushed::class,
|
||||||
Subscribers\EnsureTeamCityEnabled::class,
|
Subscribers\EnsureTeamCityEnabled::class,
|
||||||
|
Subscribers\EnsureTiaIsRunningPestTestsOnly::class,
|
||||||
|
Subscribers\EnsureTiaStarts::class,
|
||||||
|
Subscribers\EnsureTiaEnds::class,
|
||||||
|
Subscribers\EnsureTiaResultsAreCollected::class,
|
||||||
|
Subscribers\EnsureTiaResultIsRecordedOnPassed::class,
|
||||||
|
Subscribers\EnsureTiaResultIsRecordedOnFailed::class,
|
||||||
|
Subscribers\EnsureTiaResultIsRecordedOnErrored::class,
|
||||||
|
Subscribers\EnsureTiaResultIsRecordedOnSkipped::class,
|
||||||
|
Subscribers\EnsureTiaResultIsRecordedOnIncomplete::class,
|
||||||
|
Subscribers\EnsureTiaResultIsRecordedOnRisky::class,
|
||||||
|
Subscribers\EnsureTiaResultIsRecordedOnNoticeTriggered::class,
|
||||||
|
Subscribers\EnsureTiaResultIsRecordedOnPhpNoticeTriggered::class,
|
||||||
|
Subscribers\EnsureTiaResultIsRecordedOnDeprecationTriggered::class,
|
||||||
|
Subscribers\EnsureTiaResultIsRecordedOnPhpDeprecationTriggered::class,
|
||||||
|
Subscribers\EnsureTiaResultIsRecordedOnWarningTriggered::class,
|
||||||
|
Subscribers\EnsureTiaResultIsRecordedOnPhpWarningTriggered::class,
|
||||||
|
Subscribers\EnsureTiaAssertionsAreRecordedOnFinished::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new instance of the Boot Subscribers.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly Container $container,
|
private Container $container,
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Boots the list of Subscribers.
|
|
||||||
*/
|
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
foreach (self::SUBSCRIBERS as $subscriber) {
|
foreach (self::SUBSCRIBERS as $subscriber) {
|
||||||
|
|||||||
@@ -11,20 +11,14 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
final class BootView implements Bootstrapper
|
final readonly class BootView implements Bootstrapper
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new instance of the Boot View.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly OutputInterface $output
|
private OutputInterface $output
|
||||||
) {
|
) {
|
||||||
// ..
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Boots the view renderer.
|
|
||||||
*/
|
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
View::renderUsing($this->output);
|
View::renderUsing($this->output);
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Collision;
|
||||||
|
|
||||||
|
use NunoMaduro\Collision\Adapters\Phpunit\TestResult;
|
||||||
|
use Pest\Configuration\Project;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
use function Termwind\render;
|
||||||
|
use function Termwind\renderUsing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class Events
|
||||||
|
{
|
||||||
|
private static ?OutputInterface $output = null;
|
||||||
|
|
||||||
|
public static function setOutput(OutputInterface $output): void
|
||||||
|
{
|
||||||
|
self::$output = $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function beforeTestMethodDescription(TestResult $result, string $description): string
|
||||||
|
{
|
||||||
|
if (($context = $result->context) === []) {
|
||||||
|
return $description;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderUsing(self::$output);
|
||||||
|
|
||||||
|
[
|
||||||
|
'assignees' => $assignees,
|
||||||
|
'issues' => $issues,
|
||||||
|
'prs' => $prs,
|
||||||
|
] = $context;
|
||||||
|
|
||||||
|
if (($link = Project::getInstance()->issues) !== '') {
|
||||||
|
$issuesDescription = array_map(fn (int $issue): string => sprintf('<a href="%s">#%s</a>', sprintf($link, $issue), $issue), $issues);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($link = Project::getInstance()->prs) !== '') {
|
||||||
|
$prsDescription = array_map(fn (int $pr): string => sprintf('<a href="%s">#%s</a>', sprintf($link, $pr), $pr), $prs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($link = Project::getInstance()->assignees) !== '' && count($assignees) > 0) {
|
||||||
|
$assigneesDescription = array_map(fn (string $assignee): string => sprintf(
|
||||||
|
'<a href="%s">@%s</a>',
|
||||||
|
sprintf($link, $assignee),
|
||||||
|
$assignee,
|
||||||
|
), $assignees);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($assignees) > 0 || count($issues) > 0 || count($prs) > 0) {
|
||||||
|
$description .= ' '.implode(', ', array_merge(
|
||||||
|
$issuesDescription ?? [],
|
||||||
|
$prsDescription ?? [],
|
||||||
|
isset($assigneesDescription) ? ['['.implode(', ', $assigneesDescription).']'] : [],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function afterTestMethodDescription(TestResult $result): void
|
||||||
|
{
|
||||||
|
if (($context = $result->context) === []) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderUsing(self::$output);
|
||||||
|
|
||||||
|
[
|
||||||
|
'notes' => $notes,
|
||||||
|
] = $context;
|
||||||
|
|
||||||
|
foreach ($notes as $note) {
|
||||||
|
render(sprintf(<<<'HTML'
|
||||||
|
<div class="ml-2">
|
||||||
|
<span class="text-gray"> // %s</span>
|
||||||
|
</div>
|
||||||
|
HTML, $note,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,8 +14,6 @@ trait Expectable
|
|||||||
/**
|
/**
|
||||||
* @template TValue
|
* @template TValue
|
||||||
*
|
*
|
||||||
* Creates a new Expectation.
|
|
||||||
*
|
|
||||||
* @param TValue $value
|
* @param TValue $value
|
||||||
* @return Expectation<TValue>
|
* @return Expectation<TValue>
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -8,27 +8,24 @@ use Closure;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
|
*
|
||||||
|
* @template T of object
|
||||||
*/
|
*/
|
||||||
trait Extendable
|
trait Extendable
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The list of extends.
|
|
||||||
*
|
|
||||||
* @var array<string, Closure>
|
* @var array<string, Closure>
|
||||||
*/
|
*/
|
||||||
private static array $extends = [];
|
private static array $extends = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a new extend.
|
* @param-closure-this T $extend
|
||||||
*/
|
*/
|
||||||
public function extend(string $name, Closure $extend): void
|
public function extend(string $name, Closure $extend): void
|
||||||
{
|
{
|
||||||
static::$extends[$name] = $extend;
|
static::$extends[$name] = $extend;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if given extend name is registered.
|
|
||||||
*/
|
|
||||||
public static function hasExtend(string $name): bool
|
public static function hasExtend(string $name): bool
|
||||||
{
|
{
|
||||||
return array_key_exists($name, static::$extends);
|
return array_key_exists($name, static::$extends);
|
||||||
|
|||||||
@@ -9,33 +9,21 @@ namespace Pest\Concerns\Logging;
|
|||||||
*/
|
*/
|
||||||
trait WritesToConsole
|
trait WritesToConsole
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Writes the given success message to the console.
|
|
||||||
*/
|
|
||||||
private function writeSuccess(string $message): void
|
private function writeSuccess(string $message): void
|
||||||
{
|
{
|
||||||
$this->writePestTestOutput($message, 'fg-green, bold', '✓');
|
$this->writePestTestOutput($message, 'fg-green, bold', '✓');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes the given error message to the console.
|
|
||||||
*/
|
|
||||||
private function writeError(string $message): void
|
private function writeError(string $message): void
|
||||||
{
|
{
|
||||||
$this->writePestTestOutput($message, 'fg-red, bold', '⨯');
|
$this->writePestTestOutput($message, 'fg-red, bold', '⨯');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes the given warning message to the console.
|
|
||||||
*/
|
|
||||||
private function writeWarning(string $message): void
|
private function writeWarning(string $message): void
|
||||||
{
|
{
|
||||||
$this->writePestTestOutput($message, 'fg-yellow, bold', '-');
|
$this->writePestTestOutput($message, 'fg-yellow, bold', '-');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes the give message to the console.
|
|
||||||
*/
|
|
||||||
private function writePestTestOutput(string $message, string $color, string $symbol): void
|
private function writePestTestOutput(string $message, string $color, string $symbol): void
|
||||||
{
|
{
|
||||||
$this->writeWithColor($color, "$symbol ", false);
|
$this->writeWithColor($color, "$symbol ", false);
|
||||||
|
|||||||
@@ -12,30 +12,21 @@ use Closure;
|
|||||||
trait Pipeable
|
trait Pipeable
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The list of pipes.
|
|
||||||
*
|
|
||||||
* @var array<string, array<Closure(Closure, mixed ...$arguments): void>>
|
* @var array<string, array<Closure(Closure, mixed ...$arguments): void>>
|
||||||
*/
|
*/
|
||||||
private static array $pipes = [];
|
private static array $pipes = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of interceptors.
|
|
||||||
*
|
|
||||||
* @var array<string, array<Closure(Closure, mixed ...$arguments): void>>
|
* @var array<string, array<Closure(Closure, mixed ...$arguments): void>>
|
||||||
*/
|
*/
|
||||||
private static array $interceptors = [];
|
private static array $interceptors = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a pipe to be applied before an expectation is checked.
|
|
||||||
*/
|
|
||||||
public function pipe(string $name, Closure $pipe): void
|
public function pipe(string $name, Closure $pipe): void
|
||||||
{
|
{
|
||||||
self::$pipes[$name][] = $pipe;
|
self::$pipes[$name][] = $pipe;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register an interceptor that should replace an existing expectation.
|
|
||||||
*
|
|
||||||
* @param string|Closure(mixed $value, mixed ...$arguments):bool $filter
|
* @param string|Closure(mixed $value, mixed ...$arguments):bool $filter
|
||||||
*/
|
*/
|
||||||
public function intercept(string $name, string|Closure $filter, Closure $handler): void
|
public function intercept(string $name, string|Closure $filter, Closure $handler): void
|
||||||
@@ -60,12 +51,10 @@ trait Pipeable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get th list of pipes by the given name.
|
|
||||||
*
|
|
||||||
* @return array<int, Closure>
|
* @return array<int, Closure>
|
||||||
*/
|
*/
|
||||||
private function pipes(string $name, object $context, string $scope): array
|
private function pipes(string $name, object $context, string $scope): array
|
||||||
{
|
{
|
||||||
return array_map(fn (Closure $pipe): \Closure => $pipe->bindTo($context, $scope), self::$pipes[$name] ?? []);
|
return array_map(fn (Closure $pipe): Closure => $pipe->bindTo($context, $scope), self::$pipes[$name] ?? []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ trait Retrievable
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @template TRetrievableValue
|
* @template TRetrievableValue
|
||||||
*
|
|
||||||
* Safely retrieve the value at the given key from an object or array.
|
|
||||||
* @template TRetrievableValue
|
* @template TRetrievableValue
|
||||||
*
|
*
|
||||||
* @param array<string, TRetrievableValue>|object $value
|
* @param array<string, TRetrievableValue>|object $value
|
||||||
|
|||||||
+279
-115
@@ -5,14 +5,29 @@ declare(strict_types=1);
|
|||||||
namespace Pest\Concerns;
|
namespace Pest\Concerns;
|
||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use Pest\Exceptions\DatasetArgsCountMismatch;
|
use Pest\Exceptions\DatasetArgumentsMismatch;
|
||||||
|
use Pest\Panic;
|
||||||
|
use Pest\Plugins\Tia;
|
||||||
|
use Pest\Plugins\Tia\Collectors;
|
||||||
|
use Pest\Plugins\Tia\Enums\ReplayType;
|
||||||
|
use Pest\Plugins\Tia\Recorder;
|
||||||
|
use Pest\Preset;
|
||||||
use Pest\Support\ChainableClosure;
|
use Pest\Support\ChainableClosure;
|
||||||
|
use Pest\Support\Container;
|
||||||
use Pest\Support\ExceptionTrace;
|
use Pest\Support\ExceptionTrace;
|
||||||
use Pest\Support\Reflection;
|
use Pest\Support\Reflection;
|
||||||
|
use Pest\Support\Shell;
|
||||||
use Pest\TestSuite;
|
use Pest\TestSuite;
|
||||||
|
use PHPUnit\Framework\AssertionFailedError;
|
||||||
|
use PHPUnit\Framework\Attributes\PostCondition;
|
||||||
|
use PHPUnit\Framework\IncompleteTest;
|
||||||
|
use PHPUnit\Framework\SkippedTest;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use PHPUnit\Framework\TestCase\ExceptionExpectation;
|
||||||
|
use PHPUnit\Framework\TestCase\OutputBuffer;
|
||||||
use ReflectionException;
|
use ReflectionException;
|
||||||
use ReflectionFunction;
|
use ReflectionFunction;
|
||||||
|
use ReflectionParameter;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,83 +37,65 @@ use Throwable;
|
|||||||
*/
|
*/
|
||||||
trait Testable
|
trait Testable
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* The test's description.
|
|
||||||
*/
|
|
||||||
private string $__description;
|
private string $__description;
|
||||||
|
|
||||||
/**
|
|
||||||
* The test's latest description.
|
|
||||||
*/
|
|
||||||
private static string $__latestDescription;
|
private static string $__latestDescription;
|
||||||
|
|
||||||
/**
|
private static array $__latestAssignees = [];
|
||||||
* The test's describing, if any.
|
|
||||||
*/
|
private static array $__latestNotes = [];
|
||||||
public ?string $__describing = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The test's test closure.
|
* @var array<int, int>
|
||||||
*/
|
*/
|
||||||
|
private static array $__latestIssues = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<int, int>
|
||||||
|
*/
|
||||||
|
private static array $__latestPrs = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
public array $__describing = [];
|
||||||
|
|
||||||
|
public bool $__ran = false;
|
||||||
|
|
||||||
|
private ReplayType $__replay = ReplayType::None;
|
||||||
|
|
||||||
|
private int $__replayAssertions = 0;
|
||||||
|
|
||||||
private Closure $__test;
|
private Closure $__test;
|
||||||
|
|
||||||
/**
|
|
||||||
* The test's before each closure.
|
|
||||||
*/
|
|
||||||
private ?Closure $__beforeEach = null;
|
private ?Closure $__beforeEach = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* The test's after each closure.
|
|
||||||
*/
|
|
||||||
private ?Closure $__afterEach = null;
|
private ?Closure $__afterEach = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* The test's before all closure.
|
|
||||||
*/
|
|
||||||
private static ?Closure $__beforeAll = null;
|
private static ?Closure $__beforeAll = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* The test's after all closure.
|
|
||||||
*/
|
|
||||||
private static ?Closure $__afterAll = null;
|
private static ?Closure $__afterAll = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* The list of snapshot changes, if any.
|
|
||||||
*/
|
|
||||||
private array $__snapshotChanges = [];
|
private array $__snapshotChanges = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets the test case static properties.
|
|
||||||
*/
|
|
||||||
public static function flush(): void
|
public static function flush(): void
|
||||||
{
|
{
|
||||||
self::$__beforeAll = null;
|
self::$__beforeAll = null;
|
||||||
self::$__afterAll = null;
|
self::$__afterAll = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function note(array|string $note): self
|
||||||
* Creates a new Test Case instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $name)
|
|
||||||
{
|
{
|
||||||
parent::__construct($name);
|
$note = is_array($note) ? $note : [$note];
|
||||||
|
|
||||||
$test = TestSuite::getInstance()->tests->get(self::$__filename);
|
self::$__latestNotes = array_merge(self::$__latestNotes, $note);
|
||||||
|
|
||||||
if ($test->hasMethod($name)) {
|
return $this;
|
||||||
$method = $test->getMethod($name);
|
|
||||||
$this->__description = self::$__latestDescription = $method->description;
|
|
||||||
$this->__describing = $method->describing;
|
|
||||||
$this->__test = $method->getClosure($this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new "setUpBeforeClass" to the Test Case.
|
|
||||||
*/
|
|
||||||
public function __addBeforeAll(?Closure $hook): void
|
public function __addBeforeAll(?Closure $hook): void
|
||||||
{
|
{
|
||||||
if (! $hook instanceof \Closure) {
|
if (! $hook instanceof Closure) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,12 +104,9 @@ trait Testable
|
|||||||
: $hook;
|
: $hook;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new "tearDownAfterClass" to the Test Case.
|
|
||||||
*/
|
|
||||||
public function __addAfterAll(?Closure $hook): void
|
public function __addAfterAll(?Closure $hook): void
|
||||||
{
|
{
|
||||||
if (! $hook instanceof \Closure) {
|
if (! $hook instanceof Closure) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,28 +115,19 @@ trait Testable
|
|||||||
: $hook;
|
: $hook;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new "setUp" to the Test Case.
|
|
||||||
*/
|
|
||||||
public function __addBeforeEach(?Closure $hook): void
|
public function __addBeforeEach(?Closure $hook): void
|
||||||
{
|
{
|
||||||
$this->__addHook('__beforeEach', $hook);
|
$this->__addHook('__beforeEach', $hook);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new "tearDown" to the Test Case.
|
|
||||||
*/
|
|
||||||
public function __addAfterEach(?Closure $hook): void
|
public function __addAfterEach(?Closure $hook): void
|
||||||
{
|
{
|
||||||
$this->__addHook('__afterEach', $hook);
|
$this->__addHook('__afterEach', $hook);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new "hook" to the Test Case.
|
|
||||||
*/
|
|
||||||
private function __addHook(string $property, ?Closure $hook): void
|
private function __addHook(string $property, ?Closure $hook): void
|
||||||
{
|
{
|
||||||
if (! $hook instanceof \Closure) {
|
if (! $hook instanceof Closure) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,9 +136,6 @@ trait Testable
|
|||||||
: $hook;
|
: $hook;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called before the first test of this Test Case is run.
|
|
||||||
*/
|
|
||||||
public static function setUpBeforeClass(): void
|
public static function setUpBeforeClass(): void
|
||||||
{
|
{
|
||||||
parent::setUpBeforeClass();
|
parent::setUpBeforeClass();
|
||||||
@@ -164,12 +146,13 @@ trait Testable
|
|||||||
$beforeAll = ChainableClosure::boundStatically(self::$__beforeAll, $beforeAll);
|
$beforeAll = ChainableClosure::boundStatically(self::$__beforeAll, $beforeAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
call_user_func(Closure::bind($beforeAll, null, self::class));
|
call_user_func(Closure::bind($beforeAll, null, self::class));
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
Panic::with($e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called after the last test of this Test Case is run.
|
|
||||||
*/
|
|
||||||
public static function tearDownAfterClass(): void
|
public static function tearDownAfterClass(): void
|
||||||
{
|
{
|
||||||
$afterAll = TestSuite::getInstance()->afterAll->get(self::$__filename);
|
$afterAll = TestSuite::getInstance()->afterAll->get(self::$__filename);
|
||||||
@@ -183,16 +166,20 @@ trait Testable
|
|||||||
parent::tearDownAfterClass();
|
parent::tearDownAfterClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected function setUp(...$arguments): void
|
||||||
* Gets executed before the Test Case.
|
|
||||||
*/
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
{
|
||||||
TestSuite::getInstance()->test = $this;
|
TestSuite::getInstance()->test = $this;
|
||||||
|
|
||||||
$method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name());
|
$method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name());
|
||||||
|
|
||||||
$description = $this->dataName() ? $method->description.' with '.$this->dataName() : $method->description;
|
$description = $method->description;
|
||||||
|
if ($this->dataName()) {
|
||||||
|
$description = str_contains((string) $description, ':dataset')
|
||||||
|
? str_replace(':dataset', str_replace('dataset ', '', $this->dataName()), (string) $description)
|
||||||
|
: $description.' with '.$this->dataName();
|
||||||
|
}
|
||||||
|
|
||||||
|
$description = htmlspecialchars(html_entity_decode((string) $description), ENT_NOQUOTES);
|
||||||
|
|
||||||
if ($method->repetitions > 1) {
|
if ($method->repetitions > 1) {
|
||||||
$matches = [];
|
$matches = [];
|
||||||
@@ -210,52 +197,212 @@ trait Testable
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->__description = self::$__latestDescription = $description;
|
$this->__description = self::$__latestDescription = $description;
|
||||||
|
self::$__latestAssignees = $method->assignees;
|
||||||
|
self::$__latestNotes = $method->notes;
|
||||||
|
self::$__latestIssues = $method->issues;
|
||||||
|
self::$__latestPrs = $method->prs;
|
||||||
|
|
||||||
|
/** @var Tia $tia */
|
||||||
|
$tia = Container::getInstance()->get(Tia::class);
|
||||||
|
$status = $tia->getStatus(self::$__filename, $this->valueObjectForEvents()->id());
|
||||||
|
$replay = ReplayType::fromStatus($status);
|
||||||
|
|
||||||
|
if ($replay !== ReplayType::None) {
|
||||||
|
assert($status !== null);
|
||||||
|
|
||||||
|
$this->__replay = $replay;
|
||||||
|
|
||||||
|
match ($replay) {
|
||||||
|
ReplayType::Pass, ReplayType::Risky => $this->__beginReplay($replay, $tia),
|
||||||
|
ReplayType::Skipped => $this->markTestSkipped($status->message()),
|
||||||
|
ReplayType::Incomplete => $this->markTestIncomplete($status->message()),
|
||||||
|
ReplayType::Failure => throw new AssertionFailedError($status->message() ?: 'Cached failure'),
|
||||||
|
};
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$recorder = Container::getInstance()->get(Recorder::class);
|
||||||
|
assert($recorder instanceof Recorder);
|
||||||
|
|
||||||
|
if ($recorder->isActive()) {
|
||||||
|
$recorder->beginTest($this::class, $this->name(), self::$__filename);
|
||||||
|
}
|
||||||
|
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
|
Collectors::armAll($recorder);
|
||||||
|
|
||||||
$beforeEach = TestSuite::getInstance()->beforeEach->get(self::$__filename)[1];
|
$beforeEach = TestSuite::getInstance()->beforeEach->get(self::$__filename)[1];
|
||||||
|
|
||||||
if ($this->__beforeEach instanceof Closure) {
|
if ($this->__beforeEach instanceof Closure) {
|
||||||
$beforeEach = ChainableClosure::bound($this->__beforeEach, $beforeEach);
|
$beforeEach = ChainableClosure::bound($this->__beforeEach, $beforeEach);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->__callClosure($beforeEach, func_get_args());
|
$this->__callClosure($beforeEach, $arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private function __beginReplay(ReplayType $replay, Tia $tia): void
|
||||||
* Gets executed after the Test Case.
|
|
||||||
*/
|
|
||||||
protected function tearDown(): void
|
|
||||||
{
|
{
|
||||||
|
$this->__replay = $replay;
|
||||||
|
$this->__replayAssertions = $tia->getAssertionCount($this->valueObjectForEvents()->id());
|
||||||
|
$this->__ran = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __initializeTestCase(): void
|
||||||
|
{
|
||||||
|
if (isset($this->__test)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $this->name();
|
||||||
|
$test = TestSuite::getInstance()->tests->get(self::$__filename);
|
||||||
|
|
||||||
|
if ($test->hasMethod($name)) {
|
||||||
|
$method = $test->getMethod($name);
|
||||||
|
$this->__description = self::$__latestDescription = $method->description;
|
||||||
|
self::$__latestAssignees = $method->assignees;
|
||||||
|
self::$__latestNotes = $method->notes;
|
||||||
|
self::$__latestIssues = $method->issues;
|
||||||
|
self::$__latestPrs = $method->prs;
|
||||||
|
$this->__describing = $method->describing;
|
||||||
|
$this->__test = $method->getClosure();
|
||||||
|
|
||||||
|
$method->setUp($this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function tearDown(...$arguments): void
|
||||||
|
{
|
||||||
|
if ($this->__replay !== ReplayType::None) {
|
||||||
|
TestSuite::getInstance()->test = null;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$afterEach = TestSuite::getInstance()->afterEach->get(self::$__filename);
|
$afterEach = TestSuite::getInstance()->afterEach->get(self::$__filename);
|
||||||
|
|
||||||
if ($this->__afterEach instanceof Closure) {
|
if ($this->__afterEach instanceof Closure) {
|
||||||
$afterEach = ChainableClosure::bound($this->__afterEach, $afterEach);
|
$afterEach = ChainableClosure::bound($this->__afterEach, $afterEach);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
$this->__callClosure($afterEach, func_get_args());
|
$this->__callClosure($afterEach, func_get_args());
|
||||||
|
} finally {
|
||||||
parent::tearDown();
|
parent::tearDown();
|
||||||
|
|
||||||
TestSuite::getInstance()->test = null;
|
TestSuite::getInstance()->test = null;
|
||||||
|
|
||||||
|
$method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name());
|
||||||
|
$method->tearDown($this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the Test Case current test.
|
|
||||||
*
|
|
||||||
* @throws Throwable
|
* @throws Throwable
|
||||||
*/
|
*/
|
||||||
private function __runTest(Closure $closure, ...$args): mixed
|
private function __runTest(Closure $closure, ...$args): mixed
|
||||||
{
|
{
|
||||||
$arguments = $this->__resolveTestArguments($args);
|
if ($this->__replay === ReplayType::Pass || $this->__replay === ReplayType::Risky) {
|
||||||
$this->__ensureDatasetArgumentNumberMatches($arguments);
|
if ($this->__replay === ReplayType::Pass && $this->__replayAssertions === 0) {
|
||||||
|
$this->expectNotToPerformAssertions();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addToAssertionCount($this->__replayAssertions);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$arguments = $this->__resolveTestArguments($args);
|
||||||
|
$this->__ensureDatasetArgumentNameAndNumberMatches($arguments);
|
||||||
|
|
||||||
|
$method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name());
|
||||||
|
|
||||||
|
if ($method->flakyTries === null) {
|
||||||
return $this->__callClosure($closure, $arguments);
|
return $this->__callClosure($closure, $arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$lastException = null;
|
||||||
|
$initialProperties = get_object_vars($this);
|
||||||
|
|
||||||
|
for ($attempt = 1; $attempt <= $method->flakyTries; $attempt++) {
|
||||||
|
try {
|
||||||
|
return $this->__callClosure($closure, $arguments);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
if ($e instanceof SkippedTest
|
||||||
|
|| $e instanceof IncompleteTest
|
||||||
|
|| $this->__isExpectedException($e)) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
$lastException = $e;
|
||||||
|
|
||||||
|
if ($attempt < $method->flakyTries) {
|
||||||
|
if ($this->__snapshotChanges !== []) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->tearDown();
|
||||||
|
|
||||||
|
Closure::bind(fn (): array => $this->mockObjects = [], $this, TestCase::class)();
|
||||||
|
|
||||||
|
foreach (array_keys(array_diff_key(get_object_vars($this), $initialProperties)) as $property) {
|
||||||
|
unset($this->{$property});
|
||||||
|
}
|
||||||
|
|
||||||
|
$outputBuffer = Closure::bind(fn () => $this->outputBuffer, $this, TestCase::class)();
|
||||||
|
|
||||||
|
if ($outputBuffer->hasExpectation()) {
|
||||||
|
ob_clean();
|
||||||
|
|
||||||
|
Closure::bind(function (): void {
|
||||||
|
$this->expectedString = null;
|
||||||
|
$this->expectedRegularExpression = null;
|
||||||
|
}, $outputBuffer, OutputBuffer::class)();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setUp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw $lastException;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function __isExpectedException(Throwable $e): bool
|
||||||
|
{
|
||||||
|
$expectation = Closure::bind(fn () => $this->exceptionExpectation, $this, TestCase::class)();
|
||||||
|
|
||||||
|
$read = fn (string $property): mixed => Closure::bind(fn () => $this->{$property}, $expectation, ExceptionExpectation::class)();
|
||||||
|
|
||||||
|
$expectedClass = $read('expectedException');
|
||||||
|
|
||||||
|
if ($expectedClass !== null) {
|
||||||
|
return $e instanceof $expectedClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
$expectedMessage = $read('expectedMessage');
|
||||||
|
|
||||||
|
if ($expectedMessage !== null) {
|
||||||
|
return str_contains($e->getMessage(), (string) $expectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
$expectedMessageRegex = $read('expectedMessageRegularExpression');
|
||||||
|
|
||||||
|
if ($expectedMessageRegex !== null) {
|
||||||
|
return preg_match($expectedMessageRegex, $e->getMessage()) === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$expectedCode = $read('expectedCode');
|
||||||
|
|
||||||
|
if ($expectedCode !== null) {
|
||||||
|
return $e->getCode() === $expectedCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve the passed arguments. Any Closures will be bound to the testcase and resolved.
|
|
||||||
*
|
|
||||||
* @throws Throwable
|
* @throws Throwable
|
||||||
*/
|
*/
|
||||||
private function __resolveTestArguments(array $arguments): array
|
private function __resolveTestArguments(array $arguments): array
|
||||||
@@ -263,11 +410,13 @@ trait Testable
|
|||||||
$method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name());
|
$method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name());
|
||||||
|
|
||||||
if ($method->repetitions > 1) {
|
if ($method->repetitions > 1) {
|
||||||
array_shift($arguments);
|
$firstArgument = array_shift($arguments);
|
||||||
|
$arguments[] = $firstArgument;
|
||||||
}
|
}
|
||||||
|
|
||||||
$underlyingTest = Reflection::getFunctionVariable($this->__test, 'closure');
|
$underlyingTest = Reflection::getFunctionVariable($this->__test, 'closure');
|
||||||
$testParameterTypes = array_values(Reflection::getFunctionArguments($underlyingTest));
|
$testParameterTypesByName = Reflection::getFunctionArguments($underlyingTest);
|
||||||
|
$testParameterTypes = array_values($testParameterTypesByName);
|
||||||
|
|
||||||
if (count($arguments) !== 1) {
|
if (count($arguments) !== 1) {
|
||||||
foreach ($arguments as $argumentIndex => $argumentValue) {
|
foreach ($arguments as $argumentIndex => $argumentValue) {
|
||||||
@@ -275,7 +424,11 @@ trait Testable
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (in_array($testParameterTypes[$argumentIndex], [Closure::class, 'callable', 'mixed'])) {
|
$parameterType = is_string($argumentIndex)
|
||||||
|
? $testParameterTypesByName[$argumentIndex]
|
||||||
|
: $testParameterTypes[$argumentIndex];
|
||||||
|
|
||||||
|
if (in_array($parameterType, [Closure::class, 'callable', 'mixed'])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,11 +438,11 @@ trait Testable
|
|||||||
return $arguments;
|
return $arguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $arguments[0] instanceof Closure) {
|
if (! isset($arguments[0]) || ! $arguments[0] instanceof Closure) {
|
||||||
return $arguments;
|
return $arguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (in_array($testParameterTypes[0], [Closure::class, 'callable'])) {
|
if (isset($testParameterTypes[0]) && in_array($testParameterTypes[0], [Closure::class, 'callable'])) {
|
||||||
return $arguments;
|
return $arguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,16 +454,14 @@ trait Testable
|
|||||||
return [$boundDatasetResult];
|
return [$boundDatasetResult];
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_values($boundDatasetResult);
|
return $boundDatasetResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures dataset items count matches underlying test case required parameters
|
|
||||||
*
|
|
||||||
* @throws ReflectionException
|
* @throws ReflectionException
|
||||||
* @throws DatasetArgsCountMismatch
|
* @throws DatasetArgumentsMismatch
|
||||||
*/
|
*/
|
||||||
private function __ensureDatasetArgumentNumberMatches(array $arguments): void
|
private function __ensureDatasetArgumentNameAndNumberMatches(array $arguments): void
|
||||||
{
|
{
|
||||||
if ($arguments === []) {
|
if ($arguments === []) {
|
||||||
return;
|
return;
|
||||||
@@ -321,11 +472,21 @@ trait Testable
|
|||||||
$requiredParametersCount = $testReflection->getNumberOfRequiredParameters();
|
$requiredParametersCount = $testReflection->getNumberOfRequiredParameters();
|
||||||
$suppliedParametersCount = count($arguments);
|
$suppliedParametersCount = count($arguments);
|
||||||
|
|
||||||
if ($suppliedParametersCount >= $requiredParametersCount) {
|
$datasetParameterNames = array_keys($arguments);
|
||||||
|
$testParameterNames = array_map(
|
||||||
|
fn (ReflectionParameter $reflectionParameter): string => $reflectionParameter->getName(),
|
||||||
|
array_filter($testReflection->getParameters(), fn (ReflectionParameter $reflectionParameter): bool => ! $reflectionParameter->isOptional()),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (array_diff($testParameterNames, $datasetParameterNames) === []) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new DatasetArgsCountMismatch($requiredParametersCount, $suppliedParametersCount);
|
if (isset($testParameterNames[0]) && $suppliedParametersCount >= $requiredParametersCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new DatasetArgumentsMismatch($requiredParametersCount, $suppliedParametersCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -336,45 +497,48 @@ trait Testable
|
|||||||
return ExceptionTrace::ensure(fn (): mixed => call_user_func_array(Closure::bind($closure, $this, $this::class), $arguments));
|
return ExceptionTrace::ensure(fn (): mixed => call_user_func_array(Closure::bind($closure, $this, $this::class), $arguments));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @postCondition */
|
public function preset(): Preset
|
||||||
|
{
|
||||||
|
return new Preset;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[PostCondition]
|
||||||
protected function __MarkTestIncompleteIfSnapshotHaveChanged(): void
|
protected function __MarkTestIncompleteIfSnapshotHaveChanged(): void
|
||||||
{
|
{
|
||||||
if (count($this->__snapshotChanges) === 0) {
|
if (count($this->__snapshotChanges) === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count($this->__snapshotChanges) === 1) {
|
$this->markTestIncomplete(implode('. ', $this->__snapshotChanges));
|
||||||
$this->markTestIncomplete($this->__snapshotChanges[0]);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$messages = implode(PHP_EOL, array_map(static fn (string $message): string => '- $message', $this->__snapshotChanges));
|
|
||||||
|
|
||||||
$this->markTestIncomplete($messages);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The printable test case name.
|
|
||||||
*/
|
|
||||||
public static function getPrintableTestCaseName(): string
|
public static function getPrintableTestCaseName(): string
|
||||||
{
|
{
|
||||||
return preg_replace('/P\\\/', '', self::class, 1);
|
return preg_replace('/P\\\/', '', self::class, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The printable test case method name.
|
|
||||||
*/
|
|
||||||
public function getPrintableTestCaseMethodName(): string
|
public function getPrintableTestCaseMethodName(): string
|
||||||
{
|
{
|
||||||
return $this->__description;
|
return $this->__description;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The latest printable test case method name.
|
|
||||||
*/
|
|
||||||
public static function getLatestPrintableTestCaseMethodName(): string
|
public static function getLatestPrintableTestCaseMethodName(): string
|
||||||
{
|
{
|
||||||
return self::$__latestDescription;
|
return self::$__latestDescription ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPrintableContext(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'assignees' => self::$__latestAssignees,
|
||||||
|
'issues' => self::$__latestIssues,
|
||||||
|
'prs' => self::$__latestPrs,
|
||||||
|
'notes' => self::$__latestNotes,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shell(): void
|
||||||
|
{
|
||||||
|
Shell::open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest;
|
||||||
|
|
||||||
|
use Pest\PendingCalls\BeforeEachCall;
|
||||||
|
use Pest\PendingCalls\UsesCall;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @mixin UsesCall
|
||||||
|
*/
|
||||||
|
final readonly class Configuration
|
||||||
|
{
|
||||||
|
private string $filename;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
string $filename,
|
||||||
|
) {
|
||||||
|
$this->filename = str_ends_with($filename, DIRECTORY_SEPARATOR.'Pest.php') ? dirname($filename) : $filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function in(string ...$targets): UsesCall
|
||||||
|
{
|
||||||
|
return new UsesCall($this->filename, [])->in(...$targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function extend(string ...$classAndTraits): UsesCall
|
||||||
|
{
|
||||||
|
return new UsesCall(
|
||||||
|
$this->filename,
|
||||||
|
array_values($classAndTraits)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function extends(string ...$classAndTraits): UsesCall
|
||||||
|
{
|
||||||
|
return $this->extend(...$classAndTraits);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function group(string ...$groups): UsesCall
|
||||||
|
{
|
||||||
|
return new UsesCall($this->filename, [])->group(...$groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function only(): void
|
||||||
|
{
|
||||||
|
new BeforeEachCall(TestSuite::getInstance(), $this->filename)->only();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function use(string ...$classAndTraits): UsesCall
|
||||||
|
{
|
||||||
|
return $this->extend(...$classAndTraits);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uses(string ...$classAndTraits): UsesCall
|
||||||
|
{
|
||||||
|
return $this->extends(...$classAndTraits);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function printer(): Configuration\Printer
|
||||||
|
{
|
||||||
|
return new Configuration\Printer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function presets(): Configuration\Presets
|
||||||
|
{
|
||||||
|
return new Configuration\Presets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function project(): Configuration\Project
|
||||||
|
{
|
||||||
|
return Configuration\Project::getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function browser(): Browser\Configuration
|
||||||
|
{
|
||||||
|
return new Browser\Configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function evals(): Evals\Configuration // @phpstan-ignore-line
|
||||||
|
{
|
||||||
|
return new Evals\Configuration; // @phpstan-ignore-line
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tia(): Plugins\Tia\Configuration
|
||||||
|
{
|
||||||
|
return new Plugins\Tia\Configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<array-key, mixed> $arguments
|
||||||
|
*/
|
||||||
|
public function __call(string $name, array $arguments): mixed
|
||||||
|
{
|
||||||
|
return $this->uses()->$name(...$arguments); // @phpstan-ignore-line
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Configuration;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Pest\Preset;
|
||||||
|
|
||||||
|
final class Presets
|
||||||
|
{
|
||||||
|
public function custom(string $name, Closure $execute): void
|
||||||
|
{
|
||||||
|
Preset::custom($name, $execute);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Configuration;
|
||||||
|
|
||||||
|
use NunoMaduro\Collision\Adapters\Phpunit\Printers\DefaultPrinter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final readonly class Printer
|
||||||
|
{
|
||||||
|
public function compact(): self
|
||||||
|
{
|
||||||
|
DefaultPrinter::compact(true);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class Project
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public string $assignees = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public string $issues = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public string $prs = '';
|
||||||
|
|
||||||
|
private static ?self $instance = null;
|
||||||
|
|
||||||
|
public static function getInstance(): self
|
||||||
|
{
|
||||||
|
return self::$instance ??= new self;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function github(string $project): self
|
||||||
|
{
|
||||||
|
$this->issues = "https://github.com/{$project}/issues/%s";
|
||||||
|
$this->prs = "https://github.com/{$project}/pull/%s";
|
||||||
|
|
||||||
|
$this->assignees = 'https://github.com/%s';
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function gitlab(string $project, string $hostname = 'gitlab.com'): self
|
||||||
|
{
|
||||||
|
$hostname = parse_url($hostname, PHP_URL_HOST) ?? $hostname;
|
||||||
|
|
||||||
|
$this->issues = "https://{$hostname}/{$project}/-/work_items/%s";
|
||||||
|
$this->prs = "https://{$hostname}/{$project}/-/merge_requests/%s";
|
||||||
|
|
||||||
|
$this->assignees = "https://{$hostname}/%s";
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bitbucket(string $project): self
|
||||||
|
{
|
||||||
|
$this->issues = "https://bitbucket.org/{$project}/issues/%s";
|
||||||
|
$this->prs = "https://bitbucket.org/{$project}/pull-requests/%s";
|
||||||
|
|
||||||
|
$this->assignees = 'https://bitbucket.org/%s';
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function jira(string $namespace, string $project): self
|
||||||
|
{
|
||||||
|
$this->issues = "https://{$namespace}.atlassian.net/browse/{$project}-%s";
|
||||||
|
|
||||||
|
$this->assignees = "https://{$namespace}.atlassian.net/secure/ViewProfile.jspa?name=%s";
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function custom(string $issues, string $prs, string $assignees): self
|
||||||
|
{
|
||||||
|
$this->issues = $issues;
|
||||||
|
$this->prs = $prs;
|
||||||
|
|
||||||
|
$this->assignees = $assignees;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-12
@@ -9,14 +9,12 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
final class Help
|
final readonly class Help
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The Command messages.
|
|
||||||
*
|
|
||||||
* @var array<int, string>
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
private const HELP_MESSAGES = [
|
private const array HELP_MESSAGES = [
|
||||||
'<comment>Pest Options:</comment>',
|
'<comment>Pest Options:</comment>',
|
||||||
' <info>--init</info> Initialise a standard Pest configuration',
|
' <info>--init</info> Initialise a standard Pest configuration',
|
||||||
' <info>--coverage</info> Enable coverage and output to standard output',
|
' <info>--coverage</info> Enable coverage and output to standard output',
|
||||||
@@ -24,17 +22,11 @@ final class Help
|
|||||||
' <info>--group=<fg=cyan><name></></info> Only runs tests from the specified group(s)',
|
' <info>--group=<fg=cyan><name></></info> Only runs tests from the specified group(s)',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
public function __construct(private OutputInterface $output)
|
||||||
* Creates a new Console Command instance.
|
|
||||||
*/
|
|
||||||
public function __construct(private readonly OutputInterface $output)
|
|
||||||
{
|
{
|
||||||
// ..
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes the Console Command.
|
|
||||||
*/
|
|
||||||
public function __invoke(): void
|
public function __invoke(): void
|
||||||
{
|
{
|
||||||
foreach (self::HELP_MESSAGES as $message) {
|
foreach (self::HELP_MESSAGES as $message) {
|
||||||
|
|||||||
+17
-20
@@ -15,32 +15,29 @@ use Symfony\Component\Console\Question\ConfirmationQuestion;
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
final class Thanks
|
final readonly class Thanks
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The support options.
|
|
||||||
*
|
|
||||||
* @var array<string, string>
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
private const FUNDING_MESSAGES = [
|
private const array FUNDING_MESSAGES = [
|
||||||
'Star the project on GitHub' => 'https://github.com/pestphp/pest',
|
'Star' => 'https://github.com/pestphp/pest',
|
||||||
'Tweet about the project' => 'https://twitter.com/pestphp',
|
'YouTube' => 'https://youtube.com/@nunomaduro',
|
||||||
'Sponsor the project' => 'https://github.com/sponsors/nunomaduro',
|
'TikTok' => 'https://tiktok.com/@enunomaduro',
|
||||||
|
'Twitch' => 'https://twitch.tv/nunomaduro',
|
||||||
|
'LinkedIn' => 'https://linkedin.com/in/nunomaduro',
|
||||||
|
'Instagram' => 'https://instagram.com/enunomaduro',
|
||||||
|
'X' => 'https://x.com/enunomaduro',
|
||||||
|
'Sponsor' => 'https://github.com/sponsors/nunomaduro',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Console Command instance.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly InputInterface $input,
|
private InputInterface $input,
|
||||||
private readonly OutputInterface $output
|
private OutputInterface $output
|
||||||
) {
|
) {
|
||||||
// ..
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes the Console Command.
|
|
||||||
*/
|
|
||||||
public function __invoke(): void
|
public function __invoke(): void
|
||||||
{
|
{
|
||||||
$bootstrapper = new BootView($this->output);
|
$bootstrapper = new BootView($this->output);
|
||||||
@@ -49,7 +46,7 @@ final class Thanks
|
|||||||
$wantsToSupport = false;
|
$wantsToSupport = false;
|
||||||
|
|
||||||
if (getenv('PEST_NO_SUPPORT') !== 'true' && $this->input->isInteractive()) {
|
if (getenv('PEST_NO_SUPPORT') !== 'true' && $this->input->isInteractive()) {
|
||||||
$wantsToSupport = (new SymfonyQuestionHelper())->ask(
|
$wantsToSupport = (new SymfonyQuestionHelper)->ask(
|
||||||
new ArrayInput([]),
|
new ArrayInput([]),
|
||||||
$this->output,
|
$this->output,
|
||||||
new ConfirmationQuestion(
|
new ConfirmationQuestion(
|
||||||
@@ -71,13 +68,13 @@ final class Thanks
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($wantsToSupport === true) {
|
if ($wantsToSupport === true) {
|
||||||
if (PHP_OS_FAMILY == 'Darwin') {
|
if (PHP_OS_FAMILY === 'Darwin') {
|
||||||
exec('open https://github.com/pestphp/pest');
|
exec('open https://github.com/pestphp/pest');
|
||||||
}
|
}
|
||||||
if (PHP_OS_FAMILY == 'Windows') {
|
if (PHP_OS_FAMILY === 'Windows') {
|
||||||
exec('start https://github.com/pestphp/pest');
|
exec('start https://github.com/pestphp/pest');
|
||||||
}
|
}
|
||||||
if (PHP_OS_FAMILY == 'Linux') {
|
if (PHP_OS_FAMILY === 'Linux') {
|
||||||
exec('xdg-open https://github.com/pestphp/pest');
|
exec('xdg-open https://github.com/pestphp/pest');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Pest\Contracts;
|
|
||||||
|
|
||||||
use Pest\Factories\TestCaseMethodFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
interface AddsAnnotations
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Adds annotations to the given test case method.
|
|
||||||
*
|
|
||||||
* @param array<int, string> $annotations
|
|
||||||
* @return array<int, string>
|
|
||||||
*/
|
|
||||||
public function __invoke(TestCaseMethodFactory $method, array $annotations): array;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Contracts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
interface ArchPreset {}
|
||||||
@@ -9,8 +9,5 @@ namespace Pest\Contracts;
|
|||||||
*/
|
*/
|
||||||
interface Bootstrapper
|
interface Bootstrapper
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Boots the bootstrapper.
|
|
||||||
*/
|
|
||||||
public function boot(): void;
|
public function boot(): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,5 +11,5 @@ use NunoMaduro\Collision\Contracts\Adapters\Phpunit\HasPrintableTestCaseName as
|
|||||||
*/
|
*/
|
||||||
interface HasPrintableTestCaseName extends BaseHasPrintableTestCaseName
|
interface HasPrintableTestCaseName extends BaseHasPrintableTestCaseName
|
||||||
{
|
{
|
||||||
// ..
|
//
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,13 +11,7 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||||||
*/
|
*/
|
||||||
interface Panicable
|
interface Panicable
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Renders the panic on the given output.
|
|
||||||
*/
|
|
||||||
public function render(OutputInterface $output): void;
|
public function render(OutputInterface $output): void;
|
||||||
|
|
||||||
/**
|
|
||||||
* The exit code to be used.
|
|
||||||
*/
|
|
||||||
public function exitCode(): int;
|
public function exitCode(): int;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,5 @@ namespace Pest\Contracts\Plugins;
|
|||||||
*/
|
*/
|
||||||
interface AddsOutput
|
interface AddsOutput
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Adds output after the Test Suite execution.
|
|
||||||
*/
|
|
||||||
public function addOutput(int $exitCode): int;
|
public function addOutput(int $exitCode): int;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,5 @@ namespace Pest\Contracts\Plugins;
|
|||||||
*/
|
*/
|
||||||
interface Bootable
|
interface Bootable
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Boots the plugin.
|
|
||||||
*/
|
|
||||||
public function boot(): void;
|
public function boot(): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ namespace Pest\Contracts\Plugins;
|
|||||||
interface HandlesArguments
|
interface HandlesArguments
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Adds arguments before the Test Suite execution.
|
|
||||||
*
|
|
||||||
* @param array<int, string> $arguments
|
* @param array<int, string> $arguments
|
||||||
* @return array<int, string>
|
* @return array<int, string>
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Contracts\Plugins;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
interface HandlesOriginalArguments
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param array<int, string> $arguments
|
||||||
|
*/
|
||||||
|
public function handleOriginalArguments(array $arguments): void;
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Pest\Contracts\Plugins;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
interface Shutdownable
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Shutdowns the plugin.
|
|
||||||
*/
|
|
||||||
public function shutdown(): void;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Contracts\Plugins;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
interface Terminable
|
||||||
|
{
|
||||||
|
public function terminate(): void;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Contracts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
interface Restarter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param array<int, string> $arguments
|
||||||
|
*/
|
||||||
|
public function maybeRestart(string $projectRoot, array $arguments): void;
|
||||||
|
}
|
||||||
@@ -6,8 +6,5 @@ namespace Pest\Contracts;
|
|||||||
|
|
||||||
interface TestCaseFilter
|
interface TestCaseFilter
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Whether the test case is accepted.
|
|
||||||
*/
|
|
||||||
public function accept(string $testCaseFilename): bool;
|
public function accept(string $testCaseFilename): bool;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,5 @@ use Pest\Factories\TestCaseMethodFactory;
|
|||||||
|
|
||||||
interface TestCaseMethodFilter
|
interface TestCaseMethodFilter
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Whether the test case method is accepted.
|
|
||||||
*/
|
|
||||||
public function accept(TestCaseMethodFactory $factory): bool;
|
public function accept(TestCaseMethodFactory $factory): bool;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Evaluators;
|
||||||
|
|
||||||
|
use Pest\Factories\Attribute;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class Attributes
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param iterable<int, Attribute> $attributes
|
||||||
|
*/
|
||||||
|
public static function code(iterable $attributes): string
|
||||||
|
{
|
||||||
|
return implode(PHP_EOL, array_map(function (Attribute $attribute): string {
|
||||||
|
$name = $attribute->name;
|
||||||
|
|
||||||
|
if ($attribute->arguments === []) {
|
||||||
|
return " #[\\{$name}]";
|
||||||
|
}
|
||||||
|
|
||||||
|
$arguments = array_map(fn (string $argument): string => var_export($argument, true), iterator_to_array($attribute->arguments));
|
||||||
|
|
||||||
|
return sprintf(' #[\\%s(%s)]', $name, implode(', ', $arguments));
|
||||||
|
}, iterator_to_array($attributes)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,11 +14,8 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class AfterAllAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class AfterAllAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $filename)
|
public function __construct(string $filename)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf('The afterAll already exists in the filename `%s`.', $filename));
|
parent::__construct(sprintf('The [afterAll] hook is already defined in [%s]. Each test file may only define it once.', $filename));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,8 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class AfterAllWithinDescribe extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class AfterAllWithinDescribe extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $filename)
|
public function __construct(string $filename)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf('The afterAll method can not be used within describe functions. Filename `%s`.', $filename));
|
parent::__construct(sprintf('The [afterAll] hook may not be used inside a [describe] block. Please move it to the top level of [%s].', $filename));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Exceptions;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessEditor;
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessTrace;
|
||||||
|
use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class AfterBeforeTestFunction extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
|
{
|
||||||
|
public function __construct(string $filename)
|
||||||
|
{
|
||||||
|
parent::__construct(sprintf('The [after] hook may only be chained onto [beforeEach] inside a [describe] block. Please move it inside one in [%s].', $filename));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Exceptions;
|
||||||
|
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessEditor;
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessTrace;
|
||||||
|
use Pest\Contracts\Panicable;
|
||||||
|
use Pest\Support\View;
|
||||||
|
use RuntimeException;
|
||||||
|
use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class BaselineFetchFailed extends RuntimeException implements ExceptionInterface, Panicable, RenderlessEditor, RenderlessTrace
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly string $headline,
|
||||||
|
private readonly string $hint,
|
||||||
|
private readonly bool $hasAnchor = false,
|
||||||
|
) {
|
||||||
|
parent::__construct($headline);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(OutputInterface $output): void
|
||||||
|
{
|
||||||
|
View::renderUsing($output);
|
||||||
|
|
||||||
|
if (! $this->hasAnchor) {
|
||||||
|
View::render('components.badge', ['type' => 'ERROR', 'content' => $this->headline]);
|
||||||
|
$this->renderChild($output, $this->hint.' Or use [--fresh] to record locally.');
|
||||||
|
$output->writeln('');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->renderChild($output, $this->headline);
|
||||||
|
$this->renderChild($output, $this->hint.' Or use [--fresh] to record locally.');
|
||||||
|
$output->writeln('');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exitCode(): int
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderChild(OutputInterface $output, string $text): void
|
||||||
|
{
|
||||||
|
$output->writeln(sprintf(' <fg=gray>─ %s</>', $text));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,11 +14,8 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class BeforeAllAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class BeforeAllAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $filename)
|
public function __construct(string $filename)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf('The beforeAll already exists in the filename `%s`.', $filename));
|
parent::__construct(sprintf('The [beforeAll] hook is already defined in [%s]. Each test file may only define it once.', $filename));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,8 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class BeforeAllWithinDescribe extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class BeforeAllWithinDescribe extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $filename)
|
public function __construct(string $filename)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf('The beforeAll method can not be used within describe functions. Filename `%s`.', $filename));
|
parent::__construct(sprintf('The [beforeAll] hook may not be used inside a [describe] block. Please move it to the top level of [%s].', $filename));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,8 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class DatasetAlreadyExists extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class DatasetAlreadyExists extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $name, string $scope)
|
public function __construct(string $name, string $scope)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf('A dataset with the name `%s` already exists in scope [%s].', $name, $scope));
|
parent::__construct(sprintf('A dataset named [%s] is already registered in [%s]. Please choose a different name.', $name, $scope));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Pest\Exceptions;
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
|
|
||||||
final class DatasetArgsCountMismatch extends Exception
|
|
||||||
{
|
|
||||||
public function __construct(int $requiredCount, int $suppliedCount)
|
|
||||||
{
|
|
||||||
parent::__construct(sprintf('Test expects %d arguments but dataset only provides %d', $requiredCount, $suppliedCount));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
final class DatasetArgumentsMismatch extends Exception
|
||||||
|
{
|
||||||
|
public function __construct(int $requiredCount, int $suppliedCount)
|
||||||
|
{
|
||||||
|
if ($requiredCount <= $suppliedCount) {
|
||||||
|
parent::__construct('The test arguments do not match the dataset keys. Please make sure each argument is named after a key in the dataset.');
|
||||||
|
} else {
|
||||||
|
parent::__construct(sprintf('The test expects [%d] argument(s), but the dataset only provides [%d].', $requiredCount, $suppliedCount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,11 +14,8 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class DatasetDoesNotExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class DatasetDoesNotExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $name)
|
public function __construct(string $name)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf("A dataset with the name `%s` does not exist. You can create it using `dataset('%s', ['a', 'b']);`.", $name, $name));
|
parent::__construct(sprintf("A dataset named [%s] does not exist. You may create one using `dataset('%s', ['a', 'b']);`.", $name, $name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,18 +15,16 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
final class DatasetMissing extends BadFunctionCallException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class DatasetMissing extends BadFunctionCallException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Creates a new Exception instance.
|
|
||||||
*
|
|
||||||
* @param array<string, string> $arguments
|
* @param array<string, string> $arguments
|
||||||
*/
|
*/
|
||||||
public function __construct(string $file, string $name, array $arguments)
|
public function __construct(string $file, string $name, array $arguments)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf(
|
parent::__construct(sprintf(
|
||||||
"A test with the description '%s' has %d argument(s) ([%s]) and no dataset(s) provided in %s",
|
'The test [%s] in [%s] expects [%d] argument(s) ([%s]), but no dataset was provided. Please chain [with()] onto the test to supply one.',
|
||||||
$name,
|
$name,
|
||||||
|
$file,
|
||||||
count($arguments),
|
count($arguments),
|
||||||
implode(', ', array_map(static fn (string $arg, string $type): string => sprintf('%s $%s', $type, $arg), array_keys($arguments), $arguments)),
|
implode(', ', array_map(static fn (string $arg, string $type): string => sprintf('%s $%s', $type, $arg), array_keys($arguments), $arguments)),
|
||||||
$file,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Exceptions;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class DatasetProviderError extends RuntimeException
|
||||||
|
{
|
||||||
|
public function __construct(Throwable $previous)
|
||||||
|
{
|
||||||
|
parent::__construct($previous->getMessage(), (int) $previous->getCode(), $previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,11 +11,8 @@ use Exception;
|
|||||||
*/
|
*/
|
||||||
final class ExpectationNotFound extends Exception
|
final class ExpectationNotFound extends Exception
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new ExpectationNotFound instance from the given name.
|
|
||||||
*/
|
|
||||||
public static function fromName(string $name): ExpectationNotFound
|
public static function fromName(string $name): ExpectationNotFound
|
||||||
{
|
{
|
||||||
return new self("Expectation [$name] does not exist.");
|
return new self("The expectation [$name] does not exist. You may register it using [expect()->extend()].");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Exceptions;
|
||||||
|
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessTrace;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class FatalException extends RuntimeException implements RenderlessTrace
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
@@ -14,11 +14,8 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class FileOrFolderNotFound extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class FileOrFolderNotFound extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $filename)
|
public function __construct(string $filename)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf('The file or folder with the name `%s` could not be found.', $filename));
|
parent::__construct(sprintf('The file or folder [%s] could not be found. Please check the path and try again.', $filename));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user