11import asyncio
22import socket
33import types
4- from unittest .mock import AsyncMock , Mock , patch
4+ from unittest .mock import patch
55
66import 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