mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
Show return values (#761)
* Add show return value feature * Add show return value tests * Fix linter * Check presentation hints in tests
This commit is contained in:
parent
634926b6a8
commit
c68cd7be64
3 changed files with 169 additions and 13 deletions
|
|
@ -704,6 +704,7 @@ DEBUG_OPTIONS_PARSER = {
|
|||
'CLIENT_OS_TYPE': unquote,
|
||||
'DEBUG_STDLIB': bool_parser,
|
||||
'STOP_ON_ENTRY': bool_parser,
|
||||
'SHOW_RETURN_VALUE': bool_parser,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -719,6 +720,7 @@ DEBUG_OPTIONS_BY_FLAG = {
|
|||
'WindowsClient': 'CLIENT_OS_TYPE=WINDOWS',
|
||||
'UnixClient': 'CLIENT_OS_TYPE=UNIX',
|
||||
'StopOnEntry': 'STOP_ON_ENTRY=True',
|
||||
'ShowReturnValue': 'SHOW_RETURN_VALUE=True',
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1362,6 +1364,9 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
if opts.get('STOP_ON_ENTRY', False) and self.start_reason == 'launch':
|
||||
self.pydevd_request(pydevd_comm.CMD_STOP_ON_START, '1')
|
||||
|
||||
if opts.get('SHOW_RETURN_VALUE', False):
|
||||
self.pydevd_request(pydevd_comm.CMD_SHOW_RETURN_VALUES, '1\t1')
|
||||
|
||||
self._apply_code_stepping_settings()
|
||||
|
||||
def _is_just_my_code_stepping_enabled(self):
|
||||
|
|
@ -1708,6 +1713,7 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
|
||||
variables = VariablesSorter()
|
||||
for xvar in xvars:
|
||||
attributes = []
|
||||
var_name = unquote(xvar['name'])
|
||||
var_type = unquote(xvar['type'])
|
||||
var_value = unquote(xvar['value'])
|
||||
|
|
@ -1718,16 +1724,24 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
}
|
||||
|
||||
if self._is_raw_string(var_type):
|
||||
var['presentationHint'] = {'attributes': ['rawString']}
|
||||
attributes.append('rawString')
|
||||
|
||||
if bool(xvar['isContainer']):
|
||||
pyd_child = pyd_var + (var_name,)
|
||||
var['variablesReference'] = self.var_map.to_vscode(
|
||||
pyd_child, autogen=True)
|
||||
if bool(xvar['isRetVal']):
|
||||
attributes.append('readOnly')
|
||||
var['name'] = '(return) %s' % var_name
|
||||
else:
|
||||
if bool(xvar['isContainer']):
|
||||
pyd_child = pyd_var + (var_name,)
|
||||
var['variablesReference'] = self.var_map.to_vscode(
|
||||
pyd_child, autogen=True)
|
||||
|
||||
eval_name = self._get_variable_evaluate_name(pyd_var, var_name)
|
||||
if eval_name:
|
||||
var['evaluateName'] = eval_name
|
||||
eval_name = self._get_variable_evaluate_name(
|
||||
pyd_var, var_name)
|
||||
if eval_name:
|
||||
var['evaluateName'] = eval_name
|
||||
|
||||
if len(attributes) > 0:
|
||||
var['presentationHint'] = {'attributes': attributes}
|
||||
|
||||
variables.append(var)
|
||||
|
||||
|
|
@ -1779,18 +1793,23 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
@async_handler
|
||||
def on_setVariable(self, request, args):
|
||||
"""Handles DAP SetVariableRequest."""
|
||||
|
||||
var_name = args['name']
|
||||
var_value = args['value']
|
||||
vsc_var = int(args['variablesReference'])
|
||||
fmt = args.get('format', {})
|
||||
|
||||
if var_name.startswith('(return) '):
|
||||
self.send_error_response(
|
||||
request,
|
||||
'Cannot change return value.')
|
||||
return
|
||||
|
||||
try:
|
||||
pyd_var = self.var_map.to_pydevd(vsc_var)
|
||||
except KeyError:
|
||||
self.send_error_response(request)
|
||||
return
|
||||
|
||||
var_name = args['name']
|
||||
var_value = args['value']
|
||||
fmt = args.get('format', {})
|
||||
|
||||
lhs_expr = self._get_variable_evaluate_name(pyd_var, var_name)
|
||||
if not lhs_expr:
|
||||
lhs_expr = var_name
|
||||
|
|
|
|||
12
tests/resources/system_tests/test_misc/returnvalues.py
Normal file
12
tests/resources/system_tests/test_misc/returnvalues.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
class MyClass(object):
|
||||
def do_something(self):
|
||||
return 'did something'
|
||||
|
||||
|
||||
def my_func():
|
||||
return 'did more things'
|
||||
|
||||
|
||||
MyClass().do_something()
|
||||
my_func()
|
||||
print('done')
|
||||
|
|
@ -198,3 +198,128 @@ class StopOnEntryLaunchPackageTests(StopOnEntryTests):
|
|||
modulename='mymod',
|
||||
cwd=cwd,
|
||||
env=env))
|
||||
|
||||
|
||||
class ShowReturnValueTests(LifecycleTestsBase):
|
||||
def _debugger_step_next(self, session, thread_id):
|
||||
stopped = session.get_awaiter_for_event('stopped')
|
||||
req_next = session.send_request('next', threadId=thread_id)
|
||||
req_next.wait(timeout=2.0)
|
||||
stopped.wait(timeout=2.0)
|
||||
|
||||
def _get_variables(self, session, thread_id):
|
||||
req_stacktrace = session.send_request(
|
||||
'stackTrace',
|
||||
threadId=thread_id,
|
||||
)
|
||||
req_stacktrace.wait(timeout=2.0)
|
||||
|
||||
frames = req_stacktrace.resp.body['stackFrames']
|
||||
frame_id = frames[0]['id']
|
||||
|
||||
req_scopes = session.send_request(
|
||||
'scopes',
|
||||
frameId=frame_id,
|
||||
)
|
||||
req_scopes.wait(timeout=2.0)
|
||||
|
||||
scopes = req_scopes.resp.body['scopes']
|
||||
variables_reference = scopes[0]['variablesReference']
|
||||
|
||||
req_variables = session.send_request(
|
||||
'variables',
|
||||
variablesReference=variables_reference,
|
||||
)
|
||||
req_variables.wait(timeout=2.0)
|
||||
|
||||
return req_variables.resp.body['variables']
|
||||
|
||||
def run_test_return_values(self, debug_info, is_enabled):
|
||||
if is_enabled:
|
||||
options = {'debugOptions': ['RedirectOutput', 'ShowReturnValue']}
|
||||
else:
|
||||
options = {'debugOptions': ['RedirectOutput']}
|
||||
|
||||
breakpoints = [{
|
||||
'source': {
|
||||
'path': debug_info.filename
|
||||
},
|
||||
'breakpoints': [{'line': 10}, {'line': 11}, {'line': 12}],
|
||||
'lines': [10, 11, 12]
|
||||
}]
|
||||
with self.start_debugging(debug_info) as dbg:
|
||||
session = dbg.session
|
||||
stopped = session.get_awaiter_for_event('stopped')
|
||||
(_, req_launch_attach, _, _, _, _
|
||||
) = lifecycle_handshake(session, debug_info.starttype,
|
||||
options=options,
|
||||
breakpoints=breakpoints)
|
||||
req_launch_attach.wait(timeout=3.0)
|
||||
|
||||
stopped.wait()
|
||||
thread_id = stopped.event.body['threadId']
|
||||
|
||||
self._debugger_step_next(session, thread_id)
|
||||
variables = self._get_variables(session, thread_id)
|
||||
|
||||
return_values = list(v for v in variables
|
||||
if v['name'].startswith('(return) '))
|
||||
|
||||
if is_enabled:
|
||||
self.assertEqual(len(return_values), 1)
|
||||
self.assertEqual(return_values[0]['name'],
|
||||
'(return) MyClass.do_something')
|
||||
self.assertEqual(return_values[0]['value'],
|
||||
"'did something'")
|
||||
self.assertEqual(return_values[0]['type'],
|
||||
'str')
|
||||
attributes = return_values[0]['presentationHint']['attributes']
|
||||
self.assertTrue('readOnly' in attributes)
|
||||
else:
|
||||
self.assertEqual(len(return_values), 0)
|
||||
|
||||
self._debugger_step_next(session, thread_id)
|
||||
variables = self._get_variables(session, thread_id)
|
||||
|
||||
return_values = list(v for v in variables
|
||||
if v['name'].startswith('(return) '))
|
||||
|
||||
if is_enabled:
|
||||
self.assertEqual(len(return_values), 2)
|
||||
self.assertEqual(return_values[0]['name'],
|
||||
'(return) MyClass.do_something')
|
||||
self.assertEqual(return_values[0]['value'],
|
||||
"'did something'")
|
||||
self.assertEqual(return_values[0]['type'],
|
||||
'str')
|
||||
attributes = return_values[0]['presentationHint']['attributes']
|
||||
self.assertTrue('readOnly' in attributes)
|
||||
|
||||
self.assertEqual(return_values[1]['name'],
|
||||
'(return) my_func')
|
||||
self.assertEqual(return_values[1]['value'],
|
||||
"'did more things'")
|
||||
self.assertEqual(return_values[1]['type'],
|
||||
'str')
|
||||
attributes = return_values[1]['presentationHint']['attributes']
|
||||
self.assertTrue('readOnly' in attributes)
|
||||
else:
|
||||
self.assertEqual(len(return_values), 0)
|
||||
|
||||
exited = session.get_awaiter_for_event('exited')
|
||||
session.send_request('continue').wait(timeout=2.0)
|
||||
exited.wait(timeout=3.0)
|
||||
|
||||
def test_return_values_enabled(self):
|
||||
filename = TEST_FILES.resolve('returnvalues.py')
|
||||
cwd = os.path.dirname(filename)
|
||||
self.run_test_return_values(
|
||||
DebugInfo(filename=filename, cwd=cwd),
|
||||
is_enabled=True)
|
||||
|
||||
def test_return_values_disabled(self):
|
||||
filename = TEST_FILES.resolve('returnvalues.py')
|
||||
cwd = os.path.dirname(filename)
|
||||
self.run_test_return_values(
|
||||
DebugInfo(filename=filename, cwd=cwd),
|
||||
is_enabled=False)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue