@@ -27,7 +27,7 @@ class TestIsPortAvailable(unittest.TestCase):
2727 def test_available_port (self ):
2828 """An unused port should be available."""
2929 # Use a random high port unlikely to be in use
30- self .assertTrue (is_port_available (59123 ))
30+ self .assertTrue (is_port_available ("127.0.0.1" , 59123 ))
3131
3232 def test_occupied_port (self ):
3333 """A port with a listener should not be available."""
@@ -37,7 +37,7 @@ def test_occupied_port(self):
3737 s .listen (1 )
3838 port = s .getsockname ()[1 ]
3939 try :
40- self .assertFalse (is_port_available (port ))
40+ self .assertFalse (is_port_available ("127.0.0.1" , port ))
4141 finally :
4242 s .close ()
4343
@@ -49,7 +49,7 @@ def test_port_after_close(self):
4949 s .listen (1 )
5050 port = s .getsockname ()[1 ]
5151 s .close ()
52- self .assertTrue (is_port_available (port ))
52+ self .assertTrue (is_port_available ("127.0.0.1" , port ))
5353
5454
5555class TestGetPortOwner (unittest .TestCase ):
@@ -130,33 +130,32 @@ class TestCheckAndFreePort(unittest.TestCase):
130130
131131 def test_available_port (self ):
132132 """Should return True immediately for a free port."""
133- self .assertTrue (check_and_free_port (59126 ))
133+ self .assertTrue (check_and_free_port ("127.0.0.1" , 59126 ))
134134
135135 @patch ("utils.net.kill_port_owner" , return_value = True )
136136 @patch ("utils.net.is_port_available" , return_value = False )
137137 def test_occupied_then_freed (self , mock_avail , mock_kill ):
138138 """Should try to kill and return True on success."""
139- self .assertTrue (check_and_free_port (12345 ))
139+ self .assertTrue (check_and_free_port ("127.0.0.1" , 12345 ))
140140 mock_kill .assert_called_once_with (12345 )
141141
142142 @patch ("utils.net.kill_port_owner" , return_value = False )
143143 @patch ("utils.net.is_port_available" , return_value = False )
144144 def test_occupied_kill_fails (self , mock_avail , mock_kill ):
145145 """Should return False when kill fails."""
146- self .assertFalse (check_and_free_port (12345 ))
146+ self .assertFalse (check_and_free_port ("127.0.0.1" , 12345 ))
147147
148148
149149class TestGDBPortConflict (unittest .TestCase ):
150150 """Integration test: GDB server port conflict detection."""
151151
152152 @patch ("utils.net.get_port_owner" )
153- def test_gdb_manager_uses_check_and_free_port (self , mock_owner ):
154- """start_external_gdb_server should call check_and_free_port."""
155-
153+ def test_gdb_manager_checks_port (self , mock_owner ):
154+ """start_external_gdb_server should check port availability."""
156155 mock_owner .return_value = None
157156
158157 with patch (
159- "core.gdb_manager.check_and_free_port " , return_value = True
158+ "core.gdb_manager.is_port_available " , return_value = True
160159 ) as mock_check :
161160 with patch ("core.gdb_manager.GDBRSPBridge" ) as mock_bridge_cls :
162161 mock_bridge = MagicMock ()
@@ -174,19 +173,53 @@ def test_gdb_manager_uses_check_and_free_port(self, mock_owner):
174173
175174 result = start_external_gdb_server (state )
176175 self .assertTrue (result )
177- mock_check .assert_called_once_with (3333 )
176+ mock_check .assert_called_once_with ("127.0.0.1" , 3333 )
178177
179178 def test_gdb_manager_rejects_occupied_port (self ):
180- """start_external_gdb_server should fail if port can't be freed."""
181- with patch ("core.gdb_manager.check_and_free_port" , return_value = False ):
182- from core .gdb_manager import start_external_gdb_server
183-
184- state = MagicMock ()
185- state .device .external_gdb_port = 3333
186- state .external_gdb_bridge = None
187-
188- result = start_external_gdb_server (state )
189- self .assertFalse (result )
179+ """start_external_gdb_server should fail with detailed info if port can't be freed."""
180+ with patch ("core.gdb_manager.is_port_available" , return_value = False ):
181+ with patch (
182+ "core.gdb_manager.get_port_owner" ,
183+ return_value = {
184+ "pid" : 999 ,
185+ "name" : "python" ,
186+ "cmdline" : "python main.py" ,
187+ },
188+ ):
189+ with patch ("core.gdb_manager.kill_port_owner" , return_value = False ):
190+ from core .gdb_manager import start_external_gdb_server
191+
192+ state = MagicMock ()
193+ state .device .external_gdb_port = 3333
194+ state .external_gdb_bridge = None
195+
196+ result = start_external_gdb_server (state )
197+ self .assertFalse (result )
198+
199+ def test_gdb_manager_kills_stale_and_starts (self ):
200+ """start_external_gdb_server should kill stale process and succeed."""
201+ with patch ("core.gdb_manager.is_port_available" , return_value = False ):
202+ with patch (
203+ "core.gdb_manager.get_port_owner" ,
204+ return_value = {"pid" : 888 , "name" : "python" , "cmdline" : "old" },
205+ ):
206+ with patch ("core.gdb_manager.kill_port_owner" , return_value = True ):
207+ with patch ("core.gdb_manager.GDBRSPBridge" ) as mock_bridge_cls :
208+ mock_bridge = MagicMock ()
209+ mock_bridge .start .return_value = 3333
210+ mock_bridge .is_running = False
211+ mock_bridge_cls .return_value = mock_bridge
212+
213+ from core .gdb_manager import start_external_gdb_server
214+
215+ state = MagicMock ()
216+ state .device .external_gdb_port = 3333
217+ state .device .elf_path = None
218+ state .device .download_chunk_size = 1024
219+ state .external_gdb_bridge = None
220+
221+ result = start_external_gdb_server (state )
222+ self .assertTrue (result )
190223
191224
192225if __name__ == "__main__" :
0 commit comments