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 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()
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()
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__"))
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()
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.")
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
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")