Repository: microsoft/ptvsd

Issue:
debugging pytest_subprocess causes a usage error
<!-- Please search existing issues to avoid creating duplicates. -->

## Environment data

- VS Code version: 1.41.1
- Extension version (available under the Extensions sidebar): 2020.1.57204
- OS and version: MacOS Mojave
- Python version (& distribution if applicable, e.g. Anaconda): 3.7.2
- Type of virtual environment used (N/A | venv | virtualenv | conda | ...): virtualenv
- Relevant/affected Python packages and their versions: pytest 5.3.2
- Jedi or Language Server? (i.e. what is `"python.jediEnabled"` set to; more info microsoft/vscode-python#3977): Jedi

## Expected behaviour

able to debug tests with `testdir.runpytest_subprocess()`

## Actual behaviour

I see the following in `result.errlines`:
```
ERROR: usage: __main__.py [options] [file_or_dir] [file_or_dir] [...]
__main__.py: error: unrecognized arguments: --port 62985 --client 127.0.0.1 --multiprocess --json-dap-http --file
  inifile: None
  rootdir: /Users/dyitzchaki/.vscode/extensions/ms-python.python-2020.1.57204/pythonFiles/lib/python/new_ptvsd/wheels/ptvsd/_vendored/pydevd
```

Seems like the debugger is adding command line options to pytest
## Steps to reproduce:
[**NOTE**: Self-contained, minimal reproducing code samples are **extremely** helpful and will expedite addressing your issue]

put a breakpoint on `result.assert_outcomes()` in the following test:
```
def test_subprocess(testdir):
  result = testdir.runpytest_subprocess()
  result.assert_outcomes()
```

debug configuration:
```
    {
      "name": "PyTest",
      "type": "python",
      "request": "launch",
      "module": "pytest",
      "console": "integratedTerminal",
      "justMyCode": false,
      "subProcess": true,
      "args": [
        "${workspaceFolder}/tests/pytest/test_bug.py"
      ]
    }
```

<!--
Note: If you think a GIF of what is happening would be helpful, consider tools like https://www.cockos.com/licecap/, https://github.com/phw/peek or https://www.screentogif.com/ .
-->

## Logs
Output for `Python` in the `Output` panel (`View`→`Output`, change the drop-down the upper-right of the `Output` panel to `Python`)

None

Output from `Console` under the `Developer Tools` panel (toggle Developer Tools on under `Help`; turn on source maps to make any tracebacks be useful by running `Enable source map support for extension debugging`)

```
workbench.desktop.main.js:sourcemap:254 [Extension Host] Info Python Extension: 2020-01-11 20:37:06: Cached data exists getEnvironmentVariables, /Users/dyitzchaki/projects/falcon
workbench.desktop.main.js:sourcemap:254 [Extension Host] Info Python Extension: 2020-01-11 20:37:06: Cached data exists KeyPrefix=Cache_Method_Output_b.getCondaVersion-Args=
workbench.desktop.main.js:sourcemap:1572   ERR timeout after 500 ms: Error: timeout after 500 ms
    at t.RawDebugSession.handleErrorResponse (file:///Applications/Visual Studio Code.app/Contents/Resources/app/out/vs/workbench/workbench.desktop.main.js:2821:819)
    at file:///Applications/Visual Studio Code.app/Contents/Resources/app/out/vs/workbench/workbench.desktop.main.js:2821:250
    at async t.RawDebugSession.shutdown (file:///Applications/Visual Studio Code.app/Contents/Resources/app/out/vs/workbench/workbench.desktop.main.js:2819:336)
    at async R.terminate (file:///Applications/Visual Studio Code.app/Contents/Resources/app/out/vs/workbench/workbench.desktop.main.js:5113:637)
workbench.desktop.main.js:sourcemap:254 [Extension Host] Info Python Extension: 2020-01-11 20:37:33: Cached data exists getEnvironmentVariables, /Users/dyitzchaki/projects/falcon
2workbench.desktop.main.js:sourcemap:254 [Extension Host] Info Python Extension: 2020-01-11 20:37:33: Cached data exists getEnvironmentVariables, <No Resource>
workbench.desktop.main.js:sourcemap:254 [Extension Host] Info Python Extension: 2020-01-11 20:37:33: > conda info --json
workbench.desktop.main.js:sourcemap:254 [Extension Host] Info Python Extension: 2020-01-11 20:37:33: > conda --version
```



Discussion:
You can disable subprocess debugging with `"subProcess": false` in your launch.json - this should allow the test to run, although it would prevent debugging of any code in the subprocess.

It does add command line arguments to the Python binary - it has to, in order to debug the subprocess. Those arguments shouldn't be visible in `sys.argv`, though, because they're removed before the user code starts running.But if the app accesses the command line directly via OS APIs, it can still see the original command line - we've seen it with Flask on 2.7. Perhaps pytest is doing something like this here, as well?


I still see the same error with `"subProcess": false` and it seems that it does read the arguments from `sys.argv`: https://github.com/pytest-dev/pytest/blob/7ff91d81277f92a9fa0910133b61f9eb80a0a8c0/src/_pytest/config/__init__.py#L206

Resolving patch:
diff --git a/src/ptvsd/_vendored/pydevd/_pydev_bundle/pydev_monkey.py b/src/ptvsd/_vendored/pydevd/_pydev_bundle/pydev_monkey.py
--- a/src/ptvsd/_vendored/pydevd/_pydev_bundle/pydev_monkey.py
+++ b/src/ptvsd/_vendored/pydevd/_pydev_bundle/pydev_monkey.py
@@ -3,8 +3,7 @@
 import re
 import sys
 from _pydev_imps._pydev_saved_modules import threading
-from _pydevd_bundle.pydevd_constants import get_global_debugger, IS_WINDOWS, IS_JYTHON, get_current_thread_id, \
-    NULL
+from _pydevd_bundle.pydevd_constants import get_global_debugger, IS_WINDOWS, IS_JYTHON, get_current_thread_id
 from _pydev_bundle import pydev_log
 from contextlib import contextmanager
 from _pydevd_bundle import pydevd_constants
@@ -88,7 +87,8 @@ def _get_host_port():
 
 
 def _is_managed_arg(arg):
-    if arg.endswith('pydevd.py'):
+    pydevd_py = _get_str_type_compatible(arg, 'pydevd.py')
+    if arg.endswith(pydevd_py):
         return True
     return False
 
@@ -110,14 +110,41 @@ def _on_set_trace_for_new_thread(global_debugger):
         global_debugger.enable_tracing()
 
 
+def _get_str_type_compatible(s, args):
+    '''
+    This method converts `args` to byte/unicode based on the `s' type.
+    '''
+    if isinstance(args, (list, tuple)):
+        ret = []
+        for arg in args:
+            if type(s) == type(arg):
+                ret.append(arg)
+            else:
+                if isinstance(s, bytes):
+                    ret.append(arg.encode('utf-8'))
+                else:
+                    ret.append(arg.decode('utf-8'))
+        return ret
+    else:
+        if type(s) == type(args):
+            return args
+        else:
+            if isinstance(s, bytes):
+                return args.encode('utf-8')
+            else:
+                return args.decode('utf-8')
+
+
 #===============================================================================
 # Things related to monkey-patching
 #===============================================================================
 def is_python(path):
-    if path.endswith("'") or path.endswith('"'):
+    single_quote, double_quote = _get_str_type_compatible(path, ["'", '"'])
+
+    if path.endswith(single_quote) or path.endswith(double_quote):
         path = path[1:len(path) - 1]
     filename = os.path.basename(path).lower()
-    for name in ['python', 'jython', 'pypy']:
+    for name in _get_str_type_compatible(filename, ['python', 'jython', 'pypy']):
         if filename.find(name) != -1:
             return True
 
@@ -127,10 +154,14 @@ def is_python(path):
 def remove_quotes_from_args(args):
     if sys.platform == "win32":
         new_args = []
+
         for x in args:
-            if x != '""':
-                if len(x) > 1 and x.startswith('"') and x.endswith('"'):
+            double_quote, two_double_quotes = _get_str_type_compatible(x, ['"', '""'])
+
+            if x != two_double_quotes:
+                if len(x) > 1 and x.startswith(double_quote) and x.endswith(double_quote):
                     x = x[1:-1]
+
             new_args.append(x)
         return new_args
     else:
@@ -138,9 +169,11 @@ def remove_quotes_from_args(args):
 
 
 def quote_arg_win32(arg):
+    fix_type = lambda x: _get_str_type_compatible(arg, x)
+
     # See if we need to quote at all - empty strings need quoting, as do strings
     # with whitespace or quotes in them. Backslashes do not need quoting.
-    if arg and not set(arg).intersection(' "\t\n\v'):
+    if arg and not set(arg).intersection(fix_type(' "\t\n\v')):
         return arg
 
     # Per https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-commandlinetoargvw,
@@ -163,9 +196,9 @@ def quote_arg_win32(arg):
     #
     #       N backslashes in any other position remain as is.
 
-    arg = re.sub(r'(\\*)\"', r'\1\1\\"', arg)
-    arg = re.sub(r'(\\*)$', r'\1\1', arg)
-    return '"' + arg + '"'
+    arg = re.sub(fix_type(r'(\\*)\"'), fix_type(r'\1\1\\"'), arg)
+    arg = re.sub(fix_type(r'(\\*)$'), fix_type(r'\1\1'), arg)
+    return fix_type('"') + arg + fix_type('"')
 
 
 def quote_args(args):
@@ -181,16 +214,17 @@ def get_c_option_index(args):
     :param args: list of arguments
     :return: index of "-c" if it's an interpreter's option and -1 if it doesn't exist or program's option
     """
-    try:
-        ind_c = args.index('-c')
-    except ValueError:
-        return -1
+    for ind_c, arg in enumerate(args):
+        if arg == _get_str_type_compatible(arg, '-c'):
+            break
     else:
-        for i in range(1, ind_c):
-            if not args[i].startswith('-'):
-                # there is an arg without "-" before "-c", so it's not an interpreter's option
-                return -1
-        return ind_c
+        return -1
+
+    for i in range(1, ind_c):
+        if not args[i].startswith(_get_str_type_compatible(args[i], '-')):
+            # there is an arg without "-" before "-c", so it's not an interpreter's option
+            return -1
+    return ind_c
 
 
 def patch_args(args):
@@ -224,12 +258,14 @@ def patch_args(args):
                         continue
 
                     arg = args[i]
-                    if arg.startswith('-'):
+                    if arg.startswith(_get_str_type_compatible(arg, '-')):
                         # Skip the next arg too if this flag expects a value.
-                        continue_next = arg in ['-m', '-W', '-X']
+                        continue_next = arg in _get_str_type_compatible(arg, ['-m', '-W', '-X'])
                         continue
 
-                    if arg.rsplit('.')[-1] in ['zip', 'pyz', 'pyzw']:
+                    dot = _get_str_type_compatible(arg, '.')
+                    extensions = _get_str_type_compatible(arg, ['zip', 'pyz', 'pyzw'])
+                    if arg.rsplit(dot)[-1] in extensions:
                         pydev_log.debug('Executing a PyZip, returning')
                         return args
                     break
@@ -245,12 +281,19 @@ def patch_args(args):
         #  '--vm_type', 'python', '--client', '127.0.0.1', '--port', '56352', '--file', 'x:\\snippet1.py']
         from _pydevd_bundle.pydevd_command_line_handling import setup_to_argv
         original = setup_to_argv(_get_setup_updated_with_protocol(SetupHolder.setup)) + ['--file']
+
+        module_name = None
+        m_flag = _get_str_type_compatible(args[i], '-m')
         while i < len(args):
-            if args[i] == '-m':
+            if args[i] == m_flag:
                 # Always insert at pos == 1 (i.e.: pydevd "--module" --multiprocess ...)
                 original.insert(1, '--module')
+            elif args[i].startswith(m_flag):
+                # Case where the user does: python -mmodule_name (using a single parameter).
+                original.insert(1, '--module')
+                module_name = args[i][2:]
             else:
-                if args[i].startswith('-'):
+                if args[i].startswith(_get_str_type_compatible(args[i], '-')):
                     new_args.append(args[i])
                 else:
                     break
@@ -265,9 +308,12 @@ def patch_args(args):
 
         for x in original:
             new_args.append(x)
-            if x == '--file':
+            if x == _get_str_type_compatible(x, '--file'):
                 break
 
+        if module_name is not None:
+            new_args.append(module_name)
+
         while i < len(args):
             new_args.append(args[i])
             i += 1


Test patch:
diff --git a/src/ptvsd/_vendored/pydevd/tests_python/test_pydev_monkey.py b/src/ptvsd/_vendored/pydevd/tests_python/test_pydev_monkey.py
--- a/src/ptvsd/_vendored/pydevd/tests_python/test_pydev_monkey.py
+++ b/src/ptvsd/_vendored/pydevd/tests_python/test_pydev_monkey.py
@@ -1,6 +1,8 @@
+# coding: utf-8
 import os
 import sys
-import unittest
+import pytest
+from tests_python.debug_constants import IS_PY2
 
 try:
     from _pydev_bundle import pydev_monkey
@@ -11,170 +13,213 @@
 from _pydev_bundle.pydev_monkey import pydev_src_dir
 
 
-class TestCase(unittest.TestCase):
-
-    def test_monkey(self):
-        original = SetupHolder.setup
-
-        try:
-            SetupHolder.setup = {'client': '127.0.0.1', 'port': '0', 'protocol-quoted-line': True}
-            check = '''C:\\bin\\python.exe -u -c connect(\\"127.0.0.1\\")'''
-            debug_command = (
-                'import sys; '
-                'sys.path.insert(0, r\'%s\'); '
-                "import pydevd; pydevd.PydevdCustomization.DEFAULT_PROTOCOL='quoted-line'; "
-                "pydevd.settrace(host='127.0.0.1', port=0, suspend=False, "
-                'trace_only_current_thread=False, patch_multiprocessing=True, access_token=None, client_access_token=None); '
-                ''
-                "from pydevd import SetupHolder; "
-                "SetupHolder.setup = %s; "
-                ''
-                'connect("127.0.0.1")') % (pydev_src_dir, SetupHolder.setup)
-            if sys.platform == "win32":
-                debug_command = debug_command.replace('"', '\\"')
-                debug_command = '"%s"' % debug_command
-
-            self.assertEqual(
-                'C:\\bin\\python.exe -u -c %s' % debug_command,
-                pydev_monkey.patch_arg_str_win(check))
-        finally:
-            SetupHolder.setup = original
-
-    def test_str_to_args_windows(self):
-        self.assertEqual(['a', 'b'], pydev_monkey.str_to_args_windows('a "b"'))
-
-    def test_monkey_patch_args_indc(self):
-        original = SetupHolder.setup
-
-        try:
-            SetupHolder.setup = {'client': '127.0.0.1', 'port': '0', 'protocol-quoted-line': True}
-            check = ['C:\\bin\\python.exe', '-u', '-c', 'connect("127.0.0.1")']
-            debug_command = (
-                "import sys; sys.path.insert(0, r\'%s\'); import pydevd; pydevd.PydevdCustomization.DEFAULT_PROTOCOL='quoted-line'; "
-                'pydevd.settrace(host=\'127.0.0.1\', port=0, suspend=False, trace_only_current_thread=False, patch_multiprocessing=True, access_token=None, client_access_token=None); '
-                ''
-                "from pydevd import SetupHolder; "
-                "SetupHolder.setup = %s; "
-                ''
-                'connect("127.0.0.1")') % (pydev_src_dir, SetupHolder.setup)
-            if sys.platform == "win32":
-                debug_command = debug_command.replace('"', '\\"')
-                debug_command = '"%s"' % debug_command
-            res = pydev_monkey.patch_args(check)
-            self.assertEqual(res, [
-                'C:\\bin\\python.exe',
-                '-u',
-                '-c',
-                debug_command
-            ])
-        finally:
-            SetupHolder.setup = original
-
-    def test_monkey_patch_args_module(self):
-        original = SetupHolder.setup
-
-        try:
-            SetupHolder.setup = {'client': '127.0.0.1', 'port': '0', 'multiprocess': True}
-            check = ['C:\\bin\\python.exe', '-m', 'test']
-            from _pydevd_bundle.pydevd_command_line_handling import get_pydevd_file
-            self.assertEqual(pydev_monkey.patch_args(check), [
-                'C:\\bin\\python.exe',
-                get_pydevd_file(),
-                '--module',
-                '--port',
-                '0',
-                '--client',
-                '127.0.0.1',
-                '--multiprocess',
-                '--protocol-quoted-line',
-                '--file',
-                'test',
-            ])
-        finally:
-            SetupHolder.setup = original
-
-    def test_monkey_patch_args_no_indc(self):
-        original = SetupHolder.setup
-
-        try:
-            SetupHolder.setup = {'client': '127.0.0.1', 'port': '0'}
-            check = ['C:\\bin\\python.exe', 'connect(\\"127.0.0.1\\")', 'with spaces']
-            from _pydevd_bundle.pydevd_command_line_handling import get_pydevd_file
-            self.assertEqual(pydev_monkey.patch_args(check), [
-                'C:\\bin\\python.exe',
-                get_pydevd_file(),
-                '--port',
-                '0',
-                '--client',
-                '127.0.0.1',
-                '--protocol-quoted-line',
-                '--file',
-                '"connect(\\\\\\"127.0.0.1\\\\\\")"' if sys.platform == 'win32' else 'connect(\\"127.0.0.1\\")',
-                '"with spaces"'  if sys.platform == 'win32' else 'with spaces',
-            ])
-        finally:
-            SetupHolder.setup = original
-
-    def test_monkey_patch_args_no_indc_with_pydevd(self):
-        original = SetupHolder.setup
-
-        try:
-            SetupHolder.setup = {'client': '127.0.0.1', 'port': '0'}
-            check = ['C:\\bin\\python.exe', 'pydevd.py', 'connect(\\"127.0.0.1\\")', 'bar']
-
-            self.assertEqual(pydev_monkey.patch_args(check), [
-                'C:\\bin\\python.exe', 'pydevd.py', 'connect(\\"127.0.0.1\\")', 'bar'])
-        finally:
-            SetupHolder.setup = original
-
-    def test_monkey_patch_args_no_indc_without_pydevd(self):
-        original = SetupHolder.setup
+def test_monkey():
+    original = SetupHolder.setup
+
+    try:
+        SetupHolder.setup = {'client': '127.0.0.1', 'port': '0', 'protocol-quoted-line': True}
+        check = '''C:\\bin\\python.exe -u -c connect(\\"127.0.0.1\\")'''
+        debug_command = (
+            'import sys; '
+            'sys.path.insert(0, r\'%s\'); '
+            "import pydevd; pydevd.PydevdCustomization.DEFAULT_PROTOCOL='quoted-line'; "
+            "pydevd.settrace(host='127.0.0.1', port=0, suspend=False, "
+            'trace_only_current_thread=False, patch_multiprocessing=True, access_token=None, client_access_token=None); '
+            ''
+            "from pydevd import SetupHolder; "
+            "SetupHolder.setup = %s; "
+            ''
+            'connect("127.0.0.1")') % (pydev_src_dir, SetupHolder.setup)
+        if sys.platform == "win32":
+            debug_command = debug_command.replace('"', '\\"')
+            debug_command = '"%s"' % debug_command
+
+        assert 'C:\\bin\\python.exe -u -c %s' % debug_command == pydev_monkey.patch_arg_str_win(check)
+    finally:
+        SetupHolder.setup = original
+
+
+def test_str_to_args_windows():
+    assert ['a', 'b'] == pydev_monkey.str_to_args_windows('a "b"')
+
+
+def test_get_c_option_index():
+    # Note: arg[0] is ignored.
+    assert pydev_monkey.get_c_option_index(['-a', '-b', '-c', 'd']) == 2
+    assert pydev_monkey.get_c_option_index(['-a', 'b', '-c', 'd']) == -1
+    assert pydev_monkey.get_c_option_index(['a', '-b', '-c', 'd']) == 2
+    assert pydev_monkey.get_c_option_index(['a', '-c', 'd']) == 1
+
+
+def test_monkey_patch_args_indc():
+    original = SetupHolder.setup
+
+    try:
+        SetupHolder.setup = {'client': '127.0.0.1', 'port': '0', 'protocol-quoted-line': True}
+        check = ['C:\\bin\\python.exe', '-u', '-c', 'connect("127.0.0.1")']
+        debug_command = (
+            "import sys; sys.path.insert(0, r\'%s\'); import pydevd; pydevd.PydevdCustomization.DEFAULT_PROTOCOL='quoted-line'; "
+            'pydevd.settrace(host=\'127.0.0.1\', port=0, suspend=False, trace_only_current_thread=False, patch_multiprocessing=True, access_token=None, client_access_token=None); '
+            ''
+            "from pydevd import SetupHolder; "
+            "SetupHolder.setup = %s; "
+            ''
+            'connect("127.0.0.1")') % (pydev_src_dir, SetupHolder.setup)
+        if sys.platform == "win32":
+            debug_command = debug_command.replace('"', '\\"')
+            debug_command = '"%s"' % debug_command
+        res = pydev_monkey.patch_args(check)
+        assert res == [
+            'C:\\bin\\python.exe',
+            '-u',
+            '-c',
+            debug_command
+        ]
+    finally:
+        SetupHolder.setup = original
+
+
+def test_monkey_patch_args_module():
+    original = SetupHolder.setup
+
+    try:
+        SetupHolder.setup = {'client': '127.0.0.1', 'port': '0', 'multiprocess': True}
+        check = ['C:\\bin\\python.exe', '-m', 'test']
         from _pydevd_bundle.pydevd_command_line_handling import get_pydevd_file
-
-        try:
-            SetupHolder.setup = {'client': '127.0.0.1', 'port': '0'}
-            check = ['C:\\bin\\python.exe', 'target.py', 'connect(\\"127.0.0.1\\")', 'bar']
-            self.assertEqual(pydev_monkey.patch_args(check), [
-                'C:\\bin\\python.exe',
-                get_pydevd_file(),
-                '--port',
-                '0',
-                '--client',
-                '127.0.0.1',
-                '--protocol-quoted-line',
-                '--file',
-                'target.py',
-                '"connect(\\\\\\"127.0.0.1\\\\\\")"' if sys.platform == 'win32' else 'connect(\\"127.0.0.1\\")',
-                'bar',
-            ])
-        finally:
-            SetupHolder.setup = original
-
-    def test_monkey_patch_c_program_arg(self):
-        original = SetupHolder.setup
+        assert pydev_monkey.patch_args(check) == [
+            'C:\\bin\\python.exe',
+            get_pydevd_file(),
+            '--module',
+            '--port',
+            '0',
+            '--client',
+            '127.0.0.1',
+            '--multiprocess',
+            '--protocol-quoted-line',
+            '--file',
+            'test',
+        ]
+    finally:
+        SetupHolder.setup = original
+
+
+def test_monkey_patch_args_no_indc():
+    original = SetupHolder.setup
+
+    try:
+        SetupHolder.setup = {'client': '127.0.0.1', 'port': '0'}
+        check = ['C:\\bin\\python.exe', 'connect(\\"127.0.0.1\\")', 'with spaces']
         from _pydevd_bundle.pydevd_command_line_handling import get_pydevd_file
-
-        try:
-            SetupHolder.setup = {'client': '127.0.0.1', 'port': '0'}
-            check = ['C:\\bin\\python.exe', '-u', 'target.py', '-c', '-another_arg']
-
-            self.assertEqual(pydev_monkey.patch_args(check), [
-                'C:\\bin\\python.exe',
-                '-u',
-                get_pydevd_file(),
-                '--port',
-                '0',
-                '--client',
-                '127.0.0.1',
-                '--protocol-quoted-line',
-                '--file',
-                'target.py',
-                '-c',
-                '-another_arg'
-            ])
-        finally:
-            SetupHolder.setup = original
-
-
-if __name__ == '__main__':
-    unittest.main()
+        assert pydev_monkey.patch_args(check) == [
+            'C:\\bin\\python.exe',
+            get_pydevd_file(),
+            '--port',
+            '0',
+            '--client',
+            '127.0.0.1',
+            '--protocol-quoted-line',
+            '--file',
+            '"connect(\\\\\\"127.0.0.1\\\\\\")"' if sys.platform == 'win32' else 'connect(\\"127.0.0.1\\")',
+            '"with spaces"'  if sys.platform == 'win32' else 'with spaces',
+        ]
+    finally:
+        SetupHolder.setup = original
+
+
+def test_monkey_patch_args_no_indc_with_pydevd():
+    original = SetupHolder.setup
+
+    try:
+        SetupHolder.setup = {'client': '127.0.0.1', 'port': '0'}
+        check = ['C:\\bin\\python.exe', 'pydevd.py', 'connect(\\"127.0.0.1\\")', 'bar']
+
+        assert pydev_monkey.patch_args(check) == [
+            'C:\\bin\\python.exe', 'pydevd.py', 'connect(\\"127.0.0.1\\")', 'bar']
+    finally:
+        SetupHolder.setup = original
+
+
+def test_monkey_patch_args_no_indc_without_pydevd():
+    original = SetupHolder.setup
+    from _pydevd_bundle.pydevd_command_line_handling import get_pydevd_file
+
+    try:
+        SetupHolder.setup = {'client': '127.0.0.1', 'port': '0'}
+        check = ['C:\\bin\\python.exe', 'target.py', 'connect(\\"127.0.0.1\\")', 'bar']
+        assert pydev_monkey.patch_args(check) == [
+            'C:\\bin\\python.exe',
+            get_pydevd_file(),
+            '--port',
+            '0',
+            '--client',
+            '127.0.0.1',
+            '--protocol-quoted-line',
+            '--file',
+            'target.py',
+            '"connect(\\\\\\"127.0.0.1\\\\\\")"' if sys.platform == 'win32' else 'connect(\\"127.0.0.1\\")',
+            'bar',
+        ]
+    finally:
+        SetupHolder.setup = original
+
+
+@pytest.mark.parametrize('use_bytes', [True, False])
+def test_monkey_patch_c_program_arg(use_bytes):
+    original = SetupHolder.setup
+    from _pydevd_bundle.pydevd_command_line_handling import get_pydevd_file
+
+    try:
+        SetupHolder.setup = {'client': '127.0.0.1', 'port': '0'}
+        check = ['C:\\bin\\python.exe', '-u', 'target.py', '-c', '-�����']
+
+        encode = lambda s:s
+        if use_bytes:
+            if not IS_PY2:
+                check = [c.encode('utf-8') for c in check]
+                encode = lambda s:s.encode('utf-8')
+        else:
+            if IS_PY2:
+                check = [c.decode('utf-8') for c in check]
+                encode = lambda s:s.decode('utf-8')
+
+        assert pydev_monkey.patch_args(check) == [
+            encode('C:\\bin\\python.exe'),
+            encode('-u'),
+            get_pydevd_file(),
+            '--port',
+            '0',
+            '--client',
+            '127.0.0.1',
+            '--protocol-quoted-line',
+            '--file',
+            encode('target.py'),
+            encode('-c'),
+            encode('-�����')
+        ]
+    finally:
+        SetupHolder.setup = original
+
+
+def test_monkey_patch_args_module_single_arg():
+    original = SetupHolder.setup
+
+    try:
+        SetupHolder.setup = {'client': '127.0.0.1', 'port': '0', 'multiprocess': True}
+        check = ['C:\\bin\\python.exe', '-mtest', 'bar']
+        from _pydevd_bundle.pydevd_command_line_handling import get_pydevd_file
+        assert pydev_monkey.patch_args(check) == [
+            'C:\\bin\\python.exe',
+            get_pydevd_file(),
+            '--module',
+            '--port',
+            '0',
+            '--client',
+            '127.0.0.1',
+            '--multiprocess',
+            '--protocol-quoted-line',
+            '--file',
+            'test',
+            'bar',
+        ]
+    finally:
+        SetupHolder.setup = original