From b44f10c98ad30b7ebaa759a8c5dfaaa50c810cb1 Mon Sep 17 00:00:00 2001 From: Fabio Zadrozny Date: Fri, 4 Dec 2020 11:17:06 -0300 Subject: [PATCH] Fix issue with conflicting path mapping. Fixes #482 --- .../_vendored/pydevd/pydevd_file_utils.py | 44 ++++++--- .../tests_python/test_convert_utilities.py | 94 +++++++++++++++++++ 2 files changed, 126 insertions(+), 12 deletions(-) diff --git a/src/debugpy/_vendored/pydevd/pydevd_file_utils.py b/src/debugpy/_vendored/pydevd/pydevd_file_utils.py index 7addb0aba..fe6d5fd46 100644 --- a/src/debugpy/_vendored/pydevd/pydevd_file_utils.py +++ b/src/debugpy/_vendored/pydevd/pydevd_file_utils.py @@ -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) @@ -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) @@ -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 @@ -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: diff --git a/src/debugpy/_vendored/pydevd/tests_python/test_convert_utilities.py b/src/debugpy/_vendored/pydevd/tests_python/test_convert_utilities.py index 5dcec9371..9f08e7ebf 100644 --- a/src/debugpy/_vendored/pydevd/tests_python/test_convert_utilities.py +++ b/src/debugpy/_vendored/pydevd/tests_python/test_convert_utilities.py @@ -425,3 +425,97 @@ class _DummyPyDB(object): assert source_mapping.map_to_client(filename, 12) == (filename, 12, False) + +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" + }, +] + + +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" + }, +] +