From b56082a9809fb4fc23c4e0bc8ae3967564471e15 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 31 Aug 2025 10:34:57 +0300 Subject: [PATCH 01/16] Update `test_keywordonlyarg.py` from 3.13.7 --- Lib/test/test_keywordonlyarg.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_keywordonlyarg.py b/Lib/test/test_keywordonlyarg.py index 9e9c6651dd..e41e7c051f 100644 --- a/Lib/test/test_keywordonlyarg.py +++ b/Lib/test/test_keywordonlyarg.py @@ -58,8 +58,7 @@ def testSyntaxForManyArguments(self): fundef = "def f(*, %s):\n pass\n" % ', '.join('i%d' % i for i in range(300)) compile(fundef, "", "single") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def testTooManyPositionalErrorMessage(self): def f(a, b=None, *, c=None): pass @@ -158,8 +157,7 @@ def test_issue13343(self): # used to fail with a SystemError. lambda *, k1=unittest: None - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_mangling(self): class X: def f(self, *, __a=42): @@ -174,7 +172,7 @@ def f(v=a, x=b, *, y=c, z=d): pass self.assertEqual(str(err.exception), "name 'b' is not defined") with self.assertRaises(NameError) as err: - f = lambda v=a, x=b, *, y=c, z=d: None + g = lambda v=a, x=b, *, y=c, z=d: None self.assertEqual(str(err.exception), "name 'b' is not defined") From 88b12bafc9696a36cc5a152c6ce9a23dd3a03f31 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 31 Aug 2025 10:35:23 +0300 Subject: [PATCH 02/16] Update `test_kqueue.py` from 3.13.7 --- Lib/test/test_kqueue.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Lib/test/test_kqueue.py b/Lib/test/test_kqueue.py index 998fd9d464..e94edcbc10 100644 --- a/Lib/test/test_kqueue.py +++ b/Lib/test/test_kqueue.py @@ -5,6 +5,7 @@ import os import select import socket +from test import support import time import unittest @@ -256,6 +257,23 @@ def test_fd_non_inheritable(self): self.addCleanup(kqueue.close) self.assertEqual(os.get_inheritable(kqueue.fileno()), False) + @support.requires_fork() + def test_fork(self): + # gh-110395: kqueue objects must be closed after fork + kqueue = select.kqueue() + if (pid := os.fork()) == 0: + try: + self.assertTrue(kqueue.closed) + with self.assertRaisesRegex(ValueError, "closed kqueue"): + kqueue.fileno() + except: + os._exit(1) + finally: + os._exit(0) + else: + support.wait_process(pid, exitcode=0) + self.assertFalse(kqueue.closed) # child done, we're still open. + if __name__ == "__main__": unittest.main() From 37324b443b215fce9154331538f8c2d7d9fb43dd Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 31 Aug 2025 10:54:43 +0300 Subject: [PATCH 03/16] Update `test_eof.py` from 3.13.7 --- Lib/test/test_eof.py | 188 +++++++++++++++++++++++++++++++------------ 1 file changed, 138 insertions(+), 50 deletions(-) diff --git a/Lib/test/test_eof.py b/Lib/test/test_eof.py index 082103b338..32efe15b5d 100644 --- a/Lib/test/test_eof.py +++ b/Lib/test/test_eof.py @@ -1,87 +1,175 @@ """test script for a few new invalid token catches""" import sys -from test import support +from codecs import BOM_UTF8 +from test.support import force_not_colorized from test.support import os_helper from test.support import script_helper from test.support import warnings_helper import unittest class EOFTestCase(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_EOF_single_quote(self): expect = "unterminated string literal (detected at line 1) (, line 1)" for quote in ("'", "\""): - try: + with self.assertRaises(SyntaxError) as cm: eval(f"""{quote}this is a test\ """) - except SyntaxError as msg: - self.assertEqual(str(msg), expect) - self.assertEqual(msg.offset, 1) - else: - raise support.TestFailed - - # TODO: RUSTPYTHON - @unittest.expectedFailure + self.assertEqual(str(cm.exception), expect) + self.assertEqual(cm.exception.offset, 1) + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_EOFS(self): - expect = ("unterminated triple-quoted string literal (detected at line 1) (, line 1)") - try: - eval("""'''this is a test""") - except SyntaxError as msg: - self.assertEqual(str(msg), expect) - self.assertEqual(msg.offset, 1) - else: - raise support.TestFailed - - # TODO: RUSTPYTHON - @unittest.expectedFailure + expect = ("unterminated triple-quoted string literal (detected at line 3) (, line 1)") + with self.assertRaises(SyntaxError) as cm: + eval("""ä = '''thîs is \na \ntest""") + self.assertEqual(str(cm.exception), expect) + self.assertEqual(cm.exception.text, "ä = '''thîs is ") + self.assertEqual(cm.exception.offset, 5) + + with self.assertRaises(SyntaxError) as cm: + eval("""ä = '''thîs is \na \ntest""".encode()) + self.assertEqual(str(cm.exception), expect) + self.assertEqual(cm.exception.text, "ä = '''thîs is ") + self.assertEqual(cm.exception.offset, 5) + + with self.assertRaises(SyntaxError) as cm: + eval(BOM_UTF8 + """ä = '''thîs is \na \ntest""".encode()) + self.assertEqual(str(cm.exception), expect) + self.assertEqual(cm.exception.text, "ä = '''thîs is ") + self.assertEqual(cm.exception.offset, 5) + + with self.assertRaises(SyntaxError) as cm: + eval("""# coding: latin1\nä = '''thîs is \na \ntest""".encode('latin1')) + self.assertEqual(str(cm.exception), "unterminated triple-quoted string literal (detected at line 4) (, line 2)") + self.assertEqual(cm.exception.text, "ä = '''thîs is ") + self.assertEqual(cm.exception.offset, 5) + + @force_not_colorized def test_EOFS_with_file(self): expect = ("(, line 1)") with os_helper.temp_dir() as temp_dir: - file_name = script_helper.make_script(temp_dir, 'foo', """'''this is \na \ntest""") - rc, out, err = script_helper.assert_python_failure(file_name) - self.assertIn(b'unterminated triple-quoted string literal (detected at line 3)', err) + file_name = script_helper.make_script(temp_dir, 'foo', + """ä = '''thîs is \na \ntest""") + rc, out, err = script_helper.assert_python_failure('-X', 'utf8', file_name) + err = err.decode().splitlines() + self.assertEqual(err[-3:], [ + " ä = '''thîs is ", + ' ^', + 'SyntaxError: unterminated triple-quoted string literal (detected at line 3)']) + + file_name = script_helper.make_script(temp_dir, 'foo', + """ä = '''thîs is \na \ntest""".encode()) + rc, out, err = script_helper.assert_python_failure('-X', 'utf8', file_name) + err = err.decode().splitlines() + self.assertEqual(err[-3:], [ + " ä = '''thîs is ", + ' ^', + 'SyntaxError: unterminated triple-quoted string literal (detected at line 3)']) + + file_name = script_helper.make_script(temp_dir, 'foo', + BOM_UTF8 + """ä = '''thîs is \na \ntest""".encode()) + rc, out, err = script_helper.assert_python_failure('-X', 'utf8', file_name) + err = err.decode().splitlines() + self.assertEqual(err[-3:], [ + " ä = '''thîs is ", + ' ^', + 'SyntaxError: unterminated triple-quoted string literal (detected at line 3)']) + + file_name = script_helper.make_script(temp_dir, 'foo', + """# coding: latin1\nä = '''thîs is \na \ntest""".encode('latin1')) + rc, out, err = script_helper.assert_python_failure('-X', 'utf8', file_name) + err = err.decode().splitlines() + self.assertEqual(err[-3:], [ + " ä = '''thîs is ", + ' ^', + 'SyntaxError: unterminated triple-quoted string literal (detected at line 4)']) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @warnings_helper.ignore_warnings(category=SyntaxWarning) def test_eof_with_line_continuation(self): expect = "unexpected EOF while parsing (, line 1)" - try: + with self.assertRaises(SyntaxError) as cm: compile('"\\Xhh" \\', '', 'exec') - except SyntaxError as msg: - self.assertEqual(str(msg), expect) - else: - raise support.TestFailed + self.assertEqual(str(cm.exception), expect) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_line_continuation_EOF(self): """A continuation at the end of input must be an error; bpo2180.""" expect = 'unexpected EOF while parsing (, line 1)' - with self.assertRaises(SyntaxError) as excinfo: - exec('x = 5\\') - self.assertEqual(str(excinfo.exception), expect) - with self.assertRaises(SyntaxError) as excinfo: + with self.assertRaises(SyntaxError) as cm: + exec('ä = 5\\') + self.assertEqual(str(cm.exception), expect) + self.assertEqual(cm.exception.text, 'ä = 5\\\n') + self.assertEqual(cm.exception.offset, 7) + + with self.assertRaises(SyntaxError) as cm: + exec('ä = 5\\'.encode()) + self.assertEqual(str(cm.exception), expect) + self.assertEqual(cm.exception.text, 'ä = 5\\\n') + self.assertEqual(cm.exception.offset, 7) + + with self.assertRaises(SyntaxError) as cm: + exec('# coding:latin1\nä = 5\\'.encode('latin1')) + self.assertEqual(str(cm.exception), + 'unexpected EOF while parsing (, line 2)') + self.assertEqual(cm.exception.text, 'ä = 5\\\n') + self.assertEqual(cm.exception.offset, 7) + + with self.assertRaises(SyntaxError) as cm: + exec(BOM_UTF8 + 'ä = 5\\'.encode()) + self.assertEqual(str(cm.exception), expect) + self.assertEqual(cm.exception.text, 'ä = 5\\\n') + self.assertEqual(cm.exception.offset, 7) + + with self.assertRaises(SyntaxError) as cm: exec('\\') - self.assertEqual(str(excinfo.exception), expect) + self.assertEqual(str(cm.exception), expect) @unittest.skipIf(not sys.executable, "sys.executable required") + @force_not_colorized def test_line_continuation_EOF_from_file_bpo2180(self): """Ensure tok_nextc() does not add too many ending newlines.""" with os_helper.temp_dir() as temp_dir: file_name = script_helper.make_script(temp_dir, 'foo', '\\') - rc, out, err = script_helper.assert_python_failure(file_name) - self.assertIn(b'unexpected EOF while parsing', err) - self.assertIn(b'line 1', err) - self.assertIn(b'\\', err) - - file_name = script_helper.make_script(temp_dir, 'foo', 'y = 6\\') - rc, out, err = script_helper.assert_python_failure(file_name) - self.assertIn(b'unexpected EOF while parsing', err) - self.assertIn(b'line 1', err) - self.assertIn(b'y = 6\\', err) + rc, out, err = script_helper.assert_python_failure('-X', 'utf8', file_name) + err = err.decode().splitlines() + self.assertEqual(err[-2:], [ + ' \\', + 'SyntaxError: unexpected EOF while parsing']) + self.assertEqual(err[-3][-8:], ', line 1', err) + + file_name = script_helper.make_script(temp_dir, 'foo', 'ä = 6\\') + rc, out, err = script_helper.assert_python_failure('-X', 'utf8', file_name) + err = err.decode().splitlines() + self.assertEqual(err[-3:], [ + ' ä = 6\\', + ' ^', + 'SyntaxError: unexpected EOF while parsing']) + self.assertEqual(err[-4][-8:], ', line 1', err) + + file_name = script_helper.make_script(temp_dir, 'foo', + '# coding:latin1\n' + 'ä = 7\\'.encode('latin1')) + rc, out, err = script_helper.assert_python_failure('-X', 'utf8', file_name) + err = err.decode().splitlines() + self.assertEqual(err[-3:], [ + ' ä = 7\\', + ' ^', + 'SyntaxError: unexpected EOF while parsing']) + self.assertEqual(err[-4][-8:], ', line 2', err) + + file_name = script_helper.make_script(temp_dir, 'foo', + BOM_UTF8 + 'ä = 8\\'.encode()) + rc, out, err = script_helper.assert_python_failure('-X', 'utf8', file_name) + err = err.decode().splitlines() + self.assertEqual(err[-3:], [ + ' ä = 8\\', + ' ^', + 'SyntaxError: unexpected EOF while parsing']) + self.assertEqual(err[-4][-8:], ', line 1', err) + if __name__ == "__main__": unittest.main() From ed6caed3d9ebe14e3ab975cdc4f77318dc74fb32 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 31 Aug 2025 11:08:20 +0300 Subject: [PATCH 04/16] Update `test_decorators.py` from 3.13.7 --- Lib/test/test_decorators.py | 126 +----------------------------------- 1 file changed, 1 insertion(+), 125 deletions(-) diff --git a/Lib/test/test_decorators.py b/Lib/test/test_decorators.py index 739c9b3909..4da2c13a60 100644 --- a/Lib/test/test_decorators.py +++ b/Lib/test/test_decorators.py @@ -291,46 +291,7 @@ def bar(): return 42 self.assertEqual(bar(), 42) self.assertEqual(actions, expected_actions) - def test_wrapped_descriptor_inside_classmethod(self): - class BoundWrapper: - def __init__(self, wrapped): - self.__wrapped__ = wrapped - - def __call__(self, *args, **kwargs): - return self.__wrapped__(*args, **kwargs) - - class Wrapper: - def __init__(self, wrapped): - self.__wrapped__ = wrapped - - def __get__(self, instance, owner): - bound_function = self.__wrapped__.__get__(instance, owner) - return BoundWrapper(bound_function) - - def decorator(wrapped): - return Wrapper(wrapped) - - class Class: - @decorator - @classmethod - def inner(cls): - # This should already work. - return 'spam' - - @classmethod - @decorator - def outer(cls): - # Raised TypeError with a message saying that the 'Wrapper' - # object is not callable. - return 'eggs' - - self.assertEqual(Class.inner(), 'spam') - self.assertEqual(Class.outer(), 'eggs') - self.assertEqual(Class().inner(), 'spam') - self.assertEqual(Class().outer(), 'eggs') - - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_bound_function_inside_classmethod(self): class A: def foo(self, cls): @@ -341,91 +302,6 @@ class B: self.assertEqual(B.bar(), 'spam') - def test_wrapped_classmethod_inside_classmethod(self): - class MyClassMethod1: - def __init__(self, func): - self.func = func - - def __call__(self, cls): - if hasattr(self.func, '__get__'): - return self.func.__get__(cls, cls)() - return self.func(cls) - - def __get__(self, instance, owner=None): - if owner is None: - owner = type(instance) - return MethodType(self, owner) - - class MyClassMethod2: - def __init__(self, func): - if isinstance(func, classmethod): - func = func.__func__ - self.func = func - - def __call__(self, cls): - return self.func(cls) - - def __get__(self, instance, owner=None): - if owner is None: - owner = type(instance) - return MethodType(self, owner) - - for myclassmethod in [MyClassMethod1, MyClassMethod2]: - class A: - @myclassmethod - def f1(cls): - return cls - - @classmethod - @myclassmethod - def f2(cls): - return cls - - @myclassmethod - @classmethod - def f3(cls): - return cls - - @classmethod - @classmethod - def f4(cls): - return cls - - @myclassmethod - @MyClassMethod1 - def f5(cls): - return cls - - @myclassmethod - @MyClassMethod2 - def f6(cls): - return cls - - self.assertIs(A.f1(), A) - self.assertIs(A.f2(), A) - self.assertIs(A.f3(), A) - self.assertIs(A.f4(), A) - self.assertIs(A.f5(), A) - self.assertIs(A.f6(), A) - a = A() - self.assertIs(a.f1(), A) - self.assertIs(a.f2(), A) - self.assertIs(a.f3(), A) - self.assertIs(a.f4(), A) - self.assertIs(a.f5(), A) - self.assertIs(a.f6(), A) - - def f(cls): - return cls - - self.assertIs(myclassmethod(f).__get__(a)(), A) - self.assertIs(myclassmethod(f).__get__(a, A)(), A) - self.assertIs(myclassmethod(f).__get__(A, A)(), A) - self.assertIs(myclassmethod(f).__get__(A)(), type(A)) - self.assertIs(classmethod(f).__get__(a)(), A) - self.assertIs(classmethod(f).__get__(a, A)(), A) - self.assertIs(classmethod(f).__get__(A, A)(), A) - self.assertIs(classmethod(f).__get__(A)(), type(A)) class TestClassDecorators(unittest.TestCase): From d9ffc47c438fd9597aec98447b7f65165e22fc8b Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 31 Aug 2025 11:09:34 +0300 Subject: [PATCH 05/16] Update `test_dynamic.py` from 3.13.7 --- Lib/test/test_dynamic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_dynamic.py b/Lib/test/test_dynamic.py index 4217e8e01c..9fd2d86710 100644 --- a/Lib/test/test_dynamic.py +++ b/Lib/test/test_dynamic.py @@ -4,7 +4,7 @@ import sys import unittest -from test.support import swap_item, swap_attr +from test.support import swap_item, swap_attr, is_wasi, Py_DEBUG class RebindBuiltinsTests(unittest.TestCase): @@ -134,8 +134,8 @@ def test_eval_gives_lambda_custom_globals(self): self.assertEqual(foo(), 7) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.skipIf(is_wasi and Py_DEBUG, "requires too much stack") def test_load_global_specialization_failure_keeps_oparg(self): # https://github.com/python/cpython/issues/91625 class MyGlobals(dict): From 6a4d4b727cf1aa30250381dff652257f92b6bd92 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 31 Aug 2025 11:47:32 +0300 Subject: [PATCH 06/16] Uodate `test_grammar.py` from 3.13.7 --- Lib/test/test_grammar.py | 450 +++++++++++++++++++++++++++------------ 1 file changed, 310 insertions(+), 140 deletions(-) diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py index e40f569d2c..323f4ee4c6 100644 --- a/Lib/test/test_grammar.py +++ b/Lib/test/test_grammar.py @@ -2,7 +2,7 @@ # This just tests whether the parser accepts them all. from test.support import check_syntax_error -from test.support.warnings_helper import check_syntax_warning +from test.support import import_helper import inspect import unittest import sys @@ -12,97 +12,19 @@ # different import patterns to check that __annotations__ does not interfere # with import machinery -# import test.ann_module as ann_module +import test.typinganndata.ann_module as ann_module import typing -from collections import ChainMap -# from test import ann_module2 +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): from test.support import check_syntax_error + from test.support.warnings_helper import check_syntax_warning def test_backslash(self): # Backslash means line continuation: @@ -164,7 +86,7 @@ def test_floats(self): x = 3.14 x = 314. x = 0.314 - # XXX x = 000.314 + x = 000.314 x = .314 x = 3e14 x = 3E14 @@ -176,8 +98,10 @@ def test_floats(self): def test_float_exponent_tokenization(self): # See issue 21642. - self.assertEqual(1 if 1else 0, 1) - self.assertEqual(1 if 0else 0, 0) + with warnings.catch_warnings(): + warnings.simplefilter('ignore', SyntaxWarning) + self.assertEqual(eval("1 if 1else 0"), 1) + self.assertEqual(eval("1 if 0else 0"), 0) self.assertRaises(SyntaxError, eval, "0 if 1Else 0") def test_underscore_literals(self): @@ -188,8 +112,7 @@ def test_underscore_literals(self): # Sanity check: no literal begins with an underscore self.assertRaises(NameError, eval, "_0") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_bad_numerical_literals(self): check = self.check_syntax_error check("0b12", "invalid digit '2' in binary literal") @@ -212,6 +135,47 @@ def test_bad_numerical_literals(self): check("1e2_", "invalid decimal literal") check("1e+", "invalid decimal literal") + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_end_of_numerical_literals(self): + def check(test, error=False): + with self.subTest(expr=test): + if error: + with warnings.catch_warnings(record=True) as w: + with self.assertRaisesRegex(SyntaxError, + r'invalid \w+ literal'): + compile(test, "", "eval") + self.assertEqual(w, []) + else: + self.check_syntax_warning(test, + errtext=r'invalid \w+ literal') + + for num in "0xf", "0o7", "0b1", "9", "0", "1.", "1e3", "1j": + compile(num, "", "eval") + check(f"{num}and x", error=(num == "0xf")) + check(f"{num}or x", error=(num == "0")) + check(f"{num}in x") + check(f"{num}not in x") + check(f"{num}if x else y") + check(f"x if {num}else y", error=(num == "0xf")) + check(f"[{num}for x in ()]") + check(f"{num}spam", error=True) + + # gh-88943: Invalid non-ASCII character following a numerical literal. + with self.assertRaisesRegex(SyntaxError, r"invalid character '⁄' \(U\+2044\)"): + compile(f"{num}⁄7", "", "eval") + + with self.assertWarnsRegex(SyntaxWarning, r'invalid \w+ literal'): + compile(f"{num}is x", "", "eval") + with warnings.catch_warnings(): + warnings.simplefilter('error', SyntaxWarning) + with self.assertRaisesRegex(SyntaxError, + r'invalid \w+ literal'): + compile(f"{num}is x", "", "eval") + + check("[0x1ffor x in ()]") + check("[0x1for x in ()]") + check("[0xfor x in ()]") + def test_string_literals(self): x = ''; y = ""; self.assertTrue(len(x) == 0 and x == y) x = '\''; y = "'"; self.assertTrue(len(x) == 1 and x == y and ord(x) == 39) @@ -257,12 +221,13 @@ def test_ellipsis(self): self.assertTrue(x is Ellipsis) self.assertRaises(SyntaxError, eval, ".. .") + @unittest.expectedFailure # TODO: RUSTPYTHON def test_eof_error(self): samples = ("def foo(", "\ndef foo(", "def foo(\n") for s in samples: with self.assertRaises(SyntaxError) as cm: compile(s, "", "exec") - self.assertIn("unexpected EOF", str(cm.exception)) + self.assertIn("was never closed", str(cm.exception)) var_annot_global: int # a global annotated is necessary for test_var_annot @@ -281,6 +246,7 @@ class GrammarTests(unittest.TestCase): from test.support import check_syntax_error from test.support.warnings_helper import check_syntax_warning + from test.support.warnings_helper import check_no_warnings # single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE # XXX can't test in a script -- this rule is only used when interactive @@ -307,14 +273,18 @@ def one(): my_lst[one()-1]: int = 5 self.assertEqual(my_lst, [5]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_var_annot_syntax_errors(self): # parser pass check_syntax_error(self, "def f: int") check_syntax_error(self, "x: int: str") check_syntax_error(self, "def f():\n" " nonlocal x: int\n") + check_syntax_error(self, "def f():\n" + " global x: int\n") + check_syntax_error(self, "x: int = y = 1") + check_syntax_error(self, "z = w: int = 1") + check_syntax_error(self, "x: int = y: int = 1") # AST pass check_syntax_error(self, "[x, 0]: int\n") check_syntax_error(self, "f(): int\n") @@ -328,9 +298,14 @@ def test_var_annot_syntax_errors(self): check_syntax_error(self, "def f():\n" " global x\n" " x: int\n") + check_syntax_error(self, "def f():\n" + " x: int\n" + " nonlocal x\n") + check_syntax_error(self, "def f():\n" + " nonlocal x\n" + " x: int\n") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_var_annot_basic_semantics(self): # execution order with self.assertRaises(ZeroDivisionError): @@ -377,6 +352,28 @@ class Cbad2(C): x: int x.y: list = [] + def test_annotations_inheritance(self): + # Check that annotations are not inherited by derived classes + class A: + attr: int + class B(A): + pass + class C(A): + attr: str + class D: + attr2: int + class E(A, D): + pass + class F(C, A): + pass + self.assertEqual(A.__annotations__, {"attr": int}) + self.assertEqual(B.__annotations__, {}) + self.assertEqual(C.__annotations__, {"attr" : str}) + self.assertEqual(D.__annotations__, {"attr2" : int}) + self.assertEqual(E.__annotations__, {}) + self.assertEqual(F.__annotations__, {}) + + def test_var_annot_metaclass_semantics(self): class CMeta(type): @classmethod @@ -386,13 +383,11 @@ class CC(metaclass=CMeta): XX: 'ANNOT' self.assertEqual(CC.__annotations__['xx'], 'ANNOT') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_var_annot_module_semantics(self): - with self.assertRaises(AttributeError): - print(test.__annotations__) + self.assertEqual(test.__annotations__, {}) self.assertEqual(ann_module.__annotations__, - {1: 2, 'x': int, 'y': str, 'f': typing.Tuple[int, int]}) + {1: 2, 'x': int, 'y': str, 'f': typing.Tuple[int, int], 'u': int | float}) self.assertEqual(ann_module.M.__annotations__, {'123': 123, 'o': type}) self.assertEqual(ann_module2.__annotations__, {}) @@ -400,13 +395,13 @@ def test_var_annot_module_semantics(self): def test_var_annot_in_module(self): # check that functions fail the same way when executed # outside of module where they were defined - from test.typinganndata.ann_module3 import f_bad_ann, g_bad_ann, D_bad_ann + ann_module3 = import_helper.import_fresh_module("test.typinganndata.ann_module3") with self.assertRaises(NameError): - f_bad_ann() + ann_module3.f_bad_ann() with self.assertRaises(NameError): - g_bad_ann() + ann_module3.g_bad_ann() with self.assertRaises(NameError): - D_bad_ann(5) + ann_module3.D_bad_ann(5) def test_var_annot_simple_exec(self): gns = {}; lns= {} @@ -417,8 +412,7 @@ def test_var_annot_simple_exec(self): with self.assertRaises(KeyError): gns['__annotations__'] - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_var_annot_custom_maps(self): # tests with custom locals() and __annotations__ ns = {'__annotations__': CNS()} @@ -440,8 +434,7 @@ def __getitem__(self, item): exec('x: int = 1', {}, CNS2()) self.assertEqual(nonloc_ns['__annotations__']['x'], int) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_var_annot_refleak(self): # complex case: custom locals plus custom __annotations__ # this was causing refleak @@ -459,7 +452,6 @@ def __getitem__(self, item): exec('X: str', {}, CNS2()) self.assertEqual(nonloc_ns['__annotations__']['x'], str) - def test_var_annot_rhs(self): ns = {} exec('x: tuple = 1, 2', ns) @@ -475,7 +467,7 @@ def test_var_annot_rhs(self): def test_funcdef(self): ### [decorators] 'def' NAME parameters ['->' test] ':' suite - ### decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE + ### decorator: '@' namedexpr_test NEWLINE ### decorators: decorator+ ### parameters: '(' [typedargslist] ')' ### typedargslist: ((tfpdef ['=' test] ',')* @@ -597,12 +589,14 @@ def d22v(a, b, c=1, d=2, *rest): pass d22v(1, *(2, 3), **{'d': 4}) # keyword argument type tests - try: - str('x', **{b'foo':1 }) - except TypeError: - pass - else: - self.fail('Bytes should not work as keyword argument names') + with warnings.catch_warnings(): + warnings.simplefilter('ignore', BytesWarning) + try: + str('x', **{b'foo':1 }) + except TypeError: + pass + else: + self.fail('Bytes should not work as keyword argument names') # keyword only argument tests def pos0key1(*, key): return key pos0key1(key=100) @@ -681,6 +675,20 @@ def null(x): return x def f(x) -> list: pass self.assertEqual(f.__annotations__, {'return': list}) + # Test expressions as decorators (PEP 614): + @False or null + def f(x): pass + @d := null + def f(x): pass + @lambda f: null(f) + def f(x): pass + @[..., null, ...][1] + def f(x): pass + @null(null)(null) + def f(x): pass + @[null][0].__call__.__call__ + def f(x): pass + # test closures with a variety of opargs closure = 1 def f(): return closure @@ -768,10 +776,9 @@ def test_expr_stmt(self): check_syntax_error(self, "x + 1 = 1") check_syntax_error(self, "a + 1 = b + 2") - # TODO: RUSTPYTHON - @unittest.expectedFailure # Check the heuristic for print & exec covers significant cases # As well as placing some limits on false positives + @unittest.expectedFailure # TODO: RUSTPYTHON def test_former_statements_refer_to_builtins(self): keywords = "print", "exec" # Cases where we want the custom error @@ -804,6 +811,23 @@ def test_del_stmt(self): del abc del x, y, (z, xyz) + x, y, z = "xyz" + del x + del y, + del (z) + del () + + a, b, c, d, e, f, g = "abcdefg" + del a, (b, c), (d, (e, f)) + + a, b, c, d, e, f, g = "abcdefg" + del a, [b, c], (d, [e, f]) + + abcd = list("abcd") + del abcd[1:2] + + compile("del a, (b[0].c, (d.e, f.g[1:2])), [h.i.j], ()", "", "exec") + def test_pass_stmt(self): # 'pass' pass @@ -1062,8 +1086,7 @@ def g2(x): self.assertEqual(g2(False), 0) self.assertEqual(g2(True), ('end', 1)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_yield(self): # Allowed as standalone statement def g(): yield 1 @@ -1103,8 +1126,7 @@ def g(): rest = 4, 5, 6; yield 1, 2, 3, *rest # Check annotation refleak on SyntaxError check_syntax_error(self, "def g(a:(yield)): pass") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_yield_in_comprehensions(self): # Check yield in comprehensions def g(): [x for x in [(yield 1)]] @@ -1184,11 +1206,9 @@ def test_assert(self): self.fail("'assert True, msg' should not have " "raised an AssertionError") - # TODO: RUSTPYTHON - @unittest.expectedFailure # these tests fail if python is run with -O, so check __debug__ @unittest.skipUnless(__debug__, "Won't work if __debug__ is False") - def testAssert2(self): + def test_assert_failures(self): try: assert 0, "msg" except AssertionError as e: @@ -1203,11 +1223,38 @@ def testAssert2(self): else: self.fail("AssertionError not raised by 'assert False'") + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_assert_syntax_warnings(self): + # Ensure that we warn users if they provide a non-zero length tuple as + # the assertion test. self.check_syntax_warning('assert(x, "msg")', 'assertion is always true') + self.check_syntax_warning('assert(False, "msg")', + 'assertion is always true') + self.check_syntax_warning('assert(False,)', + 'assertion is always true') + + with self.check_no_warnings(category=SyntaxWarning): + compile('assert x, "msg"', '', 'exec') + compile('assert False, "msg"', '', 'exec') + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_assert_warning_promotes_to_syntax_error(self): + # If SyntaxWarning is configured to be an error, it actually raises a + # SyntaxError. + # https://bugs.python.org/issue35029 with warnings.catch_warnings(): warnings.simplefilter('error', SyntaxWarning) - compile('assert x, "msg"', '', 'exec') + try: + compile('assert x, "msg" ', '', 'exec') + except SyntaxError: + self.fail('SyntaxError incorrectly raised for \'assert x, "msg"\'') + with self.assertRaises(SyntaxError): + compile('assert(x, "msg")', '', 'exec') + with self.assertRaises(SyntaxError): + compile('assert(False, "msg")', '', 'exec') + with self.assertRaises(SyntaxError): + compile('assert(False,)', '', 'exec') ### compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | funcdef | classdef @@ -1268,10 +1315,16 @@ def __getitem__(self, i): result.append(x) self.assertEqual(result, [1, 2, 3]) + result = [] + a = b = c = [1, 2, 3] + for x in *a, *b, *c: + result.append(x) + self.assertEqual(result, 3 * a) + def test_try(self): ### try_stmt: 'try' ':' suite (except_clause ':' suite)+ ['else' ':' suite] ### | 'try' ':' suite 'finally' ':' suite - ### except_clause: 'except' [expr ['as' expr]] + ### except_clause: 'except' [expr ['as' NAME]] try: 1/0 except ZeroDivisionError: @@ -1289,6 +1342,36 @@ def test_try(self): except (EOFError, TypeError, ZeroDivisionError) as msg: pass try: pass finally: pass + with self.assertRaises(SyntaxError): + compile("try:\n pass\nexcept Exception as a.b:\n pass", "?", "exec") + compile("try:\n pass\nexcept Exception as a[b]:\n pass", "?", "exec") + + # TODO: RUSTPYTHON + ''' + def test_try_star(self): + ### try_stmt: 'try': suite (except_star_clause : suite) + ['else' ':' suite] + ### except_star_clause: 'except*' expr ['as' NAME] + try: + 1/0 + except* ZeroDivisionError: + pass + else: + pass + try: 1/0 + except* EOFError: pass + except* ZeroDivisionError as msg: pass + else: pass + try: 1/0 + except* (EOFError, TypeError, ZeroDivisionError): pass + try: 1/0 + except* (EOFError, TypeError, ZeroDivisionError) as msg: pass + try: pass + finally: pass + with self.assertRaises(SyntaxError): + compile("try:\n pass\nexcept* Exception as a.b:\n pass", "?", "exec") + compile("try:\n pass\nexcept* Exception as a[b]:\n pass", "?", "exec") + compile("try:\n pass\nexcept*:\n pass", "?", "exec") + ''' def test_suite(self): # simple_stmt | NEWLINE INDENT NEWLINE* (stmt NEWLINE*)+ DEDENT @@ -1333,17 +1416,24 @@ def test_comparison(self): if 1 not in (): pass if 1 < 1 > 1 == 1 >= 1 <= 1 != 1 in 1 not in x is x is not x: pass - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_comparison_is_literal(self): - def check(test, msg='"is" with a literal'): + def check(test, msg): self.check_syntax_warning(test, msg) - check('x is 1') - check('x is "thing"') - check('1 is x') - check('x is y is 1') - check('x is not 1', '"is not" with a literal') + check('x is 1', '"is" with \'int\' literal') + check('x is "thing"', '"is" with \'str\' literal') + check('1 is x', '"is" with \'int\' literal') + check('x is y is 1', '"is" with \'int\' literal') + check('x is not 1', '"is not" with \'int\' literal') + check('x is not (1, 2)', '"is not" with \'tuple\' literal') + check('(1, 2) is not x', '"is not" with \'tuple\' literal') + + check('None is 1', '"is" with \'int\' literal') + check('1 is None', '"is" with \'int\' literal') + + check('x == 3 is y', '"is" with \'int\' literal') + check('x == "thing" is y', '"is" with \'str\' literal') with warnings.catch_warnings(): warnings.simplefilter('error', SyntaxWarning) @@ -1351,9 +1441,12 @@ def check(test, msg='"is" with a literal'): compile('x is False', '', 'exec') compile('x is True', '', 'exec') compile('x is ...', '', 'exec') + compile('None is x', '', 'exec') + compile('False is x', '', 'exec') + compile('True is x', '', 'exec') + compile('... is x', '', 'exec') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_warn_missed_comma(self): def check(test): self.check_syntax_warning(test, msg) @@ -1483,7 +1576,7 @@ def test_selectors(self): s = a[-5:] s = a[:-1] s = a[-4:-3] - # A rough test of SF bug 1333982. http://python.org/sf/1333982 + # A rough test of SF bug 1333982. https://bugs.python.org/issue1333982 # The testing here is fairly incomplete. # Test cases should include: commas with 1 and 2 colons d = {} @@ -1530,6 +1623,8 @@ def test_atoms(self): ### testlist: test (',' test)* [','] # These have been exercised enough above + # TODO: RUSTPYTHON + ''' def test_classdef(self): # 'class' NAME ['(' [testlist] ')'] ':' suite class B: pass @@ -1542,13 +1637,28 @@ def meth1(self): pass def meth2(self, arg): pass def meth3(self, a1, a2): pass - # decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE + # decorator: '@' namedexpr_test NEWLINE # decorators: decorator+ # decorated: decorators (classdef | funcdef) def class_decorator(x): return x @class_decorator class G: pass + # Test expressions as decorators (PEP 614): + @False or class_decorator + class H: pass + @d := class_decorator + class I: pass + @lambda c: class_decorator(c) + class J: pass + @[..., class_decorator, ...][1] + class K: pass + @class_decorator(class_decorator)(class_decorator) + class L: pass + @[class_decorator][0].__call__.__call__ + class M: pass + ''' + def test_dictcomps(self): # dictorsetmaker: ( (test ':' test (comp_for | # (',' test ':' test)* [','])) | @@ -1693,6 +1803,54 @@ def __exit__(self, *args): with manager() as x, manager(): pass + with ( + manager() + ): + pass + + with ( + manager() as x + ): + pass + + with ( + manager() as (x, y), + manager() as z, + ): + pass + + with ( + manager(), + manager() + ): + pass + + with ( + manager() as x, + manager() as y + ): + pass + + with ( + manager() as x, + manager() + ): + pass + + with ( + manager() as x, + manager() as y, + manager() as z, + ): + pass + + with ( + manager() as x, + manager() as y, + manager(), + ): + pass + def test_if_else_expr(self): # Test ifelse expressions in various cases def _checkeval(msg, ret): @@ -1815,6 +1973,18 @@ async def foo(): with self.assertRaises(Done): foo().send(None) + def test_complex_lambda(self): + def test1(foo, bar): + return "" + + def test2(): + return f"{test1( + foo=lambda: '、、、、、、、、、、、、、、、、、', + bar=lambda: 'abcdefghijklmnopqrstuvwxyz 123456789 123456789', + )}" + + self.assertEqual(test2(), "") + if __name__ == '__main__': unittest.main() From 15b1b62adb42749dfe263ac6078c5b9b51e1e43d Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 31 Aug 2025 11:50:04 +0300 Subject: [PATCH 07/16] Update `test_isinstance.py` from 3.13.7 --- Lib/test/test_isinstance.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Lib/test/test_isinstance.py b/Lib/test/test_isinstance.py index de80e47209..95a119ba68 100644 --- a/Lib/test/test_isinstance.py +++ b/Lib/test/test_isinstance.py @@ -352,9 +352,7 @@ def blowstack(fxn, arg, compare_to): # Make sure that calling isinstance with a deeply nested tuple for its # argument will raise RecursionError eventually. tuple_arg = (compare_to,) - # XXX: RUSTPYTHON; support.exceeds_recursion_limit() is not available yet. - import sys - for cnt in range(sys.getrecursionlimit()+5): + for cnt in range(support.exceeds_recursion_limit()): tuple_arg = (tuple_arg,) fxn(arg, tuple_arg) From 88506059f98c0572369e3652666adaeeb5f154aa Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 31 Aug 2025 11:58:21 +0300 Subject: [PATCH 08/16] Update `test_pow.py` from 3.13.7 --- Lib/test/test_pow.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_pow.py b/Lib/test/test_pow.py index 5cea9ceb20..eeb482ec4b 100644 --- a/Lib/test/test_pow.py +++ b/Lib/test/test_pow.py @@ -19,12 +19,11 @@ def powtest(self, type): self.assertEqual(pow(2, i), pow2) if i != 30 : pow2 = pow2*2 - for othertype in (int,): - for i in list(range(-10, 0)) + list(range(1, 10)): - ii = type(i) - for j in range(1, 11): - jj = -othertype(j) - pow(ii, jj) + for i in list(range(-10, 0)) + list(range(1, 10)): + ii = type(i) + inv = pow(ii, -1) # inverse of ii + for jj in range(-10, 0): + self.assertAlmostEqual(pow(ii, jj), pow(inv, -jj)) for othertype in int, float: for i in range(1, 100): From ec8f37dcd6dc7c1de8693e65efe0499706262e9e Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 31 Aug 2025 11:59:46 +0300 Subject: [PATCH 09/16] Update `test_pprint.py` from 3.13.7 --- Lib/test/test_pprint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_pprint.py b/Lib/test/test_pprint.py index 4e6fed1ab9..ace75561f2 100644 --- a/Lib/test/test_pprint.py +++ b/Lib/test/test_pprint.py @@ -374,7 +374,7 @@ def __new__(cls, celsius_degrees): return super().__new__(Temperature, celsius_degrees) def __repr__(self): kelvin_degrees = self + 273.15 - return f"{kelvin_degrees}°K" + return f"{kelvin_degrees:.2f}°K" self.assertEqual(pprint.pformat(Temperature(1000)), '1273.15°K') def test_sorted_dict(self): From 6a3c348351e975728bc5fbd6f902b664d2d1fbad Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 31 Aug 2025 12:01:32 +0300 Subject: [PATCH 10/16] Update test_richcmp.py --- Lib/test/test_richcmp.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_richcmp.py b/Lib/test/test_richcmp.py index 58729a9fea..5f449cdc05 100644 --- a/Lib/test/test_richcmp.py +++ b/Lib/test/test_richcmp.py @@ -221,6 +221,7 @@ def do(bad): self.assertRaises(Exc, func, Bad()) @support.no_tracing + @support.infinite_recursion(25) def test_recursion(self): # Check that comparison for recursive objects fails gracefully from collections import UserList From d732c307dc5e19d9032cca2fe12b129fbf7acd38 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 31 Aug 2025 12:13:09 +0300 Subject: [PATCH 11/16] Update `test_univnewlines.py` from 3.13.7 --- Lib/test/test_univnewlines.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_univnewlines.py b/Lib/test/test_univnewlines.py index b905491878..ed2e0970ba 100644 --- a/Lib/test/test_univnewlines.py +++ b/Lib/test/test_univnewlines.py @@ -4,7 +4,6 @@ import unittest import os import sys -from test import support from test.support import os_helper From e00a95d15c4ed90f580b98c7ad59489483a3beb9 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 31 Aug 2025 12:14:40 +0300 Subject: [PATCH 12/16] Update `test_userdict.py` from 3.13.7 --- Lib/test/test_userdict.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_userdict.py b/Lib/test/test_userdict.py index 483910aaa4..61e79f553e 100644 --- a/Lib/test/test_userdict.py +++ b/Lib/test/test_userdict.py @@ -1,6 +1,6 @@ # Check every path through every method of UserDict -from test import mapping_tests +from test import mapping_tests, support import unittest import collections @@ -213,6 +213,11 @@ class G(collections.UserDict): else: self.fail("g[42] didn't raise KeyError") + # Decorate existing test with recursion limit, because + # the test is for C structure, but `UserDict` is a Python structure. + test_repr_deep = support.infinite_recursion(25)( + mapping_tests.TestHashMappingProtocol.test_repr_deep, + ) if __name__ == "__main__": From 41fb6c5a1ae96551c585bbe678f6c967abf5cdf4 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 31 Aug 2025 12:23:58 +0300 Subject: [PATCH 13/16] Add `Lib/test/test_file_eintr.py` from 3.13.7 --- Lib/test/test_file_eintr.py | 262 ++++++++++++++++++++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 Lib/test/test_file_eintr.py diff --git a/Lib/test/test_file_eintr.py b/Lib/test/test_file_eintr.py new file mode 100644 index 0000000000..55cc31dc59 --- /dev/null +++ b/Lib/test/test_file_eintr.py @@ -0,0 +1,262 @@ +# Written to test interrupted system calls interfering with our many buffered +# IO implementations. http://bugs.python.org/issue12268 +# +# It was suggested that this code could be merged into test_io and the tests +# made to work using the same method as the existing signal tests in test_io. +# I was unable to get single process tests using alarm or setitimer that way +# to reproduce the EINTR problems. This process based test suite reproduces +# the problems prior to the issue12268 patch reliably on Linux and OSX. +# - gregory.p.smith + +import os +import select +import signal +import subprocess +import sys +import time +import unittest +from test import support + +if not support.has_subprocess_support: + raise unittest.SkipTest("test module requires subprocess") + +# Test import all of the things we're about to try testing up front. +import _io +import _pyio + +@unittest.skipUnless(os.name == 'posix', 'tests requires a posix system.') +class TestFileIOSignalInterrupt: + def setUp(self): + self._process = None + + def tearDown(self): + if self._process and self._process.poll() is None: + try: + self._process.kill() + except OSError: + pass + + def _generate_infile_setup_code(self): + """Returns the infile = ... line of code for the reader process. + + subclasseses should override this to test different IO objects. + """ + return ('import %s as io ;' + 'infile = io.FileIO(sys.stdin.fileno(), "rb")' % + self.modname) + + def fail_with_process_info(self, why, stdout=b'', stderr=b'', + communicate=True): + """A common way to cleanup and fail with useful debug output. + + Kills the process if it is still running, collects remaining output + and fails the test with an error message including the output. + + Args: + why: Text to go after "Error from IO process" in the message. + stdout, stderr: standard output and error from the process so + far to include in the error message. + communicate: bool, when True we call communicate() on the process + after killing it to gather additional output. + """ + if self._process.poll() is None: + time.sleep(0.1) # give it time to finish printing the error. + try: + self._process.terminate() # Ensure it dies. + except OSError: + pass + if communicate: + stdout_end, stderr_end = self._process.communicate() + stdout += stdout_end + stderr += stderr_end + self.fail('Error from IO process %s:\nSTDOUT:\n%sSTDERR:\n%s\n' % + (why, stdout.decode(), stderr.decode())) + + def _test_reading(self, data_to_write, read_and_verify_code): + """Generic buffered read method test harness to validate EINTR behavior. + + Also validates that Python signal handlers are run during the read. + + Args: + data_to_write: String to write to the child process for reading + before sending it a signal, confirming the signal was handled, + writing a final newline and closing the infile pipe. + read_and_verify_code: Single "line" of code to read from a file + object named 'infile' and validate the result. This will be + executed as part of a python subprocess fed data_to_write. + """ + infile_setup_code = self._generate_infile_setup_code() + # Total pipe IO in this function is smaller than the minimum posix OS + # pipe buffer size of 512 bytes. No writer should block. + assert len(data_to_write) < 512, 'data_to_write must fit in pipe buf.' + + # Start a subprocess to call our read method while handling a signal. + self._process = subprocess.Popen( + [sys.executable, '-u', '-c', + 'import signal, sys ;' + 'signal.signal(signal.SIGINT, ' + 'lambda s, f: sys.stderr.write("$\\n")) ;' + + infile_setup_code + ' ;' + + 'sys.stderr.write("Worm Sign!\\n") ;' + + read_and_verify_code + ' ;' + + 'infile.close()' + ], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + # Wait for the signal handler to be installed. + worm_sign = self._process.stderr.read(len(b'Worm Sign!\n')) + if worm_sign != b'Worm Sign!\n': # See also, Dune by Frank Herbert. + self.fail_with_process_info('while awaiting a sign', + stderr=worm_sign) + self._process.stdin.write(data_to_write) + + signals_sent = 0 + rlist = [] + # We don't know when the read_and_verify_code in our child is actually + # executing within the read system call we want to interrupt. This + # loop waits for a bit before sending the first signal to increase + # the likelihood of that. Implementations without correct EINTR + # and signal handling usually fail this test. + while not rlist: + rlist, _, _ = select.select([self._process.stderr], (), (), 0.05) + self._process.send_signal(signal.SIGINT) + signals_sent += 1 + if signals_sent > 200: + self._process.kill() + self.fail('reader process failed to handle our signals.') + # This assumes anything unexpected that writes to stderr will also + # write a newline. That is true of the traceback printing code. + signal_line = self._process.stderr.readline() + if signal_line != b'$\n': + self.fail_with_process_info('while awaiting signal', + stderr=signal_line) + + # We append a newline to our input so that a readline call can + # end on its own before the EOF is seen and so that we're testing + # the read call that was interrupted by a signal before the end of + # the data stream has been reached. + stdout, stderr = self._process.communicate(input=b'\n') + if self._process.returncode: + self.fail_with_process_info( + 'exited rc=%d' % self._process.returncode, + stdout, stderr, communicate=False) + # PASS! + + # String format for the read_and_verify_code used by read methods. + _READING_CODE_TEMPLATE = ( + 'got = infile.{read_method_name}() ;' + 'expected = {expected!r} ;' + 'assert got == expected, (' + '"{read_method_name} returned wrong data.\\n"' + '"got data %r\\nexpected %r" % (got, expected))' + ) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_readline(self): + """readline() must handle signals and not lose data.""" + self._test_reading( + data_to_write=b'hello, world!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='readline', + expected=b'hello, world!\n')) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_readlines(self): + """readlines() must handle signals and not lose data.""" + self._test_reading( + data_to_write=b'hello\nworld!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='readlines', + expected=[b'hello\n', b'world!\n'])) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_readall(self): + """readall() must handle signals and not lose data.""" + self._test_reading( + data_to_write=b'hello\nworld!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='readall', + expected=b'hello\nworld!\n')) + # read() is the same thing as readall(). + self._test_reading( + data_to_write=b'hello\nworld!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='read', + expected=b'hello\nworld!\n')) + + +class CTestFileIOSignalInterrupt(TestFileIOSignalInterrupt, unittest.TestCase): + modname = '_io' + +class PyTestFileIOSignalInterrupt(TestFileIOSignalInterrupt, unittest.TestCase): + modname = '_pyio' + + +class TestBufferedIOSignalInterrupt(TestFileIOSignalInterrupt): + def _generate_infile_setup_code(self): + """Returns the infile = ... line of code to make a BufferedReader.""" + return ('import %s as io ;infile = io.open(sys.stdin.fileno(), "rb") ;' + 'assert isinstance(infile, io.BufferedReader)' % + self.modname) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_readall(self): + """BufferedReader.read() must handle signals and not lose data.""" + self._test_reading( + data_to_write=b'hello\nworld!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='read', + expected=b'hello\nworld!\n')) + +class CTestBufferedIOSignalInterrupt(TestBufferedIOSignalInterrupt, unittest.TestCase): + modname = '_io' + +class PyTestBufferedIOSignalInterrupt(TestBufferedIOSignalInterrupt, unittest.TestCase): + modname = '_pyio' + + +class TestTextIOSignalInterrupt(TestFileIOSignalInterrupt): + def _generate_infile_setup_code(self): + """Returns the infile = ... line of code to make a TextIOWrapper.""" + return ('import %s as io ;' + 'infile = io.open(sys.stdin.fileno(), encoding="utf-8", newline=None) ;' + 'assert isinstance(infile, io.TextIOWrapper)' % + self.modname) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_readline(self): + """readline() must handle signals and not lose data.""" + self._test_reading( + data_to_write=b'hello, world!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='readline', + expected='hello, world!\n')) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_readlines(self): + """readlines() must handle signals and not lose data.""" + self._test_reading( + data_to_write=b'hello\r\nworld!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='readlines', + expected=['hello\n', 'world!\n'])) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_readall(self): + """read() must handle signals and not lose data.""" + self._test_reading( + data_to_write=b'hello\nworld!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='read', + expected="hello\nworld!\n")) + +class CTestTextIOSignalInterrupt(TestTextIOSignalInterrupt, unittest.TestCase): + modname = '_io' + +class PyTestTextIOSignalInterrupt(TestTextIOSignalInterrupt, unittest.TestCase): + modname = '_pyio' + + +if __name__ == '__main__': + unittest.main() From a1c11cdc4074865fd91ffc88c2d7f2dd4fe4aac4 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 31 Aug 2025 12:37:48 +0300 Subject: [PATCH 14/16] Update `test_fileio.py` from 3.13.7 --- Lib/test/test_fileio.py | 92 ++++++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 38 deletions(-) diff --git a/Lib/test/test_fileio.py b/Lib/test/test_fileio.py index 06c8f0de11..edc29b34d5 100644 --- a/Lib/test/test_fileio.py +++ b/Lib/test/test_fileio.py @@ -10,10 +10,14 @@ from functools import wraps from test.support import ( - cpython_only, swap_attr, gc_collect, is_emscripten, is_wasi + cpython_only, swap_attr, gc_collect, is_emscripten, is_wasi, + infinite_recursion, ) -from test.support.os_helper import (TESTFN, TESTFN_UNICODE, make_bad_fd) +from test.support.os_helper import ( + TESTFN, TESTFN_ASCII, TESTFN_UNICODE, make_bad_fd, + ) from test.support.warnings_helper import check_warnings +from test.support.import_helper import import_module from collections import UserList import _io # C implementation of io @@ -171,6 +175,16 @@ def testRepr(self): self.assertEqual(repr(self.f), "<%s.FileIO [closed]>" % (self.modulename,)) + def test_subclass_repr(self): + class TestSubclass(self.FileIO): + pass + + f = TestSubclass(TESTFN) + with f: + self.assertIn(TestSubclass.__name__, repr(f)) + + self.assertIn(TestSubclass.__name__, repr(f)) + def testReprNoCloseFD(self): fd = os.open(TESTFN, os.O_RDONLY) try: @@ -181,6 +195,7 @@ def testReprNoCloseFD(self): finally: os.close(fd) + @infinite_recursion(25) def testRecursiveRepr(self): # Issue #25455 with swap_attr(self.f, 'name', self.f): @@ -348,40 +363,34 @@ class CAutoFileTests(AutoFileTests, unittest.TestCase): FileIO = _io.FileIO modulename = '_io' - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def testBlksize(self): - super().testBlksize() + return super().testBlksize() - # TODO: RUSTPYTHON - if sys.platform == "win32": - @unittest.expectedFailure - def testErrnoOnClosedTruncate(self): - super().testErrnoOnClosedTruncate() + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") + def testErrnoOnClosedTruncate(self): + return super().testErrnoOnClosedTruncate() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def testMethods(self): - super().testMethods() - - # TODO: RUSTPYTHON - @unittest.expectedFailure + return super().testMethods() + + @unittest.expectedFailure # TODO: RUSTPYTHON def testOpenDirFD(self): - super().testOpenDirFD() + return super().testOpenDirFD() + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_subclass_repr(self): + return super().test_subclass_repr() @unittest.skipIf(sys.platform == "win32", "TODO: RUSTPYTHON, test setUp errors on Windows") class PyAutoFileTests(AutoFileTests, unittest.TestCase): FileIO = _pyio.FileIO modulename = '_pyio' - def testOpendir(self): - super().testOpendir() - class OtherFileTests: - @unittest.skip("TODO: non-deterministic failures, FileIO.seekable()?") def testAbles(self): try: f = self.FileIO(TESTFN, "w") @@ -458,18 +467,15 @@ def testUnicodeOpen(self): def testBytesOpen(self): # Opening a bytes filename - try: - fn = TESTFN.encode("ascii") - except UnicodeEncodeError: - self.skipTest('could not encode %r to ascii' % TESTFN) + fn = TESTFN_ASCII.encode("ascii") f = self.FileIO(fn, "w") try: f.write(b"abc") f.close() - with open(TESTFN, "rb") as f: + with self.open(TESTFN_ASCII, "rb") as f: self.assertEqual(f.read(), b"abc") finally: - os.unlink(TESTFN) + os.unlink(TESTFN_ASCII) @unittest.skipIf(sys.getfilesystemencoding() != 'utf-8', "test only works for utf-8 filesystems") @@ -483,7 +489,7 @@ def testUtf8BytesOpen(self): try: f.write(b"abc") f.close() - with open(TESTFN_UNICODE, "rb") as f: + with self.open(TESTFN_UNICODE, "rb") as f: self.assertEqual(f.read(), b"abc") finally: os.unlink(TESTFN_UNICODE) @@ -500,6 +506,15 @@ def testInvalidFd(self): import msvcrt self.assertRaises(OSError, msvcrt.get_osfhandle, make_bad_fd()) + @unittest.expectedFailure # TODO: RUSTPYTHON + def testBooleanFd(self): + for fd in False, True: + with self.assertWarnsRegex(RuntimeWarning, + 'bool is used as a file descriptor') as cm: + f = self.FileIO(fd, closefd=False) + f.close() + self.assertEqual(cm.filename, __file__) + def testBadModeArgument(self): # verify that we get a sensible error message for bad mode argument bad_mode = "qwerty" @@ -530,7 +545,7 @@ def testTruncate(self): def testTruncateOnWindows(self): def bug801631(): - # SF bug + # SF bug # "file.truncate fault on windows" f = self.FileIO(TESTFN, 'w') f.write(bytes(range(11))) @@ -559,13 +574,13 @@ def bug801631(): def testAppend(self): try: - f = open(TESTFN, 'wb') + f = self.FileIO(TESTFN, 'wb') f.write(b'spam') f.close() - f = open(TESTFN, 'ab') + f = self.FileIO(TESTFN, 'ab') f.write(b'eggs') f.close() - f = open(TESTFN, 'rb') + f = self.FileIO(TESTFN, 'rb') d = f.read() f.close() self.assertEqual(d, b'spameggs') @@ -601,16 +616,12 @@ def __setattr__(self, name, value): class COtherFileTests(OtherFileTests, unittest.TestCase): FileIO = _io.FileIO modulename = '_io' - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def testUnclosedFDOnException(self): - super().testUnclosedFDOnException() + open = _io.open @cpython_only def testInvalidFd_overflow(self): # Issue 15989 - import _testcapi + _testcapi = import_module("_testcapi") self.assertRaises(TypeError, self.FileIO, _testcapi.INT_MAX + 1) self.assertRaises(TypeError, self.FileIO, _testcapi.INT_MIN - 1) @@ -623,10 +634,15 @@ def test_open_code(self): actual = f.read() self.assertEqual(expected, actual) + @unittest.expectedFailure # TODO: RUSTPYTHON + def testUnclosedFDOnException(self): + return super().testUnclosedFDOnException() + class PyOtherFileTests(OtherFileTests, unittest.TestCase): FileIO = _pyio.FileIO modulename = '_pyio' + open = _pyio.open def test_open_code(self): # Check that the default behaviour of open_code matches From 373de5ee579b1a01d30de58533102a8292d6474c Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 31 Aug 2025 12:43:52 +0300 Subject: [PATCH 15/16] Update `test_with.py` from 3.13.7 --- Lib/test/test_with.py | 57 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_with.py b/Lib/test/test_with.py index 07522bda6a..b321dac6c6 100644 --- a/Lib/test/test_with.py +++ b/Lib/test/test_with.py @@ -5,6 +5,7 @@ __email__ = "mbland at acm dot org" import sys +import traceback import unittest from collections import deque from contextlib import _GeneratorContextManager, contextmanager, nullcontext @@ -79,11 +80,11 @@ def __exit__(self, *exc_info): try: if mgr.__exit__(*ex): ex = (None, None, None) - except: - ex = sys.exc_info() + except BaseException as e: + ex = (type(e), e, e.__traceback__) self.entered = None if ex is not exc_info: - raise ex[0](ex[1]).with_traceback(ex[2]) + raise ex class MockNested(Nested): @@ -170,7 +171,10 @@ def __exit__(self, *args): def shouldThrow(): ct = EnterThrows() self.foo = None - with ct as self.foo: + # Ruff complains that we're redefining `self.foo` here, + # but the whole point of the test is to check that `self.foo` + # is *not* redefined (because `__enter__` raises) + with ct as self.foo: # noqa: F811 pass self.assertRaises(RuntimeError, shouldThrow) self.assertEqual(self.foo, None) @@ -251,7 +255,6 @@ def testInlineGeneratorBoundSyntax(self): self.assertAfterWithGeneratorInvariantsNoError(foo) def testInlineGeneratorBoundToExistingVariable(self): - foo = None with mock_contextmanager_generator() as foo: self.assertInWithGeneratorInvariants(foo) self.assertAfterWithGeneratorInvariantsNoError(foo) @@ -749,5 +752,49 @@ def testEnterReturnsTuple(self): self.assertEqual(10, b1) self.assertEqual(20, b2) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'FrameSummary' object has no attribute 'end_lineno' + def testExceptionLocation(self): + # The location of an exception raised from + # __init__, __enter__ or __exit__ of a context + # manager should be just the context manager expression, + # pinpointing the precise context manager in case there + # is more than one. + + def init_raises(): + try: + with self.Dummy(), self.InitRaises() as cm, self.Dummy() as d: + pass + except Exception as e: + return e + + def enter_raises(): + try: + with self.EnterRaises(), self.Dummy() as d: + pass + except Exception as e: + return e + + def exit_raises(): + try: + with self.ExitRaises(), self.Dummy() as d: + pass + except Exception as e: + return e + + for func, expected in [(init_raises, "self.InitRaises()"), + (enter_raises, "self.EnterRaises()"), + (exit_raises, "self.ExitRaises()"), + ]: + with self.subTest(func): + exc = func() + f = traceback.extract_tb(exc.__traceback__)[0] + indent = 16 + co = func.__code__ + self.assertEqual(f.lineno, co.co_firstlineno + 2) + self.assertEqual(f.end_lineno, co.co_firstlineno + 2) + self.assertEqual(f.line[f.colno - indent : f.end_colno - indent], + expected) + + if __name__ == '__main__': unittest.main() From de3cb8cdbbad426521a87f553c08538bf3e84613 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Mon, 1 Sep 2025 11:19:00 +0300 Subject: [PATCH 16/16] Mark more failing tests --- Lib/test/test_eof.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_eof.py b/Lib/test/test_eof.py index 32efe15b5d..2c74e2d87d 100644 --- a/Lib/test/test_eof.py +++ b/Lib/test/test_eof.py @@ -46,6 +46,7 @@ def test_EOFS(self): self.assertEqual(cm.exception.text, "ä = '''thîs is ") self.assertEqual(cm.exception.offset, 5) + @unittest.expectedFailure # TODO: RUSTPYTHON @force_not_colorized def test_EOFS_with_file(self): expect = ("(, line 1)") @@ -127,6 +128,7 @@ def test_line_continuation_EOF(self): exec('\\') self.assertEqual(str(cm.exception), expect) + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf(not sys.executable, "sys.executable required") @force_not_colorized def test_line_continuation_EOF_from_file_bpo2180(self):