Skip to content

Commit 78c81f4

Browse files
committed
Test that reading a message of two chunks after a disconnect() works.
1 parent d786ed2 commit 78c81f4

File tree

1 file changed

+32
-18
lines changed

1 file changed

+32
-18
lines changed

tests/test_asyncio/test_connection.py

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import asyncio
22
import socket
33
import types
4-
from unittest.mock import AsyncMock, Mock, patch
4+
from unittest.mock import patch
55

66
import pytest
77

@@ -151,7 +151,10 @@ async def test_connection_parse_response_resume(r: redis.Redis):
151151

152152

153153
@pytest.mark.onlynoncluster
154-
async def test_connection_hiredis_disconect_race():
154+
@pytest.mark.parametrize(
155+
"parser_class", [PythonParser, HiredisParser], ids=["PythonParser", "HiredisParser"]
156+
)
157+
async def test_connection_disconect_race(parser_class):
155158
"""
156159
This test reproduces the case in issue #2349
157160
where a connection is closed while the parser is reading to feed the
@@ -163,12 +166,14 @@ async def test_connection_hiredis_disconect_race():
163166
This test verifies that a read in progress can finish even
164167
if the `disconnect()` method is called.
165168
"""
166-
if not HIREDIS_AVAILABLE:
167-
pytest.skip("Hiredis not available)")
168-
parser_class = HiredisParser
169+
if parser_class == PythonParser:
170+
pytest.xfail("doesn't work yet with PythonParser")
171+
if parser_class == HiredisParser and not HIREDIS_AVAILABLE:
172+
pytest.skip("Hiredis not available")
169173

170174
args = {}
171175
args["parser_class"] = parser_class
176+
172177
conn = Connection(**args)
173178

174179
cond = asyncio.Condition()
@@ -177,15 +182,20 @@ async def test_connection_hiredis_disconect_race():
177182
# 2 == closer has closed and is waiting for close to finish
178183
state = 0
179184

180-
# mock read function, which wait for a close to happen before returning
181-
async def read(_):
185+
# Mock read function, which wait for a close to happen before returning
186+
# Can either be invoked as two `read()` calls (HiredisParser)
187+
# or as a `readline()` followed by `readexact()` (PythonParser)
188+
chunks = [b"$13\r\n", b"Hello, World!\r\n"]
189+
190+
async def read(_=None):
182191
nonlocal state
183192
async with cond:
184-
state = 1 # we are reading
185-
cond.notify()
186-
# wait until the closing task has done
187-
await cond.wait_for(lambda: state == 2)
188-
return b" "
193+
if state == 0:
194+
state = 1 # we are reading
195+
cond.notify()
196+
# wait until the closing task has done
197+
await cond.wait_for(lambda: state == 2)
198+
return chunks.pop(0)
189199

190200
# function closes the connection while reader is still blocked reading
191201
async def do_close():
@@ -197,20 +207,24 @@ async def do_close():
197207
await conn.disconnect()
198208

199209
async def do_read():
200-
with pytest.raises(InvalidResponse):
201-
await conn.read_response()
210+
return await conn.read_response()
202211

203-
reader = AsyncMock()
204-
writer = AsyncMock()
205-
writer.transport = Mock()
212+
reader = mock.AsyncMock()
213+
writer = mock.AsyncMock()
214+
writer.transport = mock.Mock()
206215
writer.transport.get_extra_info.side_effect = None
207216

217+
# for HiredisParser
208218
reader.read.side_effect = read
219+
# for PythonParser
220+
reader.readline.side_effect = read
221+
reader.readexactly.side_effect = read
209222

210223
async def open_connection(*args, **kwargs):
211224
return reader, writer
212225

213226
with patch.object(asyncio, "open_connection", open_connection):
214227
await conn.connect()
215228

216-
await asyncio.gather(do_read(), do_close())
229+
vals = await asyncio.gather(do_read(), do_close())
230+
assert vals == [b"Hello, World!", None]

0 commit comments

Comments
 (0)