התסמין
אחרי ש-tests/test_cache_warming_job.py רץ באותו תהליך, כל קריאה ל-save_code_snippet נכשלת בשקט ומחזירה False:
db_save_code_snippet_error error="'_Cache' object has no attribute 'delete_pattern'"
הטסט שנפל בפועל הוא tests/test_created_at_preserved_across_versions.py::test_new_version_inherits_created_at, אבל אין לו שום קשר לבאג — הוא רק הראשון שמנסה לשמור.
שחזור
pytest tests/test_cache_warming_job.py tests/test_created_at_preserved_across_versions.py -p no:randomly
הקובץ השני עובר במלואו כשמריצים אותו לבד.
השורש — אומת בהרצה
tests/test_cache_warming_job.py:51 מזריק מודול מזויף ל-sys.modules:
class _Cache:
is_enabled = True
def set_dynamic(self, *a, **k):
return True
monkeypatch.setitem(importlib.sys.modules, "cache_manager", types.SimpleNamespace(cache=_Cache()))
ו-database/repository.py:35 מייבא את האובייקט ברמת המודול:
from cache_manager import cache as _cache_instance
cache: _CacheLike = cast(_CacheLike, _cache_instance)
אם database.repository מיובא לראשונה בזמן שהמזויף מותקן (והטסט אכן מייבא main, שמייבא database), השם repository.cache נקשר ל-_Cache לצמיתות. monkeypatch משחזר את sys.modules בסיום — אבל שם שכבר נקשר אינו מתעדכן.
הרצתי את המנגנון בבידוד:
ה-cache שנתפס ב-repository: _Cache
יש לו delete_pattern? False
אחרי ש-sys.modules שוחזר, repository עדיין מחזיק: _Cache
ואז save_code_snippet קורא ל-cache.delete_pattern(...) בשלב ה-invalidation, ה-AttributeError נתפס ב-except החיצוני, והשמירה מחזירה False.
למה ההגנה הקיימת לא תפסה
ההערה ב-database/repository.py:32 אומרת: "ייבוא חסין לאובייקט cache — הטסטים לעיתים ממקפים את המודול". ה-try/except שם אכן נכתב בדיוק מול הבעיה הזו — אבל הוא מגן מפני כשל ייבוא, ולא מפני ייבוא שמצליח ונקשר למוק חלקי.
היקף
- קיים מ-main, לא מ-PR פתוח. אימתתי: התסמין מופיע גם כשמחליפים את
database/repository.py לגרסה של origin/main.
- ה-CI ירוק כי בסדר הריצה שלו הצירוף לא נוצר. עם
pytest-randomly הסדר משתנה בין ריצות, ולכן זה מועמד לפליק אקראי.
- התסמין מטעה: ההודעה מצביעה על שמירה, והכשל האמיתי נמצא בקובץ טסט אחר לגמרי.
כיוונים אפשריים לתיקון
- לייבא את
cache בתוך הפונקציות שמשתמשות בו במקום ברמת המודול.
- להחזיק
import cache_manager ולגשת דרך cache_manager.cache, כך שהחלפת מודול תשפיע כצפוי.
- בטסט:
monkeypatch.setattr(cache_manager.cache, ...) על האטריביוטים הנחוצים במקום להחליף את המודול כולו — הסטאב יהיה שלם והבעיה לא תיווצר מלכתחילה.
הכיוון השלישי הוא היחיד שגם מונע את הישנות הדפוס בטסטים אחרים.
הפניה
זה TESTING-PATTERNS.md T3 בריפו amir-bug-patterns — "תשתית הבדיקות עצמה כמקור לכשל מבלבל", ובפרט הסעיף על mock עם state שאין לו reset ובדיקה שנכשלת רק בסדר מסוים.
התסמין
אחרי ש-
tests/test_cache_warming_job.pyרץ באותו תהליך, כל קריאה ל-save_code_snippetנכשלת בשקט ומחזירהFalse:הטסט שנפל בפועל הוא
tests/test_created_at_preserved_across_versions.py::test_new_version_inherits_created_at, אבל אין לו שום קשר לבאג — הוא רק הראשון שמנסה לשמור.שחזור
הקובץ השני עובר במלואו כשמריצים אותו לבד.
השורש — אומת בהרצה
tests/test_cache_warming_job.py:51מזריק מודול מזויף ל-sys.modules:ו-
database/repository.py:35מייבא את האובייקט ברמת המודול:אם
database.repositoryמיובא לראשונה בזמן שהמזויף מותקן (והטסט אכן מייבאmain, שמייבאdatabase), השםrepository.cacheנקשר ל-_Cacheלצמיתות.monkeypatchמשחזר אתsys.modulesבסיום — אבל שם שכבר נקשר אינו מתעדכן.הרצתי את המנגנון בבידוד:
ואז
save_code_snippetקורא ל-cache.delete_pattern(...)בשלב ה-invalidation, ה-AttributeErrorנתפס ב-exceptהחיצוני, והשמירה מחזירהFalse.למה ההגנה הקיימת לא תפסה
ההערה ב-
database/repository.py:32אומרת: "ייבוא חסין לאובייקט cache — הטסטים לעיתים ממקפים את המודול". ה-try/exceptשם אכן נכתב בדיוק מול הבעיה הזו — אבל הוא מגן מפני כשל ייבוא, ולא מפני ייבוא שמצליח ונקשר למוק חלקי.היקף
database/repository.pyלגרסה שלorigin/main.pytest-randomlyהסדר משתנה בין ריצות, ולכן זה מועמד לפליק אקראי.כיוונים אפשריים לתיקון
cacheבתוך הפונקציות שמשתמשות בו במקום ברמת המודול.import cache_managerולגשת דרךcache_manager.cache, כך שהחלפת מודול תשפיע כצפוי.monkeypatch.setattr(cache_manager.cache, ...)על האטריביוטים הנחוצים במקום להחליף את המודול כולו — הסטאב יהיה שלם והבעיה לא תיווצר מלכתחילה.הכיוון השלישי הוא היחיד שגם מונע את הישנות הדפוס בטסטים אחרים.
הפניה
זה
TESTING-PATTERNS.mdT3 בריפוamir-bug-patterns— "תשתית הבדיקות עצמה כמקור לכשל מבלבל", ובפרט הסעיף על mock עם state שאין לו reset ובדיקה שנכשלת רק בסדר מסוים.