mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
Account for the scope when changing variables (#1869)
* Support using scope during setVariable request * Fix other overloads to accept scope too * Use 3.9 compatible unions * Review feedback * Pydevd test wasn't actually validating
This commit is contained in:
parent
e01e6dd8a9
commit
f7d8963f99
8 changed files with 42 additions and 20 deletions
|
|
@ -934,7 +934,7 @@ def internal_change_variable_json(py_db, request):
|
|||
)
|
||||
return
|
||||
|
||||
child_var = variable.change_variable(arguments.name, arguments.value, py_db, fmt=fmt)
|
||||
child_var = variable.change_variable(arguments.name, arguments.value, py_db, fmt=fmt, scope=scope)
|
||||
|
||||
if child_var is None:
|
||||
_write_variable_response(py_db, request, value="", success=False, message="Unable to change: %s." % (arguments.name,))
|
||||
|
|
|
|||
|
|
@ -199,9 +199,9 @@ class PluginManager(object):
|
|||
|
||||
return None
|
||||
|
||||
def change_variable(self, frame, attr, expression):
|
||||
def change_variable(self, frame, attr, expression, scope=None):
|
||||
for plugin in self.active_plugins:
|
||||
ret = plugin.change_variable(frame, attr, expression, self.EMPTY_SENTINEL)
|
||||
ret = plugin.change_variable(frame, attr, expression, self.EMPTY_SENTINEL, scope)
|
||||
if ret is not self.EMPTY_SENTINEL:
|
||||
return ret
|
||||
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ class _ObjectVariable(_AbstractVariable):
|
|||
|
||||
return children_variables
|
||||
|
||||
def change_variable(self, name, value, py_db, fmt=None):
|
||||
def change_variable(self, name, value, py_db, fmt=None, scope: Optional[ScopeRequest]=None):
|
||||
children_variable = self.get_child_variable_named(name)
|
||||
if children_variable is None:
|
||||
return None
|
||||
|
|
@ -255,12 +255,10 @@ class _FrameVariable(_AbstractVariable):
|
|||
self._register_variable = register_variable
|
||||
self._register_variable(self)
|
||||
|
||||
def change_variable(self, name, value, py_db, fmt=None):
|
||||
def change_variable(self, name, value, py_db, fmt=None, scope: Optional[ScopeRequest]=None):
|
||||
frame = self.frame
|
||||
|
||||
pydevd_vars.change_attr_expression(frame, name, value, py_db)
|
||||
|
||||
return self.get_child_variable_named(name, fmt=fmt)
|
||||
pydevd_vars.change_attr_expression(frame, name, value, py_db, scope=scope)
|
||||
return self.get_child_variable_named(name, fmt=fmt, scope=scope)
|
||||
|
||||
@silence_warnings_decorator
|
||||
@overrides(_AbstractVariable.get_children_variables)
|
||||
|
|
|
|||
|
|
@ -15,11 +15,12 @@ import sys # @Reimport
|
|||
from _pydev_bundle._pydev_saved_modules import threading
|
||||
from _pydevd_bundle import pydevd_save_locals, pydevd_timeout, pydevd_constants
|
||||
from _pydev_bundle.pydev_imports import Exec, execfile
|
||||
from _pydevd_bundle.pydevd_utils import to_string
|
||||
from _pydevd_bundle.pydevd_utils import to_string, ScopeRequest
|
||||
import inspect
|
||||
from _pydevd_bundle.pydevd_daemon_thread import PyDBDaemonThread
|
||||
from _pydevd_bundle.pydevd_save_locals import update_globals_and_locals
|
||||
from functools import lru_cache
|
||||
from typing import Optional
|
||||
|
||||
SENTINEL_VALUE = []
|
||||
|
||||
|
|
@ -595,11 +596,15 @@ def evaluate_expression(py_db, frame, expression, is_exec):
|
|||
del frame
|
||||
|
||||
|
||||
def change_attr_expression(frame, attr, expression, dbg, value=SENTINEL_VALUE):
|
||||
def change_attr_expression(frame, attr, expression, dbg, value=SENTINEL_VALUE, /, scope: Optional[ScopeRequest]=None):
|
||||
"""Changes some attribute in a given frame."""
|
||||
if frame is None:
|
||||
return
|
||||
|
||||
if scope is not None:
|
||||
assert isinstance(scope, ScopeRequest)
|
||||
scope = scope.scope
|
||||
|
||||
try:
|
||||
expression = expression.replace("@LINE@", "\n")
|
||||
|
||||
|
|
@ -608,13 +613,15 @@ def change_attr_expression(frame, attr, expression, dbg, value=SENTINEL_VALUE):
|
|||
if result is not dbg.plugin.EMPTY_SENTINEL:
|
||||
return result
|
||||
|
||||
if attr[:7] == "Globals":
|
||||
attr = attr[8:]
|
||||
if attr[:7] == "Globals" or scope == "globals":
|
||||
attr = attr[8:] if attr.startswith("Globals") else attr
|
||||
if attr in frame.f_globals:
|
||||
if value is SENTINEL_VALUE:
|
||||
value = eval(expression, frame.f_globals, frame.f_locals)
|
||||
frame.f_globals[attr] = value
|
||||
return frame.f_globals[attr]
|
||||
else:
|
||||
raise VariableError("Attribute %s not found in globals" % attr)
|
||||
else:
|
||||
if "." not in attr: # i.e.: if we have a '.', we're changing some attribute of a local var.
|
||||
if pydevd_save_locals.is_save_locals_available():
|
||||
|
|
@ -631,8 +638,9 @@ def change_attr_expression(frame, attr, expression, dbg, value=SENTINEL_VALUE):
|
|||
Exec("%s=%s" % (attr, expression), frame.f_globals, frame.f_locals)
|
||||
return result
|
||||
|
||||
except Exception:
|
||||
pydev_log.exception()
|
||||
except Exception as e:
|
||||
pydev_log.exception(e)
|
||||
|
||||
|
||||
|
||||
MAXIMUM_ARRAY_SIZE = 100
|
||||
|
|
|
|||
|
|
@ -427,10 +427,10 @@ class DjangoTemplateSyntaxErrorFrame(object):
|
|||
self.f_trace = None
|
||||
|
||||
|
||||
def change_variable(frame, attr, expression, default):
|
||||
def change_variable(frame, attr, expression, default, scope=None):
|
||||
if isinstance(frame, DjangoTemplateFrame):
|
||||
result = eval(expression, frame.f_globals, frame.f_locals)
|
||||
frame._change_variable(attr, result)
|
||||
frame._change_variable(attr, result, scope=scope)
|
||||
return result
|
||||
return default
|
||||
|
||||
|
|
|
|||
|
|
@ -249,10 +249,10 @@ class Jinja2TemplateSyntaxErrorFrame(object):
|
|||
self.f_trace = None
|
||||
|
||||
|
||||
def change_variable(frame, attr, expression, default):
|
||||
def change_variable(frame, attr, expression, default, scope=None):
|
||||
if isinstance(frame, Jinja2TemplateFrame):
|
||||
result = eval(expression, frame.f_globals, frame.f_locals)
|
||||
frame._change_variable(frame.f_back, attr, result)
|
||||
frame._change_variable(frame.f_back, attr, result, scope=scope)
|
||||
return result
|
||||
return default
|
||||
|
||||
|
|
|
|||
|
|
@ -9,4 +9,5 @@ class SomeClass(object):
|
|||
|
||||
if __name__ == '__main__':
|
||||
SomeClass().method()
|
||||
print('second breakpoint')
|
||||
print('TEST SUCEEDED')
|
||||
|
|
|
|||
|
|
@ -5931,13 +5931,28 @@ def test_send_json_message(case_setup_dap):
|
|||
def test_global_scope(case_setup_dap):
|
||||
with case_setup_dap.test_file("_debugger_case_globals.py") as writer:
|
||||
json_facade = JsonFacade(writer)
|
||||
json_facade.write_set_breakpoints(writer.get_line_index_with_content("breakpoint here"))
|
||||
break1 = writer.get_line_index_with_content("breakpoint here")
|
||||
break2 = writer.get_line_index_with_content("second breakpoint")
|
||||
json_facade.write_set_breakpoints([break1, break2])
|
||||
|
||||
json_facade.write_make_initial_run()
|
||||
json_hit = json_facade.wait_for_thread_stopped()
|
||||
|
||||
local_var = json_facade.get_global_var(json_hit.frame_id, "in_global_scope")
|
||||
assert local_var.value == "'in_global_scope_value'"
|
||||
|
||||
scopes_request = json_facade.write_request(pydevd_schema.ScopesRequest(pydevd_schema.ScopesArguments(json_hit.frame_id)))
|
||||
scopes_response = json_facade.wait_for_response(scopes_request)
|
||||
assert len(scopes_response.body.scopes) == 2
|
||||
assert scopes_response.body.scopes[0]["name"] == "Locals"
|
||||
assert scopes_response.body.scopes[1]["name"] == "Globals"
|
||||
globals_varreference = scopes_response.body.scopes[1]["variablesReference"]
|
||||
|
||||
json_facade.write_set_variable(globals_varreference, "in_global_scope", "'new_value'")
|
||||
json_facade.write_continue()
|
||||
json_hit2 = json_facade.wait_for_thread_stopped()
|
||||
global_var = json_facade.get_global_var(json_hit2.frame_id, "in_global_scope")
|
||||
assert global_var.value == "'new_value'"
|
||||
json_facade.write_continue()
|
||||
|
||||
writer.finished_ok = True
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue