diff --git a/src/ptvsd/__main__.py b/src/ptvsd/__main__.py index 15f95a3d..7b4d8f16 100644 --- a/src/ptvsd/__main__.py +++ b/src/ptvsd/__main__.py @@ -7,8 +7,6 @@ from __future__ import absolute_import, print_function, unicode_literals import os.path import sys -__all__ = ["main"] - # Force absolute path on Python 2. __file__ = os.path.abspath(__file__) @@ -41,5 +39,5 @@ if __name__ == "__main__": import ptvsd # noqa del sys.path[0] - from ptvsd.server import main - main.main() + from ptvsd.server import cli + cli.main() diff --git a/src/ptvsd/server/cli.py b/src/ptvsd/server/cli.py index 4f33d1c3..fb17ee0a 100644 --- a/src/ptvsd/server/cli.py +++ b/src/ptvsd/server/cli.py @@ -26,7 +26,7 @@ See https://aka.ms/ptvsd for documentation. Usage: ptvsd [--client] --host
[--port', set_target('code'), False),
- ('--pid', '', set_target('pid', pid), False),
+ ("", "", set_target("file", positional=True), False),
+ ("-m", "", set_target("module"), False),
+ ("-c", "", set_target("code"), False),
+ ("--pid", "", set_target("pid", pid), False),
]
# fmt: on
@@ -174,7 +174,7 @@ def parse(args):
def setup_debug_server(argv_0):
# We need to set up sys.argv[0] before invoking attach() or enable_attach(),
- # because they use it to report the 'process' event. Thus, we can't rely on
+ # because they use it to report the "process" event. Thus, we can't rely on
# run_path() and run_module() doing that, even though they will eventually.
sys.argv[0] = compat.filename(argv_0)
log.debug("sys.argv after patching: {0!r}", sys.argv)
@@ -240,9 +240,9 @@ def run_module():
log.info("Running module {0!r}", target)
# Docs say that runpy.run_module is equivalent to -m, but it's not actually
- # the case for packages - -m sets __name__ to '__main__', but run_module sets
- # it to `pkg.__main__`. This breaks everything that uses the standard pattern
- # __name__ == '__main__' to detect being run as a CLI app. On the other hand,
+ # the case for packages - -m sets __name__ to "__main__", but run_module sets
+ # it to "pkg.__main__". This breaks everything that uses the standard pattern
+ # __name__ == "__main__" to detect being run as a CLI app. On the other hand,
# runpy._run_module_as_main is a private function that actually implements -m.
try:
run_module_as_main = runpy._run_module_as_main
@@ -266,7 +266,7 @@ def run_code():
def attach_to_pid():
- log.info('Attaching to process with ID {0}', options.target)
+ log.info("Attaching to process with ID {0}", options.target)
pid = options.target
host = options.host
@@ -277,21 +277,24 @@ def attach_to_pid():
log_dir = ""
try:
- attach_pid_injected_dirname = os.path.join(os.path.dirname(ptvsd.__file__), 'server')
+ attach_pid_injected_dirname = os.path.join(
+ os.path.dirname(ptvsd.__file__), "server"
+ )
assert os.path.exists(attach_pid_injected_dirname)
- log_dir = log_dir.replace('\\', '/')
+ log_dir = log_dir.replace("\\", "/")
encode = lambda s: list(bytearray(s.encode("utf-8")))
setup = {
- 'script': encode(attach_pid_injected_dirname),
- 'host': encode(host),
- 'port': port,
- 'client': client,
- 'log_dir': encode(log_dir),
+ "script": encode(attach_pid_injected_dirname),
+ "host": encode(host),
+ "port": port,
+ "client": client,
+ "log_dir": encode(log_dir),
}
- python_code = '''import sys;
+ python_code = """
+import sys;
import codecs;
decode = lambda s: codecs.utf_8_decode(bytearray(s))[0];
script_path = decode({script});
@@ -301,34 +304,35 @@ sys.path.remove(script_path);
host = decode({host});
log_dir = decode({log_dir}) or None;
attach_pid_injected.attach(port={port}, host=host, client={client}, log_dir=log_dir)
-'''.replace('\r', '').replace('\n', '')
+"""
+ python_code = python_code.replace("\r", "").replace("\n", "").format(**setup)
+ log.info("Code to be injected: \n{0}", python_code.replace(";", ";\n"))
- python_code = python_code.format(**setup)
- log.info("Code to be injected: \n{0}", python_code.replace(';', '\r\n'))
-
- # pydevd requires injected code to not contain any single quotes, double quotes or
- # new lines.
- assert "'" not in python_code, "Injected code should not contain any single quotes"
- assert "\"" not in python_code, "Injected code should not contain any double quotes"
- assert "\n" not in python_code, "Injected code should not contain any new lines"
- assert "\r" not in python_code, "Injected code should not contain any "
+ # pydevd restriction on characters in injected code.
+ assert not (
+ {'"', "'", "\r", "\n"} & set(python_code)
+ ), "Injected code should not contain any single quotes, double quots, or newlines."
pydevd_attach_to_process_path = os.path.join(
- os.path.dirname(pydevd.__file__),
- 'pydevd_attach_to_process')
+ os.path.dirname(pydevd.__file__), "pydevd_attach_to_process"
+ )
assert os.path.exists(pydevd_attach_to_process_path)
sys.path.append(pydevd_attach_to_process_path)
-
import add_code_to_python_process # noqa
+
show_debug_info_on_target_process = 0 # hard-coded (1 to debug)
- log.info('Code injector begin')
+ log.info("Code injector begin")
add_code_to_python_process.run_python_code(
- pid, python_code, connect_debugger_tracing=True, show_debug_info=show_debug_info_on_target_process)
- except:
+ pid,
+ python_code,
+ connect_debugger_tracing=True,
+ show_debug_info=show_debug_info_on_target_process,
+ )
+ except Exception:
raise log.exception()
- log.info('Code injector exiting')
+ log.info("Code injector exiting")
def main():
diff --git a/tests/ptvsd/server/test_parse_args.py b/tests/ptvsd/server/test_parse_args.py
index e700ac16..8152a3dd 100644
--- a/tests/ptvsd/server/test_parse_args.py
+++ b/tests/ptvsd/server/test_parse_args.py
@@ -9,15 +9,15 @@ import pytest
from ptvsd.common import log
from ptvsd.common.compat import reload
-from ptvsd.server import main, options
+from ptvsd.server import cli, options
@pytest.mark.parametrize("target_kind", ["file", "module", "code"])
@pytest.mark.parametrize("client", ["", "client"])
@pytest.mark.parametrize("wait", ["", "wait"])
-@pytest.mark.parametrize("multiproc", ["", "multiproc"])
+@pytest.mark.parametrize("subprocesses", ["", "subprocesses"])
@pytest.mark.parametrize("extra", ["", "extra"])
-def test_targets(target_kind, client, wait, multiproc, extra):
+def test_targets(target_kind, client, wait, subprocesses, extra):
args = ["--host", "localhost", "--port", "8888"]
if client:
@@ -26,8 +26,8 @@ def test_targets(target_kind, client, wait, multiproc, extra):
if wait:
args += ["--wait"]
- if multiproc:
- args += ["--multiprocess"]
+ if not subprocesses:
+ args += ["--no-subprocesses"]
if target_kind == "file":
target = "spam.py"
@@ -61,7 +61,7 @@ def test_targets(target_kind, client, wait, multiproc, extra):
log.debug("args = {0!r}", args)
reload(options)
- rest = list(main.parse(args))
+ rest = list(cli.parse(args))
assert rest == extra
expected_options = {
@@ -70,7 +70,7 @@ def test_targets(target_kind, client, wait, multiproc, extra):
"host": "localhost",
"port": 8888,
"wait": bool(wait),
- "multiprocess": bool(multiproc),
+ "multiprocess": bool(subprocesses),
}
actual_options = {name: vars(options)[name] for name in expected_options}
assert expected_options == actual_options
@@ -79,22 +79,22 @@ def test_targets(target_kind, client, wait, multiproc, extra):
def test_unsupported_arg():
reload(options)
with pytest.raises(Exception):
- main.parse(["--port", "8888", "--xyz", "123", "spam.py"])
+ cli.parse(["--port", "8888", "--xyz", "123", "spam.py"])
def test_host_required():
reload(options)
with pytest.raises(Exception):
- main.parse(["--port", "8888", "-m", "spam"])
+ cli.parse(["--port", "8888", "-m", "spam"])
def test_host_empty():
reload(options)
- main.parse(["--host", "", "--port", "8888", "spam.py"])
+ cli.parse(["--host", "", "--port", "8888", "spam.py"])
assert options.host == ""
def test_port_default():
reload(options)
- main.parse(["--host", "localhost", "spam.py"])
+ cli.parse(["--host", "localhost", "spam.py"])
assert options.port == 5678