async def test_pubsub_numsub(self, r: redis.Redis): p1 = r.pubsub() await p1.subscribe("foo", "bar", "baz") for i in range(3): assert (await wait_for_message(p1))["type"] == "subscribe" p2 = r.pubsub() await p2.subscribe("bar", "baz") for i in range(2): assert (await wait_for_message(p2))["type"] == "subscribe" p3 = r.pubsub() await p3.subscribe("baz") assert (await wait_for_message(p3))["type"] == "subscribe" channels = [(b"foo", 1), (b"bar", 2), (b"baz", 3)] assert await r.pubsub_numsub("foo", "bar", "baz") == channels
async def test_late_subscribe(self, r: redis.Redis): def callback(message): messages.put_nowait(message) messages = asyncio.Queue() p = r.pubsub() task = asyncio.get_event_loop().create_task(p.run()) # wait until loop gets settled. Add a subscription await asyncio.sleep(0.1) await p.subscribe(foo=callback) # wait tof the subscribe to finish. Cannot use _subscribe() because # p.run() is already accepting messages await asyncio.sleep(0.1) await r.publish("foo", "bar") message = None try: async with async_timeout.timeout(0.1): message = await messages.get() except asyncio.TimeoutError: pass task.cancel() # we expect a cancelled error, not the Runtime error # ("did you forget to call subscribe()"") with pytest.raises(asyncio.CancelledError): await task assert message == { "channel": b"foo", "data": b"bar", "pattern": None, "type": "message", }
async def test_send_pubsub_ping_message(self, r: redis.Redis): p = r.pubsub(ignore_subscribe_messages=True) await p.subscribe("foo") await p.ping(message="hello world") assert await wait_for_message(p) == make_message( type="pong", channel=None, data="hello world", pattern=None )
async def test_pubsub_channels(self, r: redis.Redis): p = r.pubsub() await p.subscribe("foo", "bar", "baz", "quux") for i in range(4): assert (await wait_for_message(p))["type"] == "subscribe" expected = [b"bar", b"baz", b"foo", b"quux"] assert all([channel in await r.pubsub_channels() for channel in expected])
async def test_get_message_without_subscribe(self, r: redis.Redis): p = r.pubsub() with pytest.raises(RuntimeError) as info: await p.get_message() expect = ("connection not set: " "did you forget to call subscribe() or psubscribe()?") assert expect in info.exconly()
async def test_channel_message_handler(self, r: redis.Redis): p = r.pubsub(ignore_subscribe_messages=True) await p.subscribe(foo=self.message_handler) assert await wait_for_message(p) is None assert await r.publish("foo", "test message") == 1 assert await wait_for_message(p) is None assert self.message == make_message("message", "foo", "test message")
async def test_channel_publish(self, r: redis.Redis): p = r.pubsub() await p.subscribe(self.channel) assert await wait_for_message(p) == self.make_message( "subscribe", self.channel, 1) await r.publish(self.channel, self.data) assert await wait_for_message(p) == self.make_message( "message", self.channel, self.data)
async def test_pattern_publish(self, r: redis.Redis): p = r.pubsub() await p.psubscribe(self.pattern) assert await wait_for_message(p) == self.make_message( "psubscribe", self.pattern, 1) await r.publish(self.channel, self.data) assert await wait_for_message(p) == self.make_message( "pmessage", self.channel, self.data, pattern=self.pattern)
async def test_context_manager(self, r: redis.Redis): async with r.pubsub() as pubsub: await pubsub.subscribe("foo") assert pubsub.connection is not None assert pubsub.connection is None assert pubsub.channels == {} assert pubsub.patterns == {}
async def test_connection_error_raised_when_connection_dies(self, r: redis.Redis): p = r.pubsub() await p.subscribe("foo") assert await wait_for_message(p) == make_message("subscribe", "foo", 1) for client in await r.client_list(): if client["cmd"] == "subscribe": await r.client_kill_filter(_id=client["id"]) with pytest.raises(ConnectionError): await wait_for_message(p)
async def test_pattern_message_handler(self, r: redis.Redis): p = r.pubsub(ignore_subscribe_messages=True) await p.psubscribe(**{"f*": self.message_handler}) assert await wait_for_message(p) is None assert await r.publish("foo", "test message") == 1 assert await wait_for_message(p) is None assert self.message == make_message( "pmessage", "foo", "test message", pattern="f*" )
async def test_channel_subscribe_unsubscribe(self, r: redis.Redis): p = r.pubsub() await p.subscribe(self.channel) assert await wait_for_message(p) == self.make_message( "subscribe", self.channel, 1) await p.unsubscribe(self.channel) assert await wait_for_message(p) == self.make_message( "unsubscribe", self.channel, 0)
async def test_unicode_channel_message_handler(self, r: redis.Redis): p = r.pubsub(ignore_subscribe_messages=True) channel = "uni" + chr(4456) + "code" channels = {channel: self.message_handler} await p.subscribe(**channels) assert await wait_for_message(p) is None assert await r.publish(channel, "test message") == 1 assert await wait_for_message(p) is None assert self.message == make_message("message", channel, "test message")
async def test_published_message_to_channel(self, r: redis.Redis): p = r.pubsub() await p.subscribe("foo") assert await wait_for_message(p) == make_message("subscribe", "foo", 1) assert await r.publish("foo", "test message") == 1 message = await wait_for_message(p) assert isinstance(message, dict) assert message == make_message("message", "foo", "test message")
async def test_pattern_subscribe_unsubscribe(self, r: redis.Redis): p = r.pubsub() await p.psubscribe(self.pattern) assert await wait_for_message(p) == self.make_message( "psubscribe", self.pattern, 1) await p.punsubscribe(self.pattern) assert await wait_for_message(p) == self.make_message( "punsubscribe", self.pattern, 0)
async def test_unicode_pattern_message_handler(self, r: redis.Redis): p = r.pubsub(ignore_subscribe_messages=True) pattern = "uni" + chr(4456) + "*" channel = "uni" + chr(4456) + "code" await p.psubscribe(**{pattern: self.message_handler}) assert await wait_for_message(p) is None assert await r.publish(channel, "test message") == 1 assert await wait_for_message(p) is None assert self.message == make_message( "pmessage", channel, "test message", pattern=pattern )
async def test_channel_message_handler(self, r: redis.Redis): p = r.pubsub(ignore_subscribe_messages=True) await p.subscribe(**{self.channel: self.message_handler}) assert await wait_for_message(p) is None await r.publish(self.channel, self.data) assert await wait_for_message(p) is None assert self.message == self.make_message("message", self.channel, self.data) # test that we reconnected to the correct channel self.message = None await p.connection.disconnect() assert await wait_for_message(p) is None # should reconnect new_data = self.data + "new data" await r.publish(self.channel, new_data) assert await wait_for_message(p) is None assert self.message == self.make_message("message", self.channel, new_data)
async def test_ignore_all_subscribe_messages(self, r: redis.Redis): p = r.pubsub(ignore_subscribe_messages=True) checks = ( (p.subscribe, "foo"), (p.unsubscribe, "foo"), (p.psubscribe, "f*"), (p.punsubscribe, "f*"), ) assert p.subscribed is False for func, channel in checks: assert await func(channel) is None assert p.subscribed is True assert await wait_for_message(p) is None assert p.subscribed is False
async def test_exception_handler(self, r: redis.Redis): def exception_handler_callback(e, pubsub) -> None: assert pubsub == p exceptions.put_nowait(e) exceptions = asyncio.Queue() p = r.pubsub() await self._subscribe(p, foo=lambda x: None) with mock.patch.object(p, "get_message", side_effect=Exception("error")): task = asyncio.get_event_loop().create_task( p.run(exception_handler=exception_handler_callback) ) e = await exceptions.get() task.cancel() try: await task except asyncio.CancelledError: pass assert str(e) == "error"
async def test_callbacks(self, r: redis.Redis): def callback(message): messages.put_nowait(message) messages = asyncio.Queue() p = r.pubsub() await self._subscribe(p, foo=callback) task = asyncio.get_event_loop().create_task(p.run()) await r.publish("foo", "bar") message = await messages.get() task.cancel() try: await task except asyncio.CancelledError: pass assert message == { "channel": b"foo", "data": b"bar", "pattern": None, "type": "message", }
async def test_published_message_to_pattern(self, r: redis.Redis): p = r.pubsub() await p.subscribe("foo") await p.psubscribe("f*") assert await wait_for_message(p) == make_message("subscribe", "foo", 1) assert await wait_for_message(p) == make_message("psubscribe", "f*", 2) # 1 to pattern, 1 to channel assert await r.publish("foo", "test message") == 2 message1 = await wait_for_message(p) message2 = await wait_for_message(p) assert isinstance(message1, dict) assert isinstance(message2, dict) expected = [ make_message("message", "foo", "test message"), make_message("pmessage", "foo", "test message", pattern="f*"), ] assert message1 in expected assert message2 in expected assert message1 != message2
async def test_resubscribe_to_patterns_on_reconnection( self, r: redis.Redis): kwargs = make_subscribe_test_data(r.pubsub(), "pattern") await self._test_resubscribe_on_reconnection(**kwargs)
async def test_get_message_with_timeout_returns_none(self, r: redis.Redis): p = r.pubsub() await p.subscribe("foo") assert await wait_for_message(p) == make_message("subscribe", "foo", 1) assert await p.get_message(timeout=0.01) is None
async def test_pubsub_numpat(self, r: redis.Redis): p = r.pubsub() await p.psubscribe("*oo", "*ar", "b*z") for i in range(3): assert (await wait_for_message(p))["type"] == "psubscribe" assert await r.pubsub_numpat() == 3
async def test_channel_subscribe(self, r: redis.Redis): r = redis.Redis(host="localhost", port=6390) p = r.pubsub() with pytest.raises(ConnectionError): await p.subscribe("foo")
async def test_reconnect_listen(self, r: redis.Redis): """ Test that a loop processing PubSub messages can survive a disconnect, by issuing a connect() call. """ messages = asyncio.Queue() pubsub = r.pubsub() interrupt = False async def loop(): # must make sure the task exits async with async_timeout.timeout(2): nonlocal interrupt await pubsub.subscribe("foo") while True: # print("loop") try: try: await pubsub.connect() await loop_step() # print("succ") except redis.ConnectionError: await asyncio.sleep(0.1) except asyncio.CancelledError: # we use a cancel to interrupt the "listen" # when we perform a disconnect # print("cancel", interrupt) if interrupt: interrupt = False else: raise async def loop_step(): # get a single message via listen() async for message in pubsub.listen(): await messages.put(message) break task = asyncio.get_event_loop().create_task(loop()) # get the initial connect message async with async_timeout.timeout(1): message = await messages.get() assert message == { "channel": b"foo", "data": 1, "pattern": None, "type": "subscribe", } # now, disconnect the connection. await pubsub.connection.disconnect() interrupt = True task.cancel() # interrupt the listen call # await another auto-connect message message = await messages.get() assert message == { "channel": b"foo", "data": 1, "pattern": None, "type": "subscribe", } task.cancel() with pytest.raises(asyncio.CancelledError): await task
async def test_subscribe_property_with_channels(self, r: redis.Redis): kwargs = make_subscribe_test_data(r.pubsub(), "channel") await self._test_subscribed_property(**kwargs)
async def test_channel_subscribe_unsubscribe(self, r: redis.Redis): kwargs = make_subscribe_test_data(r.pubsub(), "channel") await self._test_subscribe_unsubscribe(**kwargs)
async def test_pattern_subscribe_unsubscribe(self, r: redis.Redis): kwargs = make_subscribe_test_data(r.pubsub(), "pattern") await self._test_subscribe_unsubscribe(**kwargs)
async def test_subscribe_property_with_patterns(self, r: redis.Redis): kwargs = make_subscribe_test_data(r.pubsub(), "pattern") await self._test_subscribed_property(**kwargs)