Skip to content
This repository has been archived by the owner on Aug 2, 2023. It is now read-only.

Commit

Permalink
Make it possible to evaluate without a frameId. Fixes #1716
Browse files Browse the repository at this point in the history
  • Loading branch information
fabioz committed Aug 30, 2019
1 parent 2a367b2 commit f45444f
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 46 deletions.
57 changes: 38 additions & 19 deletions src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -701,22 +701,25 @@ def on_evaluate_request(self, py_db, request):
# : :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
Expand Down
43 changes: 31 additions & 12 deletions src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,14 @@ def write_initialize(self, access_token, success=True):
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:
Expand Down Expand Up @@ -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)

Expand Down

0 comments on commit f45444f

Please sign in to comment.