1212import threading
1313import unittest
1414import unittest .mock
15- from contextlib import contextmanager , redirect_stdout , ExitStack
15+ from contextlib import closing , contextmanager , redirect_stdout , ExitStack
1616from pathlib import Path
1717from test .support import is_wasi , os_helper , requires_subprocess , SHORT_TIMEOUT
1818from test .support .os_helper import temp_dir , TESTFN , unlink
@@ -79,52 +79,14 @@ def get_output(self) -> List[dict]:
7979 return results
8080
8181
82- class MockDebuggerSocket :
83- """Mock file-like simulating a connection to a _RemotePdb instance"""
84-
85- def __init__ (self , incoming ):
86- self .incoming = iter (incoming )
87- self .outgoing = []
88- self .buffered = bytearray ()
89-
90- def write (self , data : bytes ) -> None :
91- """Simulate write to socket."""
92- self .buffered += data
93-
94- def flush (self ) -> None :
95- """Ensure each line is valid JSON."""
96- lines = self .buffered .splitlines (keepends = True )
97- self .buffered .clear ()
98- for line in lines :
99- assert line .endswith (b"\n " )
100- self .outgoing .append (json .loads (line ))
101-
102- def readline (self ) -> bytes :
103- """Read a line from the prepared input queue."""
104- # Anything written must be flushed before trying to read,
105- # since the read will be dependent upon the last write.
106- assert not self .buffered
107- try :
108- item = next (self .incoming )
109- if not isinstance (item , bytes ):
110- item = json .dumps (item ).encode ()
111- return item + b"\n "
112- except StopIteration :
113- return b""
114-
115- def close (self ) -> None :
116- """No-op close implementation."""
117- pass
118-
119-
12082class PdbClientTestCase (unittest .TestCase ):
12183 """Tests for the _PdbClient class."""
12284
12385 def do_test (
12486 self ,
12587 * ,
12688 incoming ,
127- simulate_failure = None ,
89+ simulate_send_failure = False ,
12890 expected_outgoing = None ,
12991 expected_completions = None ,
13092 expected_exception = None ,
@@ -142,16 +104,6 @@ def do_test(
142104 expected_state .setdefault ("write_failed" , False )
143105 messages = [m for source , m in incoming if source == "server" ]
144106 prompts = [m ["prompt" ] for source , m in incoming if source == "user" ]
145- sockfile = MockDebuggerSocket (messages )
146- stdout = io .StringIO ()
147-
148- if simulate_failure :
149- sockfile .write = unittest .mock .Mock ()
150- sockfile .flush = unittest .mock .Mock ()
151- if simulate_failure == "write" :
152- sockfile .write .side_effect = OSError ("write failed" )
153- elif simulate_failure == "flush" :
154- sockfile .flush .side_effect = OSError ("flush failed" )
155107
156108 input_iter = (m for source , m in incoming if source == "user" )
157109 completions = []
@@ -181,14 +133,33 @@ def mock_input(prompt):
181133 return reply
182134
183135 with ExitStack () as stack :
136+ client_sock , server_sock = socket .socketpair ()
137+ stack .enter_context (closing (client_sock ))
138+ stack .enter_context (closing (server_sock ))
139+
140+ server_sock = unittest .mock .Mock (wraps = server_sock )
141+
142+ client_sock .sendall (
143+ b"" .join (
144+ (m if isinstance (m , bytes ) else json .dumps (m ).encode ()) + b"\n "
145+ for m in messages
146+ )
147+ )
148+ client_sock .shutdown (socket .SHUT_WR )
149+
150+ if simulate_send_failure :
151+ client_sock .shutdown (socket .SHUT_RD )
152+
153+ stdout = io .StringIO ()
154+
184155 input_mock = stack .enter_context (
185156 unittest .mock .patch ("pdb.input" , side_effect = mock_input )
186157 )
187158 stack .enter_context (redirect_stdout (stdout ))
188159
189160 client = _PdbClient (
190161 pid = 0 ,
191- sockfile = sockfile ,
162+ server_socket = server_sock ,
192163 interrupt_script = "/a/b.py" ,
193164 )
194165
@@ -199,13 +170,12 @@ def mock_input(prompt):
199170
200171 client .cmdloop ()
201172
202- actual_outgoing = sockfile .outgoing
203- if simulate_failure :
204- actual_outgoing += [
205- json .loads (msg .args [0 ]) for msg in sockfile .write .mock_calls
206- ]
173+ sent_msgs = [msg .args [0 ] for msg in server_sock .sendall .mock_calls ]
174+ for msg in sent_msgs :
175+ assert msg .endswith (b"\n " )
176+ actual_outgoing = [json .loads (msg ) for msg in sent_msgs ]
207177
208- self .assertEqual (sockfile . outgoing , expected_outgoing )
178+ self .assertEqual (actual_outgoing , expected_outgoing )
209179 self .assertEqual (completions , expected_completions )
210180 if expected_stdout_substring and not expected_stdout :
211181 self .assertIn (expected_stdout_substring , stdout .getvalue ())
@@ -478,20 +448,7 @@ def test_write_failing(self):
478448 self .do_test (
479449 incoming = incoming ,
480450 expected_outgoing = [{"signal" : "INT" }],
481- simulate_failure = "write" ,
482- expected_state = {"write_failed" : True },
483- )
484-
485- def test_flush_failing (self ):
486- """Test terminating if flush fails due to a half closed socket."""
487- incoming = [
488- ("server" , {"prompt" : "(Pdb) " , "state" : "pdb" }),
489- ("user" , {"prompt" : "(Pdb) " , "input" : KeyboardInterrupt ()}),
490- ]
491- self .do_test (
492- incoming = incoming ,
493- expected_outgoing = [{"signal" : "INT" }],
494- simulate_failure = "flush" ,
451+ simulate_send_failure = True ,
495452 expected_state = {"write_failed" : True },
496453 )
497454
@@ -622,42 +579,7 @@ def test_write_failure_during_completion(self):
622579 },
623580 {"reply" : "xyz" },
624581 ],
625- simulate_failure = "write" ,
626- expected_completions = [],
627- expected_state = {"state" : "interact" , "write_failed" : True },
628- )
629-
630- def test_flush_failure_during_completion (self ):
631- """Test failing to flush to the socket to request tab completions."""
632- incoming = [
633- ("server" , {"prompt" : ">>> " , "state" : "interact" }),
634- (
635- "user" ,
636- {
637- "prompt" : ">>> " ,
638- "completion_request" : {
639- "line" : "xy" ,
640- "begidx" : 0 ,
641- "endidx" : 2 ,
642- },
643- "input" : "xyz" ,
644- },
645- ),
646- ]
647- self .do_test (
648- incoming = incoming ,
649- expected_outgoing = [
650- {
651- "complete" : {
652- "text" : "xy" ,
653- "line" : "xy" ,
654- "begidx" : 0 ,
655- "endidx" : 2 ,
656- }
657- },
658- {"reply" : "xyz" },
659- ],
660- simulate_failure = "flush" ,
582+ simulate_send_failure = True ,
661583 expected_completions = [],
662584 expected_state = {"state" : "interact" , "write_failed" : True },
663585 )
0 commit comments