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 2025-12-20 21:45:32 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-debugpy (Old) and /work/SRC/openSUSE:Factory/.python-debugpy.new.1928 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-debugpy" Sat Dec 20 21:45:32 2025 rev:24 rq:1323656 version:1.8.18 Changes: -------- --- /work/SRC/openSUSE:Factory/python-debugpy/python-debugpy.changes 2025-12-11 18:31:43.537247624 +0100 +++ /work/SRC/openSUSE:Factory/.python-debugpy.new.1928/python-debugpy.changes 2025-12-20 21:46:15.906627656 +0100 @@ -1,0 +2,6 @@ +Fri Dec 12 10:29:25 UTC 2025 - Dirk Müller <[email protected]> + +- update to 1.8.18: + * Spaces in python interpreter cause debug launch failure + +------------------------------------------------------------------- Old: ---- debugpy-1.8.17.tar.gz New: ---- debugpy-1.8.18.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-debugpy.spec ++++++ --- /var/tmp/diff_new_pack.Mln8t0/_old 2025-12-20 21:46:16.618656922 +0100 +++ /var/tmp/diff_new_pack.Mln8t0/_new 2025-12-20 21:46:16.618656922 +0100 @@ -27,7 +27,7 @@ %endif %{?sle15_python_module_pythons} Name: python-debugpy%{psuffix} -Version: 1.8.17 +Version: 1.8.18 Release: 0 Summary: An implementation of the Debug Adapter Protocol for Python License: MIT ++++++ debugpy-1.8.17.tar.gz -> debugpy-1.8.18.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.17/.github/CODEOWNERS new/debugpy-1.8.18/.github/CODEOWNERS --- old/debugpy-1.8.17/.github/CODEOWNERS 2025-09-05 18:14:53.000000000 +0200 +++ new/debugpy-1.8.18/.github/CODEOWNERS 2025-12-10 19:39:27.000000000 +0100 @@ -4,4 +4,4 @@ # in the repository, i.e. bar/baz will match /bar/baz and /foo/bar/baz. # The default owners for everything that is not overridden by specific patterns below. -* @microsoft/debugpy-CodeReviewers +* @microsoft/pyrx-admins diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.17/.github/workflows/round-robin.yml new/debugpy-1.8.18/.github/workflows/round-robin.yml --- old/debugpy-1.8.17/.github/workflows/round-robin.yml 2025-09-05 18:14:53.000000000 +0200 +++ new/debugpy-1.8.18/.github/workflows/round-robin.yml 2025-12-10 19:39:27.000000000 +0100 @@ -32,7 +32,7 @@ - uses: lee-dohm/team-rotation@v1 with: last: ${{ steps.assigned.outputs.result }} - include: AdamYoblick bschnurr debonte heejaechang StellaHuang95 rchiodo KacieKK judej + include: bschnurr heejaechang StellaHuang95 rchiodo gramster id: rotation - name: Dump next in rotation env: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.17/CONTRIBUTING.md new/debugpy-1.8.18/CONTRIBUTING.md --- old/debugpy-1.8.17/CONTRIBUTING.md 2025-09-05 18:14:53.000000000 +0200 +++ new/debugpy-1.8.18/CONTRIBUTING.md 2025-12-10 19:39:27.000000000 +0100 @@ -24,7 +24,7 @@ - [Black](https://black.readthedocs.io/en/stable/) - [tox](https://tox.readthedocs.io/en/latest/) -We recommend using [Visual Studio Code](https://code.visualstudio.com/) with the (Python extension)[https://marketplace.visualstudio.com/items?itemName=ms-python.python] to work on debugpy, but it's not a requirement. A workspace file, [debugpy.code-workspace], is provided for the convenience of VSCode users, and sets it up to use the other tools listed above properly. +We recommend using [Visual Studio Code](https://code.visualstudio.com/) with the [Python extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python) to work on debugpy, but it's not a requirement. A workspace file, [debugpy.code-workspace], is provided for the convenience of VSCode users, and sets it up to use the other tools listed above properly. Tools that are Python packages should be installed via pip corresponding to the Python 3 installation. On Windows: ``` @@ -93,6 +93,8 @@ While tox is the recommended way to run the test suite, pytest can also be invoked directly from the root of the repository. This requires packages in tests/requirements.txt to be installed first. +Using a venv created by tox in the '.tox' folder can make it easier to get the pytest configuration correct. Debugpy needs to be installed into the venv for the tests to run, so using the tox generated .venv makes that easier. + #### Keeping logs on test success There's an internal setting `debugpy_log_passed` that if set to true will not erase the logs after a successful test run. Just search for this in the code and remove the code that deletes the logs on success. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.17/pytest.ini new/debugpy-1.8.18/pytest.ini --- old/debugpy-1.8.17/pytest.ini 2025-09-05 18:14:53.000000000 +0200 +++ new/debugpy-1.8.18/pytest.ini 2025-12-10 19:39:27.000000000 +0100 @@ -1,5 +1,5 @@ [pytest] testpaths=tests -timeout=60 +timeout=120 timeout_method=thread addopts=-n8 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.17/setup.py new/debugpy-1.8.18/setup.py --- old/debugpy-1.8.17/setup.py 2025-09-05 18:14:53.000000000 +0200 +++ new/debugpy-1.8.18/setup.py 2025-12-10 19:39:27.000000000 +0100 @@ -186,7 +186,7 @@ "debugpy._vendored", ], package_data={ - "debugpy": ["ThirdPartyNotices.txt"], + "debugpy": ["ThirdPartyNotices.txt", "py.typed"], "debugpy._vendored": [ # pydevd extensions must be built before this list can be computed properly, # so it is populated in the overridden build_py.finalize_options(). diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.17/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/linux_and_mac/compile_linux.sh new/debugpy-1.8.18/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/linux_and_mac/compile_linux.sh --- old/debugpy-1.8.17/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/linux_and_mac/compile_linux.sh 2025-09-05 18:14:53.000000000 +0200 +++ new/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 @@ -8,4 +8,4 @@ esac SRC="$(dirname "$0")/.." -g++ -std=c++11 -shared -fPIC -O2 -D_FORTIFY_SOURCE=2 -nostartfiles --stack-protector-strong $SRC/linux_and_mac/attach.cpp -o $SRC/attach_linux_$SUFFIX.so +g++ -std=c++11 -shared -fPIC -O2 -D_FORTIFY_SOURCE=2 -nostartfiles -fstack-protector-strong $SRC/linux_and_mac/attach.cpp -o $SRC/attach_linux_$SUFFIX.so diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.17/src/debugpy/_version.py new/debugpy-1.8.18/src/debugpy/_version.py --- old/debugpy-1.8.17/src/debugpy/_version.py 2025-09-05 18:14:53.000000000 +0200 +++ new/debugpy-1.8.18/src/debugpy/_version.py 2025-12-10 19:39:27.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 = " (tag: v1.8.17)" - git_full = "6cbdf8767e4c88dfaedf3db7b09ce2781496fc51" - git_date = "2025-09-05 09:14:53 -0700" + git_refnames = " (HEAD -> main, tag: v1.8.18)" + git_full = "e5017d736052d8d84484cdfe05750bd61cc7c50f" + git_date = "2025-12-10 10:39:27 -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.17/src/debugpy/adapter/launchers.py new/debugpy-1.8.18/src/debugpy/adapter/launchers.py --- old/debugpy-1.8.17/src/debugpy/adapter/launchers.py 2025-09-05 18:14:53.000000000 +0200 +++ new/debugpy-1.8.18/src/debugpy/adapter/launchers.py 2025-12-10 19:39:27.000000000 +0100 @@ -153,6 +153,23 @@ request_args["cwd"] = cwd if shell_expand_args: request_args["argsCanBeInterpretedByShell"] = True + + # VS Code debugger extension may pass us an argument indicating the + # quoting character to use in the terminal. Otherwise default based on platform. + default_quote = '"' if os.name != "nt" else "'" + 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. + 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 + try: # It is unspecified whether this request receives a response immediately, or only # after the spawned command has completed running, so do not block waiting for it. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.17/tests/debug/comms.py new/debugpy-1.8.18/tests/debug/comms.py --- old/debugpy-1.8.17/tests/debug/comms.py 2025-09-05 18:14:53.000000000 +0200 +++ new/debugpy-1.8.18/tests/debug/comms.py 2025-12-10 19:39:27.000000000 +0100 @@ -27,6 +27,7 @@ self._server_socket = sockets.create_server("127.0.0.1", 0, self.TIMEOUT) _, self.port = sockets.get_address(self._server_socket) self._server_socket.listen(0) + log.info("{0} created server socket on port {1}", self, self.port) def accept_worker(): log.info( @@ -67,8 +68,14 @@ self._established.set() def receive(self): - self._established.wait() - return self._stream.read_json() + log.info("{0} waiting for connection to be established...", self) + if not self._established.wait(timeout=self.TIMEOUT): + log.error("{0} timed out waiting for connection after {1} seconds", self, self.TIMEOUT) + raise TimeoutError(f"{self} timed out waiting for debuggee to connect") + log.info("{0} connection established, reading JSON...", self) + result = self._stream.read_json() + log.info("{0} received: {1}", self, result) + return result def send(self, value): self.session.timeline.unfreeze() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.17/tests/debug/session.py new/debugpy-1.8.18/tests/debug/session.py --- old/debugpy-1.8.17/tests/debug/session.py 2025-09-05 18:14:53.000000000 +0200 +++ new/debugpy-1.8.18/tests/debug/session.py 2025-12-10 19:39:27.000000000 +0100 @@ -281,7 +281,11 @@ if self.adapter_endpoints is not None and self.expected_exit_code is not None: log.info("Waiting for {0} to close listener ports ...", self.adapter_id) + timeout_start = time.time() while self.adapter_endpoints.check(): + if time.time() - timeout_start > 10: + log.warning("{0} listener ports did not close within 10 seconds", self.adapter_id) + break time.sleep(0.1) if self.adapter is not None: @@ -290,8 +294,20 @@ self.adapter_id, self.adapter.pid, ) - self.adapter.wait() - watchdog.unregister_spawn(self.adapter.pid, self.adapter_id) + try: + self.adapter.wait(timeout=10) + except Exception: + log.warning("{0} did not exit gracefully within 10 seconds, force-killing", self.adapter_id) + try: + self.adapter.kill() + self.adapter.wait(timeout=5) + except Exception as e: + log.error("Failed to force-kill {0}: {1}", self.adapter_id, e) + + try: + watchdog.unregister_spawn(self.adapter.pid, self.adapter_id) + except Exception as e: + log.warning("Failed to unregister adapter spawn: {0}", e) self.adapter = None if self.backchannel is not None: @@ -366,9 +382,23 @@ return env def _make_python_cmdline(self, exe, *args): - return [ - str(s.strpath if isinstance(s, py.path.local) else s) for s in [exe, *args] - ] + def normalize(s, strip_quotes=False): + # Convert py.path.local to string + if isinstance(s, py.path.local): + s = s.strpath + else: + s = str(s) + # Strip surrounding quotes if requested + if strip_quotes and len(s) >= 2 and " " in s and (s[0] == s[-1] == '"' or s[0] == s[-1] == "'"): + s = s[1:-1] + return s + + # Strip quotes from exe + result = [normalize(exe, strip_quotes=True)] + for arg in args: + # Don't strip quotes on anything except the exe + result.append(normalize(arg, strip_quotes=False)) + return result def spawn_debuggee(self, args, cwd=None, exe=sys.executable, setup=None): assert self.debuggee is None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.17/tests/debugpy/test_args.py new/debugpy-1.8.18/tests/debugpy/test_args.py --- old/debugpy-1.8.17/tests/debugpy/test_args.py 2025-09-05 18:14:53.000000000 +0200 +++ new/debugpy-1.8.18/tests/debugpy/test_args.py 2025-12-10 19:39:27.000000000 +0100 @@ -2,6 +2,8 @@ # Licensed under the MIT License. See LICENSE in the project root # for license information. +import os +import sys import pytest from debugpy.common import log @@ -35,9 +37,15 @@ @pytest.mark.parametrize("target", targets.all) @pytest.mark.parametrize("run", runners.all_launch) @pytest.mark.parametrize("expansion", ["preserve", "expand"]) -def test_shell_expansion(pyfile, target, run, expansion): [email protected]("python_with_space", [False, True]) +def test_shell_expansion(pyfile, tmpdir, target, run, expansion, python_with_space): if expansion == "expand" and run.console == "internalConsole": pytest.skip('Shell expansion is not supported for "internalConsole"') + + # Skip tests with python_with_space=True and target="code" on Windows + # because .cmd wrappers cannot properly handle multiline string arguments + if (python_with_space and target == targets.Code and sys.platform == "win32"): + pytest.skip('Windows .cmd wrapper cannot handle multiline code arguments') @pyfile def code_to_debug(): @@ -57,14 +65,34 @@ args[i] = arg[1:] log.info("After expansion: {0}", args) + captured_run_in_terminal_args = [] + class Session(debug.Session): def run_in_terminal(self, args, cwd, env): + captured_run_in_terminal_args.append(args[:]) # Capture a copy of the args expand(args) return super().run_in_terminal(args, cwd, env) argslist = ["0", "$1", "2"] args = argslist if expansion == "preserve" else " ".join(argslist) + with Session() as session: + # Create a Python wrapper with a space in the path if requested + if python_with_space: + # Create a directory with a space in the name + python_dir = tmpdir / "python with space" + python_dir.mkdir() + + if sys.platform == "win32": + wrapper = python_dir / "python.cmd" + wrapper.write(f'@echo off\n"{sys.executable}" %*') + else: + wrapper = python_dir / "python.sh" + wrapper.write(f'#!/bin/sh\nexec "{sys.executable}" "$@"') + os.chmod(wrapper.strpath, 0o777) + + session.config["python"] = wrapper.strpath + backchannel = session.open_backchannel() with run(session, target(code_to_debug, args=args)): pass @@ -73,3 +101,15 @@ expand(argslist) assert argv == [some.str] + argslist + + # Verify that the python executable path is correctly quoted if it contains spaces + if python_with_space and captured_run_in_terminal_args: + terminal_args = captured_run_in_terminal_args[0] + log.info("Captured runInTerminal args: {0}", terminal_args) + + # Check if the python executable (first arg) contains a space + python_arg = terminal_args[0] + assert "python with space" in python_arg, \ + 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}" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.17/tests/debugpy/test_django.py new/debugpy-1.8.18/tests/debugpy/test_django.py --- old/debugpy-1.8.17/tests/debugpy/test_django.py 2025-09-05 18:14:53.000000000 +0200 +++ new/debugpy-1.8.18/tests/debugpy/test_django.py 2025-12-10 19:39:27.000000000 +0100 @@ -5,7 +5,7 @@ import pytest from tests import code, debug, log, net, test_data -from tests.debug import runners, targets +from tests.debug import targets from tests.patterns import some pytestmark = pytest.mark.timeout(60) @@ -25,7 +25,6 @@ @pytest.fixture [email protected]("run", [runners.launch, runners.attach_connect["cli"]]) def start_django(run): def start(session, multiprocess=False): # No clean way to kill Django server, expect non-zero exit code diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.17/tests/debugpy/test_flask.py new/debugpy-1.8.18/tests/debugpy/test_flask.py --- old/debugpy-1.8.17/tests/debugpy/test_flask.py 2025-09-05 18:14:53.000000000 +0200 +++ new/debugpy-1.8.18/tests/debugpy/test_flask.py 2025-12-10 19:39:27.000000000 +0100 @@ -6,7 +6,7 @@ import sys from tests import code, debug, log, net, test_data -from tests.debug import runners, targets +from tests.debug import targets from tests.patterns import some pytestmark = pytest.mark.timeout(60) @@ -27,7 +27,6 @@ @pytest.fixture [email protected]("run", [runners.launch, runners.attach_connect["cli"]]) def start_flask(run): def start(session, multiprocess=False): # No clean way to kill Flask server, expect non-zero exit code diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.17/tests/net.py new/debugpy-1.8.18/tests/net.py --- old/debugpy-1.8.17/tests/net.py 2025-09-05 18:14:53.000000000 +0200 +++ new/debugpy-1.8.18/tests/net.py 2025-12-10 19:39:27.000000000 +0100 @@ -17,7 +17,7 @@ used_ports = set() -def get_test_server_port(): +def get_test_server_port(max_retries=10): """Returns a server port number that can be safely used for listening without clashing with another test worker process, when running with pytest-xdist. @@ -27,6 +27,9 @@ Note that if multiple test workers invoke this function with different ranges that overlap, conflicts are possible! + + Args: + max_retries: Number of times to retry finding an available port """ try: @@ -39,11 +42,32 @@ ), "Unrecognized PYTEST_XDIST_WORKER format" n = int(worker_id[2:]) + # Try multiple times to find an available port, with retry logic + for attempt in range(max_retries): + port = 5678 + (n * 300) + attempt + while port in used_ports: + port += 1 + + # Verify the port is actually available by trying to bind to it + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + try: + sock.bind(("127.0.0.1", port)) + sock.close() + used_ports.add(port) + log.info("Allocated port {0} for worker {1}", port, n) + return port + except OSError as e: + log.warning("Port {0} unavailable (attempt {1}/{2}): {3}", port, attempt + 1, max_retries, e) + sock.close() + time.sleep(0.1 * (attempt + 1)) # Exponential backoff + + # Fall back to original behavior if all retries fail port = 5678 + (n * 300) while port in used_ports: port += 1 used_ports.add(port) - + log.warning("Using fallback port {0} after {1} retries", port, max_retries) return port diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/debugpy-1.8.17/tests/pytest_fixtures.py new/debugpy-1.8.18/tests/pytest_fixtures.py --- old/debugpy-1.8.17/tests/pytest_fixtures.py 2025-09-05 18:14:53.000000000 +0200 +++ new/debugpy-1.8.18/tests/pytest_fixtures.py 2025-12-10 19:39:27.000000000 +0100 @@ -46,19 +46,27 @@ session.Session.reset_counter() - session.Session.tmpdir = long_tmpdir + # Add worker-specific isolation for tmpdir and log directory + try: + worker_id = os.environ.get("PYTEST_XDIST_WORKER", "gw0") + worker_suffix = f"_{worker_id}" + except Exception: + worker_suffix = "" + + session.Session.tmpdir = long_tmpdir / f"session{worker_suffix}" + session.Session.tmpdir.ensure(dir=True) original_log_dir = log.log_dir failed = True try: if log.log_dir is None: - log.log_dir = (long_tmpdir / "debugpy_logs").strpath + log.log_dir = (long_tmpdir / f"debugpy_logs{worker_suffix}").strpath else: log_subdir = request.node.nodeid log_subdir = log_subdir.replace("::", "/") for ch in r":?*|<>": log_subdir = log_subdir.replace(ch, f"&#{ord(ch)};") - log.log_dir += "/" + log_subdir + log.log_dir += "/" + log_subdir + worker_suffix try: py.path.local(log.log_dir).remove()
