def debug(address, log_dir=None, multiprocess=True): if log_dir: log.log_dir = log_dir log.to_file(prefix="ptvsd.server") log.describe_environment("ptvsd.server debug start environment:") log.debug("{0}{1!r}", func.__name__, (address, log_dir, multiprocess)) if is_attached(): log.info("{0}() ignored - already attached.", func.__name__) return options.host, options.port # Ensure port is int if address is not options: host, port = address options.host, options.port = (host, int(port)) if multiprocess is not options: options.multiprocess = multiprocess ptvsd_path, _, _ = get_abs_path_real_path_and_base_from_file(ptvsd.__file__) ptvsd_path = os.path.dirname(ptvsd_path) start_patterns = (ptvsd_path,) end_patterns = ("ptvsd_launcher.py",) log.info( "Won't trace filenames starting with: {0!j}\n" "Won't trace filenames ending with: {1!j}", start_patterns, end_patterns, ) try: return func(start_patterns, end_patterns) except Exception: raise log.exception("{0}() failed:", func.__name__, level="info")
def main(): from ptvsd.common import log from ptvsd import launcher from ptvsd.launcher import debuggee log.to_file(prefix="ptvsd.launcher") log.describe_environment("ptvsd.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) def option(name, type, *args): try: return type(os.environ.pop(name, *args)) except Exception: raise log.exception("Error parsing {0!r}:", name) launcher_port = option("PTVSD_LAUNCHER_PORT", int) launcher.connect(launcher_port) launcher.channel.wait() if debuggee.process is not None: sys.exit(debuggee.process.returncode)
def main(): original_argv = list(sys.argv) try: sys.argv[1:] = parse(sys.argv[1:]) except Exception as ex: print(HELP + "\nError: " + str(ex), file=sys.stderr) sys.exit(2) log.to_file(prefix="ptvsd.server") log.describe_environment("ptvsd.server startup environment:") 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 ex: log.exception("Debuggee exited via SystemExit: {0!r}", ex.code, level="debug") raise
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 main(args): from ptvsd.common import log, options as common_options from ptvsd.adapter import ide, servers, sessions, options as adapter_options if args.log_stderr: log.stderr.levels |= set(log.LEVELS) adapter_options.log_stderr = True if args.log_dir is not None: common_options.log_dir = args.log_dir log.to_file(prefix="ptvsd.adapter") log.describe_environment("ptvsd.adapter startup environment:") if args.for_enable_attach and args.port is None: log.error("--for-enable-attach requires --port") sys.exit(64) server_host, server_port = servers.listen() ide_host, ide_port = ide.listen(port=args.port) if args.for_enable_attach: endpoints = { "ide": { "host": ide_host, "port": ide_port }, "server": { "host": server_host, "port": server_port }, } log.info("Sending endpoints to stdout: {0!r}", endpoints) print(json.dumps(endpoints)) sys.stdout.flush() if args.port is None: ide.IDE("stdio") 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 main(): from ptvsd.common import log from ptvsd.launcher import adapter, debuggee log.to_file(prefix="ptvsd.launcher") log.describe_environment("ptvsd.launcher startup environment:") def option(name, type, *args): try: return type(os.environ.pop(name, *args)) except Exception: raise log.exception("Error parsing {0!r}:", name) session_id = option("PTVSD_SESSION_ID", int) launcher_port = option("PTVSD_LAUNCHER_PORT", int) adapter.connect(session_id, launcher_port) adapter.channel.wait() if debuggee.process is not None: sys.exit(debuggee.process.returncode)
def main(args): from ptvsd.common import log, options as common_options from ptvsd.adapter import session, options as adapter_options if args.log_stderr: log.stderr_levels |= set(log.LEVELS) adapter_options.log_stderr = True if args.log_dir is not None: common_options.log_dir = args.log_dir log.to_file(prefix="ptvsd.adapter") log.describe_environment("ptvsd.adapter startup environment:") session = session.Session() if args.port is None: session.connect_to_ide() else: if args.for_server_on_port is not None: session.connect_to_server(("127.0.0.1", args.for_server_on_port)) with session.accept_connection_from_ide((args.host, args.port)) as (_, port): if session.server: session.server.set_debugger_property({"adapterPort": port}) session.wait_for_completion()
pytest.register_assert_rewrite(modname) _register_assert_rewrite("ptvsd.common") tests_submodules = pkgutil.iter_modules([str(root)]) for _, submodule, _ in tests_submodules: submodule = str("{0}.{1}".format(__name__, submodule)) _register_assert_rewrite(submodule) # Now we can import these, and pytest will rewrite asserts in them. from ptvsd.common import json, log import ptvsd.server # noqa # Enable full logging to stderr, and make timestamps shorter to match maximum test # run time better. log.stderr.levels = all log.timestamp_format = "06.3f" log.to_file(prefix="tests") # Enable JSON serialization for py.path.local. def json_default(self, obj): if isinstance(obj, py.path.local): return obj.strpath return self.original_default(obj) json.JsonEncoder.original_default = json.JsonEncoder.default json.JsonEncoder.default = json_default
def main(tests_pid): # To import ptvsd, the "" entry in sys.path - which is added automatically on # Python 2 - must be removed first; otherwise, we end up importing tests/ptvsd. if "" in sys.path: sys.path.remove("") from ptvsd.common import fmt, log, messaging # log.stderr_levels |= {"info"} log.timestamp_format = "06.3f" log_file = log.to_file(prefix="tests.watchdog") stream = messaging.JsonIOStream.from_stdio(fmt("tests-{0}", tests_pid)) log.info("Spawned WatchDog-{0} for tests-{0}", tests_pid) tests_process = psutil.Process(tests_pid) stream.write_json(["watchdog", log_file.filename]) spawned_processes = {} # pid -> ProcessInfo try: stop = False while not stop: try: message = stream.read_json() except Exception: break command = message[0] args = message[1:] if command == "stop": assert not args stop = True elif command == "register_spawn": pid, name = args pid = int(pid) log.info( "WatchDog-{0} registering spawned process {1} (pid={2})", tests_pid, name, pid, ) try: _, old_name = spawned_processes[pid] except KeyError: pass else: log.warning( "WatchDog-{0} already tracks a process with pid={1}: {2}", tests_pid, pid, old_name, ) spawned_processes[pid] = ProcessInfo(psutil.Process(pid), name) elif command == "unregister_spawn": pid, name = args pid = int(pid) log.info( "WatchDog-{0} unregistering spawned process {1} (pid={2})", tests_pid, name, pid, ) spawned_processes.pop(pid, None) else: raise AssertionError( fmt("Unknown watchdog command: {0!r}", command)) stream.write_json(["ok"]) except Exception as ex: stream.write_json(["error", str(ex)]) raise log.exception() finally: try: stream.close() except Exception: log.exception() # If the test runner becomes a zombie process, it is still considered alive, # and wait() will block indefinitely. Poll status instead. while True: try: status = tests_process.status() except Exception: # If we can't even get its status, assume that it's dead. break # If it's dead or a zombie, time to clean it up. if status in (psutil.STATUS_DEAD, psutil.STATUS_ZOMBIE): break # Otherwise, let's wait a bit to see if anything changes. try: tests_process.wait(0.1) except Exception: pass leftover_processes = {proc for proc, _ in spawned_processes.values()} for proc, _ in spawned_processes.values(): try: leftover_processes |= proc.children(recursive=True) except Exception: pass leftover_processes = { proc for proc in leftover_processes if proc.is_running() } if not leftover_processes: return # Wait a bit to allow the terminal to catch up on the test runner output. time.sleep(0.3) log.newline(level="warning") log.warning( "tests-{0} process terminated unexpectedly, and left some orphan child " "processes behind: {1!r}", tests_pid, sorted({proc.pid for proc in leftover_processes}), ) for proc in leftover_processes: log.warning( "WatchDog-{0} killing orphaned test child process (pid={1})", tests_pid, proc.pid, ) if platform.system() == "Linux": try: # gcore will automatically add pid to the filename core_file = os.path.join(tempfile.gettempdir(), "ptvsd_core") gcore_cmd = fmt("gcore -o {0} {1}", core_file, proc.pid) log.warning("WatchDog-{0}: {1}", tests_pid, gcore_cmd) os.system(gcore_cmd) except Exception: log.exception() try: proc.kill() except psutil.NoSuchProcess: pass except Exception: log.exception() log.info("WatchDog-{0} exiting", tests_pid)
def main(args): from ptvsd import adapter from ptvsd.common import compat, log, sockets from ptvsd.adapter import ide, 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="ptvsd.adapter") log.describe_environment("ptvsd.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")) try: server_host, server_port = servers.listen() except Exception as exc: if args.for_server is None: raise endpoints = { "error": "Can't listen for server connections: " + str(exc) } else: endpoints = {"server": {"host": server_host, "port": server_port}} try: ide_host, ide_port = ide.listen(port=args.port) except Exception as exc: if args.for_server is None: raise endpoints = { "error": "Can't listen for IDE connections: " + str(exc) } else: endpoints["ide"] = {"host": ide_host, "port": ide_port} if args.for_server is not None: 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: raise log.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("PTVSD_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.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: raise log.exception("Error writing endpoints info to file:") if args.port is None: ide.IDE("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_listening) atexit.register(ide.stop_listening) 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 main(args): from ptvsd import adapter from ptvsd.common import compat, log from ptvsd.adapter import ide, servers, sessions 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="ptvsd.adapter") log.describe_environment("ptvsd.adapter startup environment:") if args.for_server and args.port is None: log.error("--for-server requires --port") sys.exit(64) servers.access_token = args.server_access_token if not args.for_server: adapter.access_token = compat.force_str( codecs.encode(os.urandom(32), "hex")) server_host, server_port = servers.listen() ide_host, ide_port = ide.listen(port=args.port) endpoints_info = { "ide": { "host": ide_host, "port": ide_port }, "server": { "host": server_host, "port": server_port }, } if args.for_server: log.info("Writing endpoints info to stdout:\n{0!r}", endpoints_info) print(json.dumps(endpoints_info)) sys.stdout.flush() if args.port is None: ide.IDE("stdio") listener_file = os.getenv("PTVSD_ADAPTER_ENDPOINTS") if listener_file is not None: log.info("Writing endpoints info to {0!r}:\n{1!r}", listener_file, endpoints_info) def delete_listener_file(): log.info("Listener ports closed; deleting {0!r}", listener_file) try: os.remove(listener_file) except Exception: log.exception("Failed to delete {0!r}", listener_file, level="warning") with open(listener_file, "w") as f: atexit.register(delete_listener_file) print(json.dumps(endpoints_info), file=f) # 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_listening) atexit.register(ide.stop_listening) 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.")