This commit is contained in:
2026-09-09 15:07:58 +08:00
parent d656c05b3d
commit 71a0f6e404
31 changed files with 3657 additions and 9 deletions

View File

@@ -0,0 +1,94 @@
"""cache.py 本地层(不碰 RedisTTL 过期、容量淘汰、熔断冷却恢复。"""
from __future__ import annotations
import pytest
import app.cache as cache_mod
from app import cache
class _FakeClock:
"""替换 cache 命名空间里的 time不影响全局 time 模块)。"""
def __init__(self):
self.now = 1000.0
def monotonic(self) -> float:
return self.now
def time(self) -> float:
return self.now
@pytest.fixture(autouse=True)
def _reset_state():
"""每个用例干净的本地缓存状态。"""
cache._local_store.clear()
cache._local_bytes = 0
cache._disabled_until = 0.0
yield
cache._local_store.clear()
cache._local_bytes = 0
cache._disabled_until = 0.0
def test_local_set_get_roundtrip():
cache.local_set("k", '{"a":1}', ttl=60)
assert cache.local_get("k") == '{"a":1}'
def test_local_get_miss():
assert cache.local_get("nope") is None
def test_local_expiry(monkeypatch):
clock = _FakeClock()
monkeypatch.setattr(cache_mod, "time", clock)
cache.local_set("k", "v", ttl=10)
clock.now = 1005.0
assert cache.local_get("k") == "v"
clock.now = 1101.0 # 过期
assert cache.local_get("k") is None
assert "k" not in cache._local_store # 过期读取顺手清理
def test_local_ttl_capped_at_120s():
"""本地层恒 ≤120s多进程部署时最多比 Redis 多陈旧 120s 的约定)。"""
base = cache.time.monotonic()
cache.local_set("k", "v", ttl=99999)
ent = cache._local_store["k"]
assert ent[0] - base <= 120.0 + 5 # 相对当前 monotonic 的上限(留误差余量)
def test_local_entries_cap_evicts_oldest():
for i in range(cache._LOCAL_MAX_ENTRIES + 5):
cache.local_set(f"k{i}", "v", ttl=60)
assert len(cache._local_store) <= cache._LOCAL_MAX_ENTRIES
# 先插入的(最旧)被近似 LRU 淘汰
assert cache.local_get("k0") is None
assert cache.local_get(f"k{cache._LOCAL_MAX_ENTRIES + 4}") == "v"
def test_local_overwrite_releases_bytes():
cache.local_set("k", "x" * 1000, ttl=60)
before = cache._local_bytes
cache.local_set("k", "y", ttl=60)
assert cache._local_bytes < before
assert cache.local_get("k") == "y"
def test_bail_cooldown_recovers(monkeypatch):
"""熔断 60s期间 _client 为 None到期自动放行Redis 未配置时也返回 None但不熔断"""
clock = _FakeClock()
monkeypatch.setattr(cache_mod, "time", clock)
cache._bail()
assert cache._disabled_until > clock.now
clock.now += 59.0
assert clock.monotonic() < cache._disabled_until # 仍在熔断期
clock.now += 2.0 # 越过 60s 冷却
assert clock.monotonic() >= cache._disabled_until # 恢复探测资格
def test_digest_stable_and_distinct():
assert cache.digest("a", 1, None) == cache.digest("a", 1, None)
assert cache.digest("a", 1) != cache.digest("a", 2)