Files
devplacepy/tests/unit/services/nodes/cache.py
T
retoor 6f80a820da
DevPlace CI / test (push) Failing after 3m13s
Zo doen wijdat in oost
2026-10-16 07:20:44 +02:00

110 lines
3.4 KiB
Python

# retoor <retoor@molodetz.nl>
import random
from devplacepy.services.nodes.cache import ResultCache, cache_key
from devplacepy.services.nodes.spec import SPEC
LIMITS = SPEC.limits
class Clock:
def __init__(self):
self.now = 100.0
def __call__(self):
return self.now
def make(**kwargs):
clock = Clock()
return ResultCache(clock=clock, **kwargs), clock
def test_hit_and_miss_counters():
cache, _ = make()
assert cache.get("GET", "u") is None
assert cache.put("GET", "u", 200, {"a": "b"}, b"xyz", False)
hit = cache.get("GET", "u")
assert hit.body == b"xyz" and hit.headers == {"a": "b"}
assert (cache.hits, cache.misses) == (1, 1)
def test_method_is_part_of_the_key():
assert cache_key("GET", "u") != cache_key("HEAD", "u")
assert cache_key("get", "u") == cache_key("GET", "u")
def test_entries_expire_after_the_ttl():
cache, clock = make(ttl_seconds=10)
cache.put("GET", "u", 200, {}, b"x", False)
clock.now += 9.9
assert cache.get("GET", "u") is not None
clock.now += 0.2
assert cache.get("GET", "u") is None
assert len(cache) == 0 and cache.size_bytes == 0
def test_ttl_is_clamped_to_the_spec_ceiling_and_zero_disables():
assert ResultCache(ttl_seconds=10**9).ttl_seconds == LIMITS.cache_ttl_seconds
off, _ = make(ttl_seconds=0)
assert off.put("GET", "u", 200, {}, b"x", False) is False
def test_errors_and_truncated_bodies_are_never_cached():
cache, _ = make()
assert not cache.put("GET", "u", 500, {}, b"x", False)
assert not cache.put("GET", "u", 503, {}, b"x", False)
assert not cache.put("GET", "u", 200, {}, b"x", True)
assert cache.put("GET", "u", 404, {}, b"x", False)
def test_an_oversized_entry_is_refused():
cache, _ = make(entry_max_bytes=10, max_bytes=100)
assert not cache.put("GET", "u", 200, {}, b"x" * 11, False)
assert cache.put("GET", "u", 200, {}, b"x" * 10, False)
def test_the_entry_cap_cannot_exceed_the_spec_ceiling():
assert ResultCache(entry_max_bytes=10**12).entry_max_bytes == LIMITS.cache_entry_max_bytes
def test_least_recently_used_goes_first():
cache, _ = make(max_bytes=30, entry_max_bytes=30)
for name in "abc":
cache.put("GET", name, 200, {}, b"x" * 10, False)
cache.get("GET", "a")
cache.put("GET", "d", 200, {}, b"x" * 10, False)
assert cache.get("GET", "b") is None
assert cache.get("GET", "a") is not None
assert cache.get("GET", "c") is not None
assert cache.get("GET", "d") is not None
def test_replacing_an_entry_does_not_leak_bytes():
cache, _ = make()
cache.put("GET", "u", 200, {}, b"x" * 100, False)
cache.put("GET", "u", 200, {}, b"y" * 40, False)
assert cache.size_bytes == 40 and len(cache) == 1
def test_size_never_exceeds_the_cap_under_a_random_workload():
cache, clock = make(max_bytes=1000, entry_max_bytes=400, ttl_seconds=50)
rng = random.Random(7)
for step in range(5000):
clock.now += rng.random()
key = f"k{rng.randint(0, 60)}"
if rng.random() < 0.7:
cache.put("GET", key, rng.choice([200, 404, 500]), {}, b"x" * rng.randint(0, 500), False)
else:
cache.get("GET", key)
assert 0 <= cache.size_bytes <= 1000
assert cache.size_bytes == sum(len(e.body) for e in cache._entries.values())
def test_clear_empties_everything():
cache, _ = make()
cache.put("GET", "u", 200, {}, b"x", False)
cache.clear()
assert len(cache) == 0 and cache.size_bytes == 0