mirror of
https://github.com/pestphp/pest.git
synced 2026-03-06 15:57:21 +01:00
Compare commits
1493 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 221ac62f03 | |||
| 4b6c949032 | |||
| 1408cffc02 | |||
| 95b5379945 | |||
| cb1c777b9b | |||
| 176d3efbc6 | |||
| d635665c1b | |||
| 22467d05c8 | |||
| 7a699e16db | |||
| 341ba56bb9 | |||
| a320cc3e2b | |||
| 8b428357b2 | |||
| bb6d6b0951 | |||
| b94b8c6a4f | |||
| 43894afa18 | |||
| 28de31a8b9 | |||
| 974e70d7d1 | |||
| f914f1ad87 | |||
| 14dd5cb57b | |||
| 077ed287b7 | |||
| 9a41f2ff82 | |||
| 88f29e4180 | |||
| c34f649724 | |||
| e1e4f8d884 | |||
| 2d6d8b810b | |||
| bcd1503cad | |||
| e0f2919f62 | |||
| a8bd353ba6 | |||
| ed3bb2634d | |||
| 48ae4bfc18 | |||
| 26bb0b6eec | |||
| 236a9bd7ce | |||
| f4d19c90d3 | |||
| ecbaff503e | |||
| 9d0cd32e3f | |||
| 8782e9c34e | |||
| a4932e41de | |||
| 522ac55d5f | |||
| b3a8aef6ac | |||
| 8068bebebd | |||
| b887116e5c | |||
| 6071d86ac6 | |||
| 5723da1043 | |||
| 17e242a5f6 | |||
| c9a8007811 | |||
| c64c41a4d9 | |||
| da4bf7f5c3 | |||
| bb5dbc878e | |||
| e3ab27e2ec | |||
| 8f91f40e8e | |||
| 2973b600f5 | |||
| 37d4434000 | |||
| 627b673380 | |||
| bc1b11054c | |||
| 8350d74020 | |||
| 1431a2a897 | |||
| 2906a2de2d | |||
| 540c2a56bd | |||
| 2fb8690320 | |||
| 88e047bd27 | |||
| 8f630c064f | |||
| 54fd188299 | |||
| 4db2318a66 | |||
| 7e1c769d1c | |||
| 7a57f9f9b8 | |||
| 961e0aec66 | |||
| ccfcd336fe | |||
| 7287d65865 | |||
| d96ddaeaac | |||
| 085d3436c8 | |||
| 2da899a2b1 | |||
| 48ea48981b | |||
| 084f7c596f | |||
| aafdf6f39c | |||
| d4c66d73a0 | |||
| 7d89d3546e | |||
| 840364891c | |||
| 1f3e5115c7 | |||
| 9de85175db | |||
| 0bf051610d | |||
| 42fc80b76d | |||
| ff1249b5cf | |||
| 331585a0ba | |||
| 0c808736b8 | |||
| 9f0dc108fa | |||
| cf5c6f9ffd | |||
| f6d6a4db78 | |||
| ef3305ec23 | |||
| f6ce6cece7 | |||
| 32923f32a8 | |||
| 02f3202a61 | |||
| e6af0c8a77 | |||
| 52a8183fcb | |||
| c2f57811e1 | |||
| 639029349d | |||
| 595172aba6 | |||
| e7af5a4cf9 | |||
| ef7d1527da | |||
| 2a370a2a76 | |||
| b828843974 | |||
| 0b99c72937 | |||
| ec4f15132e | |||
| 0e4cc94471 | |||
| 9e89fb5e23 | |||
| 26cf278103 | |||
| 050fe38a90 | |||
| f07c3a2480 | |||
| ae365324a8 | |||
| b4b212a426 | |||
| f52d4392a6 | |||
| c662c59daf | |||
| ef4a487322 | |||
| 30b151f927 | |||
| b93bf82be6 | |||
| ea3827fe7f | |||
| 7cd6b4ff40 | |||
| 544af117bb | |||
| d71af91360 | |||
| fccb90c8ea | |||
| 4b1cad2493 | |||
| 57cd294f0f | |||
| 837552b11d | |||
| 966c382d1a | |||
| 43e17741eb | |||
| d6e3906da6 | |||
| dbef162fa6 | |||
| 1bd9c9e60d | |||
| d1adc65037 | |||
| ae15fa668d | |||
| 88e576c3a3 | |||
| 60950c624f | |||
| 3d1e0c3f9f | |||
| d6c8149a3a | |||
| aedf2e3727 | |||
| 6316828fbd | |||
| 24a9ab6761 | |||
| 0a76032b1b | |||
| 730bc7d3b3 | |||
| 99c7bb705e | |||
| fc747c306d | |||
| b6b8a72a6a | |||
| 36f005981b | |||
| eadc6f4307 | |||
| 2876ac590d | |||
| 99436f94f9 | |||
| 60f82a21db | |||
| f68e6cefd4 | |||
| b939d94cda | |||
| 37d71adb4e | |||
| 813f63360b | |||
| ad97b202c4 | |||
| 1e61034e86 | |||
| 3f6b2e856e | |||
| 3df68b288a | |||
| a75e899d98 | |||
| f25a9f5558 | |||
| 69a0c3ba99 | |||
| e1bb1d8c2d | |||
| 62238b2714 | |||
| e9f83dc020 | |||
| b15dc03d16 | |||
| 3af60b874e | |||
| 1da1eeb384 | |||
| 8abad572ec | |||
| 20012b65fb | |||
| 244087db27 | |||
| b54c24a589 | |||
| c664094f35 | |||
| f5d71b9282 | |||
| a478798cfe | |||
| a6e133a194 | |||
| 46b785f29f | |||
| 69b1c08558 | |||
| 2a1db41880 | |||
| 64dbcf0a26 | |||
| 683910bff4 | |||
| 7492b331a0 | |||
| ae8df3f51d | |||
| 658c428b71 | |||
| d7ecef80e9 | |||
| 09644640bf | |||
| ba1f2df40b | |||
| 0348037638 | |||
| 23b42730ba | |||
| 9ded563e4b | |||
| 8a48a3da7f | |||
| 61a35d65c4 | |||
| aafbd04753 | |||
| 56a2f1b733 | |||
| aa3369757c | |||
| f309e06292 | |||
| 05989c35a7 | |||
| 813a74759b | |||
| c9fb8e6f52 | |||
| d374a46c4d | |||
| 0ce70a0180 | |||
| 7fc12613a8 | |||
| d0e949bf19 | |||
| 957dabc3f0 | |||
| b4b4bf3685 | |||
| 49619ff2b5 | |||
| efca71f1e7 | |||
| 86fdfb75cf | |||
| f865e93050 | |||
| 4fccddebb0 | |||
| 15ef5f12b8 | |||
| c87aabf5d4 | |||
| 0d9c11c99a | |||
| 7fdc7d6997 | |||
| 7fe8399d48 | |||
| 9be89c4042 | |||
| a61db76c24 | |||
| ea6af719e0 | |||
| 9f5506364b | |||
| 2d70f18a93 | |||
| 427ee89ae6 | |||
| dd4247e150 | |||
| cac777bce1 | |||
| 3c0390b1ac | |||
| 2df49947df | |||
| 26369a19b4 | |||
| 2b3e146d5a | |||
| aff8e33eca | |||
| 104db3f6a5 | |||
| ec8fb202b3 | |||
| e22f6e1e4d | |||
| d57437ff02 | |||
| eb64113a3d | |||
| 3b5f9ec59d | |||
| 666b2f3fd0 | |||
| 2bc33c7cd5 | |||
| 8ee7a4deef | |||
| 69afb31bb9 | |||
| 5ca4c5bca9 | |||
| b6fb81e506 | |||
| 735f131222 | |||
| 8a58e984fe | |||
| 0e9c1bc0f7 | |||
| e749af6d91 | |||
| 052b9e051b | |||
| 6ddc5c8572 | |||
| 8eaf4859ff | |||
| e1406554fc | |||
| 504fd04705 | |||
| 7fe7a01d43 | |||
| b7ec3c59b8 | |||
| 757a98230e | |||
| c319a8e84c | |||
| 8d33c9dc89 | |||
| 2ae06a0e2d | |||
| f107fdfa08 | |||
| 2561d47bb5 | |||
| aff11486b2 | |||
| 17cda168e1 | |||
| d5495a7e3a | |||
| 87ee5ef36b | |||
| a34001faf0 | |||
| dd840f8861 | |||
| f94ea9ba0d | |||
| 6338d762fa | |||
| f48ae48677 | |||
| 1658176fe1 | |||
| 2f519261f5 | |||
| 7466667c08 | |||
| d03302db7b | |||
| 951b54e7cd | |||
| 2929af4715 | |||
| 48309931ef | |||
| d69f61c8d3 | |||
| a8f0b96338 | |||
| c1663191ee | |||
| ded14c5425 | |||
| 80bea0b4b4 | |||
| 2cdcf7f3e3 | |||
| 8d34d0743f | |||
| fe763c1dc7 | |||
| 8a2aeff9a0 | |||
| b1545f270f | |||
| 975f1bd9fa | |||
| fed9776f9b | |||
| 803ba535c3 | |||
| 60358461c4 | |||
| bdff30b511 | |||
| 6e3d940c8c | |||
| b702e0c084 | |||
| 48caaed58c | |||
| c45a303451 | |||
| 2bffd6a51e | |||
| 179e6a9db1 | |||
| 94cef989d3 | |||
| 6982b02d48 | |||
| 3a4a57a262 | |||
| 67d26388de | |||
| a26e3946bd | |||
| 026d4920cf | |||
| d305f4dca0 | |||
| 217fae0967 | |||
| 349e2f45df | |||
| 0675529320 | |||
| 0839c7e127 | |||
| 15931e2418 | |||
| f6676118ac | |||
| 78673ceeb1 | |||
| e228d565af | |||
| 51bcf6a2be | |||
| feedeab7e3 | |||
| 76d1a8ffed | |||
| 5436ff8c49 | |||
| e8b10fcc91 | |||
| 4083e6e26e | |||
| 3ecf351432 | |||
| 6595e6960b | |||
| 330b4f0171 | |||
| 43a7df2cc1 | |||
| 0e98c5c5c4 | |||
| 39ee5b9b08 | |||
| 227d32a1fd | |||
| b7e2cd758f | |||
| c9a02b964d | |||
| 0fd5b2efe1 | |||
| 476f56b617 | |||
| 174a9ca60b | |||
| 406fcf72ae | |||
| 37b1367d25 | |||
| a55953a8e0 | |||
| f120b32791 | |||
| ca113127cc | |||
| 138bdf599b | |||
| 522504916b | |||
| b04207d9ea | |||
| 9596274b14 | |||
| 8d018ea3f1 | |||
| 0f0af9eefb | |||
| 8aece3981d | |||
| 1c9c408cf3 | |||
| 3911cfec6d | |||
| 6d7fd66f82 | |||
| e1e926076a | |||
| 68bf8a2d26 | |||
| c0d9f739b3 | |||
| d33cc19778 | |||
| f940b89284 | |||
| 21990ccd8b | |||
| c01654efcc | |||
| 1ad631c528 | |||
| ecb5d9c83e | |||
| 0039dbde38 | |||
| 70f447a8bc | |||
| d29d68a2c2 | |||
| c5f6923e5a | |||
| 34878bf432 | |||
| fab08f0e20 | |||
| 850955d7dd | |||
| 606d627f1d | |||
| dfe8a3deeb | |||
| 3bc356ceec | |||
| 7ad045d6b7 | |||
| 3324455e0a | |||
| 7ffc5602b4 | |||
| 8944fdd96f | |||
| 3aaa93931a | |||
| d77715b0fe | |||
| 17cc194ad1 | |||
| f9397924fa | |||
| 9281060ab5 | |||
| 6a3cc48d6b | |||
| 7194a87d0a | |||
| 3e325e3364 | |||
| 348bd4b923 | |||
| f6cfd425c6 | |||
| 7683d791f4 | |||
| 649047f087 | |||
| 3aab10774e | |||
| e37f3d3d45 | |||
| e105afce83 | |||
| 3d16183a93 | |||
| 129325db8e | |||
| f214a78c75 | |||
| 42c10f9a62 | |||
| 8d4fa06ba3 | |||
| a73744c081 | |||
| e2ab53ed53 | |||
| 5c4e98cf0c | |||
| 6bad9d302b | |||
| db00fc8c09 | |||
| 98b04632ce | |||
| cbd4cefc1a | |||
| 84b8c389b2 | |||
| e34364d8b1 | |||
| 80854d5f87 | |||
| cbee6e76b0 | |||
| 12618ff8b3 | |||
| 115bb551df | |||
| 3799dc7a2a | |||
| a8b8adafdf | |||
| 961e44b5ad | |||
| 20e99af194 | |||
| 1c888ce055 | |||
| f3a748fee3 | |||
| 2a68234bd3 | |||
| 8275d7e08d | |||
| e9630ff9a6 | |||
| 97431f2622 | |||
| df8a64b017 | |||
| accfe86eaa | |||
| 667918905f | |||
| c1231c9bde | |||
| bac941715a | |||
| 8a3caa5e7f | |||
| 7bc513bc2b | |||
| eecc598471 | |||
| d8d1baf945 | |||
| c5cb1fc325 | |||
| 93a118a532 | |||
| d6ddd7326a | |||
| fa1145dced | |||
| 06ef57060f | |||
| 921d580d47 | |||
| 7c7c8358e9 | |||
| 9b54b61e2e | |||
| b93e3524f5 | |||
| b6598af59f | |||
| 4de7284657 | |||
| 0e0e2adfbe | |||
| 08b62f6633 | |||
| b0b83505af | |||
| 3d5271f512 | |||
| 462982bb28 | |||
| 245f636fa8 | |||
| 9fd8610390 | |||
| 7a41a540f2 | |||
| fb588711ef | |||
| 1e7e164d84 | |||
| 036f2de795 | |||
| 45011ebd14 | |||
| e9564febaf | |||
| 579b975318 | |||
| d8f3e9c313 | |||
| 01ccbfe254 | |||
| af82c1005a | |||
| 02e3b5aa77 | |||
| fb378bed56 | |||
| 1454bcf165 | |||
| f20a7cc9e4 | |||
| b21bfe3666 | |||
| 0669423138 | |||
| 3f111708e3 | |||
| a1f47f1a90 | |||
| 24a17cfb7c | |||
| bee4eda3ef | |||
| 797ebb2986 | |||
| ddb75441e7 | |||
| c61d70abf1 | |||
| 0953ae431e | |||
| b3a2e6026f | |||
| 46ffdf9c7a | |||
| 99e607dcfb | |||
| 1006b9cb7b | |||
| 6769231b00 | |||
| 3ff95faaaa | |||
| eab944023c | |||
| 00d3c735fe | |||
| 173a72e69d | |||
| 38a82cd142 | |||
| 42ceddf374 | |||
| 6252e288b9 | |||
| 8594980dae | |||
| c22fed89f3 | |||
| 429660fe57 | |||
| a4ec4b2841 | |||
| c0d66b7dc7 | |||
| 787e7f6ce5 | |||
| 2c338468bc | |||
| d8b456d89a | |||
| bff18ab381 | |||
| 4e95a65af1 | |||
| 1706e1d2e0 | |||
| 324823fef0 | |||
| afedf83f5d | |||
| 402995bf29 | |||
| c0fc52f719 | |||
| 41dbc947fa | |||
| fe09184c9c | |||
| 4821bd4423 | |||
| 1461c9ba88 | |||
| 452d4b26b9 | |||
| 3dd7b677bd | |||
| 1e86dcecd0 | |||
| 949ba1f298 | |||
| 77b7181b08 | |||
| 6e120d60f6 | |||
| 10ff36480a | |||
| ffb20c9956 | |||
| 751a532124 | |||
| 80c411be44 | |||
| db5c11d96e | |||
| f3ed9bdf8e | |||
| 53c20d9cd2 | |||
| fca0c8fc0c | |||
| c8aa204ee0 | |||
| a3889110f1 | |||
| cda4665979 | |||
| 09e2a26b7d | |||
| 3dc451cf44 | |||
| 3795870150 | |||
| 24204adc09 | |||
| a027e24e3c | |||
| 15e2e1711b | |||
| d363321db5 | |||
| 3ffed844a6 | |||
| 9cf1005183 | |||
| 3601d01bd5 | |||
| d0136b63d4 | |||
| 00029c15ef | |||
| a5cbdea868 | |||
| edd1d890ca | |||
| 985bbf4ea5 | |||
| 443f848386 | |||
| 32dbac87c8 | |||
| 1dc33070fe | |||
| 1079793ccf | |||
| 21364779f9 | |||
| 50d8688b79 | |||
| 27baad82d0 | |||
| a894386b49 | |||
| 2465b88462 | |||
| 7660517f7c | |||
| 74470ec96d | |||
| 03ccea8978 | |||
| ed89689425 | |||
| 62d7652376 | |||
| 0e85921964 | |||
| 51ec80f11f | |||
| e08d6f2803 | |||
| 22030bffd0 | |||
| bc105bc818 | |||
| 2c3a296040 | |||
| 10b204e19d | |||
| 36130eb7a0 | |||
| 6d5a8a9235 | |||
| 04663e0c8e | |||
| 6f9ebe04b0 | |||
| 8ca4caaffa | |||
| 8baaf80691 | |||
| e91c85496f | |||
| 6bf92d20ad | |||
| 35f607c46c | |||
| b99f65d936 | |||
| ead2dfd0a9 | |||
| d2ca6e630d | |||
| 12b48a6cf6 | |||
| 635a71ce66 | |||
| b99c4d611b | |||
| af8886c062 | |||
| 30b1f6cd0a | |||
| d24091d224 | |||
| b3d3b4485d | |||
| 108fe45164 | |||
| e1a30e3c92 | |||
| d969eaac2c | |||
| dd081c59b7 | |||
| b0264886c9 | |||
| 6dcdfdb82f | |||
| e7d75365fd | |||
| 266447bcc0 | |||
| b74a688677 | |||
| b1f9ce2283 | |||
| e64b6fe924 | |||
| c8697e0310 | |||
| 98db677646 | |||
| 6c3a8be049 | |||
| 129a733888 | |||
| 5aeda553a4 | |||
| 0146186ddb | |||
| 106b279ed0 | |||
| 05f44ed84a | |||
| 6e7890c206 | |||
| 2d2760e15c | |||
| b2eb69cbc1 | |||
| 0fc90ec181 | |||
| 337d55b9ab | |||
| 9f1e3dadf4 | |||
| 24943e5fbb | |||
| 33d1579660 | |||
| 8047ae570d | |||
| e236bf3821 | |||
| 32c2df0444 | |||
| 7a0e841a0d | |||
| ce2dcc3128 | |||
| 52314a4928 | |||
| d65cc9be84 | |||
| beb14ce5f4 | |||
| c49700dd47 | |||
| 3d0f267a5c | |||
| 729d7c4bef | |||
| d10b8f5d2c | |||
| 8494d4566a | |||
| 7fd9cfa2e9 | |||
| 3df78f2edf | |||
| 5f0752e874 | |||
| cdd67a6900 | |||
| fce24ef01f | |||
| 47264416b1 | |||
| 86dca12c09 | |||
| 0b5cea6df1 | |||
| 9258dcc988 | |||
| 24edab45b1 | |||
| f3371e51fe | |||
| 83b9f86972 | |||
| ca30677c53 | |||
| b205b8e748 | |||
| b9b9de1945 | |||
| 22895ce682 | |||
| 3829623984 | |||
| 3f3bc525bc | |||
| ece0930319 | |||
| 94585789dc | |||
| 7ea6d8a35d | |||
| 9dd40e4610 | |||
| 8cdca8d012 | |||
| 7bcd3ebaee | |||
| d4a8a3ec37 | |||
| f460cceeba | |||
| f3f58c7f52 | |||
| f2a9b73b83 | |||
| ffd4e6d577 | |||
| 5287eff507 | |||
| f6004e07c1 | |||
| b2cd60395f | |||
| 183f975166 | |||
| 8ace01b6f1 | |||
| 4b213d63bd | |||
| da5c21de8f | |||
| 408ae4cad8 | |||
| fc2484a28a | |||
| be58d5517a | |||
| 602403eb59 | |||
| 5f1776829b | |||
| 4a22c5d673 | |||
| 3943919709 | |||
| 8174f2d973 | |||
| 22d16d54c6 | |||
| cd34f0ba81 | |||
| 648c6c5a27 | |||
| 2b687a7269 | |||
| e8c2fe6e35 | |||
| 88dfabc633 | |||
| a35bf249a0 | |||
| cf47b45262 | |||
| eed3ed5513 | |||
| de46ee0f64 | |||
| 1e011c7b40 | |||
| 04dcebf3aa | |||
| 0346450a51 | |||
| fc53f08e37 | |||
| bc4e5b9b4e | |||
| c3a445534b | |||
| 70877bfad4 | |||
| 55376d32e5 | |||
| 8835502074 | |||
| ba9b06adf3 | |||
| e92d9bfaae | |||
| d802e88148 | |||
| e853792a59 | |||
| b0fbe54181 | |||
| 2a649bdfc0 | |||
| e042bf7d3a | |||
| 3ff71a4563 | |||
| 205238fcbf | |||
| ba06c5a76d | |||
| 78ffc491e9 | |||
| 7f38de11b7 | |||
| a6e34d204c | |||
| 66d47e4922 | |||
| 7d70b6e95a | |||
| 076dcab4c5 | |||
| 6f42e336c9 | |||
| 0d72b5197c | |||
| 7691e3c602 | |||
| b43a59868d | |||
| 457972716f | |||
| ae029660e3 | |||
| dc12419078 | |||
| f0ddd10a54 | |||
| 4daf7ee4ab | |||
| d60f320382 | |||
| 3c3e6b160b | |||
| c99f8f196e | |||
| 9cc4ecd5ab | |||
| 8d96f975e0 | |||
| 7f214f9e12 | |||
| da258fa89f | |||
| f23f857903 | |||
| fec11928cf | |||
| f6131d042b | |||
| 543b9542ae | |||
| 1681c1f4f8 | |||
| f41c3ce9ba | |||
| 847b06e558 | |||
| 601c4b01fc | |||
| 05c1c82ae2 | |||
| 1bde49b3c4 | |||
| b22f5e0c85 | |||
| dd643faa5c | |||
| facbf05016 | |||
| 58cff003d8 | |||
| ae997e6eee | |||
| e6c7d68def | |||
| 0b19672963 | |||
| a16a19e121 | |||
| 3dd10b3c7c | |||
| 12e63c7376 | |||
| 2f0cd7a4e3 | |||
| 447af55e7c | |||
| 253e9d10c8 | |||
| 536ce1eca0 | |||
| 4331b2aaf6 | |||
| 8d99cacc95 | |||
| 3c38facc8a | |||
| ed389d35d0 | |||
| 60c0636523 | |||
| 16b6f96b47 | |||
| 042f2ec3f3 | |||
| 851ce36010 | |||
| 4f386894bd | |||
| 2289adade2 | |||
| 29e21e3814 | |||
| 8367af22e7 | |||
| e3d678dc04 | |||
| 4ae482c707 | |||
| 075c31bc78 | |||
| 2125bf9668 | |||
| dbf3c0a8cf | |||
| eca5f89e59 | |||
| 0b0beac122 | |||
| 578e97123d | |||
| 01d672d563 | |||
| 490b2d66e5 | |||
| 0368c4846f | |||
| 4dfc02c5da | |||
| 5c84b0c6d3 | |||
| b6c06e8c30 | |||
| c6435d5606 | |||
| 2887d212e3 | |||
| cadae52d5d | |||
| d9749ca65b | |||
| c2070cd99d | |||
| 03d34e9a10 | |||
| 45e76a6df6 | |||
| 28dd3c2a03 | |||
| 5de981d923 | |||
| a55b31e7c3 | |||
| 5f0bd8180e | |||
| e1f1fcccbe | |||
| ab04aef561 | |||
| 79ddb1f58e | |||
| c7a2e68941 | |||
| 5c592928d4 | |||
| bcab4224fb | |||
| 892f70b5b5 | |||
| 5c7de5ad75 | |||
| 995088b522 | |||
| ef503646ee | |||
| a760470e48 | |||
| 1d4c1a5359 | |||
| 8e32b88fc8 | |||
| 1a7baad338 | |||
| 31d1b1b91d | |||
| 7524c80af6 | |||
| 721d5134b7 | |||
| 0b5321fdd7 | |||
| c86058fed1 | |||
| 8b295b5e9d | |||
| 221248e691 | |||
| 7621247bb7 | |||
| 463a50ebd4 | |||
| 62aabc6ae1 | |||
| 1ca9aa5ca6 | |||
| 7a76f8dce2 | |||
| beca27599c | |||
| 256b167eaf | |||
| 5526d4c24d | |||
| 7ea138c640 | |||
| c4a659c3b5 | |||
| 0a3991c314 | |||
| d1a9e0bbe3 | |||
| 17eacfdf95 | |||
| 9ec0762d41 | |||
| 30f39f1850 | |||
| 8ee07330b3 | |||
| cffde4564d | |||
| ce7a7649a2 | |||
| eeed7e6a0a | |||
| d6844f5239 | |||
| 06c4019e81 | |||
| 7785a8cc58 | |||
| 663516c1e3 | |||
| e83667a20b | |||
| aa96d75fb9 | |||
| a5af4bc5ed | |||
| 57161ba5ad | |||
| b1a9254fc1 | |||
| e56e818659 | |||
| 79de0d5875 | |||
| f6f8140ebc | |||
| eed221af46 | |||
| 524457a4e6 | |||
| 8e289b7a7d | |||
| 172b69cf15 | |||
| 475279a4fa | |||
| 4b236bf9ff | |||
| 50e2d10029 | |||
| fa8a57f1ab | |||
| 715f8b420b | |||
| c776bcf86d | |||
| a0637d86ff | |||
| 1a941d7f92 | |||
| b5959aa3fa | |||
| 8861dd2401 | |||
| da73f4b395 | |||
| d5097d0fe5 | |||
| 022ad4be0d | |||
| 2d2a83e9e8 | |||
| 67c7bee4fa | |||
| 675b0f1ec8 | |||
| c2b86c3ab3 | |||
| 46337b8085 | |||
| df172d8eed | |||
| 24b9160b79 | |||
| a7860b0b8e | |||
| 7471c224fa | |||
| 2996135155 | |||
| 1abab8d440 | |||
| e52c83e5be | |||
| 5ed4545737 | |||
| 8b39df68ce | |||
| 04d8a3762b | |||
| 0f7c8d00d6 | |||
| 12fb4f8639 | |||
| 6a84b825e6 | |||
| e8595c56b3 | |||
| 4a9fb2fa74 | |||
| d8fae6d689 | |||
| 252f9a0e46 | |||
| 8d24b4a217 | |||
| 43920f79a9 | |||
| 55bfc5856b | |||
| cd9d4acbc2 | |||
| 2b5355419a | |||
| 22b822ce87 | |||
| b2c298b926 | |||
| 671f3df115 | |||
| 2dd77001b7 | |||
| 5f574ded81 | |||
| 6309e6818d | |||
| 4813ab6ffb | |||
| 4ebba1298a | |||
| 2e0d1bb5a0 | |||
| 09d2b16767 | |||
| f56556eb73 | |||
| f387ca8624 | |||
| ca9d783cf9 | |||
| 863ddea50b | |||
| 00b4bb6305 | |||
| d217503a6a | |||
| b6012862c4 | |||
| 60c0ad006f | |||
| 79ff332afe | |||
| 595bbe32a4 | |||
| 328427bfdb | |||
| 51f556799c | |||
| 5e0a0855ea | |||
| 7ec3460d73 | |||
| 4c8c42cd20 | |||
| 09682dd393 | |||
| 82c18d3848 | |||
| 3bdba9210d | |||
| 371620d161 | |||
| d10281f851 | |||
| 47ceb2419b | |||
| 771b5b2e53 | |||
| 05f72f9b6d | |||
| f9de1b9c00 | |||
| 9516e56242 | |||
| 5f315fc899 | |||
| e59606818d | |||
| e16104350e | |||
| df31191f4e | |||
| 5b310f6f93 | |||
| 0200f90e9a | |||
| 027e69e48f | |||
| 33e01e3805 | |||
| 13781dcd14 | |||
| 164bad437a | |||
| 7ddcc03ad9 | |||
| bbe4445257 | |||
| d90ddf889c | |||
| 5907164749 | |||
| eb6de433b7 | |||
| 32f72cdf55 | |||
| c160d97428 | |||
| 2a8de0565f | |||
| 5ab3c6e2d7 | |||
| 50ece576a7 | |||
| 9c202fa2d7 | |||
| e4e9cb09e4 | |||
| 6a7597c01a | |||
| dd05452edd | |||
| 99ea9f42e5 | |||
| 7d6a86adc7 | |||
| 7fbd2661c8 | |||
| 6ce678d1c2 | |||
| 5049b996db | |||
| d838456caa | |||
| e45c4ff4f1 | |||
| fa3959db17 | |||
| b97e206f7a | |||
| 7e9edecc7f | |||
| dc75b34deb | |||
| 8e22803797 | |||
| c290909eb3 | |||
| f2e56da2da | |||
| e6b258534a | |||
| acef002a2d | |||
| 11ebe014fb | |||
| 2d13c6e219 | |||
| fbcb492c79 | |||
| 4f67eff619 | |||
| 3c2c767e09 | |||
| ff527baa1d | |||
| 621718d4b1 | |||
| 59adc57344 | |||
| 82bd836ae9 | |||
| 1680613e12 | |||
| f6d3ce41bc | |||
| d16a48bf0f | |||
| 9459ce4030 | |||
| c773d1cd57 | |||
| 22a1aac84a | |||
| 3a20696da4 | |||
| 99bcf98617 | |||
| 09d9bae988 | |||
| 27de6106ab | |||
| aeded0a356 | |||
| afef1d56e8 | |||
| 4b55de27f1 | |||
| 3d7b6426a1 | |||
| 1d415eb7fb | |||
| 8a384a6d65 | |||
| 7c4dd2f2e7 | |||
| b6f0496c3c | |||
| 9b34650e72 | |||
| db9f1254b5 | |||
| b0e2ce6896 | |||
| 3afdedbd3f | |||
| 4bf69b97bd | |||
| ecb37fce91 | |||
| c1b27579ca | |||
| e64856c664 | |||
| de86598c0d | |||
| 553b45306f | |||
| d96a2485b6 | |||
| 9e9d1cc8cc | |||
| 579bb1b90c | |||
| 5116b4341e | |||
| 7ff64540a6 | |||
| 2e7192ab95 | |||
| 241d4cf94c | |||
| a5ce2dc1a1 | |||
| e21e3080e0 | |||
| 6a7ee90ff5 | |||
| 9d66893d5a | |||
| 64e780cf72 | |||
| 9bf141f698 | |||
| 9904094590 | |||
| 9e5b779abc | |||
| 729638a3bb | |||
| 82768382da | |||
| 3cb52447bb | |||
| 8d670b4ed7 | |||
| 624f6e0acc | |||
| c07cd8c252 | |||
| a10b29a8da | |||
| ea696f819e | |||
| e9b564a50c | |||
| fa16775ee2 | |||
| 55449c956a | |||
| e4b4e55dcd | |||
| 1c57de7e36 | |||
| dd2921fd26 | |||
| 977dbb5bcb | |||
| 49de462250 | |||
| 95e8add29b | |||
| b682fe631d | |||
| d32a648af5 | |||
| 50e9978dc3 | |||
| 17d407a26a | |||
| cdc3bd3f45 | |||
| 95b4192c0d | |||
| 4c911cd0eb | |||
| 7408999b0e | |||
| bb13bdaa80 | |||
| 10e7cbe006 | |||
| 0ad232e9de | |||
| 574cd11a40 | |||
| c04d6d946d | |||
| 36c2a985a6 | |||
| 91eff755fd | |||
| c05d287fcc | |||
| ea0be9e7a4 | |||
| 838ac273ab | |||
| 296e1c37e8 | |||
| 3117f11fae | |||
| 294c41f0dc | |||
| 60afbb2c20 | |||
| 19a45c856e | |||
| 3b784060b8 | |||
| dd5a11a61f | |||
| 9133b88d65 | |||
| 93b9afbd27 | |||
| 6c6bba2a04 | |||
| dd24b7e347 | |||
| ce896b9c83 | |||
| a3c1a61b59 | |||
| 98c62f4b1d | |||
| dd78cc9a50 | |||
| 9027411004 | |||
| 9394aa4649 | |||
| 88dc74bbe4 | |||
| 7023cec432 | |||
| 99d6fb9f5f | |||
| c9f723530d | |||
| 3205b571b0 | |||
| 2c4aef5272 | |||
| 4a45a7cc6b | |||
| ea8ab88056 | |||
| 564a21badd | |||
| 41ce87450f | |||
| fb0eef4200 | |||
| 819da37b89 | |||
| 8eb9c408a9 | |||
| 1f10b46402 | |||
| 7bb12b73e8 | |||
| b8103697c9 | |||
| ca3f8b5702 | |||
| daa01ea44b | |||
| 26d577f9c5 | |||
| 43fb711251 | |||
| 567af55a19 | |||
| c6a2e3b4d0 | |||
| a10428efe6 | |||
| 1440637e41 | |||
| 14cee66dfd | |||
| d048d60d04 | |||
| 1b9162151c | |||
| 885c9f1f06 | |||
| 90efcc8a8a | |||
| 7d35ee9998 | |||
| 584a7ac8a5 | |||
| f21e45ae64 | |||
| c7d26a27b6 | |||
| 54f9397f47 | |||
| ff44589572 | |||
| 53333b56ab | |||
| 6616b6299b | |||
| 0bab649156 | |||
| 2de7cb4726 | |||
| 99500d0cae | |||
| 7eb5478c42 | |||
| d2babb1331 | |||
| a6cced6b63 | |||
| 7917313422 | |||
| e02c22e136 | |||
| 2157644a39 | |||
| b6c2812a91 | |||
| 4b65d2c426 | |||
| 3589f3d5e7 | |||
| 1f39b8d239 | |||
| 7c3c390cbf | |||
| 19a1569fa8 | |||
| 9a0240bc7b | |||
| dd94a843b5 | |||
| 9426d08aa2 | |||
| fe2fac37f8 | |||
| cabd64df00 | |||
| bb57a54089 | |||
| 5e0bfba7bf | |||
| f6c19e469f | |||
| 13f09cc662 | |||
| a7e2856887 | |||
| 721e047485 | |||
| 301ff155a4 | |||
| 40f2065575 | |||
| be906eb823 | |||
| 2cee825f61 | |||
| ea6308bfdf | |||
| c6a6f7e2ab | |||
| 20077c285a | |||
| fa13016785 | |||
| b81bb9d621 | |||
| 2deb53c14f | |||
| 6b8feed08a | |||
| 2fe8e07cf3 | |||
| d2c907868e | |||
| a2900a5e09 | |||
| df934bacd9 | |||
| b0f03c278d | |||
| b59b321249 | |||
| 82d6991cf8 | |||
| d693d99379 | |||
| 50c1136be8 | |||
| dd491e516c | |||
| 078aab0d3d | |||
| 89c9f4b428 | |||
| 2148e896e2 | |||
| 7ba49b2e3e | |||
| 54a285f7e3 | |||
| 3ed20d059c | |||
| 92b6800f28 | |||
| 29cfd1a2dc | |||
| 45c09ea0ed | |||
| 424e24d530 | |||
| 26b2e3561a | |||
| 60aea6798d | |||
| fac3fe3f55 | |||
| 17fac0a488 | |||
| 6abc2207b2 | |||
| 885d224c5d | |||
| 12441deab8 | |||
| 8852fd14ce | |||
| e3814e6d9c | |||
| e19eba0942 | |||
| b6e2763731 | |||
| f75a3ee865 | |||
| d707c2f208 | |||
| a43b86d9eb | |||
| 5279d5e913 | |||
| df7fb92e03 | |||
| 95c3418313 | |||
| 67a8be9347 | |||
| 5d7f262f4a | |||
| 23eebc8127 | |||
| 3b435e460e | |||
| b52e9826e7 | |||
| ba49dd0499 | |||
| f9f6f28950 | |||
| f017015d1e | |||
| 29b4ee33bb | |||
| 03dc11c2f6 | |||
| b79ba5098b | |||
| 3c418d82e6 | |||
| 73ede2e344 | |||
| 266d891488 | |||
| c71490b472 | |||
| 07705079e2 | |||
| d88c268426 | |||
| a1b142e885 | |||
| a50d739e50 | |||
| f82bb56d89 | |||
| de593c3b93 | |||
| eedd5d80a0 | |||
| 5bbdd4f41e | |||
| 3fd24d96d3 | |||
| 7bea51fe09 | |||
| cdf0a38145 | |||
| bb2474ccbe | |||
| 01143a6f84 | |||
| b93485c2ed | |||
| 04681690b6 | |||
| 200877d691 | |||
| feb6417f45 | |||
| e7585a4ba2 | |||
| c33ab0f670 | |||
| 78181f66f6 | |||
| 925636be61 | |||
| 6be131d602 | |||
| f950f57eed | |||
| c6369feaea | |||
| 1bdd3f4908 | |||
| 924e095dfc | |||
| 72041a4a21 | |||
| d576446639 | |||
| 3fbec70ed3 | |||
| d177ab5ec2 | |||
| ba08f2c11e | |||
| 13a8aee049 | |||
| e4f5a284a6 | |||
| 6671b266da | |||
| 3728bd8e0f | |||
| c3616edbc8 | |||
| 21143b2693 | |||
| 006f9232cc | |||
| d1b61a34de | |||
| 896317ac97 | |||
| 4fd5c0edd4 | |||
| e2c5d6d857 | |||
| 8057fe4bc2 | |||
| ebc9690301 | |||
| 049ce1845e | |||
| faa6cd7deb | |||
| 1f9362c4e7 | |||
| 36fd18bcc8 | |||
| aa352317cb | |||
| edcd2cb50e | |||
| be8a64e4b8 | |||
| 2336bc0f65 | |||
| da82eecbae | |||
| 4855987ba8 | |||
| a493db1873 | |||
| 4f677a6cc2 | |||
| 228f2deb64 | |||
| 0fadf9a02c | |||
| 2b138ad76b | |||
| e3e4815b55 | |||
| f76f353c32 | |||
| 16b9f54dc3 | |||
| 281166475e | |||
| 23805cb5d6 | |||
| 76d0f9cfc1 | |||
| 5b083e4eb1 | |||
| f48694b18a | |||
| 8fa59ddbf0 | |||
| 2619db4026 | |||
| f3a71fb100 | |||
| 04fafe742c | |||
| cad8a41e6d | |||
| 1567923cda | |||
| c7116afcae | |||
| 4e184b2f90 | |||
| 9b5f664f00 | |||
| 0e89525ea8 | |||
| 0b6cdf8f02 | |||
| 5f63d959e1 | |||
| be7fe41179 | |||
| 204f343831 | |||
| aa230a1716 | |||
| 97f98569bc | |||
| 1318bf9830 | |||
| 3b58f946f1 | |||
| dfc2470764 | |||
| f650978dd0 | |||
| c6ba469e68 | |||
| 3a9997f9af | |||
| fb6cb891be | |||
| 76beda74c9 | |||
| 0398d4223b | |||
| 360eeb4c7d | |||
| 79b4224a35 | |||
| 3c79c893c9 | |||
| a11f507191 | |||
| 6413f7040f | |||
| 278af4b835 | |||
| fe885fbfb6 | |||
| 61b2c426e4 | |||
| 4272d49fb7 | |||
| 014ab3b957 | |||
| 09a0a64f20 | |||
| bf79f7c63f | |||
| 36b879f97d | |||
| bcc206d183 | |||
| 1e7b6a0396 | |||
| 57a1ccd213 | |||
| 708b4b1d49 | |||
| 3695736b3a | |||
| 8cc9580253 | |||
| e1fbf56f3d | |||
| 50cd1056eb | |||
| cfe6a6728f | |||
| 38344c99f1 | |||
| 98ed779424 | |||
| 943707cbcd | |||
| 940e246f27 | |||
| 439ebcdcaf | |||
| d2db71bb78 | |||
| d85432933c | |||
| 3cfadee2bb | |||
| 051ca73cae | |||
| 50a273f1f1 | |||
| ffe5dde7f0 | |||
| 7e1747a364 | |||
| 356b9c01c7 | |||
| 4009177e56 | |||
| 46d1d46384 | |||
| 128ff1006f | |||
| 64bb853720 | |||
| 330dafa294 | |||
| a51c354268 | |||
| cc1abe7f06 | |||
| 0c16942d37 | |||
| fa413aafbb | |||
| 75f17bb118 | |||
| 19ce733207 | |||
| 7529d44f81 | |||
| e4e4eb0a57 | |||
| c8eb1397b4 | |||
| c4c768dcaa | |||
| 8c60a9fff5 | |||
| 206e613711 | |||
| 5fb81e9eb4 | |||
| d130a1aea0 | |||
| 03201cb8b7 | |||
| 46e900e8d2 | |||
| f0f79ab244 | |||
| 1e61144cd2 | |||
| 2751bc9674 | |||
| e2deaae6c9 | |||
| 1aec8bac55 | |||
| 32ef377284 | |||
| ab017e17e2 | |||
| 832882160f | |||
| e03d015120 | |||
| 819825bdd2 | |||
| d29c789788 | |||
| b4c45af785 | |||
| b4bf799d75 | |||
| 9f62f2d483 | |||
| 679082e805 | |||
| 42f0bd052e | |||
| 01b9bab55f | |||
| 3eb0a95955 | |||
| d11157f7b2 | |||
| 1f2ec74d6c | |||
| 1241e929b1 | |||
| db7c4b174f | |||
| 460ce45349 | |||
| 8e203e914e | |||
| 64a8ed04ff | |||
| 234b6847ad | |||
| 933820c8d3 | |||
| 4e9587da59 | |||
| 60a5db14ba | |||
| 7ea3e02afa | |||
| 421c52fb74 | |||
| a3644f7efa | |||
| c7326f430b | |||
| 9ea51caf3f | |||
| cb4c9563bd | |||
| fac44d9afe | |||
| 6e27c6d85d | |||
| 9a0cfaf339 | |||
| 321b3e8df3 | |||
| bff97418ed | |||
| a47ad6a1d3 | |||
| 74c14808cf | |||
| eff2d7f613 | |||
| e135979f34 | |||
| 15edde8e87 | |||
| 211f5c2433 | |||
| 5d58d58f71 | |||
| d5a4008d3e | |||
| 1bf802268f | |||
| 807a4b004f | |||
| 53a8c7b05e | |||
| ef5715ce10 | |||
| 0c04882389 | |||
| 323c5f211f | |||
| f7a3fa15f4 | |||
| 6dd3ca20e4 | |||
| d9ea378819 | |||
| 6aa0356570 | |||
| 45b0d5d899 | |||
| 5be1edd7b7 | |||
| 75f7ee0acf | |||
| d0a74931dd | |||
| 0738e113ad | |||
| 283d8f3e03 | |||
| 1c3e820283 | |||
| accd4eb7b4 | |||
| ae7991c7e9 | |||
| e9e72d607e | |||
| 40766f9275 | |||
| a3fd60ce4d | |||
| f9a3e39902 | |||
| 06d707fb41 | |||
| a70c64d704 | |||
| 3a78aaef8f | |||
| c79c0feec8 | |||
| 39a5a94f3e | |||
| 961bfcaff7 | |||
| 52ba5dbd00 | |||
| 518b056fb9 | |||
| f9a936b4d9 | |||
| 4448761852 | |||
| 1192d13e6b | |||
| 57b982de48 | |||
| a3366379e0 | |||
| cd8d8fce61 | |||
| 4254d71039 | |||
| bd48232c61 | |||
| 70b3c7ea1d | |||
| c186035a13 | |||
| 2efed3ef80 | |||
| 58f2581307 | |||
| 6c4fd61db5 | |||
| afbbc35984 | |||
| a13c19cc29 | |||
| 7d7c5f1ab1 | |||
| 865f33bf80 | |||
| e33419545c | |||
| b93cd724c1 | |||
| 40a5d067ec | |||
| 3640ab0945 | |||
| cb2336d220 | |||
| 71fcb281b0 | |||
| d24830121e | |||
| 5a00a732e3 | |||
| 38584bec93 | |||
| ffa3f1d683 | |||
| dff9bbc134 | |||
| f5f717f1ad | |||
| 9bdd254007 | |||
| 6e18912ea6 | |||
| d35320c697 | |||
| fe11140fc2 | |||
| 0d198f589d | |||
| 8a42d40506 | |||
| 83797431fb | |||
| c05df43217 | |||
| f6859eeb3b | |||
| a0b8082631 | |||
| 926d8ecb8d | |||
| 24f85354e2 | |||
| 163de28338 | |||
| 3d2c83a501 | |||
| b0c964d4d9 | |||
| 20d2d9f3b7 | |||
| 6b7aa10e91 | |||
| 067aa58817 | |||
| 337e751200 | |||
| b20f208b55 | |||
| 9899b3c3a4 | |||
| 6437db7aa0 | |||
| 7d38d4bd4f | |||
| e8d426f574 | |||
| bb711108a2 | |||
| a482341b99 | |||
| f5ce472006 | |||
| a53ff3e459 | |||
| e15d77f3f6 | |||
| 504128730c | |||
| 098acaecc0 | |||
| b2dd573a67 | |||
| 84c9078bb7 | |||
| aa1917c28d | |||
| c81dce0f6d | |||
| 249869c2db | |||
| d59d8d6245 | |||
| b0680b7e2c | |||
| c7fd21999b | |||
| 28d06663d3 | |||
| 73f56e58a5 | |||
| 76796ffc67 | |||
| 01daf0316c | |||
| 1f2976c0e0 | |||
| 2e7ed741b6 | |||
| a0a4730ef8 | |||
| 5ed927d226 | |||
| 78cdd4da36 | |||
| 332139e614 | |||
| b093e8ee29 | |||
| 937374431a | |||
| ddc08cf0f9 | |||
| 88d2391d2e | |||
| bd02196950 | |||
| 4de6019206 | |||
| 85630b0aa2 | |||
| 0fda39467c | |||
| 415f571910 | |||
| 8284219035 | |||
| d0d34c7872 | |||
| 2869f11ae5 | |||
| 340c7ca04e | |||
| 81a646d64e | |||
| c23f2e4bd6 | |||
| 4496e9d9ee | |||
| ce14ffd49a | |||
| c18c481628 | |||
| 0695ea5d33 | |||
| 2e321f5465 | |||
| cbeec31bfc | |||
| 69f6a22121 | |||
| 9a179d2891 | |||
| 6c4be0190e | |||
| 5c6bb43d8d | |||
| e536c28e34 | |||
| 67bb23cda3 | |||
| 59398cfcf8 | |||
| 182377abe3 | |||
| 242e74964b | |||
| 9a2c7e45f7 | |||
| 99205c4aa8 | |||
| 1268384fb3 | |||
| c97fd17120 |
@ -14,5 +14,5 @@ trim_trailing_whitespace = true
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.yml]
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
34
.gitattributes
vendored
34
.gitattributes
vendored
@ -1,16 +1,18 @@
|
||||
/art export-ignore
|
||||
/docs export-ignore
|
||||
/tests export-ignore
|
||||
/scripts export-ignore
|
||||
/.github export-ignore
|
||||
/.php_cs export-ignore
|
||||
.editorconfig export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.travis.yml export-ignore
|
||||
phpstan.neon export-ignore
|
||||
rector.yaml export-ignore
|
||||
phpunit.xml export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
CONTRIBUTING.md export-ignore
|
||||
README.md export-ignore
|
||||
/docker export-ignore
|
||||
/docs export-ignore
|
||||
/tests export-ignore
|
||||
/scripts export-ignore
|
||||
/.github export-ignore
|
||||
.editorconfig export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
/phpstan.neon export-ignore
|
||||
/phpunit.xml export-ignore
|
||||
/CHANGELOG.md export-ignore
|
||||
/CONTRIBUTING.md export-ignore
|
||||
/docker-compose.yml export-ignore
|
||||
/Makefile export-ignore
|
||||
/rector.php export-ignore
|
||||
/README.md export-ignore
|
||||
/RELEASE.md export-ignore
|
||||
|
||||
|
||||
5
.github/FUNDING.yml
vendored
5
.github/FUNDING.yml
vendored
@ -1,5 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: nunomaduro
|
||||
patreon: nunomaduro
|
||||
custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L
|
||||
github: [nunomaduro]
|
||||
custom: https://www.paypal.com/paypalme/enunomaduro
|
||||
|
||||
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
| 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.
|
||||
-->
|
||||
|
||||
51
.github/workflows/ci.yml
vendored
51
.github/workflows/ci.yml
vendored
@ -1,51 +0,0 @@
|
||||
name: Continuous Integration
|
||||
|
||||
on: ['push', 'pull_request']
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [7.3, 7.4]
|
||||
dependency-version: [prefer-lowest, prefer-stable]
|
||||
|
||||
name: CI - PHP ${{ matrix.php }} (${{ matrix.dependency-version }})
|
||||
|
||||
steps:
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.composer/cache/files
|
||||
key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: mbstring, zip
|
||||
tools: prestissimo
|
||||
coverage: pcov
|
||||
|
||||
- name: Install Composer dependencies
|
||||
run: composer update --${{ matrix.dependency-version }} --no-interaction --prefer-dist
|
||||
|
||||
- name: Coding Style Checks
|
||||
run: |
|
||||
vendor/bin/rector process src --dry-run
|
||||
vendor/bin/php-cs-fixer fix -v --dry-run
|
||||
|
||||
- name: Type Checks
|
||||
run: vendor/bin/phpstan analyse --ansi
|
||||
|
||||
- name: Unit Tests
|
||||
run: bin/pest --colors=always --exclude-group=integration
|
||||
|
||||
- name: Integration Tests
|
||||
run: bin/pest --colors=always --group=integration
|
||||
41
.github/workflows/integration-tests.yml
vendored
Normal file
41
.github/workflows/integration-tests.yml
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
name: Integration Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
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
|
||||
|
||||
39
.github/workflows/static.yml
vendored
Normal file
39
.github/workflows/static.yml
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
name: Static Analysis
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
jobs:
|
||||
static:
|
||||
name: Static Tests
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
dependency-version: [prefer-lowest, prefer-stable]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.1
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
- name: Install Dependencies
|
||||
run: composer update --prefer-stable --no-interaction --no-progress --ansi
|
||||
|
||||
- name: Types
|
||||
run: composer test:types
|
||||
|
||||
- name: Refacto
|
||||
run: composer test:refacto
|
||||
|
||||
- name: Style
|
||||
run: composer test:lint
|
||||
45
.github/workflows/tests.yml
vendored
Normal file
45
.github/workflows/tests.yml
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
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: Unit Tests
|
||||
run: composer test:unit
|
||||
|
||||
- name: Unit Tests in Parallel
|
||||
run: composer test:parallel
|
||||
if: startsWith(matrix.os, 'windows') != true
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,10 +1,14 @@
|
||||
.idea/*
|
||||
.idea/codeStyleSettings.xml
|
||||
.temp/*
|
||||
composer.lock
|
||||
/vendor/
|
||||
coverage.xml
|
||||
.phpunit.result.cache
|
||||
.php_cs.cache
|
||||
.phpunit.cache
|
||||
/.php-cs-fixer.php
|
||||
.php-cs-fixer.cache
|
||||
.temp/coverage.php
|
||||
*.swp
|
||||
*.swo
|
||||
.vscode/
|
||||
|
||||
32
.php_cs
32
.php_cs
@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
$finder = PhpCsFixer\Finder::create()
|
||||
->in(__DIR__ . DIRECTORY_SEPARATOR . 'tests')
|
||||
->in(__DIR__ . DIRECTORY_SEPARATOR . 'bin')
|
||||
->in(__DIR__ . DIRECTORY_SEPARATOR . 'compiled')
|
||||
->in(__DIR__ . DIRECTORY_SEPARATOR . 'scripts')
|
||||
->in(__DIR__ . DIRECTORY_SEPARATOR . 'stubs')
|
||||
->in(__DIR__ . DIRECTORY_SEPARATOR . 'src')
|
||||
->append(['.php_cs']);
|
||||
|
||||
$rules = [
|
||||
'@Symfony' => true,
|
||||
'phpdoc_no_empty_return' => false,
|
||||
'array_syntax' => ['syntax' => 'short'],
|
||||
'yoda_style' => false,
|
||||
'binary_operator_spaces' => [
|
||||
'operators' => [
|
||||
'=>' => 'align',
|
||||
'=' => 'align',
|
||||
],
|
||||
],
|
||||
'concat_space' => ['spacing' => 'one'],
|
||||
'not_operator_with_space' => false,
|
||||
];
|
||||
|
||||
$rules['increment_style'] = ['style' => 'post'];
|
||||
|
||||
return PhpCsFixer\Config::create()
|
||||
->setUsingCache(true)
|
||||
->setRules($rules)
|
||||
->setFinder($finder);
|
||||
49
CHANGELOG.md
49
CHANGELOG.md
@ -1,18 +1,43 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
# Release Notes for 2.x
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
## Unreleased
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v0.1.1 (2020-05-14)](https://github.com/pestphp/pest/compare/v0.1.0...v0.1.1)
|
||||
### Added
|
||||
- `test` function without any arguments returns the current test case ([6fc55be](https://github.com/pestphp/pest/commit/6fc55becc8aecff685a958617015be1a4c118b01))
|
||||
## [v2.2.1 (2023-03-22)](https://github.com/pestphp/pest/compare/v2.2.0...v2.2.1)
|
||||
|
||||
### Fixed
|
||||
- "No coverage driver error" now returns proper error on Laravel ([28d8822](https://github.com/pestphp/pest/commit/28d8822de01f4fa92c62d8b8e019313f382b97e9))
|
||||
- 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)
|
||||
|
||||
## [v0.1.0 (2020-05-09)](https://github.com/pestphp/pest/commit/de2929077b344a099ef9c2ddc2f48abce14e248f)
|
||||
### Added
|
||||
- First version
|
||||
- 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.
|
||||
|
||||
@ -31,6 +31,10 @@ composer lint
|
||||
```
|
||||
## Tests
|
||||
|
||||
Update the snapshots:
|
||||
```bash
|
||||
composer update:snapshots
|
||||
```
|
||||
Run all tests:
|
||||
```bash
|
||||
composer test
|
||||
@ -50,3 +54,22 @@ Integration tests:
|
||||
```bash
|
||||
composer test:integration
|
||||
```
|
||||
|
||||
## Simplified setup using Docker
|
||||
|
||||
If you have Docker installed, you can quickly get all dependencies for Pest in place using
|
||||
our Docker files. Assuming you have the repository cloned, you may run the following
|
||||
commands:
|
||||
|
||||
1. `make build` to build the Docker image
|
||||
2. `make install` to install Composer dependencies
|
||||
3. `make test` to run the project tests and analysis tools
|
||||
|
||||
If you want to check things work against a specific version of PHP, you may include
|
||||
the `PHP` build argument when building the image:
|
||||
|
||||
```bash
|
||||
make build ARGS="--build-arg PHP=8.2"
|
||||
```
|
||||
|
||||
The default PHP version will always be the lowest version of PHP supported by Pest.
|
||||
|
||||
14
Makefile
Normal file
14
Makefile
Normal file
@ -0,0 +1,14 @@
|
||||
# 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
|
||||
41
README.md
41
README.md
@ -1,17 +1,40 @@
|
||||
<p align="center">
|
||||
<img src="https://next.pestphp.com/assets/img/og.png" width="600" alt="PEST Preview">
|
||||
<img src="https://raw.githubusercontent.com/pestphp/art/master/v2/banner.png" width="600" alt="PEST">
|
||||
<p align="center">
|
||||
<a href="https://github.com/pestphp/pest/actions"><img src="https://github.com/pest/pestphp/workflows/tests/badge.svg" alt="Build Status"></a>
|
||||
<a href="https://packagist.org/packages/pestphp/pest"><img src="https://poser.pugx.org/pestphp/pest/d/total.svg" alt="Total Downloads"></a>
|
||||
<a href="https://packagist.org/packages/pestphp/pest"><img src="https://poser.pugx.org/pestphp/pest/v/stable.svg" alt="Latest Version"></a>
|
||||
<a href="https://packagist.org/packages/pestphp/pest"><img src="https://poser.pugx.org/pestphp/pest/license.svg" alt="License"></a>
|
||||
<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://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="License" src="https://img.shields.io/packagist/l/pestphp/pest"></a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
------
|
||||
**Pest** it's an elegant PHP Testing Framework with a focus on simplicity. It was carefully crafted to bring the joy of testing to PHP.
|
||||
**Pest** is an elegant PHP testing Framework with a focus on simplicity, meticulously designed to bring back the joy of testing in PHP.
|
||||
|
||||
- Explore the docs: **[pestphp.com »](https://pestphp.com)**
|
||||
- Join the Discord Server: **[discord.gg/4UMHUb5 »](https://discord.gg/4UMHUb5)**
|
||||
- Explore our docs at **[pestphp.com »](https://pestphp.com)**
|
||||
- Follow us on Twitter at **[@pestphp »](https://twitter.com/pestphp)**
|
||||
- Join us at **[discord.gg/kaHY6p54JH »](https://discord.gg/kaHY6p54JH)** or **[t.me/+kYH5G4d5MV83ODk0 »](https://t.me/+kYH5G4d5MV83ODk0)**
|
||||
|
||||
Pest was created by **[Nuno Maduro](https://twitter.com/enunomaduro)** and is open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.
|
||||
## 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)**.
|
||||
|
||||
### Platinum Sponsors
|
||||
|
||||
- **[Forge](https://forge.laravel.com)**
|
||||
- **[LoadForge](https://loadforge.com)**
|
||||
- **[Spatie](https://spatie.be)**
|
||||
- **[Worksome](https://www.worksome.com/)**
|
||||
|
||||
### Premium Sponsors
|
||||
|
||||
- [Akaunting](https://akaunting.com)
|
||||
- [Codecourse](https://codecourse.com/)
|
||||
- [Laracasts](https://laracasts.com/)
|
||||
- [Localazy](https://localazy.com)
|
||||
- [Hyvor](https://hyvor.com/)
|
||||
- [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)**.
|
||||
|
||||
19
RELEASE.md
Normal file
19
RELEASE.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Release process
|
||||
|
||||
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.**
|
||||
|
||||
- Clear your local repository with: `git add . && git reset --hard && git checkout 2.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
|
||||
- Update the version number in [src/Pest.php](src/Pest.php)
|
||||
- Run the tests locally using: `composer test`
|
||||
- Commit the CHANGELOG and Pest file with the message: `git commit -m "release: vX.X.X"`
|
||||
- Push the changes to GitHub
|
||||
- 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`
|
||||
- Publish release here: [github.com/pestphp/pest/releases/new](https://github.com/pestphp/pest/releases/new).
|
||||
|
||||
### Plugins
|
||||
|
||||
Plugins should be versioned using the same major (or minor for `0.x` releases) version as Pest core.
|
||||
98
bin/pest
98
bin/pest
@ -1,31 +1,99 @@
|
||||
#!/usr/bin/env php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
use Pest\Actions\ValidatesEnvironment;
|
||||
use Pest\Console\Command;
|
||||
use Pest\Kernel;
|
||||
use Pest\Panic;
|
||||
use Pest\TestCaseFilters\GitDirtyTestCaseFilter;
|
||||
use Pest\TestCaseMethodFilters\TodoTestCaseFilter;
|
||||
use Pest\TestSuite;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
|
||||
(static function () {
|
||||
// Used when Pest is required using composer.
|
||||
$vendorPath = realpath(__DIR__ . '/../../../../vendor/autoload.php');
|
||||
// Ensures Collision's Printer is registered.
|
||||
$_SERVER['COLLISION_PRINTER'] = 'DefaultPrinter';
|
||||
|
||||
// Used when Pest maintainers are running Pest tests.
|
||||
$localPath = realpath(__DIR__ . '/../vendor/autoload.php');
|
||||
$args = $_SERVER['argv'];
|
||||
|
||||
if ($vendorPath) {
|
||||
include_once $vendorPath;
|
||||
} else {
|
||||
include_once $localPath;
|
||||
$dirty = false;
|
||||
$todo = false;
|
||||
|
||||
foreach ($args as $key => $value) {
|
||||
if ($value === '--compact') {
|
||||
$_SERVER['COLLISION_PRINTER_COMPACT'] = 'true';
|
||||
unset($args[$key]);
|
||||
}
|
||||
|
||||
if ($value === '--profile') {
|
||||
$_SERVER['COLLISION_PRINTER_PROFILE'] = 'true';
|
||||
unset($args[$key]);
|
||||
}
|
||||
|
||||
if (str_contains($value, '--test-directory')) {
|
||||
unset($args[$key]);
|
||||
}
|
||||
|
||||
if ($value === '--dirty') {
|
||||
$dirty = true;
|
||||
unset($args[$key]);
|
||||
}
|
||||
|
||||
if ($value === '--todos') {
|
||||
$todo = true;
|
||||
unset($args[$key]);
|
||||
}
|
||||
|
||||
if (str_contains($value, '--teamcity')) {
|
||||
unset($args[$key]);
|
||||
$args[] = '--no-output';
|
||||
unset($_SERVER['COLLISION_PRINTER']);
|
||||
}
|
||||
}
|
||||
|
||||
(new \NunoMaduro\Collision\Provider)->register();
|
||||
// Used when Pest is required using composer.
|
||||
$vendorPath = dirname(__DIR__, 4).'/vendor/autoload.php';
|
||||
|
||||
$rootPath = getcwd();
|
||||
// Used when Pest maintainers are running Pest tests.
|
||||
$localPath = dirname(__DIR__).'/vendor/autoload.php';
|
||||
|
||||
$testSuite = TestSuite::getInstance($rootPath);
|
||||
if (file_exists($vendorPath)) {
|
||||
include_once $vendorPath;
|
||||
$autoloadPath = $vendorPath;
|
||||
} else {
|
||||
include_once $localPath;
|
||||
$autoloadPath = $localPath;
|
||||
}
|
||||
|
||||
ValidatesEnvironment::in($testSuite);
|
||||
// Get $rootPath based on $autoloadPath
|
||||
$rootPath = dirname($autoloadPath, 2);
|
||||
$input = new ArgvInput();
|
||||
|
||||
exit((new Command($testSuite, new ConsoleOutput()))->run($_SERVER['argv']));
|
||||
$testSuite = TestSuite::getInstance(
|
||||
$rootPath,
|
||||
$input->getParameterOption('--test-directory', 'tests'),
|
||||
);
|
||||
|
||||
if ($dirty) {
|
||||
$testSuite->tests->addTestCaseFilter(new GitDirtyTestCaseFilter($rootPath));
|
||||
}
|
||||
|
||||
if ($todo) {
|
||||
$testSuite->tests->addTestCaseMethodFilter(new TodoTestCaseFilter());
|
||||
}
|
||||
|
||||
$isDecorated = $input->getParameterOption('--colors', 'always') !== 'never';
|
||||
|
||||
$output = new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, $isDecorated);
|
||||
|
||||
try {
|
||||
$kernel = Kernel::boot($testSuite, $input, $output);
|
||||
|
||||
$result = $kernel->handle($args);
|
||||
|
||||
$kernel->shutdown();
|
||||
} catch (Throwable|Error $e) {
|
||||
Panic::with($e);
|
||||
}
|
||||
|
||||
exit($result);
|
||||
})();
|
||||
|
||||
100
bin/worker.php
Normal file
100
bin/worker.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use ParaTest\WrapperRunner\ApplicationForWrapperWorker;
|
||||
use ParaTest\WrapperRunner\WrapperWorker;
|
||||
use Pest\Kernel;
|
||||
use Pest\Plugins\Actions\CallsHandleArguments;
|
||||
use Pest\TestSuite;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
$bootPest = (static function (): void {
|
||||
$workerArgv = new ArgvInput();
|
||||
|
||||
$rootPath = dirname(PHPUNIT_COMPOSER_INSTALL, 2);
|
||||
$testSuite = TestSuite::getInstance($rootPath, $workerArgv->getParameterOption(
|
||||
'--test-directory',
|
||||
'tests'
|
||||
));
|
||||
|
||||
$input = new ArgvInput();
|
||||
|
||||
$output = new ConsoleOutput(OutputInterface::VERBOSITY_NORMAL, true);
|
||||
|
||||
Kernel::boot($testSuite, $input, $output);
|
||||
});
|
||||
|
||||
(static function () use ($bootPest): void {
|
||||
$getopt = getopt('', [
|
||||
'status-file:',
|
||||
'progress-file:',
|
||||
'testresult-file:',
|
||||
'teamcity-file:',
|
||||
'testdox-file:',
|
||||
'testdox-color',
|
||||
'phpunit-argv:',
|
||||
]);
|
||||
|
||||
$composerAutoloadFiles = [
|
||||
dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'autoload.php',
|
||||
dirname(__DIR__, 2).DIRECTORY_SEPARATOR.'vendor'.DIRECTORY_SEPARATOR.'autoload.php',
|
||||
dirname(__DIR__).DIRECTORY_SEPARATOR.'vendor'.DIRECTORY_SEPARATOR.'autoload.php',
|
||||
];
|
||||
|
||||
foreach ($composerAutoloadFiles as $file) {
|
||||
if (file_exists($file)) {
|
||||
require_once $file;
|
||||
define('PHPUNIT_COMPOSER_INSTALL', $file);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert(isset($getopt['status-file']) && is_string($getopt['status-file']));
|
||||
$statusFile = fopen($getopt['status-file'], 'wb');
|
||||
assert(is_resource($statusFile));
|
||||
|
||||
assert(isset($getopt['progress-file']) && is_string($getopt['progress-file']));
|
||||
assert(isset($getopt['testresult-file']) && is_string($getopt['testresult-file']));
|
||||
assert(! isset($getopt['teamcity-file']) || is_string($getopt['teamcity-file']));
|
||||
assert(! isset($getopt['testdox-file']) || is_string($getopt['testdox-file']));
|
||||
|
||||
assert(isset($getopt['phpunit-argv']) && is_string($getopt['phpunit-argv']));
|
||||
$phpunitArgv = unserialize($getopt['phpunit-argv'], ['allowed_classes' => false]);
|
||||
assert(is_array($phpunitArgv));
|
||||
|
||||
$bootPest();
|
||||
|
||||
$phpunitArgv = CallsHandleArguments::execute($phpunitArgv);
|
||||
|
||||
$application = new ApplicationForWrapperWorker(
|
||||
$phpunitArgv,
|
||||
$getopt['progress-file'],
|
||||
$getopt['testresult-file'],
|
||||
$getopt['teamcity-file'] ?? null,
|
||||
$getopt['testdox-file'] ?? null,
|
||||
isset($getopt['testdox-color']),
|
||||
);
|
||||
|
||||
while (true) {
|
||||
if (feof(STDIN)) {
|
||||
$application->end();
|
||||
exit;
|
||||
}
|
||||
|
||||
$testPath = fgets(STDIN);
|
||||
if ($testPath === false || $testPath === WrapperWorker::COMMAND_EXIT) {
|
||||
$application->end();
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
$exitCode = $application->runTest(trim($testPath));
|
||||
|
||||
fwrite($statusFile, (string) $exitCode);
|
||||
fflush($statusFile);
|
||||
}
|
||||
})();
|
||||
1926
compiled/globals.php
1926
compiled/globals.php
File diff suppressed because it is too large
Load Diff
@ -17,65 +17,88 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.3",
|
||||
"nunomaduro/collision": "^5.0",
|
||||
"phpunit/phpunit": "^9.1.4",
|
||||
"sebastian/environment": "^5.1"
|
||||
"php": "^8.1.0",
|
||||
"brianium/paratest": "^7.1.2",
|
||||
"nunomaduro/collision": "^7.3.2",
|
||||
"nunomaduro/termwind": "^1.15.1",
|
||||
"pestphp/pest-plugin": "^2.0.0",
|
||||
"pestphp/pest-plugin-arch": "^2.0.1",
|
||||
"phpunit/phpunit": "^10.0.18"
|
||||
},
|
||||
"conflict": {
|
||||
"webmozart/assert": "<1.11.0",
|
||||
"phpunit/phpunit": ">10.0.18"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Pest\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/globals.php",
|
||||
"compiled/globals.php"
|
||||
"src/Functions.php",
|
||||
"src/Pest.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\Fixtures\\Covers\\": "tests/Fixtures/Covers",
|
||||
"Tests\\": "tests/PHPUnit/"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"tests/Autoload.php"
|
||||
]
|
||||
},
|
||||
"require-dev": {
|
||||
"ergebnis/phpstan-rules": "^0.14.4",
|
||||
"friendsofphp/php-cs-fixer": "^2.16.3",
|
||||
"illuminate/console": "^7.10.3",
|
||||
"illuminate/support": "^7.10.3",
|
||||
"mockery/mockery": "^1.3.1",
|
||||
"phpstan/phpstan": "^0.12.25",
|
||||
"phpstan/phpstan-strict-rules": "^0.12.2",
|
||||
"rector/rector": "^0.7.25",
|
||||
"symfony/var-dumper": "^5.0.8",
|
||||
"thecodingmachine/phpstan-strict-rules": "^0.12.0"
|
||||
"pestphp/pest-dev-tools": "^2.5.0",
|
||||
"symfony/process": "^6.2.7"
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"minimum-stability": "stable",
|
||||
"config": {
|
||||
"sort-packages": true,
|
||||
"preferred-install": "dist"
|
||||
"preferred-install": "dist",
|
||||
"allow-plugins": {
|
||||
"pestphp/pest-plugin": true
|
||||
}
|
||||
},
|
||||
"bin": [
|
||||
"bin/pest"
|
||||
],
|
||||
"scripts": {
|
||||
"compile": "@php ./scripts/compile.php",
|
||||
"lint": "rector process src && php-cs-fixer fix -v",
|
||||
"test:lint": "php-cs-fixer fix -v --dry-run && rector process src --dry-run",
|
||||
"test:types": "phpstan analyse --ansi",
|
||||
"test:unit": "bin/pest --colors=always --exclude-group=integration",
|
||||
"test:integration": "bin/pest --colors=always --group=integration",
|
||||
"test:integration:snapshots": "REBUILD_SNAPSHOTS=true bin/pest --colors=always",
|
||||
"refacto": "rector",
|
||||
"lint": "pint",
|
||||
"test:refacto": "rector --dry-run",
|
||||
"test:lint": "pint --test",
|
||||
"test:types": "phpstan analyse --ansi --memory-limit=-1 --debug",
|
||||
"test:unit": "php bin/pest --colors=always --exclude-group=integration --compact",
|
||||
"test:inline": "php bin/pest --colors=always --configuration=phpunit.inline.xml",
|
||||
"test:parallel": "php bin/pest --colors=always --exclude-group=integration --parallel --processes=10",
|
||||
"test:integration": "php bin/pest --colors=always --group=integration",
|
||||
"update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --colors=always",
|
||||
"test": [
|
||||
"@test:refacto",
|
||||
"@test:lint",
|
||||
"@test:types",
|
||||
"@test:unit",
|
||||
"@test:parallel",
|
||||
"@test:integration"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Pest\\Laravel\\PestServiceProvider"
|
||||
"pest": {
|
||||
"plugins": [
|
||||
"Pest\\Plugins\\Bail",
|
||||
"Pest\\Plugins\\Cache",
|
||||
"Pest\\Plugins\\Coverage",
|
||||
"Pest\\Plugins\\Init",
|
||||
"Pest\\Plugins\\Environment",
|
||||
"Pest\\Plugins\\Help",
|
||||
"Pest\\Plugins\\Memory",
|
||||
"Pest\\Plugins\\Only",
|
||||
"Pest\\Plugins\\Printer",
|
||||
"Pest\\Plugins\\ProcessIsolation",
|
||||
"Pest\\Plugins\\Profile",
|
||||
"Pest\\Plugins\\Retry",
|
||||
"Pest\\Plugins\\Version",
|
||||
"Pest\\Plugins\\Parallel"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
14
docker-compose.yml
Normal file
14
docker-compose.yml
Normal file
@ -0,0 +1,14 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
php:
|
||||
build:
|
||||
context: ./docker
|
||||
volumes:
|
||||
- .:/var/www/html
|
||||
composer:
|
||||
build:
|
||||
context: ./docker
|
||||
volumes:
|
||||
- .:/var/www/html
|
||||
entrypoint: ["composer"]
|
||||
23
docker/Dockerfile
Normal file
23
docker/Dockerfile
Normal file
@ -0,0 +1,23 @@
|
||||
ARG PHP=8.1
|
||||
FROM php:${PHP}-cli-alpine
|
||||
|
||||
RUN apk update \
|
||||
&& apk add zip libzip-dev icu-dev
|
||||
|
||||
RUN docker-php-ext-configure zip
|
||||
RUN docker-php-ext-install zip
|
||||
RUN docker-php-ext-enable zip
|
||||
|
||||
RUN docker-php-ext-configure intl
|
||||
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 docker-php-ext-enable xdebug
|
||||
|
||||
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
ENTRYPOINT ["php"]
|
||||
171
overrides/Runner/Filter/NameFilterIterator.php
Normal file
171
overrides/Runner/Filter/NameFilterIterator.php
Normal file
@ -0,0 +1,171 @@
|
||||
<?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.
|
||||
*/
|
||||
|
||||
namespace PHPUnit\Runner\Filter;
|
||||
|
||||
use function end;
|
||||
use Exception;
|
||||
use function implode;
|
||||
use Pest\Contracts\HasPrintableTestCaseName;
|
||||
use PHPUnit\Framework\SelfDescribing;
|
||||
use PHPUnit\Framework\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\TestSuite;
|
||||
use function preg_match;
|
||||
use RecursiveFilterIterator;
|
||||
use RecursiveIterator;
|
||||
use function sprintf;
|
||||
use function str_replace;
|
||||
|
||||
/**
|
||||
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
||||
*/
|
||||
final class NameFilterIterator extends RecursiveFilterIterator
|
||||
{
|
||||
private ?string $filter = null;
|
||||
|
||||
private ?int $filterMin = null;
|
||||
|
||||
private ?int $filterMax = null;
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct(RecursiveIterator $iterator, string $filter)
|
||||
{
|
||||
parent::__construct($iterator);
|
||||
|
||||
$this->setFilter($filter);
|
||||
}
|
||||
|
||||
public function accept(): bool
|
||||
{
|
||||
$test = $this->getInnerIterator()->current();
|
||||
|
||||
if ($test instanceof TestSuite) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$tmp = $this->describe($test);
|
||||
|
||||
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 data set #(\d+)$',
|
||||
$matches[1]
|
||||
);
|
||||
|
||||
$this->filterMin = (int) $matches[2];
|
||||
$this->filterMax = (int) $matches[3];
|
||||
} else {
|
||||
$filter = sprintf(
|
||||
'%s.*with data set #%s$',
|
||||
$matches[1],
|
||||
$matches[2]
|
||||
);
|
||||
}
|
||||
} // Handles:
|
||||
// * testDetermineJsonError@JSON_ERROR_NONE
|
||||
// * testDetermineJsonError@JSON.*
|
||||
elseif (preg_match('/^(.*?)@(.+)$/', $filter, $matches)) {
|
||||
$filter = sprintf(
|
||||
'%s.*with data set "%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) {
|
||||
return [
|
||||
$test::getPrintableTestCaseName(),
|
||||
$test->getPrintableTestCaseMethodName(),
|
||||
];
|
||||
}
|
||||
|
||||
if ($test instanceof TestCase) {
|
||||
return [$test::class, $test->nameWithDataSet()];
|
||||
}
|
||||
|
||||
if ($test instanceof SelfDescribing) {
|
||||
return ['', $test->toString()];
|
||||
}
|
||||
|
||||
return ['', $test::class];
|
||||
}
|
||||
}
|
||||
195
overrides/Runner/ResultCache/DefaultResultCache.php
Normal file
195
overrides/Runner/ResultCache/DefaultResultCache.php
Normal file
@ -0,0 +1,195 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
204
overrides/Runner/TestSuiteLoader.php
Normal file
204
overrides/Runner/TestSuiteLoader.php
Normal file
@ -0,0 +1,204 @@
|
||||
<?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);
|
||||
|
||||
namespace PHPUnit\Runner;
|
||||
|
||||
use function array_diff;
|
||||
use function array_values;
|
||||
use function basename;
|
||||
use function class_exists;
|
||||
use Exception;
|
||||
use function get_declared_classes;
|
||||
use Pest\Contracts\HasPrintableTestCaseName;
|
||||
use Pest\TestCases\IgnorableTestCase;
|
||||
use Pest\TestSuite;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use function substr;
|
||||
|
||||
/**
|
||||
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
||||
*/
|
||||
final class TestSuiteLoader
|
||||
{
|
||||
/**
|
||||
* @psalm-var list<class-string>
|
||||
*/
|
||||
private static array $loadedClasses = [];
|
||||
|
||||
/**
|
||||
* @psalm-var list<class-string>
|
||||
*/
|
||||
private static array $declaredClasses = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (empty(self::$declaredClasses)) {
|
||||
self::$declaredClasses = get_declared_classes();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function load(string $suiteClassFile): ReflectionClass
|
||||
{
|
||||
$suiteClassName = $this->classNameFromFileName($suiteClassFile);
|
||||
|
||||
(static function () use ($suiteClassFile) {
|
||||
include_once $suiteClassFile;
|
||||
|
||||
TestSuite::getInstance()->tests->makeIfNeeded($suiteClassFile);
|
||||
})();
|
||||
|
||||
$loadedClasses = array_values(
|
||||
array_diff(
|
||||
get_declared_classes(),
|
||||
array_merge(
|
||||
self::$declaredClasses,
|
||||
self::$loadedClasses
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
self::$loadedClasses = array_merge($loadedClasses, self::$loadedClasses);
|
||||
|
||||
if (empty($loadedClasses)) {
|
||||
return $this->exceptionFor($suiteClassName, $suiteClassFile);
|
||||
}
|
||||
|
||||
$testCaseFound = false;
|
||||
$class = false;
|
||||
|
||||
foreach (array_reverse($loadedClasses) as $loadedClass) {
|
||||
if (
|
||||
is_subclass_of($loadedClass, HasPrintableTestCaseName::class)
|
||||
|| is_subclass_of($loadedClass, TestCase::class)) {
|
||||
try {
|
||||
$class = new ReflectionClass($loadedClass);
|
||||
// @codeCoverageIgnoreStart
|
||||
} catch (ReflectionException) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($class->isAbstract() || ($class->getFileName() !== $suiteClassFile)) {
|
||||
if (! str_contains($class->getFileName(), 'TestCaseFactory.php')) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$suiteClassName = $loadedClass;
|
||||
$testCaseFound = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $testCaseFound) {
|
||||
foreach (array_reverse($loadedClasses) as $loadedClass) {
|
||||
$offset = 0 - strlen($suiteClassName);
|
||||
|
||||
if (stripos(substr($loadedClass, $offset - 1), '\\'.$suiteClassName) === 0 ||
|
||||
stripos(substr($loadedClass, $offset - 1), '_'.$suiteClassName) === 0) {
|
||||
try {
|
||||
$class = new ReflectionClass($loadedClass);
|
||||
// @codeCoverageIgnoreStart
|
||||
} catch (ReflectionException) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$suiteClassName = $loadedClass;
|
||||
$testCaseFound = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! $testCaseFound) {
|
||||
return $this->exceptionFor($suiteClassName, $suiteClassFile);
|
||||
}
|
||||
|
||||
if (! class_exists($suiteClassName, false)) {
|
||||
return $this->exceptionFor($suiteClassName, $suiteClassFile);
|
||||
}
|
||||
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
if ($class->isSubclassOf(TestCase::class) && ! $class->isAbstract()) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
if ($class->hasMethod('suite')) {
|
||||
try {
|
||||
$method = $class->getMethod('suite');
|
||||
// @codeCoverageIgnoreStart
|
||||
} catch (ReflectionException $e) {
|
||||
throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
if (! $method->isAbstract() && $method->isPublic() && $method->isStatic()) {
|
||||
return $class;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->exceptionFor($suiteClassName, $suiteClassFile);
|
||||
}
|
||||
|
||||
public function reload(ReflectionClass $aClass): ReflectionClass
|
||||
{
|
||||
return $aClass;
|
||||
}
|
||||
|
||||
private function classNameFromFileName(string $suiteClassFile): string
|
||||
{
|
||||
$className = basename($suiteClassFile, '.php');
|
||||
$dotPos = strpos($className, '.');
|
||||
|
||||
if ($dotPos !== false) {
|
||||
$className = substr($className, 0, $dotPos);
|
||||
}
|
||||
|
||||
return $className;
|
||||
}
|
||||
|
||||
private function exceptionFor(string $className, string $filename): ReflectionClass
|
||||
{
|
||||
return new ReflectionClass(IgnorableTestCase::class);
|
||||
}
|
||||
}
|
||||
104
overrides/TextUI/Command/WarmCodeCoverageCacheCommand.php
Normal file
104
overrides/TextUI/Command/WarmCodeCoverageCacheCommand.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?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\Command;
|
||||
|
||||
use PHPUnit\TextUI\Configuration\CodeCoverageFilterRegistry;
|
||||
use PHPUnit\TextUI\Configuration\Configuration;
|
||||
use PHPUnit\TextUI\Configuration\NoCoverageCacheDirectoryException;
|
||||
use SebastianBergmann\CodeCoverage\StaticAnalysis\CacheWarmer;
|
||||
use SebastianBergmann\Timer\NoActiveTimerException;
|
||||
use SebastianBergmann\Timer\Timer;
|
||||
|
||||
/**
|
||||
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
||||
*/
|
||||
final class WarmCodeCoverageCacheCommand implements Command
|
||||
{
|
||||
private readonly Configuration $configuration;
|
||||
|
||||
private readonly CodeCoverageFilterRegistry $codeCoverageFilterRegistry;
|
||||
|
||||
public function __construct(Configuration $configuration, CodeCoverageFilterRegistry $codeCoverageFilterRegistry)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
$this->codeCoverageFilterRegistry = $codeCoverageFilterRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NoActiveTimerException
|
||||
* @throws NoCoverageCacheDirectoryException
|
||||
*/
|
||||
public function execute(): Result
|
||||
{
|
||||
if (! $this->configuration->hasCoverageCacheDirectory()) {
|
||||
return Result::from(
|
||||
'Cache for static analysis has not been configured'.PHP_EOL,
|
||||
Result::FAILURE
|
||||
);
|
||||
}
|
||||
|
||||
$this->codeCoverageFilterRegistry->init($this->configuration);
|
||||
|
||||
if (! $this->codeCoverageFilterRegistry->configured()) {
|
||||
return Result::from(
|
||||
'Filter for code coverage has not been configured'.PHP_EOL,
|
||||
Result::FAILURE
|
||||
);
|
||||
}
|
||||
|
||||
$timer = new Timer;
|
||||
$timer->start();
|
||||
|
||||
(new CacheWarmer)->warmCache(
|
||||
$this->configuration->coverageCacheDirectory(),
|
||||
! $this->configuration->disableCodeCoverageIgnore(),
|
||||
$this->configuration->ignoreDeprecatedCodeUnitsFromCodeCoverage(),
|
||||
$this->codeCoverageFilterRegistry->get()
|
||||
);
|
||||
|
||||
return Result::from();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,80 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
128
overrides/TextUI/TestSuiteFilterProcessor.php
Normal file
128
overrides/TextUI/TestSuiteFilterProcessor.php
Normal file
@ -0,0 +1,128 @@
|
||||
<?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;
|
||||
|
||||
use function array_map;
|
||||
use Pest\Plugins\Only;
|
||||
use PHPUnit\Event;
|
||||
use PHPUnit\Framework\TestSuite;
|
||||
use PHPUnit\Runner\Filter\Factory;
|
||||
use PHPUnit\TextUI\Configuration\Configuration;
|
||||
use PHPUnit\TextUI\Configuration\FilterNotConfiguredException;
|
||||
|
||||
/**
|
||||
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
||||
*/
|
||||
final class TestSuiteFilterProcessor
|
||||
{
|
||||
private Factory $filterFactory;
|
||||
|
||||
public function __construct(Factory $factory = new Factory)
|
||||
{
|
||||
$this->filterFactory = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Event\RuntimeException
|
||||
* @throws FilterNotConfiguredException
|
||||
*/
|
||||
public function process(Configuration $configuration, TestSuite $suite): void
|
||||
{
|
||||
if (! $configuration->hasFilter() &&
|
||||
! $configuration->hasGroups() &&
|
||||
! $configuration->hasExcludeGroups() &&
|
||||
! $configuration->hasTestsCovering() &&
|
||||
! $configuration->hasTestsUsing() &&
|
||||
! Only::isEnabled()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($configuration->hasExcludeGroups()) {
|
||||
$this->filterFactory->addExcludeGroupFilter(
|
||||
$configuration->excludeGroups()
|
||||
);
|
||||
}
|
||||
|
||||
if (Only::isEnabled()) {
|
||||
$this->filterFactory->addIncludeGroupFilter(['__pest_only']);
|
||||
} elseif ($configuration->hasGroups()) {
|
||||
$this->filterFactory->addIncludeGroupFilter(
|
||||
$configuration->groups()
|
||||
);
|
||||
}
|
||||
|
||||
if ($configuration->hasTestsCovering()) {
|
||||
$this->filterFactory->addIncludeGroupFilter(
|
||||
array_map(
|
||||
static fn (string $name): string => '__phpunit_covers_'.$name,
|
||||
$configuration->testsCovering()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ($configuration->hasTestsUsing()) {
|
||||
$this->filterFactory->addIncludeGroupFilter(
|
||||
array_map(
|
||||
static fn (string $name): string => '__phpunit_uses_'.$name,
|
||||
$configuration->testsUsing()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ($configuration->hasFilter()) {
|
||||
$this->filterFactory->addNameFilter(
|
||||
$configuration->filter()
|
||||
);
|
||||
}
|
||||
|
||||
$suite->injectFilter($this->filterFactory);
|
||||
|
||||
Event\Facade::emitter()->testSuiteFiltered(
|
||||
Event\TestSuite\TestSuiteBuilder::from($suite)
|
||||
);
|
||||
}
|
||||
}
|
||||
11
phpstan.neon
11
phpstan.neon
@ -7,16 +7,17 @@ parameters:
|
||||
level: max
|
||||
paths:
|
||||
- src
|
||||
excludes_analyse:
|
||||
- src/globals.php
|
||||
|
||||
checkMissingIterableValueType: true
|
||||
checkGenericClassInNonGenericObjectType: false
|
||||
reportUnmatchedIgnoredErrors: true
|
||||
|
||||
ignoreErrors:
|
||||
- "#Language construct isset\\(\\) should not be used.#"
|
||||
- "#is not allowed to extend#"
|
||||
- "#Language construct eval#"
|
||||
- "#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#"
|
||||
- "#Using \\$this in static method#"
|
||||
- "#has parameter \\$closure with default value.#"
|
||||
- "#has parameter \\$description with default value.#"
|
||||
- "#Method Pest\\\\Support\\\\Reflection::getParameterClassName\\(\\) has a nullable return type declaration.#"
|
||||
|
||||
34
phpunit.xml
34
phpunit.xml
@ -1,16 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
beStrictAboutTestsThatDoNotTestAnything="true"
|
||||
beStrictAboutOutputDuringTests="true"
|
||||
bootstrap="vendor/autoload.php"
|
||||
colors="true"
|
||||
failOnRisky="true"
|
||||
failOnWarning="false"
|
||||
processIsolation="false"
|
||||
stopOnError="false"
|
||||
stopOnFailure="false"
|
||||
backupStaticProperties="false"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="default">
|
||||
<directory suffix=".php">./tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<whitelist processUncoveredFilesFromWhitelist="true">
|
||||
<directory suffix=".php">./src</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
<testsuites>
|
||||
<testsuite name="default">
|
||||
<directory suffix=".php">./tests</directory>
|
||||
<exclude>./tests/.snapshots</exclude>
|
||||
<exclude>./tests/.tests</exclude>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<coverage>
|
||||
<include>
|
||||
<directory suffix=".php">./src</directory>
|
||||
</include>
|
||||
</coverage>
|
||||
</phpunit>
|
||||
|
||||
31
rector.php
Normal file
31
rector.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector;
|
||||
use Rector\Config\RectorConfig;
|
||||
use Rector\Set\ValueObject\LevelSetList;
|
||||
use Rector\Set\ValueObject\SetList;
|
||||
|
||||
return static function (RectorConfig $rectorConfig): void {
|
||||
$rectorConfig->paths([
|
||||
__DIR__.'/src',
|
||||
]);
|
||||
|
||||
$rectorConfig->rules([
|
||||
InlineConstructorDefaultToPropertyRector::class,
|
||||
]);
|
||||
|
||||
$rectorConfig->skip([
|
||||
__DIR__.'/src/Plugins/Parallel/Paratest/WrapperRunner.php',
|
||||
]);
|
||||
|
||||
$rectorConfig->sets([
|
||||
LevelSetList::UP_TO_PHP_81,
|
||||
SetList::CODE_QUALITY,
|
||||
SetList::DEAD_CODE,
|
||||
SetList::EARLY_RETURN,
|
||||
SetList::TYPE_DECLARATION,
|
||||
SetList::PRIVATIZATION,
|
||||
]);
|
||||
};
|
||||
16
rector.yaml
16
rector.yaml
@ -1,16 +0,0 @@
|
||||
# rector.yaml
|
||||
parameters:
|
||||
sets:
|
||||
- 'action-injection-to-constructor-injection'
|
||||
- 'array-str-functions-to-static-call'
|
||||
- 'celebrity'
|
||||
- 'doctrine'
|
||||
- 'phpstan'
|
||||
- 'phpunit-code-quality'
|
||||
- 'solid'
|
||||
- 'early-return'
|
||||
- 'doctrine-code-quality'
|
||||
- 'code-quality'
|
||||
- 'php71'
|
||||
- 'php72'
|
||||
- 'php73'
|
||||
17
resources/views/components/badge.php
Normal file
17
resources/views/components/badge.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
/** @var string $type */
|
||||
/** @var string $content */
|
||||
[$bgBadgeColor, $bgBadgeText] = match ($type) {
|
||||
'INFO' => ['blue', 'INFO'],
|
||||
'ERROR' => ['red', 'ERROR'],
|
||||
};
|
||||
|
||||
?>
|
||||
|
||||
<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-1">
|
||||
<?php echo htmlspecialchars($content) ?>
|
||||
</span>
|
||||
</div>
|
||||
1
resources/views/components/new-line.php
Normal file
1
resources/views/components/new-line.php
Normal file
@ -0,0 +1 @@
|
||||
<div></div>
|
||||
12
resources/views/components/two-column-detail.php
Normal file
12
resources/views/components/two-column-detail.php
Normal file
@ -0,0 +1,12 @@
|
||||
<div class="flex mx-2 max-w-150">
|
||||
<span>
|
||||
<?php echo htmlspecialchars($left) ?>
|
||||
</span>
|
||||
<span class="flex-1 content-repeat-[.] text-gray ml-1"></span>
|
||||
<?php if ($right !== '') { ?>
|
||||
<span class="ml-1 text-gray">
|
||||
<?php echo htmlspecialchars($right) ?>
|
||||
</span>
|
||||
<?php } ?>
|
||||
</div>
|
||||
|
||||
4
resources/views/usage.php
Normal file
4
resources/views/usage.php
Normal file
@ -0,0 +1,4 @@
|
||||
<div class="mx-2">
|
||||
<span class="text-yellow font-bold">USAGE:</span><span class="ml-1">pest</span><span class="ml-1 text-gray"><?php echo htmlspecialchars('<file>') ?> [options]</span>
|
||||
</div>
|
||||
|
||||
3
resources/views/version.php
Normal file
3
resources/views/version.php
Normal file
@ -0,0 +1,3 @@
|
||||
<div class="my-1 mx-2">
|
||||
<span>Pest Testing Framework</span><span class="ml-1 text-blue font-bold"><?php echo htmlspecialchars($version) ?></span><span>.</span>
|
||||
</div>
|
||||
@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$globalsFilePath = implode(DIRECTORY_SEPARATOR, [
|
||||
dirname(__DIR__),
|
||||
'vendor',
|
||||
'phpunit',
|
||||
'phpunit',
|
||||
'src',
|
||||
'Framework',
|
||||
'Assert',
|
||||
'Functions.php',
|
||||
]);
|
||||
|
||||
$compiledFilePath = implode(DIRECTORY_SEPARATOR, [dirname(__DIR__), 'compiled', 'globals.php']);
|
||||
|
||||
@unlink($compiledFilePath);
|
||||
|
||||
$replace = function ($contents, $string, $by) {
|
||||
return str_replace($string, $by, $contents);
|
||||
};
|
||||
|
||||
$remove = function ($contents, $string) {
|
||||
return str_replace($string, '', $contents);
|
||||
};
|
||||
|
||||
$contents = file_get_contents($globalsFilePath);
|
||||
$contents = $replace($contents, 'namespace PHPUnit\Framework;', 'use PHPUnit\Framework\Assert;');
|
||||
$contents = $remove($contents, 'use ArrayAccess;');
|
||||
$contents = $remove($contents, 'use Countable;');
|
||||
$contents = $remove($contents, 'use DOMDocument;');
|
||||
$contents = $remove($contents, 'use DOMElement;');
|
||||
$contents = $remove($contents, 'use Throwable;');
|
||||
|
||||
file_put_contents(implode(DIRECTORY_SEPARATOR, [dirname(__DIR__), 'compiled', 'globals.php']), $contents);
|
||||
@ -1,79 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Actions;
|
||||
|
||||
use Pest\Console\Coverage;
|
||||
use Pest\Support\Str;
|
||||
use Pest\TestSuite;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Input\InputDefinition;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class AddsCoverage
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const COVERAGE_OPTION = 'coverage';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const MIN_OPTION = 'min';
|
||||
|
||||
/**
|
||||
* Holds the coverage related options.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private const OPTIONS = [self::COVERAGE_OPTION, self::MIN_OPTION];
|
||||
|
||||
/**
|
||||
* If any, adds the coverage params to the given original arguments.
|
||||
*
|
||||
* @param array<int, string> $originals
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public static function from(TestSuite $testSuite, array $originals): array
|
||||
{
|
||||
$arguments = array_merge([''], array_values(array_filter($originals, function ($original): bool {
|
||||
foreach (self::OPTIONS as $option) {
|
||||
if ($original === sprintf('--%s', $option) || Str::startsWith($original, sprintf('--%s=', $option))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
})));
|
||||
|
||||
$originals = array_flip($originals);
|
||||
foreach ($arguments as $argument) {
|
||||
unset($originals[$argument]);
|
||||
}
|
||||
$originals = array_flip($originals);
|
||||
|
||||
$inputs = [];
|
||||
$inputs[] = new InputOption(self::COVERAGE_OPTION, null, InputOption::VALUE_NONE);
|
||||
$inputs[] = new InputOption(self::MIN_OPTION, null, InputOption::VALUE_REQUIRED);
|
||||
|
||||
$input = new ArgvInput($arguments, new InputDefinition($inputs));
|
||||
if ((bool) $input->getOption(self::COVERAGE_OPTION)) {
|
||||
$testSuite->coverage = true;
|
||||
$originals[] = '--coverage-php';
|
||||
$originals[] = Coverage::getPath();
|
||||
}
|
||||
|
||||
if ($input->getOption(self::MIN_OPTION) !== null) {
|
||||
/* @phpstan-ignore-next-line */
|
||||
$testSuite->coverageMin = (float) $input->getOption(self::MIN_OPTION);
|
||||
}
|
||||
|
||||
return $originals;
|
||||
}
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Actions;
|
||||
|
||||
use NunoMaduro\Collision\Adapters\Phpunit\Printer;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class AddsDefaults
|
||||
{
|
||||
/**
|
||||
* Adds default arguments to the given `arguments` array.
|
||||
*
|
||||
* @param array<string, mixed> $arguments
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function to(array $arguments): array
|
||||
{
|
||||
if (!array_key_exists('printer', $arguments)) {
|
||||
$arguments['printer'] = new Printer();
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Actions;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\TestSuite;
|
||||
use PHPUnit\Framework\WarningTestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class AddsTests
|
||||
{
|
||||
/**
|
||||
* Adds tests to the given test suite.
|
||||
*
|
||||
* @param TestSuite<\PHPUnit\Framework\TestCase> $testSuite
|
||||
*/
|
||||
public static function to(TestSuite $testSuite, \Pest\TestSuite $pestTestSuite): void
|
||||
{
|
||||
self::removeTestClosureWarnings($testSuite);
|
||||
|
||||
// @todo refactor this...
|
||||
|
||||
$testSuites = [];
|
||||
$pestTestSuite->tests->build($pestTestSuite, function (TestCase $testCase) use (&$testSuites): void {
|
||||
$testCaseClass = get_class($testCase);
|
||||
if (!array_key_exists($testCaseClass, $testSuites)) {
|
||||
$testSuites[$testCaseClass] = [];
|
||||
}
|
||||
|
||||
$testSuites[$testCaseClass][] = $testCase;
|
||||
});
|
||||
|
||||
foreach ($testSuites as $testCaseName => $testCases) {
|
||||
$testTestSuite = new TestSuite($testCaseName);
|
||||
$testTestSuite->setTests([]);
|
||||
foreach ($testCases as $testCase) {
|
||||
$testTestSuite->addTest($testCase, $testCase->getGroups());
|
||||
}
|
||||
$testSuite->addTestSuite($testTestSuite);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TestSuite<\PHPUnit\Framework\TestCase> $testSuite
|
||||
*/
|
||||
private static function removeTestClosureWarnings(TestSuite $testSuite): void
|
||||
{
|
||||
$tests = $testSuite->tests();
|
||||
|
||||
foreach ($tests as $key => $test) {
|
||||
if ($test instanceof TestSuite) {
|
||||
self::removeTestClosureWarnings($test);
|
||||
}
|
||||
|
||||
if ($test instanceof WarningTestCase) {
|
||||
unset($tests[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$testSuite->setTests($tests);
|
||||
}
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Actions;
|
||||
|
||||
use Pest\Support\Str;
|
||||
use PHPUnit\TextUI\Configuration\Configuration;
|
||||
use PHPUnit\Util\FileLoader;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class LoadStructure
|
||||
{
|
||||
/**
|
||||
* The Pest convention.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private const STRUCTURE = [
|
||||
'Datasets.php',
|
||||
'Pest.php',
|
||||
'Datasets',
|
||||
];
|
||||
|
||||
/**
|
||||
* Validates the configuration in the given `configuration`.
|
||||
*/
|
||||
public static function in(string $rootPath): void
|
||||
{
|
||||
$testsPath = $rootPath . DIRECTORY_SEPARATOR . 'tests';
|
||||
|
||||
$load = function ($filename): bool {
|
||||
return file_exists($filename) && (bool) FileLoader::checkAndLoad($filename);
|
||||
};
|
||||
|
||||
foreach (self::STRUCTURE as $filename) {
|
||||
$filename = sprintf('%s%s%s', $testsPath, DIRECTORY_SEPARATOR, $filename);
|
||||
|
||||
if (!file_exists($filename)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_dir($filename)) {
|
||||
$directory = new RecursiveDirectoryIterator($filename);
|
||||
$iterator = new RecursiveIteratorIterator($directory);
|
||||
foreach ($iterator as $file) {
|
||||
$filename = $file->__toString();
|
||||
if (Str::endsWith($filename, '.php') && file_exists($filename)) {
|
||||
require_once $filename;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$load($filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Actions;
|
||||
|
||||
use Pest\Exceptions\AttributeNotSupportedYet;
|
||||
use Pest\Exceptions\FileOrFolderNotFound;
|
||||
use PHPUnit\TextUI\Configuration\Configuration;
|
||||
use PHPUnit\TextUI\Configuration\Registry;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ValidatesConfiguration
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const CONFIGURATION_KEY = 'configuration';
|
||||
|
||||
/**
|
||||
* Validates the configuration in the given `configuration`.
|
||||
*
|
||||
* @param array<string, mixed> $arguments
|
||||
*/
|
||||
public static function in($arguments): void
|
||||
{
|
||||
if (!array_key_exists(self::CONFIGURATION_KEY, $arguments) || !file_exists($arguments[self::CONFIGURATION_KEY])) {
|
||||
throw new FileOrFolderNotFound('phpunit.xml');
|
||||
}
|
||||
|
||||
$configuration = Registry::getInstance()
|
||||
->get($arguments[self::CONFIGURATION_KEY])
|
||||
->phpunit();
|
||||
|
||||
if ($configuration->processIsolation()) {
|
||||
throw new AttributeNotSupportedYet('processIsolation', 'true');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Actions;
|
||||
|
||||
use Pest\Exceptions\FileOrFolderNotFound;
|
||||
use Pest\TestSuite;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ValidatesEnvironment
|
||||
{
|
||||
/**
|
||||
* The need files on the root path.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private const NEEDED_FILES = [
|
||||
'composer.json',
|
||||
'tests',
|
||||
];
|
||||
|
||||
/**
|
||||
* Validates the environment.
|
||||
*/
|
||||
public static function in(TestSuite $testSuite): void
|
||||
{
|
||||
$rootPath = $testSuite->rootPath;
|
||||
|
||||
$exists = function ($neededFile) use ($rootPath): bool {
|
||||
return file_exists(sprintf('%s%s%s', $rootPath, DIRECTORY_SEPARATOR, $neededFile));
|
||||
};
|
||||
|
||||
foreach (self::NEEDED_FILES as $neededFile) {
|
||||
if (!$exists($neededFile)) {
|
||||
throw new FileOrFolderNotFound($neededFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
88
src/Bootstrappers/BootFiles.php
Normal file
88
src/Bootstrappers/BootFiles.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Bootstrappers;
|
||||
|
||||
use Pest\Contracts\Bootstrapper;
|
||||
use Pest\Support\DatasetInfo;
|
||||
use Pest\Support\Str;
|
||||
use function Pest\testDirectory;
|
||||
use Pest\TestSuite;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use SebastianBergmann\FileIterator\Facade as PhpUnitFileIterator;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BootFiles implements Bootstrapper
|
||||
{
|
||||
/**
|
||||
* The structure of the tests directory.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private const STRUCTURE = [
|
||||
'Expectations',
|
||||
'Expectations.php',
|
||||
'Helpers',
|
||||
'Helpers.php',
|
||||
'Pest.php',
|
||||
];
|
||||
|
||||
/**
|
||||
* Boots the structure of the tests directory.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
$rootPath = TestSuite::getInstance()->rootPath;
|
||||
$testsPath = $rootPath.DIRECTORY_SEPARATOR.testDirectory();
|
||||
|
||||
foreach (self::STRUCTURE as $filename) {
|
||||
$filename = sprintf('%s%s%s', $testsPath, DIRECTORY_SEPARATOR, $filename);
|
||||
|
||||
if (! file_exists($filename)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_dir($filename)) {
|
||||
$directory = new RecursiveDirectoryIterator($filename);
|
||||
$iterator = new RecursiveIteratorIterator($directory);
|
||||
/** @var \DirectoryIterator $file */
|
||||
foreach ($iterator as $file) {
|
||||
$this->load($file->__toString());
|
||||
}
|
||||
} else {
|
||||
$this->load($filename);
|
||||
}
|
||||
}
|
||||
|
||||
$this->bootDatasets($testsPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads, if possible, the given file.
|
||||
*/
|
||||
private function load(string $filename): void
|
||||
{
|
||||
if (! Str::endsWith($filename, '.php')) {
|
||||
return;
|
||||
}
|
||||
if (! file_exists($filename)) {
|
||||
return;
|
||||
}
|
||||
include_once $filename;
|
||||
}
|
||||
|
||||
private function bootDatasets(string $testsPath): void
|
||||
{
|
||||
$files = (new PhpUnitFileIterator)->getFilesAsArray($testsPath, '.php');
|
||||
|
||||
foreach ($files as $file) {
|
||||
if (DatasetInfo::isADatasetsFile($file) || DatasetInfo::isInsideADatasetsDirectory($file)) {
|
||||
$this->load($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/Bootstrappers/BootKernelDump.php
Normal file
37
src/Bootstrappers/BootKernelDump.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Bootstrappers;
|
||||
|
||||
use Pest\Contracts\Bootstrapper;
|
||||
use Pest\KernelDump;
|
||||
use Pest\Support\Container;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BootKernelDump implements Bootstrapper
|
||||
{
|
||||
/**
|
||||
* Creates a new Boot Kernel Dump instance.
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly OutputInterface $output,
|
||||
) {
|
||||
// ...
|
||||
}
|
||||
|
||||
/**
|
||||
* Boots the kernel dump.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
Container::getInstance()->add(KernelDump::class, $kernelDump = new KernelDump(
|
||||
$this->output,
|
||||
));
|
||||
|
||||
$kernelDump->enable();
|
||||
}
|
||||
}
|
||||
44
src/Bootstrappers/BootOverrides.php
Normal file
44
src/Bootstrappers/BootOverrides.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Bootstrappers;
|
||||
|
||||
use Pest\Contracts\Bootstrapper;
|
||||
use Pest\Exceptions\ShouldNotHappen;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BootOverrides implements Bootstrapper
|
||||
{
|
||||
/**
|
||||
* The list of files to be overridden.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private const FILES = [
|
||||
'Runner/Filter/NameFilterIterator.php',
|
||||
'Runner/ResultCache/DefaultResultCache.php',
|
||||
'Runner/TestSuiteLoader.php',
|
||||
'TextUI/Command/WarmCodeCoverageCacheCommand.php',
|
||||
'TextUI/Output/Default/ProgressPrinter/TestSkippedSubscriber.php',
|
||||
'TextUI/TestSuiteFilterProcessor.php',
|
||||
];
|
||||
|
||||
/**
|
||||
* Boots the list of files to be overridden.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
foreach (self::FILES as $file) {
|
||||
$file = __DIR__."/../../overrides/$file";
|
||||
|
||||
if (! file_exists($file)) {
|
||||
throw ShouldNotHappen::fromMessage(sprintf('File [%s] does not exist.', $file));
|
||||
}
|
||||
|
||||
require_once $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
51
src/Bootstrappers/BootSubscribers.php
Normal file
51
src/Bootstrappers/BootSubscribers.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Bootstrappers;
|
||||
|
||||
use Pest\Contracts\Bootstrapper;
|
||||
use Pest\Subscribers;
|
||||
use Pest\Support\Container;
|
||||
use PHPUnit\Event;
|
||||
use PHPUnit\Event\Subscriber;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BootSubscribers implements Bootstrapper
|
||||
{
|
||||
/**
|
||||
* The list of Subscribers.
|
||||
*
|
||||
* @var array<int, class-string<Subscriber>>
|
||||
*/
|
||||
private const SUBSCRIBERS = [
|
||||
Subscribers\EnsureConfigurationIsAvailable::class,
|
||||
Subscribers\EnsureIgnorableTestCasesAreIgnored::class,
|
||||
Subscribers\EnsureKernelDumpIsFlushed::class,
|
||||
Subscribers\EnsureTeamCityEnabled::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Creates a new instance of the Boot Subscribers.
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly Container $container,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Boots the list of Subscribers.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
foreach (self::SUBSCRIBERS as $subscriber) {
|
||||
$instance = $this->container->get($subscriber);
|
||||
|
||||
assert($instance instanceof Subscriber);
|
||||
|
||||
Event\Facade::instance()->registerSubscriber($instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/Bootstrappers/BootView.php
Normal file
32
src/Bootstrappers/BootView.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Bootstrappers;
|
||||
|
||||
use Pest\Contracts\Bootstrapper;
|
||||
use Pest\Support\View;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BootView implements Bootstrapper
|
||||
{
|
||||
/**
|
||||
* Creates a new instance of the Boot View.
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly OutputInterface $output
|
||||
) {
|
||||
// ..
|
||||
}
|
||||
|
||||
/**
|
||||
* Boots the view renderer.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
View::renderUsing($this->output);
|
||||
}
|
||||
}
|
||||
26
src/Concerns/Expectable.php
Normal file
26
src/Concerns/Expectable.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Concerns;
|
||||
|
||||
use Pest\Expectation;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
trait Expectable
|
||||
{
|
||||
/**
|
||||
* @template TValue
|
||||
*
|
||||
* Creates a new Expectation.
|
||||
*
|
||||
* @param TValue $value
|
||||
* @return Expectation<TValue>
|
||||
*/
|
||||
public function expect(mixed $value): Expectation
|
||||
{
|
||||
return new Expectation($value);
|
||||
}
|
||||
}
|
||||
36
src/Concerns/Extendable.php
Normal file
36
src/Concerns/Extendable.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Concerns;
|
||||
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
trait Extendable
|
||||
{
|
||||
/**
|
||||
* The list of extends.
|
||||
*
|
||||
* @var array<string, Closure>
|
||||
*/
|
||||
private static array $extends = [];
|
||||
|
||||
/**
|
||||
* Register a new extend.
|
||||
*/
|
||||
public function extend(string $name, Closure $extend): void
|
||||
{
|
||||
static::$extends[$name] = $extend;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if given extend name is registered.
|
||||
*/
|
||||
public static function hasExtend(string $name): bool
|
||||
{
|
||||
return array_key_exists($name, static::$extends);
|
||||
}
|
||||
}
|
||||
45
src/Concerns/Logging/WritesToConsole.php
Normal file
45
src/Concerns/Logging/WritesToConsole.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Concerns\Logging;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
trait WritesToConsole
|
||||
{
|
||||
/**
|
||||
* Writes the given success message to the console.
|
||||
*/
|
||||
private function writeSuccess(string $message): void
|
||||
{
|
||||
$this->writePestTestOutput($message, 'fg-green, bold', '✓');
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given error message to the console.
|
||||
*/
|
||||
private function writeError(string $message): void
|
||||
{
|
||||
$this->writePestTestOutput($message, 'fg-red, bold', '⨯');
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given warning message to the console.
|
||||
*/
|
||||
private function writeWarning(string $message): void
|
||||
{
|
||||
$this->writePestTestOutput($message, 'fg-yellow, bold', '-');
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the give message to the console.
|
||||
*/
|
||||
private function writePestTestOutput(string $message, string $color, string $symbol): void
|
||||
{
|
||||
$this->writeWithColor($color, "$symbol ", false);
|
||||
$this->write($message);
|
||||
$this->writeNewLine();
|
||||
}
|
||||
}
|
||||
72
src/Concerns/Pipeable.php
Normal file
72
src/Concerns/Pipeable.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Pest\Expectation;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
trait Pipeable
|
||||
{
|
||||
/**
|
||||
* The list of pipes.
|
||||
*
|
||||
* @var array<string, array<Closure(Closure, mixed ...$arguments): void>>
|
||||
*/
|
||||
private static array $pipes = [];
|
||||
|
||||
/**
|
||||
* The list of interceptors.
|
||||
*
|
||||
* @var array<string, array<Closure(Closure, mixed ...$arguments): void>>
|
||||
*/
|
||||
private static array $interceptors = [];
|
||||
|
||||
/**
|
||||
* Register a pipe to be applied before an expectation is checked.
|
||||
*/
|
||||
public function pipe(string $name, Closure $pipe): void
|
||||
{
|
||||
self::$pipes[$name][] = $pipe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an interceptor that should replace an existing expectation.
|
||||
*
|
||||
* @param string|Closure(mixed $value, mixed ...$arguments):bool $filter
|
||||
*/
|
||||
public function intercept(string $name, string|Closure $filter, Closure $handler): void
|
||||
{
|
||||
if (is_string($filter)) {
|
||||
$filter = fn ($value): bool => $value instanceof $filter;
|
||||
}
|
||||
|
||||
self::$interceptors[$name][] = $handler;
|
||||
|
||||
$this->pipe($name, function ($next, ...$arguments) use ($handler, $filter): void {
|
||||
/* @phpstan-ignore-next-line */
|
||||
if ($filter($this->value, ...$arguments)) {
|
||||
// @phpstan-ignore-next-line
|
||||
$handler->bindTo($this, $this::class)(...$arguments);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$next();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get th list of pipes by the given name.
|
||||
*
|
||||
* @return array<int, Closure>
|
||||
*/
|
||||
private function pipes(string $name, object $context, string $scope): array
|
||||
{
|
||||
return array_map(fn (Closure $pipe): \Closure => $pipe->bindTo($context, $scope), self::$pipes[$name] ?? []);
|
||||
}
|
||||
}
|
||||
31
src/Concerns/Retrievable.php
Normal file
31
src/Concerns/Retrievable.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Concerns;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
trait Retrievable
|
||||
{
|
||||
/**
|
||||
* @template TRetrievableValue
|
||||
*
|
||||
* Safely retrieve the value at the given key from an object or array.
|
||||
* @template TRetrievableValue
|
||||
*
|
||||
* @param array<string, TRetrievableValue>|object $value
|
||||
* @param TRetrievableValue|null $default
|
||||
* @return TRetrievableValue|null
|
||||
*/
|
||||
private function retrieve(string $key, mixed $value, mixed $default = null): mixed
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return $value[$key] ?? $default;
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
return $value->$key ?? $default;
|
||||
}
|
||||
}
|
||||
@ -1,149 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Pest\Support\ExceptionTrace;
|
||||
use Pest\TestSuite;
|
||||
use PHPUnit\Util\Test;
|
||||
|
||||
/**
|
||||
* To avoid inheritance conflicts, all the fields related
|
||||
* to Pest only will be prefixed by double underscore.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait TestCase
|
||||
{
|
||||
/**
|
||||
* The test case description. Contains the first
|
||||
* argument of global functions like `it` and `test`.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $__description;
|
||||
|
||||
/**
|
||||
* Holds the test closure function.
|
||||
*
|
||||
* @var Closure
|
||||
*/
|
||||
private $__test;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the test case.
|
||||
*/
|
||||
public function __construct(Closure $test, string $description, array $data)
|
||||
{
|
||||
$this->__test = $test;
|
||||
$this->__description = $description;
|
||||
|
||||
parent::__construct('__test', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the groups to the current test case.
|
||||
*/
|
||||
public function addGroups(array $groups): void
|
||||
{
|
||||
$groups = array_unique(array_merge($this->getGroups(), $groups));
|
||||
|
||||
$this->setGroups($groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the test case name. Note that, in Pest
|
||||
* we ignore withDataset argument as the description
|
||||
* already contains the dataset description.
|
||||
*/
|
||||
public function getName(bool $withDataSet = true): string
|
||||
{
|
||||
return $this->__description;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called before the first test of this test class is run.
|
||||
*/
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
parent::setUpBeforeClass();
|
||||
|
||||
$beforeAll = TestSuite::getInstance()->beforeAll->get(self::$__filename);
|
||||
|
||||
call_user_func(Closure::bind($beforeAll, null, self::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called after the last test of this test class is run.
|
||||
*/
|
||||
public static function tearDownAfterClass(): void
|
||||
{
|
||||
$afterAll = TestSuite::getInstance()->afterAll->get(self::$__filename);
|
||||
|
||||
call_user_func(Closure::bind($afterAll, null, self::class));
|
||||
|
||||
parent::tearDownAfterClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets executed before the test.
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
TestSuite::getInstance()->test = $this;
|
||||
|
||||
parent::setUp();
|
||||
|
||||
$beforeEach = TestSuite::getInstance()->beforeEach->get(self::$__filename);
|
||||
|
||||
$this->__callClosure($beforeEach, func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets executed after the test.
|
||||
*/
|
||||
protected function tearDown(): void
|
||||
{
|
||||
$afterEach = TestSuite::getInstance()->afterEach->get(self::$__filename);
|
||||
|
||||
$this->__callClosure($afterEach, func_get_args());
|
||||
|
||||
parent::tearDown();
|
||||
|
||||
TestSuite::getInstance()->test = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the test case as string.
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
return \sprintf(
|
||||
'%s::%s',
|
||||
self::$__filename,
|
||||
$this->__description
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the test.
|
||||
*/
|
||||
public function __test(): void
|
||||
{
|
||||
$this->__callClosure($this->__test, func_get_args());
|
||||
}
|
||||
|
||||
private function __callClosure(Closure $closure, array $arguments): void
|
||||
{
|
||||
ExceptionTrace::ensure(function () use ($closure, $arguments) {
|
||||
call_user_func_array(Closure::bind($closure, $this, get_class($this)), $arguments);
|
||||
});
|
||||
}
|
||||
|
||||
public function getPrintableTestCaseName(): string
|
||||
{
|
||||
return ltrim(self::class, 'P\\');
|
||||
}
|
||||
}
|
||||
328
src/Concerns/Testable.php
Normal file
328
src/Concerns/Testable.php
Normal file
@ -0,0 +1,328 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Pest\Exceptions\DatasetArgsCountMismatch;
|
||||
use Pest\Support\ChainableClosure;
|
||||
use Pest\Support\ExceptionTrace;
|
||||
use Pest\Support\Reflection;
|
||||
use Pest\TestSuite;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionException;
|
||||
use ReflectionFunction;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @mixin TestCase
|
||||
*/
|
||||
trait Testable
|
||||
{
|
||||
/**
|
||||
* Test method description.
|
||||
*/
|
||||
private string $__description;
|
||||
|
||||
/**
|
||||
* Test "latest" method description.
|
||||
*/
|
||||
private static string $__latestDescription;
|
||||
|
||||
/**
|
||||
* The Test Case "test" closure.
|
||||
*/
|
||||
private Closure $__test;
|
||||
|
||||
/**
|
||||
* The Test Case "setUp" closure.
|
||||
*/
|
||||
private ?Closure $__beforeEach = null;
|
||||
|
||||
/**
|
||||
* The Test Case "tearDown" closure.
|
||||
*/
|
||||
private ?Closure $__afterEach = null;
|
||||
|
||||
/**
|
||||
* The Test Case "setUpBeforeClass" closure.
|
||||
*/
|
||||
private static ?Closure $__beforeAll = null;
|
||||
|
||||
/**
|
||||
* The test "tearDownAfterClass" closure.
|
||||
*/
|
||||
private static ?Closure $__afterAll = null;
|
||||
|
||||
/**
|
||||
* Resets the test case static properties.
|
||||
*/
|
||||
public static function flush(): void
|
||||
{
|
||||
self::$__beforeAll = null;
|
||||
self::$__afterAll = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Test Case instance.
|
||||
*/
|
||||
public function __construct(string $name)
|
||||
{
|
||||
parent::__construct($name);
|
||||
|
||||
$test = TestSuite::getInstance()->tests->get(self::$__filename);
|
||||
|
||||
if ($test->hasMethod($name)) {
|
||||
$method = $test->getMethod($name);
|
||||
$this->__description = self::$__latestDescription = $method->description;
|
||||
$this->__test = $method->getClosure($this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new "setUpBeforeClass" to the Test Case.
|
||||
*/
|
||||
public function __addBeforeAll(?Closure $hook): void
|
||||
{
|
||||
if ($hook === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$__beforeAll = (self::$__beforeAll instanceof Closure)
|
||||
? ChainableClosure::fromStatic(self::$__beforeAll, $hook)
|
||||
: $hook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new "tearDownAfterClass" to the Test Case.
|
||||
*/
|
||||
public function __addAfterAll(?Closure $hook): void
|
||||
{
|
||||
if ($hook === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$__afterAll = (self::$__afterAll instanceof Closure)
|
||||
? ChainableClosure::fromStatic(self::$__afterAll, $hook)
|
||||
: $hook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new "setUp" to the Test Case.
|
||||
*/
|
||||
public function __addBeforeEach(?Closure $hook): void
|
||||
{
|
||||
$this->__addHook('__beforeEach', $hook);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new "tearDown" to the Test Case.
|
||||
*/
|
||||
public function __addAfterEach(?Closure $hook): void
|
||||
{
|
||||
$this->__addHook('__afterEach', $hook);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new "hook" to the Test Case.
|
||||
*/
|
||||
private function __addHook(string $property, ?Closure $hook): void
|
||||
{
|
||||
if ($hook === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->{$property} = ($this->{$property} instanceof Closure)
|
||||
? ChainableClosure::from($this->{$property}, $hook)
|
||||
: $hook;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called before the first test of this Test Case is run.
|
||||
*/
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
parent::setUpBeforeClass();
|
||||
|
||||
$beforeAll = TestSuite::getInstance()->beforeAll->get(self::$__filename);
|
||||
|
||||
if (self::$__beforeAll instanceof Closure) {
|
||||
$beforeAll = ChainableClosure::fromStatic(self::$__beforeAll, $beforeAll);
|
||||
}
|
||||
|
||||
call_user_func(Closure::bind($beforeAll, null, self::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called after the last test of this Test Case is run.
|
||||
*/
|
||||
public static function tearDownAfterClass(): void
|
||||
{
|
||||
$afterAll = TestSuite::getInstance()->afterAll->get(self::$__filename);
|
||||
|
||||
if (self::$__afterAll instanceof Closure) {
|
||||
$afterAll = ChainableClosure::fromStatic(self::$__afterAll, $afterAll);
|
||||
}
|
||||
|
||||
call_user_func(Closure::bind($afterAll, null, self::class));
|
||||
|
||||
parent::tearDownAfterClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets executed before the Test Case.
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
TestSuite::getInstance()->test = $this;
|
||||
|
||||
parent::setUp();
|
||||
|
||||
$beforeEach = TestSuite::getInstance()->beforeEach->get(self::$__filename);
|
||||
|
||||
if ($this->__beforeEach instanceof Closure) {
|
||||
$beforeEach = ChainableClosure::from($this->__beforeEach, $beforeEach);
|
||||
}
|
||||
|
||||
$this->__callClosure($beforeEach, func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets executed after the Test Case.
|
||||
*/
|
||||
protected function tearDown(): void
|
||||
{
|
||||
$afterEach = TestSuite::getInstance()->afterEach->get(self::$__filename);
|
||||
|
||||
if ($this->__afterEach instanceof Closure) {
|
||||
$afterEach = ChainableClosure::from($this->__afterEach, $afterEach);
|
||||
}
|
||||
|
||||
$this->__callClosure($afterEach, func_get_args());
|
||||
|
||||
parent::tearDown();
|
||||
|
||||
TestSuite::getInstance()->test = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the Test Case current test.
|
||||
*
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function __runTest(Closure $closure, ...$args): mixed
|
||||
{
|
||||
$arguments = $this->__resolveTestArguments($args);
|
||||
$this->__ensureDatasetArgumentNumberMatches($arguments);
|
||||
|
||||
return $this->__callClosure($closure, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the passed arguments. Any Closures will be bound to the testcase and resolved.
|
||||
*
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function __resolveTestArguments(array $arguments): array
|
||||
{
|
||||
$method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name());
|
||||
|
||||
$this->__description = self::$__latestDescription = $this->dataName() ? $method->description.' with '.$this->dataName() : $method->description;
|
||||
|
||||
$underlyingTest = Reflection::getFunctionVariable($this->__test, 'closure');
|
||||
$testParameterTypes = array_values(Reflection::getFunctionArguments($underlyingTest));
|
||||
|
||||
if (count($arguments) !== 1) {
|
||||
foreach ($arguments as $argumentIndex => $argumentValue) {
|
||||
if (! $argumentValue instanceof Closure) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($testParameterTypes[$argumentIndex], [\Closure::class, 'callable', 'mixed'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$arguments[$argumentIndex] = $this->__callClosure($argumentValue, []);
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
if (! $arguments[0] instanceof Closure) {
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
if (in_array($testParameterTypes[0], [\Closure::class, 'callable'])) {
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
$boundDatasetResult = $this->__callClosure($arguments[0], []);
|
||||
if (count($testParameterTypes) === 1) {
|
||||
return [$boundDatasetResult];
|
||||
}
|
||||
if (! is_array($boundDatasetResult)) {
|
||||
return [$boundDatasetResult];
|
||||
}
|
||||
|
||||
return array_values($boundDatasetResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures dataset items count matches underlying test case required parameters
|
||||
*
|
||||
* @throws ReflectionException
|
||||
* @throws DatasetArgsCountMismatch
|
||||
*/
|
||||
private function __ensureDatasetArgumentNumberMatches(array $arguments): void
|
||||
{
|
||||
if ($arguments === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$underlyingTest = Reflection::getFunctionVariable($this->__test, 'closure');
|
||||
$testReflection = new ReflectionFunction($underlyingTest);
|
||||
$requiredParametersCount = $testReflection->getNumberOfRequiredParameters();
|
||||
$suppliedParametersCount = count($arguments);
|
||||
|
||||
if ($suppliedParametersCount >= $requiredParametersCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new DatasetArgsCountMismatch($this->dataName(), $requiredParametersCount, $suppliedParametersCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function __callClosure(Closure $closure, array $arguments): mixed
|
||||
{
|
||||
return ExceptionTrace::ensure(fn (): mixed => call_user_func_array(Closure::bind($closure, $this, $this::class), $arguments));
|
||||
}
|
||||
|
||||
/**
|
||||
* The printable test case name.
|
||||
*/
|
||||
public static function getPrintableTestCaseName(): string
|
||||
{
|
||||
return preg_replace('/P\\\/', '', self::class, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* The printable test case method name.
|
||||
*/
|
||||
public function getPrintableTestCaseMethodName(): string
|
||||
{
|
||||
return $this->__description;
|
||||
}
|
||||
|
||||
/**
|
||||
* The latest printable test case method name.
|
||||
*/
|
||||
public static function getLatestPrintableTestCaseMethodName(): string
|
||||
{
|
||||
return self::$__latestDescription;
|
||||
}
|
||||
}
|
||||
@ -1,145 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Console;
|
||||
|
||||
use Pest\Actions\AddsCoverage;
|
||||
use Pest\Actions\AddsDefaults;
|
||||
use Pest\Actions\AddsTests;
|
||||
use Pest\Actions\LoadStructure;
|
||||
use Pest\Actions\ValidatesConfiguration;
|
||||
use Pest\TestSuite;
|
||||
use PHPUnit\Framework\TestSuite as BaseTestSuite;
|
||||
use PHPUnit\TextUI\Command as BaseCommand;
|
||||
use PHPUnit\TextUI\TestRunner;
|
||||
use SebastianBergmann\FileIterator\Facade as FileIteratorFacade;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Command extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* Holds the current testing suite.
|
||||
*
|
||||
* @var TestSuite
|
||||
*/
|
||||
private $testSuite;
|
||||
|
||||
/**
|
||||
* Holds the current console output.
|
||||
*
|
||||
* @var OutputInterface
|
||||
*/
|
||||
private $output;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the command class.
|
||||
*/
|
||||
public function __construct(TestSuite $testSuite, OutputInterface $output)
|
||||
{
|
||||
$this->testSuite = $testSuite;
|
||||
$this->output = $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @phpstan-ignore-next-line
|
||||
*
|
||||
* @param array<int, string> $argv
|
||||
*/
|
||||
protected function handleArguments(array $argv): void
|
||||
{
|
||||
/*
|
||||
* First, let's handle pest is own `--coverage` param.
|
||||
*/
|
||||
$argv = AddsCoverage::from($this->testSuite, $argv);
|
||||
|
||||
/*
|
||||
* Next, as usual, let's send the console arguments to PHPUnit.
|
||||
*/
|
||||
parent::handleArguments($argv);
|
||||
|
||||
/*
|
||||
* Finally, let's validate the configuration. Making
|
||||
* sure all options are yet supported by Pest.
|
||||
*/
|
||||
ValidatesConfiguration::in($this->arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new PHPUnit test runner.
|
||||
*/
|
||||
protected function createRunner(): TestRunner
|
||||
{
|
||||
/*
|
||||
* First, let's add the defaults we use on `pest`. Those
|
||||
* are the printer class, and others that may be appear.
|
||||
*/
|
||||
$this->arguments = AddsDefaults::to($this->arguments);
|
||||
|
||||
$testRunner = new TestRunner($this->arguments['loader']);
|
||||
$testSuite = $this->arguments['test'];
|
||||
|
||||
if (is_string($testSuite)) {
|
||||
if (\is_dir($testSuite)) {
|
||||
/** @var string[] $files */
|
||||
$files = (new FileIteratorFacade())->getFilesAsArray(
|
||||
$testSuite,
|
||||
$this->arguments['testSuffixes']
|
||||
);
|
||||
} else {
|
||||
$files = [$testSuite];
|
||||
}
|
||||
|
||||
$testSuite = new BaseTestSuite($testSuite);
|
||||
|
||||
$testSuite->addTestFiles($files);
|
||||
|
||||
$this->arguments['test'] = $testSuite;
|
||||
}
|
||||
|
||||
LoadStructure::in($this->testSuite->rootPath);
|
||||
AddsTests::to($testSuite, $this->testSuite);
|
||||
|
||||
return $testRunner;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @phpstan-ignore-next-line
|
||||
*
|
||||
* @param array<int, string> $argv
|
||||
*/
|
||||
public function run(array $argv, bool $exit = true): int
|
||||
{
|
||||
$result = parent::run($argv, false);
|
||||
|
||||
if ($result === 0 && $this->testSuite->coverage) {
|
||||
if (!Coverage::isAvailable()) {
|
||||
$this->output->writeln(
|
||||
"\n <fg=white;bg=red;options=bold> ERROR </> No code coverage driver is available.</>",
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$coverage = Coverage::report($this->output);
|
||||
|
||||
$result = (int) ($coverage < $this->testSuite->coverageMin);
|
||||
|
||||
if ($result === 1) {
|
||||
$this->output->writeln(sprintf(
|
||||
"\n <fg=white;bg=red;options=bold> FAIL </> Code coverage below expected:<fg=red;options=bold> %s %%</>. Minimum:<fg=white;options=bold> %s %%</>.",
|
||||
number_format($coverage, 1),
|
||||
number_format($this->testSuite->coverageMin, 1)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
exit($result);
|
||||
}
|
||||
}
|
||||
@ -1,167 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Console;
|
||||
|
||||
use Pest\Exceptions\ShouldNotHappen;
|
||||
use SebastianBergmann\CodeCoverage\Node\Directory;
|
||||
use SebastianBergmann\CodeCoverage\Node\File;
|
||||
use SebastianBergmann\Environment\Runtime;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Terminal;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Coverage
|
||||
{
|
||||
/**
|
||||
* Returns the coverage path.
|
||||
*/
|
||||
public static function getPath(): string
|
||||
{
|
||||
return implode(DIRECTORY_SEPARATOR, [
|
||||
dirname(__DIR__, 2),
|
||||
'.temp',
|
||||
'coverage.php',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs true there is any code
|
||||
* coverage driver available.
|
||||
*/
|
||||
public static function isAvailable(): bool
|
||||
{
|
||||
return (new Runtime())->canCollectCodeCoverage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports the code coverage report to the
|
||||
* console and returns the result in float.
|
||||
*/
|
||||
public static function report(OutputInterface $output): float
|
||||
{
|
||||
if (!file_exists($reportPath = self::getPath())) {
|
||||
throw ShouldNotHappen::fromMessage(sprintf('Coverage not found in path: %s.', $reportPath));
|
||||
}
|
||||
|
||||
/** @var \SebastianBergmann\CodeCoverage\CodeCoverage $codeCoverage */
|
||||
$codeCoverage = require $reportPath;
|
||||
unlink($reportPath);
|
||||
|
||||
$totalWidth = (new Terminal())->getWidth();
|
||||
|
||||
$dottedLineLength = $totalWidth <= 70 ? $totalWidth : 70;
|
||||
|
||||
$totalCoverage = $codeCoverage->getReport()->getLineExecutedPercent();
|
||||
|
||||
$output->writeln(
|
||||
sprintf(
|
||||
' <fg=white;options=bold>Cov: </><fg=default>%s</>',
|
||||
$totalCoverage
|
||||
)
|
||||
);
|
||||
|
||||
$output->writeln('');
|
||||
|
||||
/** @var Directory<File|Directory> $report */
|
||||
$report = $codeCoverage->getReport();
|
||||
|
||||
foreach ($report->getIterator() as $file) {
|
||||
if (!$file instanceof File) {
|
||||
continue;
|
||||
}
|
||||
$dirname = dirname($file->getId());
|
||||
$basename = basename($file->getId(), '.php');
|
||||
|
||||
$name = $dirname === '.' ? $basename : implode(DIRECTORY_SEPARATOR, [
|
||||
$dirname,
|
||||
$basename,
|
||||
]);
|
||||
$rawName = $dirname === '.' ? $basename : implode(DIRECTORY_SEPARATOR, [
|
||||
$dirname,
|
||||
$basename,
|
||||
]);
|
||||
|
||||
$linesExecutedTakenSize = 0;
|
||||
|
||||
if ($file->getLineExecutedPercent() != '0.00%') {
|
||||
$linesExecutedTakenSize = strlen($uncoveredLines = trim(implode(', ', self::getMissingCoverage($file)))) + 1;
|
||||
$name .= sprintf(' <fg=red>%s</>', $uncoveredLines);
|
||||
}
|
||||
|
||||
$percentage = $file->getNumExecutableLines() === 0
|
||||
? '100.0'
|
||||
: number_format((float) $file->getLineExecutedPercent(), 1, '.', '');
|
||||
|
||||
$takenSize = strlen($rawName . $percentage) + 4 + $linesExecutedTakenSize; // adding 3 space and percent sign
|
||||
|
||||
$percentage = sprintf(
|
||||
'<fg=%s>%s</>',
|
||||
$percentage === '100.0' ? 'green' : ($percentage === '0.0' ? 'red' : 'yellow'),
|
||||
$percentage
|
||||
);
|
||||
|
||||
$output->writeln(sprintf(' %s %s %s %%',
|
||||
$name,
|
||||
str_repeat('.', max($dottedLineLength - $takenSize, 1)),
|
||||
$percentage
|
||||
));
|
||||
}
|
||||
|
||||
return (float) $totalCoverage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of missing coverage on the following format:.
|
||||
*
|
||||
* ```
|
||||
* ['11', '20..25', '50', '60...80'];
|
||||
* ```
|
||||
*
|
||||
* @param File $file
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public static function getMissingCoverage($file): array
|
||||
{
|
||||
$shouldBeNewLine = true;
|
||||
|
||||
$eachLine = function (array $array, array $tests, int $line) use (&$shouldBeNewLine): array {
|
||||
if (count($tests) > 0) {
|
||||
$shouldBeNewLine = true;
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
if ($shouldBeNewLine) {
|
||||
$array[] = (string) $line;
|
||||
$shouldBeNewLine = false;
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
$lastKey = count($array) - 1;
|
||||
|
||||
if (array_key_exists($lastKey, $array) && strpos($array[$lastKey], '..') !== false) {
|
||||
[$from] = explode('..', $array[$lastKey]);
|
||||
$array[$lastKey] = sprintf('%s..%s', $from, $line);
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
$array[$lastKey] = sprintf('%s..%s', $array[$lastKey], $line);
|
||||
|
||||
return $array;
|
||||
};
|
||||
|
||||
$array = [];
|
||||
foreach (array_filter($file->getCoverageData(), 'is_array') as $line => $tests) {
|
||||
$array = $eachLine($array, $tests, $line);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
44
src/Console/Help.php
Normal file
44
src/Console/Help.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Console;
|
||||
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Help
|
||||
{
|
||||
/**
|
||||
* The Command messages.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private const HELP_MESSAGES = [
|
||||
'<comment>Pest Options:</comment>',
|
||||
' <info>--init</info> Initialise a standard Pest configuration',
|
||||
' <info>--coverage</info> Enable coverage and output to standard output',
|
||||
' <info>--min=<fg=cyan><N></></info> Set the minimum required coverage percentage (<N>), and fail if not met',
|
||||
' <info>--group=<fg=cyan><name></></info> Only runs tests from the specified group(s)',
|
||||
];
|
||||
|
||||
/**
|
||||
* Creates a new Console Command instance.
|
||||
*/
|
||||
public function __construct(private readonly OutputInterface $output)
|
||||
{
|
||||
// ..
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the Console Command.
|
||||
*/
|
||||
public function __invoke(): void
|
||||
{
|
||||
foreach (self::HELP_MESSAGES as $message) {
|
||||
$this->output->writeln($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
85
src/Console/Thanks.php
Normal file
85
src/Console/Thanks.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Console;
|
||||
|
||||
use Pest\Bootstrappers\BootView;
|
||||
use Pest\Support\View;
|
||||
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Thanks
|
||||
{
|
||||
/**
|
||||
* The support options.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private const FUNDING_MESSAGES = [
|
||||
'Star the project on GitHub' => 'https://github.com/pestphp/pest',
|
||||
'Tweet about the project' => 'https://twitter.com/pestphp',
|
||||
'Sponsor the project' => 'https://github.com/sponsors/nunomaduro',
|
||||
];
|
||||
|
||||
/**
|
||||
* Creates a new Console Command instance.
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly InputInterface $input,
|
||||
private readonly OutputInterface $output
|
||||
) {
|
||||
// ..
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the Console Command.
|
||||
*/
|
||||
public function __invoke(): void
|
||||
{
|
||||
$bootstrapper = new BootView($this->output);
|
||||
$bootstrapper->boot();
|
||||
|
||||
$wantsToSupport = false;
|
||||
|
||||
if (getenv('PEST_NO_SUPPORT') !== 'true' && $this->input->isInteractive()) {
|
||||
$wantsToSupport = (new SymfonyQuestionHelper())->ask(
|
||||
new ArrayInput([]),
|
||||
$this->output,
|
||||
new ConfirmationQuestion(
|
||||
' <options=bold>Wanna show Pest some love by starring it on GitHub?</>',
|
||||
false,
|
||||
)
|
||||
);
|
||||
|
||||
View::render('components.new-line');
|
||||
|
||||
foreach (self::FUNDING_MESSAGES as $message => $link) {
|
||||
View::render('components.two-column-detail', [
|
||||
'left' => $message,
|
||||
'right' => $link,
|
||||
]);
|
||||
}
|
||||
|
||||
View::render('components.new-line');
|
||||
}
|
||||
|
||||
if ($wantsToSupport === true) {
|
||||
if (PHP_OS_FAMILY == 'Darwin') {
|
||||
exec('open https://github.com/pestphp/pest');
|
||||
}
|
||||
if (PHP_OS_FAMILY == 'Windows') {
|
||||
exec('start https://github.com/pestphp/pest');
|
||||
}
|
||||
if (PHP_OS_FAMILY == 'Linux') {
|
||||
exec('xdg-open https://github.com/pestphp/pest');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/Contracts/AddsAnnotations.php
Normal file
21
src/Contracts/AddsAnnotations.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Contracts;
|
||||
|
||||
use Pest\Factories\TestCaseMethodFactory;
|
||||
|
||||
/**
|
||||
* @interal
|
||||
*/
|
||||
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;
|
||||
}
|
||||
16
src/Contracts/Bootstrapper.php
Normal file
16
src/Contracts/Bootstrapper.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Contracts;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface Bootstrapper
|
||||
{
|
||||
/**
|
||||
* Boots the bootstrapper.
|
||||
*/
|
||||
public function boot(): void;
|
||||
}
|
||||
@ -4,18 +4,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace Pest\Contracts;
|
||||
|
||||
if (interface_exists(\NunoMaduro\Collision\Contracts\Adapters\Phpunit\HasPrintableTestCaseName::class)) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface HasPrintableTestCaseName extends \NunoMaduro\Collision\Contracts\Adapters\Phpunit\HasPrintableTestCaseName
|
||||
{
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface HasPrintableTestCaseName
|
||||
{
|
||||
}
|
||||
use NunoMaduro\Collision\Contracts\Adapters\Phpunit\HasPrintableTestCaseName as BaseHasPrintableTestCaseName;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface HasPrintableTestCaseName extends BaseHasPrintableTestCaseName
|
||||
{
|
||||
// ..
|
||||
}
|
||||
|
||||
23
src/Contracts/Panicable.php
Normal file
23
src/Contracts/Panicable.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Contracts;
|
||||
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface Panicable
|
||||
{
|
||||
/**
|
||||
* Renders the panic on the given output.
|
||||
*/
|
||||
public function render(OutputInterface $output): void;
|
||||
|
||||
/**
|
||||
* The exit code to be used.
|
||||
*/
|
||||
public function exitCode(): int;
|
||||
}
|
||||
16
src/Contracts/Plugins/AddsOutput.php
Normal file
16
src/Contracts/Plugins/AddsOutput.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Contracts\Plugins;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface AddsOutput
|
||||
{
|
||||
/**
|
||||
* Adds output after the Test Suite execution.
|
||||
*/
|
||||
public function addOutput(int $exitCode): int;
|
||||
}
|
||||
16
src/Contracts/Plugins/Bootable.php
Normal file
16
src/Contracts/Plugins/Bootable.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Contracts\Plugins;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface Bootable
|
||||
{
|
||||
/**
|
||||
* Boots the plugin.
|
||||
*/
|
||||
public function boot(): void;
|
||||
}
|
||||
19
src/Contracts/Plugins/HandlesArguments.php
Normal file
19
src/Contracts/Plugins/HandlesArguments.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Contracts\Plugins;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface HandlesArguments
|
||||
{
|
||||
/**
|
||||
* Adds arguments before of the Test Suite execution.
|
||||
*
|
||||
* @param array<int, string> $arguments
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function handleArguments(array $arguments): array;
|
||||
}
|
||||
16
src/Contracts/Plugins/Shutdownable.php
Normal file
16
src/Contracts/Plugins/Shutdownable.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Contracts\Plugins;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface Shutdownable
|
||||
{
|
||||
/**
|
||||
* Shutdowns the plugin.
|
||||
*/
|
||||
public function shutdown(): void;
|
||||
}
|
||||
13
src/Contracts/TestCaseFilter.php
Normal file
13
src/Contracts/TestCaseFilter.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Contracts;
|
||||
|
||||
interface TestCaseFilter
|
||||
{
|
||||
/**
|
||||
* Whether the test case is accepted.
|
||||
*/
|
||||
public function accept(string $testCaseFilename): bool;
|
||||
}
|
||||
15
src/Contracts/TestCaseMethodFilter.php
Normal file
15
src/Contracts/TestCaseMethodFilter.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Contracts;
|
||||
|
||||
use Pest\Factories\TestCaseMethodFactory;
|
||||
|
||||
interface TestCaseMethodFilter
|
||||
{
|
||||
/**
|
||||
* Whether the test case method is accepted.
|
||||
*/
|
||||
public function accept(TestCaseMethodFactory $factory): bool;
|
||||
}
|
||||
@ -1,97 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest;
|
||||
|
||||
use Closure;
|
||||
use Pest\Exceptions\DatasetAlreadyExist;
|
||||
use Pest\Exceptions\DatasetDoesNotExist;
|
||||
use SebastianBergmann\Exporter\Exporter;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Datasets
|
||||
{
|
||||
/**
|
||||
* Holds the datasets.
|
||||
*
|
||||
* @var array<string, \Closure|iterable<int, mixed>>
|
||||
*/
|
||||
private static $datasets = [];
|
||||
|
||||
/**
|
||||
* Sets the given.
|
||||
*
|
||||
* @param Closure|iterable<int, mixed> $data
|
||||
*/
|
||||
public static function set(string $name, $data): void
|
||||
{
|
||||
if (array_key_exists($name, self::$datasets)) {
|
||||
throw new DatasetAlreadyExist($name);
|
||||
}
|
||||
|
||||
self::$datasets[$name] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Closure|iterable<int, mixed>
|
||||
*/
|
||||
public static function get(string $name)
|
||||
{
|
||||
if (!array_key_exists($name, self::$datasets)) {
|
||||
throw new DatasetDoesNotExist($name);
|
||||
}
|
||||
|
||||
return self::$datasets[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the current dataset to an array value.
|
||||
*
|
||||
* @param Traversable<int, mixed>|Closure|iterable<int, mixed>|string|null $data
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function resolve(string $description, $data): array
|
||||
{
|
||||
/* @phpstan-ignore-next-line */
|
||||
if (is_null($data) || empty($data)) {
|
||||
return [$description => []];
|
||||
}
|
||||
|
||||
if (is_string($data)) {
|
||||
$data = self::get($data);
|
||||
}
|
||||
|
||||
if (is_callable($data)) {
|
||||
$data = call_user_func($data);
|
||||
}
|
||||
|
||||
if ($data instanceof Traversable) {
|
||||
$data = iterator_to_array($data);
|
||||
}
|
||||
|
||||
$namedData = [];
|
||||
foreach ($data as $values) {
|
||||
$values = is_array($values) ? $values : [$values];
|
||||
|
||||
$name = $description . self::getDataSetDescription($values);
|
||||
$namedData[$name] = $values;
|
||||
}
|
||||
|
||||
return $namedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, mixed> $data
|
||||
*/
|
||||
private static function getDataSetDescription(array $data): string
|
||||
{
|
||||
$exporter = new Exporter();
|
||||
|
||||
return \sprintf(' with (%s)', $exporter->shortenedRecursiveExport($data));
|
||||
}
|
||||
}
|
||||
@ -15,10 +15,10 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||
final class AfterAllAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||
{
|
||||
/**
|
||||
* Creates a new instance of after all already exist exception.
|
||||
* Creates a new Exception instance.
|
||||
*/
|
||||
public function __construct(string $filename)
|
||||
{
|
||||
parent::__construct(sprintf('The afterAll already exist in the filename `%s`.', $filename));
|
||||
parent::__construct(sprintf('The afterAll already exists in the filename `%s`.', $filename));
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,10 +15,10 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||
final class AfterEachAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||
{
|
||||
/**
|
||||
* Creates a new instance of after each already exist exception.
|
||||
* Creates a new Exception instance.
|
||||
*/
|
||||
public function __construct(string $filename)
|
||||
{
|
||||
parent::__construct(sprintf('The afterEach already exist in the filename `%s`.', $filename));
|
||||
parent::__construct(sprintf('The afterEach already exists in the filename `%s`.', $filename));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
<?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 AttributeNotSupportedYet extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||
{
|
||||
/**
|
||||
* Creates a new instance of attribute not supported yet.
|
||||
*/
|
||||
public function __construct(string $attribute, string $value)
|
||||
{
|
||||
parent::__construct(sprintf('The PHPUnit attribute `%s` with value `%s` is not supported yet.', $attribute, $value));
|
||||
}
|
||||
}
|
||||
@ -15,10 +15,10 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||
final class BeforeEachAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||
{
|
||||
/**
|
||||
* Creates a new instance of before each already exist exception.
|
||||
* Creates a new Exception instance.
|
||||
*/
|
||||
public function __construct(string $filename)
|
||||
{
|
||||
parent::__construct(sprintf('The beforeEach already exist in the filename `%s`.', $filename));
|
||||
parent::__construct(sprintf('The beforeEach already exists in the filename `%s`.', $filename));
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,13 +12,13 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class DatasetAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||
final class DatasetAlreadyExists extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||
{
|
||||
/**
|
||||
* Creates a new instance of dataset already exist.
|
||||
* Creates a new Exception instance.
|
||||
*/
|
||||
public function __construct(string $name)
|
||||
public function __construct(string $name, string $scope)
|
||||
{
|
||||
parent::__construct(sprintf('A dataset with the name `%s` already exist.', $name));
|
||||
parent::__construct(sprintf('A dataset with the name `%s` already exists in scope [%s].', $name, $scope));
|
||||
}
|
||||
}
|
||||
15
src/Exceptions/DatasetArgsCountMismatch.php
Normal file
15
src/Exceptions/DatasetArgsCountMismatch.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
final class DatasetArgsCountMismatch extends Exception
|
||||
{
|
||||
public function __construct(string $dataName, int $requiredCount, int $suppliedCount)
|
||||
{
|
||||
parent::__construct(sprintf('Test expects %d arguments but dataset [%s] only provides %d', $requiredCount, $dataName, $suppliedCount));
|
||||
}
|
||||
}
|
||||
@ -15,7 +15,7 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||
final class DatasetDoesNotExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||
{
|
||||
/**
|
||||
* Creates a new instance of dataset does not exist.
|
||||
* Creates a new Exception instance.
|
||||
*/
|
||||
public function __construct(string $name)
|
||||
{
|
||||
|
||||
32
src/Exceptions/DatasetMissing.php
Normal file
32
src/Exceptions/DatasetMissing.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Exceptions;
|
||||
|
||||
use BadFunctionCallException;
|
||||
use NunoMaduro\Collision\Contracts\RenderlessEditor;
|
||||
use NunoMaduro\Collision\Contracts\RenderlessTrace;
|
||||
use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class DatasetMissing extends BadFunctionCallException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||
{
|
||||
/**
|
||||
* Creates a new Exception instance.
|
||||
*
|
||||
* @param array<string, string> $arguments
|
||||
*/
|
||||
public function __construct(string $file, string $name, array $arguments)
|
||||
{
|
||||
parent::__construct(sprintf(
|
||||
"A test with the description '%s' has %d argument(s) ([%s]) and no dataset(s) provided in %s",
|
||||
$name,
|
||||
count($arguments),
|
||||
implode(', ', array_map(static fn (string $arg, string $type): string => sprintf('%s $%s', $type, $arg), array_keys($arguments), $arguments)),
|
||||
$file,
|
||||
));
|
||||
}
|
||||
}
|
||||
21
src/Exceptions/ExpectationNotFound.php
Normal file
21
src/Exceptions/ExpectationNotFound.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ExpectationNotFound extends Exception
|
||||
{
|
||||
/**
|
||||
* Creates a new ExpectationNotFound instance from the given name.
|
||||
*/
|
||||
public static function fromName(string $name): ExpectationNotFound
|
||||
{
|
||||
return new self("Expectation [$name] does not exist.");
|
||||
}
|
||||
}
|
||||
@ -15,10 +15,10 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||
final class FileOrFolderNotFound extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||
{
|
||||
/**
|
||||
* Creates a new instance of file not found.
|
||||
* Creates a new Exception instance.
|
||||
*/
|
||||
public function __construct(string $filename)
|
||||
{
|
||||
parent::__construct(sprintf('The file or folder with the name `%s` not found.', $filename));
|
||||
parent::__construct(sprintf('The file or folder with the name `%s` could not be found.', $filename));
|
||||
}
|
||||
}
|
||||
|
||||
26
src/Exceptions/InvalidExpectation.php
Normal file
26
src/Exceptions/InvalidExpectation.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Exceptions;
|
||||
|
||||
use LogicException;
|
||||
use NunoMaduro\Collision\Contracts\RenderlessEditor;
|
||||
use NunoMaduro\Collision\Contracts\RenderlessTrace;
|
||||
use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class InvalidExpectation extends LogicException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||
{
|
||||
/**
|
||||
* @param array<int, string> $methods
|
||||
*
|
||||
* @throws self
|
||||
*/
|
||||
public static function fromMethods(array $methods): never
|
||||
{
|
||||
throw new self(sprintf('Expectation [%s] is not valid.', implode('->', $methods)));
|
||||
}
|
||||
}
|
||||
21
src/Exceptions/InvalidExpectationValue.php
Normal file
21
src/Exceptions/InvalidExpectationValue.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Exceptions;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class InvalidExpectationValue extends InvalidArgumentException
|
||||
{
|
||||
/**
|
||||
* @throws self
|
||||
*/
|
||||
public static function expected(string $type): never
|
||||
{
|
||||
throw new self(sprintf('Invalid expectation value type. Expected [%s].', $type));
|
||||
}
|
||||
}
|
||||
@ -12,10 +12,10 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class InvalidConsoleArgument extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||
final class InvalidOption extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||
{
|
||||
/**
|
||||
* Creates a new instance of should not happen.
|
||||
* Creates a new Exception instance.
|
||||
*/
|
||||
public function __construct(string $message)
|
||||
{
|
||||
@ -15,10 +15,10 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||
final class InvalidPestCommand extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||
{
|
||||
/**
|
||||
* Creates a new instance of invalid pest command exception.
|
||||
* Creates a new Exception instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('Please run `./vendor/bin/pest` instead of `/vendor/bin/phpunit`.');
|
||||
parent::__construct('Please run [./vendor/bin/pest] instead.');
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
<?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 InvalidUsesPath extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||
{
|
||||
/**
|
||||
* Creates a new instance of invalid uses path.
|
||||
*/
|
||||
public function __construct(string $target)
|
||||
{
|
||||
parent::__construct(sprintf('The path `%s` is not valid.', $target));
|
||||
}
|
||||
}
|
||||
24
src/Exceptions/MissingDependency.php
Normal file
24
src/Exceptions/MissingDependency.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?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 MissingDependency extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||
{
|
||||
/**
|
||||
* Creates a new Exception instance.
|
||||
*/
|
||||
public function __construct(string $feature, string $dependency)
|
||||
{
|
||||
parent::__construct(sprintf('The feature "%s" requires "%s".', $feature, $dependency));
|
||||
}
|
||||
}
|
||||
38
src/Exceptions/NoDirtyTestsFound.php
Normal file
38
src/Exceptions/NoDirtyTestsFound.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Exceptions;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use NunoMaduro\Collision\Contracts\RenderlessEditor;
|
||||
use NunoMaduro\Collision\Contracts\RenderlessTrace;
|
||||
use Pest\Contracts\Panicable;
|
||||
use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class NoDirtyTestsFound extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace, Panicable
|
||||
{
|
||||
/**
|
||||
* Renders the panic on the given output.
|
||||
*/
|
||||
public function render(OutputInterface $output): void
|
||||
{
|
||||
$output->writeln([
|
||||
'',
|
||||
' <fg=white;options=bold;bg=blue> INFO </> No "dirty" tests found.',
|
||||
'',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The exit code to be used.
|
||||
*/
|
||||
public function exitCode(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@ -13,19 +13,18 @@ use RuntimeException;
|
||||
final class ShouldNotHappen extends RuntimeException
|
||||
{
|
||||
/**
|
||||
* Creates a new instance of should not happen.
|
||||
* Creates a new Exception instance.
|
||||
*/
|
||||
public function __construct(Exception $exception)
|
||||
{
|
||||
$message = $exception->getMessage();
|
||||
|
||||
parent::__construct(sprintf(<<<EOF
|
||||
|
||||
parent::__construct(sprintf(<<<'EOF'
|
||||
This should not happen - please create an new issue here: https://github.com/pestphp/pest.
|
||||
|
||||
- Issue: %s
|
||||
- PHP version: %s
|
||||
- Operating system: %s
|
||||
Issue: %s
|
||||
PHP version: %s
|
||||
Operating system: %s
|
||||
EOF
|
||||
, $message, phpversion(), PHP_OS), 1, $exception);
|
||||
}
|
||||
|
||||
@ -15,10 +15,10 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||
final class TestAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||
{
|
||||
/**
|
||||
* Creates a new instance of test already exist.
|
||||
* Creates a new Exception instance.
|
||||
*/
|
||||
public function __construct(string $fileName, string $description)
|
||||
{
|
||||
parent::__construct(sprintf('A test with the description `%s` already exist in the filename `%s`.', $description, $fileName));
|
||||
parent::__construct(sprintf('A test with the description `%s` already exists in the filename `%s`.', $description, $fileName));
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||
final class TestCaseAlreadyInUse extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||
{
|
||||
/**
|
||||
* Creates a new instance of test case already in use.
|
||||
* Creates a new Exception instance.
|
||||
*/
|
||||
public function __construct(string $inUse, string $newOne, string $folder)
|
||||
{
|
||||
|
||||
@ -15,7 +15,7 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||
final class TestCaseClassOrTraitNotFound extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||
{
|
||||
/**
|
||||
* Creates a new instance of after each already exist exception.
|
||||
* Creates a new Exception instance.
|
||||
*/
|
||||
public function __construct(string $testCaseClass)
|
||||
{
|
||||
|
||||
24
src/Exceptions/TestDescriptionMissing.php
Normal file
24
src/Exceptions/TestDescriptionMissing.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?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 TestDescriptionMissing extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||
{
|
||||
/**
|
||||
* Creates a new Exception instance.
|
||||
*/
|
||||
public function __construct(string $fileName)
|
||||
{
|
||||
parent::__construct(sprintf('Test description is missing in the filename `%s`.', $fileName));
|
||||
}
|
||||
}
|
||||
422
src/Expectation.php
Normal file
422
src/Expectation.php
Normal file
@ -0,0 +1,422 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Closure;
|
||||
use Pest\Arch\Contracts\ArchExpectation;
|
||||
use Pest\Arch\Expectations\ToBeUsedIn;
|
||||
use Pest\Arch\Expectations\ToBeUsedInNothing;
|
||||
use Pest\Arch\Expectations\ToOnlyBeUsedIn;
|
||||
use Pest\Arch\Expectations\ToOnlyUse;
|
||||
use Pest\Arch\Expectations\ToUse;
|
||||
use Pest\Arch\Expectations\ToUseNothing;
|
||||
use Pest\Concerns\Extendable;
|
||||
use Pest\Concerns\Pipeable;
|
||||
use Pest\Concerns\Retrievable;
|
||||
use Pest\Exceptions\ExpectationNotFound;
|
||||
use Pest\Exceptions\InvalidExpectation;
|
||||
use Pest\Exceptions\InvalidExpectationValue;
|
||||
use Pest\Expectations\EachExpectation;
|
||||
use Pest\Expectations\HigherOrderExpectation;
|
||||
use Pest\Expectations\OppositeExpectation;
|
||||
use Pest\Matchers\Any;
|
||||
use Pest\Support\ExpectationPipeline;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @template TValue
|
||||
*
|
||||
* @property OppositeExpectation $not Creates the opposite expectation.
|
||||
* @property EachExpectation $each Creates an expectation on each element on the traversable value.
|
||||
*
|
||||
* @mixin Mixins\Expectation<TValue>
|
||||
*/
|
||||
final class Expectation
|
||||
{
|
||||
use Extendable;
|
||||
use Pipeable;
|
||||
use Retrievable;
|
||||
|
||||
/**
|
||||
* Creates a new expectation.
|
||||
*
|
||||
* @param TValue $value
|
||||
*/
|
||||
public function __construct(
|
||||
public mixed $value
|
||||
) {
|
||||
// ..
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new expectation.
|
||||
*
|
||||
* @template TAndValue
|
||||
*
|
||||
* @param TAndValue $value
|
||||
* @return self<TAndValue>
|
||||
*/
|
||||
public function and(mixed $value): Expectation
|
||||
{
|
||||
return $value instanceof self ? $value : new self($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new expectation with the decoded JSON value.
|
||||
*
|
||||
* @return self<array<int|string, mixed>|bool>
|
||||
*/
|
||||
public function json(): Expectation
|
||||
{
|
||||
if (! is_string($this->value)) {
|
||||
InvalidExpectationValue::expected('string');
|
||||
}
|
||||
|
||||
$this->toBeJson();
|
||||
|
||||
/** @var array<int|string, mixed>|bool $value */
|
||||
$value = json_decode($this->value, true, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
return $this->and($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the expectation value.
|
||||
*
|
||||
* @return self<TValue>
|
||||
*/
|
||||
public function dump(mixed ...$arguments): self
|
||||
{
|
||||
if (function_exists('dump')) {
|
||||
dump($this->value, ...$arguments);
|
||||
} else {
|
||||
var_dump($this->value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the expectation value and end the script.
|
||||
*
|
||||
* @return never
|
||||
*/
|
||||
public function dd(mixed ...$arguments): void
|
||||
{
|
||||
if (function_exists('dd')) {
|
||||
dd($this->value, ...$arguments);
|
||||
}
|
||||
|
||||
var_dump($this->value);
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the expectation value to Ray along with all given arguments.
|
||||
*
|
||||
* @return self<TValue>
|
||||
*/
|
||||
public function ray(mixed ...$arguments): self
|
||||
{
|
||||
if (function_exists('ray')) {
|
||||
ray($this->value, ...$arguments);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the opposite expectation for the value.
|
||||
*
|
||||
* @return OppositeExpectation<TValue>
|
||||
*/
|
||||
public function not(): OppositeExpectation
|
||||
{
|
||||
return new OppositeExpectation($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an expectation on each item of the iterable "value".
|
||||
*
|
||||
* @return EachExpectation<TValue>
|
||||
*/
|
||||
public function each(callable $callback = null): EachExpectation
|
||||
{
|
||||
if (! is_iterable($this->value)) {
|
||||
throw new BadMethodCallException('Expectation value is not iterable.');
|
||||
}
|
||||
|
||||
if (is_callable($callback)) {
|
||||
foreach ($this->value as $key => $item) {
|
||||
$callback(new self($item), $key);
|
||||
}
|
||||
}
|
||||
|
||||
return new EachExpectation($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows you to specify a sequential set of expectations for each item in a iterable "value".
|
||||
*
|
||||
* @template TSequenceValue
|
||||
*
|
||||
* @param (callable(self<TValue>, self<string|int>): void)|TSequenceValue ...$callbacks
|
||||
* @return self<TValue>
|
||||
*/
|
||||
public function sequence(mixed ...$callbacks): self
|
||||
{
|
||||
if (! is_iterable($this->value)) {
|
||||
throw new BadMethodCallException('Expectation value is not iterable.');
|
||||
}
|
||||
|
||||
$value = is_array($this->value) ? $this->value : iterator_to_array($this->value);
|
||||
$keys = array_keys($value);
|
||||
$values = array_values($value);
|
||||
$callbacksCount = count($callbacks);
|
||||
|
||||
$index = 0;
|
||||
|
||||
while (count($callbacks) < count($values)) {
|
||||
$callbacks[] = $callbacks[$index];
|
||||
$index = $index < count($values) - 1 ? $index + 1 : 0;
|
||||
}
|
||||
|
||||
if ($callbacksCount > count($values)) {
|
||||
Assert::assertLessThanOrEqual(count($value), count($callbacks));
|
||||
}
|
||||
|
||||
foreach ($values as $key => $item) {
|
||||
if ($callbacks[$key] instanceof Closure) {
|
||||
call_user_func($callbacks[$key], new self($item), new self($keys[$key]));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
(new self($item))->toEqual($callbacks[$key]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the subject matches one of the given "expressions", the expression callback will run.
|
||||
*
|
||||
* @template TMatchSubject of array-key
|
||||
*
|
||||
* @param (callable(): TMatchSubject)|TMatchSubject $subject
|
||||
* @param array<TMatchSubject, (callable(self<TValue>): mixed)|TValue> $expressions
|
||||
* @return self<TValue>
|
||||
*/
|
||||
public function match(mixed $subject, array $expressions): self
|
||||
{
|
||||
$subject = $subject instanceof Closure ? $subject() : $subject;
|
||||
|
||||
$matched = false;
|
||||
|
||||
foreach ($expressions as $key => $callback) {
|
||||
if ($subject != $key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$matched = true;
|
||||
|
||||
if (is_callable($callback)) {
|
||||
$callback(new self($this->value));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->and($this->value)->toEqual($callback);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ($matched === false) {
|
||||
throw new ExpectationFailedException('Unhandled match value.');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the callback if the given "condition" is falsy.
|
||||
*
|
||||
* @param (callable(): bool)|bool $condition
|
||||
* @param callable(Expectation<TValue>): mixed $callback
|
||||
* @return self<TValue>
|
||||
*/
|
||||
public function unless(callable|bool $condition, callable $callback): Expectation
|
||||
{
|
||||
$condition = is_callable($condition)
|
||||
? $condition
|
||||
: static fn (): bool => $condition;
|
||||
|
||||
return $this->when(! $condition(), $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the callback if the given "condition" is truthy.
|
||||
*
|
||||
* @param (callable(): bool)|bool $condition
|
||||
* @param callable(self<TValue>): mixed $callback
|
||||
* @return self<TValue>
|
||||
*/
|
||||
public function when(callable|bool $condition, callable $callback): self
|
||||
{
|
||||
$condition = is_callable($condition)
|
||||
? $condition
|
||||
: static fn (): bool => $condition;
|
||||
|
||||
if ($condition()) {
|
||||
$callback($this->and($this->value));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically calls methods on the class or creates a new higher order expectation.
|
||||
*
|
||||
* @param array<int, mixed> $parameters
|
||||
* @return Expectation<TValue>|HigherOrderExpectation<Expectation<TValue>, TValue>
|
||||
*/
|
||||
public function __call(string $method, array $parameters): Expectation|HigherOrderExpectation
|
||||
{
|
||||
if (! self::hasMethod($method)) {
|
||||
/* @phpstan-ignore-next-line */
|
||||
return new HigherOrderExpectation($this, call_user_func_array($this->value->$method(...), $parameters));
|
||||
}
|
||||
|
||||
ExpectationPipeline::for($this->getExpectationClosure($method))
|
||||
->send(...$parameters)
|
||||
->through($this->pipes($method, $this, Expectation::class))
|
||||
->run();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new expectation closure from the given name.
|
||||
*
|
||||
* @throws ExpectationNotFound
|
||||
*/
|
||||
private function getExpectationClosure(string $name): Closure
|
||||
{
|
||||
if (method_exists(Mixins\Expectation::class, $name)) {
|
||||
// @phpstan-ignore-next-line
|
||||
return Closure::fromCallable([new Mixins\Expectation($this->value), $name]);
|
||||
}
|
||||
|
||||
if (self::hasExtend($name)) {
|
||||
$extend = self::$extends[$name]->bindTo($this, Expectation::class);
|
||||
|
||||
if ($extend != false) {
|
||||
return $extend;
|
||||
}
|
||||
}
|
||||
|
||||
throw ExpectationNotFound::fromName($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically calls methods on the class without any arguments or creates a new higher order expectation.
|
||||
*
|
||||
* @return Expectation<TValue>|OppositeExpectation<TValue>|EachExpectation<TValue>|HigherOrderExpectation<Expectation<TValue>, TValue|null>|TValue
|
||||
*/
|
||||
public function __get(string $name)
|
||||
{
|
||||
if (! self::hasMethod($name)) {
|
||||
/* @phpstan-ignore-next-line */
|
||||
return new HigherOrderExpectation($this, $this->retrieve($name, $this->value));
|
||||
}
|
||||
|
||||
/* @phpstan-ignore-next-line */
|
||||
return $this->{$name}();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given expectation method exists.
|
||||
*/
|
||||
public static function hasMethod(string $name): bool
|
||||
{
|
||||
return method_exists(self::class, $name)
|
||||
|| method_exists(Mixins\Expectation::class, $name)
|
||||
|| self::hasExtend($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches any value.
|
||||
*/
|
||||
public function any(): Any
|
||||
{
|
||||
return new Any();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given expectation target use the given dependencies.
|
||||
*
|
||||
* @param array<int, string>|string $targets
|
||||
*/
|
||||
public function toUse(array|string $targets): ArchExpectation
|
||||
{
|
||||
return ToUse::make($this, $targets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given expectation target "only" use on the given dependencies.
|
||||
*
|
||||
* @param array<int, string>|string $targets
|
||||
*/
|
||||
public function toOnlyUse(array|string $targets): ArchExpectation
|
||||
{
|
||||
return ToOnlyUse::make($this, $targets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given expectation target does not use any dependencies.
|
||||
*/
|
||||
public function toUseNothing(): ArchExpectation
|
||||
{
|
||||
return ToUseNothing::make($this);
|
||||
}
|
||||
|
||||
public function toBeUsed(): never
|
||||
{
|
||||
throw InvalidExpectation::fromMethods(['toBeUsed']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given expectation dependency is used by the given targets.
|
||||
*
|
||||
* @param array<int, string>|string $targets
|
||||
*/
|
||||
public function toBeUsedIn(array|string $targets): ArchExpectation
|
||||
{
|
||||
return ToBeUsedIn::make($this, $targets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given expectation dependency is "only" used by the given targets.
|
||||
*
|
||||
* @param array<int, string>|string $targets
|
||||
*/
|
||||
public function toOnlyBeUsedIn(array|string $targets): ArchExpectation
|
||||
{
|
||||
return ToOnlyBeUsedIn::make($this, $targets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given expectation dependency is not used.
|
||||
*/
|
||||
public function toBeUsedInNothing(): ArchExpectation
|
||||
{
|
||||
return ToBeUsedInNothing::make($this);
|
||||
}
|
||||
}
|
||||
83
src/Expectations/EachExpectation.php
Normal file
83
src/Expectations/EachExpectation.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Expectations;
|
||||
|
||||
use function expect;
|
||||
use Pest\Expectation;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @template TValue
|
||||
*
|
||||
* @mixin Expectation<TValue>
|
||||
*/
|
||||
final class EachExpectation
|
||||
{
|
||||
private bool $opposite = false;
|
||||
|
||||
/**
|
||||
* Creates an expectation on each item of the iterable "value".
|
||||
*
|
||||
* @param Expectation<TValue> $original
|
||||
*/
|
||||
public function __construct(private readonly Expectation $original)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new expectation.
|
||||
*
|
||||
* @template TAndValue
|
||||
*
|
||||
* @param TAndValue $value
|
||||
* @return Expectation<TAndValue>
|
||||
*/
|
||||
public function and(mixed $value): Expectation
|
||||
{
|
||||
return $this->original->and($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the opposite expectation for the value.
|
||||
*
|
||||
* @return self<TValue>
|
||||
*/
|
||||
public function not(): self
|
||||
{
|
||||
$this->opposite = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically calls methods on the class with the given arguments on each item.
|
||||
*
|
||||
* @param array<int|string, mixed> $arguments
|
||||
* @return self<TValue>
|
||||
*/
|
||||
public function __call(string $name, array $arguments): self
|
||||
{
|
||||
foreach ($this->original->value as $item) {
|
||||
/* @phpstan-ignore-next-line */
|
||||
$this->opposite ? expect($item)->not()->$name(...$arguments) : expect($item)->$name(...$arguments);
|
||||
}
|
||||
|
||||
$this->opposite = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically calls methods on the class without any arguments on each item.
|
||||
*
|
||||
* @return self<TValue>
|
||||
*/
|
||||
public function __get(string $name): self
|
||||
{
|
||||
/* @phpstan-ignore-next-line */
|
||||
return $this->$name();
|
||||
}
|
||||
}
|
||||
183
src/Expectations/HigherOrderExpectation.php
Normal file
183
src/Expectations/HigherOrderExpectation.php
Normal file
@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Expectations;
|
||||
|
||||
use Closure;
|
||||
use Pest\Concerns\Retrievable;
|
||||
use Pest\Expectation;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @template TOriginalValue
|
||||
* @template TValue
|
||||
*
|
||||
* @mixin Expectation<TOriginalValue>
|
||||
*/
|
||||
final class HigherOrderExpectation
|
||||
{
|
||||
use Retrievable;
|
||||
|
||||
/**
|
||||
* @var Expectation<TValue>|EachExpectation<TValue>
|
||||
*/
|
||||
private Expectation|EachExpectation $expectation;
|
||||
|
||||
private bool $opposite = false;
|
||||
|
||||
private bool $shouldReset = false;
|
||||
|
||||
/**
|
||||
* Creates a new higher order expectation.
|
||||
*
|
||||
* @param Expectation<TOriginalValue> $original
|
||||
* @param TValue $value
|
||||
*/
|
||||
public function __construct(private readonly Expectation $original, mixed $value)
|
||||
{
|
||||
$this->expectation = $this->expect($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the opposite expectation for the value.
|
||||
*
|
||||
* @return self<TOriginalValue, TValue>
|
||||
*/
|
||||
public function not(): self
|
||||
{
|
||||
$this->opposite = ! $this->opposite;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Expectation.
|
||||
*
|
||||
* @template TExpectValue
|
||||
*
|
||||
* @param TExpectValue $value
|
||||
* @return Expectation<TExpectValue>
|
||||
*/
|
||||
public function expect(mixed $value): Expectation
|
||||
{
|
||||
return new Expectation($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new expectation.
|
||||
*
|
||||
* @template TExpectValue
|
||||
*
|
||||
* @param TExpectValue $value
|
||||
* @return Expectation<TExpectValue>
|
||||
*/
|
||||
public function and(mixed $value): Expectation
|
||||
{
|
||||
return $this->expect($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope an expectation callback to the current value in
|
||||
* the HigherOrderExpectation chain.
|
||||
*
|
||||
* @param Closure(Expectation<TValue>): void $expectation
|
||||
* @return HigherOrderExpectation<TOriginalValue, TOriginalValue>
|
||||
*/
|
||||
public function scoped(Closure $expectation): self
|
||||
{
|
||||
$expectation->__invoke($this->expectation);
|
||||
|
||||
return new self($this->original, $this->original->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new expectation with the decoded JSON value.
|
||||
*
|
||||
* @return self<TOriginalValue, array<string|int, mixed>|bool>
|
||||
*/
|
||||
public function json(): self
|
||||
{
|
||||
return new self($this->original, $this->expectation->json()->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically calls methods on the class with the given arguments.
|
||||
*
|
||||
* @param array<int, mixed> $arguments
|
||||
* @return self<TOriginalValue, mixed>|self<TOriginalValue, TValue>
|
||||
*/
|
||||
public function __call(string $name, array $arguments): self
|
||||
{
|
||||
if (! $this->expectationHasMethod($name)) {
|
||||
/* @phpstan-ignore-next-line */
|
||||
return new self($this->original, $this->getValue()->$name(...$arguments));
|
||||
}
|
||||
|
||||
return $this->performAssertion($name, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accesses properties in the value or in the expectation.
|
||||
*
|
||||
* @return self<TOriginalValue, mixed>|self<TOriginalValue, TValue>
|
||||
*/
|
||||
public function __get(string $name): self
|
||||
{
|
||||
if ($name === 'not') {
|
||||
return $this->not();
|
||||
}
|
||||
|
||||
if (! $this->expectationHasMethod($name)) {
|
||||
/** @var array<string, mixed>|object $value */
|
||||
$value = $this->getValue();
|
||||
|
||||
return new self($this->original, $this->retrieve($name, $value));
|
||||
}
|
||||
|
||||
return $this->performAssertion($name, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the original expectation has the given method name.
|
||||
*/
|
||||
private function expectationHasMethod(string $name): bool
|
||||
{
|
||||
if (method_exists($this->original, $name)) {
|
||||
return true;
|
||||
}
|
||||
if ($this->original::hasMethod($name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->original::hasExtend($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the applicable value based on the current reset condition.
|
||||
*
|
||||
* @return TOriginalValue|TValue
|
||||
*/
|
||||
private function getValue(): mixed
|
||||
{
|
||||
return $this->shouldReset ? $this->original->value : $this->expectation->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the given assertion with the current expectation.
|
||||
*
|
||||
* @param array<int, mixed> $arguments
|
||||
* @return self<TOriginalValue, TValue>
|
||||
*/
|
||||
private function performAssertion(string $name, array $arguments): self
|
||||
{
|
||||
/* @phpstan-ignore-next-line */
|
||||
$this->expectation = ($this->opposite ? $this->expectation->not() : $this->expectation)->{$name}(...$arguments);
|
||||
|
||||
$this->opposite = false;
|
||||
$this->shouldReset = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
169
src/Expectations/OppositeExpectation.php
Normal file
169
src/Expectations/OppositeExpectation.php
Normal file
@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Expectations;
|
||||
|
||||
use Pest\Arch\Contracts\ArchExpectation;
|
||||
use Pest\Arch\Expectations\ToBeUsedIn;
|
||||
use Pest\Arch\Expectations\ToBeUsedInNothing;
|
||||
use Pest\Arch\Expectations\ToUse;
|
||||
use Pest\Arch\GroupArchExpectation;
|
||||
use Pest\Arch\SingleArchExpectation;
|
||||
use Pest\Exceptions\InvalidExpectation;
|
||||
use Pest\Expectation;
|
||||
use Pest\Support\Arr;
|
||||
use Pest\Support\Exporter;
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @template TValue
|
||||
*
|
||||
* @mixin Expectation<TValue>
|
||||
*/
|
||||
final class OppositeExpectation
|
||||
{
|
||||
/**
|
||||
* Creates a new opposite expectation.
|
||||
*
|
||||
* @param Expectation<TValue> $original
|
||||
*/
|
||||
public function __construct(private readonly Expectation $original)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the value array not has the provided $keys.
|
||||
*
|
||||
* @param array<int, int|string|array<int-string, mixed>> $keys
|
||||
* @return Expectation<TValue>
|
||||
*/
|
||||
public function toHaveKeys(array $keys): Expectation
|
||||
{
|
||||
foreach ($keys as $k => $key) {
|
||||
try {
|
||||
if (is_array($key)) {
|
||||
$this->toHaveKeys(array_keys(Arr::dot($key, $k.'.')));
|
||||
} else {
|
||||
$this->original->toHaveKey($key);
|
||||
}
|
||||
} catch (ExpectationFailedException) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->throwExpectationFailedException('toHaveKey', [$key]);
|
||||
}
|
||||
|
||||
return $this->original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given expectation target does not use any of the given dependencies.
|
||||
*
|
||||
* @param array<int, string>|string $targets
|
||||
*/
|
||||
public function toUse(array|string $targets): ArchExpectation
|
||||
{
|
||||
return GroupArchExpectation::fromExpectations($this->original, array_map(fn (string $target): SingleArchExpectation => ToUse::make($this->original, $target)->opposite(
|
||||
fn () => $this->throwExpectationFailedException('toUse', $target),
|
||||
), is_string($targets) ? [$targets] : $targets));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string>|string $targets
|
||||
*/
|
||||
public function toOnlyUse(array|string $targets): never
|
||||
{
|
||||
throw InvalidExpectation::fromMethods(['not', 'toOnlyUse']);
|
||||
}
|
||||
|
||||
public function toUseNothing(): never
|
||||
{
|
||||
throw InvalidExpectation::fromMethods(['not', 'toUseNothing']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given expectation dependency is not used.
|
||||
*/
|
||||
public function toBeUsed(): ArchExpectation
|
||||
{
|
||||
return ToBeUsedInNothing::make($this->original);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given expectation dependency is not used by any of the given targets.
|
||||
*
|
||||
* @param array<int, string>|string $targets
|
||||
*/
|
||||
public function toBeUsedIn(array|string $targets): ArchExpectation
|
||||
{
|
||||
return GroupArchExpectation::fromExpectations($this->original, array_map(fn (string $target): GroupArchExpectation => ToBeUsedIn::make($this->original, $target)->opposite(
|
||||
fn () => $this->throwExpectationFailedException('toBeUsedIn', $target),
|
||||
), is_string($targets) ? [$targets] : $targets));
|
||||
}
|
||||
|
||||
public function toOnlyBeUsedIn(): never
|
||||
{
|
||||
throw InvalidExpectation::fromMethods(['not', 'toOnlyBeUsedIn']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given expectation dependency is not used.
|
||||
*/
|
||||
public function toBeUsedInNothing(): never
|
||||
{
|
||||
throw InvalidExpectation::fromMethods(['not', 'toBeUsedInNothing']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle dynamic method calls into the original expectation.
|
||||
*
|
||||
* @param array<int, mixed> $arguments
|
||||
* @return Expectation<TValue>|Expectation<mixed>|never
|
||||
*/
|
||||
public function __call(string $name, array $arguments): Expectation
|
||||
{
|
||||
try {
|
||||
/* @phpstan-ignore-next-line */
|
||||
$this->original->{$name}(...$arguments);
|
||||
} catch (ExpectationFailedException) {
|
||||
return $this->original;
|
||||
}
|
||||
|
||||
$this->throwExpectationFailedException($name, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle dynamic properties gets into the original expectation.
|
||||
*
|
||||
* @return Expectation<TValue>|Expectation<mixed>|never
|
||||
*/
|
||||
public function __get(string $name): Expectation
|
||||
{
|
||||
try {
|
||||
$this->original->{$name}; // @phpstan-ignore-line
|
||||
} catch (ExpectationFailedException) { // @phpstan-ignore-line
|
||||
return $this->original;
|
||||
}
|
||||
|
||||
$this->throwExpectationFailedException($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new expectation failed exception with a nice readable message.
|
||||
*
|
||||
* @param array<int, mixed>|string $arguments
|
||||
*/
|
||||
public function throwExpectationFailedException(string $name, array|string $arguments = []): never
|
||||
{
|
||||
$arguments = is_array($arguments) ? $arguments : [$arguments];
|
||||
|
||||
$exporter = Exporter::default();
|
||||
|
||||
$toString = fn ($argument): string => $exporter->shortenedExport($argument);
|
||||
|
||||
throw new ExpectationFailedException(sprintf('Expecting %s not %s %s.', $toString($this->original->value), strtolower((string) preg_replace('/(?<!\ )[A-Z]/', ' $0', $name)), implode(' ', array_map(fn ($argument): string => $toString($argument), $arguments))));
|
||||
}
|
||||
}
|
||||
27
src/Factories/Annotations/CoversNothing.php
Normal file
27
src/Factories/Annotations/CoversNothing.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Factories\Annotations;
|
||||
|
||||
use Pest\Contracts\AddsAnnotations;
|
||||
use Pest\Factories\Covers\CoversNothing as CoversNothingFactory;
|
||||
use Pest\Factories\TestCaseMethodFactory;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class CoversNothing implements AddsAnnotations
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __invoke(TestCaseMethodFactory $method, array $annotations): array
|
||||
{
|
||||
if (($method->covers[0] ?? null) instanceof CoversNothingFactory) {
|
||||
$annotations[] = '@coversNothing';
|
||||
}
|
||||
|
||||
return $annotations;
|
||||
}
|
||||
}
|
||||
29
src/Factories/Annotations/Depends.php
Normal file
29
src/Factories/Annotations/Depends.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Factories\Annotations;
|
||||
|
||||
use Pest\Contracts\AddsAnnotations;
|
||||
use Pest\Factories\TestCaseMethodFactory;
|
||||
use Pest\Support\Str;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Depends implements AddsAnnotations
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __invoke(TestCaseMethodFactory $method, array $annotations): array
|
||||
{
|
||||
foreach ($method->depends as $depend) {
|
||||
$depend = Str::evaluable($depend);
|
||||
|
||||
$annotations[] = "@depends $depend";
|
||||
}
|
||||
|
||||
return $annotations;
|
||||
}
|
||||
}
|
||||
26
src/Factories/Annotations/Groups.php
Normal file
26
src/Factories/Annotations/Groups.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Factories\Annotations;
|
||||
|
||||
use Pest\Contracts\AddsAnnotations;
|
||||
use Pest\Factories\TestCaseMethodFactory;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Groups implements AddsAnnotations
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __invoke(TestCaseMethodFactory $method, array $annotations): array
|
||||
{
|
||||
foreach ($method->groups as $group) {
|
||||
$annotations[] = "@group $group";
|
||||
}
|
||||
|
||||
return $annotations;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user