def code_to_debug(): import debuggee import debugpy import sys import time from debuggee import backchannel, scratchpad debuggee.setup() _, host, port, wait_for_client, is_client_connected, stop_method = sys.argv port = int(port) debugpy.listen(address=(host, port)) if wait_for_client: backchannel.send("wait_for_client") debugpy.wait_for_client() if is_client_connected: backchannel.send("is_client_connected") while not debugpy.is_client_connected(): print("looping until is_client_connected()") time.sleep(0.1) if stop_method == "breakpoint": backchannel.send("breakpoint?") assert backchannel.receive() == "proceed" debugpy.breakpoint() print("break") # @breakpoint else: scratchpad["paused"] = False backchannel.send("loop?") assert backchannel.receive() == "proceed" while not scratchpad["paused"]: print("looping until paused") time.sleep(0.1)
def code_to_debug(): import debuggee from debuggee import backchannel debuggee.setup() backchannel.receive() # @ bp1 print("ok") # @ bp2
def child(): import os from debuggee import backchannel backchannel.send(os.getpid()) backchannel.receive() backchannel.send("ok")
def test_breakaway_job(pyfile, target, run): @pyfile def child(): import os from debuggee import backchannel backchannel.send(os.getpid()) backchannel.receive() backchannel.send("ok") @pyfile def parent(): import debuggee import os import subprocess import sys debuggee.setup() argv = [sys.executable, sys.argv[1]] env = os.environ.copy() pipe_in = open(os.devnull, "r") pipe_out = open(os.devnull, "w") CREATE_BREAKAWAY_FROM_JOB = 0x01000000 proc = subprocess.Popen( argv, env=env, stdin=pipe_in, stdout=pipe_out, stderr=pipe_out, creationflags=CREATE_BREAKAWAY_FROM_JOB, ) pipe_in.close() pipe_out.close() proc.wait() with debug.Session() as parent_session: parent_session.config.update({ "redirectOutput": False, "subProcess": False, }) parent_session.expected_exit_code = some.int backchannel = parent_session.open_backchannel() with run(parent_session, target(parent, args=[child])): pass child_pid = backchannel.receive() child_process = psutil.Process(child_pid) parent_session.request("terminate") parent_session.wait_for_exit() # child should still be running backchannel.send("proceed") assert backchannel.receive() == "ok" log.info("Waiting for child process...") child_process.wait()
def test_argv_quoting(pyfile, target, run): @pyfile def args(): args = [ # noqa r"regular", r"", r"with spaces" r'"quoted"', r'" quote at start', r'quote at end "', r'quote in " the middle', r'quotes "in the" middle', r"\path with\spaces", r"\path\with\terminal\backslash" + "\\", r"backslash \" before quote", ] @pyfile def parent(): import debuggee import sys import subprocess from args import args debuggee.setup() child = sys.argv[1] subprocess.check_call([sys.executable] + [child] + args) @pyfile def child(): import sys from debuggee import backchannel from args import args as expected_args backchannel.send(expected_args) actual_args = sys.argv[1:] backchannel.send(actual_args) with debug.Session() as parent_session: backchannel = parent_session.open_backchannel() with run(parent_session, target(parent, args=[child])): pass child_config = parent_session.wait_for_next_event("debugpyAttach") parent_session.proceed() with debug.Session(child_config) as child_session: with child_session.start(): pass expected_args = backchannel.receive() actual_args = backchannel.receive() assert expected_args == actual_args
def parent(q, a): from debuggee import backchannel debuggee.setup() print("spawning child") p = multiprocessing.Process(target=child, args=(q, a)) p.start() print("child spawned") q.put("foo?") foo = a.get() assert isinstance(foo, Foo), repr(foo) q.put("child_pid?") what, child_pid = a.get() assert what == "child_pid" backchannel.send(child_pid) q.put("grandchild_pid?") what, grandchild_pid = a.get() assert what == "grandchild_pid" backchannel.send(grandchild_pid) assert backchannel.receive() == "continue" q.put("exit!") p.join()
def test_custom_python_args(pyfile, tmpdir, run, target, python_key, python, python_args): @pyfile def code_to_debug(): import sys import debuggee from debuggee import backchannel debuggee.setup() backchannel.send([sys.flags.optimize, sys.flags.dont_write_bytecode]) custompy = make_custompy(tmpdir) python = [] if python is None else python.split(",") python = [(custompy if arg == "custompy" else arg) for arg in python] python_args = [] if python_args is None else python_args.split(",") python_cmd = (python if len(python) else [sys.executable]) + python_args with debug.Session() as session: session.config.pop("python", None) session.config.pop("pythonPath", None) if len(python): session.config[python_key] = python[0] if len( python) == 1 else python if len(python_args): session.config["pythonArgs"] = python_args backchannel = session.open_backchannel() with run(session, target(code_to_debug)): pass assert backchannel.receive() == [ "-O" in python_cmd, "-B" in python_cmd ]
def test_run(pyfile, target, run): @pyfile def code_to_debug(): import os import sys import debuggee from debuggee import backchannel debuggee.setup() print("begin") backchannel.send(os.path.abspath(sys.modules["debugpy"].__file__)) assert backchannel.receive() == "continue" print("end") with debug.Session() as session: backchannel = session.open_backchannel() with run(session, target(code_to_debug)): pass expected_debugpy_path = os.path.abspath(debugpy.__file__) assert backchannel.receive() == some.str.matching( re.escape(expected_debugpy_path) + r"(c|o)?") backchannel.send("continue") session.wait_for_next_event("terminated") session.proceed()
def test_unicode_dir(tmpdir, run, target): from debugpy.common import compat unicode_chars = "á" directory = os.path.join(compat.force_unicode(str(tmpdir), "ascii"), unicode_chars) directory = compat.filename_str(directory) os.makedirs(directory) code_to_debug = os.path.join(directory, compat.filename_str("experiment.py")) with open(code_to_debug, "wb") as stream: stream.write(b""" import debuggee from debuggee import backchannel debuggee.setup() backchannel.send('ok') """) with debug.Session() as session: backchannel = session.open_backchannel() with run(session, target(compat.filename_str(code_to_debug))): pass received = backchannel.receive() assert received == "ok"
def test_stop_on_entry(pyfile, run, target, breakpoint): @pyfile def code_to_debug(): from debuggee import backchannel # @bp backchannel.send("done") with debug.Session() as session: session.config["stopOnEntry"] = True backchannel = session.open_backchannel() with run(session, target(code_to_debug)): if breakpoint: session.set_breakpoints(code_to_debug, all) if breakpoint: stop = session.wait_for_stop( "breakpoint", expected_frames=[some.dap.frame(code_to_debug, "bp")]) session.request("next", {"threadId": stop.thread_id}) stop = session.wait_for_stop( "step", expected_frames=[some.dap.frame(code_to_debug, 3)]) else: session.wait_for_stop( "entry", expected_frames=[some.dap.frame(code_to_debug, 1)]) session.request_continue() assert backchannel.receive() == "done"
def test_run_submodule(run): with debug.Session() as session: session.config["cwd"] = test_data / "testpkgs" backchannel = session.open_backchannel() with run(session, targets.Module(name="pkg1.sub")): pass assert backchannel.receive() == "ok"
def code_to_debug(): import os import sys import debuggee from debuggee import backchannel debuggee.setup() print("begin") backchannel.send(os.path.abspath(sys.modules["debugpy"].__file__)) assert backchannel.receive() == "continue" print("end")
def code_to_debug(): import debuggee from debuggee import backchannel from _pydev_bundle.pydev_log import list_log_files debuggee.setup() from _pydevd_bundle import pydevd_constants # @ bp1 backchannel.send( list_log_files(pydevd_constants.DebugInfoHolder.PYDEVD_DEBUG_FILE) ) assert backchannel.receive() == "continue"
def test_autokill(daemon, pyfile, target, run): @pyfile def child(): import os from debuggee import backchannel backchannel.send(os.getpid()) while True: pass @pyfile def parent(): import debuggee import os import subprocess import sys debuggee.setup() argv = [sys.executable, sys.argv[1]] env = os.environ.copy() subprocess.Popen( argv, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ).wait() with debug.Session() as parent_session: parent_session.expected_exit_code = some.int backchannel = parent_session.open_backchannel() with run(parent_session, target(parent, args=[child])): pass child_config = parent_session.wait_for_next_event("debugpyAttach") parent_session.proceed() with debug.Session(child_config) as child_session: with child_session.start(): pass child_pid = backchannel.receive() assert child_config["subProcessId"] == child_pid child_process = psutil.Process(child_pid) parent_session.request("terminate") child_session.wait_for_exit() log.info("Waiting for child process...") child_process.wait()
def code_to_debug(): import debuggee import sys from debuggee import backchannel debuggee.setup() call_me_back_dir = backchannel.receive() sys.path.insert(0, call_me_back_dir) import call_me_back def call_func(): raise RuntimeError("unhandled error") # @raise call_me_back.call_me_back(call_func) # @call_me_back print("done")
def test_stdin_not_patched(pyfile, target, run): @pyfile def code_to_debug(): import sys import debuggee from debuggee import backchannel debuggee.setup() backchannel.send(sys.stdin == sys.__stdin__) with debug.Session() as session: backchannel = session.open_backchannel() with run(session, target(code_to_debug)): pass is_original_stdin = backchannel.receive() assert is_original_stdin, 'Expected sys.stdin and sys.__stdin__ to be the same.'
def test_set_variable(pyfile, target, run): @pyfile def code_to_debug(): import debuggee import debugpy from debuggee import backchannel debuggee.setup() a = 1 debugpy.breakpoint() backchannel.send(a) with debug.Session() as session: backchannel = session.open_backchannel() with run(session, target(code_to_debug)): pass stop = session.wait_for_stop() scopes = session.request("scopes", {"frameId": stop.frame_id})["scopes"] globals_ref = scopes[0]["variablesReference"] vars = session.request( "variables", {"variablesReference": globals_ref})["variables"] (a, ) = (v for v in vars if v["name"] == "a") assert a == some.dict.containing({ "type": "int", "value": "1", "name": "a", "evaluateName": "a", "variablesReference": 0, }) set_a = session.request( "setVariable", { "variablesReference": globals_ref, "name": "a", "value": "1000" }, ) assert set_a == some.dict.containing({"type": "int", "value": "1000"}) session.request_continue() assert backchannel.receive() == 1000
def code_to_debug(): import debuggee import os import sys from debuggee import backchannel debuggee.setup() backchannel.send(os.path.abspath(__file__)) call_me_back_dir = backchannel.receive() sys.path.insert(0, call_me_back_dir) import call_me_back def call_func(): print("break here") # @bp call_me_back.call_me_back(call_func) # @call_me_back print("done")
def test_args(pyfile, target, run): @pyfile def code_to_debug(): import sys import debuggee from debuggee import backchannel debuggee.setup() backchannel.send(sys.argv) args = ["--arg1", "arg2", "-arg3", "--", "arg4", "-a"] with debug.Session() as session: backchannel = session.open_backchannel() with run(session, target(code_to_debug, args=args)): pass argv = backchannel.receive() assert argv == [some.str] + args
def test_with_dot_remote_root(pyfile, long_tmpdir, target, run): @pyfile def code_to_debug(): import os import debuggee from debuggee import backchannel debuggee.setup() backchannel.send(os.path.abspath(__file__)) print("done") # @bp dir_local = long_tmpdir.mkdir("local") dir_remote = long_tmpdir.mkdir("remote") path_local = dir_local / "code_to_debug.py" path_remote = dir_remote / "code_to_debug.py" code_to_debug.copy(path_local) code_to_debug.copy(path_remote) with debug.Session() as session: session.config["pathMappings"] = [{ "localRoot": dir_local, "remoteRoot": "." }] backchannel = session.open_backchannel() with run(session, target(path_remote), cwd=dir_remote): # Set breakpoints using local path. This tests that local paths are # mapped to remote paths. session.set_breakpoints(path_local, all) actual_path_remote = backchannel.receive() assert some.path(actual_path_remote) == path_remote session.wait_for_stop( "breakpoint", expected_frames=[ some.dap.frame(some.dap.source(path_local), line="bp") ], ) session.request_continue()
def test_continue_on_disconnect_for_attach(pyfile, target, run): @pyfile def code_to_debug(): import debuggee from debuggee import backchannel debuggee.setup() backchannel.send("continued") # @bp with debug.Session() as session: backchannel = session.open_backchannel() 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")]) session.disconnect() assert "continued" == backchannel.receive()
def test_sudo(pyfile, tmpdir, run, target): # Since the test can't rely on sudo being allowed for the user, create a dummy # sudo script that doesn't actually elevate, but sets an environment variable # that can be checked in the debuggee. sudo = tmpdir / "sudo" sudo.write( """#!/bin/sh if [ "$1" = "-E" ]; then shift; fi exec env DEBUGPY_SUDO=1 "$@" """ ) os.chmod(sudo.strpath, 0o777) @pyfile def code_to_debug(): import os import debuggee from debuggee import backchannel debuggee.setup() backchannel.send(os.getenv("DEBUGPY_SUDO", "0")) with debug.Session() as session: session.config["sudo"] = True session.spawn_adapter.env["PATH"] = session.spawn_debuggee.env["PATH"] = ( tmpdir.strpath + ":" + os.environ["PATH"] ) backchannel = session.open_backchannel() with run(session, target(code_to_debug)): pass # The "runInTerminal" request sent by the adapter to spawn the launcher, # if any, shouldn't be using sudo. assert all( "sudo" not in req.arguments["args"] for req in session.all_occurrences_of(timeline.Request("runInTerminal")) ) # The launcher, however, should use our dummy sudo to spawn the debuggee, # and the debuggee should report the environment variable accordingly. assert backchannel.receive() == "1"
def test_run_relative_path(pyfile, run): @pyfile def code_to_debug(): import debuggee from debuggee import backchannel from _pydev_bundle.pydev_log import list_log_files debuggee.setup() from _pydevd_bundle import pydevd_constants # @ bp1 backchannel.send( list_log_files(pydevd_constants.DebugInfoHolder.PYDEVD_DEBUG_FILE) ) assert backchannel.receive() == "continue" with debug.Session() as session: backchannel = session.open_backchannel() code_to_debug = str(code_to_debug) cwd = os.path.dirname(os.path.dirname(code_to_debug)) program = targets.Program(code_to_debug) program.make_relative(cwd) with run(session, program): session.set_breakpoints(code_to_debug, all) session.wait_for_stop() session.request_continue() pydevd_debug_files = backchannel.receive() backchannel.send("continue") session.wait_for_next_event("terminated") session.proceed() # Check if we don't have errors in the pydevd log (the # particular error this test is covering: # https://github.com/microsoft/debugpy/issues/620 # is handled by pydevd but produces a Traceback in the logs). for pydevd_debug_file in pydevd_debug_files: with open(pydevd_debug_file, "r") as stream: contents = stream.read() assert "critical" not in contents assert "Traceback" not in contents
def test_autokill_nodebug(daemon, pyfile, target, run): @pyfile def child(): import os from debuggee import backchannel backchannel.send(os.getpid()) while True: pass @pyfile def parent(): import os import subprocess import sys argv = [sys.executable, sys.argv[1]] env = os.environ.copy() subprocess.Popen( argv, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ).wait() with debug.Session() as session: session.expected_exit_code = some.int session.config["noDebug"] = True backchannel = session.open_backchannel() run(session, target(parent, args=[child])) child_pid = backchannel.receive() child_process = psutil.Process(child_pid) session.request("terminate") log.info("Waiting for child process...") child_process.wait()
def test_frame_eval(pyfile, target, run, frame_eval): # Frame-eval optimizations are not available for some Python implementations, # but pydevd will still try to use them if the environment variable is set to # "yes" explicitly, so the test must detect and skip those cases. if frame_eval == "yes": try: import _pydevd_frame_eval.pydevd_frame_eval_cython_wrapper # noqa except ImportError: pytest.skip("Frame-eval not available") else: pass @pyfile def code_to_debug(): import debuggee from debuggee import backchannel debuggee.setup() from _pydevd_frame_eval.pydevd_frame_eval_main import USING_FRAME_EVAL backchannel.send(USING_FRAME_EVAL) with debug.Session() as session: assert "PYDEVD_USE_FRAME_EVAL" not in os.environ if len(frame_eval): env = ( session.config.env if run.request == "launch" else session.spawn_debuggee.env ) env["PYDEVD_USE_FRAME_EVAL"] = frame_eval backchannel = session.open_backchannel() with run(session, target(code_to_debug)): pass using_frame_eval = backchannel.receive() assert using_frame_eval == (frame_eval == "yes")
def test_custom_python(pyfile, run, target, python_key, python, python_args): @pyfile def code_to_debug(): import sys import debuggee from debuggee import backchannel debuggee.setup() backchannel.send( [sys.executable, sys.flags.optimize, sys.flags.verbose]) python = python.split("|") python_args = python_args.split() python_cmd = (python if len(python) else [sys.executable]) + python_args class Session(debug.Session): def run_in_terminal(self, args, cwd, env): assert args[:len(python_cmd)] == python_cmd args[0] = sys.executable return super(Session, self).run_in_terminal(args, cwd, env) with Session() as session: session.config.pop("python", None) session.config.pop("pythonPath", None) if len(python): session.config[python_key] = python[0] if len( python) == 1 else python if len(python_args): session.config["pythonArgs"] = python_args backchannel = session.open_backchannel() with run(session, target(code_to_debug)): pass assert backchannel.receive() == [ sys.executable, "-O" in python_cmd, "-v" in python_cmd, ]
def test_custom_python( pyfile, tmpdir, run, target, debuggee_custompy, launcher_custompy ): @pyfile def code_to_debug(): import os import debuggee from debuggee import backchannel debuggee.setup() backchannel.send(os.getenv("DEBUGPY_CUSTOM_PYTHON")) expected = "" if debuggee_custompy: debuggee_custompy = make_custompy(tmpdir, "debuggee") expected += "debuggee;" if launcher_custompy: launcher_custompy = make_custompy(tmpdir, "launcher") expected += "launcher;" else: # If "python" is set, it also becomes the default for "debugLauncherPython" expected *= 2 if not len(expected): pytest.skip() with debug.Session() as session: session.config.pop("python", None) if launcher_custompy: session.config["debugLauncherPython"] = launcher_custompy if debuggee_custompy: session.config["python"] = debuggee_custompy backchannel = session.open_backchannel() with run(session, target(code_to_debug)): pass assert backchannel.receive() == expected
def test_env_replace_var(pyfile, target, run, case): @pyfile def code_to_debug(): import os import debuggee from debuggee import backchannel debuggee.setup() backchannel.send(dict(os.environ)) varname = "DEBUGPY_DUMMY_ENV_VAR" with debug.Session() as session: backchannel = session.open_backchannel() session.config.env[varname if case == "match_case" else varname.lower()] = "42" os.environ[varname] = "1" with run(session, target(code_to_debug)): pass del os.environ[varname] env = backchannel.receive() if case == "match_case": # If case matches, debug config should replace global env var regardless # of the platform. assert env[varname] == "42" elif sys.platform == "win32": # On Win32, variable names are case-insensitive, so debug config should # replace the global env var even if there is a case mismatch. assert env[varname] == "42" assert varname.lower() not in env else: # On other platforms, variable names are case-sensitive, so case mismatch # should result in two different variables. assert env[varname] == "1" assert env[varname.lower()] == "42"
def test_custom_python(pyfile, run, target): @pyfile def code_to_debug(): import sys import debuggee from debuggee import backchannel debuggee.setup() backchannel.send(sys.executable) class Session(debug.Session): def run_in_terminal(self, args, cwd, env): assert args[:2] == ["CUSTOMPY", "-O"] args[0] = sys.executable return super(Session, self).run_in_terminal(args, cwd, env) with Session() as session: session.config["pythonPath"] = ["CUSTOMPY", "-O"] backchannel = session.open_backchannel() with run(session, target(code_to_debug)): pass assert backchannel.receive() == sys.executable
def test_with_path_mappings(pyfile, long_tmpdir, target, run): @pyfile def code_to_debug(): import debuggee import os import sys from debuggee import backchannel debuggee.setup() backchannel.send(os.path.abspath(__file__)) call_me_back_dir = backchannel.receive() sys.path.insert(0, call_me_back_dir) import call_me_back def call_func(): print("break here") # @bp call_me_back.call_me_back(call_func) # @call_me_back print("done") dir_local = long_tmpdir.mkdir("local") dir_remote = long_tmpdir.mkdir("remote") path_local = dir_local / "code_to_debug.py" path_remote = dir_remote / "code_to_debug.py" code_to_debug.copy(path_local) code_to_debug.copy(path_remote) call_me_back_dir = test_data / "call_me_back" call_me_back_py = call_me_back_dir / "call_me_back.py" with debug.Session() as session: session.config["pathMappings"] = [{ "localRoot": dir_local, "remoteRoot": dir_remote }] backchannel = session.open_backchannel() with run(session, target(path_remote)): # Set breakpoints using local path. This tests that local paths are # mapped to remote paths. session.set_breakpoints(path_local, ["bp"]) actual_path_remote = backchannel.receive() assert some.path(actual_path_remote) == path_remote backchannel.send(call_me_back_dir) stop = session.wait_for_stop( "breakpoint", expected_frames=[ some.dap.frame( # Mapped files should not have a sourceReference, so that the IDE # doesn't try to fetch them instead of opening the local file. some.dap.source(path_local, sourceReference=0), line="bp", ), some.dap.frame( # Unmapped files should have a sourceReference, since there's no # local file for the IDE to open. some.dap.source(call_me_back_py, sourceReference=some.int.not_equal_to(0)), line="callback", ), some.dap.frame( # Mapped files should not have a sourceReference, so that the IDE # doesn't try to fetch them instead of opening the local file. some.dap.source(path_local, sourceReference=0), line="call_me_back", ), ], ) srcref = stop.frames[1]["source"]["sourceReference"] try: session.request("source", {"sourceReference": 0}) except Exception as exc: assert "Source unavailable" in str(exc) else: pytest.fail("sourceReference=0 should not be valid") source = session.request("source", {"sourceReference": srcref}) assert "def call_me_back(callback):" in source["content"] session.request_continue()