Skip to content
This repository has been archived by the owner on Aug 2, 2023. It is now read-only.

Fix path mappings with remote debugging #624

Merged
merged 21 commits into from
Jul 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ contact [[email protected]](mailto:[email protected]) with any additio
"Jinja", // Enables Jinja (Flask) Template debugging
"FixFilePathCase", // See FIX_FILE_PATH_CASE in wrapper.py
"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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UnixClient [](start = 13, length = 10)

This should be updated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, this is what VSC sends,

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, got it.


In reply to: 201481855 [](ancestors = 201481855)

],
"pathMappings": [
{
Expand Down
70 changes: 29 additions & 41 deletions ptvsd/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ def bool_parser(str):
'DJANGO_DEBUG': bool_parser,
'FLASK_DEBUG': bool_parser,
'FIX_FILE_PATH_CASE': bool_parser,
'WINDOWS_CLIENT': bool_parser,
'CLIENT_OS_TYPE': unquote,
'DEBUG_STDLIB': bool_parser,
}

Expand All @@ -727,7 +727,8 @@ def bool_parser(str):
'Jinja': 'FLASK_DEBUG=True',
'FixFilePathCase': 'FIX_FILE_PATH_CASE=True',
'DebugStdLib': 'DEBUG_STDLIB=True',
'WindowsClient': 'WINDOWS_CLIENT=True',
'WindowsClient': 'CLIENT_OS_TYPE=WINDOWS',
'UnixClient': 'CLIENT_OS_TYPE=UNIX',
}


Expand Down Expand Up @@ -776,7 +777,7 @@ def _parse_debug_options(opts):
INTERPRETER_OPTIONS=string
WEB_BROWSER_URL=string url
DJANGO_DEBUG=True|False
WINDOWS_CLIENT=True|False
CLIENT_OS_TYPE=WINDOWS|UNIX
DEBUG_STDLIB=True|False
"""
options = {}
Expand All @@ -793,8 +794,8 @@ def _parse_debug_options(opts):
except KeyError:
continue

if 'WINDOWS_CLIENT' not in options:
options['WINDOWS_CLIENT'] = platform.system() == 'Windows' # noqa
if 'CLIENT_OS_TYPE' not in options:
options['CLIENT_OS_TYPE'] = 'WINDOWS' if platform.system() == 'Windows' else 'UNIX' # noqa

return options

Expand Down Expand Up @@ -1224,6 +1225,7 @@ def __init__(self, socket, pydevd_notify, pydevd_request,
self.source_map = IDMap()
self.enable_source_references = False
self.next_var_ref = 0
self._path_mappings = []
self.exceptions_mgr = ExceptionsManager(self)
self.modules_mgr = ModulesManager(self)
self.internals_filter = InternalsFilter()
Expand Down Expand Up @@ -1419,34 +1421,34 @@ def _should_debug(self, filepath):
return True

def _initialize_path_maps(self, args):
pathMaps = []
self._path_mappings = []
for pathMapping in args.get('pathMappings', []):
localRoot = pathMapping.get('localRoot', '')
remoteRoot = pathMapping.get('remoteRoot', '')
if (len(localRoot) > 0 and len(remoteRoot) > 0):
pathMaps.append((localRoot, remoteRoot))
self._path_mappings.append((localRoot, remoteRoot))

if len(pathMaps) > 0:
pydevd_file_utils.setup_client_server_paths(pathMaps)
if len(self._path_mappings) > 0:
pydevd_file_utils.setup_client_server_paths(self._path_mappings)

def _send_cmd_version_command(self):
cmd = pydevd_comm.CMD_VERSION
windows_client = self.debug_options.get(
'WINDOWS_CLIENT',
platform.system() == 'Windows')
os_id = 'WINDOWS' if windows_client else 'UNIX'
default_os_type = 'WINDOWS' if platform.system() == 'Windows' else 'UNIX' # noqa
client_os_type = self.debug_options.get(
'CLIENT_OS_TYPE', default_os_type)
os_id = client_os_type
msg = '1.1\t{}\tID'.format(os_id)
return self.pydevd_request(cmd, msg)

@async_handler
def _handle_attach(self, args):
self._initialize_path_maps(args)
yield self._send_cmd_version_command()
self._initialize_path_maps(args)

@async_handler
def _handle_launch(self, args):
self._initialize_path_maps(args)
yield self._send_cmd_version_command()
self._initialize_path_maps(args)

def _handle_detach(self):
debug('detaching')
Expand Down Expand Up @@ -1557,34 +1559,20 @@ def get_source_reference(self, filename):
if self.start_reason == 'launch':
return 0

# If we have no path mappings, then always enable source references.
autogen = len(self._path_mappings) == 0

try:
return self.source_map.to_vscode(filename, autogen=False)
return self.source_map.to_vscode(filename, autogen=autogen)
except KeyError:
# If attaching to a local process (then remote and local are same)
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 the mapped file is the same as the file we provided,
# 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)
pass

# 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

return self.source_map.to_vscode(filename, autogen=True)

@async_handler
def on_stackTrace(self, request, args):
Expand Down
7 changes: 7 additions & 0 deletions tests/helpers/debugadapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@
]


try:
ConnectionRefusedError
except Exception:
class ConnectionRefusedError(Exception):
pass


def _copy_env(verbose=False, env=None):
variables = {k: v for k, v in os.environ.items() if k in COPIED_ENV}
# TODO: Be smarter about the seed?
Expand Down
12 changes: 10 additions & 2 deletions tests/highlevel/test_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,20 @@ def test_attach_exit_during_session(self):
# TODO: Ensure we see the "terminated" and "exited" events.
raise NotImplementedError

def test_attach_from_unix_os_vsc(self):
attach_args = {'debugOptions': ['UnixClient']}
self.attach(expected_os_id='UNIX', attach_args=attach_args)

def test_attach_from_unix_os(self):
attach_args = {'options': 'WINDOWS_CLIENT=False'}
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):
attach_args = {'options': 'WINDOWS_CLIENT=True'}
attach_args = {'options': 'CLIENT_OS_TYPE=WINDOWS'}
self.attach(expected_os_id='WINDOWS', attach_args=attach_args)

def test_launch(self):
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import mypkg_bar.bar


def do_foo():
mypkg_bar.bar.do_bar()


do_foo()
11 changes: 11 additions & 0 deletions tests/resources/system_tests/test_forever/attach_forever.py
Original file line number Diff line number Diff line change
@@ -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
38 changes: 31 additions & 7 deletions tests/system_tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
import os
import ptvsd
import signal
import sys
import time
import traceback
import unittest

from collections import namedtuple
Expand Down Expand Up @@ -234,24 +236,46 @@ def _kill_proc(pid):
pass
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 = []
formatted_ex = ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback)) # noqa
try:
messages = [str(msg) for msg in
_strip_newline_output_events(session.received)]
except Exception:
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:
raise Exception(messages) from ex
except Exception:
print(messages)
raise ex
# Chain the original exception for py3.
exec('raise Exception(message) from ex', globals(), locals())
except SyntaxError:
# This happens when using py27.
message = message + os.linesep + formatted_ex
exec("raise Exception(message)", globals(), locals())

def _handle_exception(ex, adapter, session):
exc_type, exc_value, exc_traceback = sys.exc_info()
_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 \
debug_info.modulename is not None:
Expand Down
22 changes: 11 additions & 11 deletions tests/system_tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,18 +111,18 @@ def test_without_output(self):
)


class LaunchModuleTests(BasicTests):
class LaunchPackageTests(BasicTests):

def test_with_output(self):
module_name = 'mymod_launch1'
module_name = 'mypkg_launch1'
cwd = WITH_OUTPUT.root
env = WITH_OUTPUT.env_with_py_path()
self.run_test_output(
DebugInfo(modulename=module_name, env=env, cwd=cwd),
)

def test_without_output(self):
module_name = 'mymod_launch1'
module_name = 'mypkg_launch1'
cwd = WITHOUT_OUTPUT.root
env = WITHOUT_OUTPUT.env_with_py_path()
self.run_test_without_output(
Expand All @@ -131,7 +131,7 @@ def test_without_output(self):

@unittest.skip('Broken')
def test_termination(self):
module_name = 'mymod_launch1'
module_name = 'mypkg_launch1'
cwd = TEST_TERMINATION_FILES.root
env = TEST_TERMINATION_FILES.env_with_py_path()
self.run_test_output(
Expand All @@ -143,7 +143,7 @@ def test_termination(self):

@unittest.skip('Broken')
def test_arguments(self):
module_name = 'mymod_launch1'
module_name = 'mypkg_launch1'
cwd = WITH_ARGS.root
env = WITH_ARGS.env_with_py_path()
argv = ['arg1', 'arg2']
Expand Down Expand Up @@ -213,10 +213,10 @@ def test_without_output(self):
)


class ServerAttachModuleTests(BasicTests):
class ServerAttachPackageTests(BasicTests):

def test_with_output(self):
module_name = 'mymod_launch1'
module_name = 'mypkg_launch1'
cwd = WITH_OUTPUT.root
env = WITH_OUTPUT.env_with_py_path()
argv = ['localhost', str(PORT)]
Expand All @@ -231,7 +231,7 @@ def test_with_output(self):
)

def test_without_output(self):
module_name = 'mymod_launch1'
module_name = 'mypkg_launch1'
cwd = WITHOUT_OUTPUT.root
env = WITHOUT_OUTPUT.env_with_py_path()
argv = ['localhost', str(PORT)]
Expand All @@ -246,11 +246,11 @@ def test_without_output(self):
)


class PTVSDAttachModuleTests(BasicTests):
class PTVSDAttachPackageTests(BasicTests):

def test_with_output(self):
#self.enable_verbose()
module_name = 'mymod_attach1'
module_name = 'mypkg_attach1'
cwd = WITH_OUTPUT.root
env = WITH_OUTPUT.env_with_py_path()
argv = ['localhost', str(PORT)]
Expand All @@ -266,7 +266,7 @@ def test_with_output(self):
)

def test_without_output(self):
module_name = 'mymod_attach1'
module_name = 'mypkg_attach1'
cwd = WITHOUT_OUTPUT.root
env = WITHOUT_OUTPUT.env_with_py_path()
argv = ['localhost', str(PORT)]
Expand Down
Loading