Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-debugpy for openSUSE:Factory checked in at 2026-03-14 22:20:40 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-debugpy (Old) and /work/SRC/openSUSE:Factory/.python-debugpy.new.8177 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-debugpy" Sat Mar 14 22:20:40 2026 rev:25 rq:1338675 version:1.8.20 Changes: -------- --- /work/SRC/openSUSE:Factory/python-debugpy/python-debugpy.changes 2025-12-20 21:46:15.906627656 +0100 +++ /work/SRC/openSUSE:Factory/.python-debugpy.new.8177/python-debugpy.changes 2026-03-14 22:21:07.005954712 +0100 @@ -1,0 +2,12 @@ +Wed Mar 4 22:04:15 UTC 2026 - Dirk Müller <[email protected]> + +- update to 1.8.20: + * annotate in 3.14 causing exceptions: + * Use remote_exec if available + * Support more architectures +- update to 1.8.19: + * More fixes for + https://github.com/microsoft/debugpy/issues/1980. Spaces in + python files are handled now too. + +------------------------------------------------------------------- Old: ---- debugpy-1.8.18.tar.gz New: ---- debugpy-1.8.20.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-debugpy.spec ++++++ --- /var/tmp/diff_new_pack.tD5zQl/_old 2026-03-14 22:21:07.613979882 +0100 +++ /var/tmp/diff_new_pack.tD5zQl/_new 2026-03-14 22:21:07.617980047 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-debugpy # -# Copyright (c) 2025 SUSE LLC and contributors +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -27,7 +27,7 @@ %endif %{?sle15_python_module_pythons} Name: python-debugpy%{psuffix} -Version: 1.8.18 +Version: 1.8.20 Release: 0 Summary: An implementation of the Debug Adapter Protocol for Python License: MIT ++++++ debugpy-1.8.18.tar.gz -> debugpy-1.8.20.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.18/.devcontainer/Dockerfile new/debugpy-1.8.20/.devcontainer/Dockerfile --- old/debugpy-1.8.18/.devcontainer/Dockerfile 1970-01-01 01:00:00.000000000 +0100 +++ new/debugpy-1.8.20/.devcontainer/Dockerfile 2026-01-28 01:58:21.000000000 +0100 @@ -0,0 +1,11 @@ +FROM mcr.microsoft.com/devcontainers/python:3.14 + +# Install GDB +RUN apt-get update && \ + apt-get install -y --no-install-recommends gdb \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Switch to non-root user and install Python packages +USER vscode +RUN python -m pip install black flake8 tox \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.18/.devcontainer/devcontainer.json new/debugpy-1.8.20/.devcontainer/devcontainer.json --- old/debugpy-1.8.18/.devcontainer/devcontainer.json 1970-01-01 01:00:00.000000000 +0100 +++ new/debugpy-1.8.20/.devcontainer/devcontainer.json 2026-01-28 01:58:21.000000000 +0100 @@ -0,0 +1,12 @@ +{ + "name": "debugpy Development", + "build": { + "dockerfile": "Dockerfile" + }, + + "features": { + "ghcr.io/devcontainers/features/git:1": {} + }, + + "remoteUser": "vscode" +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.18/.gdn/.gdnsuppress new/debugpy-1.8.20/.gdn/.gdnsuppress --- old/debugpy-1.8.18/.gdn/.gdnsuppress 1970-01-01 01:00:00.000000000 +0100 +++ new/debugpy-1.8.20/.gdn/.gdnsuppress 2026-01-28 01:58:21.000000000 +0100 @@ -0,0 +1,42 @@ +{ + "hydrated": true, + "properties": { + "helpUri": "https://eng.ms/docs/microsoft-security/security/azure-security/cloudai-security-fundamentals-engineering/security-integration/guardian-wiki/microsoft-guardian/general/suppressions" + }, + "version": "1.0.0", + "suppressionSets": { + "default": { + "name": "default", + "createdDate": "2026-01-28 00:33:53Z", + "lastUpdatedDate": "2026-01-28 00:33:53Z" + } + }, + "results": { + "58a87fd4d7371ece41c06d9d8ef960a90be375cf25621fdddc0f69ed70a8b6f4": { + "signature": "58a87fd4d7371ece41c06d9d8ef960a90be375cf25621fdddc0f69ed70a8b6f4", + "alternativeSignatures": [ + "86f6ccbed316821fbcca7e2f0daafcc164b6df645a491d850e1ac16b39eb747e" + ], + "target": "pydevd/inject_dll_amd64.exe", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-01-28 00:33:53Z" + }, + "2b82071ec3bb1a8e80b1615d79febb69084008a4b5a19e66dbeac590b39d4c55": { + "signature": "2b82071ec3bb1a8e80b1615d79febb69084008a4b5a19e66dbeac590b39d4c55", + "alternativeSignatures": [ + "3e5a1d789a42983ae1541ba91e20f10f388886a5ba62833ec66e0a358adc1174" + ], + "target": "pydevd/inject_dll_x86.exe", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2007", + "createdDate": "2026-01-28 00:33:53Z" + } + } +} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.18/build_attach_binaries.py new/debugpy-1.8.20/build_attach_binaries.py --- old/debugpy-1.8.18/build_attach_binaries.py 2025-12-10 19:39:27.000000000 +0100 +++ new/debugpy-1.8.20/build_attach_binaries.py 2026-01-28 01:58:21.000000000 +0100 @@ -2,6 +2,7 @@ import argparse import os import platform +import re def build_pydevd_binaries(force: bool): os.environ["PYDEVD_USE_CYTHON"] = "yes" @@ -25,7 +26,12 @@ if not os.path.exists(os.path.join(pydevd_attach_to_process_root, "attach_amd64.dll")) or force: os.system(os.path.join(pydevd_attach_to_process_root, "windows", "compile_windows.bat")) elif platform.system() == "Linux": - if not os.path.exists(os.path.join(pydevd_attach_to_process_root, "attach_linux_amd64.so")) or force: + arch = platform.machine() + if re.match(r'^i.*86$', arch): + arch = 'x86' + if arch == "x86_64": + arch = "amd64" + if not os.path.exists(os.path.join(pydevd_attach_to_process_root, f"attach_linux_{arch}.so")) or force: os.system(os.path.join(pydevd_attach_to_process_root, "linux_and_mac", "compile_linux.sh")) elif platform.system() == "Darwin": if not os.path.exists(os.path.join(pydevd_attach_to_process_root, "attach.dylib")) or force: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.18/src/debugpy/_vendored/pydevd/pydevd.py new/debugpy-1.8.20/src/debugpy/_vendored/pydevd/pydevd.py --- old/debugpy-1.8.18/src/debugpy/_vendored/pydevd/pydevd.py 2025-12-10 19:39:27.000000000 +0100 +++ new/debugpy-1.8.20/src/debugpy/_vendored/pydevd/pydevd.py 2026-01-28 01:58:21.000000000 +0100 @@ -93,6 +93,7 @@ PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING, PYDEVD_IPYTHON_CONTEXT, PYDEVD_USE_SYS_MONITORING, + IS_PY314_OR_GREATER, ) from _pydevd_bundle.pydevd_defaults import PydevdCustomization # Note: import alias used on pydev_monkey. from _pydevd_bundle.pydevd_custom_frames import CustomFramesContainer, custom_frames_container_init @@ -1292,6 +1293,16 @@ if file_type == self.PYDEV_FILE: cache[cache_key] = False + elif IS_PY314_OR_GREATER and frame.f_code.co_name == "__annotate__": + # Special handling for __annotate__ functions (PEP 649 in Python 3.14+). + # These are compiler-generated functions that can raise NotImplementedError + # when called with unsupported format arguments by inspect.call_annotate_function. + # They should be treated as library code to avoid false positives in exception handling. + # Note: PEP 649 reserves the __annotate__ name for compiler-generated functions, + # so user-defined functions with this name are discouraged and will also be treated + # as library code to maintain consistency with the language design. + cache[cache_key] = False + elif absolute_filename == "<string>": # Special handling for '<string>' if file_type == self.LIB_FILE: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.18/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/linux_and_mac/compile_linux.sh new/debugpy-1.8.20/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/linux_and_mac/compile_linux.sh --- old/debugpy-1.8.18/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/linux_and_mac/compile_linux.sh 2025-12-10 19:39:27.000000000 +0100 +++ new/debugpy-1.8.20/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/linux_and_mac/compile_linux.sh 2026-01-28 01:58:21.000000000 +0100 @@ -4,7 +4,7 @@ case $ARCH in i*86) SUFFIX=x86;; x86_64*) SUFFIX=amd64;; - *) echo >&2 "unsupported: $ARCH"; exit 1;; + *) echo >&2 "unsupported: $ARCH, this script may not work";; esac SRC="$(dirname "$0")/.." diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.18/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/windows/compile_windows.bat new/debugpy-1.8.20/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/windows/compile_windows.bat --- old/debugpy-1.8.18/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/windows/compile_windows.bat 2025-12-10 19:39:27.000000000 +0100 +++ new/debugpy-1.8.20/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/windows/compile_windows.bat 2026-01-28 01:58:21.000000000 +0100 @@ -10,29 +10,29 @@ call "%VSDIR%\VC\Auxiliary\Build\vcvarsall.bat" x86 -vcvars_spectre_libs=spectre -cl -DUNICODE -D_UNICODE /EHsc /Zi /O1 /W3 /LD /MD /Qspectre attach.cpp /link /PROFILE /GUARD:CF /CETCOMPAT /out:attach_x86.dll +cl -DUNICODE -D_UNICODE /EHsc /Zi /O1 /W3 /LD /MD /GL /Qspectre attach.cpp /link /LTCG /PROFILE /GUARD:CF /CETCOMPAT /out:attach_x86.dll copy attach_x86.dll ..\attach_x86.dll /Y copy attach_x86.pdb ..\attach_x86.pdb /Y -cl -DUNICODE -D_UNICODE /EHsc /Zi /O1 /W3 /LD /MD /D BITS_32 /Qspectre run_code_on_dllmain.cpp /link /PROFILE /GUARD:CF /CETCOMPAT /out:run_code_on_dllmain_x86.dll +cl -DUNICODE -D_UNICODE /EHsc /Zi /O1 /W3 /LD /MD /GL /D BITS_32 /Qspectre run_code_on_dllmain.cpp /link /LTCG /PROFILE /GUARD:CF /CETCOMPAT /out:run_code_on_dllmain_x86.dll copy run_code_on_dllmain_x86.dll ..\run_code_on_dllmain_x86.dll /Y copy run_code_on_dllmain_x86.pdb ..\run_code_on_dllmain_x86.pdb /Y -cl /EHsc /Zi /O1 /W3 /Qspectre inject_dll.cpp /link /PROFILE /GUARD:CF /CETCOMPAT /out:inject_dll_x86.exe +cl /EHsc /Zi /O1 /W3 /GL /Qspectre inject_dll.cpp /link /LTCG /PROFILE /GUARD:CF /CETCOMPAT /out:inject_dll_x86.exe copy inject_dll_x86.exe ..\inject_dll_x86.exe /Y copy inject_dll_x86.pdb ..\inject_dll_x86.pdb /Y call "%VSDIR%\VC\Auxiliary\Build\vcvarsall.bat" x86_amd64 -vcvars_spectre_libs=spectre -cl -DUNICODE -D_UNICODE /EHsc /Zi /O1 /W3 /LD /MD /Qspectre attach.cpp /link /PROFILE /GUARD:CF /CETCOMPAT /out:attach_amd64.dll +cl -DUNICODE -D_UNICODE /EHsc /Zi /O1 /W3 /LD /MD /GL /Qspectre attach.cpp /link /LTCG /PROFILE /GUARD:CF /CETCOMPAT /out:attach_amd64.dll copy attach_amd64.dll ..\attach_amd64.dll /Y copy attach_amd64.pdb ..\attach_amd64.pdb /Y -cl -DUNICODE -D_UNICODE /EHsc /Zi /O1 /W3 /LD /MD /D BITS_64 /Qspectre run_code_on_dllmain.cpp /link /PROFILE /GUARD:CF /CETCOMPAT /out:run_code_on_dllmain_amd64.dll +cl -DUNICODE -D_UNICODE /EHsc /Zi /O1 /W3 /LD /MD /GL /D BITS_64 /Qspectre run_code_on_dllmain.cpp /link /LTCG /PROFILE /GUARD:CF /CETCOMPAT /out:run_code_on_dllmain_amd64.dll copy run_code_on_dllmain_amd64.dll ..\run_code_on_dllmain_amd64.dll /Y copy run_code_on_dllmain_amd64.pdb ..\run_code_on_dllmain_amd64.pdb /Y -cl /EHsc /Zi /O1 /W3 /Qspectre inject_dll.cpp /link /PROFILE /GUARD:CF /CETCOMPAT /out:inject_dll_amd64.exe +cl /EHsc /Zi /O1 /W3 /GL /Qspectre inject_dll.cpp /link /LTCG /PROFILE /GUARD:CF /CETCOMPAT /out:inject_dll_amd64.exe copy inject_dll_amd64.exe ..\inject_dll_amd64.exe /Y copy inject_dll_amd64.pdb ..\inject_dll_amd64.pdb /Y diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.18/src/debugpy/_version.py new/debugpy-1.8.20/src/debugpy/_version.py --- old/debugpy-1.8.18/src/debugpy/_version.py 2025-12-10 19:39:27.000000000 +0100 +++ new/debugpy-1.8.20/src/debugpy/_version.py 2026-01-28 01:58:21.000000000 +0100 @@ -25,9 +25,9 @@ # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call # get_keywords(). - git_refnames = " (HEAD -> main, tag: v1.8.18)" - git_full = "e5017d736052d8d84484cdfe05750bd61cc7c50f" - git_date = "2025-12-10 10:39:27 -0800" + git_refnames = " (tag: v1.8.20)" + git_full = "7ac3d1f2c65ccf6b2e62a762863093f0242179f0" + git_date = "2026-01-27 16:58:21 -0800" keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} return keywords diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.18/src/debugpy/adapter/launchers.py new/debugpy-1.8.20/src/debugpy/adapter/launchers.py --- old/debugpy-1.8.18/src/debugpy/adapter/launchers.py 2025-12-10 19:39:27.000000000 +0100 +++ new/debugpy-1.8.20/src/debugpy/adapter/launchers.py 2026-01-28 01:58:21.000000000 +0100 @@ -160,15 +160,16 @@ quote_char = arguments["terminalQuoteCharacter"] if "terminalQuoteCharacter" in arguments else default_quote # VS code doesn't quote arguments if `argsCanBeInterpretedByShell` is true, - # so we need to do it ourselves for the arguments up to the call to the adapter. + # so we need to do it ourselves for the arguments up to the first argument passed to + # debugpy (this should be the python file to run). args = request_args["args"] for i in range(len(args)): - if args[i] == "--": - break s = args[i] if " " in s and not ((s.startswith('"') and s.endswith('"')) or (s.startswith("'") and s.endswith("'"))): s = f"{quote_char}{s}{quote_char}" args[i] = s + if i > 0 and args[i-1] == "--": + break try: # It is unspecified whether this request receives a response immediately, or only diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.18/src/debugpy/server/cli.py new/debugpy-1.8.20/src/debugpy/server/cli.py --- old/debugpy-1.8.18/src/debugpy/server/cli.py 2025-12-10 19:39:27.000000000 +0100 +++ new/debugpy-1.8.20/src/debugpy/server/cli.py 2026-01-28 01:58:21.000000000 +0100 @@ -37,6 +37,7 @@ [--log-to <path>] [--log-to-stderr] [--parent-session-pid <pid>]] [--adapter-access-token <token>] + [--disable-sys-remote-exec]] {1} [<arg>]... """.format( @@ -56,6 +57,7 @@ adapter_access_token = None config: Dict[str, Any] = {} parent_session_pid: Union[int, None] = None + disable_sys_remote_exec = False options = Options() @@ -186,6 +188,7 @@ ("--configure-.+", "<value>", set_config), ("--parent-session-pid", "<pid>", set_arg("parent_session_pid", lambda x: int(x) if x else None)), ("--adapter-access-token", "<token>", set_arg("adapter_access_token")), + ("--disable-sys-remote-exec", None, set_const("disable_sys_remote_exec", True)), # Targets. The "" entry corresponds to positional command line arguments, # i.e. the ones not preceded by any switch name. @@ -447,6 +450,37 @@ .replace("\n", "") .format(script_dir=script_dir, setup=setup) ) + + # attempt pep 768 style code injection + if (not options.disable_sys_remote_exec) and hasattr(sys, "remote_exec"): + tmp_file_path = "" + try: + import tempfile + + with tempfile.NamedTemporaryFile(delete=False) as tmp_file: + tmp_file_path = tmp_file.name + log.info( + "Attempting to inject code at '{tmp_file_path}' using sys.remote_exec()", + tmp_file_path=tmp_file_path, + ) + tmp_file.write(python_code.encode()) + tmp_file.write( + """import os;os.remove("{tmp_file_path}");""".format( + tmp_file_path=tmp_file_path + ).encode() + ) + tmp_file.flush() + tmp_file.close() + sys.remote_exec(pid, tmp_file_path) + return + except Exception as e: + if os.path.exists(tmp_file_path): + os.remove(tmp_file_path) + log.warning( + 'Injecting code using sys.remote_exec() failed with error:\n"{e}"\nWill reattempt using pydevd.\n', + e=e, + ) + log.info("Code to be injected: \n{0}", python_code.replace(";", ";\n")) # pydevd restriction on characters in injected code. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.18/tests/debug/session.py new/debugpy-1.8.20/tests/debug/session.py --- old/debugpy-1.8.18/tests/debug/session.py 2025-12-10 19:39:27.000000000 +0100 +++ new/debugpy-1.8.20/tests/debug/session.py 2026-01-28 01:58:21.000000000 +0100 @@ -594,25 +594,78 @@ def run_in_terminal(self, args, cwd, env): exe = args.pop(0) + if getattr(self, "_run_in_terminal_args_can_be_interpreted_by_shell", False): + exe = self._shell_unquote(exe) + args = [self._shell_unquote(a) for a in args] self.spawn_debuggee.env.update(env) self.spawn_debuggee(args, cwd, exe=exe) return {} + @staticmethod + def _shell_unquote(s): + s = str(s) + if len(s) >= 2 and s[0] == s[-1] and s[0] in ("\"", "'"): + return s[1:-1] + return s + + @classmethod + def _split_shell_arg_string(cls, s): + """Split a shell argument string into args, honoring simple single/double quotes. + + This is intentionally minimal: it matches how terminals remove surrounding quotes + before passing args to the spawned process, which our tests need to emulate. + """ + s = str(s) + args = [] + current = [] + quote = None + + def flush(): + if current: + args.append("".join(current)) + current.clear() + + for ch in s: + if quote is None: + if ch.isspace(): + flush() + continue + if ch in ("\"", "'"): + quote = ch + continue + current.append(ch) + else: + if ch == quote: + quote = None + continue + current.append(ch) + flush() + + return [cls._shell_unquote(a) for a in args] + def _process_request(self, request): self.timeline.record_request(request, block=False) if request.command == "runInTerminal": args = request("args", json.array(str, vectorize=True)) - if len(args) > 0 and request("argsCanBeInterpretedByShell", False): + args_can_be_interpreted_by_shell = request("argsCanBeInterpretedByShell", False) + if len(args) > 0 and args_can_be_interpreted_by_shell: # The final arg is a string that contains multiple actual arguments. + # Split it like a shell would, but keep the rest of the args (including + # any quoting) intact so tests can inspect the raw runInTerminal argv. last_arg = args.pop() - args += last_arg.split() + args += self._split_shell_arg_string(last_arg) cwd = request("cwd", ".") env = request("env", json.object(str)) try: + self._run_in_terminal_args_can_be_interpreted_by_shell = ( + args_can_be_interpreted_by_shell + ) return self.run_in_terminal(args, cwd, env) except Exception as exc: log.swallow_exception('"runInTerminal" failed:') raise request.cant_handle(str(exc)) + finally: + self._run_in_terminal_args_can_be_interpreted_by_shell = False elif request.command == "startDebugging": pid = request("configuration", dict)("subProcessId", int) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.18/tests/debugpy/test_args.py new/debugpy-1.8.20/tests/debugpy/test_args.py --- old/debugpy-1.8.18/tests/debugpy/test_args.py 2025-12-10 19:39:27.000000000 +0100 +++ new/debugpy-1.8.20/tests/debugpy/test_args.py 2026-01-28 01:58:21.000000000 +0100 @@ -113,3 +113,91 @@ f"Expected 'python with space' in python path: {python_arg}" if expansion == "expand": assert (python_arg.startswith('"') or python_arg.startswith("'")), f"Python_arg is not quoted: {python_arg}" + + [email protected]("run", runners.all_launch_terminal) [email protected]("expansion", ["preserve", "expand"]) +def test_debuggee_filename_with_space(tmpdir, run, expansion): + """Test that a debuggee filename with a space gets properly quoted in runInTerminal.""" + + # Create a script file with a space in both directory and filename + + # Create a Python script with a space in the filename + script_dir = tmpdir / "test dir" + script_dir.mkdir() + script_file = script_dir / "script with space.py" + + script_content = """import sys +import debuggee +from debuggee import backchannel + +debuggee.setup() +backchannel.send(sys.argv) + +import time +time.sleep(2) +""" + script_file.write(script_content) + + captured_run_in_terminal_request = [] + captured_run_in_terminal_args = [] + + class Session(debug.Session): + def _process_request(self, request): + if request.command == "runInTerminal": + # Capture the raw runInTerminal request before any processing + args_from_request = list(request.arguments.get("args", [])) + captured_run_in_terminal_request.append({ + "args": args_from_request, + "argsCanBeInterpretedByShell": request.arguments.get("argsCanBeInterpretedByShell", False) + }) + return super()._process_request(request) + + def run_in_terminal(self, args, cwd, env): + # Capture the processed args after the framework has handled them + captured_run_in_terminal_args.append(args[:]) + return super().run_in_terminal(args, cwd, env) + + argslist = ["arg1", "arg2"] + args = argslist if expansion == "preserve" else " ".join(argslist) + + with Session() as session: + backchannel = session.open_backchannel() + target = targets.Program(script_file, args=args) + with run(session, target): + pass + + argv = backchannel.receive() + + assert argv == [some.str] + argslist + + # Verify that runInTerminal was called + assert captured_run_in_terminal_request, "Expected runInTerminal request to be sent" + request_data = captured_run_in_terminal_request[0] + terminal_request_args = request_data["args"] + args_can_be_interpreted_by_shell = request_data["argsCanBeInterpretedByShell"] + + log.info("Captured runInTerminal request args: {0}", terminal_request_args) + log.info("argsCanBeInterpretedByShell: {0}", args_can_be_interpreted_by_shell) + + # With expansion="expand", argsCanBeInterpretedByShell should be True + if expansion == "expand": + assert args_can_be_interpreted_by_shell, \ + "Expected argsCanBeInterpretedByShell=True for expansion='expand'" + + # Find the script path in the arguments (it should be after the debugpy launcher args) + script_path_found = False + for arg in terminal_request_args: + if "script with space.py" in arg: + script_path_found = True + log.info("Found script path argument: {0}", arg) + + # NOTE: With shell expansion enabled, we currently have a limitation: + # The test framework splits the last arg by spaces when argsCanBeInterpretedByShell=True, + # which makes it incompatible with quoting individual args. This causes issues with + # paths containing spaces. This is a known limitation that needs investigation. + # For now, just verify the script path is found. + break + + assert script_path_found, \ + f"Expected to find 'script with space.py' in runInTerminal args: {terminal_request_args}" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.18/tests/debugpy/test_exception.py new/debugpy-1.8.20/tests/debugpy/test_exception.py --- old/debugpy-1.8.18/tests/debugpy/test_exception.py 2025-12-10 19:39:27.000000000 +0100 +++ new/debugpy-1.8.20/tests/debugpy/test_exception.py 2026-01-28 01:58:21.000000000 +0100 @@ -5,7 +5,7 @@ import pytest import sys -from _pydevd_bundle.pydevd_constants import IS_PY312_OR_GREATER +from _pydevd_bundle.pydevd_constants import IS_PY312_OR_GREATER, IS_PY314_OR_GREATER from tests import debug from tests.debug import runners, targets from tests.patterns import some @@ -390,3 +390,54 @@ assert min_expected_lines <= stack_line_count <= max_expected_lines session.request_continue() + + [email protected](not IS_PY314_OR_GREATER, reason="Test requires Python 3.14+") +def test_annotate_function_not_treated_as_user_exception(pyfile, target, run): + """ + Test that __annotate__ functions (PEP 649) are treated as library code. + In Python 3.14+, compiler-generated __annotate__ functions can raise + NotImplementedError when called by inspect.call_annotate_function with + unsupported format arguments. These should not be reported as user exceptions. + """ + @pyfile + def code_to_debug(): + import debuggee + from typing import get_type_hints + + debuggee.setup() + + # Define a class with annotations that will trigger __annotate__ function generation + class AnnotatedClass: + value: int = 42 + name: str = "test" + + # This will trigger the __annotate__ function to be called by the runtime + # which may raise NotImplementedError internally (expected behavior) + try: + hints = get_type_hints(AnnotatedClass) + print(f"Type hints: {hints}") # @bp + except Exception as e: + print(f"Exception: {e}") + + with debug.Session() as session: + session.config["justMyCode"] = True + + with run(session, target(code_to_debug)): + # Set exception breakpoints for user uncaught exceptions + session.request( + "setExceptionBreakpoints", + {"filters": ["userUnhandled"]} + ) + session.set_breakpoints(code_to_debug, all) + + # Wait for the breakpoint + session.wait_for_stop( + "breakpoint", + expected_frames=[some.dap.frame(code_to_debug, "bp")] + ) + + # The test passes if we reach here without stopping on a NotImplementedError + # from __annotate__ function + session.request_continue() + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.18/tests/debugpy/test_threads.py new/debugpy-1.8.20/tests/debugpy/test_threads.py --- old/debugpy-1.8.18/tests/debugpy/test_threads.py 2025-12-10 19:39:27.000000000 +0100 +++ new/debugpy-1.8.20/tests/debugpy/test_threads.py 2026-01-28 01:58:21.000000000 +0100 @@ -102,7 +102,7 @@ stop = session.wait_for_stop() threads = session.request("threads") - assert len(threads["threads"]) == 3 + assert len(threads["threads"]) >= 3 thread_name_to_id = {t["name"]: t["id"] for t in threads["threads"]} assert stop.thread_id == thread_name_to_id["thread1"]
