Skip to content

Commit

Permalink
Fix issue with conflicting path mapping. Fixes #482
Browse files Browse the repository at this point in the history
  • Loading branch information
fabioz committed Dec 4, 2020
1 parent 9e2c810 commit 2341614
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 13 deletions.
44 changes: 32 additions & 12 deletions src/debugpy/_vendored/pydevd/pydevd_file_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,9 +594,13 @@ def _original_map_file_to_server(filename):
map_file_to_server = _original_map_file_to_server


def _fix_path(path, sep):
if path.endswith('/') or path.endswith('\\'):
path = path[:-1]
def _fix_path(path, sep, add_end_sep=False):
if add_end_sep:
if not path.endswith('/') and not path.endswith('\\'):
path += '/'
else:
if path.endswith('/') or path.endswith('\\'):
path = path[:-1]

if sep != '/':
path = path.replace('/', sep)
Expand Down Expand Up @@ -662,23 +666,39 @@ def setup_client_server_paths(paths):

norm_filename_to_server_container = {}
norm_filename_to_client_container = {}
initial_paths = list(paths)
paths_from_eclipse_to_python = initial_paths[:]

initial_paths = []
initial_paths_with_end_sep = []

paths_from_eclipse_to_python = []
paths_from_eclipse_to_python_with_end_sep = []

# Apply normcase to the existing paths to follow the os preferences.

for i, (path0, path1) in enumerate(paths_from_eclipse_to_python[:]):
for i, (path0, path1) in enumerate(paths):
if IS_PY2:
if isinstance(path0, unicode): # noqa
path0 = path0.encode(sys.getfilesystemencoding())
if isinstance(path1, unicode): # noqa
path1 = path1.encode(sys.getfilesystemencoding())

path0 = _fix_path(path0, eclipse_sep)
path1 = _fix_path(path1, python_sep)
initial_paths[i] = (path0, path1)
force_only_slash = path0.endswith(('/', '\\')) and path1.endswith(('/', '\\'))

if not force_only_slash:
path0 = _fix_path(path0, eclipse_sep, False)
path1 = _fix_path(path1, python_sep, False)
initial_paths.append((path0, path1))
paths_from_eclipse_to_python.append((_normcase_from_client(path0), normcase(path1)))

# Now, make a version with a slash in the end.
path0 = _fix_path(path0, eclipse_sep, True)
path1 = _fix_path(path1, python_sep, True)
initial_paths_with_end_sep.append((path0, path1))
paths_from_eclipse_to_python_with_end_sep.append((_normcase_from_client(path0), normcase(path1)))

paths_from_eclipse_to_python[i] = (_normcase_from_client(path0), normcase(path1))
# Fix things so that we always match the versions with a slash in the end first.
initial_paths = initial_paths_with_end_sep + initial_paths
paths_from_eclipse_to_python = paths_from_eclipse_to_python_with_end_sep + paths_from_eclipse_to_python

if not paths_from_eclipse_to_python:
# no translation step needed (just inline the calls)
Expand Down Expand Up @@ -707,7 +727,7 @@ def _map_file_to_server(filename, cache=norm_filename_to_server_container):
pydev_log.critical('pydev debugger: replacing to server: %s', filename)
translated = server_prefix + filename[len(eclipse_prefix):]
if DEBUG_CLIENT_SERVER_TRANSLATION:
pydev_log.critical('pydev debugger: sent to server: %s', translated)
pydev_log.critical('pydev debugger: sent to server: %s - matched prefix: %s', translated, eclipse_prefix)
break
else:
found_translation = False
Expand Down Expand Up @@ -765,7 +785,7 @@ def _map_file_to_client(filename, cache=norm_filename_to_client_container):
eclipse_prefix = initial_paths[i][0]
translated = eclipse_prefix + translated_proper_case[len(python_prefix):]
if DEBUG_CLIENT_SERVER_TRANSLATION:
pydev_log.critical('pydev debugger: sent to client: %s', translated)
pydev_log.critical('pydev debugger: sent to client: %s - matched prefix: %s', translated, python_prefix)
path_mapping_applied = True
break
else:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from _pydev_bundle._pydev_filesystem_encoding import getfilesystemencoding
import io
from _pydev_bundle.pydev_log import log_context
import pytest


def test_convert_utilities(tmpdir):
Expand Down Expand Up @@ -122,7 +123,7 @@ def check(obtained, expected):
stream = io.StringIO()
with log_context(0, stream=stream):
pydevd_file_utils.map_file_to_server('y:\\only_exists_in_client_not_in_server')
assert r'pydev debugger: unable to find translation for: "y:\only_exists_in_client_not_in_server" in ["c:\foo", "c:\foo2"] (please revise your path mappings).' in stream.getvalue()
assert r'pydev debugger: unable to find translation for: "y:\only_exists_in_client_not_in_server" in ["c:\foo\", "c:\foo2\", "c:\foo", "c:\foo2"] (please revise your path mappings).' in stream.getvalue()

# Client and server are on windows.
pydevd_file_utils.set_ide_os('WINDOWS')
Expand Down Expand Up @@ -425,3 +426,99 @@ class _DummyPyDB(object):

assert source_mapping.map_to_client(filename, 12) == (filename, 12, False)


@pytest.mark.skipif(IS_WINDOWS, reason='Linux-only test')
def test_mapping_conflict_to_client():
import pydevd_file_utils

path_mappings = []
for pathMapping in _MAPPING_CONFLICT:
localRoot = pathMapping.get('localRoot', '')
remoteRoot = pathMapping.get('remoteRoot', '')
if (localRoot != '') and (remoteRoot != ''):
path_mappings.append((localRoot, remoteRoot))

pydevd_file_utils.setup_client_server_paths(path_mappings)

assert pydevd_file_utils.map_file_to_client('/opt/pathsomething/foo.py') == \
('/var/home/p2/foo.py', True)

assert pydevd_file_utils.map_file_to_client('/opt/v2/pathsomething/foo.py') == \
('/var/home/p4/foo.py', True)

# This is an odd case, but the user didn't really put a slash in the end,
# so, it's possible that this is what the user actually wants.
assert pydevd_file_utils.map_file_to_client('/opt/v2/path_r1/foo.py') == \
('/var/home/p3_r1/foo.py', True)

# The client said both local and remote end with a slash, so, we can only
# match it with the slash in the end.
assert pydevd_file_utils.map_file_to_client('/opt/pathsomething_foo.py') == \
('/opt/pathsomething_foo.py', False)


_MAPPING_CONFLICT = [
{
"localRoot": "/var/home/p1/",
"remoteRoot": "/opt/path/"
},
{
"localRoot": "/var/home/p2/",
"remoteRoot": "/opt/pathsomething/"
},
{
"localRoot": "/var/home/p3",
"remoteRoot": "/opt/v2/path"
},
{
"localRoot": "/var/home/p4",
"remoteRoot": "/opt/v2/pathsomething"
},
]


@pytest.mark.skipif(IS_WINDOWS, reason='Linux-only test')
def test_mapping_conflict_to_server():
import pydevd_file_utils

path_mappings = []
for pathMapping in _MAPPING_CONFLICT_TO_SERVER:
localRoot = pathMapping.get('localRoot', '')
remoteRoot = pathMapping.get('remoteRoot', '')
if (localRoot != '') and (remoteRoot != ''):
path_mappings.append((localRoot, remoteRoot))

pydevd_file_utils.setup_client_server_paths(path_mappings)

assert pydevd_file_utils.map_file_to_server('/opt/pathsomething/foo.py') == '/var/home/p2/foo.py'

assert pydevd_file_utils.map_file_to_server('/opt/v2/pathsomething/foo.py') == '/var/home/p4/foo.py'

# This is an odd case, but the user didn't really put a slash in the end,
# so, it's possible that this is what the user actually wants.
assert pydevd_file_utils.map_file_to_server('/opt/v2/path_r1/foo.py') == '/var/home/p3_r1/foo.py'

# The client said both local and remote end with a slash, so, we can only
# match it with the slash in the end.
assert pydevd_file_utils.map_file_to_server('/opt/pathsomething_foo.py') == '/opt/pathsomething_foo.py'


_MAPPING_CONFLICT_TO_SERVER = [
{
"remoteRoot": "/var/home/p1/",
"localRoot": "/opt/path/"
},
{
"remoteRoot": "/var/home/p2/",
"localRoot": "/opt/pathsomething/"
},
{
"remoteRoot": "/var/home/p3",
"localRoot": "/opt/v2/path"
},
{
"remoteRoot": "/var/home/p4",
"localRoot": "/opt/v2/pathsomething"
},
]

0 comments on commit 2341614

Please sign in to comment.