diff --git a/src/debugpy/_vendored/force_pydevd.py b/src/debugpy/_vendored/force_pydevd.py index c0b058f1..f4dec254 100644 --- a/src/debugpy/_vendored/force_pydevd.py +++ b/src/debugpy/_vendored/force_pydevd.py @@ -25,6 +25,11 @@ if "DEBUGPY_LOG_DIR" in os.environ: os.environ["PYDEVD_DEBUG"] = str("True") os.environ["PYDEVD_DEBUG_FILE"] = os.environ["DEBUGPY_LOG_DIR"] + str("/debugpy.pydevd.log") +# Work around https://github.com/microsoft/debugpy/issues/346. +# Disable pydevd frame-eval optimizations only if unset, to allow opt-in. +if "PYDEVD_USE_FRAME_EVAL" not in os.environ: + os.environ["PYDEVD_USE_FRAME_EVAL"] = str("NO") + # Constants must be set before importing any other pydevd module # # due to heavy use of "from" in them. diff --git a/tests/__init__.py b/tests/__init__.py index d22af475..878fded4 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -56,6 +56,17 @@ for _, submodule, _ in tests_submodules: from debugpy.common import json, log import debugpy.server # noqa +# Clean up environment variables that were automatically set when importing pydevd - +# we don't need them in the test runner process (since pydevd is not tracing it), +# and some tests must be able to spawn debuggee with them unset. +for name in ( + "DEBUGPY_LOG_DIR", + "PYDEVD_DEBUG", + "PYDEVD_DEBUG_FILE", + "PYDEVD_USE_FRAME_EVAL", +): + os.environ.pop(name, None) + # Enable full logging to stderr, and make timestamps shorter to match maximum test # run time better. log.stderr.levels = all @@ -64,8 +75,6 @@ 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 diff --git a/tests/debugpy/test_run.py b/tests/debugpy/test_run.py index f8bb9f78..d7a0c6b1 100644 --- a/tests/debugpy/test_run.py +++ b/tests/debugpy/test_run.py @@ -178,3 +178,47 @@ def test_custom_python(pyfile, run, target, python_key, python, python_args): "-O" in python_cmd, "-v" in python_cmd, ] + + +@pytest.mark.parametrize("run", runners.all) +@pytest.mark.parametrize("target", targets.all) +@pytest.mark.parametrize("frame_eval", ["", "yes", "no"]) +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")