Repository: microsoft/ptvsd

Issue:
Path Mapping does not work
## Environment data

- PTVSD version: master
- OS and version: Windows, linux
- Python version (& distribution if applicable, e.g. Anaconda): 3.6
- Using VS Code or Visual Studio: VSC

## Actual behavior

run any script using the master, and use the default configuration connect to linux machine:
```js
        {
            "name": "Remote 1",
            "type": "python",
            "request": "attach",
            "port": 5678,
            "host": "localhost",
            "pathMappings": [
                {
                    "localRoot": "${workspaceFolder}",
                    "remoteRoot": "."
                }
            ]
        },
```
Opens file using sourceReference

## Expected  behavior

Should open the mapped file.



Discussion:
Looks like `sourceReference` != 0 when path mapping is available. Non-zero sourceReference causes VSC to ignore the mapped file and use source request to get the file.

```js
{
    "message": "",
    "request_seq": 8,
    "body": {
        "totalFrames": 7,
        "stackFrames": [
            {
                "column": 1,
                "name": "fibb",
                "id": 140426910675912,
                "line": 2,
                "source": {
                    "sourceReference": 1,
                    "path": "C:\\GIT\\ptvsd-demo\\fibb.py"
                }
            },
        ],
    }, 
    "seq": 13,
    "success": true, 
    "command": "stackTrace", 
    "type": "response"
}
```
Will take a look at this.

Resolving patch:
diff --git a/src/ptvsd/_vendored/pydevd/pydevd_file_utils.py b/src/ptvsd/_vendored/pydevd/pydevd_file_utils.py
--- a/src/ptvsd/_vendored/pydevd/pydevd_file_utils.py
+++ b/src/ptvsd/_vendored/pydevd/pydevd_file_utils.py
@@ -429,8 +429,14 @@ def setup_client_server_paths(paths):
     global norm_file_to_client
     global norm_file_to_server
     global _last_client_server_paths_set
+    global _next_source_reference
+
     _last_client_server_paths_set = paths[:]
 
+    _source_reference_to_server_filename.clear()
+    _client_filename_in_utf8_to_source_reference.clear()
+    _next_source_reference = partial(next, itertools.count(1))
+
     # Work on the client and server slashes.
     python_sep = '\\' if IS_WINDOWS else '/'
     eclipse_sep = '\\' if _ide_os == 'WINDOWS' else '/'
@@ -510,6 +516,8 @@ def _norm_file_to_client(filename, cache=norm_filename_to_client_container):
             translated_proper_case = get_path_with_real_case(translated)
             translated = _NormFile(translated_proper_case)
 
+            path_mapping_applied = False
+
             if IS_WINDOWS:
                 if translated.lower() != translated_proper_case.lower():
                     translated_proper_case = translated
@@ -528,6 +536,7 @@ def _norm_file_to_client(filename, cache=norm_filename_to_client_container):
                     translated = eclipse_prefix + translated_proper_case[len(python_prefix):]
                     if DEBUG_CLIENT_SERVER_TRANSLATION:
                         sys.stderr.write('pydev debugger: sent to client: %s\n' % (translated,))
+                    path_mapping_applied = True
                     break
             else:
                 if DEBUG_CLIENT_SERVER_TRANSLATION:
@@ -545,7 +554,10 @@ def _norm_file_to_client(filename, cache=norm_filename_to_client_container):
             cache[filename] = translated
 
             if translated not in _client_filename_in_utf8_to_source_reference:
-                source_reference = _next_source_reference()
+                if path_mapping_applied:
+                    source_reference = 0
+                else:
+                    source_reference = _next_source_reference()
                 _client_filename_in_utf8_to_source_reference[translated] = source_reference
                 _source_reference_to_server_filename[source_reference] = filename
 


Test patch:
diff --git a/src/ptvsd/_vendored/pydevd/tests_python/resource_path_translation/__init__.py b/src/ptvsd/_vendored/pydevd/tests_python/resource_path_translation/__init__.py
new file mode 100644
diff --git a/src/ptvsd/_vendored/pydevd/tests_python/resource_path_translation/other.py b/src/ptvsd/_vendored/pydevd/tests_python/resource_path_translation/other.py
new file mode 100644
--- /dev/null
+++ b/src/ptvsd/_vendored/pydevd/tests_python/resource_path_translation/other.py
@@ -0,0 +1,6 @@
+
+
+def call_me_back1(callback):
+    a = 'other'
+    callback()
+    return a
diff --git a/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_path_translation.py b/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_path_translation.py
--- a/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_path_translation.py
+++ b/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_path_translation.py
@@ -1,7 +1,14 @@
-def main():
+from tests_python.resource_path_translation import other
+
+
+def call_this():
     print('break here')
+
+
+def main():
+    other.call_me_back1(call_this)
     print('TEST SUCEEDED!')
 
-    
+
 if __name__ == '__main__':
     main()
diff --git a/src/ptvsd/_vendored/pydevd/tests_python/test_convert_utilities.py b/src/ptvsd/_vendored/pydevd/tests_python/test_convert_utilities.py
--- a/src/ptvsd/_vendored/pydevd/tests_python/test_convert_utilities.py
+++ b/src/ptvsd/_vendored/pydevd/tests_python/test_convert_utilities.py
@@ -40,6 +40,37 @@ def test_convert_utilities(tmpdir):
         assert pydevd_file_utils.get_path_with_real_case(test_dir) == test_dir
 
 
+def test_source_reference(tmpdir):
+    import pydevd_file_utils
+
+    pydevd_file_utils.set_ide_os('WINDOWS')
+    if IS_WINDOWS:
+        # Client and server are on windows.
+        pydevd_file_utils.setup_client_server_paths([('c:\\foo', 'c:\\bar')])
+
+        assert pydevd_file_utils.norm_file_to_client('c:\\bar\\my') == 'c:\\foo\\my'
+        assert pydevd_file_utils.get_client_filename_source_reference('c:\\foo\\my') == 0
+
+        assert pydevd_file_utils.norm_file_to_client('c:\\another\\my') == 'c:\\another\\my'
+        source_reference = pydevd_file_utils.get_client_filename_source_reference('c:\\another\\my')
+        assert source_reference != 0
+        assert pydevd_file_utils.get_server_filename_from_source_reference(source_reference) == 'c:\\another\\my'
+
+    else:
+        # Client on windows and server on unix
+        pydevd_file_utils.set_ide_os('WINDOWS')
+
+        pydevd_file_utils.setup_client_server_paths([('c:\\foo', '/bar')])
+
+        assert pydevd_file_utils.norm_file_to_client('/bar/my') == 'c:\\foo\\my'
+        assert pydevd_file_utils.get_client_filename_source_reference('c:\\foo\\my') == 0
+
+        assert pydevd_file_utils.norm_file_to_client('/another/my') == '\\another\\my'
+        source_reference = pydevd_file_utils.get_client_filename_source_reference('\\another\\my')
+        assert source_reference != 0
+        assert pydevd_file_utils.get_server_filename_from_source_reference(source_reference) == '/another/my'
+
+
 def test_to_server_and_to_client(tmpdir):
     try:
 
diff --git a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger.py b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger.py
--- a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger.py
+++ b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger.py
@@ -1836,7 +1836,8 @@ def get_environ(writer):
 
         file_in_client = get_file_in_client(writer)
         assert 'tests_python' not in file_in_client
-        writer.write_add_breakpoint(2, 'main', filename=file_in_client)
+        writer.write_add_breakpoint(
+            writer.get_line_index_with_content('break here'), 'call_this', filename=file_in_client)
         writer.write_make_initial_run()
 
         xml = writer.wait_for_message(lambda msg:'stop_reason="111"' in msg)
diff --git a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py
--- a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py
+++ b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py
@@ -816,6 +816,7 @@ def test_hex_variables(case_setup):
         writer.finished_ok = True
 
 
+@pytest.mark.skipif(IS_JYTHON, reason='Flaky on Jython.')
 def test_path_translation_and_source_reference(case_setup):
 
     def get_file_in_client(writer):
@@ -846,7 +847,9 @@ def get_environ(writer):
         json_facade = JsonFacade(writer)
 
         writer.write_set_protocol('http_json')
-        writer.write_add_breakpoint(2, 'main', filename=file_in_client)
+        bp_line = writer.get_line_index_with_content('break here')
+        writer.write_add_breakpoint(
+            bp_line, 'call_this', filename=file_in_client)
 
         json_facade.write_make_initial_run()
 
@@ -864,15 +867,23 @@ def get_environ(writer):
         )))
         stack_trace_response = json_facade.wait_for_response(stack_trace_request)
         stack_trace_response_body = stack_trace_response.body
-        stack_frame = next(iter(stack_trace_response_body.stackFrames))
-        assert stack_frame['name'] == '__main__.main : 2'
+        stack_frame = stack_trace_response_body.stackFrames[0]
+        assert stack_frame['name'] == '__main__.call_this : %s' % (bp_line,)
         assert stack_frame['source']['path'] == file_in_client
         source_reference = stack_frame['source']['sourceReference']
-        assert source_reference != 0
+        assert source_reference == 0  # When it's translated the source reference must be == 0
+
+        stack_frame_not_path_translated = stack_trace_response_body.stackFrames[1]
+        assert stack_frame_not_path_translated['name'].startswith(
+            'tests_python.resource_path_translation.other.call_me_back1 :')
+
+        assert stack_frame_not_path_translated['source']['path'].endswith('other.py')
+        source_reference = stack_frame_not_path_translated['source']['sourceReference']
+        assert source_reference != 0  # Not translated
 
         response = json_facade.wait_for_response(json_facade.write_request(
             pydevd_schema.SourceRequest(pydevd_schema.SourceArguments(source_reference))))
-        assert "print('TEST SUCEEDED!')" in response.body.content
+        assert "def call_me_back1(callback):" in response.body.content
 
         writer.write_run_thread(hit.thread_id)
 
diff --git a/tests/func/test_path_mapping.py b/tests/func/test_path_mapping.py
--- a/tests/func/test_path_mapping.py
+++ b/tests/func/test_path_mapping.py
@@ -9,6 +9,7 @@
 from tests.helpers.pattern import Path
 from tests.helpers.session import DebugSession
 from tests.helpers.timeline import Event
+from tests.helpers.pathutils import get_test_root
 
 
 def test_with_dot_remote_root(pyfile, tmpdir, run_as, start_method):
@@ -66,11 +67,22 @@ def code_to_debug():
         from dbgimporter import import_and_enable_debugger
         import_and_enable_debugger()
         import os
+        import sys
         import backchannel
+        json = backchannel.read_json()
+        call_me_back_dir = json['call_me_back_dir']
+        sys.path.append(call_me_back_dir)
+
+        import call_me_back
+
+        def call_func():
+            print('break here')
+
         backchannel.write_json(os.path.abspath(__file__))
+        call_me_back.call_me_back(call_func)
         print('done')
 
-    bp_line = 6
+    bp_line = 13
     path_local = tmpdir.mkdir('local').join('code_to_debug.py').strpath
     path_remote = tmpdir.mkdir('remote').join('code_to_debug.py').strpath
 
@@ -80,6 +92,8 @@ def code_to_debug():
     copyfile(code_to_debug, path_local)
     copyfile(code_to_debug, path_remote)
 
+    call_me_back_dir = get_test_root('call_me_back')
+
     with DebugSession() as session:
         session.initialize(
             target=(run_as, path_remote),
@@ -93,19 +107,25 @@ def code_to_debug():
         )
         session.set_breakpoints(path_remote, [bp_line])
         session.start_debugging()
+        session.write_json({'call_me_back_dir': call_me_back_dir})
         hit = session.wait_for_thread_stopped('breakpoint')
+
         frames = hit.stacktrace.body['stackFrames']
         assert frames[0]['source']['path'] == Path(path_local)
         source_reference = frames[0]['source']['sourceReference']
-        assert source_reference > 0
+        assert source_reference == 0  # Mapped files should be found locally.
 
-        remote_code_path = session.read_json()
-        assert path_remote == Path(remote_code_path)
+        assert frames[1]['source']['path'].endswith('call_me_back.py')
+        source_reference = frames[1]['source']['sourceReference']
+        assert source_reference > 0  # Unmapped file should have a source reference.
 
         resp_variables = session.send_request('source', arguments={
             'sourceReference': source_reference
         }).wait_for_response()
-        assert "print('done')" in (resp_variables.body['content'])
+        assert "def call_me_back(callback):" in (resp_variables.body['content'])
+
+        remote_code_path = session.read_json()
+        assert path_remote == Path(remote_code_path)
 
         session.send_request('continue').wait_for_response(freeze=False)
         session.wait_for_exit()
diff --git a/tests/func/testfiles/__init__.py b/tests/func/testfiles/__init__.py
new file mode 100644
diff --git a/tests/func/testfiles/call_me_back/__init__.py b/tests/func/testfiles/call_me_back/__init__.py
new file mode 100644
diff --git a/tests/func/testfiles/call_me_back/call_me_back.py b/tests/func/testfiles/call_me_back/call_me_back.py
new file mode 100644
--- /dev/null
+++ b/tests/func/testfiles/call_me_back/call_me_back.py
@@ -0,0 +1,2 @@
+def call_me_back(callback):
+    callback()