mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
The system tests have a number of minor formatting and structural issues that would be better to clean up sooner rather than later. This PR addresses nearly all of them.
250 lines
7.4 KiB
Python
250 lines
7.4 KiB
Python
from __future__ import absolute_import
|
|
|
|
import warnings
|
|
|
|
from ptvsd.socket import Address
|
|
from ptvsd._util import new_hidden_thread, Closeable, ClosedError
|
|
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):
|
|
|
|
SESSION = DebugSession
|
|
|
|
def __init__(self,
|
|
addr=None,
|
|
port=8888,
|
|
breakpoints=None,
|
|
connecttimeout=1.0,
|
|
):
|
|
super(_LifecycleClient, self).__init__()
|
|
self._addr = Address.from_raw(addr, defaultport=port)
|
|
self._connecttimeout = connecttimeout
|
|
self._adapter = None
|
|
self._session = None
|
|
|
|
self._breakpoints = breakpoints
|
|
|
|
@property
|
|
def adapter(self):
|
|
return self._adapter
|
|
|
|
@property
|
|
def session(self):
|
|
return self._session
|
|
|
|
def start_debugging(self, launchcfg):
|
|
if self.closed:
|
|
raise RuntimeError('debug client closed')
|
|
if self._adapter is not None:
|
|
raise RuntimeError('debugger already running')
|
|
assert self._session is None
|
|
|
|
raise NotImplementedError
|
|
|
|
def stop_debugging(self):
|
|
if self.closed:
|
|
raise RuntimeError('debug client closed')
|
|
if self._adapter is None:
|
|
raise RuntimeError('debugger not running')
|
|
|
|
if self._session is not None:
|
|
self._detach()
|
|
|
|
try:
|
|
self._adapter.close()
|
|
except ClosedError:
|
|
pass
|
|
self._adapter = None
|
|
|
|
def attach_pid(self, pid, **kwargs):
|
|
if self.closed:
|
|
raise RuntimeError('debug client closed')
|
|
if self._adapter is None:
|
|
raise RuntimeError('debugger not running')
|
|
if self._session is not None:
|
|
raise RuntimeError('already attached')
|
|
|
|
raise NotImplementedError
|
|
|
|
def attach_socket(self, addr=None, adapter=None, **kwargs):
|
|
if self.closed:
|
|
raise RuntimeError('debug client closed')
|
|
if adapter is None:
|
|
adapter = self._adapter
|
|
elif self._adapter is not None:
|
|
raise RuntimeError('already using managed adapter')
|
|
if adapter is None:
|
|
raise RuntimeError('debugger not running')
|
|
if self._session is not None:
|
|
raise RuntimeError('already attached')
|
|
|
|
if addr is None:
|
|
addr = adapter.address
|
|
self._attach(addr, **kwargs)
|
|
return self._session
|
|
|
|
def detach(self, adapter=None):
|
|
if self.closed:
|
|
raise RuntimeError('debug client closed')
|
|
if self._session is None:
|
|
raise RuntimeError('not attached')
|
|
if adapter is None:
|
|
adapter = self._adapter
|
|
assert adapter is not None
|
|
if not self._session.is_client:
|
|
raise RuntimeError('detach not supported')
|
|
|
|
self._detach()
|
|
|
|
# internal methods
|
|
|
|
def _close(self):
|
|
if self._session is not None:
|
|
try:
|
|
self._session.close()
|
|
except ClosedError:
|
|
pass
|
|
if self._adapter is not None:
|
|
try:
|
|
self._adapter.close()
|
|
except ClosedError:
|
|
pass
|
|
|
|
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)
|
|
else:
|
|
start = DebugAdapter.start
|
|
new_addr = Address.as_server if detachable else Address.as_client
|
|
addr = new_addr(None, self._addr.port)
|
|
self._adapter = start(argv, addr=addr, env=env, cwd=cwd)
|
|
|
|
if wait_for_connect:
|
|
wait_for_connect()
|
|
else:
|
|
wait_for_socket_server(addr)
|
|
self._attach(addr, **kwargs)
|
|
|
|
def _attach(self, addr, **kwargs):
|
|
if addr is None:
|
|
addr = self._addr
|
|
assert addr.host == 'localhost'
|
|
self._session = self.SESSION.create_client(addr, **kwargs)
|
|
|
|
def _detach(self):
|
|
session = self._session
|
|
if session is None:
|
|
return
|
|
self._session = None
|
|
try:
|
|
session.close()
|
|
except ClosedError:
|
|
pass
|
|
|
|
|
|
class DebugClient(_LifecycleClient):
|
|
"""A high-level abstraction of a debug client (i.e. editor)."""
|
|
|
|
# TODO: Manage breakpoints, etc.
|
|
# TODO: Add debugger methods here (e.g. "pause").
|
|
|
|
|
|
class EasyDebugClient(DebugClient):
|
|
|
|
def start_detached(self, argv):
|
|
"""Start an adapter in a background process."""
|
|
if self.closed:
|
|
raise RuntimeError('debug client closed')
|
|
if self._adapter is not None:
|
|
raise RuntimeError('debugger already running')
|
|
assert self._session is None
|
|
|
|
# TODO: Launch, handshake and detach?
|
|
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
|
|
if self.closed:
|
|
raise RuntimeError('debug client closed')
|
|
if self._adapter is not None:
|
|
raise RuntimeError('debugger already running')
|
|
assert self._session is None
|
|
addr = ('localhost', self._addr.port)
|
|
|
|
def run():
|
|
self._session = self.SESSION.create_server(addr, **kwargs)
|
|
t = new_hidden_thread(
|
|
target=run,
|
|
name='test.client',
|
|
)
|
|
t.start()
|
|
|
|
def wait():
|
|
t.join(timeout=self._connecttimeout)
|
|
if t.is_alive():
|
|
warnings.warn('timed out waiting for connection')
|
|
if self._session is None:
|
|
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)
|
|
|
|
return self._adapter, self._session
|
|
|
|
def launch_script(self, filename, *argv, **kwargs):
|
|
if self.closed:
|
|
raise RuntimeError('debug client closed')
|
|
if self._adapter is not None:
|
|
raise RuntimeError('debugger already running')
|
|
assert self._session is None
|
|
|
|
argv = [
|
|
filename,
|
|
] + list(argv)
|
|
if kwargs.pop('nodebug', False):
|
|
argv.insert(0, '--nodebug')
|
|
self._launch(argv, **kwargs)
|
|
return self._adapter, self._session
|
|
|
|
def launch_module(self, module, *argv, **kwargs):
|
|
if self.closed:
|
|
raise RuntimeError('debug client closed')
|
|
if self._adapter is not None:
|
|
raise RuntimeError('debugger already running')
|
|
assert self._session is None
|
|
|
|
argv = [
|
|
'-m',
|
|
module,
|
|
] + list(argv)
|
|
if kwargs.pop('nodebug', False):
|
|
argv.insert(0, '--nodebug')
|
|
self._launch(argv, **kwargs)
|
|
return self._adapter, self._session
|