From a6bb15eac41644251587ed415d753538bdcf5efe Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Sat, 18 Jan 2025 08:26:07 +0000 Subject: [PATCH 01/28] Reapply "gh-128770: raise warnings as errors in test suite - except for test_socket which still logs warnings (#128726)" (#128936) This reverts commit 76856ae1659dbba066e51f353b31cb7cf0a8a5fe. --- Lib/test/libregrtest/main.py | 4 +-- Lib/test/test_pyrepl/test_pyrepl.py | 31 ++++++++++++----------- Lib/test/test_socket.py | 38 +++++++++++++++++++++++------ 3 files changed, 47 insertions(+), 26 deletions(-) diff --git a/Lib/test/libregrtest/main.py b/Lib/test/libregrtest/main.py index dcbcc6790c68d8..cd4d512771e05f 100644 --- a/Lib/test/libregrtest/main.py +++ b/Lib/test/libregrtest/main.py @@ -642,9 +642,9 @@ def _add_ci_python_opts(self, python_opts, keep_environ): if not sys.stdout.write_through: python_opts.append('-u') - # Add warnings filter 'default' + # Add warnings filter 'error' if 'default' not in sys.warnoptions: - python_opts.extend(('-W', 'default')) + python_opts.extend(('-W', 'error')) # Error on bytes/str comparison if sys.flags.bytes_warning < 2: diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index f29a7ffbd7cafd..113ea1d2fe709f 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -1324,22 +1324,21 @@ def test_readline_history_file(self): if readline.backend != "editline": self.skipTest("GNU readline is not affected by this issue") - hfile = tempfile.NamedTemporaryFile() - self.addCleanup(unlink, hfile.name) - env = os.environ.copy() - env["PYTHON_HISTORY"] = hfile.name - - env["PYTHON_BASIC_REPL"] = "1" - output, exit_code = self.run_repl("spam \nexit()\n", env=env) - self.assertEqual(exit_code, 0) - self.assertIn("spam ", output) - self.assertNotEqual(pathlib.Path(hfile.name).stat().st_size, 0) - self.assertIn("spam\\040", pathlib.Path(hfile.name).read_text()) - - env.pop("PYTHON_BASIC_REPL", None) - output, exit_code = self.run_repl("exit\n", env=env) - self.assertEqual(exit_code, 0) - self.assertNotIn("\\040", pathlib.Path(hfile.name).read_text()) + with tempfile.NamedTemporaryFile() as hfile: + env = os.environ.copy() + env["PYTHON_HISTORY"] = hfile.name + + env["PYTHON_BASIC_REPL"] = "1" + output, exit_code = self.run_repl("spam \nexit()\n", env=env) + self.assertEqual(exit_code, 0) + self.assertIn("spam ", output) + self.assertNotEqual(pathlib.Path(hfile.name).stat().st_size, 0) + self.assertIn("spam\\040", pathlib.Path(hfile.name).read_text()) + + env.pop("PYTHON_BASIC_REPL", None) + output, exit_code = self.run_repl("exit\n", env=env) + self.assertEqual(exit_code, 0) + self.assertNotIn("\\040", pathlib.Path(hfile.name).read_text()) def test_keyboard_interrupt_after_isearch(self): output, exit_code = self.run_repl(["\x12", "\x03", "exit"]) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index faf326d9164e1b..7233847f37bf39 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -1,7 +1,8 @@ import unittest +import warnings from test import support from test.support import ( - is_apple, os_helper, refleak_helper, socket_helper, threading_helper + is_apple, os_helper, refleak_helper, socket_helper, threading_helper, ) import _thread as thread import array @@ -198,6 +199,24 @@ def socket_setdefaulttimeout(timeout): socket.setdefaulttimeout(old_timeout) +@contextlib.contextmanager +def downgrade_malformed_data_warning(): + # This warning happens on macos and win, but does not always happen on linux. + if sys.platform not in {"win32", "darwin"}: + yield + return + + with warnings.catch_warnings(): + # TODO: gh-110012, we should investigate why this warning is happening + # and fix it properly. + warnings.filterwarnings( + action="always", + message=r"received malformed or improperly-truncated ancillary data", + category=RuntimeWarning, + ) + yield + + HAVE_SOCKET_CAN = _have_socket_can() HAVE_SOCKET_CAN_ISOTP = _have_socket_can_isotp() @@ -3946,8 +3965,9 @@ def checkTruncatedArray(self, ancbuf, maxdata, mindata=0): # mindata and maxdata bytes when received with buffer size # ancbuf, and that any complete file descriptor numbers are # valid. - msg, ancdata, flags, addr = self.doRecvmsg(self.serv_sock, - len(MSG), ancbuf) + with downgrade_malformed_data_warning(): # TODO: gh-110012 + msg, ancdata, flags, addr = self.doRecvmsg(self.serv_sock, + len(MSG), ancbuf) self.assertEqual(msg, MSG) self.checkRecvmsgAddress(addr, self.cli_addr) self.checkFlags(flags, eor=True, checkset=socket.MSG_CTRUNC) @@ -4298,8 +4318,9 @@ def testSingleCmsgTruncInData(self): self.serv_sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_RECVHOPLIMIT, 1) self.misc_event.set() - msg, ancdata, flags, addr = self.doRecvmsg( - self.serv_sock, len(MSG), socket.CMSG_LEN(SIZEOF_INT) - 1) + with downgrade_malformed_data_warning(): # TODO: gh-110012 + msg, ancdata, flags, addr = self.doRecvmsg( + self.serv_sock, len(MSG), socket.CMSG_LEN(SIZEOF_INT) - 1) self.assertEqual(msg, MSG) self.checkRecvmsgAddress(addr, self.cli_addr) @@ -4402,9 +4423,10 @@ def testSecondCmsgTruncInData(self): self.serv_sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_RECVTCLASS, 1) self.misc_event.set() - msg, ancdata, flags, addr = self.doRecvmsg( - self.serv_sock, len(MSG), - socket.CMSG_SPACE(SIZEOF_INT) + socket.CMSG_LEN(SIZEOF_INT) - 1) + with downgrade_malformed_data_warning(): # TODO: gh-110012 + msg, ancdata, flags, addr = self.doRecvmsg( + self.serv_sock, len(MSG), + socket.CMSG_SPACE(SIZEOF_INT) + socket.CMSG_LEN(SIZEOF_INT) - 1) self.assertEqual(msg, MSG) self.checkRecvmsgAddress(addr, self.cli_addr) From baab6a32e1873e416641ac62b484faa473270c88 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Sat, 18 Jan 2025 08:47:59 +0000 Subject: [PATCH 02/28] convert internal test warnings into logger calls --- Lib/test/support/__init__.py | 6 +++--- Lib/test/support/os_helper.py | 23 +++++++++++++++-------- Lib/test/test_decimal.py | 7 ++++--- Lib/test/test_hashlib.py | 7 ++++++- Lib/test/test_interpreters/utils.py | 5 +++-- Lib/test/test_pty.py | 6 ++++-- 6 files changed, 35 insertions(+), 19 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index ee9520a8838625..5c34eab80f1110 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -17,6 +17,7 @@ import types import unittest import warnings +import logging __all__ = [ @@ -387,7 +388,7 @@ def skip_if_buildbot(reason=None): try: isbuildbot = getpass.getuser().lower() == 'buildbot' except (KeyError, OSError) as err: - warnings.warn(f'getpass.getuser() failed {err}.', RuntimeWarning) + logging.getLogger(__name__).warning('getpass.getuser() failed %s.', err, exc_info=err) isbuildbot = False return unittest.skipIf(isbuildbot, reason) @@ -1066,8 +1067,7 @@ def start(self): try: f = open(self.procfile, 'r') except OSError as e: - warnings.warn('/proc not available for stats: {}'.format(e), - RuntimeWarning) + logging.getLogger(__name__).warning('/proc not available for stats: %s', e, exc_info=e) sys.stderr.flush() return diff --git a/Lib/test/support/os_helper.py b/Lib/test/support/os_helper.py index 8071c248b9b67e..8b7f52082314d8 100644 --- a/Lib/test/support/os_helper.py +++ b/Lib/test/support/os_helper.py @@ -1,3 +1,4 @@ +import logging import collections.abc import contextlib import errno @@ -378,8 +379,7 @@ def _waitfor(func, pathname, waitall=False): # Increase the timeout and try again time.sleep(timeout) timeout *= 2 - warnings.warn('tests may fail, delete still pending for ' + pathname, - RuntimeWarning, stacklevel=4) + logging.getLogger(__name__).warning('tests may fail, delete still pending for %s', pathname) def _unlink(filename): _waitfor(os.unlink, filename) @@ -494,9 +494,12 @@ def temp_dir(path=None, quiet=False): except OSError as exc: if not quiet: raise - warnings.warn(f'tests may fail, unable to create ' - f'temporary directory {path!r}: {exc}', - RuntimeWarning, stacklevel=3) + logger.getLogger(__name__).warning( + "tests may fail, unable to create temporary directory %r: %s", + path, + exc, + exc_info=exc, + ) if dir_created: pid = os.getpid() try: @@ -527,9 +530,13 @@ def change_cwd(path, quiet=False): except OSError as exc: if not quiet: raise - warnings.warn(f'tests may fail, unable to change the current working ' - f'directory to {path!r}: {exc}', - RuntimeWarning, stacklevel=3) + logging.getLogger(__name__).warning( + 'tests may fail, unable to change the current working directory ' + 'to %r: %s', + path, + exc, + exc_info=exc, + ) try: yield os.getcwd() finally: diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index bc6c6427740949..f02be190c57994 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -23,7 +23,7 @@ you're working through IDLE, you can import this test module and call test() with the corresponding argument. """ - +import logging import math import os, sys import operator @@ -5946,8 +5946,9 @@ def tearDownModule(): if C: C.setcontext(ORIGINAL_CONTEXT[C].copy()) P.setcontext(ORIGINAL_CONTEXT[P].copy()) if not C: - warnings.warn('C tests skipped: no module named _decimal.', - UserWarning) + logging.getLogger(__name__).warning( + 'C tests skipped: no module named _decimal.' + ) if not orig_sys_decimal is sys.modules['decimal']: raise TestFailed("Internal error: unbalanced number of changes to " "sys.modules['decimal'].") diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py index 575b2cd0da7056..daa9b421a182bf 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -4,6 +4,7 @@ # Licensed to PSF under a Contributor Agreement. # +import logging import array from binascii import unhexlify import hashlib @@ -113,7 +114,11 @@ def _conditional_import_module(self, module_name): return importlib.import_module(module_name) except ModuleNotFoundError as error: if self._warn_on_extension_import and module_name in builtin_hashes: - warnings.warn(f'Did a C extension fail to compile? {error}') + logging.getLogger(__name__).warning( + 'Did a C extension fail to compile? %s', + error, + exc_info=error, + ) return None def __init__(self, *args, **kwargs): diff --git a/Lib/test/test_interpreters/utils.py b/Lib/test/test_interpreters/utils.py index 3cab76d0f279e0..9f5089b2018956 100644 --- a/Lib/test/test_interpreters/utils.py +++ b/Lib/test/test_interpreters/utils.py @@ -1,3 +1,4 @@ +import logging from collections import namedtuple import contextlib import json @@ -66,8 +67,8 @@ def pack_exception(exc=None): def unpack_exception(packed): try: data = json.loads(packed) - except json.decoder.JSONDecodeError: - warnings.warn('incomplete exception data', RuntimeWarning) + except json.decoder.JSONDecodeError as e: + logging.getLogger(__name__).warning('incomplete exception data', exc_info=e) print(packed if isinstance(packed, str) else packed.decode('utf-8')) return None exc = types.SimpleNamespace(**data) diff --git a/Lib/test/test_pty.py b/Lib/test/test_pty.py index dee94533c74549..c1728f5019d042 100644 --- a/Lib/test/test_pty.py +++ b/Lib/test/test_pty.py @@ -135,8 +135,10 @@ def test_openpty(self): new_dim = tty.tcgetwinsize(pty.STDIN_FILENO) self.assertEqual(new_dim, target_dim, "pty.STDIN_FILENO window size unchanged") - except OSError: - warnings.warn("Failed to set pty.STDIN_FILENO window size.") + except OSError as e: + logging.getLogger(__name__).warning( + "Failed to set pty.STDIN_FILENO window size.", exc_info=e, + ) pass try: From c93348778fbb5d9f4bb88c4e8ceb3d0473d7f7dd Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Sat, 18 Jan 2025 08:52:44 +0000 Subject: [PATCH 03/28] Apply suggestions from code review --- Lib/test/support/os_helper.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Lib/test/support/os_helper.py b/Lib/test/support/os_helper.py index 8b7f52082314d8..3f33c17ee8e381 100644 --- a/Lib/test/support/os_helper.py +++ b/Lib/test/support/os_helper.py @@ -379,7 +379,12 @@ def _waitfor(func, pathname, waitall=False): # Increase the timeout and try again time.sleep(timeout) timeout *= 2 - logging.getLogger(__name__).warning('tests may fail, delete still pending for %s', pathname) + logging.getLogger(__name__).warning( + 'tests may fail, delete still pending for %s', + pathname, + stack_info=True, + stacklevel=4, + ) def _unlink(filename): _waitfor(os.unlink, filename) @@ -494,11 +499,13 @@ def temp_dir(path=None, quiet=False): except OSError as exc: if not quiet: raise - logger.getLogger(__name__).warning( + logging.getLogger(__name__).warning( "tests may fail, unable to create temporary directory %r: %s", path, exc, exc_info=exc, + stack_info=True, + stacklevel=3, ) if dir_created: pid = os.getpid() @@ -536,6 +543,8 @@ def change_cwd(path, quiet=False): path, exc, exc_info=exc, + stack_info=True, + stacklevel=3, ) try: yield os.getcwd() From 9e532fa32aa549ab225cff210065087d0f79726c Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Sat, 18 Jan 2025 09:50:18 +0000 Subject: [PATCH 04/28] test that support logs instead of warns --- Lib/test/test_support.py | 118 ++++++++++++++++++++++++++++++++------- 1 file changed, 98 insertions(+), 20 deletions(-) diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index d900db546ada8d..ba55b38948aa4b 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -1,3 +1,5 @@ +import contextlib +import logging import errno import importlib import io @@ -24,6 +26,73 @@ TESTFN = os_helper.TESTFN +class LogCaptureHandler(logging.StreamHandler): + """ + A logging handler that stores log records and the log text. + + derrived from pytest caplog + + The MIT License (MIT) + + Copyright (c) 2004 Holger Krekel and others + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + """ + + def __init__(self) -> None: + """Create a new log handler.""" + super().__init__(io.StringIO()) + self.records = [] + + def emit(self, record: logging.LogRecord) -> None: + """Keep the log records in a list in addition to the log text.""" + self.records.append(record) + super().emit(record) + + def reset(self): + self.records = [] + self.stream = io.StringIO() + + def clear(self): + self.records.clear() + self.stream = io.StringIO() + + def handleError(self, record): + if logging.raiseExceptions: + # Fail the test if the log message is bad (emit failed). + # The default behavior of logging is to print "Logging error" + # to stderr with the call stack and some extra details. + # pytest wants to make such mistakes visible during testing. + raise + + +@contextlib.contextmanager +def _caplog(): + handler = LogCaptureHandler() + root_logger = logging.getLogger() + root_logger.addHandler(handler) + try: + yield handler + finally: + root_logger.removeHandler(handler) + + class TestSupport(unittest.TestCase): @classmethod def setUpClass(cls): @@ -187,7 +256,7 @@ def test_temp_dir__existing_dir__quiet_true(self): path = os.path.realpath(path) try: - with warnings_helper.check_warnings() as recorder: + with warnings_helper.check_warnings() as recorder, _caplog() as caplog: with os_helper.temp_dir(path, quiet=True) as temp_path: self.assertEqual(path, temp_path) warnings = [str(w.message) for w in recorder.warnings] @@ -196,11 +265,14 @@ def test_temp_dir__existing_dir__quiet_true(self): finally: shutil.rmtree(path) - self.assertEqual(len(warnings), 1, warnings) - warn = warnings[0] - self.assertTrue(warn.startswith(f'tests may fail, unable to create ' - f'temporary directory {path!r}: '), - warn) + self.assertListEqual(warnings, []) + self.assertEqual(len(caplog.records), 1) + record, = caplog.records + self.assertStartsWith( + record.getMessage(), + f'tests may fail, unable to create ' + f'temporary directory {path!r}: ' + ) @support.requires_fork() def test_temp_dir__forked_child(self): @@ -260,35 +332,41 @@ def test_change_cwd__non_existent_dir__quiet_true(self): with os_helper.temp_dir() as parent_dir: bad_dir = os.path.join(parent_dir, 'does_not_exist') - with warnings_helper.check_warnings() as recorder: + with warnings_helper.check_warnings() as recorder, _caplog() as caplog: with os_helper.change_cwd(bad_dir, quiet=True) as new_cwd: self.assertEqual(new_cwd, original_cwd) self.assertEqual(os.getcwd(), new_cwd) warnings = [str(w.message) for w in recorder.warnings] - self.assertEqual(len(warnings), 1, warnings) - warn = warnings[0] - self.assertTrue(warn.startswith(f'tests may fail, unable to change ' - f'the current working directory ' - f'to {bad_dir!r}: '), - warn) + self.assertListEqual(warnings, []) + self.assertEqual(len(caplog.records), 1) + record, = caplog.records + self.assertStartsWith( + record.getMessage(), + f'tests may fail, unable to change ' + f'the current working directory ' + f'to {bad_dir!r}: ' + ) # Tests for change_cwd() def test_change_cwd__chdir_warning(self): """Check the warning message when os.chdir() fails.""" path = TESTFN + '_does_not_exist' - with warnings_helper.check_warnings() as recorder: + with warnings_helper.check_warnings() as recorder, _caplog() as caplog: with os_helper.change_cwd(path=path, quiet=True): pass messages = [str(w.message) for w in recorder.warnings] - self.assertEqual(len(messages), 1, messages) - msg = messages[0] - self.assertTrue(msg.startswith(f'tests may fail, unable to change ' - f'the current working directory ' - f'to {path!r}: '), - msg) + self.assertListEqual(messages, []) + self.assertEqual(len(caplog.records), 1) + record, = caplog.records + self.assertStartsWith( + record.getMessage(), + f'tests may fail, unable to change ' + f'the current working directory ' + f'to {path!r}: ', + ) # Tests for temp_cwd() From 104f4275b3e5892a2843c000a8b66019ca2daa7f Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Sat, 18 Jan 2025 23:09:07 +0000 Subject: [PATCH 05/28] Update test_hashlib.py --- Lib/test/test_hashlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py index daa9b421a182bf..67463328dec259 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -4,13 +4,13 @@ # Licensed to PSF under a Contributor Agreement. # -import logging import array from binascii import unhexlify import hashlib import importlib import io import itertools +import logging import os import sys import sysconfig From b5fc0dfb071c6b52d5f27569cfc36260318d2238 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Mon, 20 Jan 2025 12:42:50 +0000 Subject: [PATCH 06/28] sort logging import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/support/__init__.py | 2 +- Lib/test/support/os_helper.py | 2 +- Lib/test/test_decimal.py | 1 + Lib/test/test_interpreters/utils.py | 2 +- Lib/test/test_socket.py | 4 ++-- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 5c34eab80f1110..47212e271916b0 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -6,6 +6,7 @@ import contextlib import functools import inspect +import logging import _opcode import os import re @@ -17,7 +18,6 @@ import types import unittest import warnings -import logging __all__ = [ diff --git a/Lib/test/support/os_helper.py b/Lib/test/support/os_helper.py index 3f33c17ee8e381..0e1efc96e7b019 100644 --- a/Lib/test/support/os_helper.py +++ b/Lib/test/support/os_helper.py @@ -1,7 +1,7 @@ -import logging import collections.abc import contextlib import errno +import logging import os import re import stat diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 86ceddab8b050c..c84975d32ef2d6 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -23,6 +23,7 @@ you're working through IDLE, you can import this test module and call test() with the corresponding argument. """ + import logging import math import os, sys diff --git a/Lib/test/test_interpreters/utils.py b/Lib/test/test_interpreters/utils.py index 9f5089b2018956..fc4ad662e03b66 100644 --- a/Lib/test/test_interpreters/utils.py +++ b/Lib/test/test_interpreters/utils.py @@ -1,7 +1,7 @@ -import logging from collections import namedtuple import contextlib import json +import logging import os import os.path #import select diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 7233847f37bf39..39d34633e7d064 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -1,8 +1,7 @@ import unittest -import warnings from test import support from test.support import ( - is_apple, os_helper, refleak_helper, socket_helper, threading_helper, + is_apple, os_helper, refleak_helper, socket_helper, threading_helper ) import _thread as thread import array @@ -28,6 +27,7 @@ import threading import time import traceback +import warnings from weakref import proxy try: import multiprocessing From 6bdaad49a15b7c3b387cb75c7cf7145b1e4ca0ea Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Mon, 20 Jan 2025 13:15:50 +0000 Subject: [PATCH 07/28] Update Lib/test/test_support.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_support.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index ba55b38948aa4b..454a7f2a76093b 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -30,7 +30,7 @@ class LogCaptureHandler(logging.StreamHandler): """ A logging handler that stores log records and the log text. - derrived from pytest caplog + Derived from pytest caplog. The MIT License (MIT) From 3473aab264bfb490c876b1834715cc2454a5ec79 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Mon, 20 Jan 2025 13:16:26 +0000 Subject: [PATCH 08/28] Update Lib/test/test_support.py --- Lib/test/test_support.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index 454a7f2a76093b..d078569f402ac8 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -60,7 +60,7 @@ def __init__(self) -> None: super().__init__(io.StringIO()) self.records = [] - def emit(self, record: logging.LogRecord) -> None: + def emit(self, record) -> None: """Keep the log records in a list in addition to the log text.""" self.records.append(record) super().emit(record) From b49e3f90b886adb0d707806b408ec17cfa9d5edd Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Mon, 20 Jan 2025 13:16:42 +0000 Subject: [PATCH 09/28] Update Lib/test/test_support.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_support.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index d078569f402ac8..8daff0d111f553 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -55,7 +55,7 @@ class LogCaptureHandler(logging.StreamHandler): SOFTWARE. """ - def __init__(self) -> None: + def __init__(self): """Create a new log handler.""" super().__init__(io.StringIO()) self.records = [] From b92cbec6ec4617822b388d6f1c367a72bfbd72c4 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Mon, 20 Jan 2025 13:18:47 +0000 Subject: [PATCH 10/28] Update Lib/test/test_support.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> From fbb99a3e96a2f7c7aef6b3fbb3c13c5c3bc8b738 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Mon, 20 Jan 2025 13:19:19 +0000 Subject: [PATCH 11/28] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_socket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 39d34633e7d064..651adc012ccdc3 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -211,7 +211,7 @@ def downgrade_malformed_data_warning(): # and fix it properly. warnings.filterwarnings( action="always", - message=r"received malformed or improperly-truncated ancillary data", + message="received malformed or improperly-truncated ancillary data", category=RuntimeWarning, ) yield From f6909e8fabdc8410e616324fbe67a29e7f410efc Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 21 Jan 2025 09:37:51 +0000 Subject: [PATCH 12/28] move logging into alphabetical position --- Lib/test/test_support.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index 8daff0d111f553..6546ce7d0f25da 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -1,8 +1,8 @@ import contextlib -import logging import errno import importlib import io +import logging import os import shutil import signal From 3b895f2cc255b5ab180a5862d1e7f7821ebb9546 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 21 Jan 2025 09:38:21 +0000 Subject: [PATCH 13/28] do the hanging indent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_socket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 651adc012ccdc3..c6d0f3f018b43c 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -3967,7 +3967,7 @@ def checkTruncatedArray(self, ancbuf, maxdata, mindata=0): # valid. with downgrade_malformed_data_warning(): # TODO: gh-110012 msg, ancdata, flags, addr = self.doRecvmsg(self.serv_sock, - len(MSG), ancbuf) + len(MSG), ancbuf) self.assertEqual(msg, MSG) self.checkRecvmsgAddress(addr, self.cli_addr) self.checkFlags(flags, eor=True, checkset=socket.MSG_CTRUNC) From 8e17c8a3eccc9c15c409df4565342941c8d82ef8 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 21 Jan 2025 09:39:32 +0000 Subject: [PATCH 14/28] use indexing rather than unpacking one item MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_support.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index 6546ce7d0f25da..5c89d0cf6c6807 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -267,7 +267,7 @@ def test_temp_dir__existing_dir__quiet_true(self): self.assertListEqual(warnings, []) self.assertEqual(len(caplog.records), 1) - record, = caplog.records + record = caplog.records[0] self.assertStartsWith( record.getMessage(), f'tests may fail, unable to create ' @@ -340,7 +340,7 @@ def test_change_cwd__non_existent_dir__quiet_true(self): self.assertListEqual(warnings, []) self.assertEqual(len(caplog.records), 1) - record, = caplog.records + record = caplog.records[0] self.assertStartsWith( record.getMessage(), f'tests may fail, unable to change ' @@ -360,7 +360,7 @@ def test_change_cwd__chdir_warning(self): self.assertListEqual(messages, []) self.assertEqual(len(caplog.records), 1) - record, = caplog.records + record = caplog.records[0] self.assertStartsWith( record.getMessage(), f'tests may fail, unable to change ' From 1af4d6f39c4f23091db0317be6a0ead85115d3de Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Sat, 25 Jan 2025 10:02:28 +0000 Subject: [PATCH 15/28] prevent SyntaxError/SyntaxWarning in test_fstring --- Lib/test/test_fstring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py index 1d96b7a2c2459b..ddc50c0427b2b2 100644 --- a/Lib/test/test_fstring.py +++ b/Lib/test/test_fstring.py @@ -1651,7 +1651,7 @@ def __repr__(self): def test_debug_expressions_are_raw_strings(self): - self.assertEqual(f'{b"\N{OX}"=}', 'b"\\N{OX}"=b\'\\\\N{OX}\'') + self.assertEqual(f'{b"\\N{OX}"=}', 'b"\\N{OX}"=b\'\\\\N{OX}\'') self.assertEqual(f'{r"\xff"=}', 'r"\\xff"=\'\\\\xff\'') self.assertEqual(f'{r"\n"=}', 'r"\\n"=\'\\\\n\'') self.assertEqual(f"{'\''=}", "'\\''=\"'\"") From 2f29d807e4767b3a21a22a209fefcf9357008edc Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 19 Mar 2025 09:26:00 +0000 Subject: [PATCH 16/28] fix SyntaxWarning in test_join_windows.py --- Lib/test/test_pathlib/test_join_windows.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_pathlib/test_join_windows.py b/Lib/test/test_pathlib/test_join_windows.py index 783248d1697eef..5969610fe545a9 100644 --- a/Lib/test/test_pathlib/test_join_windows.py +++ b/Lib/test/test_pathlib/test_join_windows.py @@ -54,7 +54,7 @@ def test_div(self): self.assertEqual(p / 'x/y', P(r'C:/a/b\x/y')) self.assertEqual(p / 'x' / 'y', P(r'C:/a/b\x\y')) self.assertEqual(p / '/x/y', P('C:/x/y')) - self.assertEqual(p / '/x' / 'y', P('C:/x\y')) + self.assertEqual(p / '/x' / 'y', P(r'C:/x\y')) # Joining with a different drive => the first path is ignored, even # if the second path is relative. self.assertEqual(p / 'D:x/y', P('D:x/y')) From 19dc865e15617505025631a9f2b73076c433dcdc Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 19 Mar 2025 09:42:52 +0000 Subject: [PATCH 17/28] fix 'sys.path_hooks is empty' warning in test_permission_error_cwd --- Lib/test/test_importlib/import_/test_path.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_importlib/import_/test_path.py b/Lib/test/test_importlib/import_/test_path.py index 51ff6115e1281e..79e0bdca94c15c 100644 --- a/Lib/test/test_importlib/import_/test_path.py +++ b/Lib/test/test_importlib/import_/test_path.py @@ -158,11 +158,15 @@ def test_deleted_cwd(self): def test_permission_error_cwd(self): # gh-115911: Test that an unreadable CWD does not break imports, in # particular during early stages of interpreter startup. + + def noop_hook(*args): + raise ImportError + with ( os_helper.temp_dir() as new_dir, os_helper.save_mode(new_dir), os_helper.change_cwd(new_dir), - util.import_state(path=['']), + util.import_state(path=[''], path_hooks=[noop_hook]), ): # chmod() is done here (inside the 'with' block) because the order # of teardown operations cannot be the reverse of setup order. See From 12b6c646e880fecfcc7767092c1ab4f830029b02 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 19 Mar 2025 10:01:01 +0000 Subject: [PATCH 18/28] fix meta invalid escape in test_invalid_escape_locations_with_offset --- Lib/test/test_string_literals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_string_literals.py b/Lib/test/test_string_literals.py index 9d57233eb0882a..161bde3eef3440 100644 --- a/Lib/test/test_string_literals.py +++ b/Lib/test/test_string_literals.py @@ -177,7 +177,7 @@ def test_eval_str_invalid_octal_escape(self): def test_invalid_escape_locations_with_offset(self): with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always', category=SyntaxWarning) - eval("\"'''''''''''''''''''''invalid\ Escape\"") + eval("\"'''''''''''''''''''''invalid\\ Escape\"") self.assertEqual(len(w), 1) self.assertEqual(str(w[0].message), r'"\ " is an invalid escape sequence. Such sequences ' From f8e4f56954ced1d5f61de751d1a46a57949068fd Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 19 Mar 2025 10:10:45 +0000 Subject: [PATCH 19/28] fix test_string_literals SyntaxWarning --- Lib/test/test_string_literals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_string_literals.py b/Lib/test/test_string_literals.py index 161bde3eef3440..1800b17e1df3d7 100644 --- a/Lib/test/test_string_literals.py +++ b/Lib/test/test_string_literals.py @@ -188,7 +188,7 @@ def test_invalid_escape_locations_with_offset(self): with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always', category=SyntaxWarning) - eval("\"''Incorrect \ logic?\"") + eval("\"''Incorrect \\ logic?\"") self.assertEqual(len(w), 1) self.assertEqual(str(w[0].message), r'"\ " is an invalid escape sequence. Such sequences ' From b9ecf976d54d55f1f1554e59a73b7849db8bfa29 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 19 Mar 2025 10:11:38 +0000 Subject: [PATCH 20/28] exclude test_grammar.py from test_write_filtered_python_package --- Lib/test/test_zipfile/test_core.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_zipfile/test_core.py b/Lib/test/test_zipfile/test_core.py index 44e8190ac6710b..016464083f449b 100644 --- a/Lib/test/test_zipfile/test_core.py +++ b/Lib/test/test_zipfile/test_core.py @@ -1424,8 +1424,10 @@ def test_write_filtered_python_package(self): # then check that the filter works on individual files def filter(path): - return not os.path.basename(path).startswith("bad") - with captured_stdout() as reportSIO, self.assertWarns(UserWarning): + basename = os.path.basename(path) + return not (basename.startswith("bad") or basename != "test_grammar.py") + + with captured_stdout() as reportSIO: zipfp.writepy(packagedir, filterfunc=filter) reportStr = reportSIO.getvalue() if reportStr: From 0305cfea7ebde636158035fc01b5d7a40f717923 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 19 Mar 2025 10:25:34 +0000 Subject: [PATCH 21/28] fix ResourceWarning when writing a unwritable gzipfile --- Lib/gzip.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Lib/gzip.py b/Lib/gzip.py index c9c088783bea65..ab7ee459cc459a 100644 --- a/Lib/gzip.py +++ b/Lib/gzip.py @@ -245,8 +245,13 @@ def __init__(self, filename=None, mode=None, self.fileobj = fileobj - if self.mode == WRITE: - self._write_gzip_header(compresslevel) + try: + if self.mode == WRITE: + self._write_gzip_header(compresslevel) + except BaseException: + # Avoid a ResourceWarning if the write fails, eg read-only file or KI + self.fileobj = None + raise @property def mtime(self): From 6dab4285ee92699861f07eb469f65411435e5a96 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 19 Mar 2025 10:35:35 +0000 Subject: [PATCH 22/28] fix ResourceWarnings in test_asyncio.test_events --- Lib/test/test_asyncio/test_events.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py index ead22999bfeac1..feecdd7b0601f3 100644 --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -1,6 +1,7 @@ """Tests for events.py.""" import concurrent.futures +import contextlib import functools import io import multiprocessing @@ -55,9 +56,9 @@ def _test_get_event_loop_new_process__sub_proc(): async def doit(): return 'hello' - loop = asyncio.new_event_loop() - asyncio._set_event_loop(loop) - return loop.run_until_complete(doit()) + with contextlib.closing(asyncio.new_event_loop()) as loop: + asyncio._set_event_loop(loop) + return loop.run_until_complete(doit()) class CoroLike: @@ -3005,13 +3006,13 @@ async def main(): def test_get_running_loop_already_running(self): async def main(): running_loop = asyncio.get_running_loop() - loop = asyncio.new_event_loop() - try: - loop.run_forever() - except RuntimeError: - pass - else: - self.fail("RuntimeError not raised") + with contextlib.closing(asyncio.new_event_loop()) as loop: + try: + loop.run_forever() + except RuntimeError: + pass + else: + self.fail("RuntimeError not raised") self.assertIs(asyncio.get_running_loop(), running_loop) From 6e83d44dcc8c38023242f52f3dc537bf154267ab Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 19 Mar 2025 10:39:24 +0000 Subject: [PATCH 23/28] prevent another ResourceWarning in GzipFile due to an unclosed myfileobj --- Lib/gzip.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Lib/gzip.py b/Lib/gzip.py index ab7ee459cc459a..37fd7caeac0029 100644 --- a/Lib/gzip.py +++ b/Lib/gzip.py @@ -250,6 +250,11 @@ def __init__(self, filename=None, mode=None, self._write_gzip_header(compresslevel) except BaseException: # Avoid a ResourceWarning if the write fails, eg read-only file or KI + try: + if self.myfileobj is not None: + self.myfileobj.close() + finally: + self.fileobj = None self.fileobj = None raise From aa35271ccfaa54c7c6c894c5288ca97ff6a245c3 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 19 Mar 2025 11:08:00 +0000 Subject: [PATCH 24/28] extract UNDERSCORE_LITERALS from test_grammar --- Lib/test/support/numbers.py | 81 ++++++++++++++++++++++++++++++++++ Lib/test/test_complex.py | 6 ++- Lib/test/test_float.py | 6 ++- Lib/test/test_grammar.py | 86 ++----------------------------------- Lib/test/test_int.py | 6 ++- Lib/test/test_tokenize.py | 7 ++- 6 files changed, 102 insertions(+), 90 deletions(-) create mode 100644 Lib/test/support/numbers.py diff --git a/Lib/test/support/numbers.py b/Lib/test/support/numbers.py new file mode 100644 index 00000000000000..42bff0e1862e24 --- /dev/null +++ b/Lib/test/support/numbers.py @@ -0,0 +1,81 @@ +# These are shared with test_tokenize and other test modules. +# +# Note: since several test cases filter out floats by looking for "e" and ".", +# don't add hexadecimal literals that contain "e" or "E". +VALID_UNDERSCORE_LITERALS = [ + '0_0_0', + '4_2', + '1_0000_0000', + '0b1001_0100', + '0xffff_ffff', + '0o5_7_7', + '1_00_00.5', + '1_00_00.5e5', + '1_00_00e5_1', + '1e1_0', + '.1_4', + '.1_4e1', + '0b_0', + '0x_f', + '0o_5', + '1_00_00j', + '1_00_00.5j', + '1_00_00e5_1j', + '.1_4j', + '(1_2.5+3_3j)', + '(.5_6j)', +] +INVALID_UNDERSCORE_LITERALS = [ + # Trailing underscores: + '0_', + '42_', + '1.4j_', + '0x_', + '0b1_', + '0xf_', + '0o5_', + '0 if 1_Else 1', + # Underscores in the base selector: + '0_b0', + '0_xf', + '0_o5', + # Old-style octal, still disallowed: + '0_7', + '09_99', + # Multiple consecutive underscores: + '4_______2', + '0.1__4', + '0.1__4j', + '0b1001__0100', + '0xffff__ffff', + '0x___', + '0o5__77', + '1e1__0', + '1e1__0j', + # Underscore right before a dot: + '1_.4', + '1_.4j', + # Underscore right after a dot: + '1._4', + '1._4j', + '._5', + '._5j', + # Underscore right after a sign: + '1.0e+_1', + '1.0e+_1j', + # Underscore right before j: + '1.4_j', + '1.4e5_j', + # Underscore right before e: + '1_e1', + '1.4_e1', + '1.4_e1j', + # Underscore right after e: + '1e_1', + '1.4e_1', + '1.4e_1j', + # Complex cases with parens: + '(1+1.5_j_)', + '(1+1.5_j)', +] + diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index fd002fb00ac338..0c7e7341f13d4e 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -2,8 +2,10 @@ import sys from test import support from test.support.testcase import ComplexesAreIdenticalMixin -from test.test_grammar import (VALID_UNDERSCORE_LITERALS, - INVALID_UNDERSCORE_LITERALS) +from test.support.numbers import ( + VALID_UNDERSCORE_LITERALS, + INVALID_UNDERSCORE_LITERALS, +) from random import random from math import isnan, copysign diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py index ee8e28bf75b5a8..237d7b5d35edd7 100644 --- a/Lib/test/test_float.py +++ b/Lib/test/test_float.py @@ -9,8 +9,10 @@ from test import support from test.support.testcase import FloatsAreIdenticalMixin -from test.test_grammar import (VALID_UNDERSCORE_LITERALS, - INVALID_UNDERSCORE_LITERALS) +from test.support.numbers import ( + VALID_UNDERSCORE_LITERALS, + INVALID_UNDERSCORE_LITERALS, +) from math import isinf, isnan, copysign, ldexp import math diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py index bde0b45b3ceb93..faf458c04d3b6c 100644 --- a/Lib/test/test_grammar.py +++ b/Lib/test/test_grammar.py @@ -17,88 +17,10 @@ import typing from test.typinganndata import ann_module2 import test - -# These are shared with test_tokenize and other test modules. -# -# Note: since several test cases filter out floats by looking for "e" and ".", -# don't add hexadecimal literals that contain "e" or "E". -VALID_UNDERSCORE_LITERALS = [ - '0_0_0', - '4_2', - '1_0000_0000', - '0b1001_0100', - '0xffff_ffff', - '0o5_7_7', - '1_00_00.5', - '1_00_00.5e5', - '1_00_00e5_1', - '1e1_0', - '.1_4', - '.1_4e1', - '0b_0', - '0x_f', - '0o_5', - '1_00_00j', - '1_00_00.5j', - '1_00_00e5_1j', - '.1_4j', - '(1_2.5+3_3j)', - '(.5_6j)', -] -INVALID_UNDERSCORE_LITERALS = [ - # Trailing underscores: - '0_', - '42_', - '1.4j_', - '0x_', - '0b1_', - '0xf_', - '0o5_', - '0 if 1_Else 1', - # Underscores in the base selector: - '0_b0', - '0_xf', - '0_o5', - # Old-style octal, still disallowed: - '0_7', - '09_99', - # Multiple consecutive underscores: - '4_______2', - '0.1__4', - '0.1__4j', - '0b1001__0100', - '0xffff__ffff', - '0x___', - '0o5__77', - '1e1__0', - '1e1__0j', - # Underscore right before a dot: - '1_.4', - '1_.4j', - # Underscore right after a dot: - '1._4', - '1._4j', - '._5', - '._5j', - # Underscore right after a sign: - '1.0e+_1', - '1.0e+_1j', - # Underscore right before j: - '1.4_j', - '1.4e5_j', - # Underscore right before e: - '1_e1', - '1.4_e1', - '1.4_e1j', - # Underscore right after e: - '1e_1', - '1.4e_1', - '1.4e_1j', - # Complex cases with parens: - '(1+1.5_j_)', - '(1+1.5_j)', -] - +from test.support.numbers import ( + VALID_UNDERSCORE_LITERALS, + INVALID_UNDERSCORE_LITERALS, +) class TokenTests(unittest.TestCase): diff --git a/Lib/test/test_int.py b/Lib/test/test_int.py index 8870d7aa5d663d..d1bf847a7a3e9f 100644 --- a/Lib/test/test_int.py +++ b/Lib/test/test_int.py @@ -3,8 +3,10 @@ import unittest from unittest import mock from test import support -from test.test_grammar import (VALID_UNDERSCORE_LITERALS, - INVALID_UNDERSCORE_LITERALS) +from test.support.numbers import ( + VALID_UNDERSCORE_LITERALS, + INVALID_UNDERSCORE_LITERALS, +) try: import _pylong diff --git a/Lib/test/test_tokenize.py b/Lib/test/test_tokenize.py index 5fa4e0d922ed08..849b8fe5c24fef 100644 --- a/Lib/test/test_tokenize.py +++ b/Lib/test/test_tokenize.py @@ -7,10 +7,13 @@ from textwrap import dedent from unittest import TestCase, mock from test import support -from test.test_grammar import (VALID_UNDERSCORE_LITERALS, - INVALID_UNDERSCORE_LITERALS) from test.support import os_helper from test.support.script_helper import run_test_script, make_script, run_python_until_end +from test.support.numbers import ( + VALID_UNDERSCORE_LITERALS, + INVALID_UNDERSCORE_LITERALS, +) + # Converts a source string into a list of textual representation # of the tokens such as: From 6c918b39b460ced1cab8261e89d6251bcd5b5055 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 19 Mar 2025 11:10:52 +0000 Subject: [PATCH 25/28] pre-commit --- Lib/test/support/numbers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/support/numbers.py b/Lib/test/support/numbers.py index 42bff0e1862e24..d5dbb41acebc38 100644 --- a/Lib/test/support/numbers.py +++ b/Lib/test/support/numbers.py @@ -78,4 +78,3 @@ '(1+1.5_j_)', '(1+1.5_j)', ] - From 3d68f6ecf2b7e938ad58f62809cc55c13a8ebb38 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Sun, 23 Mar 2025 08:31:40 +0000 Subject: [PATCH 26/28] mimimize LogCaptureHandler, and reduce pytest citation --- Lib/test/test_support.py | 46 ++-------------------------------------- 1 file changed, 2 insertions(+), 44 deletions(-) diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index 7f731c97f0317d..efe6b77a7faa18 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -27,59 +27,17 @@ class LogCaptureHandler(logging.StreamHandler): - """ - A logging handler that stores log records and the log text. - - Derived from pytest caplog. - - The MIT License (MIT) - - Copyright (c) 2004 Holger Krekel and others - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies - of the Software, and to permit persons to whom the Software is furnished to do - so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - """ - + # Inspired by pytest's caplog def __init__(self): - """Create a new log handler.""" super().__init__(io.StringIO()) self.records = [] def emit(self, record) -> None: - """Keep the log records in a list in addition to the log text.""" self.records.append(record) super().emit(record) - def reset(self): - self.records = [] - self.stream = io.StringIO() - - def clear(self): - self.records.clear() - self.stream = io.StringIO() - def handleError(self, record): - if logging.raiseExceptions: - # Fail the test if the log message is bad (emit failed). - # The default behavior of logging is to print "Logging error" - # to stderr with the call stack and some extra details. - # pytest wants to make such mistakes visible during testing. - raise + raise @contextlib.contextmanager From 774dd988ee43124928f1a6b7cc137d85afa4f580 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Mon, 24 Mar 2025 08:21:01 +0000 Subject: [PATCH 27/28] Discard changes to Lib/test/test_zipfile/test_core.py --- Lib/test/test_zipfile/test_core.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_zipfile/test_core.py b/Lib/test/test_zipfile/test_core.py index 016464083f449b..44e8190ac6710b 100644 --- a/Lib/test/test_zipfile/test_core.py +++ b/Lib/test/test_zipfile/test_core.py @@ -1424,10 +1424,8 @@ def test_write_filtered_python_package(self): # then check that the filter works on individual files def filter(path): - basename = os.path.basename(path) - return not (basename.startswith("bad") or basename != "test_grammar.py") - - with captured_stdout() as reportSIO: + return not os.path.basename(path).startswith("bad") + with captured_stdout() as reportSIO, self.assertWarns(UserWarning): zipfp.writepy(packagedir, filterfunc=filter) reportStr = reportSIO.getvalue() if reportStr: From 78ffa2a9b0e5128bd2f5e384b64d34b6174fdd0a Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 25 Mar 2025 07:55:58 +0000 Subject: [PATCH 28/28] gh-131707: fix unawaited coroutine warning in test_coroutines.CoroutineTest.test_17 --- Lib/test/test_coroutines.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_coroutines.py b/Lib/test/test_coroutines.py index d78eaaca2796a6..deeaa724e79544 100644 --- a/Lib/test/test_coroutines.py +++ b/Lib/test/test_coroutines.py @@ -1199,8 +1199,8 @@ async def __anext__(self): def __aiter__(self): return self - anext_awaitable = anext(A(), "a").__await__() - self.assertRaises(TypeError, anext_awaitable.close, 1) + with contextlib.closing(anext(A(), "a").__await__()) as anext_awaitable: + self.assertRaises(TypeError, anext_awaitable.close, 1) def test_with_1(self): class Manager: