示例#1
0
    def spawn_adapter(self, args=()):
        assert self.adapter is None
        assert self.channel is None

        args = [sys.executable,
                os.path.dirname(debugpy.adapter.__file__)] + list(args)
        env = self._make_env(self.spawn_adapter.env)

        log.info(
            "Spawning {0}:\n\n"
            "Command line: {1!j}\n\n"
            "Environment variables: {2!j}\n\n",
            self.adapter_id,
            args,
            env,
        )
        self.adapter = psutil.Popen(
            args,
            bufsize=0,
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            env=env.for_popen(),
        )
        log.info("Spawned {0} with PID={1}", self.adapter_id, self.adapter.pid)
        watchdog.register_spawn(self.adapter.pid, self.adapter_id)

        stream = messaging.JsonIOStream.from_process(self.adapter,
                                                     name=self.adapter_id)
        self._start_channel(stream)
示例#2
0
 def _propagate_deferred_events(self):
     log.debug("Propagating deferred events to {0}...", self.client)
     for event in self._deferred_events:
         log.debug("Propagating deferred {0}", event.describe())
         self.client.channel.propagate(event)
     log.info("All deferred events propagated to {0}.", self.client)
     self._deferred_events = None
示例#3
0
def spawn(process_name, cmdline, env, redirect_output):
    log.info(
        "Spawning debuggee process:\n\n"
        "Command line: {0!r}\n\n"
        "Environment variables: {1!r}\n\n",
        cmdline,
        env,
    )

    close_fds = set()
    try:
        if redirect_output:
            # subprocess.PIPE behavior can vary substantially depending on Python version
            # and platform; using our own pipes keeps it simple, predictable, and fast.
            stdout_r, stdout_w = os.pipe()
            stderr_r, stderr_w = os.pipe()
            close_fds |= {stdout_r, stdout_w, stderr_r, stderr_w}
            kwargs = dict(stdout=stdout_w, stderr=stderr_w)
        else:
            kwargs = {}

        try:
            global process
            process = subprocess.Popen(cmdline, env=env, bufsize=0, **kwargs)
        except Exception as exc:
            raise messaging.MessageHandlingError(
                fmt("Couldn't spawn debuggee: {0}\n\nCommand line:{1!r}", exc, cmdline)
            )

        log.info("Spawned {0}.", describe())
        atexit.register(kill)
        launcher.channel.send_event(
            "process",
            {
                "startMethod": "launch",
                "isLocalProcess": True,
                "systemProcessId": process.pid,
                "name": process_name,
                "pointerSize": struct.calcsize(compat.force_str("P")) * 8,
            },
        )

        if redirect_output:
            for category, fd, tee in [
                ("stdout", stdout_r, sys.stdout),
                ("stderr", stderr_r, sys.stderr),
            ]:
                output.CaptureOutput(describe(), category, fd, tee)
                close_fds.remove(fd)

        wait_thread = threading.Thread(target=wait_for_exit, name="wait_for_exit()")
        wait_thread.daemon = True
        wait_thread.start()

    finally:
        for fd in close_fds:
            try:
                os.close(fd)
            except Exception:
                log.swallow_exception()
示例#4
0
def wait_for_connection(session, predicate, timeout=None):
    """Waits until there is a server with the specified PID connected to this adapter,
    and returns the corresponding Connection.

    If there is more than one server connection already available, returns the oldest
    one.
    """

    def wait_for_timeout():
        time.sleep(timeout)
        wait_for_timeout.timed_out = True
        with _lock:
            _connections_changed.set()

    wait_for_timeout.timed_out = timeout == 0
    if timeout:
        thread = threading.Thread(
            target=wait_for_timeout, name="servers.wait_for_connection() timeout"
        )
        thread.daemon = True
        thread.start()

    if timeout != 0:
        log.info("{0} waiting for connection from debug server...", session)
    while True:
        with _lock:
            _connections_changed.clear()
            conns = (conn for conn in _connections if predicate(conn))
            conn = next(conns, None)
            if conn is not None or wait_for_timeout.timed_out:
                return conn
        _connections_changed.wait()
示例#5
0
文件: output.py 项目: 0keita/configs
def wait_for_remaining_output():
    """Waits for all remaining output to be captured and propagated.
    """
    for category, instance in CaptureOutput.instances.items():
        log.info("Waiting for remaining {0} of {1}.", category,
                 instance._whose)
        instance._worker_thread.join()
示例#6
0
    def debug(address, **kwargs):
        if _settrace.called:
            raise RuntimeError("this process already has a debug adapter")

        try:
            _, port = address
        except Exception:
            port = address
            address = ("127.0.0.1", port)
        try:
            port.__index__()  # ensure it's int-like
        except Exception:
            raise ValueError("expected port or (host, port)")
        if not (0 <= port < 2**16):
            raise ValueError("invalid port number")

        ensure_logging()
        log.debug("{0}({1!r}, **{2!r})", func.__name__, address, kwargs)
        log.info("Initial debug configuration: {0!j}", _config)

        settrace_kwargs = {
            "suspend": False,
            "patch_multiprocessing": _config.get("subProcess", True),
        }

        debugpy_path, _, _ = get_abs_path_real_path_and_base_from_file(
            debugpy.__file__)
        debugpy_path = os.path.dirname(debugpy_path)
        settrace_kwargs["dont_trace_start_patterns"] = (debugpy_path, )
        settrace_kwargs["dont_trace_end_patterns"] = ("debugpy_launcher.py", )

        try:
            return func(address, settrace_kwargs, **kwargs)
        except Exception:
            log.reraise_exception("{0}() failed:", func.__name__, level="info")
示例#7
0
def main():
    original_argv = list(sys.argv)
    try:
        parse_argv()
    except Exception as exc:
        print(HELP + "\nError: " + str(exc), file=sys.stderr)
        sys.exit(2)

    if options.log_to is not None:
        debugpy.log_to(options.log_to)
    if options.log_to_stderr:
        debugpy.log_to(sys.stderr)

    api.ensure_logging()

    log.info(
        "sys.argv before parsing: {0!r}\n"
        "         after parsing:  {1!r}",
        original_argv,
        sys.argv,
    )

    try:
        run = {
            "file": run_file,
            "module": run_module,
            "code": run_code,
            "pid": attach_to_pid,
        }[options.target_kind]
        run()
    except SystemExit as exc:
        log.reraise_exception("Debuggee exited via SystemExit: {0!r}",
                              exc.code,
                              level="debug")
示例#8
0
    def notify_of_subprocess(self, conn):
        with self.session:
            if self.start_request is None or conn in self._known_subprocesses:
                return
            if "processId" in self.start_request.arguments:
                log.warning(
                    "Not reporting subprocess for {0}, because the parent process "
                    'was attached to using "processId" rather than "port".',
                    self.session,
                )
                return

            log.info("Notifying {0} about {1}.", self, conn)
            body = dict(self.start_request.arguments)
            self._known_subprocesses.add(conn)

        body.pop("processId", None)
        body.pop("listen", None)
        body["name"] = fmt("Subprocess {0}", conn.pid)
        body["request"] = "attach"
        body["subProcessId"] = conn.pid

        host = body.pop("host", None)
        port = body.pop("port", None)
        if "connect" not in body:
            body["connect"] = {}
        if "host" not in body["connect"]:
            body["connect"]["host"] = host if host is not None else "127.0.0.1"
        if "port" not in body["connect"]:
            if port is None:
                _, port = listener.getsockname()
            body["connect"]["port"] = port

        self.channel.send_event("debugpyAttach", body)
示例#9
0
        def accept_worker():
            log.info(
                "Listening for incoming connection from {0} on port {1}...",
                self,
                self.port,
            )

            server_socket = self._server_socket
            if server_socket is None:
                return  # concurrent close()

            try:
                sock, _ = server_socket.accept()
            except socket.timeout:
                if self._server_socket is None:
                    return
                else:
                    log.reraise_exception(
                        "Timed out waiting for {0} to connect", self)
            except Exception:
                if self._server_socket is None:
                    return
                else:
                    log.reraise_exception(
                        "Error accepting connection for {0}:", self)

            sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
            log.info("Incoming connection from {0} accepted.", self)

            self._socket = sock
            self._setup_stream()
示例#10
0
def wait_for_exit():
    try:
        code = process.wait()
        if sys.platform != "win32" and code < 0:
            # On POSIX, if the process was terminated by a signal, Popen will use
            # a negative returncode to indicate that - but the actual exit code of
            # the process is always an unsigned number, and can be determined by
            # taking the lowest 8 bits of that negative returncode.
            code &= 0xFF
    except Exception:
        log.swallow_exception("Couldn't determine process exit code")
        code = -1

    log.info("{0} exited with code {1}", describe(), code)
    output.wait_for_remaining_output()

    # Determine whether we should wait or not before sending "exited", so that any
    # follow-up "terminate" requests don't affect the predicates.
    should_wait = any(pred(code) for pred in wait_on_exit_predicates)

    try:
        launcher.channel.send_event("exited", {"exitCode": code})
    except Exception:
        pass

    if should_wait:
        _wait_for_user_input()

    try:
        launcher.channel.send_event("terminated")
    except Exception:
        pass
示例#11
0
            def _send_requests_and_events(self, channel):
                types = [
                    random.choice(("event", "request")) for _ in range(0, 100)
                ]

                for typ in types:
                    name = random.choice(("fizz", "buzz", "fizzbuzz"))
                    body = random.randint(0, 100)

                    with self.lock:
                        self.sent.append((typ, name, body))

                    if typ == "event":
                        channel.send_event(name, body)
                    elif typ == "request":
                        req = channel.send_request(name, body)
                        req.on_response(self._got_response)

                channel.send_event("done")

                # Spin until we receive "done", and also get responses to all requests.
                requests_sent = types.count("request")
                log.info("{0} waiting for {1} responses...", self.name,
                         requests_sent)
                while True:
                    with self.lock:
                        if self.done:
                            if requests_sent == len(self.responses_received):
                                break
                    time.sleep(0.1)
示例#12
0
    def finalize(self, why, terminate_debuggee=None):
        """Finalizes the debug session.

        If the server is present, sends "disconnect" request with "terminateDebuggee"
        set as specified request to it; waits for it to disconnect, allowing any
        remaining messages from it to be handled; and closes the server channel.

        If the launcher is present, sends "terminate" request to it, regardless of the
        value of terminate; waits for it to disconnect, allowing any remaining messages
        from it to be handled; and closes the launcher channel.

        If the client is present, sends "terminated" event to it.

        If terminate_debuggee=None, it is treated as True if the session has a Launcher
        component, and False otherwise.
        """

        if self.is_finalizing:
            return
        self.is_finalizing = True
        log.info("{0}; finalizing {1}.", why, self)

        if terminate_debuggee is None:
            terminate_debuggee = bool(self.launcher)

        try:
            self._finalize(why, terminate_debuggee)
        except Exception:
            # Finalization should never fail, and if it does, the session is in an
            # indeterminate and likely unrecoverable state, so just fail fast.
            log.swallow_exception("Fatal error while finalizing {0}", self)
            os._exit(1)

        log.info("{0} finalized.", self)
示例#13
0
def launch(session, target, console=None, cwd=None):
    assert console in (None, "internalConsole", "integratedTerminal",
                       "externalTerminal")

    log.info("Launching {0} in {1} using {2!j}.", target, session, console)

    target.configure(session)
    config = session.config
    config.setdefaults({
        "console": "externalTerminal",
        "internalConsoleOptions": "neverOpen"
    })
    if console is not None:
        config["console"] = console
    if cwd is not None:
        config["cwd"] = cwd
    if "python" not in config and "pythonPath" not in config:
        config["python"] = sys.executable

    env = (session.spawn_adapter.env
           if config["console"] == "internalConsole" else config.env)
    target.cli(env)

    session.spawn_adapter()
    return session.request_launch()
示例#14
0
def main():
    from debugpy import launcher
    from debugpy.common import log
    from debugpy.launcher import debuggee

    log.to_file(prefix="debugpy.launcher")
    log.describe_environment("debugpy.launcher startup environment:")

    # Disable exceptions on Ctrl+C - we want to allow the debuggee process to handle
    # these, or not, as it sees fit. If the debuggee exits on Ctrl+C, the launcher
    # will also exit, so it doesn't need to observe the signal directly.
    signal.signal(signal.SIGINT, signal.SIG_IGN)

    # Everything before "--" is command line arguments for the launcher itself,
    # and everything after "--" is command line arguments for the debuggee.
    log.info("sys.argv before parsing: {0}", sys.argv)
    sep = sys.argv.index("--")
    launcher_argv = sys.argv[1:sep]
    sys.argv[:] = [sys.argv[0]] + sys.argv[sep + 1:]
    log.info("sys.argv after patching: {0}", sys.argv)

    # The first argument specifies the host/port on which the adapter is waiting
    # for launcher to connect. It's either host:port, or just port.
    adapter = launcher_argv[0]
    host, sep, port = adapter.partition(":")
    if not sep:
        host = "127.0.0.1"
        port = adapter
    port = int(port)

    launcher.connect(host, port)
    launcher.channel.wait()

    if debuggee.process is not None:
        sys.exit(debuggee.process.returncode)
示例#15
0
文件: output.py 项目: 0keita/configs
    def __init__(self, whose, category, fd, stream):
        assert category not in self.instances
        self.instances[category] = self
        log.info("Capturing {0} of {1}.", category, whose)

        self.category = category
        self._whose = whose
        self._fd = fd
        self._decoder = codecs.getincrementaldecoder("utf-8")(
            errors="surrogateescape")

        if stream is None:
            # Can happen if running under pythonw.exe.
            self._stream = None
        else:
            self._stream = stream if sys.version_info < (
                3, ) else stream.buffer
            encoding = stream.encoding
            if encoding is None or encoding == "cp65001":
                encoding = "utf-8"
            try:
                self._encode = codecs.getencoder(encoding)
            except Exception:
                log.swallow_exception(
                    "Unsupported {0} encoding {1!r}; falling back to UTF-8.",
                    category,
                    encoding,
                    level="warning",
                )
                self._encode = codecs.getencoder("utf-8")

        self._worker_thread = threading.Thread(target=self._worker,
                                               name=category)
        self._worker_thread.start()
示例#16
0
 def capture_output():
     while True:
         line = injector.stdout.readline()
         if not line:
             break
         log.info("Injector[PID={0}] output:\n{1}", pid, line.rstrip())
     log.info("Injector[PID={0}] exited.", pid)
示例#17
0
    def _explain_how_realized(self, expectation, reasons):
        message = fmt("Realized {0!r}", expectation)

        # For the breakdown, we want to skip any expectations that were exact occurrences,
        # since there's no point explaining that occurrence was realized by itself.
        skip = [exp for exp in reasons.keys() if isinstance(exp, Occurrence)]
        for exp in skip:
            reasons.pop(exp, None)

        if reasons == {expectation: some.object}:
            # If there's only one expectation left to explain, and it's the top-level
            # one, then we have already printed it, so just add the explanation.
            reason = reasons[expectation]
            if "\n" in message:
                message += fmt(" == {0!r}", reason)
            else:
                message += fmt("\n      == {0!r}", reason)
        elif reasons:
            # Otherwise, break it down expectation by expectation.
            message += ":"
            for exp, reason in reasons.items():
                message += fmt("\n\n   where {0!r}\n      == {1!r}", exp,
                               reason)
        else:
            message += "."

        log.info("{0}", message)
示例#18
0
def attach_connect(session, target, method, cwd=None, wait=True, log_dir=None):
    log.info("Attaching {0} to {1} by socket using {2}.", session, target,
             method.upper())

    assert method in ("api", "cli")

    config = _attach_common_config(session, target, cwd)
    config["connect"] = {}
    config["connect"]["host"] = host = attach_connect.host
    config["connect"]["port"] = port = attach_connect.port

    if method == "cli":
        args = [
            os.path.dirname(debugpy.__file__),
            "--listen",
            compat.filename_str(host) + ":" + str(port),
        ]
        if wait:
            args += ["--wait-for-client"]
        if log_dir is not None:
            args += ["--log-to", log_dir]
        if "subProcess" in config:
            args += ["--configure-subProcess", str(config["subProcess"])]
        debuggee_setup = None
    elif method == "api":
        args = []
        api_config = {k: v for k, v in config.items() if k in {"subProcess"}}
        debuggee_setup = """
import debugpy
if {log_dir!r}:
    debugpy.log_to({log_dir!r})
debugpy.configure({api_config!r})
debugpy.listen(({host!r}, {port!r}))
if {wait!r}:
    debugpy.wait_for_client()
"""
        debuggee_setup = fmt(
            debuggee_setup,
            host=host,
            port=port,
            wait=wait,
            log_dir=log_dir,
            api_config=api_config,
        )
    else:
        raise ValueError
    args += target.cli(session.spawn_debuggee.env)

    try:
        del config["subProcess"]
    except KeyError:
        pass

    session.spawn_debuggee(args, cwd=cwd, setup=debuggee_setup)
    if wait:
        session.wait_for_adapter_socket()

    session.connect_to_adapter((host, port))
    return session.request_attach()
示例#19
0
 def delete_listener_file():
     log.info("Listener ports closed; deleting {0!r}", listener_file)
     try:
         os.remove(listener_file)
     except Exception:
         log.swallow_exception("Failed to delete {0!r}",
                               listener_file,
                               level="warning")
示例#20
0
def pytest_configure(config):
    if config.option.debugpy_log_dir:
        log.log_dir = config.option.debugpy_log_dir
    else:
        bits = 64 if sys.maxsize > 2 ** 32 else 32
        ver = fmt("{0}.{1}-{bits}", *sys.version_info, bits=bits)
        log.log_dir = (tests.root / "_logs" / ver).strpath
    log.info("debugpy and pydevd logs will be under {0}", log.log_dir)
示例#21
0
def test_docstrings():
    for attr in debugpy.__all__:
        log.info("Checking docstring for debugpy.{0}", attr)
        member = getattr(debugpy, attr)

        doc = inspect.getdoc(member)
        for lineno, line in enumerate(doc.split("\n")):
            assert len(line) <= 72
示例#22
0
 def wait_until_realized(self,
                         expectation,
                         freeze=None,
                         explain=True,
                         observe=True):
     if explain:
         log.info("Waiting for {0!r}", expectation)
     return self._wait_until_realized(expectation, freeze, explain, observe)
示例#23
0
 def wait_for_next(self,
                   expectation,
                   freeze=True,
                   explain=True,
                   observe=True):
     if explain:
         log.info("Waiting for next {0!r}", expectation)
     return self._wait_until_realized(self._proceeding_from >> expectation,
                                      freeze, explain, observe)
示例#24
0
def kill():
    if process is None:
        return
    try:
        if process.poll() is None:
            log.info("Killing {0}", describe())
            process.kill()
    except Exception:
        log.swallow_exception("Failed to kill {0}", describe())
示例#25
0
    def __init__(self, session, **fds):
        self.session = session
        self._lock = threading.Lock()
        self._chunks = {}
        self._worker_threads = []

        for stream_name, fd in fds.items():
            log.info("Capturing {0} {1}", session.debuggee_id, stream_name)
            self._capture(fd, stream_name)
示例#26
0
 def wait_for(self, expectation, freeze=None, explain=True):
     assert expectation.has_lower_bound, (
         "Expectation must have a lower time bound to be used with wait_for()! "
         "Use >> to sequence an expectation against an occurrence to establish a lower bound, "
         "or wait_for_next() to wait for the next expectation since the timeline was last "
         "frozen, or wait_until_realized() when a lower bound is really not necessary."
     )
     if explain:
         log.info("Waiting for {0!r}", expectation)
     return self._wait_until_realized(expectation, freeze, explain=explain)
示例#27
0
    def connect_to_adapter(self, address):
        assert self.channel is None

        host, port = address
        log.info("Connecting to {0} at {1}:{2}", self.adapter_id, host, port)
        sock = sockets.create_client()
        sock.connect(address)

        stream = messaging.JsonIOStream.from_socket(sock, name=self.adapter_id)
        self._start_channel(stream)
示例#28
0
    def attach_to_session(self, session):
        """Attaches this server to the specified Session as a Server component.

        Raises ValueError if the server already belongs to some session.
        """

        with _lock:
            if self.server is not None:
                raise ValueError
            log.info("Attaching {0} to {1}", self, session)
            self.server = Server(session, self)
示例#29
0
def run_code():
    # Add current directory to path, like Python itself does for -c.
    sys.path.insert(0, "")
    code = compile(options.target, "<string>", "exec")

    start_debugging("-c")

    log.describe_environment("Pre-launch environment:")
    log.info("Running code:\n\n{0}", options.target)

    eval(code, {})
示例#30
0
def attach_listen(session, target, method, cwd=None, log_dir=None):
    log.info("Attaching {0} to {1} by socket using {2}.", session, target,
             method.upper())

    assert method in ("api", "cli")

    config = _attach_common_config(session, target, cwd)
    config["listen"] = {}
    config["listen"]["host"] = host = attach_listen.host
    config["listen"]["port"] = port = attach_listen.port

    if method == "cli":
        args = [
            os.path.dirname(debugpy.__file__),
            "--connect",
            compat.filename_str(host) + ":" + str(port),
        ]
        if log_dir is not None:
            args += ["--log-to", log_dir]
        if "subProcess" in config:
            args += ["--configure-subProcess", str(config["subProcess"])]
        debuggee_setup = None
    elif method == "api":
        args = []
        api_config = {k: v for k, v in config.items() if k in {"subProcess"}}
        debuggee_setup = """
import debugpy
if {log_dir!r}:
    debugpy.log_to({log_dir!r})
debugpy.configure({api_config!r})
debugpy.connect({address!r})
"""
        debuggee_setup = fmt(debuggee_setup,
                             address=(host, port),
                             log_dir=log_dir,
                             api_config=api_config)
    else:
        raise ValueError
    args += target.cli(session.spawn_debuggee.env)

    try:
        del config["subProcess"]
    except KeyError:
        pass

    def spawn_debuggee(occ):
        assert occ.body == some.dict.containing({"host": host, "port": port})
        session.spawn_debuggee(args, cwd=cwd, setup=debuggee_setup)

    session.timeline.when(timeline.Event("debugpyWaitingForServer"),
                          spawn_debuggee)
    session.spawn_adapter(
        args=[] if log_dir is None else ["--log-dir", log_dir])
    return session.request_attach()