diff --git a/Doc/library/uuid.rst b/Doc/library/uuid.rst index 4b505c81c06f0f..f1c49f8a94c174 100644 --- a/Doc/library/uuid.rst +++ b/Doc/library/uuid.rst @@ -44,9 +44,10 @@ which relays any information about the UUID's safety, using this enumeration: .. class:: UUID(hex=None, bytes=None, bytes_le=None, fields=None, int=None, version=None, *, is_safe=SafeUUID.unknown) - Create a UUID from either a string of 32 hexadecimal digits, a string of 16 - bytes in big-endian order as the *bytes* argument, a string of 16 bytes in - little-endian order as the *bytes_le* argument, a tuple of six integers + Create a UUID from either a string of 32 hexadecimal digits, a 16-byte + :class:`bytes` object in big-endian order as the *bytes* argument, a + 16-byte :class:`bytes` object in little-endian order as the *bytes_le* + argument, a tuple of six integers (32-bit *time_low*, 16-bit *time_mid*, 16-bit *time_hi_version*, 8-bit *clock_seq_hi_variant*, 8-bit *clock_seq_low*, 48-bit *node*) as the *fields* argument, or a single 128-bit integer as the *int* argument. @@ -80,14 +81,14 @@ which relays any information about the UUID's safety, using this enumeration: .. attribute:: UUID.bytes - The UUID as a 16-byte string (containing the six integer fields in big-endian - byte order). + The UUID as a 16-byte :class:`bytes` object (containing the six integer + fields in big-endian byte order). .. attribute:: UUID.bytes_le - The UUID as a 16-byte string (with *time_low*, *time_mid*, and *time_hi_version* - in little-endian byte order). + The UUID as a 16-byte :class:`bytes` object (with *time_low*, *time_mid*, + and *time_hi_version* in little-endian byte order). .. attribute:: UUID.fields @@ -435,7 +436,7 @@ Here are some examples of typical usage of the :mod:`!uuid` module:: >>> x.bytes b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f' - >>> # make a UUID from a 16-byte string + >>> # make a UUID from a 16-byte bytes object >>> uuid.UUID(bytes=x.bytes) UUID('00010203-0405-0607-0809-0a0b0c0d0e0f') diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 21ff5df44aad37..f348c9a5bbb31c 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -3555,7 +3555,7 @@ def inject_memory_error(start=0, stop=0): @contextlib.contextmanager -def memory_error_cm(start=0, stop=0): +def inject_memory_error_cm(start=0, stop=0): """ Similar to inject_memory_error() but can be used as a context manager. diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index bab6006df36a44..419bee5583de47 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -52,7 +52,7 @@ def __index__(self): @contextlib.contextmanager def inject_memory_error(testcase, start=0): with testcase.assertRaises(MemoryError): - with support.memory_error_cm(start): + with support.inject_memory_error_cm(start): yield diff --git a/Lib/test/test_capi/test_bytes.py b/Lib/test/test_capi/test_bytes.py index 1356e5d6c51c14..733a8ebbf1e5c3 100644 --- a/Lib/test/test_capi/test_bytes.py +++ b/Lib/test/test_capi/test_bytes.py @@ -511,12 +511,9 @@ def test_resize_error(self): writer = self.create_writer(len(init)) writer.write(0, init) size = len(init) + 100 - try: - with self.assertRaises(MemoryError): - _testcapi.set_nomemory(0) + with self.assertRaises(MemoryError): + with support.inject_memory_error_cm(): writer.resize(size) - finally: - _testcapi.remove_mem_hooks() suffix = b'still working' writer.write_bytes(suffix, -1) self.assertEqual(writer.finish(), init + suffix) @@ -590,12 +587,9 @@ def test_grow_error(self): init = b'x' * self.LARGE_BUFFER writer = self.create_writer(len(init)) writer.write(0, init) - try: - with self.assertRaises(MemoryError): - _testcapi.set_nomemory(0) + with self.assertRaises(MemoryError): + with support.inject_memory_error_cm(): writer.grow(100) - finally: - _testcapi.remove_mem_hooks() suffix = b'still working' writer.write_bytes(suffix, -1) self.assertEqual(writer.finish(), init + suffix) diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index b8b35f68aa7a05..3fdc87700f99ad 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -1030,7 +1030,7 @@ def __init__(self): d = a.__dict__ try: with support.catch_unraisable_exception() as ex: - with support.memory_error_cm(n, n + 1): + with support.inject_memory_error_cm(n, n + 1): del a exc_type = ex.unraisable and ex.unraisable.exc_type except MemoryError: diff --git a/Lib/test/test_external_inspection.py b/Lib/test/test_external_inspection.py index 910fe96d5e7d81..1fed0f9175e8e1 100644 --- a/Lib/test/test_external_inspection.py +++ b/Lib/test/test_external_inspection.py @@ -1,3 +1,4 @@ +import asyncio import unittest import os import textwrap @@ -440,6 +441,22 @@ def _extract_coroutine_stacks_lineno_only(self, stack_trace): @requires_remote_subprocess_debugging() class TestSelfStackTrace(RemoteInspectionTestBase): + @skip_if_not_supported + def test_long_task_name_is_truncated(self): + # gh-157788 + async def main(): + asyncio.create_task(asyncio.sleep(10_000), name="x" * 300) + await asyncio.sleep(0) + return [ + task.task_name + for info in RemoteUnwinder(os.getpid()).get_all_awaited_by() + for task in info.awaited_by + ] + + names = asyncio.run(main()) + self.assertIn("Task-1", names) + self.assertEqual([len(n) for n in names if n.startswith("x")], [255]) + @skip_if_not_supported @unittest.skipIf( sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py index 2d6320549b03f6..2fe959f6c14f77 100644 --- a/Lib/test/test_fstring.py +++ b/Lib/test/test_fstring.py @@ -832,6 +832,18 @@ def build_fstr(n, extra=''): s = "f'{1}' 'x' 'y'" * 1024 self.assertEqual(eval(s), '1xy' * 1024) + @support.requires_resource('cpu') + def test_many_fstrings_in_module(self): + fields = ''.join(f'{{x{i}}}' for i in range(100)) + source = ''.join( + f"value_{i} = f'{fields}'\n" for i in range(1_000) + ) + namespace = {f'x{i}': str(i) for i in range(100)} + expected = ''.join(str(i) for i in range(100)) + exec(source, namespace) + self.assertEqual(namespace['value_0'], expected) + self.assertEqual(namespace['value_999'], expected) + def test_format_specifier_expressions(self): width = 10 precision = 4 @@ -1348,6 +1360,9 @@ def test_not_equal(self): self.assertEqual(f'{3!=4:}', 'True') self.assertEqual(f'{3!=4!s}', 'True') self.assertEqual(f'{3!=4!s:.3}', 'Tru') + a = 3 + b = 4 + self.assertEqual(f'{a!=b=:>10}', 'a!=b= 1') def test_equal_equal(self): # Because an expression ending in = has special meaning, @@ -1819,6 +1834,19 @@ def test_debug_in_file(self): self.assertEqual(stdout.decode('utf-8').strip().replace('\r\n', '\n').replace('\r', '\n'), "3\n=3") + def test_debug_in_file_after_buffer_resize(self): + expression = "(\n" + (" " * 64 + "\n") * 256 + "1\n)" + expected = expression + "=1" + with temp_cwd(): + script = 'script.py' + source = ( + f"result = f'''{{{expression}=}}'''\n" + f"assert result == {expected!r}\n" + ) + with open(script, 'w') as f: + f.write(source) + assert_python_ok(script) + def test_syntax_warning_infinite_recursion_in_file(self): with temp_cwd(): script = 'script.py' diff --git a/Lib/test/test_interpreters/test_stress.py b/Lib/test/test_interpreters/test_stress.py index 50d2444a4c72d3..56bfc12f951f79 100644 --- a/Lib/test/test_interpreters/test_stress.py +++ b/Lib/test/test_interpreters/test_stress.py @@ -78,15 +78,9 @@ def run(): @support.nomemtest def test_create_interpreter_no_memory(self): - import _testcapi - - assertion = self.assertRaises(InterpreterError) - try: - _testcapi.set_nomemory(0, 1) - with assertion: + with self.assertRaises(InterpreterError): + with support.inject_memory_error_cm(0, 1): _interpreters.create() - finally: - _testcapi.remove_mem_hooks() if __name__ == '__main__': diff --git a/Lib/test/test_io/test_memoryio.py b/Lib/test/test_io/test_memoryio.py index 59e0dc4435d1f3..b378505aa8f7db 100644 --- a/Lib/test/test_io/test_memoryio.py +++ b/Lib/test/test_io/test_memoryio.py @@ -5,7 +5,6 @@ import unittest from test import support -from test.support import import_helper import gc import io @@ -757,18 +756,14 @@ def __buffer__(self, flags): @support.nomemtest def test_memory_error(self): # gh-157242: io.BytesIO() must not close the file on MemoryError - _testcapi = import_helper.import_module('_testcapi') # write() stream = self.ioclass() stream.write(self.buftype('abc')) + data = self.buftype('def') with self.assertRaises(MemoryError): - try: - data = self.buftype('def') - _testcapi.set_nomemory(0) + with support.inject_memory_error_cm(): stream.write(data) - finally: - _testcapi.remove_mem_hooks() stream.write(self.buftype('123')) self.assertEqual(stream.getvalue(), self.buftype('abc123')) @@ -777,11 +772,8 @@ def test_memory_error(self): stream = self.ioclass() stream.write(data) with self.assertRaises(MemoryError): - try: - _testcapi.set_nomemory(0) + with support.inject_memory_error_cm(): stream.truncate(5) - finally: - _testcapi.remove_mem_hooks() self.assertEqual(stream.getvalue(), data) diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py index 23b82dc1fd2179..fc5c7b311934e5 100644 --- a/Lib/test/test_pyexpat.py +++ b/Lib/test/test_pyexpat.py @@ -1078,7 +1078,7 @@ def test_error_path_no_crash(self): rc_before = sys.getrefcount(parser) with self.assertRaises(MemoryError): - with support.memory_error_cm(1, 10): + with support.inject_memory_error_cm(1, 10): parser.ExternalEntityParserCreate(None) rc_after = sys.getrefcount(parser) diff --git a/Lib/test/test_str.py b/Lib/test/test_str.py index 17163182be08c4..bef0e62fb42bcc 100644 --- a/Lib/test/test_str.py +++ b/Lib/test/test_str.py @@ -614,7 +614,7 @@ def test_replace_oom(self): s2 = "&" s3 = "&" with self.assertRaises(MemoryError): - with support.memory_error_cm(): + with support.inject_memory_error_cm(): s1.replace(s2, s3) # this line used to crash before def test_repeat_id_preserving(self): diff --git a/Lib/test/test_tstring.py b/Lib/test/test_tstring.py index 67a8e0fc6bcffb..20a5083f60d11a 100644 --- a/Lib/test/test_tstring.py +++ b/Lib/test/test_tstring.py @@ -1,5 +1,8 @@ import unittest +from test import support +from test.support.os_helper import temp_cwd +from test.support.script_helper import assert_python_ok from test.test_string._support import TStringBaseCase, fstring @@ -79,6 +82,31 @@ def upper(self): ) self.assertEqual(fstring(t), "Name: Bob, Age: 30") + def test_interpolation_expression_in_file_after_buffer_resize(self): + expression = "(\n" + (" " * 64 + "\n") * 256 + "1\n)" + with temp_cwd(): + script = 'script.py' + source = ( + f"template = t'''{{{expression}}}'''\n" + "interpolation = template.interpolations[0]\n" + f"assert interpolation.expression == {expression!r}\n" + ) + with open(script, 'w') as f: + f.write(source) + assert_python_ok(script) + + @support.requires_resource('cpu') + def test_many_tstrings_in_module(self): + fields = ''.join(f'{{x{i}}}' for i in range(100)) + source = ''.join( + f"value_{i} = t'{fields}'\n" for i in range(1_000) + ) + namespace = {f'x{i}': str(i) for i in range(100)} + expected = ''.join(str(i) for i in range(100)) + exec(source, namespace) + self.assertEqual(fstring(namespace['value_0']), expected) + self.assertEqual(fstring(namespace['value_999']), expected) + def test_format_specifiers(self): # Test basic format specifiers value = 3.14159 @@ -88,6 +116,14 @@ def test_format_specifiers(self): ) self.assertEqual(fstring(t), "Pi: 3.14") + a = 3 + b = 4 + t = t"{a!=b:>10}" + self.assertTStringEqual( + t, ("", ""), [(a != b, "a!=b", None, ">10")] + ) + self.assertEqual(fstring(t), " 1") + def test_conversions(self): # Test !s conversion (str) obj = object() diff --git a/Lib/test/test_ttk/test_widgets.py b/Lib/test/test_ttk/test_widgets.py index 6ed593e3e43bb0..223078625ecaad 100644 --- a/Lib/test/test_ttk/test_widgets.py +++ b/Lib/test/test_ttk/test_widgets.py @@ -214,6 +214,13 @@ class LabelTest(AbstractLabelTest, unittest.TestCase): def create(self, **kwargs): return ttk.Label(self.root, **kwargs) + def test_configure_anchor(self): + widget = self.create() + values = ('n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw', 'center') + if get_tk_patchlevel(self.root) >= (9, 0, 5): + values += ('',) + self.checkEnumParam(widget, 'anchor', *values) + test_configure_justify = StandardOptionsTests.test_configure_justify @@ -347,6 +354,7 @@ class EntryTest(AbstractWidgetTest, unittest.TestCase): def setUp(self): super().setUp() self.entry = self.create() + self._allow_empty_justify = get_tk_patchlevel(self.root) >= (9, 0, 5) def create(self, **kwargs): return ttk.Entry(self.root, **kwargs) diff --git a/Lib/uuid.py b/Lib/uuid.py index 4bdcb67775a2ea..3f10fed81c1bf9 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -43,7 +43,7 @@ >>> x.bytes b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f' - # make a UUID from a 16-byte string + # make a UUID from a 16-byte bytes object >>> uuid.UUID(bytes=x.bytes) UUID('00010203-0405-0607-0809-0a0b0c0d0e0f') @@ -118,19 +118,19 @@ class UUID: '12345678-1234-1234-1234-123456789abc'. The UUID constructor accepts five possible forms: a similar string of hexadecimal digits, or a tuple of six integer fields (with 32-bit, 16-bit, 16-bit, 8-bit, 8-bit, and - 48-bit values respectively) as an argument named 'fields', or a string - of 16 bytes (with all the integer fields in big-endian order) as an - argument named 'bytes', or a string of 16 bytes (with the first three - fields in little-endian order) as an argument named 'bytes_le', or a - single 128-bit integer as an argument named 'int'. + 48-bit values respectively) as an argument named 'fields', or a 16-byte + bytes object (with all the integer fields in big-endian order) as an + argument named 'bytes', or a 16-byte bytes object (with the first three + fields in little-endian order) as an argument named 'bytes_le', or a single + 128-bit integer as an argument named 'int'. UUIDs have these read-only attributes: - bytes the UUID as a 16-byte string (containing the six + bytes the UUID as a 16-byte bytes object (containing the six integer fields in big-endian byte order) - bytes_le the UUID as a 16-byte string (with time_low, time_mid, - and time_hi_version in little-endian byte order) + bytes_le the UUID as a 16-byte bytes object (with time_low, + time_mid, and time_hi_version in little-endian byte order) fields a tuple of the six integer fields of the UUID, which are also available as six individual attributes @@ -179,7 +179,7 @@ def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None, int=None, version=None, *, is_safe=SafeUUID.unknown): r"""Create a UUID from either a string of 32 hexadecimal digits, - a string of 16 bytes as the 'bytes' argument, a string of 16 bytes + a 16-byte bytes object as the 'bytes' argument, a 16-byte bytes object in little-endian order as the 'bytes_le' argument, a tuple of six integers (32-bit time_low, 16-bit time_mid, 16-bit time_hi_version, 8-bit clock_seq_hi_variant, 8-bit clock_seq_low, 48-bit node) as @@ -191,9 +191,9 @@ def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None, UUID('{12345678-1234-5678-1234-567812345678}') UUID('12345678123456781234567812345678') UUID('urn:uuid:12345678-1234-5678-1234-567812345678') - UUID(bytes='\x12\x34\x56\x78'*4) - UUID(bytes_le='\x78\x56\x34\x12\x34\x12\x78\x56' + - '\x12\x34\x56\x78\x12\x34\x56\x78') + UUID(bytes=b'\x12\x34\x56\x78'*4) + UUID(bytes_le=b'\x78\x56\x34\x12\x34\x12\x78\x56' + + b'\x12\x34\x56\x78\x12\x34\x56\x78') UUID(fields=(0x12345678, 0x1234, 0x5678, 0x12, 0x34, 0x567812345678)) UUID(int=0x12345678123456781234567812345678) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-01-07-15-20.gh-issue-155525.A7kP2m.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-01-07-15-20.gh-issue-155525.A7kP2m.rst new file mode 100644 index 00000000000000..9da3c38ce4691b --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-01-07-15-20.gh-issue-155525.A7kP2m.rst @@ -0,0 +1,2 @@ +Fix quadratic-time tokenization of modules containing many f-strings or +t-strings. diff --git a/Misc/NEWS.d/next/Library/2026-09-19-14-10-18.gh-issue-157788.v70Lxd.rst b/Misc/NEWS.d/next/Library/2026-09-19-14-10-18.gh-issue-157788.v70Lxd.rst new file mode 100644 index 00000000000000..641ca0a82a0915 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-19-14-10-18.gh-issue-157788.v70Lxd.rst @@ -0,0 +1,2 @@ +Fix ``python -m asyncio ps`` failing on a process that has a task with a +name longer than 255 characters. diff --git a/Modules/_remote_debugging/object_reading.c b/Modules/_remote_debugging/object_reading.c index 1cea96a2151fcc..56d9f80a80fd0f 100644 --- a/Modules/_remote_debugging/object_reading.c +++ b/Modules/_remote_debugging/object_reading.c @@ -64,12 +64,16 @@ read_py_str( } Py_ssize_t len = GET_MEMBER(Py_ssize_t, unicode_obj, unwinder->debug_offsets.unicode_object.length); - if (len < 0 || len > max_len) { + if (len < 0) { PyErr_Format(PyExc_RuntimeError, "Invalid string length (%zd) at 0x%lx", len, address); set_exception_cause(unwinder, PyExc_RuntimeError, "Invalid string length in remote Unicode object"); return NULL; } + if (len > max_len) { + // gh-157788: a long name must not fail the whole read + len = max_len; + } // Inspect state to pick the right data offset and character width. // We rely on the remote process sharing this Python version's