جميع الملاحظات

، من Lilies

حدث Paper الذي يصل ملغى مسبقاً

لماذا تجاهلت سبع من معالجات إضافات Minecraft لدي كل نقرة في الهواء، ولماذا لم تلاحظ 5 اختبارات وحدة ناجحة ذلك.

في إعادة كتابة Lilies بلغة Java، كان من المفترض أن يؤدي النقر بزر الفأرة الأيمن على زهرة التوليب في الهواء إلى إيداعها. لكن لم يحدث شيء. كانت الوحدة تحتوي على خمسة اختبارات وحدة، ونجحت جميعها.

كيف كان شكل المعالج

@EventHandler(ignoreCancelled = true)
public void onUse(PlayerInteractEvent e) {
    if (e.getAction() != Action.RIGHT_CLICK_AIR) return;
    // bank the tulip ...
}

ignoreCancelled = true يُعد عادة شائعة في إضافات Bukkit: إذا ألغى امتداد آخر الحدث، يتم تخطيه. يبدو الأمر غير ضار. لكن هنا جعل المعالج بأكمله كوداً ميتاً لا يُنفَّذ أبداً.

لماذا لم يُنفَّذ أبداً

بالنسبة لـPlayerInteractEvent، فإن isCancelled() ليست علامة منفصلة. بل تُرجع القيمة true عندما تكون useInteractedBlock() مساوية لـ DENY. عند النقر في الهواء لا توجد كتلة، ويقوم Paper ببناء الحدث بقيمة DENY مضبوطة مسبقاً. لذلك تصل كل نقرة في الهواء ملغاة بالفعل، ولا يرى المعالج الذي يتجاهل الأحداث الملغاة أياً منها.

لم تتمكن اختبارات الوحدة من اكتشاف ذلك. فقد بنت الحدث يدوياً واستدعت دالة المعالج مباشرة، لذا لم يُلغَ الحدث أبداً ولم يُنفَّذ فحص ignoreCancelled على الإطلاق.

كيف تم اكتشاف ذلك

اختبار إقلاع (boot test). يشغّل خادماً حقيقياً مع تحميل كل ملفات jar الخاصة بالإضافات، ويطلق أحداثاً حقيقية عبر مدير الإضافات، بنفس المسار الذي تسلكه نقرة اللاعب. فشل من أول تشغيل. وبالبحث في بقية مجموعة الاختبارات، وُجدت نفس العلامة في سبعة معالجات، من بينها المعالج الذي يلتقط إسقاطات المال.

الإصلاح

@EventHandler
public void onUse(PlayerInteractEvent e) {
    if (e.getAction() == Action.RIGHT_CLICK_BLOCK && e.isCancelled()) return;
    // ...
}

احذف العلامة وقل بالضبط ما تقصده. نقرة الكتلة المرفوضة لا تزال لا تفعل شيئاً، بينما تمر نقرات الهواء.

الدرس المستفاد

الاختبار الذي يستدعي المعالج مباشرة يتخطى كل ما يقوم به الإطار قبل تشغيل المعالج. احتفظ بما لا يقل عن اختبار واحد يمر عبر الباب الأمامي الحقيقي.