Esempio n. 1
0
def Response(request, body=some.object):
    assert isinstance(request, Expectation) or isinstance(
        request, RequestOccurrence)

    exp = PatternExpectation("response", request, body)
    exp.timeline = request.timeline
    exp.has_lower_bound = request.has_lower_bound

    # Try to be as specific as possible.
    if isinstance(request, Expectation):
        if request.circumstances[0] != "request":
            exp.describe = lambda: fmt("response to {0!r}", request)
            return
        else:
            items = (("command", request.circumstances[1]), )
    else:
        items = (("command", request.command), )

    if isinstance(request, Occurrence):
        items += (("request_seq", request.seq), )

    if body is some.object:
        items += (("\002...", "...\003"), )
    elif body is some.error or body == some.error:
        items += (("success", False), )
        if body == some.error:
            items += (("message", compat.force_str(body)), )
    else:
        items += (("body", body), )

    exp.describe = lambda: _describe_message("response", *items)
    return exp
Esempio n. 2
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()
Esempio n. 3
0
def test_django_breakpoint_no_multiproc(start_django, bp_target):
    bp_file, bp_line, bp_name = {
        "code": (paths.app_py, lines.app_py["bphome"], "home"),
        "template": (paths.hello_html, 8, "Django Template"),
    }[bp_target]
    bp_var_content = compat.force_str("Django-Django-Test")

    with debug.Session() as session:
        with start_django(session):
            session.set_breakpoints(bp_file, [bp_line])

        with django_server:
            home_request = django_server.get("/home")
            session.wait_for_stop(
                "breakpoint",
                expected_frames=[
                    some.dap.frame(some.dap.source(bp_file), line=bp_line, name=bp_name)
                ],
            )

            var_content = session.get_variable("content")
            assert var_content == some.dict.containing(
                {
                    "name": "content",
                    "type": "str",
                    "value": compat.unicode_repr(bp_var_content),
                    "presentationHint": {"attributes": ["rawString"]},
                    "evaluateName": "content",
                    "variablesReference": 0,
                }
            )

            session.request_continue()
            assert bp_var_content in home_request.response_text()
Esempio n. 4
0
def run_file():
    start_debugging(options.target)

    # run_path has one difference with invoking Python from command-line:
    # if the target is a file (rather than a directory), it does not add its
    # parent directory to sys.path. Thus, importing other modules from the
    # same directory is broken unless sys.path is patched here.
    if os.path.isfile(options.target):
        dir = os.path.dirname(options.target)
        sys.path.insert(0, dir)
    else:
        log.debug("Not a file: {0!r}", options.target)

    log.describe_environment("Pre-launch environment:")
    log.info("Running file {0!r}", options.target)

    runpy.run_path(options.target, run_name=compat.force_str("__main__"))
Esempio n. 5
0
def test_flask_breakpoint_multiproc(start_flask):
    bp_line = lines.app_py["bphome"]
    bp_var_content = compat.force_str("Flask-Jinja-Test")

    with debug.Session() as parent_session:
        with start_flask(parent_session, multiprocess=True):
            parent_session.set_breakpoints(paths.app_py, [bp_line])

        with parent_session.wait_for_next_subprocess() as child_session:
            with child_session.start():
                child_session.set_breakpoints(paths.app_py, [bp_line])

            with flask_server:
                home_request = flask_server.get("/")
                child_session.wait_for_stop(
                    "breakpoint",
                    expected_frames=[
                        some.dap.frame(some.dap.source(paths.app_py),
                                       line=bp_line,
                                       name="home")
                    ],
                )

                var_content = child_session.get_variable("content")
                assert var_content == some.dict.containing({
                    "name":
                    "content",
                    "type":
                    "str",
                    "value":
                    repr(bp_var_content),
                    "presentationHint": {
                        "attributes": ["rawString"]
                    },
                    "evaluateName":
                    "content",
                    "variablesReference":
                    0,
                })

                child_session.request_continue()
                assert bp_var_content in home_request.response_text()
Esempio n. 6
0
def main(args):
    # If we're talking DAP over stdio, stderr is not guaranteed to be read from,
    # so disable it to avoid the pipe filling and locking up. This must be done
    # as early as possible, before the logging module starts writing to it.
    if args.port is None:
        sys.stderr = open(os.devnull, "w")

    from debugpy import adapter
    from debugpy.common import compat, log, sockets
    from debugpy.adapter import clients, servers, sessions

    if args.for_server is not None:
        if os.name == "posix":
            # On POSIX, we need to leave the process group and its session, and then
            # daemonize properly by double-forking (first fork already happened when
            # this process was spawned).
            os.setsid()
            if os.fork() != 0:
                sys.exit(0)

        for stdio in sys.stdin, sys.stdout, sys.stderr:
            if stdio is not None:
                stdio.close()

    if args.log_stderr:
        log.stderr.levels |= set(log.LEVELS)
    if args.log_dir is not None:
        log.log_dir = args.log_dir

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

    servers.access_token = args.server_access_token
    if args.for_server is None:
        adapter.access_token = compat.force_str(
            codecs.encode(os.urandom(32), "hex"))

    endpoints = {}
    try:
        client_host, client_port = clients.serve(args.host, args.port)
    except Exception as exc:
        if args.for_server is None:
            raise
        endpoints = {
            "error": "Can't listen for client connections: " + str(exc)
        }
    else:
        endpoints["client"] = {"host": client_host, "port": client_port}

    if args.for_server is not None:
        try:
            server_host, server_port = servers.serve()
        except Exception as exc:
            endpoints = {
                "error": "Can't listen for server connections: " + str(exc)
            }
        else:
            endpoints["server"] = {"host": server_host, "port": server_port}

        log.info(
            "Sending endpoints info to debug server at localhost:{0}:\n{1!j}",
            args.for_server,
            endpoints,
        )

        try:
            sock = sockets.create_client()
            try:
                sock.settimeout(None)
                sock.connect(("127.0.0.1", args.for_server))
                sock_io = sock.makefile("wb", 0)
                try:
                    sock_io.write(json.dumps(endpoints).encode("utf-8"))
                finally:
                    sock_io.close()
            finally:
                sockets.close_socket(sock)
        except Exception:
            log.reraise_exception(
                "Error sending endpoints info to debug server:")

        if "error" in endpoints:
            log.error("Couldn't set up endpoints; exiting.")
            sys.exit(1)

    listener_file = os.getenv("DEBUGPY_ADAPTER_ENDPOINTS")
    if listener_file is not None:
        log.info("Writing endpoints info to {0!r}:\n{1!j}", listener_file,
                 endpoints)

        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")

        try:
            with open(listener_file, "w") as f:
                atexit.register(delete_listener_file)
                print(json.dumps(endpoints), file=f)
        except Exception:
            log.reraise_exception("Error writing endpoints info to file:")

    if args.port is None:
        clients.Client("stdio")

    # These must be registered after the one above, to ensure that the listener sockets
    # are closed before the endpoint info file is deleted - this way, another process
    # can wait for the file to go away as a signal that the ports are no longer in use.
    atexit.register(servers.stop_serving)
    atexit.register(clients.stop_serving)

    servers.wait_until_disconnected()
    log.info(
        "All debug servers disconnected; waiting for remaining sessions...")

    sessions.wait_until_ended()
    log.info("All debug sessions have ended; exiting.")
Esempio n. 7
0
def listen(address, settrace_kwargs):
    # Errors below are logged with level="info", because the caller might be catching
    # and handling exceptions, and we don't want to spam their stderr unnecessarily.

    import subprocess

    server_access_token = compat.force_str(codecs.encode(os.urandom(32), "hex"))

    try:
        endpoints_listener = sockets.create_server("127.0.0.1", 0, timeout=10)
    except Exception as exc:
        log.swallow_exception("Can't listen for adapter endpoints:")
        raise RuntimeError("can't listen for adapter endpoints: " + str(exc))
    endpoints_host, endpoints_port = endpoints_listener.getsockname()
    log.info(
        "Waiting for adapter endpoints on {0}:{1}...", endpoints_host, endpoints_port
    )

    host, port = address
    adapter_args = [
        sys.executable,
        os.path.dirname(adapter.__file__),
        "--for-server",
        str(endpoints_port),
        "--host",
        host,
        "--port",
        str(port),
        "--server-access-token",
        server_access_token,
    ]
    if log.log_dir is not None:
        adapter_args += ["--log-dir", log.log_dir]
    log.info("debugpy.listen() spawning adapter: {0!j}", adapter_args)

    # On Windows, detach the adapter from our console, if any, so that it doesn't
    # receive Ctrl+C from it, and doesn't keep it open once we exit.
    creationflags = 0
    if sys.platform == "win32":
        creationflags |= 0x08000000  # CREATE_NO_WINDOW
        creationflags |= 0x00000200  # CREATE_NEW_PROCESS_GROUP

    # Adapter will outlive this process, so we shouldn't wait for it. However, we
    # need to ensure that the Popen instance for it doesn't get garbage-collected
    # by holding a reference to it in a non-local variable, to avoid triggering
    # https://bugs.python.org/issue37380.
    try:
        global _adapter_process
        _adapter_process = subprocess.Popen(
            adapter_args, close_fds=True, creationflags=creationflags
        )
        if os.name == "posix":
            # It's going to fork again to daemonize, so we need to wait on it to
            # clean it up properly.
            _adapter_process.wait()
        else:
            # Suppress misleading warning about child process still being alive when
            # this process exits (https://bugs.python.org/issue38890).
            _adapter_process.returncode = 0
            pydevd.add_dont_terminate_child_pid(_adapter_process.pid)
    except Exception as exc:
        log.swallow_exception("Error spawning debug adapter:", level="info")
        raise RuntimeError("error spawning debug adapter: " + str(exc))

    try:
        sock, _ = endpoints_listener.accept()
        try:
            sock.settimeout(None)
            sock_io = sock.makefile("rb", 0)
            try:
                endpoints = json.loads(sock_io.read().decode("utf-8"))
            finally:
                sock_io.close()
        finally:
            sockets.close_socket(sock)
    except socket.timeout:
        log.swallow_exception("Timed out waiting for adapter to connect:", level="info")
        raise RuntimeError("timed out waiting for adapter to connect")
    except Exception as exc:
        log.swallow_exception("Error retrieving adapter endpoints:", level="info")
        raise RuntimeError("error retrieving adapter endpoints: " + str(exc))

    log.info("Endpoints received from adapter: {0!j}", endpoints)

    if "error" in endpoints:
        raise RuntimeError(str(endpoints["error"]))

    try:
        server_host = str(endpoints["server"]["host"])
        server_port = int(endpoints["server"]["port"])
        client_host = str(endpoints["client"]["host"])
        client_port = int(endpoints["client"]["port"])
    except Exception as exc:
        log.swallow_exception(
            "Error parsing adapter endpoints:\n{0!j}\n", endpoints, level="info"
        )
        raise RuntimeError("error parsing adapter endpoints: " + str(exc))
    log.info(
        "Adapter is accepting incoming client connections on {0}:{1}",
        client_host,
        client_port,
    )

    _settrace(
        host=server_host,
        port=server_port,
        wait_for_ready_to_run=False,
        block_until_connected=True,
        access_token=server_access_token,
        **settrace_kwargs
    )
    log.info("pydevd is connected to adapter at {0}:{1}", server_host, server_port)
    return client_host, client_port
Esempio n. 8
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 = {}

        if sys.platform != "win32":

            def preexec_fn():
                try:
                    # Start the debuggee in a new process group, so that the launcher can
                    # kill the entire process tree later.
                    os.setpgrp()

                    # Make the new process group the foreground group in its session, so
                    # that it can interact with the terminal. The debuggee will receive
                    # SIGTTOU when tcsetpgrp() is called, and must ignore it.
                    old_handler = signal.signal(signal.SIGTTOU, signal.SIG_IGN)
                    try:
                        tty = os.open("/dev/tty", os.O_RDWR)
                        try:
                            os.tcsetpgrp(tty, os.getpgrp())
                        finally:
                            os.close(tty)
                    finally:
                        signal.signal(signal.SIGTTOU, old_handler)
                except Exception:
                    # Not an error - /dev/tty doesn't work when there's no terminal.
                    log.swallow_exception(
                        "Failed to set up process group", level="info"
                    )

            kwargs.update(preexec_fn=preexec_fn)

        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())

        if sys.platform == "win32":
            # Assign the debuggee to a new job object, so that the launcher can kill
            # the entire process tree later.
            try:
                global job_handle
                job_handle = winapi.kernel32.CreateJobObjectA(None, None)

                job_info = winapi.JOBOBJECT_EXTENDED_LIMIT_INFORMATION()
                job_info_size = winapi.DWORD(ctypes.sizeof(job_info))
                winapi.kernel32.QueryInformationJobObject(
                    job_handle,
                    winapi.JobObjectExtendedLimitInformation,
                    ctypes.pointer(job_info),
                    job_info_size,
                    ctypes.pointer(job_info_size),
                )

                # Setting this flag ensures that the job will be terminated by the OS once the
                # launcher exits, even if it doesn't terminate the job explicitly.
                job_info.BasicLimitInformation.LimitFlags |= (
                    winapi.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
                )
                winapi.kernel32.SetInformationJobObject(
                    job_handle,
                    winapi.JobObjectExtendedLimitInformation,
                    ctypes.pointer(job_info),
                    job_info_size,
                )

                process_handle = winapi.kernel32.OpenProcess(
                    winapi.PROCESS_TERMINATE | winapi.PROCESS_SET_QUOTA,
                    False,
                    process.pid,
                )

                winapi.kernel32.AssignProcessToJobObject(job_handle, process_handle)

            except Exception:
                log.swallow_exception("Failed to set up job object", level="warning")

        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(level="warning")