Skip to content

Commit 2c3cd28

Browse files
committed
Improve IndividualCache test cases
1 parent 29d1ac1 commit 2c3cd28

File tree

2 files changed

+31
-10
lines changed

2 files changed

+31
-10
lines changed

msal/individual_cache.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ def __init__(self, mapping=None, capacity=None, expires_in=None, lock=None,
5959
self._expires_in = expires_in
6060
self._lock = Lock() if lock is None else lock
6161

62+
def _peek(self):
63+
# Returns (sequence, timestamps) without triggering maintenance
64+
return self._mapping.get(self._INDEX, ([], {}))
65+
6266
def _validate_key(self, key):
6367
if key == self._INDEX:
6468
raise ValueError("key {} is a reserved keyword in {}".format(
@@ -85,7 +89,7 @@ def _set(self, key, value, expires_in):
8589
# This internal implementation powers both set() and __setitem__(),
8690
# so that they don't depend on each other.
8791
self._validate_key(key)
88-
sequence, timestamps = self._mapping.get(self._INDEX, ([], {}))
92+
sequence, timestamps = self._peek()
8993
self._maintenance(sequence, timestamps) # O(logN)
9094
now = int(time.time())
9195
expires_at = now + expires_in
@@ -136,7 +140,7 @@ def __getitem__(self, key): # O(1)
136140
self._validate_key(key)
137141
with self._lock:
138142
# Skip self._maintenance(), because it would need O(logN) time
139-
sequence, timestamps = self._mapping.get(self._INDEX, ([], {}))
143+
sequence, timestamps = self._peek()
140144
expires_at, created_at = timestamps[key] # Would raise KeyError accordingly
141145
now = int(time.time())
142146
if not created_at <= now < expires_at:
@@ -155,22 +159,22 @@ def __delitem__(self, key): # O(1)
155159
with self._lock:
156160
# Skip self._maintenance(), because it would need O(logN) time
157161
self._mapping.pop(key, None) # O(1)
158-
sequence, timestamps = self._mapping.get(self._INDEX, ([], {}))
162+
sequence, timestamps = self._peek()
159163
del timestamps[key] # O(1)
160164
self._mapping[self._INDEX] = sequence, timestamps
161165

162166
def __len__(self): # O(logN)
163167
"""Drop all expired items and return the remaining length"""
164168
with self._lock:
165-
sequence, timestamps = self._mapping.get(self._INDEX, ([], {}))
169+
sequence, timestamps = self._peek()
166170
self._maintenance(sequence, timestamps) # O(logN)
167171
self._mapping[self._INDEX] = sequence, timestamps
168172
return len(timestamps) # Faster than iter(self._mapping) when it is on disk
169173

170174
def __iter__(self):
171175
"""Drop all expired items and return an iterator of the remaining items"""
172176
with self._lock:
173-
sequence, timestamps = self._mapping.get(self._INDEX, ([], {}))
177+
sequence, timestamps = self._peek()
174178
self._maintenance(sequence, timestamps) # O(logN)
175179
self._mapping[self._INDEX] = sequence, timestamps
176180
return iter(timestamps) # Faster than iter(self._mapping) when it is on disk

tests/test_individual_cache.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@
88
class TestExpiringMapping(unittest.TestCase):
99
def setUp(self):
1010
self.mapping = {}
11-
self.m = ExpiringMapping(mapping=self.mapping, capacity=2, expires_in=1)
11+
self.expires_in = 1
12+
self.m = ExpiringMapping(
13+
mapping=self.mapping, capacity=2, expires_in=self.expires_in)
14+
15+
def how_many(self):
16+
# This helper checks how many items are in the mapping, WITHOUT triggering purge
17+
return len(self.m._peek()[1])
1218

1319
def test_should_disallow_accessing_reserved_keyword(self):
1420
with self.assertRaises(ValueError):
@@ -40,11 +46,21 @@ def test_iter_should_purge(self):
4046
sleep(1)
4147
self.assertEqual([], list(self.m))
4248

43-
def test_get_should_purge(self):
49+
def test_get_should_not_purge_and_should_return_only_when_the_item_is_still_valid(self):
4450
self.m["thing one"] = "one"
51+
self.m["thing two"] = "two"
4552
sleep(1)
53+
self.assertEqual(2, self.how_many(), "We begin with 2 items")
4654
with self.assertRaises(KeyError):
4755
self.m["thing one"]
56+
self.assertEqual(1, self.how_many(), "get() should not purge the remaining items")
57+
58+
def test_setitem_should_purge(self):
59+
self.m["thing one"] = "one"
60+
sleep(1)
61+
self.m["thing two"] = "two"
62+
self.assertEqual(1, self.how_many(), "setitem() should purge all expired items")
63+
self.assertEqual("two", self.m["thing two"], "The remaining item should be thing two")
4864

4965
def test_various_expiring_time(self):
5066
self.assertEqual(0, len(self.m))
@@ -57,12 +73,13 @@ def test_various_expiring_time(self):
5773
def test_old_item_can_be_updated_with_new_expiry_time(self):
5874
self.assertEqual(0, len(self.m))
5975
self.m["thing"] = "one"
60-
self.m.set("thing", "two", 2)
76+
new_lifetime = 3 # 2-second seems too short and causes flakiness
77+
self.m.set("thing", "two", new_lifetime)
6178
self.assertEqual(1, len(self.m), "It contains 1 item")
6279
self.assertEqual("two", self.m["thing"], 'Already been updated to "two"')
63-
sleep(1)
80+
sleep(self.expires_in)
6481
self.assertEqual("two", self.m["thing"], "Not yet expires")
65-
sleep(1)
82+
sleep(new_lifetime - self.expires_in)
6683
self.assertEqual(0, len(self.m))
6784

6885
def test_oversized_input_should_purge_most_aging_item(self):

0 commit comments

Comments
 (0)