Example #1
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)
Example #2
0
 def parse(s):
     n = parser(s)
     if start is not None and n < start:
         raise ValueError(fmt("must be >= {0}", start))
     if stop is not None and n >= stop:
         raise ValueError(fmt("must be < {0}", stop))
     return n
Example #3
0
def srcnameof(obj):
    """Returns the most descriptive name of a Python module, class, or function,
    including source information (filename and linenumber), if available.

    Best-effort, but guaranteed to not fail - always returns something.
    """

    name = nameof(obj, quote=True)

    # Get the source information if possible.
    try:
        src_file = filename(inspect.getsourcefile(obj), "replace")
    except Exception:
        pass
    else:
        name += fmt(" (file {0!r}", src_file)
        try:
            _, src_lineno = inspect.getsourcelines(obj)
        except Exception:
            pass
        else:
            name += fmt(", line {0}", src_lineno)
        name += ")"

    return name
Example #4
0
def configure(properties, **kwargs):
    if _settrace.called:
        raise RuntimeError("debug adapter is already running")

    ensure_logging()
    log.debug("configure{0!r}", (properties, kwargs))

    if properties is None:
        properties = kwargs
    else:
        properties = dict(properties)
        properties.update(kwargs)

    for k, v in properties.items():
        if k not in _config:
            raise ValueError(fmt("Unknown property {0!r}", k))
        expected_type = type(_config[k])
        if type(v) is not expected_type:
            raise ValueError(
                fmt("{0!r} must be a {1}", k, expected_type.__name__))
        valid_values = _config_valid_values.get(k)
        if (valid_values is not None) and (v not in valid_values):
            raise ValueError(
                fmt("{0!r} must be one of: {1!r}", k, valid_values))
        _config[k] = v
Example #5
0
def test_log_point(pyfile, target, run, condition):
    @pyfile
    def code_to_debug():
        import debuggee
        import sys

        debuggee.setup()
        for i in range(0, 10):
            sys.stderr.write(str(i * 10) + "\n")  # @bp
            sys.stderr.flush()
        ()  # @wait_for_output

    lines = code_to_debug.lines
    with debug.Session() as session:
        session.config["redirectOutput"] = True

        with run(session, target(code_to_debug)):
            bp = {"line": lines["bp"], "logMessage": "{i}"}
            if condition:
                bp["condition"] = "i == 5"
            session.request(
                "setBreakpoints",
                {
                    "source": {"path": code_to_debug},
                    "breakpoints": [bp, {"line": lines["wait_for_output"]}],
                },
            )

        if condition:
            session.wait_for_stop(
                "breakpoint", expected_frames=[some.dap.frame(code_to_debug, line="bp")]
            )

            var_i = session.get_variable("i")
            assert var_i == some.dict.containing(
                {"name": "i", "evaluateName": "i", "type": "int", "value": "5"}
            )

            session.request_continue()

        session.wait_for_stop(
            "breakpoint",
            expected_frames=[some.dap.frame(code_to_debug, line="wait_for_output")],
        )
        session.request_continue()

    # print() should produce both actual output, and "output" events on stderr,
    # but logpoints should only produce "output" events on stdout.
    if "internalConsole" not in str(run):
        assert not session.captured_stdout()

    expected_stdout = "".join(
        (fmt(r"{0}\r?\n", re.escape(str(i))) for i in range(0, 10))
    )
    expected_stderr = "".join(
        (fmt(r"{0}\r?\n", re.escape(str(i * 10))) for i in range(0, 10))
    )
    assert session.output("stdout") == some.str.matching(expected_stdout)
    assert session.output("stderr") == some.str.matching(expected_stderr)
Example #6
0
    def describe_circumstances(self):
        id = collections.OrderedDict(self._id)

        # Keep it all on one line if it's short enough, but indent longer ones.
        s = fmt("{0!j:indent=None}", id)
        if len(s) > SINGLE_LINE_REPR_LIMIT:
            s = fmt("{0!j}", id)
        return s
Example #7
0
 def __repr__(self):
     if self._cwd:
         return fmt(
             "program (relative) {0!j} / {1!j}",
             self._cwd,
             self._get_relative_program(),
         )
     else:
         return fmt("program {0!j}", compat.filename(self.filename.strpath))
Example #8
0
def inject(pid, debugpy_args):
    host, port = listener.getsockname()

    cmdline = [
        sys.executable,
        compat.filename(os.path.dirname(debugpy.__file__)),
        "--connect",
        host + ":" + str(port),
    ]
    if adapter.access_token is not None:
        cmdline += ["--adapter-access-token", adapter.access_token]
    cmdline += debugpy_args
    cmdline += ["--pid", str(pid)]

    log.info("Spawning attach-to-PID debugger injector: {0!r}", cmdline)
    try:
        injector = subprocess.Popen(
            cmdline,
            bufsize=0,
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
        )
    except Exception as exc:
        log.swallow_exception(
            "Failed to inject debug server into process with PID={0}", pid
        )
        raise messaging.MessageHandlingError(
            fmt(
                "Failed to inject debug server into process with PID={0}: {1}", pid, exc
            )
        )

    # We need to capture the output of the injector - otherwise it can get blocked
    # on a write() syscall when it tries to print something.

    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)

    thread = threading.Thread(
        target=capture_output, name=fmt("Injector[PID={0}] output", pid)
    )
    thread.daemon = True
    thread.start()
Example #9
0
def test_add_and_remove_breakpoint(pyfile, target, run):
    @pyfile
    def code_to_debug():
        import debuggee

        debuggee.setup()
        for i in range(0, 10):
            print(i)  # @bp
        ()  # @wait_for_output

    with debug.Session() as session:
        session.config["redirectOutput"] = True

        with run(session, target(code_to_debug)):
            session.set_breakpoints(code_to_debug, all)

        session.wait_for_stop(
            "breakpoint", expected_frames=[some.dap.frame(code_to_debug, line="bp")]
        )

        # Remove breakpoint inside the loop.
        session.set_breakpoints(code_to_debug, ["wait_for_output"])
        session.request_continue()

        session.wait_for_stop(
            "breakpoint",
            expected_frames=[some.dap.frame(code_to_debug, line="wait_for_output")],
        )
        session.request_continue()

    expected_stdout = "".join((fmt("{0}\n", i) for i in range(0, 10)))
    assert session.output("stdout") == expected_stdout
Example #10
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
Example #11
0
 def __repr__(self):
     return fmt(
         "{2}{0}.{1}",
         self.index,
         self.describe_circumstances(),
         "" if self.observed else "*",
     )
Example #12
0
    def __init__(self, component, message):
        """Parses an "initialize" request or response and extracts the feature flags.

        For every "X" in self.PROPERTIES, sets self["X"] to the corresponding value
        from message.payload if it's present there, or to the default value otherwise.
        """

        assert message.is_request("initialize") or message.is_response("initialize")

        self.component = component

        payload = message.payload
        for name, validate in self.PROPERTIES.items():
            value = payload.get(name, ())
            if not callable(validate):
                validate = json.default(validate)

            try:
                value = validate(value)
            except Exception as exc:
                raise message.isnt_valid("{0!j} {1}", name, exc)

            assert value != (), fmt(
                "{0!j} must provide a default value for missing properties.", validate
            )
            self[name] = value

        log.debug("{0}", self)
Example #13
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()
Example #14
0
    def __init__(self, name=None):
        self.name = str(name if name is not None else id(self))
        self.ignore_unobserved = []

        self._listeners = []  # [(expectation, callable)]
        self._index_iter = itertools.count(1)
        self._accepting_new = threading.Event()
        self._finalized = threading.Event()
        self._recorded_new = threading.Condition()
        self._record_queue = queue.Queue()

        self._recorder_thread = threading.Thread(target=self._recorder_worker,
                                                 name=fmt(
                                                     "{0} recorder", self))
        self._recorder_thread.daemon = True
        self._recorder_thread.start()

        # Set up initial environment for our first mark()
        self._last = None
        self._beginning = None
        self._accepting_new.set()

        self._beginning = self.mark("START")
        assert self._last is self._beginning
        self._proceeding_from = self._beginning
Example #15
0
    def report_paths(get_paths, label=None):
        prefix = fmt("    {0}: ", label or get_paths)

        expr = None
        if not callable(get_paths):
            expr = get_paths
            get_paths = lambda: util.evaluate(expr)
        try:
            paths = get_paths()
        except AttributeError:
            report("{0}<missing>\n", prefix)
            return
        except Exception:
            swallow_exception(
                "Error evaluating {0}",
                repr(expr) if expr else compat.srcnameof(get_paths),
            )
            return

        if not isinstance(paths, (list, tuple)):
            paths = [paths]

        for p in sorted(paths):
            report("{0}{1}", prefix, p)
            rp = os.path.realpath(p)
            if p != rp:
                report("({0})", rp)
            report("\n")

            prefix = " " * len(prefix)
Example #16
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)
Example #17
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()
Example #18
0
def parse_argv():
    seen = set()
    it = consume_argv()

    while True:
        try:
            arg = next(it)
        except StopIteration:
            raise ValueError("missing target: " + TARGET)

        switch = compat.filename(arg)
        if not switch.startswith("-"):
            switch = ""
        for pattern, placeholder, action in switches:
            if re.match("^(" + pattern + ")$", switch):
                break
        else:
            raise ValueError("unrecognized switch " + switch)

        if switch in seen:
            raise ValueError("duplicate switch " + switch)
        else:
            seen.add(switch)

        try:
            action(arg, it)
        except StopIteration:
            assert placeholder is not None
            raise ValueError(fmt("{0}: missing {1}", switch, placeholder))
        except Exception as exc:
            raise ValueError(
                fmt("invalid {0} {1}: {2}", switch, placeholder, exc))

        if options.target is not None:
            break

    if options.mode is None:
        raise ValueError("either --listen or --connect is required")
    if options.adapter_access_token is not None and options.mode != "connect":
        raise ValueError("--adapter-access-token requires --connect")
    if options.target_kind == "pid" and options.wait_for_client:
        raise ValueError("--pid does not support --wait-for-client")

    assert options.target is not None
    assert options.target_kind is not None
    assert options.address is not None
Example #19
0
 def __repr__(self):
     result = type(self).__name__
     args = [str(x) for x in self._args] + [
         fmt("{0}={1}", k, v) for k, v in self._kwargs.items()
     ]
     if len(args):
         result += "(" + ", ".join(args) + ")"
     return result
Example #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)
Example #21
0
    def _capture(self, fd, name):
        assert name not in self._chunks
        self._chunks[name] = []

        thread = threading.Thread(target=lambda: self._worker(fd, name),
                                  name=fmt("{0} {1}", self, name))
        thread.daemon = True
        thread.start()
        self._worker_threads.append(thread)
Example #22
0
 def make_breakpoint(line):
     if isinstance(line, int):
         descr = str(line)
     else:
         marker = line
         line = get_marked_line_numbers()[marker]
         descr = fmt("{0} (@{1})", line, marker)
     bp_log.append((line, descr))
     return {"line": line}
Example #23
0
def set_config(arg, it):
    prefix = "--configure-"
    assert arg.startswith(prefix)
    name = arg[len(prefix) :]
    value = next(it)

    if name not in options.config:
        raise ValueError(fmt("unknown property {0!r}", name))

    expected_type = type(options.config[name])
    try:
        if expected_type is bool:
            value = {"true": True, "false": False}[value.lower()]
        else:
            value = expected_type(value)
    except Exception:
        raise ValueError(fmt("{0!r} must be a {1}", name, expected_type.__name__))

    options.config[name] = value
Example #24
0
def _dump_worker_log(command, problem, exc_info=None):
    reason = fmt("{0}.{1}() {2}", _name, command, problem)
    if _worker_log_filename is None:
        reason += ", but there is no log."
    else:
        try:
            with open(_worker_log_filename) as f:
                worker_log = f.read()
        except Exception:
            reason += fmt(", but log {0} could not be retrieved.",
                          _worker_log_filename)
        else:
            reason += fmt("; watchdog worker process log:\n\n{0}", worker_log)

    if exc_info is None:
        log.error("{0}", reason)
    else:
        log.swallow_exception("{0}", reason, exc_info=exc_info)
    return reason
Example #25
0
 def __setitem__(self, key, value):
     """Sets debuggee.scratchpad[key] = value inside the debugged process.
     """
     log.info("{0} debuggee.scratchpad[{1!r}] = {2!r}", self.session, key,
              value)
     expr = fmt("sys.modules['debuggee'].scratchpad[{0!r}] = {1!r}", key,
                value)
     self.session.request("evaluate", {
         "context": "repl",
         "expression": expr
     })
Example #26
0
 def _process_event(self, event):
     occ = self.timeline.record_event(event, block=False)
     if event.event == "exited":
         self.observe(occ)
         self.exit_code = event("exitCode", int)
         assert self.exit_code == self.expected_exit_code
     elif event.event == "debugpyAttach":
         self.observe(occ)
         pid = event("subProcessId", int)
         watchdog.register_spawn(
             pid, fmt("{0}-subprocess-{1}", self.debuggee_id, pid))
Example #27
0
 def start(self):
     config = self.config
     request = config.get("request", None)
     if request == "attach":
         host = config["connect"]["host"]
         port = config["connect"]["port"]
         self.connect_to_adapter((host, port))
         return self.request_attach()
     else:
         raise ValueError(
             fmt('Unsupported "request":{0!j} in session.config', request))
Example #28
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()
Example #29
0
def prefixed(format_string, *args, **kwargs):
    """Adds a prefix to all messages logged from the current thread for the duration
    of the context manager.
    """
    prefix = fmt(format_string, *args, **kwargs)
    old_prefix = getattr(_tls, "prefix", "")
    _tls.prefix = prefix + old_prefix
    try:
        yield
    finally:
        _tls.prefix = old_prefix
Example #30
0
def _attach_common_config(session, target, cwd):
    assert target.code is None or "debuggee.setup()" in target.code, fmt(
        "{0} must invoke debuggee.setup().", target.filename)

    target.configure(session)
    config = session.config
    if cwd is not None:
        config.setdefault("pathMappings", [{
            "localRoot": cwd,
            "remoteRoot": "."
        }])
    return config