From c41f84ff61c52e3ff7ef86b0c66208b29613d23d Mon Sep 17 00:00:00 2001 From: tconley1428 Date: Tue, 28 Oct 2025 23:06:22 -0700 Subject: [PATCH 1/4] gh-140228: Avoid making unnecessary syscalls in linecache for frozen modules (#140377) --- Lib/linecache.py | 5 ++++- .../Library/2025-10-28-17-43-51.gh-issue-140228.8kfHhO.rst | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2025-10-28-17-43-51.gh-issue-140228.8kfHhO.rst diff --git a/Lib/linecache.py b/Lib/linecache.py index ef73d1aa99774a..ef3b2d9136b4d2 100644 --- a/Lib/linecache.py +++ b/Lib/linecache.py @@ -123,9 +123,12 @@ def updatecache(filename, module_globals=None): if _source_unavailable(filename): return [] - if filename.startswith(' Date: Wed, 29 Oct 2025 14:12:12 +0800 Subject: [PATCH 2/4] gh-139940: Handle RuntimeError when attaching to a non-existing process in pdb. (#139941) --- Lib/pdb.py | 8 +++++++- Lib/test/test_remote_pdb.py | 12 ++++++++++++ .../2025-10-11-09-07-06.gh-issue-139940.g54efZ.rst | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2025-10-11-09-07-06.gh-issue-139940.g54efZ.rst diff --git a/Lib/pdb.py b/Lib/pdb.py index f695a39332e461..4ee12d17a611e6 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -3577,7 +3577,13 @@ def main(): parser.error("argument -m: not allowed with argument --pid") try: attach(opts.pid, opts.commands) - except PermissionError as e: + except RuntimeError: + print( + f"Cannot attach to pid {opts.pid}, please make sure that the process exists " + "and is using the same Python version." + ) + sys.exit(1) + except PermissionError: exit_with_permission_help_text() return elif opts.module: diff --git a/Lib/test/test_remote_pdb.py b/Lib/test/test_remote_pdb.py index ec11e41678849b..ede99de981971a 100644 --- a/Lib/test/test_remote_pdb.py +++ b/Lib/test/test_remote_pdb.py @@ -1590,5 +1590,17 @@ def test_attach_to_process_with_colors(self): self.assertNotIn("while x == 1", output["client"]["stdout"]) self.assertIn("while x == 1", re.sub("\x1b[^m]*m", "", output["client"]["stdout"])) + def test_attach_to_non_existent_process(self): + with force_color(False): + result = subprocess.run([sys.executable, "-m", "pdb", "-p", "999999"], text=True, capture_output=True) + self.assertNotEqual(result.returncode, 0) + if sys.platform == "darwin": + # On MacOS, attaching to a non-existent process gives PermissionError + error = "The specified process cannot be attached to due to insufficient permissions" + else: + error = "Cannot attach to pid 999999, please make sure that the process exists" + self.assertIn(error, result.stdout) + + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2025-10-11-09-07-06.gh-issue-139940.g54efZ.rst b/Misc/NEWS.d/next/Library/2025-10-11-09-07-06.gh-issue-139940.g54efZ.rst new file mode 100644 index 00000000000000..2501135e657e7d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-11-09-07-06.gh-issue-139940.g54efZ.rst @@ -0,0 +1 @@ +Print clearer error message when using ``pdb`` to attach to a non-existing process. From 9f8d005d2961777aa533ec330f96b50324a3446f Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 29 Oct 2025 16:33:04 +0800 Subject: [PATCH 3/4] gh-140702: Add test skip for Unix Datagram tests on iOS when on Github Actions (#140740) Exposes the GITHUB_ACTIONS environment variable to iOS simulator test runs, and uses this variable to skip a Unix Datagram socketserver test that is unreliable in the iOS GitHub Actions environment. Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Apple/iOS/README.md | 11 +++++++++++ Apple/testbed/TestbedTests/TestbedTests.m | 3 +++ Apple/testbed/__main__.py | 9 +++++++++ Lib/test/support/__init__.py | 3 ++- Lib/test/test_socketserver.py | 4 ++++ .../2025-10-29-15-20-19.gh-issue-140702.ZXtW8h.rst | 2 ++ 6 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Tools-Demos/2025-10-29-15-20-19.gh-issue-140702.ZXtW8h.rst diff --git a/Apple/iOS/README.md b/Apple/iOS/README.md index 124a05657aae09..7ee257b5d648f4 100644 --- a/Apple/iOS/README.md +++ b/Apple/iOS/README.md @@ -224,6 +224,17 @@ Once you have a built an XCframework, you can test that framework by running: $ python Apple test iOS +This test will attempt to find an "SE-class" simulator (i.e., an iPhone SE, or +iPhone 16e, or similar), and run the test suite on the most recent version of +iOS that is available. You can specify a simulator using the `--simulator` +command line argument, providing the name of the simulator (e.g., `--simulator +'iPhone 16 Pro'`). You can also use this argument to control the OS version used +for testing; `--simulator 'iPhone 16 Pro,OS=18.2'` would attempt to run the +tests on an iPhone 16 Pro running iOS 18.2. + +If the test runner is executed on GitHub Actions, the `GITHUB_ACTIONS` +environment variable will be exposed to the iOS process at runtime. + ### Testing a single-architecture framework The `Apple/testbed` folder that contains an Xcode project that is able to run diff --git a/Apple/testbed/TestbedTests/TestbedTests.m b/Apple/testbed/TestbedTests/TestbedTests.m index 80741097e4c80d..f7788c47f2c229 100644 --- a/Apple/testbed/TestbedTests/TestbedTests.m +++ b/Apple/testbed/TestbedTests/TestbedTests.m @@ -35,6 +35,9 @@ - (void)testPython { setenv("NO_COLOR", "1", true); setenv("PYTHON_COLORS", "0", true); + if (getenv("GITHUB_ACTIONS")) { + NSLog(@"Running in a GitHub Actions environment"); + } // Arguments to pass into the test suite runner. // argv[0] must identify the process; any subsequent arg // will be handled as if it were an argument to `python -m test` diff --git a/Apple/testbed/__main__.py b/Apple/testbed/__main__.py index f3407ecdf7e734..42eb60a4c8dc02 100644 --- a/Apple/testbed/__main__.py +++ b/Apple/testbed/__main__.py @@ -1,5 +1,6 @@ import argparse import json +import os import re import shutil import subprocess @@ -78,6 +79,13 @@ def xcode_test(location: Path, platform: str, simulator: str, verbose: bool): check=True, ) + # Any environment variable prefixed with TEST_RUNNER_ is exposed into the + # test runner environment. There are some variables (like those identifying + # CI platforms) that can be useful to have access to. + test_env = os.environ.copy() + if "GITHUB_ACTIONS" in os.environ: + test_env["TEST_RUNNER_GITHUB_ACTIONS"] = os.environ["GITHUB_ACTIONS"] + print("Running test project...") # Test execution *can't* be run -quiet; verbose mode # is how we see the output of the test output. @@ -85,6 +93,7 @@ def xcode_test(location: Path, platform: str, simulator: str, verbose: bool): ["xcodebuild", "test-without-building"] + args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + env=test_env, ) while line := (process.stdout.readline()).decode(*DECODE_ARGS): # Strip the timestamp/process prefix from each log line diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 098bdcc0542b90..0a50912ff0ea8c 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -68,7 +68,7 @@ "BrokenIter", "in_systemd_nspawn_sync_suppressed", "run_no_yield_async_fn", "run_yielding_async_fn", "async_yield", - "reset_code", + "reset_code", "on_github_actions" ] @@ -1369,6 +1369,7 @@ def reset_code(f: types.FunctionType) -> types.FunctionType: f.__code__ = f.__code__.replace() return f +on_github_actions = "GITHUB_ACTIONS" in os.environ #======================================================================= # Check for the presence of docstrings. diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py index a5e03020e5bd57..ca33a9a6dac8d3 100644 --- a/Lib/test/test_socketserver.py +++ b/Lib/test/test_socketserver.py @@ -223,12 +223,16 @@ def test_ForkingUDPServer(self): self.dgram_examine) @requires_unix_sockets + @unittest.skipIf(test.support.is_apple_mobile and test.support.on_github_actions, + "gh-140702: Test fails regularly on iOS simulator on GitHub Actions") def test_UnixDatagramServer(self): self.run_server(socketserver.UnixDatagramServer, socketserver.DatagramRequestHandler, self.dgram_examine) @requires_unix_sockets + @unittest.skipIf(test.support.is_apple_mobile and test.support.on_github_actions, + "gh-140702: Test fails regularly on iOS simulator on GitHub Actions") def test_ThreadingUnixDatagramServer(self): self.run_server(socketserver.ThreadingUnixDatagramServer, socketserver.DatagramRequestHandler, diff --git a/Misc/NEWS.d/next/Tools-Demos/2025-10-29-15-20-19.gh-issue-140702.ZXtW8h.rst b/Misc/NEWS.d/next/Tools-Demos/2025-10-29-15-20-19.gh-issue-140702.ZXtW8h.rst new file mode 100644 index 00000000000000..9efbf0162dd1c1 --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2025-10-29-15-20-19.gh-issue-140702.ZXtW8h.rst @@ -0,0 +1,2 @@ +The iOS testbed app will now expose the ``GITHUB_ACTIONS`` environment +variable to iOS apps being tested. From 02202c117b5702f3325e62b07ccdeaa125fc8722 Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Wed, 29 Oct 2025 12:06:23 +0300 Subject: [PATCH 4/4] gh-140551: Fix `dict` crash if `clear` is called at `lookup` stage (#140558) Co-authored-by: Inada Naoki --- Lib/test/test_dict.py | 20 +++++ ...-10-24-20-42-33.gh-issue-140551.-9swrl.rst | 2 + Objects/dictobject.c | 82 ++++++++----------- 3 files changed, 54 insertions(+), 50 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-10-24-20-42-33.gh-issue-140551.-9swrl.rst diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index 60c62430370e96..2e6c2bbdf19409 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -1601,6 +1601,26 @@ def __hash__(self): with self.assertRaises(KeyError): d.get(key2) + def test_clear_at_lookup(self): + class X: + def __hash__(self): + return 1 + def __eq__(self, other): + nonlocal d + d.clear() + + d = {} + for _ in range(10): + d[X()] = None + + self.assertEqual(len(d), 1) + + d = {} + for _ in range(10): + d.setdefault(X(), None) + + self.assertEqual(len(d), 1) + class CAPITest(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-24-20-42-33.gh-issue-140551.-9swrl.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-24-20-42-33.gh-issue-140551.-9swrl.rst new file mode 100644 index 00000000000000..8fd9b46c0aeabe --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-24-20-42-33.gh-issue-140551.-9swrl.rst @@ -0,0 +1,2 @@ +Fixed crash in :class:`dict` if :meth:`dict.clear` is called at the lookup +stage. Patch by Mikhail Efimov and Inada Naoki. diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 73d4db4cac7963..65eed151c2829d 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -1775,6 +1775,14 @@ static inline int insert_combined_dict(PyInterpreterState *interp, PyDictObject *mp, Py_hash_t hash, PyObject *key, PyObject *value) { + // gh-140551: If dict was cleared in _Py_dict_lookup, + // we have to resize one more time to force general key kind. + if (DK_IS_UNICODE(mp->ma_keys) && !PyUnicode_CheckExact(key)) { + if (insertion_resize(mp, 0) < 0) + return -1; + assert(mp->ma_keys->dk_kind == DICT_KEYS_GENERAL); + } + if (mp->ma_keys->dk_usable <= 0) { /* Need to resize. */ if (insertion_resize(mp, 1) < 0) { @@ -1871,38 +1879,31 @@ insertdict(PyInterpreterState *interp, PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value) { PyObject *old_value; + Py_ssize_t ix; ASSERT_DICT_LOCKED(mp); - if (DK_IS_UNICODE(mp->ma_keys) && !PyUnicode_CheckExact(key)) { - if (insertion_resize(mp, 0) < 0) - goto Fail; - assert(mp->ma_keys->dk_kind == DICT_KEYS_GENERAL); - } - - if (_PyDict_HasSplitTable(mp)) { - Py_ssize_t ix = insert_split_key(mp->ma_keys, key, hash); + if (_PyDict_HasSplitTable(mp) && PyUnicode_CheckExact(key)) { + ix = insert_split_key(mp->ma_keys, key, hash); if (ix != DKIX_EMPTY) { insert_split_value(interp, mp, key, value, ix); Py_DECREF(key); Py_DECREF(value); return 0; } - - /* No space in shared keys. Resize and continue below. */ - if (insertion_resize(mp, 1) < 0) { + // No space in shared keys. Go to insert_combined_dict() below. + } + else { + ix = _Py_dict_lookup(mp, key, hash, &old_value); + if (ix == DKIX_ERROR) goto Fail; - } } - Py_ssize_t ix = _Py_dict_lookup(mp, key, hash, &old_value); - if (ix == DKIX_ERROR) - goto Fail; - if (ix == DKIX_EMPTY) { - assert(!_PyDict_HasSplitTable(mp)); - /* Insert into new slot. */ - assert(old_value == NULL); + // insert_combined_dict() will convert from non DICT_KEYS_GENERAL table + // into DICT_KEYS_GENERAL table if key is not Unicode. + // We don't convert it before _Py_dict_lookup because non-Unicode key + // may change generic table into Unicode table. if (insert_combined_dict(interp, mp, hash, key, value) < 0) { goto Fail; } @@ -4374,6 +4375,7 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu PyDictObject *mp = (PyDictObject *)d; PyObject *value; Py_hash_t hash; + Py_ssize_t ix; PyInterpreterState *interp = _PyInterpreterState_GET(); ASSERT_DICT_LOCKED(d); @@ -4409,17 +4411,8 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu return 0; } - if (!PyUnicode_CheckExact(key) && DK_IS_UNICODE(mp->ma_keys)) { - if (insertion_resize(mp, 0) < 0) { - if (result) { - *result = NULL; - } - return -1; - } - } - - if (_PyDict_HasSplitTable(mp)) { - Py_ssize_t ix = insert_split_key(mp->ma_keys, key, hash); + if (_PyDict_HasSplitTable(mp) && PyUnicode_CheckExact(key)) { + ix = insert_split_key(mp->ma_keys, key, hash); if (ix != DKIX_EMPTY) { PyObject *value = mp->ma_values->values[ix]; int already_present = value != NULL; @@ -4432,27 +4425,22 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu } return already_present; } - - /* No space in shared keys. Resize and continue below. */ - if (insertion_resize(mp, 1) < 0) { - goto error; - } + // No space in shared keys. Go to insert_combined_dict() below. } - - assert(!_PyDict_HasSplitTable(mp)); - - Py_ssize_t ix = _Py_dict_lookup(mp, key, hash, &value); - if (ix == DKIX_ERROR) { - if (result) { - *result = NULL; + else { + ix = _Py_dict_lookup(mp, key, hash, &value); + if (ix == DKIX_ERROR) { + if (result) { + *result = NULL; + } + return -1; } - return -1; } if (ix == DKIX_EMPTY) { - assert(!_PyDict_HasSplitTable(mp)); value = default_value; + // See comment to this function in insertdict. if (insert_combined_dict(interp, mp, hash, Py_NewRef(key), Py_NewRef(value)) < 0) { Py_DECREF(key); Py_DECREF(value); @@ -4477,12 +4465,6 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu *result = incref_result ? Py_NewRef(value) : value; } return 1; - -error: - if (result) { - *result = NULL; - } - return -1; } int