Merge pull request #972 from int19h/966

Fix #966: Sub process debugging not working with PTVSD
This commit is contained in:
Pavel Minaev 2018-10-30 17:58:34 -07:00 committed by GitHub
commit f63825fa15
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 79 additions and 29 deletions

View file

@ -1,7 +1,8 @@
[flake8]
ignore = W,
E24,E121,E123,E125,E126,E221,E226,E266,E704,
E265,E722,E501,E731,E306,E401,E302,E222,E303
E265,E722,E501,E731,E306,E401,E302,E222,E303,
E402
exclude =
ptvsd/_vendored/pydevd,
./.eggs,

View file

@ -2,10 +2,37 @@
# Licensed under the MIT License. See LICENSE in the project root
# for license information.
from __future__ import print_function, with_statement, absolute_import
import argparse
import os.path
import sys
# ptvsd can also be invoked directly rather than via -m. In this case, the
# first entry on sys.path is the one added automatically by Python for the
# directory containing this file. This means that 1) import ptvsd will not
# work, since we need the parent directory of ptvsd/ to be on path, rather
# than ptvsd/ itself, and 2) many other absolute imports will break, because
# they will be resolved relative to ptvsd/ - e.g. import socket will then
# try to import ptvsd/socket.py!
#
# To fix this, we need to replace the automatically added entry such that it
# points at the parent directory instead, import ptvsd from that directory,
# and then remove than entry altogether so that it doesn't affect any further
# imports. For example, suppose the user did:
#
# python /foo/bar/ptvsd ...
#
# At the beginning of this script, sys.path will contain '/foo/bar/ptvsd' as
# the first entry. What we want is to replace it with '/foo/bar', then import
# ptvsd with that in effect, and then remove it before continuing execution.
if __name__ == '__main__' and 'ptvsd' not in sys.modules:
sys.path[0] = os.path.dirname(sys.path[0])
import ptvsd # noqa
del sys.path[0]
from ptvsd import multiproc, options
from ptvsd._attach import attach_main
from ptvsd._local import debug_main, run_main
@ -13,6 +40,17 @@ from ptvsd.socket import Address
from ptvsd.version import __version__, __author__ # noqa
# When forming the command line involving __main__.py, it might be tempting to
# import it as a module, and then use its __file__. However, that does not work
# reliably, because __file__ can be a relative path - and when it is relative,
# that's relative to the current directory at the time import was done, which
# may be different from the current directory at the time the path is used.
#
# So, to be able to correctly locate the script at any point, we compute the
# absolute path at import time.
__file__ = os.path.abspath(__file__)
##################################
# the script

View file

@ -18,10 +18,10 @@ try:
except ImportError:
import Queue as queue
from . import options
from .socket import create_server, create_client
from .messaging import JsonIOStream, JsonMessageChannel
from ._util import new_hidden_thread, debug
from ptvsd import options
from ptvsd.socket import create_server, create_client
from ptvsd.messaging import JsonIOStream, JsonMessageChannel
from ptvsd._util import new_hidden_thread, debug
from _pydev_bundle import pydev_monkey
from _pydevd_bundle.pydevd_comm import get_global_debugger
@ -168,9 +168,7 @@ def patch_args(args):
the result should be:
python -R -Q warn -m ptvsd --host localhost --port 0 ... -m app
Note that the first -m above is interpreted by Python, and the second by ptvsd.
python -R -Q warn .../ptvsd/__main__.py --host localhost --port 0 ... -m app
"""
if not options.multiprocess:
return args
@ -242,8 +240,9 @@ def patch_args(args):
# Now we need to inject the ptvsd invocation right before the target. The target
# itself can remain as is, because ptvsd is compatible with Python in that respect.
from ptvsd import __main__
args[i:i] = [
'-m', 'ptvsd',
__main__.__file__,
'--host', 'localhost',
'--port', '0',
'--wait',

View file

@ -8,9 +8,9 @@ import platform
import pytest
import sys
from ..helpers.pattern import ANY
from ..helpers.session import DebugSession
from ..helpers.timeline import Event, Request
from pytests.helpers.pattern import ANY
from pytests.helpers.session import DebugSession
from pytests.helpers.timeline import Event, Request
@pytest.mark.timeout(60)
@ -43,7 +43,7 @@ def test_multiprocessing(debug_session, pyfile):
print('leaving child')
if __name__ == '__main__':
import pytests.helpers.backchannel as backchannel
import backchannel
if sys.version_info >= (3, 4):
multiprocessing.set_start_method('spawn')
else:
@ -144,7 +144,8 @@ def test_subprocess(debug_session, pyfile):
@pyfile
def child():
import sys
print(' '.join(sys.argv))
import backchannel
backchannel.write_json(sys.argv)
@pyfile
def parent():
@ -159,7 +160,7 @@ def test_subprocess(debug_session, pyfile):
debug_session.multiprocess = True
debug_session.program_args += [child]
debug_session.prepare_to_run(filename=parent)
debug_session.prepare_to_run(filename=parent, backchannel=True)
debug_session.start_debugging()
root_start_request, = debug_session.all_occurrences_of(Request('launch') | Request('attach'))
@ -180,15 +181,15 @@ def test_subprocess(debug_session, pyfile):
}
})
child_port = child_subprocess.body['port']
debug_session.proceed()
child_session = DebugSession(method='attach_socket', ptvsd_port=child_port)
child_session.ignore_unobserved = debug_session.ignore_unobserved
child_session.connect()
child_session.handshake()
child_session.start_debugging()
debug_session.proceed()
child_args_output = child_session.wait_for_next(Event('output'))
assert child_args_output.body['output'].endswith('child.py --arg1 --arg2 --arg3')
child_argv = debug_session.read_json()
assert child_argv == [child, '--arg1', '--arg2', '--arg3']
debug_session.wait_for_exit()

View file

@ -20,7 +20,7 @@ def test_run(debug_session, pyfile, run_as):
def code_to_debug():
import os
import sys
from pytests.helpers import backchannel
import backchannel
print('begin')
assert backchannel.read_json() == 'continue'

View file

@ -0,0 +1,16 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE in the project root
# for license information.
from __future__ import print_function, with_statement, absolute_import
# This dummy package contains modules that are only supposed to be imported from
# the code that is executed under debugger as part of the test (e.g. via @pyfile).
# PYTHONPATH has an entry appended to it that allows these modules to be imported
# directly from such code, i.e. "import backchannel". Consequently, these modules
# should not assume that any other code from pytests/ is importable.
# Ensure that __file__ is always absolute.
import os
__file__ = os.path.abspath(__file__)

View file

@ -14,17 +14,15 @@ import time
import traceback
import ptvsd
import ptvsd.__main__
from ptvsd.messaging import JsonIOStream, JsonMessageChannel, MessageHandlers
from . import colors, print, watchdog
from . import colors, debuggee, print, watchdog
from .messaging import LoggingJsonStream
from .pattern import ANY
from .timeline import Timeline, Event, Response
# ptvsd.__file__ will be <dir>/ptvsd/__main__.py - we want <dir>.
PTVSD_SYS_PATH = os.path.dirname(os.path.dirname(ptvsd.__file__))
class DebugSession(object):
WAIT_FOR_EXIT_TIMEOUT = 5
BACKCHANNEL_TIMEOUT = 15
@ -41,7 +39,7 @@ class DebugSession(object):
self.multiprocess_port_range = None
self.debug_options = ['RedirectOutput']
self.env = os.environ.copy()
self.env['PYTHONPATH'] = PTVSD_SYS_PATH
self.env['PYTHONPATH'] = os.path.dirname(debuggee.__file__)
self.cwd = None
self.expected_returncode = 0
self.program_args = []
@ -131,7 +129,7 @@ class DebugSession(object):
argv = [sys.executable]
if self.method != 'attach_pid':
argv += ['-m', 'ptvsd']
argv += [ptvsd.__main__.__file__]
if self.method == 'attach_socket':
argv += ['--wait']
@ -375,9 +373,6 @@ class DebugSession(object):
if not freeze:
self.proceed()
if self.backchannel_port:
self.backchannel_established.wait()
return start
def _process_event(self, event):