Fuzz: فاز تستینگ رو میبره داخل Pest ۵
خلاصهٔ کاملتر
تستی که برای یه parser مینویسی معمولاً یه رشتهٔ درست رو چک میکنه و یه رشتهٔ خالی. فاز تستینگ (یعنی ساختن خودکار ورودی و دادنش به کد تا جایی که بشکنه) دنبال خرابیهاییه که بیرون همین چند حالت دستیان. نویسنده میگه با پکیج Fuzz از Jon Purvis میتونی این کارو همونجا داخل یه تست Pest 5 انجام بدی؛ پکیج از PHP-Fuzzer نوشتهٔ nikic استفاده میکنه و خرابیها رو از طریق Pest گزارش میده.
فازر ورودی رو با اضافه، حذف یا جایگزینکردن بخشهایی از رشته عوض میکنه. حالت coverage-guided یه قدم جلوتره: حواسش هست هر ورودی از چه مسیری توی کد رد میشه و اگه ورودیای به مسیری برسه که قبلاً دیده نشده، اونو نگه میداره و ازش نسخههای تازه میسازه. به این مجموعهٔ ذخیرهشده میگن corpus. مثال مقاله روشنه: اگه parser اول دنبال براکت باز بگرده، بیشتر ورودیهای تصادفی همونجا رد میشن، ولی همین که یکی از سد اول رد شه فازر میتونه روی همون ادامه بده.
خبر خوب اینه که برای این فیدبک نه به Xdebug نیاز داری و نه به گزینهٔ --coverage خود Pest؛ PHP-Fuzzer خودش جابهجایی بین بلوکهای کد رو ردیابی میکنه. تو فقط تعیین میکنی کدوم کد صدا زده بشه و چقدر بودجهٔ اجرا داشته باشه.
fuzz($target)
->seed(['100/60s', '5/1s', '1000/3600s'])
->withDictionary(['/', 's', '0', '1'])
->runs(2000)
->maxLen(16)
->run('rate-limit-parser');رشتههای seed() نمونههای شروع هستن، withDictionary() تکههایی رو میده که فازر میتونه وسط ورودی بچپونه (بدون اینکه محدود به همونها بشه)، runs(2000) بودجهٔ جستوجوئه و maxLen(16) طول ورودی ساختهشده رو محدود میکنه. توی اجرای مقاله، فازر روی یه helper ساده که رشتهای مثل 100/60s رو به «تعداد درخواست در ثانیه» تبدیل میکنه، ورودی 5/ رو پیدا کرد: پنجرهٔ زمانی خالی میمونه، PHP اونو صفر در نظر میگیره و تقسیم بر صفر DivisionByZeroError میده. ورودی خراب هم بهصورت پیشفرض توی .pest/fuzz-crashes/ ذخیره میشه تا بتونی بازتولیدش کنی.
یه نکتهٔ عملی که نویسنده تأکید میکنه: تابع هدف رو بیرون از test() تعریف کن. به گفتهٔ نویسنده، توی بررسی خودشون روی Fuzz v1.0.1 همین شکل coverage ثبت میکرد ولی پاسدادن مستقیم Closure::fromCallable() هیچی ثبت نمیکرد، چون Fuzz تابع رو توی یه پروسهٔ PHP جدا اجرا میکنه و کلاس تستی که Pest میسازه اونجا در دسترس نیست.
Fuzz خطاهایی مثل TypeError و هشدارها و نوتیسهای سرکوبنشدهٔ PHP رو گزارش میکنه، ولی اکسپشنهای معمولی از جمله اکسپشنهای اعتبارسنجی لاراول پیشفرض نادیده گرفته میشن و با allow() میتونی این لیست رو تنگتر کنی. برای تایماوت هر ورودی هم timeout() هست که اکستنشن pcntl میخواد. توصیهٔ نویسنده اینه که تستها و دیتاستهای معمولی رو برای حالتهای شناختهشده نگه داری و فاز رو با بودجهٔ کم توی سوییت عادی بذاری و اجرای طولانیتر رو به یه جاب زمانبندیشدهٔ CI بسپری.
نکات کلیدی:
- Fuzz به PHP 8.4 و Pest 5 نیاز داره و با composer require jonpurvis/fuzz --dev نصب میشه
- coverage-guided یعنی ورودیهایی که مسیر تازهای از کد رو باز میکنن توی corpus ذخیره میشن
- بدون Xdebug و بدون --coverage کار میکنه؛ ردیابی رو خود PHP-Fuzzer انجام میده
- seed() نمونهٔ شروع، withDictionary() تکههای قابل درج، runs() بودجه و maxLen() سقف طول ورودیه
- ورودیهای خراب پیشفرض توی .pest/fuzz-crashes/ ذخیره میشن
- تابع هدف باید بیرون از test() تعریف شه، چون Fuzz توی یه پروسهٔ PHP جداگانه اجراش میکنه
- اکسپشنهای معمولی پیشفرض نادیده گرفته میشن؛ allow() و timeout() (نیازمند pcntl) رفتار رو تنظیم میکنن




