Skip to content

Commit efa4375

Browse files
committed
add regression test
1 parent 6219574 commit efa4375

File tree

2 files changed

+56
-0
lines changed

2 files changed

+56
-0
lines changed

tests/test_asyncio/test_connection.py

+22
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,25 @@ async def test_connect_timeout_error_without_retry():
112112
await conn.connect()
113113
assert conn._connect.call_count == 1
114114
assert str(e.value) == "Timeout connecting to server"
115+
116+
117+
@pytest.mark.parametrize('exc_type', [Exception, BaseException])
118+
async def test_read_response__interrupt_does_not_corrupt(exc_type):
119+
conn = Connection()
120+
121+
await conn.send_command("GET non_existent_key")
122+
resp = await conn.read_response()
123+
assert resp is None
124+
125+
with pytest.raises(exc_type):
126+
await conn.send_command("EXISTS non_existent_key")
127+
# due to the interrupt, the integer '0' result of EXISTS will remain on the socket's buffer
128+
with patch.object(socket.socket, "recv", side_effect=exc_type) as mock_recv:
129+
await conn.read_response()
130+
mock_recv.assert_called_once()
131+
132+
await conn.send_command("GET non_existent_key")
133+
resp = await conn.read_response()
134+
# If working properly, this will get a None.
135+
# If not, it will get a zero (the integer result of the previous EXISTS command).
136+
assert resp is None

tests/test_connection.py

+34
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,37 @@ def test_connect_timeout_error_without_retry(self):
122122
assert conn._connect.call_count == 1
123123
assert str(e.value) == "Timeout connecting to server"
124124
self.clear(conn)
125+
126+
@pytest.mark.parametrize('exc_type', [Exception, BaseException])
127+
def test_read_response__interrupt_does_not_corrupt(self, exc_type):
128+
conn = Connection()
129+
130+
# A note on BaseException:
131+
# While socket.recv is not supposed to raise BaseException, gevent's version of socket
132+
# (which, when using gevent + redis-py, one would monkey-patch in) can raise BaseException
133+
# on a timer elapse, since `gevent.Timeout` derives from BaseException. This design suggests
134+
# that a timeout should not be suppressed but rather allowed to propagate.
135+
# asyncio.exceptions.CancelledError also derives from BaseException for same reason.
136+
#
137+
# The notion that one should never `expect:` or `expect BaseException`, however, is misguided.
138+
# It's idiomatic to handle it, to provide for exception safety, as long as you re-raise.
139+
#
140+
# with gevent.Timeout(5):
141+
# res = client.exists('my_key')
142+
143+
conn.send_command("GET non_existent_key")
144+
resp = conn.read_response()
145+
assert resp is None
146+
147+
with pytest.raises(exc_type):
148+
conn.send_command("EXISTS non_existent_key")
149+
# due to the interrupt, the integer '0' result of EXISTS will remain on the socket's buffer
150+
with patch.object(socket.socket, "recv", side_effect=exc_type) as mock_recv:
151+
_ = conn.read_response()
152+
mock_recv.assert_called_once()
153+
154+
conn.send_command("GET non_existent_key")
155+
resp = conn.read_response()
156+
# If working properly, this will get a None.
157+
# If not, it will get a zero (the integer result of the previous EXISTS command).
158+
assert resp is None

0 commit comments

Comments
 (0)