Skip to content

Commit 83d7825

Browse files
committed
Add mock-based tests for passing BaseException through
1 parent a478be2 commit 83d7825

File tree

2 files changed

+103
-22
lines changed

2 files changed

+103
-22
lines changed

tests/test_asyncio/test_pubsub.py

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import functools
33
import sys
44
from typing import Optional
5+
from unittest.mock import patch
56

67
import async_timeout
78
import pytest
@@ -773,20 +774,52 @@ def callback(message):
773774
}
774775

775776

776-
@pytest.mark.skipif(sys.version_info < (3, 8), reason="requires python 3.8 or higher")
777777
@pytest.mark.onlynoncluster
778-
async def test_outer_timeout(r: redis.Redis):
779-
"""
780-
Using asyncio_timeout manually outside the inner method timeouts works.
781-
This works on Python versions 3.8 and greater, at which time asyncio.CancelledError became a BaseException
782-
instead of an Exception before.
783-
"""
784-
pubsub = r.pubsub()
785-
await pubsub.subscribe("foo")
786-
assert pubsub.connection.is_connected
787-
788-
async def get_msg_or_timeout(timeout=0.1):
789-
async with async_timeout.timeout(timeout):
778+
class TestBaseException:
779+
@pytest.mark.skipif(
780+
sys.version_info < (3, 8), reason="requires python 3.8 or higher"
781+
)
782+
async def test_outer_timeout(self, r: redis.Redis):
783+
"""
784+
Using asyncio_timeout manually outside the inner method timeouts works.
785+
This works on Python versions 3.8 and greater, at which time asyncio.CancelledError
786+
became a BaseException instead of an Exception before.
787+
"""
788+
pubsub = r.pubsub()
789+
await pubsub.subscribe("foo")
790+
assert pubsub.connection.is_connected
791+
792+
async def get_msg_or_timeout(timeout=0.1):
793+
async with async_timeout.timeout(timeout):
794+
# blocking method to return messages
795+
while True:
796+
response = await pubsub.parse_response(block=True)
797+
message = await pubsub.handle_message(
798+
response, ignore_subscribe_messages=False
799+
)
800+
if message is not None:
801+
return message
802+
803+
# get subscribe message
804+
msg = await get_msg_or_timeout(10)
805+
assert msg is not None
806+
# timeout waiting for another message which never arrives
807+
assert pubsub.connection.is_connected
808+
with pytest.raises(asyncio.TimeoutError):
809+
await get_msg_or_timeout()
810+
# the timeout on the read should not cause disconnect
811+
assert pubsub.connection.is_connected
812+
813+
async def test_base_exception(self, r: redis.Redis):
814+
"""
815+
Manually trigger a BaseException inside the parser's .read_response method
816+
and verify that it isn't caught
817+
"""
818+
pubsub = r.pubsub()
819+
await pubsub.subscribe("foo")
820+
assert pubsub.connection.is_connected
821+
822+
async def get_msg():
790823
# blocking method to return messages
791824
while True:
792825
response = await pubsub.parse_response(block=True)
@@ -796,12 +829,18 @@ async def get_msg_or_timeout(timeout=0.1):
796829
if message is not None:
797830
return message
798831

799-
# get subscribe message
800-
msg = await get_msg_or_timeout(10)
801-
assert msg is not None
802-
# timeout waiting for another message which never arrives
803-
assert pubsub.connection.is_connected
804-
with pytest.raises(asyncio.TimeoutError):
805-
await get_msg_or_timeout()
806-
# the timeout on the read should not cause disconnect
807-
assert pubsub.connection.is_connected
832+
# get subscribe message
833+
msg = await get_msg()
834+
assert msg is not None
835+
# timeout waiting for another message which never arrives
836+
assert pubsub.connection.is_connected
837+
with patch("redis.asyncio.connection.PythonParser.read_response") as mock1:
838+
mock1.side_effect = BaseException("boom")
839+
with patch("redis.asyncio.connection.HiredisParser.read_response") as mock2:
840+
mock2.side_effect = BaseException("boom")
841+
842+
with pytest.raises(BaseException):
843+
await get_msg()
844+
845+
# the timeout on the read should not cause disconnect
846+
assert pubsub.connection.is_connected

tests/test_pubsub.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,3 +608,45 @@ def test_pubsub_deadlock(self, master_host):
608608
p = r.pubsub()
609609
p.subscribe("my-channel-1", "my-channel-2")
610610
pool.reset()
611+
612+
613+
@pytest.mark.onlynoncluster
614+
class TestBaseException:
615+
def test_base_exception(self, r: redis.Redis):
616+
"""
617+
Manually trigger a BaseException inside the parser's .read_response method
618+
and verify that it isn't caught
619+
"""
620+
pubsub = r.pubsub()
621+
pubsub.subscribe("foo")
622+
623+
def is_connected():
624+
return pubsub.connection._sock is not None
625+
626+
assert is_connected()
627+
628+
def get_msg():
629+
# blocking method to return messages
630+
while True:
631+
response = pubsub.parse_response(block=True)
632+
message = pubsub.handle_message(
633+
response, ignore_subscribe_messages=False
634+
)
635+
if message is not None:
636+
return message
637+
638+
# get subscribe message
639+
msg = get_msg()
640+
assert msg is not None
641+
# timeout waiting for another message which never arrives
642+
assert is_connected()
643+
with patch("redis.connection.PythonParser.read_response") as mock1:
644+
mock1.side_effect = BaseException("boom")
645+
with patch("redis.connection.HiredisParser.read_response") as mock2:
646+
mock2.side_effect = BaseException("boom")
647+
648+
with pytest.raises(BaseException):
649+
get_msg()
650+
651+
# the timeout on the read should not cause disconnect
652+
assert is_connected()

0 commit comments

Comments
 (0)