|
@@ -0,0 +1,198 @@
|
|
|
+from tests import unittest
|
|
|
+from mock import patch
|
|
|
+
|
|
|
+from synapse.util.caches.stream_change_cache import StreamChangeCache
|
|
|
+
|
|
|
+
|
|
|
+class StreamChangeCacheTests(unittest.TestCase):
|
|
|
+ """
|
|
|
+ Tests for StreamChangeCache.
|
|
|
+ """
|
|
|
+
|
|
|
+ def test_prefilled_cache(self):
|
|
|
+ """
|
|
|
+ Providing a prefilled cache to StreamChangeCache will result in a cache
|
|
|
+ with the prefilled-cache entered in.
|
|
|
+ """
|
|
|
+ cache = StreamChangeCache("#test", 1, prefilled_cache={"user@foo.com": 2})
|
|
|
+ self.assertTrue(cache.has_entity_changed("user@foo.com", 1))
|
|
|
+
|
|
|
+ def test_has_entity_changed(self):
|
|
|
+ """
|
|
|
+ StreamChangeCache.entity_has_changed will mark entities as changed, and
|
|
|
+ has_entity_changed will observe the changed entities.
|
|
|
+ """
|
|
|
+ cache = StreamChangeCache("#test", 3)
|
|
|
+
|
|
|
+ cache.entity_has_changed("user@foo.com", 6)
|
|
|
+ cache.entity_has_changed("bar@baz.net", 7)
|
|
|
+
|
|
|
+ # If it's been changed after that stream position, return True
|
|
|
+ self.assertTrue(cache.has_entity_changed("user@foo.com", 4))
|
|
|
+ self.assertTrue(cache.has_entity_changed("bar@baz.net", 4))
|
|
|
+
|
|
|
+ # If it's been changed at that stream position, return False
|
|
|
+ self.assertFalse(cache.has_entity_changed("user@foo.com", 6))
|
|
|
+
|
|
|
+ # If there's no changes after that stream position, return False
|
|
|
+ self.assertFalse(cache.has_entity_changed("user@foo.com", 7))
|
|
|
+
|
|
|
+ # If the entity does not exist, return False.
|
|
|
+ self.assertFalse(cache.has_entity_changed("not@here.website", 7))
|
|
|
+
|
|
|
+ # If we request before the stream cache's earliest known position,
|
|
|
+ # return True, whether it's a known entity or not.
|
|
|
+ self.assertTrue(cache.has_entity_changed("user@foo.com", 0))
|
|
|
+ self.assertTrue(cache.has_entity_changed("not@here.website", 0))
|
|
|
+
|
|
|
+ @patch("synapse.util.caches.CACHE_SIZE_FACTOR", 1.0)
|
|
|
+ def test_has_entity_changed_pops_off_start(self):
|
|
|
+ """
|
|
|
+ StreamChangeCache.entity_has_changed will respect the max size and
|
|
|
+ purge the oldest items upon reaching that max size.
|
|
|
+ """
|
|
|
+ cache = StreamChangeCache("#test", 1, max_size=2)
|
|
|
+
|
|
|
+ cache.entity_has_changed("user@foo.com", 2)
|
|
|
+ cache.entity_has_changed("bar@baz.net", 3)
|
|
|
+ cache.entity_has_changed("user@elsewhere.org", 4)
|
|
|
+
|
|
|
+ # The cache is at the max size, 2
|
|
|
+ self.assertEqual(len(cache._cache), 2)
|
|
|
+
|
|
|
+ # The oldest item has been popped off
|
|
|
+ self.assertTrue("user@foo.com" not in cache._entity_to_key)
|
|
|
+
|
|
|
+ # If we update an existing entity, it keeps the two existing entities
|
|
|
+ cache.entity_has_changed("bar@baz.net", 5)
|
|
|
+ self.assertEqual(
|
|
|
+ set(["bar@baz.net", "user@elsewhere.org"]), set(cache._entity_to_key)
|
|
|
+ )
|
|
|
+
|
|
|
+ def test_get_all_entities_changed(self):
|
|
|
+ """
|
|
|
+ StreamChangeCache.get_all_entities_changed will return all changed
|
|
|
+ entities since the given position. If the position is before the start
|
|
|
+ of the known stream, it returns None instead.
|
|
|
+ """
|
|
|
+ cache = StreamChangeCache("#test", 1)
|
|
|
+
|
|
|
+ cache.entity_has_changed("user@foo.com", 2)
|
|
|
+ cache.entity_has_changed("bar@baz.net", 3)
|
|
|
+ cache.entity_has_changed("user@elsewhere.org", 4)
|
|
|
+
|
|
|
+ self.assertEqual(
|
|
|
+ cache.get_all_entities_changed(1),
|
|
|
+ ["user@foo.com", "bar@baz.net", "user@elsewhere.org"],
|
|
|
+ )
|
|
|
+ self.assertEqual(
|
|
|
+ cache.get_all_entities_changed(2), ["bar@baz.net", "user@elsewhere.org"]
|
|
|
+ )
|
|
|
+ self.assertEqual(cache.get_all_entities_changed(3), ["user@elsewhere.org"])
|
|
|
+ self.assertEqual(cache.get_all_entities_changed(0), None)
|
|
|
+
|
|
|
+ def test_has_any_entity_changed(self):
|
|
|
+ """
|
|
|
+ StreamChangeCache.has_any_entity_changed will return True if any
|
|
|
+ entities have been changed since the provided stream position, and
|
|
|
+ False if they have not. If the cache has entries and the provided
|
|
|
+ stream position is before it, it will return True, otherwise False if
|
|
|
+ the cache has no entries.
|
|
|
+ """
|
|
|
+ cache = StreamChangeCache("#test", 1)
|
|
|
+
|
|
|
+ # With no entities, it returns False for the past, present, and future.
|
|
|
+ self.assertFalse(cache.has_any_entity_changed(0))
|
|
|
+ self.assertFalse(cache.has_any_entity_changed(1))
|
|
|
+ self.assertFalse(cache.has_any_entity_changed(2))
|
|
|
+
|
|
|
+ # We add an entity
|
|
|
+ cache.entity_has_changed("user@foo.com", 2)
|
|
|
+
|
|
|
+ # With an entity, it returns True for the past, the stream start
|
|
|
+ # position, and False for the stream position the entity was changed
|
|
|
+ # on and ones after it.
|
|
|
+ self.assertTrue(cache.has_any_entity_changed(0))
|
|
|
+ self.assertTrue(cache.has_any_entity_changed(1))
|
|
|
+ self.assertFalse(cache.has_any_entity_changed(2))
|
|
|
+ self.assertFalse(cache.has_any_entity_changed(3))
|
|
|
+
|
|
|
+ def test_get_entities_changed(self):
|
|
|
+ """
|
|
|
+ StreamChangeCache.get_entities_changed will return the entities in the
|
|
|
+ given list that have changed since the provided stream ID. If the
|
|
|
+ stream position is earlier than the earliest known position, it will
|
|
|
+ return all of the entities queried for.
|
|
|
+ """
|
|
|
+ cache = StreamChangeCache("#test", 1)
|
|
|
+
|
|
|
+ cache.entity_has_changed("user@foo.com", 2)
|
|
|
+ cache.entity_has_changed("bar@baz.net", 3)
|
|
|
+ cache.entity_has_changed("user@elsewhere.org", 4)
|
|
|
+
|
|
|
+ # Query all the entries, but mid-way through the stream. We should only
|
|
|
+ # get the ones after that point.
|
|
|
+ self.assertEqual(
|
|
|
+ cache.get_entities_changed(
|
|
|
+ ["user@foo.com", "bar@baz.net", "user@elsewhere.org"], stream_pos=2
|
|
|
+ ),
|
|
|
+ set(["bar@baz.net", "user@elsewhere.org"]),
|
|
|
+ )
|
|
|
+
|
|
|
+ # Query all the entries mid-way through the stream, but include one
|
|
|
+ # that doesn't exist in it. We should get back the one that doesn't
|
|
|
+ # exist, too.
|
|
|
+ self.assertEqual(
|
|
|
+ cache.get_entities_changed(
|
|
|
+ [
|
|
|
+ "user@foo.com",
|
|
|
+ "bar@baz.net",
|
|
|
+ "user@elsewhere.org",
|
|
|
+ "not@here.website",
|
|
|
+ ],
|
|
|
+ stream_pos=2,
|
|
|
+ ),
|
|
|
+ set(["bar@baz.net", "user@elsewhere.org", "not@here.website"]),
|
|
|
+ )
|
|
|
+
|
|
|
+ # Query all the entries, but before the first known point. We will get
|
|
|
+ # all the entries we queried for, including ones that don't exist.
|
|
|
+ self.assertEqual(
|
|
|
+ cache.get_entities_changed(
|
|
|
+ [
|
|
|
+ "user@foo.com",
|
|
|
+ "bar@baz.net",
|
|
|
+ "user@elsewhere.org",
|
|
|
+ "not@here.website",
|
|
|
+ ],
|
|
|
+ stream_pos=0,
|
|
|
+ ),
|
|
|
+ set(
|
|
|
+ [
|
|
|
+ "user@foo.com",
|
|
|
+ "bar@baz.net",
|
|
|
+ "user@elsewhere.org",
|
|
|
+ "not@here.website",
|
|
|
+ ]
|
|
|
+ ),
|
|
|
+ )
|
|
|
+
|
|
|
+ def test_max_pos(self):
|
|
|
+ """
|
|
|
+ StreamChangeCache.get_max_pos_of_last_change will return the most
|
|
|
+ recent point where the entity could have changed. If the entity is not
|
|
|
+ known, the stream start is provided instead.
|
|
|
+ """
|
|
|
+ cache = StreamChangeCache("#test", 1)
|
|
|
+
|
|
|
+ cache.entity_has_changed("user@foo.com", 2)
|
|
|
+ cache.entity_has_changed("bar@baz.net", 3)
|
|
|
+ cache.entity_has_changed("user@elsewhere.org", 4)
|
|
|
+
|
|
|
+ # Known entities will return the point where they were changed.
|
|
|
+ self.assertEqual(cache.get_max_pos_of_last_change("user@foo.com"), 2)
|
|
|
+ self.assertEqual(cache.get_max_pos_of_last_change("bar@baz.net"), 3)
|
|
|
+ self.assertEqual(cache.get_max_pos_of_last_change("user@elsewhere.org"), 4)
|
|
|
+
|
|
|
+ # Unknown entities will return the stream start position.
|
|
|
+ self.assertEqual(cache.get_max_pos_of_last_change("not@here.website"), 1)
|