Repository: microsoft/ptvsd

Issue:
Tests for local attach using pid




Discussion:

Resolving patch:
diff --git a/src/ptvsd/__main__.py b/src/ptvsd/__main__.py
--- a/src/ptvsd/__main__.py
+++ b/src/ptvsd/__main__.py
@@ -298,6 +298,7 @@ def escape(ch):
     pid = ptvsd.options.target
     host = quoted_str(ptvsd.options.host)
     port = ptvsd.options.port
+    client = ptvsd.options.client
 
     ptvsd_path = os.path.abspath(os.path.join(ptvsd.__file__, '../..'))
     if isinstance(ptvsd_path, bytes):
@@ -315,11 +316,15 @@ def escape(ch):
 del sys.path[0]
 
 import ptvsd.options
-ptvsd.options.client = True
+ptvsd.options.client = {client}
 ptvsd.options.host = {host}
 ptvsd.options.port = {port}
 
-ptvsd.enable_attach()
+if ptvsd.options.client:
+    from ptvsd._remote import attach
+    attach(({host}, {port}))
+else:
+    ptvsd.enable_attach()
 '''.format(**locals())
     print(code)
 


Test patch:
diff --git a/tests/func/test_attach.py b/tests/func/test_attach.py
--- a/tests/func/test_attach.py
+++ b/tests/func/test_attach.py
@@ -9,6 +9,7 @@
 from tests.helpers.session import DebugSession
 from tests.helpers.pathutils import get_test_root
 from tests.helpers.timeline import Event
+from tests.helpers.pattern import ANY
 
 
 @pytest.mark.parametrize('wait_for_attach', ['waitOn', 'waitOff'])
@@ -104,3 +105,36 @@ def code_to_debug():
             assert 12 == frames[0]['line']
             session.send_request('continue').wait_for_response(freeze=False)
             session.wait_for_exit()
+
+
+@pytest.mark.parametrize('start_method', ['attach_pid'])
+@pytest.mark.parametrize('run_as', ['file', 'module', 'code'])
+@pytest.mark.skip(reason='Enable after #846, #863 and #1144 are fixed')
+def test_attaching_by_pid(pyfile, run_as, start_method):
+    @pyfile
+    def code_to_debug():
+        # import_and_enable_debugger()
+        import time
+        def do_something(i):
+            time.sleep(0.1)
+            print(i)
+        for i in range(100):
+            do_something(i)
+    bp_line = 5
+    with DebugSession() as session:
+        session.initialize(
+            target=(run_as, code_to_debug),
+            start_method=start_method,
+            ignore_unobserved=[Event('continued')],
+        )
+        session.set_breakpoints(code_to_debug, [bp_line])
+        session.start_debugging()
+        hit = session.wait_for_thread_stopped()
+        frames = hit.stacktrace.body['stackFrames']
+        assert bp_line == frames[0]['line']
+
+        # remove breakpoint and continue
+        session.set_breakpoints(code_to_debug, [])
+        session.send_request('continue').wait_for_response(freeze=False)
+        session.wait_for_next(Event('output', ANY.dict_with({'category': 'stdout'})))
+        session.wait_for_exit()
diff --git a/tests/helpers/session.py b/tests/helpers/session.py
--- a/tests/helpers/session.py
+++ b/tests/helpers/session.py
@@ -165,8 +165,8 @@ def _get_argv_for_attach_using_cmdline(self):
     def _get_argv_for_attach_using_pid(self):
         argv = [sys.executable]
         argv += [os.path.dirname(ptvsd.__file__)]
-        argv += ['--host', 'localhost', '--port', str(self.ptvsd_port)]
-        argv += ['--pid', str(self.pid)]
+        argv += ['--client', '--host', 'localhost', '--port', str(self.ptvsd_port)]
+        # argv += ['--pid', '<pid>']  # pid value to be appended later
         return argv
 
     def _get_target(self):
@@ -226,32 +226,41 @@ def initialize(self, **kwargs):
         """
         self._setup_session(**kwargs)
         print('Initializing debug session for ptvsd#%d' % self.ptvsd_port)
-        argv = []
+        dbg_argv = []
+        usr_argv = []
         if self.start_method == 'launch':
             self._listen()
-            argv += self._get_argv_for_launch()
+            dbg_argv += self._get_argv_for_launch()
         elif self.start_method == 'attach_socket_cmdline':
-            argv += self._get_argv_for_attach_using_cmdline()
+            dbg_argv += self._get_argv_for_attach_using_cmdline()
         elif self.start_method == 'attach_socket_import':
-            argv += self._get_argv_for_attach_using_import()
-            # TODO: Remove adding ot python path after enabling TOX
+            dbg_argv += self._get_argv_for_attach_using_import()
+            # TODO: Remove adding to python path after enabling TOX
             ptvsd_path = os.path.dirname(os.path.dirname(ptvsd.__main__.__file__))
             self.env['PYTHONPATH'] = ptvsd_path + os.pathsep + self.env['PYTHONPATH']
             self.env[PTVSD_ENABLE_KEY] = '1'
             self.env[PTVSD_HOST_KEY] = 'localhost'
             self.env[PTVSD_PORT_KEY] = str(self.ptvsd_port)
         elif self.start_method == 'attach_pid':
-            argv += self._get_argv_for_attach_using_pid()
+            self._listen()
+            dbg_argv += self._get_argv_for_attach_using_pid()
         else:
             pytest.fail()
 
         if self.no_debug:
-            argv += ['--nodebug']
+            dbg_argv += ['--nodebug']
 
-        argv += self._get_target()
+        if self.start_method == 'attach_pid':
+            usr_argv += [sys.executable]
+            usr_argv += self._get_target()
+        else:
+            dbg_argv += self._get_target()
 
         if self.program_args:
-            argv += list(self.program_args)
+            if self.start_method == 'attach_pid':
+                usr_argv += list(self.program_args)
+            else:
+                dbg_argv += list(self.program_args)
 
         if self.multiprocess and 'Multiprocess' not in self.debug_options:
             self.debug_options += ['Multiprocess']
@@ -266,9 +275,13 @@ def initialize(self, **kwargs):
         print('Target: (%s) %s' % self.target)
         print('Current directory: %s' % self.cwd)
         print('PYTHONPATH: %s' % self.env['PYTHONPATH'])
-        print('Spawning %r' % argv)
+        if self.start_method == 'attach_pid':
+            print('Spawning %r' % usr_argv)
+        else:
+            print('Spawning %r' % dbg_argv)
 
-        self.process = subprocess.Popen(argv, env=self.env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.cwd)
+        spawn_args = usr_argv if self.start_method == 'attach_pid' else dbg_argv
+        self.process = subprocess.Popen(spawn_args, env=self.env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.cwd)
         self.pid = self.process.pid
         self.psutil_process = psutil.Process(self.pid)
         self.is_running = True
@@ -277,7 +290,15 @@ def initialize(self, **kwargs):
         self._capture_output(self.process.stdout, 'OUT')
         self._capture_output(self.process.stderr, 'ERR')
 
-        if self.start_method != 'launch':
+        if self.start_method == 'attach_pid':
+            # This is a temp process spawned to inject debugger into the
+            # running process
+            dbg_argv += ['--pid', str(self.pid)]
+            print('Spawning %r' % dbg_argv)
+            temp_process = subprocess.Popen(dbg_argv)
+            print('temp process has pid=%d' % temp_process.pid)
+
+        if self.start_method not in ('launch', 'attach_pid'):
             self.connect()
         self.connected.wait()
         assert self.ptvsd_port