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)
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
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
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
def attach_by_socket(session, target, method, listener="server", 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") assert listener in ("server") # TODO: ("adapter", "server") config = _attach_common_config(session, target, cwd) host = config["host"] = attach_by_socket.host port = config["port"] = attach_by_socket.port if method == "cli": args = [os.path.dirname(ptvsd.__file__)] if wait: args += ["--wait"] args += ["--host", compat.filename_str(host), "--port", str(port)] if not config["subProcess"]: args += ["--no-subprocesses"] if log_dir is not None: args += ["--log-dir", log_dir] debug_me = None elif method == "api": args = [] debug_me = """ import ptvsd ptvsd.enable_attach(({host!r}, {port!r}), {args}) if {wait!r}: ptvsd.wait_for_attach() """ attach_args = "" if log_dir is None else fmt("log_dir={0!r}", log_dir) debug_me = fmt(debug_me, host=host, port=port, wait=wait, args=attach_args) else: raise ValueError args += target.cli(session.spawn_debuggee.env) session.spawn_debuggee(args, cwd=cwd, debug_me=debug_me) if wait: session.wait_for_enable_attach() session.connect_to_adapter((host, port)) return session.request_attach()
def parse(args, options=options): seen = set() it = (compat.filename(arg) for arg in args) while True: try: arg = next(it) except StopIteration: raise ValueError("missing target: " + TARGET) switch = arg if arg.startswith("-") else "" for i, (sw, placeholder, action, _) in enumerate(switches): if not isinstance(sw, tuple): sw = (sw, ) if switch in sw: break else: raise ValueError("unrecognized switch " + switch) if i in seen: raise ValueError("duplicate switch " + switch) else: seen.add(i) 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 for i, (sw, placeholder, _, required) in enumerate(switches): if not required or i in seen: continue if isinstance(sw, tuple): sw = sw[0] message = fmt("missing required {0}", sw) if placeholder is not None: message += " " + placeholder raise ValueError(message) if options.target_kind == "pid" and options.wait: raise ValueError("--pid does not support --wait") return it
def inject(pid, ptvsd_args): host, port = Connection.listener.getsockname() cmdline = [ sys.executable, compat.filename(os.path.dirname(ptvsd.__file__)), "--client", "--host", host, "--port", str(port), ] if adapter.access_token is not None: cmdline += ["--client-access-token", adapter.access_token] cmdline += ptvsd_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.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()
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
def test_wrapper(request, long_tmpdir): def write_log(filename, data): filename = os.path.join(log.log_dir, filename) if not isinstance(data, bytes): data = data.encode("utf-8") with open(filename, "wb") as f: f.write(data) session.Session.reset_counter() session.Session.tmpdir = long_tmpdir original_log_dir = log.log_dir try: if log.log_dir is None: log.log_dir = (long_tmpdir / "ptvsd_logs").strpath else: log_subdir = request.node.nodeid log_subdir = log_subdir.replace("::", "/") for ch in r":?*|<>": log_subdir = log_subdir.replace(ch, fmt("&#{0};", ord(ch))) log.log_dir += "/" + log_subdir try: py.path.local(log.log_dir).remove() except Exception: pass print("\n") # make sure on-screen logs start on a new line with log.to_file(prefix="tests"): timestamp.reset() log.info("{0} started.", request.node.nodeid) try: yield finally: failed = False for report_attr in ("setup_report", "call_report", "teardown_report"): try: report = getattr(request.node, report_attr) except AttributeError: continue failed |= report.failed log.write_format( "error" if report.failed else "info", "pytest {0} phase for {1} {2}.", report.when, request.node.nodeid, report.outcome, ) write_log(report_attr + ".log", report.longreprtext) write_log(report_attr + ".stdout.log", report.capstdout) write_log(report_attr + ".stderr.log", report.capstderr) if failed: write_log("FAILED.log", "") logs.dump() finally: log.log_dir = original_log_dir
def __repr__(self): return fmt( "{2}{0}.{1}", self.index, self.describe_circumstances(), "" if self.observed else "*", )
def listen(self): self._server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._server_socket.settimeout(self.TIMEOUT) self._server_socket.bind(("127.0.0.1", 0)) _, self.port = self._server_socket.getsockname() self._server_socket.listen(0) def accept_worker(): log.info( "Listening for incoming connection from {0} on port {1}...", self, self.port, ) try: self._socket, _ = self._server_socket.accept() except socket.timeout: raise log.exception("Timed out waiting for {0} to connect", self) self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) log.info("Incoming connection from {0} accepted.", self) self._setup_stream() accept_thread = threading.Thread(target=accept_worker, name=fmt("{0} listener", self)) accept_thread.daemon = True accept_thread.start()
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)
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: 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)
def test_add_and_remove_breakpoint(pyfile, target, run): @pyfile def code_to_debug(): import debug_me # noqa 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
def __init__(self, method, url, *args, **kwargs): """Invokes requests.method(url, *args, **kwargs) on a background thread, and immediately returns. If method() raises an exception, it is logged, unless log_errors=False. """ self.method = method self.url = url self.log_errors = kwargs.pop("log_errors", True) self.request = None """The underlying requests.Request object. Not set until wait_for_response() returns. """ self.exception = None """Exception that occurred while performing the request, if any. Not set until wait_for_response() returns. """ log.info("{0}", self) func = getattr(requests, method) self._worker_thread = threading.Thread( target=lambda: self._worker(func, *args, **kwargs), name=fmt("WebRequest({0})", self), ) self._worker_thread.daemon = True self._worker_thread.start()
def inject_server(self, pid, ptvsd_args): with self.accept_connection_from_server() as (host, port): cmdline = [ sys.executable, compat.filename(os.path.dirname(ptvsd.__file__)), "--client", "--host", host, "--port", str(port), ] cmdline += ptvsd_args cmdline += ["--pid", str(pid)] log.info("{0} spawning attach-to-PID debugger injector: {1!r}", self, cmdline) try: subprocess.Popen( cmdline, bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) except Exception as exc: log.exception("{0} failed to inject debugger", self) raise messaging.MessageHandlingError( fmt("Failed to inject debugger: {0}", exc))
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["name"] = fmt("Subprocess {0}", conn.pid) body["request"] = "attach" if "host" not in body: body["host"] = "127.0.0.1" if "port" not in body: _, body["port"] = self.listener.getsockname() if "processId" in body: del body["processId"] body["subProcessId"] = conn.pid self.channel.send_event("ptvsd_attach", body)
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
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}
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)
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.exception("{0}", reason, exc_info=exc_info) return reason
def _attach_common_config(session, target, cwd): assert target.code is None or "debug_me" in target.code, fmt( "{0} must import debug_me.", target.filename ) target.configure(session) config = session.config if cwd is not None: config.setdefault("pathMappings", [{"localRoot": cwd, "remoteRoot": "."}]) return config
def __setitem__(self, key, value): """Sets debug_me.scratchpad[key] = value inside the debugged process. """ log.info("{0} debug_me.scratchpad[{1!r}] = {2!r}", self.session, key, value) expr = fmt("sys.modules['debug_me'].scratchpad[{0!r}] = {1!r}", key, value) self.session.request("evaluate", { "context": "repl", "expression": expr })
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
def connect(session_id, launcher_port): global channel assert channel is None sock = sockets.create_client() sock.connect(("127.0.0.1", launcher_port)) stream = messaging.JsonIOStream.from_socket(sock, fmt("Adapter-{0}", session_id)) channel = messaging.JsonMessageChannel(stream, handlers=Handlers()) channel.start()
def start(self): config = self.config request = config.get("request", None) if request == "attach": host = config["host"] port = config["port"] self.connect_to_adapter((host, port)) return self.request_attach() else: raise ValueError( fmt('Unsupported "request":{0!j} in session.config', request))
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 == "ptvsd_attach": self.observe(occ) pid = event("subProcessId", int) watchdog.register_spawn( pid, fmt("{0}-subprocess-{1}", self.debuggee_id, pid))
def validate(value): if value == (): return {} of_type(dict)(value) if validate_value: for k, v in value.items(): try: value[k] = validate_value(v) except (TypeError, ValueError) as exc: raise type(exc)(fmt("[{0!j}] {1}", k, exc)) return value
def write_format(level, format_string, *args, **kwargs): # Don't spend cycles doing expensive formatting if we don't have to. Errors are # always formatted, so that error() can return the text even if it's not logged. if level != "error" and level not in _levels: return try: text = fmt(format_string, *args, **kwargs) except Exception: exception() raise return write(level, text, kwargs.pop("_to_files", all))
def __init__(self, sock): from ptvsd.adapter import sessions self.server = None """The Server component, if this debug server belongs to Session. """ self.pid = None stream = messaging.JsonIOStream.from_socket(sock, str(self)) self.channel = messaging.JsonMessageChannel(stream, self) self.channel.start() try: info = self.channel.request("pydevdSystemInfo") process_info = info("process", json.object()) self.pid = process_info("pid", int) self.ppid = process_info("ppid", int, optional=True) if self.ppid == (): self.ppid = None self.channel.name = stream.name = str(self) with _lock: if any(conn.pid == self.pid for conn in _connections): raise KeyError( fmt("{0} is already connected to this adapter", self)) _connections.append(self) _connections_changed.set() except Exception: log.exception("Failed to accept incoming server connection:") # If we couldn't retrieve all the necessary info from the debug server, # or there's a PID clash, we don't want to track this debuggee anymore, # but we want to continue accepting connections. self.channel.close() return parent_session = sessions.get(self.ppid) if parent_session is None: log.info("No active debug session for parent process of {0}.", self) else: try: parent_session.ide.notify_of_subprocess(self) except Exception: # This might fail if the IDE concurrently disconnects from the parent # session. We still want to keep the connection around, in case the # IDE reconnects later. If the parent session was "launch", it'll take # care of closing the remaining server connections. log.exception("Failed to notify parent session about {0}:", self)