示例#1
0
    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)
示例#2
0
    def code_to_debug():
        import debuggee
        from debuggee import backchannel

        debuggee.setup()
        backchannel.receive()  # @ bp1
        print("ok")  # @ bp2
示例#3
0
    def child():
        import os
        from debuggee import backchannel

        backchannel.send(os.getpid())
        backchannel.receive()
        backchannel.send("ok")
示例#4
0
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()
示例#5
0
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
示例#6
0
        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()
示例#7
0
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
        ]
示例#8
0
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()
示例#9
0
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"
示例#10
0
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"
示例#11
0
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"
示例#12
0
    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")
示例#13
0
    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"
示例#14
0
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()
示例#15
0
    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")
示例#16
0
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.'
示例#17
0
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
示例#18
0
    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")
示例#19
0
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
示例#20
0
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()
示例#21
0
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()
示例#22
0
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"
示例#23
0
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
示例#24
0
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()
示例#25
0
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")
示例#26
0
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,
        ]
示例#27
0
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
示例#28
0
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"
示例#29
0
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
示例#30
0
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()