From 35be1abb32f992ad99072468f9f00fcc2ced08b6 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Thu, 28 Mar 2019 12:57:45 -0700 Subject: [PATCH] Use json for goto and gotoTargets request. Fixes #1221 (#1270) * Use json for goto and gotoTargets request * Add pydevd tests for goto and gotoTargets * Add id maps to manage goto targets --- .../pydevd_process_net_command_json.py | 56 +++++++++++++++++- .../_debugger_case_set_next_statement.py | 4 +- .../pydevd/tests_python/test_debugger_json.py | 57 +++++++++++++++++++ src/ptvsd/wrapper.py | 31 +++++----- 4 files changed, 127 insertions(+), 21 deletions(-) diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py index df902eec..fce45164 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py @@ -6,11 +6,12 @@ import os from _pydevd_bundle._debug_adapter import pydevd_base_schema from _pydevd_bundle._debug_adapter.pydevd_schema import (SourceBreakpoint, ScopesResponseBody, Scope, - VariablesResponseBody, SetVariableResponseBody, ModulesResponseBody, SourceResponseBody) + VariablesResponseBody, SetVariableResponseBody, ModulesResponseBody, SourceResponseBody, + GotoTargetsResponseBody) from _pydevd_bundle.pydevd_api import PyDevdAPI from _pydevd_bundle.pydevd_comm_constants import ( CMD_RETURN, CMD_STEP_OVER_MY_CODE, CMD_STEP_OVER, CMD_STEP_INTO_MY_CODE, - CMD_STEP_INTO, CMD_STEP_RETURN_MY_CODE, CMD_STEP_RETURN) + CMD_STEP_INTO, CMD_STEP_RETURN_MY_CODE, CMD_STEP_RETURN, CMD_SET_NEXT_STATEMENT) from _pydevd_bundle.pydevd_filtering import ExcludeFilter from _pydevd_bundle.pydevd_json_debug_options import _extract_debug_options from _pydevd_bundle.pydevd_net_command import NetCommand @@ -78,6 +79,25 @@ def _convert_rules_to_exclude_filters(rules, filename_to_server, on_error): return exclude_filters +class IDMap(object): + def __init__(self): + self._value_to_key = {} + self._key_to_value = {} + self._next_id = partial(next, itertools.count(0)) + + def obtain_value(self, key): + return self._key_to_value[key] + + def obtain_key(self, value): + try: + key = self._value_to_key[value] + except KeyError: + key = self._next_id() + self._key_to_value[key] = value + self._value_to_key[value] = key + return key + + class _PyDevJsonCommandProcessor(object): def __init__(self, from_json): @@ -85,6 +105,7 @@ class _PyDevJsonCommandProcessor(object): self.api = PyDevdAPI() self._debug_options = {} self._next_breakpoint_id = partial(next, itertools.count(0)) + self._goto_targets_map = IDMap() def process_net_command_json(self, py_db, json_contents): ''' @@ -514,5 +535,36 @@ class _PyDevJsonCommandProcessor(object): response = pydevd_base_schema.build_response(request, kwargs=response_args) return NetCommand(CMD_RETURN, 0, response, is_json=True) + def on_gototargets_request(self, py_db, request): + path = request.arguments.source.path + line = request.arguments.line + target_id = self._goto_targets_map.obtain_key((path, line)) + target = { + 'id': target_id, + 'label': '{}:{}'.format(path, line), + 'line': line + } + body = GotoTargetsResponseBody(targets=[target]) + response_args = {'body': body} + response = pydevd_base_schema.build_response(request, kwargs=response_args) + return NetCommand(CMD_RETURN, 0, response, is_json=True) + + def on_goto_request(self, py_db, request): + target_id = int(request.arguments.targetId) + thread_id = request.arguments.threadId + try: + _, line = self._goto_targets_map.obtain_value(target_id) + except KeyError: + response = pydevd_base_schema.build_response(request, + kwargs={ + 'body': {}, + 'success': False, + 'message': 'Unknown goto target id: %d' % (target_id,), + }) + return NetCommand(CMD_RETURN, 0, response, is_json=True) + + self.api.request_set_next(py_db, thread_id, CMD_SET_NEXT_STATEMENT, line, '*') + response = pydevd_base_schema.build_response(request, kwargs={'body': {}}) + return NetCommand(CMD_RETURN, 0, response, is_json=True) process_net_command_json = _PyDevJsonCommandProcessor(pydevd_base_schema.from_json).process_net_command_json diff --git a/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_set_next_statement.py b/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_set_next_statement.py index 145f36d5..dcbd1af2 100644 --- a/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_set_next_statement.py +++ b/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_set_next_statement.py @@ -1,9 +1,9 @@ def method(): - a = 1 + a = 1 # Step here print('call %s' % (a,)) a = 2 print('call %s' % (a,)) - a = 3 + a = 3 # Break here if __name__ == '__main__': method() diff --git a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py index 64702a59..687cfead 100644 --- a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py +++ b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py @@ -1021,6 +1021,63 @@ def test_exception_details(case_setup): writer.finished_ok = True + +def test_goto(case_setup): + with case_setup.test_file('_debugger_case_set_next_statement.py') as writer: + json_facade = JsonFacade(writer) + + writer.write_set_protocol('http_json') + break_line = writer.get_line_index_with_content('Break here') + step_line = writer.get_line_index_with_content('Step here') + writer.write_add_breakpoint(break_line) + + json_facade.write_make_initial_run() + + hit = writer.wait_for_breakpoint_hit() + + stack_trace_request = json_facade.write_request( + pydevd_schema.StackTraceRequest(pydevd_schema.StackTraceArguments(threadId=hit.thread_id))) + stack_trace_response = json_facade.wait_for_response(stack_trace_request) + stack_frame = next(iter(stack_trace_response.body.stackFrames)) + assert stack_frame['line'] == break_line + + goto_targets_request = json_facade.write_request( + pydevd_schema.GotoTargetsRequest(pydevd_schema.GotoTargetsArguments( + source=pydevd_schema.Source(path=writer.TEST_FILE, sourceReference=0), + line=step_line))) + goto_targets_response = json_facade.wait_for_response(goto_targets_request) + target_id = goto_targets_response.body.targets[0]['id'] + + goto_request = json_facade.write_request( + pydevd_schema.GotoRequest(pydevd_schema.GotoArguments( + threadId=hit.thread_id, + targetId=12345))) + goto_response = json_facade.wait_for_response(goto_request) + assert not goto_response.success + + goto_request = json_facade.write_request( + pydevd_schema.GotoRequest(pydevd_schema.GotoArguments( + threadId=hit.thread_id, + targetId=target_id))) + goto_response = json_facade.wait_for_response(goto_request) + + hit = writer.wait_for_breakpoint_hit(reason='127') + + stack_trace_request = json_facade.write_request( + pydevd_schema.StackTraceRequest(pydevd_schema.StackTraceArguments(threadId=hit.thread_id))) + stack_trace_response = json_facade.wait_for_response(stack_trace_request) + stack_frame = next(iter(stack_trace_response.body.stackFrames)) + assert stack_frame['line'] == step_line + + writer.write_run_thread(hit.thread_id) + + # we hit the breakpoint again. Since we moved back + hit = writer.wait_for_breakpoint_hit() + writer.write_run_thread(hit.thread_id) + + writer.finished_ok = True + + @pytest.mark.skipif(IS_JYTHON, reason='Flaky on Jython.') def test_path_translation_and_source_reference(case_setup): diff --git a/src/ptvsd/wrapper.py b/src/ptvsd/wrapper.py index 0082b025..05495631 100644 --- a/src/ptvsd/wrapper.py +++ b/src/ptvsd/wrapper.py @@ -1237,7 +1237,6 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor): self.new_thread_lock = threading.Lock() # goto - self.goto_target_map = IDMap() self.current_goto_request = None # adapter state @@ -1761,14 +1760,12 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor): @async_handler def on_gotoTargets(self, request, args): - path = args['source']['path'] - line = args['line'] - target_id = self.goto_target_map.to_vscode((path, line), autogen=True) - self.send_response(request, targets=[{ - 'id': target_id, - 'label': '{}:{}'.format(path, line), - 'line': line, - }]) + pydevd_request = copy.deepcopy(request) + del pydevd_request['seq'] # A new seq should be created for pydevd. + cmd_id = pydevd_comm.CMD_GET_NEXT_STATEMENT_TARGETS + _, _, resp_args = yield self.pydevd_request(cmd_id, pydevd_request, is_json=True) + + self.send_response(request, targets=resp_args['body']['targets']) @async_handler def on_goto(self, request, args): @@ -1776,16 +1773,16 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor): self.send_error_response(request, 'Already processing a "goto" request.') return - vsc_tid = args['threadId'] - target_id = args['targetId'] - - pyd_tid = self.thread_map.to_pydevd(vsc_tid) - path, line = self.goto_target_map.to_pydevd(target_id) + pyd_tid = self.thread_map.to_pydevd(int(args['threadId'])) + pydevd_request = copy.deepcopy(request) + del pydevd_request['seq'] # A new seq should be created for pydevd. + pydevd_request['arguments']['threadId'] = pyd_tid self.current_goto_request = request - self.pydevd_notify( - pydevd_comm.CMD_SET_NEXT_STATEMENT, - '{}\t{}\t*'.format(pyd_tid, line)) + cmd_id = pydevd_comm.CMD_SET_NEXT_STATEMENT + yield self.pydevd_request(cmd_id, pydevd_request, is_json=True) + # response for this is received via set_next_statement event + # see on_pydevd_set_next_statement below @async_handler def on_setBreakpoints(self, request, args):