Repository: microsoft/ptvsd

Issue:
Migrate pause, continue, next, stepIn, and stepOut



Discussion:

Resolving patch:
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
--- 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
@@ -7,7 +7,9 @@
 from _pydevd_bundle._debug_adapter.pydevd_schema import (SourceBreakpoint, ScopesResponseBody, Scope,
     VariablesResponseBody, SetVariableResponseBody, ModulesResponseBody, SourceResponseBody)
 from _pydevd_bundle.pydevd_api import PyDevdAPI
-from _pydevd_bundle.pydevd_comm_constants import CMD_RETURN
+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)
 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
@@ -184,6 +186,82 @@ def on_attach_request(self, py_db, request):
         response = pydevd_base_schema.build_response(request)
         return NetCommand(CMD_RETURN, 0, response.to_dict(), is_json=True)
 
+    def on_pause_request(self, py_db, request):
+        '''
+        :param PauseRequest request:
+        '''
+        arguments = request.arguments  # : :type arguments: PauseArguments
+        thread_id = arguments.threadId
+
+        self.api.request_suspend_thread(py_db, thread_id=thread_id)
+
+        response = pydevd_base_schema.build_response(request)
+        return NetCommand(CMD_RETURN, 0, response.to_dict(), is_json=True)
+
+    def on_continue_request(self, py_db, request):
+        '''
+        :param ContinueRequest request:
+        '''
+        arguments = request.arguments  # : :type arguments: ContinueArguments
+        thread_id = arguments.threadId
+
+        self.api.request_resume_thread(thread_id)
+
+        body = {'allThreadsContinued': thread_id == '*'}
+        response = pydevd_base_schema.build_response(request, kwargs={'body': body})
+        return NetCommand(CMD_RETURN, 0, response.to_dict(), is_json=True)
+
+    def on_next_request(self, py_db, request):
+        '''
+        :param NextRequest request:
+        '''
+        arguments = request.arguments  # : :type arguments: NextArguments
+        thread_id = arguments.threadId
+
+        if py_db.get_use_libraries_filter():
+            step_cmd_id = CMD_STEP_OVER_MY_CODE
+        else:
+            step_cmd_id = CMD_STEP_OVER
+
+        self.api.request_step(py_db, thread_id, step_cmd_id)
+
+        response = pydevd_base_schema.build_response(request)
+        return NetCommand(CMD_RETURN, 0, response.to_dict(), is_json=True)
+
+    def on_stepin_request(self, py_db, request):
+        '''
+        :param StepInRequest request:
+        '''
+        arguments = request.arguments  # : :type arguments: StepInArguments
+        thread_id = arguments.threadId
+
+        if py_db.get_use_libraries_filter():
+            step_cmd_id = CMD_STEP_INTO_MY_CODE
+        else:
+            step_cmd_id = CMD_STEP_INTO
+
+        self.api.request_step(py_db, thread_id, step_cmd_id)
+
+        response = pydevd_base_schema.build_response(request)
+        return NetCommand(CMD_RETURN, 0, response.to_dict(), is_json=True)
+
+    def on_stepout_request(self, py_db, request):
+        '''
+        :param StepOutRequest request:
+        '''
+        arguments = request.arguments  # : :type arguments: StepOutArguments
+        thread_id = arguments.threadId
+
+        if py_db.get_use_libraries_filter():
+            step_cmd_id = CMD_STEP_RETURN_MY_CODE
+        else:
+            step_cmd_id = CMD_STEP_RETURN
+
+        self.api.request_step(py_db, thread_id, step_cmd_id)
+
+        response = pydevd_base_schema.build_response(request)
+        return NetCommand(CMD_RETURN, 0, response.to_dict(), is_json=True)
+
     def _get_hit_condition_expression(self, hit_condition):
         '''Following hit condition values are supported
 
diff --git a/src/ptvsd/wrapper.py b/src/ptvsd/wrapper.py
--- a/src/ptvsd/wrapper.py
+++ b/src/ptvsd/wrapper.py
@@ -467,10 +467,13 @@ def make_json_packet(self, cmd_id, args):
         s = json.dumps(args)
         return seq, s
 
-    def pydevd_notify(self, cmd_id, args):
+    def pydevd_notify(self, cmd_id, args, is_json=False):
         if self.pipe_w is None:
             raise EOFError
-        seq, s = self.make_packet(cmd_id, args)
+        if is_json:
+            seq, s = self.make_json_packet(cmd_id, args)
+        else:
+            seq, s = self.make_packet(cmd_id, args)
         with self.lock:
             os.write(self.pipe_w, s.encode('utf8'))
 
@@ -478,7 +481,7 @@ def pydevd_request(self, loop, cmd_id, args, is_json=False):
         '''
         If is_json == True the args are expected to be a dict to be
         json-serialized with the request, otherwise it's expected
-        to be the text (to be concatenaded with the command id and
+        to be the text (to be concatenated with the command id and
         seq in the pydevd line-based protocol).
         '''
         if self.pipe_w is None:
@@ -1307,8 +1310,8 @@ def sleep(self):
 
     # PyDevd "socket" entry points (and related helpers)
 
-    def pydevd_notify(self, cmd_id, args):
-        return self._pydevd_notify(cmd_id, args)
+    def pydevd_notify(self, cmd_id, args, is_json=False):
+        return self._pydevd_notify(cmd_id, args, is_json=is_json)
 
     def pydevd_request(self, cmd_id, args, is_json=False):
         return self._pydevd_request(self.loop, cmd_id, args, is_json=is_json)
@@ -1673,8 +1676,6 @@ def on_modules(self, request, args):
 
     @async_handler
     def on_pause(self, request, args):
-        # TODO: docstring
-
         # Pause requests cannot be serviced until pydevd is fully initialized.
         with self.is_process_created_lock:
             if not self.is_process_created:
@@ -1685,45 +1686,70 @@ def on_pause(self, request, args):
                 )
                 return
 
+        pydevd_request = copy.deepcopy(request)
+        del pydevd_request['seq']  # A new seq should be created for pydevd.
+        try:
+            pydevd_request['arguments']['threadId'] = '*'
+        except KeyError:
+            pydevd_request['arguments'] = {'threadId': '*'}
+
         # Always suspend all threads.
-        self.pydevd_notify(pydevd_comm.CMD_THREAD_SUSPEND, '*')
+        yield self.pydevd_request(pydevd_comm.CMD_THREAD_SUSPEND,
+                                  pydevd_request, is_json=True)
         self.send_response(request)
 
     @async_handler
     def on_continue(self, request, args):
 
+        pydevd_request = copy.deepcopy(request)
+        del pydevd_request['seq']  # A new seq should be created for pydevd.
+        try:
+            pydevd_request['arguments']['threadId'] = '*'
+        except KeyError:
+            pydevd_request['arguments'] = {'threadId': '*'}
+
         # Always continue all threads.
-        self.pydevd_notify(pydevd_comm.CMD_THREAD_RUN, '*')
+        yield self.pydevd_request(pydevd_comm.CMD_THREAD_RUN,
+                                  pydevd_request, is_json=True)
         self.send_response(request, allThreadsContinued=True)
 
     @async_handler
     def on_next(self, request, args):
 
-        tid = self.thread_map.to_pydevd(int(args['threadId']))
-        if self._is_just_my_code_stepping_enabled():
-            self.pydevd_notify(pydevd_comm.CMD_STEP_OVER_MY_CODE, tid)
-        else:
-            self.pydevd_notify(pydevd_comm.CMD_STEP_OVER, tid)
+        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
+        cmd_id = pydevd_comm.CMD_STEP_OVER
+
+        yield self.pydevd_request(cmd_id, pydevd_request, is_json=True)
         self.send_response(request)
 
     @async_handler
     def on_stepIn(self, request, args):
 
-        tid = self.thread_map.to_pydevd(int(args['threadId']))
-        if self._is_just_my_code_stepping_enabled():
-            self.pydevd_notify(pydevd_comm.CMD_STEP_INTO_MY_CODE, tid)
-        else:
-            self.pydevd_notify(pydevd_comm.CMD_STEP_INTO, tid)
+        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
+        cmd_id = pydevd_comm.CMD_STEP_INTO
+
+        yield self.pydevd_request(cmd_id, pydevd_request, is_json=True)
         self.send_response(request)
 
     @async_handler
     def on_stepOut(self, request, args):
 
-        tid = self.thread_map.to_pydevd(int(args['threadId']))
-        if self._is_just_my_code_stepping_enabled():
-            self.pydevd_notify(pydevd_comm.CMD_STEP_RETURN_MY_CODE, tid)
-        else:
-            self.pydevd_notify(pydevd_comm.CMD_STEP_RETURN, tid)
+        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
+        cmd_id = pydevd_comm.CMD_STEP_RETURN
+
+        yield self.pydevd_request(cmd_id, pydevd_request, is_json=True)
         self.send_response(request)
 
     @async_handler


Test patch:
diff --git a/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_pause_continue.py b/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_pause_continue.py
new file mode 100644
--- /dev/null
+++ b/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_pause_continue.py
@@ -0,0 +1,13 @@
+import time
+def Call():
+    loop = True # Break here
+
+    count = 0
+    while(loop and (count < 200)):
+        time.sleep(0.1)
+        count += 1 # Pause here and change loop to False
+
+
+if __name__ == '__main__':
+    Call()
+    print('TEST SUCEEDED!')
\ No newline at end of file
diff --git a/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_stepping.py b/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_stepping.py
new file mode 100644
--- /dev/null
+++ b/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_stepping.py
@@ -0,0 +1,19 @@
+def step_over():
+    print("don't step here")
+
+def step_into():
+    print('step into')
+
+def step_out():
+    print('step out')  # Break here 1
+    print("don't step here")
+
+def Call():
+    step_over()  # Break here 2
+    step_into()
+    step_out()
+    print('stepped out')
+
+if __name__ == '__main__':
+    Call()
+    print('TEST SUCEEDED!')
\ No newline at end of file
diff --git a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py
--- a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py
+++ b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py
@@ -2,10 +2,10 @@
 
 from _pydevd_bundle._debug_adapter import pydevd_schema, pydevd_base_schema
 from _pydevd_bundle._debug_adapter.pydevd_base_schema import from_json
-from _pydevd_bundle._debug_adapter.pydevd_schema import ThreadEvent, ModuleEvent
+from _pydevd_bundle._debug_adapter.pydevd_schema import ThreadEvent, ModuleEvent, StoppedEvent
 from tests_python import debugger_unittest
 from tests_python.debugger_unittest import IS_JYTHON, REASON_STEP_INTO, REASON_STEP_OVER, \
-    REASON_CAUGHT_EXCEPTION
+    REASON_CAUGHT_EXCEPTION, REASON_THREAD_SUSPEND, REASON_STEP_RETURN
 import json
 from collections import namedtuple
 from _pydevd_bundle.pydevd_constants import int_types
@@ -778,38 +778,157 @@ def test_hex_variables(case_setup):
 
         # : :type variables_response: VariablesResponse
         variable_for_test_1, variable_for_test_2, variable_for_test_3, variable_for_test_4 = sorted(list(
-            v for v in variables_response.body.variables if v['name'].startswith('variable_for_test')
+            v for v in variables_response.body.variables if v['name'].startswith('variables_for_test')
         ), key=lambda v: v['name'])
-        assert variable_for_test_1 == [{
-            'name': 'variable_for_test_1',
+        assert variable_for_test_1 == {
+            'name': 'variables_for_test_1',
             'value': "0x64",
             'type': 'int',
-            'evaluateName': 'variable_for_test_1'
-        }]
+            'evaluateName': 'variables_for_test_1'
+        }
 
         assert isinstance(variable_for_test_2.pop('variablesReference'), int)
-        assert variable_for_test_2 == [{
-            'name': 'variable_for_test_2',
+        assert variable_for_test_2 == {
+            'name': 'variables_for_test_2',
             'value': "[0x1, 0xa, 0x64]",
             'type': 'list',
-            'evaluateName': 'variable_for_test_2'
-        }]
+            'evaluateName': 'variables_for_test_2'
+        }
 
         assert isinstance(variable_for_test_3.pop('variablesReference'), int)
-        assert variable_for_test_3 == [{
-            'name': 'variable_for_test_3',
+        assert variable_for_test_3 == {
+            'name': 'variables_for_test_3',
             'value': '{0xa: 0xa, 0x64: 0x64, 0x3e8: 0x3e8}',
             'type': 'dict',
-            'evaluateName': 'variable_for_test_3'
-        }]
+            'evaluateName': 'variables_for_test_3'
+        }
 
         assert isinstance(variable_for_test_4.pop('variablesReference'), int)
-        assert variable_for_test_4 == [{
-            'name': 'variable_for_test_4',
+        assert variable_for_test_4 == {
+            'name': 'variables_for_test_4',
             'value': '{(0x1, 0xa, 0x64): (0x2710, 0x186a0, 0x186a0)}',
             'type': 'dict',
-            'evaluateName': 'variable_for_test_4'
-        }]
+            'evaluateName': 'variables_for_test_4'
+        }
+
+        writer.write_run_thread(hit.thread_id)
+
+        writer.finished_ok = True
+
+
+def test_pause_and_continue(case_setup):
+    with case_setup.test_file('_debugger_case_pause_continue.py') as writer:
+        json_facade = JsonFacade(writer)
+
+        writer.write_set_protocol('http_json')
+        writer.write_add_breakpoint(writer.get_line_index_with_content('Break here'))
+
+        json_facade.write_make_initial_run()
+
+        hit = writer.wait_for_breakpoint_hit()
+
+        continue_request = json_facade.write_request(
+            pydevd_schema.ContinueRequest(pydevd_schema.ContinueArguments('*')))
+        continue_response = json_facade.wait_for_response(continue_request)
+        assert continue_response.body.allThreadsContinued
+
+        pause_request = json_facade.write_request(
+            pydevd_schema.PauseRequest(pydevd_schema.PauseArguments('*')))
+        pause_response = json_facade.wait_for_response(pause_request)
+        hit = writer.wait_for_breakpoint_hit(reason=REASON_THREAD_SUSPEND)
+
+        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))
+
+        scopes_request = json_facade.write_request(pydevd_schema.ScopesRequest(
+            pydevd_schema.ScopesArguments(stack_frame['id'])))
+        scopes_response = json_facade.wait_for_response(scopes_request)
+        scope = pydevd_schema.Scope(**next(iter(scopes_response.body.scopes)))
+        frame_variables_reference = scope.variablesReference
+
+        set_variable_request = json_facade.write_request(
+            pydevd_schema.SetVariableRequest(pydevd_schema.SetVariableArguments(
+                frame_variables_reference, 'loop', 'False'
+        )))
+        set_variable_response = json_facade.wait_for_response(set_variable_request)
+        set_variable_response_as_dict = set_variable_response.to_dict()['body']
+        assert set_variable_response_as_dict == {'value': "False", 'type': 'bool'}
+
+        continue_request = json_facade.write_request(
+        pydevd_schema.ContinueRequest(pydevd_schema.ContinueArguments('*')))
+        continue_response = json_facade.wait_for_response(continue_request)
+        assert continue_response.body.allThreadsContinued
+
+        writer.finished_ok = True
+
+
+def test_stepping(case_setup):
+    with case_setup.test_file('_debugger_case_stepping.py') as writer:
+        json_facade = JsonFacade(writer)
+
+        writer.write_set_protocol('http_json')
+        writer.write_add_breakpoint(writer.get_line_index_with_content('Break here 1'))
+        writer.write_add_breakpoint(writer.get_line_index_with_content('Break here 2'))
+
+        json_facade.write_make_initial_run()
+
+        hit = writer.wait_for_breakpoint_hit()
+
+        # Test Step-Over or 'next'
+        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))
+        before_step_over_line = stack_frame['line']
+
+        next_request = json_facade.write_request(
+            pydevd_schema.NextRequest(pydevd_schema.NextArguments(hit.thread_id)))
+        next_response = json_facade.wait_for_response(next_request)
+        hit = writer.wait_for_breakpoint_hit(REASON_STEP_OVER)
+
+        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))
+        after_step_over_line = stack_frame['line']
+
+        assert int(before_step_over_line) + 1 == int(after_step_over_line)
+
+        # Test step into or 'stepIn'
+        stepin_request = json_facade.write_request(
+            pydevd_schema.StepInRequest(pydevd_schema.StepInArguments(hit.thread_id)))
+        stepin_response = json_facade.wait_for_response(stepin_request)
+        hit = writer.wait_for_breakpoint_hit(REASON_STEP_INTO)
+
+        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['name'] == 'step_into'
+
+        # Test step return or 'stepOut'
+        writer.write_run_thread(hit.thread_id)
+        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['name'] == 'step_out'
+
+        stepout_request = json_facade.write_request(
+            pydevd_schema.StepInRequest(pydevd_schema.StepInArguments(hit.thread_id)))
+        stepout_response = json_facade.wait_for_response(stepout_request)
+        hit = writer.wait_for_breakpoint_hit(REASON_STEP_RETURN)
+
+        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['name'] == 'Call'
 
         writer.write_run_thread(hit.thread_id)