-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_cache.py
More file actions
150 lines (108 loc) · 4.32 KB
/
Copy pathtest_cache.py
File metadata and controls
150 lines (108 loc) · 4.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import threading
import time
from agentscore_commerce.identity.cache import TTLCache
def test_get_returns_none_for_missing_key():
cache: TTLCache[str] = TTLCache(default_ttl_seconds=60)
assert cache.get("nonexistent") is None
def test_set_and_get():
cache: TTLCache[int] = TTLCache(default_ttl_seconds=60)
cache.set("key", 42)
assert cache.get("key") == 42
def test_entry_expires(monkeypatch):
real_monotonic = time.monotonic
cache: TTLCache[str] = TTLCache(default_ttl_seconds=1)
cache.set("key", "value")
assert cache.get("key") == "value"
# Fast-forward time by patching monotonic.
base = real_monotonic()
monkeypatch.setattr(time, "monotonic", lambda: base + 2)
assert cache.get("key") is None
def test_custom_ttl(monkeypatch):
real_monotonic = time.monotonic
cache: TTLCache[str] = TTLCache(default_ttl_seconds=60)
cache.set("key", "value", ttl=1)
base = real_monotonic()
monkeypatch.setattr(time, "monotonic", lambda: base + 2)
assert cache.get("key") is None
def test_overwrite_value():
cache: TTLCache[str] = TTLCache(default_ttl_seconds=60)
cache.set("key", "a")
cache.set("key", "b")
assert cache.get("key") == "b"
def test_max_size_evicts_oldest():
"""When cache exceeds max_size, oldest entries (by expiry) are evicted."""
cache: TTLCache[str] = TTLCache(default_ttl_seconds=60, max_size=5)
# Fill cache to capacity
for i in range(5):
cache.set(f"key-{i}", f"value-{i}")
# All 5 entries should be present
for i in range(5):
assert cache.get(f"key-{i}") == f"value-{i}"
# Adding a 6th entry should evict the oldest (key-0, earliest expiry)
cache.set("key-5", "value-5")
assert cache.get("key-5") == "value-5"
# key-0 should have been evicted (it had the earliest expiry)
assert cache.get("key-0") is None
# Remaining keys should still be present
for i in range(1, 6):
assert cache.get(f"key-{i}") == f"value-{i}"
def test_max_size_sweeps_expired_first(monkeypatch):
"""Expired entries are swept before evicting by age."""
real_monotonic = time.monotonic
cache: TTLCache[str] = TTLCache(default_ttl_seconds=60, max_size=5)
# Add 5 entries, 2 with short TTL
cache.set("short-1", "v", ttl=1)
cache.set("short-2", "v", ttl=1)
cache.set("long-1", "v")
cache.set("long-2", "v")
cache.set("long-3", "v")
# Advance time so short-TTL entries expire
base = real_monotonic()
monkeypatch.setattr(time, "monotonic", lambda: base + 2)
# Adding a new entry should sweep expired entries, not evict long-lived ones
cache.set("new", "v")
assert cache.get("new") == "v"
assert cache.get("long-1") == "v"
assert cache.get("long-2") == "v"
assert cache.get("long-3") == "v"
def test_overwrite_resets_ttl(monkeypatch):
real_monotonic = time.monotonic
cache: TTLCache[str] = TTLCache(default_ttl_seconds=2)
cache.set("key", "first")
base = real_monotonic()
monkeypatch.setattr(time, "monotonic", lambda: base + 1.5)
cache.set("key", "second")
monkeypatch.setattr(time, "monotonic", lambda: base + 3.0)
assert cache.get("key") == "second"
def test_concurrent_access():
"""Concurrent reads and writes should not corrupt cache state."""
cache: TTLCache[int] = TTLCache(default_ttl_seconds=60)
errors: list[Exception] = []
num_threads = 10
ops_per_thread = 200
def writer(thread_id: int) -> None:
try:
for i in range(ops_per_thread):
cache.set(f"key-{thread_id}-{i}", i)
except Exception as exc:
errors.append(exc)
def reader(thread_id: int) -> None:
try:
for i in range(ops_per_thread):
cache.get(f"key-{thread_id}-{i}")
except Exception as exc:
errors.append(exc)
threads = []
for t in range(num_threads):
threads.append(threading.Thread(target=writer, args=(t,)))
threads.append(threading.Thread(target=reader, args=(t,)))
for t in threads:
t.start()
for t in threads:
t.join()
assert errors == [], f"Concurrent access caused errors: {errors}"
# Verify all written values are readable.
for t in range(num_threads):
for i in range(ops_per_thread):
val = cache.get(f"key-{t}-{i}")
assert val == i