mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
Add safety checks to debug test framework to prevent accidental misuse.
This commit is contained in:
parent
679bda4745
commit
cd5ebd1274
2 changed files with 48 additions and 25 deletions
|
|
@ -20,6 +20,8 @@ class DebugConfig(collections.MutableMapping):
|
|||
In addition, it exposes high-level wrappers over "env" and "debugOptions".
|
||||
"""
|
||||
|
||||
__slots__ = ["_dict", "_env", "_debug_options"]
|
||||
|
||||
# Valid configuration properties. Keys are names, and values are defaults that
|
||||
# are assumed by the adapter and/or the server if the property is not specified.
|
||||
# If the property is required, or if the default is computed in such a way that
|
||||
|
|
@ -69,8 +71,8 @@ class DebugConfig(collections.MutableMapping):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._dict = dict(*args, **kwargs)
|
||||
self.env = self.Env(self)
|
||||
self.debug_options = self.DebugOptions(self)
|
||||
self._env = self.Env(self)
|
||||
self._debug_options = self.DebugOptions(self)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._dict)
|
||||
|
|
@ -157,6 +159,14 @@ class DebugConfig(collections.MutableMapping):
|
|||
if self["waitOnAbnormalExit"]:
|
||||
self.debug_options.add("WaitOnAbnormalExit")
|
||||
|
||||
@property
|
||||
def env(self):
|
||||
return self._env
|
||||
|
||||
@property
|
||||
def debug_options(self):
|
||||
return self._debug_options
|
||||
|
||||
class Env(collections.MutableMapping):
|
||||
"""Wraps config["env"], automatically creating and destroying it as needed.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
import collections
|
||||
import contextlib
|
||||
import itertools
|
||||
import os
|
||||
import psutil
|
||||
|
|
@ -510,17 +509,6 @@ class Session(object):
|
|||
self.config.normalize()
|
||||
start_request = self.send_request(method, self.config)
|
||||
|
||||
def wait_for_process_event():
|
||||
process = self.wait_for_next_event("process", freeze=False)
|
||||
assert process == some.dict.containing(
|
||||
{
|
||||
"startMethod": self.start_request.command,
|
||||
"name": some.str,
|
||||
"isLocalProcess": True,
|
||||
"systemProcessId": some.int,
|
||||
}
|
||||
)
|
||||
|
||||
# Depending on whether it's "noDebug" or not, we either get the "initialized"
|
||||
# event, or an immediate response to our request.
|
||||
self.timeline.wait_until_realized(
|
||||
|
|
@ -531,21 +519,35 @@ class Session(object):
|
|||
if start_request.response is not None:
|
||||
# It was an immediate response - configuration is not possible. Just get
|
||||
# the "process" event, and return to caller.
|
||||
return wait_for_process_event()
|
||||
return self.wait_for_process()
|
||||
|
||||
# We got "initialized" - now we need to yield to the caller, so that it can
|
||||
# configure the session before it starts running, and then give control back
|
||||
# to us to finalize the configuration sequence. A nested context manager is
|
||||
# used to ensure that all code up to this point executes eagerly.
|
||||
# configure the session before it starts running.
|
||||
return self._ConfigurationContextManager(self)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def configure():
|
||||
yield
|
||||
self.request("configurationDone")
|
||||
start_request.wait_for_response()
|
||||
wait_for_process_event()
|
||||
class _ConfigurationContextManager(object):
|
||||
"""Handles the start configuration sequence from "initialized" event until
|
||||
start_request receives a response.
|
||||
"""
|
||||
|
||||
return configure()
|
||||
def __init__(self, session):
|
||||
self.session = session
|
||||
self._entered = False
|
||||
|
||||
def __enter__(self):
|
||||
self._entered = True
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.session.request("configurationDone")
|
||||
self.session.start_request.wait_for_response()
|
||||
self.session.wait_for_process()
|
||||
|
||||
def __del__(self):
|
||||
assert self._entered, (
|
||||
"The return value of request_launch() or request_attach() must be "
|
||||
"used in a with-statement."
|
||||
)
|
||||
|
||||
def request_launch(self):
|
||||
if "PYTHONPATH" in self.config.env:
|
||||
|
|
@ -658,6 +660,17 @@ class Session(object):
|
|||
timeline.Event(event, body), freeze=freeze
|
||||
).body
|
||||
|
||||
def wait_for_process(self):
|
||||
process = self.wait_for_next_event("process", freeze=False)
|
||||
assert process == some.dict.containing(
|
||||
{
|
||||
"startMethod": self.start_request.command,
|
||||
"name": some.str,
|
||||
"isLocalProcess": True,
|
||||
"systemProcessId": some.int,
|
||||
}
|
||||
)
|
||||
|
||||
def wait_for_stop(
|
||||
self,
|
||||
reason=some.str,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue