mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
Add delay waiting for socket server to start (#594)
Add delay waiting for socket server to start and port to be free
This commit is contained in:
parent
aa5a4ef524
commit
1a9112262b
4 changed files with 99 additions and 57 deletions
|
|
@ -89,6 +89,19 @@ def wait_for_socket_server(addr, timeout=3.0, **kwargs):
|
|||
raise ConnectionRefusedError('Timeout waiting for connection')
|
||||
|
||||
|
||||
def wait_for_port_to_free(port, timeout=3.0):
|
||||
start_time = time.time()
|
||||
while True:
|
||||
try:
|
||||
sock = socket.create_connection(('localhost', port))
|
||||
sock.close()
|
||||
except Exception:
|
||||
return
|
||||
time.sleep(0.1)
|
||||
if time.time() - start_time > timeout:
|
||||
raise ConnectionRefusedError('Timeout waiting for port to be free')
|
||||
|
||||
|
||||
class DebugAdapter(Closeable):
|
||||
|
||||
VERBOSE = False
|
||||
|
|
@ -105,38 +118,32 @@ class DebugAdapter(Closeable):
|
|||
argv = list(argv)
|
||||
cls._ensure_addr(argv, addr)
|
||||
return Proc.start_python_module(
|
||||
'ptvsd',
|
||||
argv,
|
||||
env=env_vars,
|
||||
cwd=cwd,
|
||||
**kwds
|
||||
)
|
||||
'ptvsd', argv, env=env_vars, cwd=cwd, **kwds)
|
||||
|
||||
return cls._start(new_proc, argv, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def start_wrapper_script(cls, filename, argv, env=None, cwd=None, **kwargs): # noqa
|
||||
def start_wrapper_script(cls, filename, argv, env=None, cwd=None,
|
||||
**kwargs): # noqa
|
||||
def new_proc(argv, addr, **kwds):
|
||||
env_vars = _copy_env(verbose=cls.VERBOSE, env=env)
|
||||
return Proc.start_python_script(
|
||||
filename,
|
||||
argv,
|
||||
env=env_vars,
|
||||
cwd=cwd,
|
||||
**kwds
|
||||
)
|
||||
filename, argv, env=env_vars, cwd=cwd, **kwds)
|
||||
|
||||
return cls._start(new_proc, argv, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def start_wrapper_module(cls, modulename, argv, env=None, cwd=None, **kwargs): # noqa
|
||||
def start_wrapper_module(cls,
|
||||
modulename,
|
||||
argv,
|
||||
env=None,
|
||||
cwd=None,
|
||||
**kwargs): # noqa
|
||||
def new_proc(argv, addr, **kwds):
|
||||
env_vars = _copy_env(verbose=cls.VERBOSE, env=env)
|
||||
return Proc.start_python_module(
|
||||
modulename,
|
||||
argv,
|
||||
env=env_vars,
|
||||
cwd=cwd,
|
||||
**kwds
|
||||
)
|
||||
modulename, argv, env=env_vars, cwd=cwd, **kwds)
|
||||
|
||||
return cls._start(new_proc, argv, **kwargs)
|
||||
|
||||
# specific factory cases
|
||||
|
|
@ -169,7 +176,12 @@ class DebugAdapter(Closeable):
|
|||
return adapter
|
||||
|
||||
@classmethod
|
||||
def _start_as(cls, addr, name, kind='script', extra=None, server=False,
|
||||
def _start_as(cls,
|
||||
addr,
|
||||
name,
|
||||
kind='script',
|
||||
extra=None,
|
||||
server=False,
|
||||
**kwargs):
|
||||
argv = []
|
||||
if server:
|
||||
|
|
@ -192,11 +204,7 @@ class DebugAdapter(Closeable):
|
|||
# TODO: Handle this case somehow?
|
||||
assert 'ptvsd.enable_attach' in content
|
||||
adapter = cls.start_wrapper_script(
|
||||
filename,
|
||||
argv=argv,
|
||||
addr=addr,
|
||||
**kwargs
|
||||
)
|
||||
filename, argv=argv, addr=addr, **kwargs)
|
||||
wait_for_socket_server(addr, **kwargs)
|
||||
return adapter
|
||||
|
||||
|
|
|
|||
|
|
@ -8,14 +8,15 @@ from . import Closeable
|
|||
from .debugadapter import DebugAdapter, wait_for_socket_server
|
||||
from .debugsession import DebugSession
|
||||
|
||||
|
||||
# TODO: Add a helper function to start a remote debugger for testing
|
||||
# remote debugging?
|
||||
|
||||
|
||||
class _LifecycleClient(Closeable):
|
||||
|
||||
def __init__(self, addr=None, port=8888, breakpoints=None,
|
||||
def __init__(self,
|
||||
addr=None,
|
||||
port=8888,
|
||||
breakpoints=None,
|
||||
connecttimeout=1.0):
|
||||
super(_LifecycleClient, self).__init__()
|
||||
self._addr = Address.from_raw(addr, defaultport=port)
|
||||
|
|
@ -101,12 +102,19 @@ class _LifecycleClient(Closeable):
|
|||
if self._adapter is not None:
|
||||
self._adapter.close()
|
||||
|
||||
def _launch(self, argv, script=None, wait_for_connect=None,
|
||||
detachable=True, env=None, cwd=None, **kwargs):
|
||||
def _launch(self,
|
||||
argv,
|
||||
script=None,
|
||||
wait_for_connect=None,
|
||||
detachable=True,
|
||||
env=None,
|
||||
cwd=None,
|
||||
**kwargs):
|
||||
if script is not None:
|
||||
|
||||
def start(*args, **kwargs):
|
||||
return DebugAdapter.start_wrapper_script(script,
|
||||
*args, **kwargs)
|
||||
return DebugAdapter.start_wrapper_script(
|
||||
script, *args, **kwargs)
|
||||
else:
|
||||
start = DebugAdapter.start
|
||||
new_addr = Address.as_server if detachable else Address.as_client
|
||||
|
|
@ -138,7 +146,6 @@ class DebugClient(_LifecycleClient):
|
|||
|
||||
|
||||
class EasyDebugClient(DebugClient):
|
||||
|
||||
def start_detached(self, argv):
|
||||
"""Start an adapter in a background process."""
|
||||
if self.closed:
|
||||
|
|
@ -151,7 +158,12 @@ class EasyDebugClient(DebugClient):
|
|||
self._adapter = DebugAdapter.start(argv, port=self._port)
|
||||
return self._adapter
|
||||
|
||||
def host_local_debugger(self, argv, script=None, env=None, cwd=None, **kwargs): # noqa
|
||||
def host_local_debugger(self,
|
||||
argv,
|
||||
script=None,
|
||||
env=None,
|
||||
cwd=None,
|
||||
**kwargs): # noqa
|
||||
if self.closed:
|
||||
raise RuntimeError('debug client closed')
|
||||
if self._adapter is not None:
|
||||
|
|
@ -161,6 +173,7 @@ class EasyDebugClient(DebugClient):
|
|||
|
||||
def run():
|
||||
self._session = DebugSession.create_server(addr, **kwargs)
|
||||
|
||||
t = new_hidden_thread(
|
||||
target=run,
|
||||
name='test.client',
|
||||
|
|
@ -172,16 +185,17 @@ class EasyDebugClient(DebugClient):
|
|||
if t.is_alive():
|
||||
warnings.warn('timed out waiting for connection')
|
||||
if self._session is None:
|
||||
raise RuntimeError('unable to connect')
|
||||
raise RuntimeError('unable to connect after {} secs'.format(
|
||||
self._connecttimeout))
|
||||
# The adapter will close when the connection does.
|
||||
|
||||
self._launch(
|
||||
argv,
|
||||
script=script,
|
||||
wait_for_connect=wait,
|
||||
detachable=False,
|
||||
env=env,
|
||||
cwd=cwd
|
||||
)
|
||||
cwd=cwd)
|
||||
|
||||
return self._adapter, self._session
|
||||
|
||||
|
|
@ -208,7 +222,8 @@ class EasyDebugClient(DebugClient):
|
|||
assert self._session is None
|
||||
|
||||
argv = [
|
||||
'-m', module,
|
||||
'-m',
|
||||
module,
|
||||
] + list(argv)
|
||||
if kwargs.pop('nodebug', False):
|
||||
argv.insert(0, '--nodebug')
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@ import contextlib
|
|||
import os
|
||||
import ptvsd
|
||||
import signal
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from collections import namedtuple
|
||||
from ptvsd.socket import Address
|
||||
from tests.helpers.debugadapter import DebugAdapter
|
||||
from tests.helpers.debugadapter import DebugAdapter, wait_for_port_to_free
|
||||
from tests.helpers.debugclient import EasyDebugClient as DebugClient
|
||||
from tests.helpers.script import find_line
|
||||
from tests.helpers.threading import get_locked_and_waiter
|
||||
|
|
@ -17,7 +18,7 @@ from tests.helpers.vsc import parse_message, VSCMessages, Response, Event # noq
|
|||
ROOT = os.path.dirname(os.path.dirname(ptvsd.__file__))
|
||||
PORT = 9876
|
||||
CONNECT_TIMEOUT = 3.0
|
||||
|
||||
DELAY_WAITING_FOR_SOCKETS = 1.0
|
||||
|
||||
DebugInfo = namedtuple('DebugInfo', 'port starttype argv filename modulename env cwd attachtype') # noqa
|
||||
DebugInfo.__new__.__defaults__ = (9876, 'launch', []) + ((None, ) * (len(DebugInfo._fields) - 3)) # noqa
|
||||
|
|
@ -218,6 +219,7 @@ class LifecycleTestsBase(TestsBase, unittest.TestCase):
|
|||
addr = Address('localhost', debug_info.port)
|
||||
cwd = debug_info.cwd
|
||||
env = debug_info.env
|
||||
wait_for_port_to_free(debug_info.port)
|
||||
|
||||
def _kill_proc(pid):
|
||||
"""If debugger does not end gracefully, then kill proc and
|
||||
|
|
@ -229,6 +231,25 @@ class LifecycleTestsBase(TestsBase, unittest.TestCase):
|
|||
import time
|
||||
time.sleep(1) # wait for socket connections to die out. # noqa
|
||||
|
||||
def _wrap_and_reraise(ex, session):
|
||||
messages = []
|
||||
try:
|
||||
messages = [str(msg) for msg in
|
||||
_strip_newline_output_events(session.received)]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
messages = os.linesep.join(messages)
|
||||
try:
|
||||
raise Exception(messages) from ex
|
||||
except Exception:
|
||||
print(messages)
|
||||
raise ex
|
||||
|
||||
def _handle_exception(ex, adapter, session):
|
||||
_kill_proc(adapter.pid)
|
||||
_wrap_and_reraise(ex, session)
|
||||
|
||||
if debug_info.attachtype == 'import' and \
|
||||
debug_info.modulename is not None:
|
||||
argv = debug_info.argv
|
||||
|
|
@ -238,13 +259,13 @@ class LifecycleTestsBase(TestsBase, unittest.TestCase):
|
|||
env=env,
|
||||
cwd=cwd) as adapter:
|
||||
with DebugClient() as editor:
|
||||
time.sleep(DELAY_WAITING_FOR_SOCKETS)
|
||||
session = editor.attach_socket(addr, adapter)
|
||||
try:
|
||||
yield Debugger(session=session, adapter=adapter)
|
||||
adapter.wait()
|
||||
except Exception:
|
||||
_kill_proc(adapter.pid)
|
||||
raise
|
||||
except Exception as ex:
|
||||
_handle_exception(ex, adapter, session)
|
||||
elif debug_info.attachtype == 'import' and \
|
||||
debug_info.starttype == 'attach' and \
|
||||
debug_info.filename is not None:
|
||||
|
|
@ -256,13 +277,13 @@ class LifecycleTestsBase(TestsBase, unittest.TestCase):
|
|||
env=env,
|
||||
cwd=cwd) as adapter:
|
||||
with DebugClient() as editor:
|
||||
time.sleep(DELAY_WAITING_FOR_SOCKETS)
|
||||
session = editor.attach_socket(addr, adapter)
|
||||
try:
|
||||
yield Debugger(session=session, adapter=adapter)
|
||||
adapter.wait()
|
||||
except Exception:
|
||||
_kill_proc(adapter.pid)
|
||||
raise
|
||||
except Exception as ex:
|
||||
_handle_exception(ex, adapter, session)
|
||||
elif debug_info.starttype == 'attach':
|
||||
if debug_info.modulename is None:
|
||||
name = debug_info.filename
|
||||
|
|
@ -279,13 +300,13 @@ class LifecycleTestsBase(TestsBase, unittest.TestCase):
|
|||
env=env,
|
||||
cwd=cwd) as adapter:
|
||||
with DebugClient() as editor:
|
||||
time.sleep(DELAY_WAITING_FOR_SOCKETS)
|
||||
session = editor.attach_socket(addr, adapter)
|
||||
try:
|
||||
yield Debugger(session=session, adapter=adapter)
|
||||
adapter.wait()
|
||||
except Exception:
|
||||
_kill_proc(adapter.pid)
|
||||
raise
|
||||
except Exception as ex:
|
||||
_handle_exception(ex, adapter, session)
|
||||
else:
|
||||
if debug_info.filename is None:
|
||||
argv = ["-m", debug_info.modulename] + debug_info.argv
|
||||
|
|
@ -294,14 +315,14 @@ class LifecycleTestsBase(TestsBase, unittest.TestCase):
|
|||
with DebugClient(
|
||||
port=debug_info.port,
|
||||
connecttimeout=CONNECT_TIMEOUT) as editor:
|
||||
time.sleep(DELAY_WAITING_FOR_SOCKETS)
|
||||
adapter, session = editor.host_local_debugger(
|
||||
argv, cwd=cwd, env=env)
|
||||
try:
|
||||
yield Debugger(session=session, adapter=adapter)
|
||||
adapter.wait()
|
||||
except Exception:
|
||||
_kill_proc(adapter.pid)
|
||||
raise
|
||||
except Exception as ex:
|
||||
_handle_exception(ex, adapter, session)
|
||||
|
||||
@property
|
||||
def messages(self):
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ class LaunchExceptionLifecycleTests(LifecycleTestsBase):
|
|||
options = {"debugOptions": ["RedirectOutput"]}
|
||||
|
||||
with self.start_debugging(debug_info) as dbg:
|
||||
stopped = dbg.session.get_awaiter_for_event('stopped')
|
||||
(_, req_launch_attach, _, _, _, _) = lifecycle_handshake(
|
||||
dbg.session,
|
||||
debug_info.starttype,
|
||||
|
|
@ -52,10 +53,7 @@ class LaunchExceptionLifecycleTests(LifecycleTestsBase):
|
|||
options=options,
|
||||
threads=True)
|
||||
|
||||
req_launch_attach.wait()
|
||||
|
||||
stopped = dbg.session.get_awaiter_for_event('stopped')
|
||||
stopped.wait()
|
||||
Awaitable.wait_all(req_launch_attach, stopped)
|
||||
self.assertEqual(stopped.event.body["text"], "ArithmeticError")
|
||||
self.assertIn("ArithmeticError('Hello'",
|
||||
stopped.event.body["description"])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue