Make it possible to evaluate without a frameId. Fixes #1716

This commit is contained in:
Fabio Zadrozny 2019-08-28 14:41:25 -03:00
parent 2a367b2f4b
commit f45444f9f9
3 changed files with 87 additions and 46 deletions

View file

@ -897,10 +897,14 @@ def _evaluate_response(py_db, request, result, error_message=''):
py_db.writer.add_command(NetCommand(CMD_RETURN, 0, variables_response, is_json=True))
_global_frame = None
def internal_evaluate_expression_json(py_db, request, thread_id):
'''
:param EvaluateRequest request:
'''
global _global_frame
# : :type arguments: EvaluateArguments
arguments = request.arguments
@ -918,28 +922,43 @@ def internal_evaluate_expression_json(py_db, request, thread_id):
_evaluate_response(py_db, request, '', error_message='Expression is not valid utf-8.')
raise
frame = py_db.find_frame(thread_id, frame_id)
result = pydevd_vars.evaluate_expression(py_db, frame, expression, is_exec=False)
is_error = isinstance(result, ExceptionOnEvaluate)
try_exec = False
if frame_id is None:
if _global_frame is None:
# Lazily create a frame to be used for evaluation with no frame id.
if is_error:
if context == 'hover':
_evaluate_response(py_db, request, result='')
return
def __create_frame():
yield sys._getframe()
elif context == 'repl':
try:
pydevd_vars.evaluate_expression(py_db, frame, expression, is_exec=True)
except Exception as ex:
err = ''.join(traceback.format_exception_only(type(ex), ex))
# Currently there is an issue in VSC where returning success=false for an
# eval request, in repl context, VSC does not show the error response in
# the debug console. So return the error message in result as well.
_evaluate_response(py_db, request, result=err, error_message=err)
_global_frame = next(__create_frame())
frame = _global_frame
try_exec = True # Always exec in this case
eval_result = None
else:
frame = py_db.find_frame(thread_id, frame_id)
eval_result = pydevd_vars.evaluate_expression(py_db, frame, expression, is_exec=False)
is_error = isinstance(eval_result, ExceptionOnEvaluate)
if is_error:
if context == 'hover': # In a hover it doesn't make sense to do an exec.
_evaluate_response(py_db, request, result='')
return
# No result on exec.
_evaluate_response(py_db, request, result='')
else:
try_exec = context == 'repl'
if try_exec:
try:
pydevd_vars.evaluate_expression(py_db, frame, expression, is_exec=True)
except Exception as ex:
err = ''.join(traceback.format_exception_only(type(ex), ex))
# Currently there is an issue in VSC where returning success=false for an
# eval request, in repl context, VSC does not show the error response in
# the debug console. So return the error message in result as well.
_evaluate_response(py_db, request, result=err, error_message=err)
return
# No result on exec.
_evaluate_response(py_db, request, result='')
return
# Ok, we have the result (could be an error), let's put it into the saved variables.
frame_tracker = py_db.suspended_frames_manager.get_frame_tracker(thread_id)
@ -948,7 +967,7 @@ def internal_evaluate_expression_json(py_db, request, thread_id):
_evaluate_response(py_db, request, result='', error_message='Thread id: %s is not current thread id.' % (thread_id,))
return
variable = frame_tracker.obtain_as_variable(expression, result, frame=frame)
variable = frame_tracker.obtain_as_variable(expression, eval_result, frame=frame)
var_data = variable.get_var_data(fmt=fmt)
body = pydevd_schema.EvaluateResponseBody(

View file

@ -701,22 +701,25 @@ class _PyDevJsonCommandProcessor(object):
# : :type arguments: EvaluateArguments
arguments = request.arguments
thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
arguments.frameId)
if thread_id is not None:
self.api.request_exec_or_evaluate_json(
py_db, request, thread_id)
if arguments.frameId is None:
self.api.request_exec_or_evaluate_json(py_db, request, thread_id='*')
else:
body = EvaluateResponseBody('', 0)
response = pydevd_base_schema.build_response(
request,
kwargs={
'body': body,
'success': False,
'message': 'Unable to find thread for evaluation.'
})
return NetCommand(CMD_RETURN, 0, response, is_json=True)
thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
arguments.frameId)
if thread_id is not None:
self.api.request_exec_or_evaluate_json(
py_db, request, thread_id)
else:
body = EvaluateResponseBody('', 0)
response = pydevd_base_schema.build_response(
request,
kwargs={
'body': body,
'success': False,
'message': 'Unable to find thread for evaluation.'
})
return NetCommand(CMD_RETURN, 0, response, is_json=True)
def on_setexpression_request(self, py_db, request):
# : :type arguments: SetExpressionArguments

View file

@ -416,6 +416,14 @@ class JsonFacade(object):
assert isinstance(process_id, int)
return response
def evaluate(self, expression, frameId=None, context=None, fmt=None, success=True):
eval_request = self.write_request(
pydevd_schema.EvaluateRequest(pydevd_schema.EvaluateArguments(
expression, frameId=frameId, context=context, format=fmt)))
eval_response = self.wait_for_response(eval_request)
assert eval_response.success == success
return eval_response
def test_case_json_logpoints(case_setup):
with case_setup.test_file('_debugger_case_change_breaks.py') as writer:
@ -1803,25 +1811,36 @@ def test_evaluate(case_setup):
stack_frame_id = stack_frame['id']
# Test evaluate request that results in 'eval'
eval_request = json_facade.write_request(
pydevd_schema.EvaluateRequest(pydevd_schema.EvaluateArguments('var_1', frameId=stack_frame_id, context='repl')))
eval_response = json_facade.wait_for_response(eval_request)
eval_response = json_facade.evaluate('var_1', frameId=stack_frame_id, context='repl')
assert eval_response.body.result == '5'
assert eval_response.body.type == 'int'
# Test evaluate request that results in 'exec'
exec_request = json_facade.write_request(
pydevd_schema.EvaluateRequest(pydevd_schema.EvaluateArguments('var_1 = 6', frameId=stack_frame_id, context='repl')))
exec_response = json_facade.wait_for_response(exec_request)
exec_response = json_facade.evaluate('var_1 = 6', frameId=stack_frame_id, context='repl')
assert exec_response.body.result == ''
# Test evaluate request that results in 'exec' but fails
exec_request = json_facade.write_request(
pydevd_schema.EvaluateRequest(pydevd_schema.EvaluateArguments('var_1 = "abc"/6', frameId=stack_frame_id, context='repl')))
exec_response = json_facade.wait_for_response(exec_request)
assert exec_response.success == False
assert exec_response.body.result.find('TypeError') > -1
assert exec_response.message.find('TypeError') > -1
exec_response = json_facade.evaluate(
'var_1 = "abc"/6', frameId=stack_frame_id, context='repl', success=False)
assert 'TypeError' in exec_response.body.result
assert 'TypeError' in exec_response.message
# Evaluate without a frameId.
# Error because 'foo_value' is not set in 'sys'.
exec_response = json_facade.evaluate('import email;email.foo_value', success=False)
assert 'AttributeError' in exec_response.body.result
assert 'AttributeError' in exec_response.message
# Reading foo_value didn't work, but 'email' should be in the namespace now.
json_facade.evaluate('email.foo_value=True')
# Ok, 'foo_value' is now set in 'email' module.
exec_response = json_facade.evaluate('email.foo_value')
# We don't actually get variables without a frameId, we can just evaluate and observe side effects
# (so, the result is always empty -- or an error).
assert exec_response.body.result == ''
json_facade.write_continue(wait_for_response=False)