Caché compartida entre workers de pytest-xdist causaba tests no deterministas
Descripción
Cambio de caché en tests: Redis → LocMemCache. Soluciona una clase latente de tests no deterministas causada por cache.clear() en un worker borrando los contadores de otro cuando pytest-xdist reparte ficheros a procesos paralelos.
El Problema
Síntomas observados
En el PR #359 (04-08-2026), tras añadir tests/api/test_signup_gate.py:
- Fallo 1:
test_upload_mib_429(intermitente, rojo sin razón visible). - Fallo 2:
test_distinct_paths_independent(intermitente, mismo ciclo). - Causa: Tests de rate limit fallaban cuando el reparto de ficheros a workers cambiaba.
Root cause
En config/settings/test.py, la caché apuntaba a una instancia Redis compartida:
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": os.getenv("REDIS_URL", "redis://localhost:6379/1"),
"OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"},
}
}
Problema: Cuatro ficheros de tests hacen cache.clear():
tests/middleware/test_rate_limit_middleware.py(rate limit).tests/ui/test_ui_prefs.py(UI preferences).tests/monitoring/test_ratelimit_hygiene.py(monitoring).tests/core/test_auth_gate.py(auth).
Cuando pytest-xdist con --dist=loadfile reparte estos ficheros a workers paralelos:
- Worker A ejecuta
test_rate_limit_middleware.py, hacecache.clear(). - Borra el contador de rate limit de Worker B (ambos leen de Redis id=1).
- Worker B intenta validar
test_distinct_paths_independent→ contador inesperadamente cero → falla.
Timing no determinista
La víctima del cache.clear() cambia según el orden de reparto del scheduler de pytest-xdist:
- 50% de las veces: caen tests de monitoring.
- 25%: caen tests de auth.
- 25%: caen tests de rate limit.
Por eso el mismo run local funciona, pero en CI falla aleatoriamente.
La Solución
Cambiar en config/settings/test.py:
# ANTES: Redis compartida
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": os.getenv("REDIS_URL", "redis://localhost:6379/1"),
...
}
}
# AHORA: LocMemCache aislada por worker
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"LOCATION": "crearack-tests",
}
}
Por qué LocMemCache
- Aislamiento: Cada worker de pytest-xdist tiene su propio proceso Python → su propia memoria → su propia caché.
- No afecta a Channels:
CHANNEL_LAYERSsigue usando Redis real (comunic. entre procesos). - Determinismo: Los
cache.clear()afectan solo al worker actual. - Precedente:
SESSION_ENGINEya usadjango.contrib.sessions.backends.db(aislado por sesión, no por worker) por razón análoga.
Side effects
✅ Ninguno visible. Los contadores de rate limit ahora son fiables porque cada worker tiene el suyo.
⚠️ Nota: En PROD, CACHES sigue usando Redis (no cambia, este fix es solo para tests).
Validación
Reproducción local (con Redis real):
# ANTES de este fix:
pytest tests/api/test_signup_gate.py tests/middleware/test_rate_limit_middleware.py \
--dist=loadfile -n4 -v
# Resultado: FALLA 3/3 (siempre, porque redis/1 está compartida)
# DESPUÉS:
# (con LocMemCache en test.py)
# Resultado: VERDE 3/3
Citado en commit: “Reproducido 3/3 en local con Redis (cae test_upload_mib_429; en CI cayo test_distinct_paths_independent - misma causa, victima segun timing) y verde 3/3 con LocMemCache.”
Archivos afectados
config/settings/test.py: +13 LOC (comentario explicativo + cambio CACHES).
Véase también
- [[feature—auth—cierre-signup-allauth]]
- [[entity—core—middleware—rate-limit]]
- [[runbook—infra—pytest-xdist-parallelization]]
- [[concept—testing—determinism]]