Repository: microsoft/ptvsd

Issue:
Remote debugging broken when remote is Windows and local is Linux
* Remote host = Windows
* Local host = Linux

In this scenario, paths returned by PTVSD are all normcased

This is because the `os_id` initialized in `on_initialize` is incorrect.
We need to send the OS of the IDE not that of the current OS.
```python
    @async_handler
    def on_initialize(self, request, args):
        # TODO: docstring
        cmd = pydevd_comm.CMD_VERSION
        os_id = 'WINDOWS' if platform.system() == 'Windows' else 'UNIX'
        msg = '1.1\t{}\tID'.format(os_id)
        yield self.pydevd_request(cmd, msg)
```

**Solution:** 
We'll need to pass an OS identifier from VS/VSC to PTVSD via the `launch` request.



Discussion:

Resolving patch:
diff --git a/ptvsd/wrapper.py b/ptvsd/wrapper.py
--- a/ptvsd/wrapper.py
+++ b/ptvsd/wrapper.py
@@ -828,10 +828,6 @@ def context():
     @async_handler
     def on_initialize(self, request, args):
         # TODO: docstring
-        cmd = pydevd_comm.CMD_VERSION
-        os_id = 'WINDOWS' if platform.system() == 'Windows' else 'UNIX'
-        msg = '1.1\t{}\tID'.format(os_id)
-        yield self.pydevd_request(cmd, msg)
         self.send_response(
             request,
             supportsExceptionInfoRequest=True,
@@ -900,6 +896,7 @@ def build_debug_options(self, debug_options):
             'Jinja': 'FLASK_DEBUG=True',
             'FixFilePathCase': 'FIX_FILE_PATH_CASE=True',
             'DebugStdLib': 'DEBUG_STD_LIB=True',
+            'FilePathIsCaseSensitive': 'FILE_PATH_IS_CASE_SENSITIVE=True',
         }
         return ';'.join(debug_option_mapping[option]
                         for option in debug_options
@@ -914,17 +911,20 @@ def _parse_debug_options(self, debug_options):
             INTERPRETER_OPTIONS=string
             WEB_BROWSER_URL=string url
             DJANGO_DEBUG=True|False
+            FILE_PATH_IS_CASE_SENSITIVE=True|False
         """
+        bool_parser = lambda str: str in ("True", "true", "1")
         DEBUG_OPTIONS_PARSER = {
-            'WAIT_ON_ABNORMAL_EXIT': bool,
-            'WAIT_ON_NORMAL_EXIT': bool,
-            'REDIRECT_OUTPUT': bool,
+            'WAIT_ON_ABNORMAL_EXIT': bool_parser,
+            'WAIT_ON_NORMAL_EXIT': bool_parser,
+            'REDIRECT_OUTPUT': bool_parser,
             'VERSION': unquote,
             'INTERPRETER_OPTIONS': unquote,
             'WEB_BROWSER_URL': unquote,
-            'DJANGO_DEBUG': bool,
-            'FLASK_DEBUG': bool,
-            'FIX_FILE_PATH_CASE': bool,
+            'DJANGO_DEBUG': bool_parser,
+            'FLASK_DEBUG': bool_parser,
+            'FIX_FILE_PATH_CASE': bool_parser,
+            'FILE_PATH_IS_CASE_SENSITIVE': bool_parser,
         }
 
         options = {}
@@ -934,6 +934,11 @@ def _parse_debug_options(self, debug_options):
             except ValueError:
                 continue
             options[key] = DEBUG_OPTIONS_PARSER[key](value)
+
+        if 'FILE_PATH_IS_CASE_SENSITIVE' not in options:
+            file_path_is_case_sensitive = platform.system() != 'Windows'
+            options['FILE_PATH_IS_CASE_SENSITIVE'] = file_path_is_case_sensitive # noqa
+
         return options
 
     def _initialize_path_maps(self, args):
@@ -947,6 +952,12 @@ def _initialize_path_maps(self, args):
         if len(pathMaps) > 0:
             pydevd_file_utils.setup_client_server_paths(pathMaps)
 
+    def _send_cmd_version_command(self, file_path_is_case_sensitive):
+        cmd = pydevd_comm.CMD_VERSION
+        os_id = 'UNIX' if file_path_is_case_sensitive else 'WINDOWS'
+        msg = '1.1\t{}\tID'.format(os_id)
+        return self.pydevd_request(cmd, msg)
+        
     @async_handler
     def on_attach(self, request, args):
         # TODO: docstring
@@ -955,6 +966,8 @@ def on_attach(self, request, args):
         options = self.build_debug_options(args.get('debugOptions', []))
         self.debug_options = self._parse_debug_options(
             args.get('options', options))
+        file_path_is_case_sensitive = self.debug_options.get('FILE_PATH_IS_CASE_SENSITIVE', False)
+        yield self._send_cmd_version_command(file_path_is_case_sensitive)
         self.send_response(request)
 
     @async_handler
@@ -964,6 +977,8 @@ def on_launch(self, request, args):
         options = self.build_debug_options(args.get('debugOptions', []))
         self.debug_options = self._parse_debug_options(
             args.get('options', options))
+        file_path_is_case_sensitive = platform.system() != 'Windows'
+        yield self._send_cmd_version_command(file_path_is_case_sensitive)
         self.send_response(request)
 
     def on_disconnect(self, request, args):


Test patch:
diff --git a/tests/highlevel/test_lifecycle.py b/tests/highlevel/test_lifecycle.py
--- a/tests/highlevel/test_lifecycle.py
+++ b/tests/highlevel/test_lifecycle.py
@@ -29,7 +29,7 @@ class LifecycleTests(HighlevelTest, unittest.TestCase):
     class FIXTURE(HighlevelFixture):
         lifecycle = None  # Make sure we don't cheat.
 
-    def test_attach(self):
+    def attach(self, expected_os_id, attach_args):
         version = self.debugger.VERSION
         addr = (None, 8888)
         daemon = self.vsc.start(addr)
@@ -44,7 +44,7 @@ def test_attach(self):
                 })
 
                 # attach
-                req_attach = self.send_request('attach')
+                req_attach = self.send_request('attach', attach_args)
 
             # configuration
             req_config = self.send_request('configurationDone')
@@ -102,11 +102,22 @@ def test_attach(self):
         ])
         self.assert_received(self.debugger, [
             self.debugger_msgs.new_request(CMD_VERSION,
-                                           *['1.1', OS_ID, 'ID']),
+                                           *['1.1', expected_os_id, 'ID']),
             self.debugger_msgs.new_request(CMD_REDIRECT_OUTPUT),
             self.debugger_msgs.new_request(CMD_RUN),
         ])
 
+    def test_attach(self):
+        self.attach(expected_os_id=OS_ID, attach_args={})
+
+    def test_attach_from_unix_os(self):
+        attach_args = {'options':'FILE_PATH_IS_CASE_SENSITIVE=True'}
+        self.attach(expected_os_id='UNIX', attach_args=attach_args)
+
+    def test_attach_from_windows_os(self):
+        attach_args = {'options': 'FILE_PATH_IS_CASE_SENSITIVE=False'}
+        self.attach(expected_os_id='WINDOWS', attach_args=attach_args)
+
     def test_launch(self):
         version = self.debugger.VERSION
         addr = (None, 8888)
diff --git a/tests/highlevel/test_messages.py b/tests/highlevel/test_messages.py
--- a/tests/highlevel/test_messages.py
+++ b/tests/highlevel/test_messages.py
@@ -93,9 +93,7 @@ class InitializeTests(LifecycleTest, unittest.TestCase):
 
     @unittest.skip('tested via test_lifecycle.py')
     def test_basic(self):
-        version = self.debugger.VERSION
         with self.lifecycle.demon_running(port=8888):
-            self.set_debugger_response(CMD_VERSION, version)
             req = self.send_request('initialize', {
                 'adapterID': 'spam',
             })
@@ -126,10 +124,7 @@ def test_basic(self):
             )),
             self.new_event(1, 'initialized'),
         ])
-        self.assert_received(self.debugger, [
-            self.new_debugger_request(CMD_VERSION,
-                                      *['1.1', OS_ID, 'ID']),
-        ])
+        self.assert_received(self.debugger, [])
 
 
 ##################################
@@ -1882,11 +1877,13 @@ class SourceTests(NormalRequestTest, unittest.TestCase):
     def test_unsupported(self):
         with self.launched():
             self.send_request(
-                sourceReference=1,
+                sourceReference=0,
             )
             received = self.vsc.received
 
-        self.assert_vsc_received(received, [])
+        self.assert_vsc_received(received, [
+            self.expected_failure('Source unavailable'),
+        ])
         self.assert_received(self.debugger, [])