Fix path mappings with remote debugging (#624)

Fixes #610
This commit is contained in:
Don Jayamanne 2018-07-10 15:09:16 -07:00 committed by GitHub
parent 2b0140e22c
commit 14982d90ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 317 additions and 148 deletions

View file

@ -45,7 +45,8 @@ contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additio
"Jinja", // Enables Jinja (Flask) Template debugging "Jinja", // Enables Jinja (Flask) Template debugging
"FixFilePathCase", // See FIX_FILE_PATH_CASE in wrapper.py "FixFilePathCase", // See FIX_FILE_PATH_CASE in wrapper.py
"DebugStdLib" // Whether to enable debugging of standard library functions "DebugStdLib" // Whether to enable debugging of standard library functions
"WindowsClient" // Whether client OS is Windows or not "WindowsClient" // Whether client OS is Windows
"UnixClient" // Whether client OS is Unix
], ],
"pathMappings": [ "pathMappings": [
{ {

View file

@ -713,7 +713,7 @@ DEBUG_OPTIONS_PARSER = {
'DJANGO_DEBUG': bool_parser, 'DJANGO_DEBUG': bool_parser,
'FLASK_DEBUG': bool_parser, 'FLASK_DEBUG': bool_parser,
'FIX_FILE_PATH_CASE': bool_parser, 'FIX_FILE_PATH_CASE': bool_parser,
'WINDOWS_CLIENT': bool_parser, 'CLIENT_OS_TYPE': unquote,
'DEBUG_STDLIB': bool_parser, 'DEBUG_STDLIB': bool_parser,
} }
@ -727,7 +727,8 @@ DEBUG_OPTIONS_BY_FLAG = {
'Jinja': 'FLASK_DEBUG=True', 'Jinja': 'FLASK_DEBUG=True',
'FixFilePathCase': 'FIX_FILE_PATH_CASE=True', 'FixFilePathCase': 'FIX_FILE_PATH_CASE=True',
'DebugStdLib': 'DEBUG_STDLIB=True', 'DebugStdLib': 'DEBUG_STDLIB=True',
'WindowsClient': 'WINDOWS_CLIENT=True', 'WindowsClient': 'CLIENT_OS_TYPE=WINDOWS',
'UnixClient': 'CLIENT_OS_TYPE=UNIX',
} }
@ -776,7 +777,7 @@ def _parse_debug_options(opts):
INTERPRETER_OPTIONS=string INTERPRETER_OPTIONS=string
WEB_BROWSER_URL=string url WEB_BROWSER_URL=string url
DJANGO_DEBUG=True|False DJANGO_DEBUG=True|False
WINDOWS_CLIENT=True|False CLIENT_OS_TYPE=WINDOWS|UNIX
DEBUG_STDLIB=True|False DEBUG_STDLIB=True|False
""" """
options = {} options = {}
@ -793,8 +794,8 @@ def _parse_debug_options(opts):
except KeyError: except KeyError:
continue continue
if 'WINDOWS_CLIENT' not in options: if 'CLIENT_OS_TYPE' not in options:
options['WINDOWS_CLIENT'] = platform.system() == 'Windows' # noqa options['CLIENT_OS_TYPE'] = 'WINDOWS' if platform.system() == 'Windows' else 'UNIX' # noqa
return options return options
@ -1224,6 +1225,7 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
self.source_map = IDMap() self.source_map = IDMap()
self.enable_source_references = False self.enable_source_references = False
self.next_var_ref = 0 self.next_var_ref = 0
self._path_mappings = []
self.exceptions_mgr = ExceptionsManager(self) self.exceptions_mgr = ExceptionsManager(self)
self.modules_mgr = ModulesManager(self) self.modules_mgr = ModulesManager(self)
self.internals_filter = InternalsFilter() self.internals_filter = InternalsFilter()
@ -1419,34 +1421,34 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
return True return True
def _initialize_path_maps(self, args): def _initialize_path_maps(self, args):
pathMaps = [] self._path_mappings = []
for pathMapping in args.get('pathMappings', []): for pathMapping in args.get('pathMappings', []):
localRoot = pathMapping.get('localRoot', '') localRoot = pathMapping.get('localRoot', '')
remoteRoot = pathMapping.get('remoteRoot', '') remoteRoot = pathMapping.get('remoteRoot', '')
if (len(localRoot) > 0 and len(remoteRoot) > 0): if (len(localRoot) > 0 and len(remoteRoot) > 0):
pathMaps.append((localRoot, remoteRoot)) self._path_mappings.append((localRoot, remoteRoot))
if len(pathMaps) > 0: if len(self._path_mappings) > 0:
pydevd_file_utils.setup_client_server_paths(pathMaps) pydevd_file_utils.setup_client_server_paths(self._path_mappings)
def _send_cmd_version_command(self): def _send_cmd_version_command(self):
cmd = pydevd_comm.CMD_VERSION cmd = pydevd_comm.CMD_VERSION
windows_client = self.debug_options.get( default_os_type = 'WINDOWS' if platform.system() == 'Windows' else 'UNIX' # noqa
'WINDOWS_CLIENT', client_os_type = self.debug_options.get(
platform.system() == 'Windows') 'CLIENT_OS_TYPE', default_os_type)
os_id = 'WINDOWS' if windows_client else 'UNIX' os_id = client_os_type
msg = '1.1\t{}\tID'.format(os_id) msg = '1.1\t{}\tID'.format(os_id)
return self.pydevd_request(cmd, msg) return self.pydevd_request(cmd, msg)
@async_handler @async_handler
def _handle_attach(self, args): def _handle_attach(self, args):
self._initialize_path_maps(args)
yield self._send_cmd_version_command() yield self._send_cmd_version_command()
self._initialize_path_maps(args)
@async_handler @async_handler
def _handle_launch(self, args): def _handle_launch(self, args):
self._initialize_path_maps(args)
yield self._send_cmd_version_command() yield self._send_cmd_version_command()
self._initialize_path_maps(args)
def _handle_detach(self): def _handle_detach(self):
debug('detaching') debug('detaching')
@ -1557,34 +1559,20 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
if self.start_reason == 'launch': if self.start_reason == 'launch':
return 0 return 0
# If we have no path mappings, then always enable source references.
autogen = len(self._path_mappings) == 0
try: try:
return self.source_map.to_vscode(filename, autogen=False) return self.source_map.to_vscode(filename, autogen=autogen)
except KeyError: except KeyError:
# If attaching to a local process (then remote and local are same) pass
for local_prefix, remote_prefix in pydevd_file_utils.PATHS_FROM_ECLIPSE_TO_PYTHON: # noqa
if local_prefix != remote_prefix:
continue
if filename.startswith(local_prefix): # noqa
return 0
if platform.system() == 'Windows' and filename.upper().startswith(local_prefix.upper()): # noqa
return 0
client_filename = pydevd_file_utils.norm_file_to_client(filename) # If file has been mapped, then source is available on client.
for local_prefix, remote_prefix in self._path_mappings:
if filename.startswith(local_prefix):
return 0
# If the mapped file is the same as the file we provided, return self.source_map.to_vscode(filename, autogen=True)
# then we can generate a soure reference.
if client_filename == filename:
return 0
elif platform.system() == 'Windows' and \
client_filename.upper() == filename.upper():
return 0
elif client_filename.replace('\\', '/') == filename.replace('\\', '/'):
# If remote is Unix and local is Windows, then PyDevD will
# replace the path separator in remote with with
# the os path separator of remote client
return 0
else:
return self.source_map.to_vscode(filename, autogen=True)
@async_handler @async_handler
def on_stackTrace(self, request, args): def on_stackTrace(self, request, args):

View file

@ -53,6 +53,13 @@ COPIED_ENV = [
] ]
try:
ConnectionRefusedError
except Exception:
class ConnectionRefusedError(Exception):
pass
def _copy_env(verbose=False, env=None): def _copy_env(verbose=False, env=None):
variables = {k: v for k, v in os.environ.items() if k in COPIED_ENV} variables = {k: v for k, v in os.environ.items() if k in COPIED_ENV}
# TODO: Be smarter about the seed? # TODO: Be smarter about the seed?

View file

@ -125,12 +125,20 @@ class LifecycleTests(HighlevelTest, unittest.TestCase):
# TODO: Ensure we see the "terminated" and "exited" events. # TODO: Ensure we see the "terminated" and "exited" events.
raise NotImplementedError raise NotImplementedError
def test_attach_from_unix_os(self): def test_attach_from_unix_os_vsc(self):
attach_args = {'options': 'WINDOWS_CLIENT=False'} attach_args = {'debugOptions': ['UnixClient']}
self.attach(expected_os_id='UNIX', attach_args=attach_args) self.attach(expected_os_id='UNIX', attach_args=attach_args)
def test_attach_from_unix_os(self):
attach_args = {'options': 'CLIENT_OS_TYPE=UNIX'}
self.attach(expected_os_id='UNIX', attach_args=attach_args)
def test_attach_from_win_os_vsc(self):
attach_args = {'debugOptions': ['WindowsClient']}
self.attach(expected_os_id='WINDOWS', attach_args=attach_args)
def test_attach_from_windows_os(self): def test_attach_from_windows_os(self):
attach_args = {'options': 'WINDOWS_CLIENT=True'} attach_args = {'options': 'CLIENT_OS_TYPE=WINDOWS'}
self.attach(expected_os_id='WINDOWS', attach_args=attach_args) self.attach(expected_os_id='WINDOWS', attach_args=attach_args)
def test_launch(self): def test_launch(self):

View file

@ -1,8 +0,0 @@
import mymod_bar.bar
def do_foo():
mymod_bar.bar.do_bar()
do_foo()

View file

@ -0,0 +1,8 @@
import mypkg_bar.bar
def do_foo():
mypkg_bar.bar.do_bar()
do_foo()

View file

@ -0,0 +1,11 @@
import ptvsd
import sys
import time
ptvsd.enable_attach((sys.argv[1], sys.argv[2]))
i = 0
while True:
time.sleep(0.1)
print(i)
i += 1

View file

@ -2,7 +2,9 @@ import contextlib
import os import os
import ptvsd import ptvsd
import signal import signal
import sys
import time import time
import traceback
import unittest import unittest
from collections import namedtuple from collections import namedtuple
@ -234,24 +236,46 @@ class LifecycleTestsBase(TestsBase, unittest.TestCase):
pass pass
time.sleep(1) # wait for socket connections to die out. time.sleep(1) # wait for socket connections to die out.
def _wrap_and_reraise(ex, session): def _wrap_and_reraise(session, ex, exc_type, exc_value, exc_traceback):
"""If we have connetion errors, then re-raised wrapped in
ConnectionTimeoutError. If using py3, then chain exceptions so
we do not loose the original exception, else try hack approach
for py27."""
messages = [] messages = []
formatted_ex = ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback)) # noqa
try: try:
messages = [str(msg) for msg in messages = [str(msg) for msg in
_strip_newline_output_events(session.received)] _strip_newline_output_events(session.received)]
except Exception: except Exception:
pass pass
messages = os.linesep.join(messages) fmt = {
"sep": os.linesep,
"messages": os.linesep.join(messages),
"error": ''.join(traceback.format_exception_only(exc_type, exc_value)) # noqa
}
message = """
Session Messages:
-----------------
%(messages)s
Original Error:
---------------
%(error)s""" % fmt
try: try:
raise Exception(messages) from ex # Chain the original exception for py3.
except Exception: exec('raise Exception(message) from ex', globals(), locals())
print(messages) except SyntaxError:
raise ex # This happens when using py27.
message = message + os.linesep + formatted_ex
exec("raise Exception(message)", globals(), locals())
def _handle_exception(ex, adapter, session): def _handle_exception(ex, adapter, session):
exc_type, exc_value, exc_traceback = sys.exc_info()
_kill_proc(adapter.pid) _kill_proc(adapter.pid)
_wrap_and_reraise(ex, session) _wrap_and_reraise(session, ex, exc_type, exc_value, exc_traceback)
if debug_info.attachtype == 'import' and \ if debug_info.attachtype == 'import' and \
debug_info.modulename is not None: debug_info.modulename is not None:

View file

@ -111,10 +111,10 @@ class LaunchFileTests(BasicTests):
) )
class LaunchModuleTests(BasicTests): class LaunchPackageTests(BasicTests):
def test_with_output(self): def test_with_output(self):
module_name = 'mymod_launch1' module_name = 'mypkg_launch1'
cwd = WITH_OUTPUT.root cwd = WITH_OUTPUT.root
env = WITH_OUTPUT.env_with_py_path() env = WITH_OUTPUT.env_with_py_path()
self.run_test_output( self.run_test_output(
@ -122,7 +122,7 @@ class LaunchModuleTests(BasicTests):
) )
def test_without_output(self): def test_without_output(self):
module_name = 'mymod_launch1' module_name = 'mypkg_launch1'
cwd = WITHOUT_OUTPUT.root cwd = WITHOUT_OUTPUT.root
env = WITHOUT_OUTPUT.env_with_py_path() env = WITHOUT_OUTPUT.env_with_py_path()
self.run_test_without_output( self.run_test_without_output(
@ -131,7 +131,7 @@ class LaunchModuleTests(BasicTests):
@unittest.skip('Broken') @unittest.skip('Broken')
def test_termination(self): def test_termination(self):
module_name = 'mymod_launch1' module_name = 'mypkg_launch1'
cwd = TEST_TERMINATION_FILES.root cwd = TEST_TERMINATION_FILES.root
env = TEST_TERMINATION_FILES.env_with_py_path() env = TEST_TERMINATION_FILES.env_with_py_path()
self.run_test_output( self.run_test_output(
@ -143,7 +143,7 @@ class LaunchModuleTests(BasicTests):
@unittest.skip('Broken') @unittest.skip('Broken')
def test_arguments(self): def test_arguments(self):
module_name = 'mymod_launch1' module_name = 'mypkg_launch1'
cwd = WITH_ARGS.root cwd = WITH_ARGS.root
env = WITH_ARGS.env_with_py_path() env = WITH_ARGS.env_with_py_path()
argv = ['arg1', 'arg2'] argv = ['arg1', 'arg2']
@ -213,10 +213,10 @@ class PTVSDAttachTests(BasicTests):
) )
class ServerAttachModuleTests(BasicTests): class ServerAttachPackageTests(BasicTests):
def test_with_output(self): def test_with_output(self):
module_name = 'mymod_launch1' module_name = 'mypkg_launch1'
cwd = WITH_OUTPUT.root cwd = WITH_OUTPUT.root
env = WITH_OUTPUT.env_with_py_path() env = WITH_OUTPUT.env_with_py_path()
argv = ['localhost', str(PORT)] argv = ['localhost', str(PORT)]
@ -231,7 +231,7 @@ class ServerAttachModuleTests(BasicTests):
) )
def test_without_output(self): def test_without_output(self):
module_name = 'mymod_launch1' module_name = 'mypkg_launch1'
cwd = WITHOUT_OUTPUT.root cwd = WITHOUT_OUTPUT.root
env = WITHOUT_OUTPUT.env_with_py_path() env = WITHOUT_OUTPUT.env_with_py_path()
argv = ['localhost', str(PORT)] argv = ['localhost', str(PORT)]
@ -246,11 +246,11 @@ class ServerAttachModuleTests(BasicTests):
) )
class PTVSDAttachModuleTests(BasicTests): class PTVSDAttachPackageTests(BasicTests):
def test_with_output(self): def test_with_output(self):
#self.enable_verbose() #self.enable_verbose()
module_name = 'mymod_attach1' module_name = 'mypkg_attach1'
cwd = WITH_OUTPUT.root cwd = WITH_OUTPUT.root
env = WITH_OUTPUT.env_with_py_path() env = WITH_OUTPUT.env_with_py_path()
argv = ['localhost', str(PORT)] argv = ['localhost', str(PORT)]
@ -266,7 +266,7 @@ class PTVSDAttachModuleTests(BasicTests):
) )
def test_without_output(self): def test_without_output(self):
module_name = 'mymod_attach1' module_name = 'mypkg_attach1'
cwd = WITHOUT_OUTPUT.root cwd = WITHOUT_OUTPUT.root
env = WITHOUT_OUTPUT.env_with_py_path() env = WITHOUT_OUTPUT.env_with_py_path()
argv = ['localhost', str(PORT)] argv = ['localhost', str(PORT)]

View file

@ -15,7 +15,17 @@ TEST_FILES = TestResources.from_module(__name__)
class BreakpointTests(LifecycleTestsBase): class BreakpointTests(LifecycleTestsBase):
def run_test_with_break_points(self, debug_info, bp_filename, bp_line): def run_test_with_break_points(self, debug_info, bp_filename, bp_line):
options = {'debugOptions': ['RedirectOutput']} pathMappings = []
# Required to ensure sourceReference = 0
if (debug_info.starttype == 'attach'):
pathMappings.append({
'localRoot': debug_info.cwd,
'remoteRoot': debug_info.cwd
})
options = {
'debugOptions': ['RedirectOutput'],
'pathMappings': pathMappings
}
breakpoints = [{ breakpoints = [{
'source': { 'source': {
'path': bp_filename 'path': bp_filename
@ -82,7 +92,7 @@ class BreakpointTests(LifecycleTestsBase):
def run_test_with_break_points_across_files( def run_test_with_break_points_across_files(
self, debug_info, first_file, second_file, second_file_line, self, debug_info, first_file, second_file, second_file_line,
expected_modules, expected_stacktrace): expected_stacktrace):
breakpoints = [{ breakpoints = [{
'source': { 'source': {
'path': second_file 'path': second_file
@ -111,14 +121,6 @@ class BreakpointTests(LifecycleTestsBase):
session.send_request('continue', threadId=tid) session.send_request('continue', threadId=tid)
received = list(_strip_newline_output_events(session.received))
for mod in expected_modules:
found_mod = self.find_events(received, 'module', mod)
self.assertEqual(len(found_mod),
1,
'Modul not found {}'.format(mod))
self.assert_is_subset(stacktrace, expected_stacktrace) self.assert_is_subset(stacktrace, expected_stacktrace)
def run_test_conditional_break_points(self, debug_info): def run_test_conditional_break_points(self, debug_info):
@ -289,19 +291,6 @@ class LaunchFileTests(BreakpointTests):
first_file = TEST_FILES.resolve('foo.py') first_file = TEST_FILES.resolve('foo.py')
second_file = TEST_FILES.resolve('bar.py') second_file = TEST_FILES.resolve('bar.py')
cwd = os.path.dirname(first_file) cwd = os.path.dirname(first_file)
expected_modules = [{
'reason': 'new',
'module': {
'path': second_file,
'name': 'bar'
}
}, {
'reason': 'new',
'module': {
'path': first_file,
'name': '__main__'
}
}]
expected_stacktrace = { expected_stacktrace = {
'stackFrames': [{ 'stackFrames': [{
'name': 'do_bar', 'name': 'do_bar',
@ -335,7 +324,6 @@ class LaunchFileTests(BreakpointTests):
first_file, first_file,
second_file, second_file,
2, 2,
expected_modules,
expected_stacktrace, expected_stacktrace,
) )
@ -422,10 +410,10 @@ class LaunchFileTests(BreakpointTests):
) )
class LaunchModuleTests(BreakpointTests): class LaunchPackageTests(BreakpointTests):
def test_with_break_points(self): def test_with_break_points(self):
module_name = 'mymod_launch1' module_name = 'mypkg_launch1'
env = TEST_FILES.env_with_py_path() env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.root cwd = TEST_FILES.root
bp_filename = os.path.join(cwd, module_name, '__init__.py') bp_filename = os.path.join(cwd, module_name, '__init__.py')
@ -436,25 +424,11 @@ class LaunchModuleTests(BreakpointTests):
) )
def test_with_break_points_across_files(self): def test_with_break_points_across_files(self):
module_name = 'mymod_foo' module_name = 'mypkg_foo'
first_file = TEST_FILES.resolve(module_name, '__init__.py') first_file = TEST_FILES.resolve(module_name, '__init__.py')
second_file = TEST_FILES.resolve('mymod_bar', 'bar.py') second_file = TEST_FILES.resolve('mypkg_bar', 'bar.py')
env = TEST_FILES.env_with_py_path() env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.root cwd = TEST_FILES.root
expected_modules = [{
'reason': 'new',
'module': {
'package': 'mymod_bar',
'path': second_file,
'name': 'mymod_bar.bar'
}
}, {
'reason': 'new',
'module': {
'path': first_file,
'name': '__main__'
}
}]
expected_stacktrace = { expected_stacktrace = {
'stackFrames': [{ 'stackFrames': [{
'name': 'do_bar', 'name': 'do_bar',
@ -488,7 +462,6 @@ class LaunchModuleTests(BreakpointTests):
first_file, first_file,
second_file, second_file,
2, 2,
expected_modules,
expected_stacktrace, expected_stacktrace,
) )
@ -530,10 +503,10 @@ class PTVSDAttachTests(BreakpointTests):
) )
class ServerAttachModuleTests(BreakpointTests): class ServerAttachPackageTests(BreakpointTests):
def test_with_break_points(self): def test_with_break_points(self):
module_name = 'mymod_launch1' module_name = 'mypkg_launch1'
env = TEST_FILES.env_with_py_path() env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.root cwd = TEST_FILES.root
argv = ['localhost', str(PORT)] argv = ['localhost', str(PORT)]
@ -552,10 +525,10 @@ class ServerAttachModuleTests(BreakpointTests):
@unittest.skip('Needs fixing') @unittest.skip('Needs fixing')
class PTVSDAttachModuleTests(BreakpointTests): class PTVSDAttachPackageTests(BreakpointTests):
def test_with_break_points(self): def test_with_break_points(self):
module_name = 'mymod_attach1' module_name = 'mypkg_attach1'
env = TEST_FILES.env_with_py_path() env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.root cwd = TEST_FILES.root
argv = ['localhost', str(PORT)] argv = ['localhost', str(PORT)]

View file

@ -100,14 +100,14 @@ class LaunchFileTests(ExceptionTests):
class LaunchModuleExceptionLifecycleTests(ExceptionTests): class LaunchModuleExceptionLifecycleTests(ExceptionTests):
def test_breaking_into_handled_exceptions(self): def test_breaking_into_handled_exceptions(self):
module_name = 'mymod_launch1' module_name = 'mypkg_launch1'
env = TEST_FILES.env_with_py_path() env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.parent.root cwd = TEST_FILES.parent.root
self.run_test_breaking_into_handled_exceptions( self.run_test_breaking_into_handled_exceptions(
DebugInfo(modulename=module_name, env=env, cwd=cwd)) DebugInfo(modulename=module_name, env=env, cwd=cwd))
def test_not_breaking_into_handled_exceptions(self): def test_not_breaking_into_handled_exceptions(self):
module_name = 'mymod_launch1' module_name = 'mypkg_launch1'
env = TEST_FILES.env_with_py_path() env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.parent.root cwd = TEST_FILES.parent.root
self.run_test_not_breaking_into_handled_exceptions( self.run_test_not_breaking_into_handled_exceptions(
@ -174,7 +174,7 @@ class PTVSDAttachExceptionLifecycleTests(ExceptionTests):
class ServerAttachModuleExceptionLifecycleTests(ExceptionTests): class ServerAttachModuleExceptionLifecycleTests(ExceptionTests):
def test_breaking_into_handled_exceptions(self): def test_breaking_into_handled_exceptions(self):
module_name = 'mymod_launch1' module_name = 'mypkg_launch1'
env = TEST_FILES.env_with_py_path() env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.root cwd = TEST_FILES.root
argv = ['localhost', str(PORT)] argv = ['localhost', str(PORT)]
@ -188,7 +188,7 @@ class ServerAttachModuleExceptionLifecycleTests(ExceptionTests):
)) ))
def test_not_breaking_into_handled_exceptions(self): def test_not_breaking_into_handled_exceptions(self):
module_name = 'mymod_launch1' module_name = 'mypkg_launch1'
env = TEST_FILES.env_with_py_path() env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.root cwd = TEST_FILES.root
argv = ['localhost', str(PORT)] argv = ['localhost', str(PORT)]
@ -206,7 +206,7 @@ class ServerAttachModuleExceptionLifecycleTests(ExceptionTests):
class PTVSDAttachModuleExceptionLifecycleTests(ExceptionTests): class PTVSDAttachModuleExceptionLifecycleTests(ExceptionTests):
def test_breaking_into_handled_exceptions(self): def test_breaking_into_handled_exceptions(self):
module_name = 'mymod_attach1' module_name = 'mypkg_attach1'
env = TEST_FILES.env_with_py_path() env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.root cwd = TEST_FILES.root
argv = ['localhost', str(PORT)] argv = ['localhost', str(PORT)]
@ -221,7 +221,7 @@ class PTVSDAttachModuleExceptionLifecycleTests(ExceptionTests):
)) ))
def test_not_breaking_into_handled_exceptions(self): def test_not_breaking_into_handled_exceptions(self):
module_name = 'mymod_attach1' module_name = 'mypkg_attach1'
env = TEST_FILES.env_with_py_path() env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.root cwd = TEST_FILES.root
argv = ['localhost', str(PORT)] argv = ['localhost', str(PORT)]

View file

@ -474,29 +474,6 @@ class LifecycleTests(LifecycleTestsBase):
'name': 'MainThread', 'name': 'MainThread',
}], }],
}), }),
self.new_event(
'module',
module={
'id': 1,
'name': '__main__',
'path': filename,
'package': None,
},
reason='new',
),
self.new_response(req_stacktrace1.req, **{
'totalFrames': 1,
'stackFrames': [{
'id': 1,
'name': '<module>',
'source': {
'path': filename,
'sourceReference': 0,
},
'line': bp1,
'column': 1,
}],
}),
self.new_response(req_disconnect.req), self.new_response(req_disconnect.req),
]) ])
self.messages.reset_all() self.messages.reset_all()

View file

@ -1,6 +1,10 @@
import os import os
import os.path import os.path
import signal
import sys
import time
from tests.helpers.debugsession import Awaitable
from tests.helpers.resource import TestResources from tests.helpers.resource import TestResources
from tests.helpers.socket import resolve_hostname from tests.helpers.socket import resolve_hostname
from . import ( from . import (
@ -11,9 +15,20 @@ from . import (
TEST_FILES = TestResources.from_module('tests.system_tests.test_basic') TEST_FILES = TestResources.from_module('tests.system_tests.test_basic')
WITH_OUTPUT = TEST_FILES.sub('test_output') WITH_OUTPUT = TEST_FILES.sub('test_output')
SYSTEM_TEST_FILES = TestResources.from_module('tests.system_tests')
WITH_TEST_FORVER = SYSTEM_TEST_FILES.sub('test_forever')
class RemoteTests(LifecycleTestsBase): class RemoteTests(LifecycleTestsBase):
def _assert_stacktrace_is_subset(self, stacktrace, expected_stacktrace):
# Ignore path case on Windows.
if sys.platform == 'win32':
for frame in stacktrace.get('stackFrames'):
frame['source']['path'] = frame['source'].get('path', '').upper() # noqa
for frame in expected_stacktrace.get('stackFrames'):
frame['source']['path'] = frame['source'].get('path', '').upper() # noqa
self.assert_is_subset(stacktrace, expected_stacktrace)
def run_test_attach(self, debug_info): def run_test_attach(self, debug_info):
options = {'debugOptions': ['RedirectOutput']} options = {'debugOptions': ['RedirectOutput']}
@ -28,6 +43,48 @@ class RemoteTests(LifecycleTestsBase):
self.new_event('output', category='stderr', output='no'), self.new_event('output', category='stderr', output='no'),
]) ])
def run_test_source_references(self,
debug_info,
expected_stacktrace,
path_mappings=[],
debug_options=[]):
options = {
'debugOptions': debug_options,
'pathMappings': path_mappings
}
with self.start_debugging(debug_info) as dbg:
(_, req_attach, _, _, _, req_threads) = lifecycle_handshake(
dbg.session,
debug_info.starttype,
options=options,
threads=True)
# wait till we enter the for loop.
time.sleep(1)
Awaitable.wait_all(req_attach, req_threads)
with dbg.session.wait_for_event('stopped') as result:
arguments = {
'source': {
'name': os.path.basename(debug_info.filename),
'path': debug_info.filename
},
'lines': [9],
'breakpoints': [{'line': 9}]
}
dbg.session.send_request('setBreakpoints', **arguments)
tid = result['msg'].body['threadId']
stacktrace = dbg.session.send_request('stackTrace', threadId=tid)
stacktrace.wait()
dbg.session.send_request('continue', threadId=tid).wait()
# Kill remove program.
os.kill(dbg.adapter.pid, signal.SIGTERM)
self._assert_stacktrace_is_subset(stacktrace.resp.body,
expected_stacktrace)
class AttachFileTests(RemoteTests): class AttachFileTests(RemoteTests):
@ -86,6 +143,129 @@ class AttachFileTests(RemoteTests):
host=ip, host=ip,
cwd=cwd, cwd=cwd,
starttype='attach', starttype='attach',
argv=argv, argv=argv))
),
) def test_source_references_should_be_returned_without_path_mappings(self):
filename = WITH_TEST_FORVER.resolve('attach_forever.py')
cwd = os.path.dirname(filename)
argv = ['localhost', str(PORT)]
expected_stacktrace = {
'stackFrames': [{
'source': {
'path': filename,
'sourceReference': 1
}
}],
}
self.run_test_source_references(
DebugInfo(
filename=filename,
attachtype='import',
cwd=cwd,
starttype='attach',
argv=argv), expected_stacktrace)
def test_source_references_should_not_be_returned_with_path_mappings(self):
filename = WITH_TEST_FORVER.resolve('attach_forever.py')
cwd = os.path.dirname(filename)
argv = ['localhost', str(PORT)]
path_mappings = [{
'localRoot': os.path.dirname(filename),
'remoteRoot': os.path.dirname(filename)
}]
expected_stacktrace = {
'stackFrames': [{
'source': {
'path': filename,
'sourceReference': 0
}
}],
}
self.run_test_source_references(
DebugInfo(
filename=filename,
attachtype='import',
cwd=cwd,
starttype='attach',
argv=argv), expected_stacktrace, path_mappings)
def test_source_references_should_be_returned_with_invalid_path_mappings(
self):
filename = WITH_TEST_FORVER.resolve('attach_forever.py')
cwd = os.path.dirname(filename)
argv = ['localhost', str(PORT)]
path_mappings = [{
'localRoot': os.path.dirname(__file__),
'remoteRoot': os.path.dirname(__file__)
}]
expected_stacktrace = {
'stackFrames': [{
'source': {
'path': filename,
'sourceReference': 1
}
}],
}
self.run_test_source_references(
DebugInfo(
filename=filename,
attachtype='import',
cwd=cwd,
starttype='attach',
argv=argv), expected_stacktrace, path_mappings)
def test_source_references_should_be_returned_with_win_client(self):
filename = WITH_TEST_FORVER.resolve('attach_forever.py')
cwd = os.path.dirname(filename)
argv = ['localhost', str(PORT)]
client_dir = 'C:\\Development\\Projects\\src\\sub dir'
path_mappings = [{
'localRoot': client_dir,
'remoteRoot': os.path.dirname(filename)
}]
expected_stacktrace = {
'stackFrames': [{
'source': {
'path': client_dir + '\\' + os.path.basename(filename),
'sourceReference': 0
}
}],
}
self.run_test_source_references(
DebugInfo(
filename=filename,
attachtype='import',
cwd=cwd,
starttype='attach',
argv=argv),
expected_stacktrace,
path_mappings=path_mappings,
debug_options=['WindowsClient'])
def test_source_references_should_be_returned_with_unix_client(self):
filename = WITH_TEST_FORVER.resolve('attach_forever.py')
cwd = os.path.dirname(filename)
argv = ['localhost', str(PORT)]
client_dir = '/Users/PeterSmith/projects/src/sub dir'
path_mappings = [{
'localRoot': client_dir,
'remoteRoot': os.path.dirname(filename)
}]
expected_stacktrace = {
'stackFrames': [{
'source': {
'path': client_dir + '/' + os.path.basename(filename),
'sourceReference': 0
}
}],
}
self.run_test_source_references(
DebugInfo(
filename=filename,
attachtype='import',
cwd=cwd,
starttype='attach',
argv=argv),
expected_stacktrace,
path_mappings=path_mappings,
debug_options=['UnixClient'])