From 4030ad12d9161843df0e75597ad56aef81991b0c Mon Sep 17 00:00:00 2001 From: Myeongseon Choi Date: Mon, 11 Jul 2022 21:44:40 +0900 Subject: [PATCH 1/8] Update test_exceptions.py from CPython 3.10.5 --- Lib/test/test_exceptions.py | 851 ++++++++++++++++++++++++++++++++---- 1 file changed, 761 insertions(+), 90 deletions(-) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index b3e3897ca..ce6a3ca88 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -1,23 +1,25 @@ # Python test set -- part 5, built-in exceptions import copy +import gc import os import sys import unittest import pickle import weakref import errno +from textwrap import dedent from test.support import (captured_stderr, check_impl_detail, cpython_only, gc_collect, no_tracing, script_helper, - SuppressCrashReport, - ) -from test.support.os_helper import TESTFN, unlink + SuppressCrashReport) from test.support.import_helper import import_module +from test.support.os_helper import TESTFN, unlink from test.support.warnings_helper import check_warnings from test import support + class NaiveException(Exception): def __init__(self, x): self.x = x @@ -36,25 +38,26 @@ class BrokenStrException(Exception): class ExceptionTests(unittest.TestCase): def raise_catch(self, exc, excname): - try: - raise exc("spam") - except exc as err: - buf1 = str(err) - try: - raise exc("spam") - except exc as err: - buf2 = str(err) - self.assertEqual(buf1, buf2) - self.assertEqual(exc.__name__, excname) + with self.subTest(exc=exc, excname=excname): + try: + raise exc("spam") + except exc as err: + buf1 = str(err) + try: + raise exc("spam") + except exc as err: + buf2 = str(err) + self.assertEqual(buf1, buf2) + self.assertEqual(exc.__name__, excname) def testRaising(self): self.raise_catch(AttributeError, "AttributeError") self.assertRaises(AttributeError, getattr, sys, "undefined_attribute") self.raise_catch(EOFError, "EOFError") - fp = open(TESTFN, 'w') + fp = open(TESTFN, 'w', encoding="utf-8") fp.close() - fp = open(TESTFN, 'r') + fp = open(TESTFN, 'r', encoding="utf-8") savestdin = sys.stdin try: try: @@ -138,13 +141,14 @@ class ExceptionTests(unittest.TestCase): # these code fragments def ckmsg(src, msg): - try: - compile(src, '', 'exec') - except SyntaxError as e: - if e.msg != msg: - self.fail("expected %s, got %s" % (msg, e.msg)) - else: - self.fail("failed to get expected SyntaxError") + with self.subTest(src=src, msg=msg): + try: + compile(src, '', 'exec') + except SyntaxError as e: + if e.msg != msg: + self.fail("expected %s, got %s" % (msg, e.msg)) + else: + self.fail("failed to get expected SyntaxError") s = '''if 1: try: @@ -168,76 +172,155 @@ class ExceptionTests(unittest.TestCase): self.fail("failed to get expected SyntaxError") s = '''print "old style"''' - ckmsg(s, "Missing parentheses in call to 'print'. " - "Did you mean print(\"old style\")?") + ckmsg(s, "Missing parentheses in call to 'print'. Did you mean print(...)?") s = '''print "old style",''' - ckmsg(s, "Missing parentheses in call to 'print'. " - "Did you mean print(\"old style\", end=\" \")?") + ckmsg(s, "Missing parentheses in call to 'print'. Did you mean print(...)?") + + s = 'print f(a+b,c)' + ckmsg(s, "Missing parentheses in call to 'print'. Did you mean print(...)?") s = '''exec "old style"''' - ckmsg(s, "Missing parentheses in call to 'exec'") + ckmsg(s, "Missing parentheses in call to 'exec'. Did you mean exec(...)?") + + s = 'exec f(a+b,c)' + ckmsg(s, "Missing parentheses in call to 'exec'. Did you mean exec(...)?") + + # Check that we don't incorrectly identify '(...)' as an expression to the right + # of 'print' + + s = 'print (a+b,c) $ 42' + ckmsg(s, "invalid syntax") + + s = 'exec (a+b,c) $ 42' + ckmsg(s, "invalid syntax") # should not apply to subclasses, see issue #31161 s = '''if True:\nprint "No indent"''' - ckmsg(s, "expected an indented block", IndentationError) + ckmsg(s, "expected an indented block after 'if' statement on line 1", IndentationError) s = '''if True:\n print()\n\texec "mixed tabs and spaces"''' ckmsg(s, "inconsistent use of tabs and spaces in indentation", TabError) - # TODO: RUSTPYTHON - @unittest.expectedFailure - def testSyntaxErrorOffset(self): - def check(src, lineno, offset): + def check(self, src, lineno, offset, end_lineno=None, end_offset=None, encoding='utf-8'): + with self.subTest(source=src, lineno=lineno, offset=offset): with self.assertRaises(SyntaxError) as cm: compile(src, '', 'exec') self.assertEqual(cm.exception.lineno, lineno) self.assertEqual(cm.exception.offset, offset) + if end_lineno is not None: + self.assertEqual(cm.exception.end_lineno, end_lineno) + if end_offset is not None: + self.assertEqual(cm.exception.end_offset, end_offset) + if cm.exception.text is not None: + if not isinstance(src, str): + src = src.decode(encoding, 'replace') + line = src.split('\n')[lineno-1] + self.assertIn(line, cm.exception.text) + + def test_error_offset_continuation_characters(self): + check = self.check + check('"\\\n"(1 for c in I,\\\n\\', 2, 2) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def testSyntaxErrorOffset(self): + check = self.check check('def fact(x):\n\treturn x!\n', 2, 10) check('1 +\n', 1, 4) check('def spam():\n print(1)\n print(2)', 3, 10) check('Python = "Python" +', 1, 20) check('Python = "\u1e54\xfd\u0163\u0125\xf2\xf1" +', 1, 20) - check('x = "a', 1, 7) + check(b'# -*- coding: cp1251 -*-\nPython = "\xcf\xb3\xf2\xee\xed" +', + 2, 19, encoding='cp1251') + check(b'Python = "\xcf\xb3\xf2\xee\xed" +', 1, 18) + check('x = "a', 1, 5) check('lambda x: x = 2', 1, 1) + check('f{a + b + c}', 1, 2) + check('[file for str(file) in []\n]', 1, 11) + check('a = « hello » « world »', 1, 5) + check('[\nfile\nfor str(file)\nin\n[]\n]', 3, 5) + check('[file for\n str(file) in []]', 2, 2) + check("ages = {'Alice'=22, 'Bob'=23}", 1, 9) + check('match ...:\n case {**rest, "key": value}:\n ...', 2, 19) + check("[a b c d e f]", 1, 2) + check("for x yfff:", 1, 7) + check("f(a for a in b, c)", 1, 3, 1, 15) + check("f(a for a in b if a, c)", 1, 3, 1, 20) + check("f(a, b for b in c)", 1, 6, 1, 18) + check("f(a, b for b in c, d)", 1, 6, 1, 18) # Errors thrown by compile.c check('class foo:return 1', 1, 11) check('def f():\n continue', 2, 3) check('def f():\n break', 2, 3) - check('try:\n pass\nexcept:\n pass\nexcept ValueError:\n pass', 2, 3) + check('try:\n pass\nexcept:\n pass\nexcept ValueError:\n pass', 3, 1) # Errors thrown by tokenizer.c check('(0x+1)', 1, 3) check('x = 0xI', 1, 6) - check('0010 + 2', 1, 4) + check('0010 + 2', 1, 1) check('x = 32e-+4', 1, 8) - check('x = 0o9', 1, 6) + check('x = 0o9', 1, 7) + check('\u03b1 = 0xI', 1, 6) + check(b'\xce\xb1 = 0xI', 1, 6) + check(b'# -*- coding: iso8859-7 -*-\n\xe1 = 0xI', 2, 6, + encoding='iso8859-7') + check(b"""if 1: + def foo(): + ''' + + def bar(): + pass + + def baz(): + '''quux''' + """, 9, 24) + check("pass\npass\npass\n(1+)\npass\npass\npass", 4, 4) + check("(1+)", 1, 4) + check("[interesting\nfoo()\n", 1, 1) + check(b"\xef\xbb\xbf#coding: utf8\nprint('\xe6\x88\x91')\n", 0, -1) + check("""f''' + { + (123_a) + }'''""", 3, 17) + check("""f''' + { + f\"\"\" + { + (123_a) + } + \"\"\" + }'''""", 5, 17) + check('''f""" + + + { + 6 + 0="""''', 5, 13) # Errors thrown by symtable.c - check('x = [(yield i) for i in range(3)]', 1, 5) - check('def f():\n from _ import *', 1, 1) - check('def f(x, x):\n pass', 1, 1) + check('x = [(yield i) for i in range(3)]', 1, 7) + check('def f():\n from _ import *', 2, 17) + check('def f(x, x):\n pass', 1, 10) + check('{i for i in range(5) if (j := 0) for j in range(5)}', 1, 38) check('def f(x):\n nonlocal x', 2, 3) check('def f(x):\n x = 1\n global x', 3, 3) check('nonlocal x', 1, 1) check('def f():\n global x\n nonlocal x', 2, 3) - # Errors thrown by ast.c - check('for 1 in []: pass', 1, 5) - check('def f(*):\n pass', 1, 7) - check('[*x for x in xs]', 1, 2) - check('def f():\n x, y: int', 2, 3) - check('(yield i) = 2', 1, 1) - check('foo(x for x in range(10), 100)', 1, 5) - check('foo(1=2)', 1, 5) - # Errors thrown by future.c check('from __future__ import doesnt_exist', 1, 1) check('from __future__ import braces', 1, 1) check('x=1\nfrom __future__ import division', 2, 1) - + check('foo(1=2)', 1, 5) + check('def f():\n x, y: int', 2, 3) + check('[*x for x in xs]', 1, 2) + check('foo(x for x in range(10), 100)', 1, 5) + check('for 1 in []: pass', 1, 5) + check('(yield i) = 2', 1, 2) + check('def f(*):\n pass', 1, 7) @cpython_only def testSettingException(self): @@ -373,25 +456,31 @@ class ExceptionTests(unittest.TestCase): 'filename' : 'filenameStr', 'filename2' : None}), (SyntaxError, (), {'msg' : None, 'text' : None, 'filename' : None, 'lineno' : None, 'offset' : None, - 'print_file_and_line' : None}), + 'end_offset': None, 'print_file_and_line' : None}), (SyntaxError, ('msgStr',), {'args' : ('msgStr',), 'text' : None, 'print_file_and_line' : None, 'msg' : 'msgStr', - 'filename' : None, 'lineno' : None, 'offset' : None}), + 'filename' : None, 'lineno' : None, 'offset' : None, + 'end_offset': None}), (SyntaxError, ('msgStr', ('filenameStr', 'linenoStr', 'offsetStr', - 'textStr')), + 'textStr', 'endLinenoStr', 'endOffsetStr')), {'offset' : 'offsetStr', 'text' : 'textStr', 'args' : ('msgStr', ('filenameStr', 'linenoStr', - 'offsetStr', 'textStr')), + 'offsetStr', 'textStr', + 'endLinenoStr', 'endOffsetStr')), 'print_file_and_line' : None, 'msg' : 'msgStr', - 'filename' : 'filenameStr', 'lineno' : 'linenoStr'}), + 'filename' : 'filenameStr', 'lineno' : 'linenoStr', + 'end_lineno': 'endLinenoStr', 'end_offset': 'endOffsetStr'}), (SyntaxError, ('msgStr', 'filenameStr', 'linenoStr', 'offsetStr', - 'textStr', 'print_file_and_lineStr'), + 'textStr', 'endLinenoStr', 'endOffsetStr', + 'print_file_and_lineStr'), {'text' : None, 'args' : ('msgStr', 'filenameStr', 'linenoStr', 'offsetStr', - 'textStr', 'print_file_and_lineStr'), + 'textStr', 'endLinenoStr', 'endOffsetStr', + 'print_file_and_lineStr'), 'print_file_and_line' : None, 'msg' : 'msgStr', - 'filename' : None, 'lineno' : None, 'offset' : None}), + 'filename' : None, 'lineno' : None, 'offset' : None, + 'end_lineno': None, 'end_offset': None}), (UnicodeError, (), {'args' : (),}), (UnicodeEncodeError, ('ascii', 'a', 0, 1, 'ordinal not in range'), @@ -437,7 +526,7 @@ class ExceptionTests(unittest.TestCase): e = exc(*args) except: print("\nexc=%r, args=%r" % (exc, args), file=sys.stderr) - raise + # raise else: # Verify module name if not type(e).__name__.endswith('NaiveException'): @@ -583,15 +672,27 @@ class ExceptionTests(unittest.TestCase): self.assertTrue(str(Exception('a'))) self.assertTrue(str(Exception('a', 'b'))) - def testExceptionCleanupNames(self): + def test_exception_cleanup_names(self): # Make sure the local variable bound to the exception instance by # an "except" statement is only visible inside the except block. try: raise Exception() except Exception as e: - self.assertTrue(e) + self.assertIsInstance(e, Exception) + self.assertNotIn('e', locals()) + with self.assertRaises(UnboundLocalError): + e + + def test_exception_cleanup_names2(self): + # Make sure the cleanup doesn't break if the variable is explicitly deleted. + try: + raise Exception() + except Exception as e: + self.assertIsInstance(e, Exception) del e self.assertNotIn('e', locals()) + with self.assertRaises(UnboundLocalError): + e # TODO: RUSTPYTHON @unittest.expectedFailure @@ -618,6 +719,7 @@ class ExceptionTests(unittest.TestCase): except MyException as e: pass obj = None + gc_collect() # For PyPy or other GCs. obj = wr() self.assertIsNone(obj) @@ -629,6 +731,7 @@ class ExceptionTests(unittest.TestCase): except MyException: pass obj = None + gc_collect() # For PyPy or other GCs. obj = wr() self.assertIsNone(obj) @@ -640,6 +743,7 @@ class ExceptionTests(unittest.TestCase): except: pass obj = None + gc_collect() # For PyPy or other GCs. obj = wr() self.assertIsNone(obj) @@ -652,6 +756,7 @@ class ExceptionTests(unittest.TestCase): except: break obj = None + gc_collect() # For PyPy or other GCs. obj = wr() self.assertIsNone(obj) @@ -670,6 +775,7 @@ class ExceptionTests(unittest.TestCase): # must clear the latter manually for our test to succeed. e.__context__ = None obj = None + gc_collect() # For PyPy or other GCs. obj = wr() # guarantee no ref cycles on CPython (don't gc_collect) if check_impl_detail(cpython=False): @@ -860,6 +966,7 @@ class ExceptionTests(unittest.TestCase): next(g) testfunc(g) g = obj = None + gc_collect() # For PyPy or other GCs. obj = wr() self.assertIsNone(obj) @@ -913,8 +1020,152 @@ class ExceptionTests(unittest.TestCase): # raise Exception(MyObject()) # except: # pass + # gc_collect() # For PyPy or other GCs. # self.assertEqual(e, (None, None, None)) + def test_raise_does_not_create_context_chain_cycle(self): + class A(Exception): + pass + class B(Exception): + pass + class C(Exception): + pass + + # Create a context chain: + # C -> B -> A + # Then raise A in context of C. + try: + try: + raise A + except A as a_: + a = a_ + try: + raise B + except B as b_: + b = b_ + try: + raise C + except C as c_: + c = c_ + self.assertIsInstance(a, A) + self.assertIsInstance(b, B) + self.assertIsInstance(c, C) + self.assertIsNone(a.__context__) + self.assertIs(b.__context__, a) + self.assertIs(c.__context__, b) + raise a + except A as e: + exc = e + + # Expect A -> C -> B, without cycle + self.assertIs(exc, a) + self.assertIs(a.__context__, c) + self.assertIs(c.__context__, b) + self.assertIsNone(b.__context__) + + def test_no_hang_on_context_chain_cycle1(self): + # See issue 25782. Cycle in context chain. + + def cycle(): + try: + raise ValueError(1) + except ValueError as ex: + ex.__context__ = ex + raise TypeError(2) + + try: + cycle() + except Exception as e: + exc = e + + self.assertIsInstance(exc, TypeError) + self.assertIsInstance(exc.__context__, ValueError) + self.assertIs(exc.__context__.__context__, exc.__context__) + + @unittest.skip("See issue 44895") + def test_no_hang_on_context_chain_cycle2(self): + # See issue 25782. Cycle at head of context chain. + + class A(Exception): + pass + class B(Exception): + pass + class C(Exception): + pass + + # Context cycle: + # +-----------+ + # V | + # C --> B --> A + with self.assertRaises(C) as cm: + try: + raise A() + except A as _a: + a = _a + try: + raise B() + except B as _b: + b = _b + try: + raise C() + except C as _c: + c = _c + a.__context__ = c + raise c + + self.assertIs(cm.exception, c) + # Verify the expected context chain cycle + self.assertIs(c.__context__, b) + self.assertIs(b.__context__, a) + self.assertIs(a.__context__, c) + + def test_no_hang_on_context_chain_cycle3(self): + # See issue 25782. Longer context chain with cycle. + + class A(Exception): + pass + class B(Exception): + pass + class C(Exception): + pass + class D(Exception): + pass + class E(Exception): + pass + + # Context cycle: + # +-----------+ + # V | + # E --> D --> C --> B --> A + with self.assertRaises(E) as cm: + try: + raise A() + except A as _a: + a = _a + try: + raise B() + except B as _b: + b = _b + try: + raise C() + except C as _c: + c = _c + a.__context__ = c + try: + raise D() + except D as _d: + d = _d + e = E() + raise e + + self.assertIs(cm.exception, e) + # Verify the expected context chain cycle + self.assertIs(e.__context__, d) + self.assertIs(d.__context__, c) + self.assertIs(c.__context__, b) + self.assertIs(b.__context__, a) + self.assertIs(a.__context__, c) + # TODO: RUSTPYTHON @unittest.expectedFailure def test_unicode_change_attributes(self): @@ -992,6 +1243,21 @@ class ExceptionTests(unittest.TestCase): self.assertIsInstance(v, RecursionError, type(v)) self.assertIn("maximum recursion depth exceeded", str(v)) + + @cpython_only + def test_trashcan_recursion(self): + # See bpo-33930 + + def foo(): + o = object() + for x in range(1_000_000): + # Create a big chain of method objects that will trigger + # a deep chain of calls when they need to be destructed. + o = o.__dir__ + + foo() + support.gc_collect() + @cpython_only def test_recursion_normalizing_exception(self): # Issue #22898. @@ -1005,7 +1271,7 @@ class ExceptionTests(unittest.TestCase): # finalization of these locals. code = """if 1: import sys - from _testcapi import get_recursion_depth + from _testinternalcapi import get_recursion_depth class MyException(Exception): pass @@ -1039,7 +1305,7 @@ class ExceptionTests(unittest.TestCase): # tstate->recursion_depth is equal to (recursion_limit - 1) # and is equal to recursion_limit when _gen_throw() calls # PyErr_NormalizeException(). - recurse(setrecursionlimit(depth + 2) - depth - 1) + recurse(setrecursionlimit(depth + 2) - depth) finally: sys.setrecursionlimit(recursionlimit) print('Done.') @@ -1069,6 +1335,54 @@ class ExceptionTests(unittest.TestCase): b'while normalizing an exception', err) self.assertIn(b'Done.', out) + + def test_recursion_in_except_handler(self): + + def set_relative_recursion_limit(n): + depth = 1 + while True: + try: + sys.setrecursionlimit(depth) + except RecursionError: + depth += 1 + else: + break + sys.setrecursionlimit(depth+n) + + def recurse_in_except(): + try: + 1/0 + except: + recurse_in_except() + + def recurse_after_except(): + try: + 1/0 + except: + pass + recurse_after_except() + + def recurse_in_body_and_except(): + try: + recurse_in_body_and_except() + except: + recurse_in_body_and_except() + + recursionlimit = sys.getrecursionlimit() + try: + set_relative_recursion_limit(10) + for func in (recurse_in_except, recurse_after_except, recurse_in_body_and_except): + with self.subTest(func=func): + try: + func() + except RecursionError: + pass + else: + self.fail("Should have raised a RecursionError") + finally: + sys.setrecursionlimit(recursionlimit) + + @cpython_only def test_recursion_normalizing_with_no_memory(self): # Issue #30697. Test that in the abort that occurs when there is no @@ -1089,8 +1403,9 @@ class ExceptionTests(unittest.TestCase): """ with SuppressCrashReport(): rc, out, err = script_helper.assert_python_failure("-c", code) - self.assertIn(b'Fatal Python error: Cannot recover from ' - b'MemoryErrors while normalizing exceptions.', err) + self.assertIn(b'Fatal Python error: _PyErr_NormalizeException: ' + b'Cannot recover from MemoryErrors while ' + b'normalizing exceptions.', err) @cpython_only def test_MemoryError(self): @@ -1104,7 +1419,7 @@ class ExceptionTests(unittest.TestCase): except MemoryError as e: tb = e.__traceback__ else: - self.fail("Should have raises a MemoryError") + self.fail("Should have raised a MemoryError") return traceback.format_tb(tb) tb1 = raiseMemError() @@ -1171,6 +1486,7 @@ class ExceptionTests(unittest.TestCase): self.assertNotEqual(wr(), None) else: self.fail("MemoryError not raised") + gc_collect() # For PyPy or other GCs. self.assertEqual(wr(), None) # TODO: RUSTPYTHON @@ -1194,8 +1510,9 @@ class ExceptionTests(unittest.TestCase): self.assertNotEqual(wr(), None) else: self.fail("RecursionError not raised") + gc_collect() # For PyPy or other GCs. self.assertEqual(wr(), None) - + @unittest.skipIf(sys.platform == 'win32', 'error specific to cpython') def test_errno_ENOTDIR(self): # Issue #12802: "not a directory" errors are ENOTDIR even on Windows @@ -1213,29 +1530,13 @@ class ExceptionTests(unittest.TestCase): # The following line is included in the traceback report: raise exc - class BrokenExceptionDel: - def __del__(self): - exc = BrokenStrException() - # The following line is included in the traceback report: - raise exc + obj = BrokenDel() + with support.catch_unraisable_exception() as cm: + del obj - for test_class in (BrokenDel, BrokenExceptionDel): - with self.subTest(test_class): - obj = test_class() - with captured_stderr() as stderr: - del obj - report = stderr.getvalue() - self.assertIn("Exception ignored", report) - self.assertIn(test_class.__del__.__qualname__, report) - self.assertIn("test_exceptions.py", report) - self.assertIn("raise exc", report) - if test_class is BrokenExceptionDel: - self.assertIn("BrokenStrException", report) - self.assertIn("", report) - else: - self.assertIn("ValueError", report) - self.assertIn("del is broken", report) - self.assertTrue(report.endswith("\n")) + gc_collect() # For PyPy or other GCs. + self.assertEqual(cm.unraisable.object, BrokenDel.__del__) + self.assertIsNotNone(cm.unraisable.exc_traceback) # TODO: RUSTPYTHON @unittest.expectedFailure @@ -1333,6 +1634,52 @@ class ExceptionTests(unittest.TestCase): next(i) next(i) + @unittest.skipUnless(__debug__, "Won't work if __debug__ is False") + def test_assert_shadowing(self): + # Shadowing AssertionError would cause the assert statement to + # misbehave. + global AssertionError + AssertionError = TypeError + try: + assert False, 'hello' + except BaseException as e: + del AssertionError + self.assertIsInstance(e, AssertionError) + self.assertEqual(str(e), 'hello') + else: + del AssertionError + self.fail('Expected exception') + + def test_memory_error_subclasses(self): + # bpo-41654: MemoryError instances use a freelist of objects that are + # linked using the 'dict' attribute when they are inactive/dead. + # Subclasses of MemoryError should not participate in the freelist + # schema. This test creates a MemoryError object and keeps it alive + # (therefore advancing the freelist) and then it creates and destroys a + # subclass object. Finally, it checks that creating a new MemoryError + # succeeds, proving that the freelist is not corrupted. + + class TestException(MemoryError): + pass + + try: + raise MemoryError + except MemoryError as exc: + inst = exc + + try: + raise TestException + except Exception: + pass + + for _ in range(10): + try: + raise MemoryError + except MemoryError as exc: + pass + + gc_collect() + global_for_suggestions = None class NameErrorTests(unittest.TestCase): @@ -1567,7 +1914,7 @@ class NameErrorTests(unittest.TestCase): with support.captured_stderr() as err: sys.__excepthook__(*sys.exc_info()) - self.assertNotIn("a1", err.getvalue()) + self.assertNotRegex(err.getvalue(), r"NameError.*a1") def test_name_error_with_custom_exceptions(self): def f(): @@ -1608,6 +1955,37 @@ class NameErrorTests(unittest.TestCase): self.assertNotIn("something", err.getvalue()) + def test_issue45826(self): + # regression test for bpo-45826 + def f(): + with self.assertRaisesRegex(NameError, 'aaa'): + aab + + try: + f() + except self.failureException: + with support.captured_stderr() as err: + sys.__excepthook__(*sys.exc_info()) + + self.assertIn("aab", err.getvalue()) + + def test_issue45826_focused(self): + def f(): + try: + nonsense + except BaseException as E: + E.with_traceback(None) + raise ZeroDivisionError() + + try: + f() + except ZeroDivisionError: + with support.captured_stderr() as err: + sys.__excepthook__(*sys.exc_info()) + + self.assertIn("nonsense", err.getvalue()) + self.assertIn("ZeroDivisionError", err.getvalue()) + class AttributeErrorTests(unittest.TestCase): # TODO: RUSTPYTHON @@ -1862,8 +2240,26 @@ class AttributeErrorTests(unittest.TestCase): self.assertNotIn("?", err.getvalue()) -class ImportErrorTests(unittest.TestCase): + def test_attribute_error_inside_nested_getattr(self): + class A: + bluch = 1 + class B: + def __getattribute__(self, attr): + a = A() + return a.blich + + try: + B().something + except AttributeError as exc: + with support.captured_stderr() as err: + sys.__excepthook__(*sys.exc_info()) + + self.assertIn("Did you mean", err.getvalue()) + self.assertIn("bluch", err.getvalue()) + + +class ImportErrorTests(unittest.TestCase): # TODO: RUSTPYTHON @unittest.expectedFailure def test_attributes(self): @@ -1942,6 +2338,281 @@ class ImportErrorTests(unittest.TestCase): self.assertEqual(exc.name, orig.name) self.assertEqual(exc.path, orig.path) +class SyntaxErrorTests(unittest.TestCase): + def test_range_of_offsets(self): + cases = [ + # Basic range from 2->7 + (("bad.py", 1, 2, "abcdefg", 1, 7), + dedent( + """ + File "bad.py", line 1 + abcdefg + ^^^^^ + SyntaxError: bad bad + """)), + # end_offset = start_offset + 1 + (("bad.py", 1, 2, "abcdefg", 1, 3), + dedent( + """ + File "bad.py", line 1 + abcdefg + ^ + SyntaxError: bad bad + """)), + # Negative end offset + (("bad.py", 1, 2, "abcdefg", 1, -2), + dedent( + """ + File "bad.py", line 1 + abcdefg + ^ + SyntaxError: bad bad + """)), + # end offset before starting offset + (("bad.py", 1, 4, "abcdefg", 1, 2), + dedent( + """ + File "bad.py", line 1 + abcdefg + ^ + SyntaxError: bad bad + """)), + # Both offsets negative + (("bad.py", 1, -4, "abcdefg", 1, -2), + dedent( + """ + File "bad.py", line 1 + abcdefg + SyntaxError: bad bad + """)), + # Both offsets negative and the end more negative + (("bad.py", 1, -4, "abcdefg", 1, -5), + dedent( + """ + File "bad.py", line 1 + abcdefg + SyntaxError: bad bad + """)), + # Both offsets 0 + (("bad.py", 1, 0, "abcdefg", 1, 0), + dedent( + """ + File "bad.py", line 1 + abcdefg + SyntaxError: bad bad + """)), + # Start offset 0 and end offset not 0 + (("bad.py", 1, 0, "abcdefg", 1, 5), + dedent( + """ + File "bad.py", line 1 + abcdefg + SyntaxError: bad bad + """)), + # End offset pass the source length + (("bad.py", 1, 2, "abcdefg", 1, 100), + dedent( + """ + File "bad.py", line 1 + abcdefg + ^^^^^^ + SyntaxError: bad bad + """)), + ] + for args, expected in cases: + with self.subTest(args=args): + try: + raise SyntaxError("bad bad", args) + except SyntaxError as exc: + with support.captured_stderr() as err: + sys.__excepthook__(*sys.exc_info()) + self.assertIn(expected, err.getvalue()) + the_exception = exc + + def test_encodings(self): + source = ( + '# -*- coding: cp437 -*-\n' + '"┬ó┬ó┬ó┬ó┬ó┬ó" + f(4, x for x in range(1))\n' + ) + try: + with open(TESTFN, 'w', encoding='cp437') as testfile: + testfile.write(source) + rc, out, err = script_helper.assert_python_failure('-Wd', '-X', 'utf8', TESTFN) + err = err.decode('utf-8').splitlines() + + self.assertEqual(err[-3], ' "┬ó┬ó┬ó┬ó┬ó┬ó" + f(4, x for x in range(1))') + self.assertEqual(err[-2], ' ^^^^^^^^^^^^^^^^^^^') + finally: + unlink(TESTFN) + + # Check backwards tokenizer errors + source = '# -*- coding: ascii -*-\n\n(\n' + try: + with open(TESTFN, 'w', encoding='ascii') as testfile: + testfile.write(source) + rc, out, err = script_helper.assert_python_failure('-Wd', '-X', 'utf8', TESTFN) + err = err.decode('utf-8').splitlines() + + self.assertEqual(err[-3], ' (') + self.assertEqual(err[-2], ' ^') + finally: + unlink(TESTFN) + + def test_non_utf8(self): + # Check non utf-8 characters + try: + with open(TESTFN, 'bw') as testfile: + testfile.write(b"\x89") + rc, out, err = script_helper.assert_python_failure('-Wd', '-X', 'utf8', TESTFN) + err = err.decode('utf-8').splitlines() + + self.assertIn("SyntaxError: Non-UTF-8 code starting with '\\x89' in file", err[-1]) + finally: + unlink(TESTFN) + + def test_attributes_new_constructor(self): + args = ("bad.py", 1, 2, "abcdefg", 1, 100) + the_exception = SyntaxError("bad bad", args) + filename, lineno, offset, error, end_lineno, end_offset = args + self.assertEqual(filename, the_exception.filename) + self.assertEqual(lineno, the_exception.lineno) + self.assertEqual(end_lineno, the_exception.end_lineno) + self.assertEqual(offset, the_exception.offset) + self.assertEqual(end_offset, the_exception.end_offset) + self.assertEqual(error, the_exception.text) + self.assertEqual("bad bad", the_exception.msg) + + def test_attributes_old_constructor(self): + args = ("bad.py", 1, 2, "abcdefg") + the_exception = SyntaxError("bad bad", args) + filename, lineno, offset, error = args + self.assertEqual(filename, the_exception.filename) + self.assertEqual(lineno, the_exception.lineno) + self.assertEqual(None, the_exception.end_lineno) + self.assertEqual(offset, the_exception.offset) + self.assertEqual(None, the_exception.end_offset) + self.assertEqual(error, the_exception.text) + self.assertEqual("bad bad", the_exception.msg) + + def test_incorrect_constructor(self): + args = ("bad.py", 1, 2) + self.assertRaises(TypeError, SyntaxError, "bad bad", args) + + args = ("bad.py", 1, 2, 4, 5, 6, 7) + self.assertRaises(TypeError, SyntaxError, "bad bad", args) + + args = ("bad.py", 1, 2, "abcdefg", 1) + self.assertRaises(TypeError, SyntaxError, "bad bad", args) + + +class PEP626Tests(unittest.TestCase): + + def lineno_after_raise(self, f, *expected): + try: + f() + except Exception as ex: + t = ex.__traceback__ + else: + self.fail("No exception raised") + lines = [] + t = t.tb_next # Skip this function + while t: + frame = t.tb_frame + lines.append( + None if frame.f_lineno is None else + frame.f_lineno-frame.f_code.co_firstlineno + ) + t = t.tb_next + self.assertEqual(tuple(lines), expected) + + def test_lineno_after_raise_simple(self): + def simple(): + 1/0 + pass + self.lineno_after_raise(simple, 1) + + def test_lineno_after_raise_in_except(self): + def in_except(): + try: + 1/0 + except: + 1/0 + pass + self.lineno_after_raise(in_except, 4) + + def test_lineno_after_other_except(self): + def other_except(): + try: + 1/0 + except TypeError as ex: + pass + self.lineno_after_raise(other_except, 3) + + def test_lineno_in_named_except(self): + def in_named_except(): + try: + 1/0 + except Exception as ex: + 1/0 + pass + self.lineno_after_raise(in_named_except, 4) + + def test_lineno_in_try(self): + def in_try(): + try: + 1/0 + finally: + pass + self.lineno_after_raise(in_try, 4) + + def test_lineno_in_finally_normal(self): + def in_finally_normal(): + try: + pass + finally: + 1/0 + pass + self.lineno_after_raise(in_finally_normal, 4) + + def test_lineno_in_finally_except(self): + def in_finally_except(): + try: + 1/0 + finally: + 1/0 + pass + self.lineno_after_raise(in_finally_except, 4) + + def test_lineno_after_with(self): + class Noop: + def __enter__(self): + return self + def __exit__(self, *args): + pass + def after_with(): + with Noop(): + 1/0 + pass + self.lineno_after_raise(after_with, 2) + + def test_missing_lineno_shows_as_none(self): + def f(): + 1/0 + self.lineno_after_raise(f, 1) + f.__code__ = f.__code__.replace(co_linetable=b'\x04\x80\xff\x80') + self.lineno_after_raise(f, None) + + def test_lineno_after_raise_in_with_exit(self): + class ExitFails: + def __enter__(self): + return self + def __exit__(self, *args): + raise ValueError + + def after_with(): + with ExitFails(): + 1/0 + self.lineno_after_raise(after_with, 1, 1) if __name__ == '__main__': unittest.main() From 43824e393b00b6b0518041c1f38c0e2f8f1557a0 Mon Sep 17 00:00:00 2001 From: Myeongseon Choi Date: Tue, 12 Jul 2022 01:19:57 +0900 Subject: [PATCH 2/8] Change Lib/test/test_exceptions.py runable --- Lib/test/test_exceptions.py | 89 ++++++++++++++++++++++++++----------- 1 file changed, 62 insertions(+), 27 deletions(-) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index ce6a3ca88..695b5bcb0 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -219,6 +219,8 @@ class ExceptionTests(unittest.TestCase): line = src.split('\n')[lineno-1] self.assertIn(line, cm.exception.text) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_error_offset_continuation_characters(self): check = self.check check('"\\\n"(1 for c in I,\\\n\\', 2, 2) @@ -672,6 +674,8 @@ class ExceptionTests(unittest.TestCase): self.assertTrue(str(Exception('a'))) self.assertTrue(str(Exception('a', 'b'))) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_exception_cleanup_names(self): # Make sure the local variable bound to the exception instance by # an "except" statement is only visible inside the except block. @@ -1063,6 +1067,9 @@ class ExceptionTests(unittest.TestCase): self.assertIs(c.__context__, b) self.assertIsNone(b.__context__) + + # TODO: RUSTPYTHON + @unittest.skip("Infinite loop") def test_no_hang_on_context_chain_cycle1(self): # See issue 25782. Cycle in context chain. @@ -1119,6 +1126,8 @@ class ExceptionTests(unittest.TestCase): self.assertIs(b.__context__, a) self.assertIs(a.__context__, c) + # TODO: RUSTPYTHON + @unittest.skip("Infinite loop") def test_no_hang_on_context_chain_cycle3(self): # See issue 25782. Longer context chain with cycle. @@ -1520,8 +1529,6 @@ class ExceptionTests(unittest.TestCase): os.listdir(__file__) self.assertEqual(cm.exception.errno, errno.ENOTDIR, cm.exception) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_unraisable(self): # Issue #22836: PyErr_WriteUnraisable() should give sensible reports class BrokenDel: @@ -1634,6 +1641,8 @@ class ExceptionTests(unittest.TestCase): next(i) next(i) + # TODO: RUSTPYTHON + @unittest.expectedFailure @unittest.skipUnless(__debug__, "Won't work if __debug__ is False") def test_assert_shadowing(self): # Shadowing AssertionError would cause the assert statement to @@ -1955,36 +1964,38 @@ class NameErrorTests(unittest.TestCase): self.assertNotIn("something", err.getvalue()) - def test_issue45826(self): - # regression test for bpo-45826 - def f(): - with self.assertRaisesRegex(NameError, 'aaa'): - aab + # TODO: RUSTPYTHON + # def test_issue45826(self): + # # regression test for bpo-45826 + # def f(): + # with self.assertRaisesRegex(NameError, 'aaa'): + # aab - try: - f() - except self.failureException: - with support.captured_stderr() as err: - sys.__excepthook__(*sys.exc_info()) + # try: + # f() + # except self.failureException: + # with support.captured_stderr() as err: + # sys.__excepthook__(*sys.exc_info()) - self.assertIn("aab", err.getvalue()) + # self.assertIn("aab", err.getvalue()) - def test_issue45826_focused(self): - def f(): - try: - nonsense - except BaseException as E: - E.with_traceback(None) - raise ZeroDivisionError() + # TODO: RUSTPYTHON + # def test_issue45826_focused(self): + # def f(): + # try: + # nonsense + # except BaseException as E: + # E.with_traceback(None) + # raise ZeroDivisionError() - try: - f() - except ZeroDivisionError: - with support.captured_stderr() as err: - sys.__excepthook__(*sys.exc_info()) + # try: + # f() + # except ZeroDivisionError: + # with support.captured_stderr() as err: + # sys.__excepthook__(*sys.exc_info()) - self.assertIn("nonsense", err.getvalue()) - self.assertIn("ZeroDivisionError", err.getvalue()) + # self.assertIn("nonsense", err.getvalue()) + # self.assertIn("ZeroDivisionError", err.getvalue()) class AttributeErrorTests(unittest.TestCase): @@ -2240,6 +2251,8 @@ class AttributeErrorTests(unittest.TestCase): self.assertNotIn("?", err.getvalue()) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_attribute_error_inside_nested_getattr(self): class A: bluch = 1 @@ -2339,6 +2352,8 @@ class ImportErrorTests(unittest.TestCase): self.assertEqual(exc.path, orig.path) class SyntaxErrorTests(unittest.TestCase): + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_range_of_offsets(self): cases = [ # Basic range from 2->7 @@ -2429,6 +2444,8 @@ class SyntaxErrorTests(unittest.TestCase): self.assertIn(expected, err.getvalue()) the_exception = exc + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_encodings(self): source = ( '# -*- coding: cp437 -*-\n' @@ -2458,6 +2475,8 @@ class SyntaxErrorTests(unittest.TestCase): finally: unlink(TESTFN) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_non_utf8(self): # Check non utf-8 characters try: @@ -2470,6 +2489,8 @@ class SyntaxErrorTests(unittest.TestCase): finally: unlink(TESTFN) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_attributes_new_constructor(self): args = ("bad.py", 1, 2, "abcdefg", 1, 100) the_exception = SyntaxError("bad bad", args) @@ -2482,6 +2503,8 @@ class SyntaxErrorTests(unittest.TestCase): self.assertEqual(error, the_exception.text) self.assertEqual("bad bad", the_exception.msg) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_attributes_old_constructor(self): args = ("bad.py", 1, 2, "abcdefg") the_exception = SyntaxError("bad bad", args) @@ -2494,6 +2517,8 @@ class SyntaxErrorTests(unittest.TestCase): self.assertEqual(error, the_exception.text) self.assertEqual("bad bad", the_exception.msg) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_incorrect_constructor(self): args = ("bad.py", 1, 2) self.assertRaises(TypeError, SyntaxError, "bad bad", args) @@ -2540,6 +2565,8 @@ class PEP626Tests(unittest.TestCase): pass self.lineno_after_raise(in_except, 4) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_lineno_after_other_except(self): def other_except(): try: @@ -2557,6 +2584,8 @@ class PEP626Tests(unittest.TestCase): pass self.lineno_after_raise(in_named_except, 4) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_lineno_in_try(self): def in_try(): try: @@ -2583,6 +2612,8 @@ class PEP626Tests(unittest.TestCase): pass self.lineno_after_raise(in_finally_except, 4) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_lineno_after_with(self): class Noop: def __enter__(self): @@ -2595,6 +2626,8 @@ class PEP626Tests(unittest.TestCase): pass self.lineno_after_raise(after_with, 2) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_missing_lineno_shows_as_none(self): def f(): 1/0 @@ -2602,6 +2635,8 @@ class PEP626Tests(unittest.TestCase): f.__code__ = f.__code__.replace(co_linetable=b'\x04\x80\xff\x80') self.lineno_after_raise(f, None) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_lineno_after_raise_in_with_exit(self): class ExitFails: def __enter__(self): From 82efdeb391fd592ab8cfd4d6562925b168dd8762 Mon Sep 17 00:00:00 2001 From: Myeongseon Choi Date: Tue, 12 Jul 2022 02:49:07 +0900 Subject: [PATCH 3/8] Update Lib/test/test_syntax.py from CPython v3.10.5 --- Lib/test/test_syntax.py | 983 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 925 insertions(+), 58 deletions(-) diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 7067a6e4d..190641b60 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -59,9 +59,17 @@ SyntaxError: cannot assign to __debug__ Traceback (most recent call last): SyntaxError: cannot assign to __debug__ +>>> del __debug__ +Traceback (most recent call last): +SyntaxError: cannot delete __debug__ + >>> f() = 1 Traceback (most recent call last): -SyntaxError: cannot assign to function call +SyntaxError: cannot assign to function call here. Maybe you meant '==' instead of '='? + +>>> yield = 1 +Traceback (most recent call last): +SyntaxError: assignment to yield expression not possible >>> del f() Traceback (most recent call last): @@ -69,7 +77,7 @@ SyntaxError: cannot delete function call >>> a + 1 = 2 Traceback (most recent call last): -SyntaxError: cannot assign to operator +SyntaxError: cannot assign to expression here. Maybe you meant '==' instead of '='? >>> (x for x in x) = 1 Traceback (most recent call last): @@ -77,19 +85,19 @@ SyntaxError: cannot assign to generator expression >>> 1 = 1 Traceback (most recent call last): -SyntaxError: cannot assign to literal +SyntaxError: cannot assign to literal here. Maybe you meant '==' instead of '='? >>> "abc" = 1 Traceback (most recent call last): -SyntaxError: cannot assign to literal +SyntaxError: cannot assign to literal here. Maybe you meant '==' instead of '='? >>> b"" = 1 Traceback (most recent call last): -SyntaxError: cannot assign to literal +SyntaxError: cannot assign to literal here. Maybe you meant '==' instead of '='? >>> ... = 1 Traceback (most recent call last): -SyntaxError: cannot assign to Ellipsis +SyntaxError: cannot assign to ellipsis here. Maybe you meant '==' instead of '='? >>> `1` = 1 Traceback (most recent call last): @@ -122,12 +130,198 @@ SyntaxError: cannot assign to __debug__ >>> [a, b, c + 1] = [1, 2, 3] Traceback (most recent call last): -SyntaxError: cannot assign to operator +SyntaxError: cannot assign to expression + +>>> [a, b[1], c + 1] = [1, 2, 3] +Traceback (most recent call last): +SyntaxError: cannot assign to expression + +>>> [a, b.c.d, c + 1] = [1, 2, 3] +Traceback (most recent call last): +SyntaxError: cannot assign to expression >>> a if 1 else b = 1 Traceback (most recent call last): SyntaxError: cannot assign to conditional expression +>>> a = 42 if True +Traceback (most recent call last): +SyntaxError: expected 'else' after 'if' expression + +>>> a = (42 if True) +Traceback (most recent call last): +SyntaxError: expected 'else' after 'if' expression + +>>> a = [1, 42 if True, 4] +Traceback (most recent call last): +SyntaxError: expected 'else' after 'if' expression + +>>> if True: +... print("Hello" +... +... if 2: +... print(123)) +Traceback (most recent call last): +SyntaxError: invalid syntax + +>>> True = True = 3 +Traceback (most recent call last): +SyntaxError: cannot assign to True + +>>> x = y = True = z = 3 +Traceback (most recent call last): +SyntaxError: cannot assign to True + +>>> x = y = yield = 1 +Traceback (most recent call last): +SyntaxError: assignment to yield expression not possible + +>>> a, b += 1, 2 +Traceback (most recent call last): +SyntaxError: 'tuple' is an illegal expression for augmented assignment + +>>> (a, b) += 1, 2 +Traceback (most recent call last): +SyntaxError: 'tuple' is an illegal expression for augmented assignment + +>>> [a, b] += 1, 2 +Traceback (most recent call last): +SyntaxError: 'list' is an illegal expression for augmented assignment + +Invalid targets in `for` loops and `with` statements should also +produce a specialized error message + +>>> for a() in b: pass +Traceback (most recent call last): +SyntaxError: cannot assign to function call + +>>> for (a, b()) in b: pass +Traceback (most recent call last): +SyntaxError: cannot assign to function call + +>>> for [a, b()] in b: pass +Traceback (most recent call last): +SyntaxError: cannot assign to function call + +>>> for (*a, b, c+1) in b: pass +Traceback (most recent call last): +SyntaxError: cannot assign to expression + +>>> for (x, *(y, z.d())) in b: pass +Traceback (most recent call last): +SyntaxError: cannot assign to function call + +>>> for a, b() in c: pass +Traceback (most recent call last): +SyntaxError: cannot assign to function call + +>>> for a, b, (c + 1, d()): pass +Traceback (most recent call last): +SyntaxError: cannot assign to expression + +>>> for i < (): pass +Traceback (most recent call last): +SyntaxError: invalid syntax + +>>> for a, b +Traceback (most recent call last): +SyntaxError: invalid syntax + +>>> with a as b(): pass +Traceback (most recent call last): +SyntaxError: cannot assign to function call + +>>> with a as (b, c()): pass +Traceback (most recent call last): +SyntaxError: cannot assign to function call + +>>> with a as [b, c()]: pass +Traceback (most recent call last): +SyntaxError: cannot assign to function call + +>>> with a as (*b, c, d+1): pass +Traceback (most recent call last): +SyntaxError: cannot assign to expression + +>>> with a as (x, *(y, z.d())): pass +Traceback (most recent call last): +SyntaxError: cannot assign to function call + +>>> with a as b, c as d(): pass +Traceback (most recent call last): +SyntaxError: cannot assign to function call + +>>> with a as b +Traceback (most recent call last): +SyntaxError: expected ':' + +>>> p = p = +Traceback (most recent call last): +SyntaxError: invalid syntax + +Comprehensions creating tuples without parentheses +should produce a specialized error message: + +>>> [x,y for x,y in range(100)] +Traceback (most recent call last): +SyntaxError: did you forget parentheses around the comprehension target? + +>>> {x,y for x,y in range(100)} +Traceback (most recent call last): +SyntaxError: did you forget parentheses around the comprehension target? + +# Missing commas in literals collections should not +# produce special error messages regarding missing +# parentheses, but about missing commas instead + +>>> [1, 2 3] +Traceback (most recent call last): +SyntaxError: invalid syntax. Perhaps you forgot a comma? + +>>> {1, 2 3} +Traceback (most recent call last): +SyntaxError: invalid syntax. Perhaps you forgot a comma? + +>>> {1:2, 2:5 3:12} +Traceback (most recent call last): +SyntaxError: invalid syntax. Perhaps you forgot a comma? + +>>> (1, 2 3) +Traceback (most recent call last): +SyntaxError: invalid syntax. Perhaps you forgot a comma? + +# Make sure soft keywords constructs don't raise specialized +# errors regarding missing commas or other spezialiced errors + +>>> match x: +... y = 3 +Traceback (most recent call last): +SyntaxError: invalid syntax + +>>> match x: +... case y: +... 3 $ 3 +Traceback (most recent call last): +SyntaxError: invalid syntax + +>>> match x: +... case $: +... ... +Traceback (most recent call last): +SyntaxError: invalid syntax + +>>> match ...: +... case {**rest, "key": value}: +... ... +Traceback (most recent call last): +SyntaxError: invalid syntax + +>>> match ...: +... case {**_}: +... ... +Traceback (most recent call last): +SyntaxError: invalid syntax + From compiler_complex_args(): >>> def f(None=1): @@ -135,7 +329,6 @@ From compiler_complex_args(): Traceback (most recent call last): SyntaxError: invalid syntax - From ast_for_arguments(): >>> def f(x, y=1, z): @@ -158,6 +351,16 @@ SyntaxError: invalid syntax Traceback (most recent call last): SyntaxError: invalid syntax +>>> import ast; ast.parse(''' +... def f( +... *, # type: int +... a, # type: int +... ): +... pass +... ''', type_comments=True) +Traceback (most recent call last): +SyntaxError: bare * has associated type comment + From ast_for_funcdef(): @@ -200,7 +403,7 @@ SyntaxError: Generator expression must be parenthesized >>> class C(x for x in L): ... pass Traceback (most recent call last): -SyntaxError: invalid syntax +SyntaxError: expected ':' >>> def g(*args, **kwargs): ... print(args, sorted(kwargs.items())) @@ -301,6 +504,11 @@ SyntaxError: invalid syntax Traceback (most recent call last): SyntaxError: expression cannot contain assignment, perhaps you meant "=="? +# Check that this error doesn't trigger for names: +>>> f(a={x: for x in {}}) +Traceback (most recent call last): +SyntaxError: invalid syntax + The grammar accepts any test (basically, any expression) in the keyword slot of a call site. Test a few different options. @@ -316,28 +524,37 @@ SyntaxError: expression cannot contain assignment, perhaps you meant "=="? >>> f((x)=2) Traceback (most recent call last): SyntaxError: expression cannot contain assignment, perhaps you meant "=="? ->>> f(True=2) +>>> f(True=1) Traceback (most recent call last): SyntaxError: cannot assign to True +>>> f(False=1) +Traceback (most recent call last): +SyntaxError: cannot assign to False +>>> f(None=1) +Traceback (most recent call last): +SyntaxError: cannot assign to None >>> f(__debug__=1) Traceback (most recent call last): SyntaxError: cannot assign to __debug__ +>>> __debug__: int +Traceback (most recent call last): +SyntaxError: cannot assign to __debug__ More set_context(): >>> (x for x in x) += 1 Traceback (most recent call last): -SyntaxError: cannot assign to generator expression +SyntaxError: 'generator expression' is an illegal expression for augmented assignment >>> None += 1 Traceback (most recent call last): -SyntaxError: cannot assign to None +SyntaxError: 'None' is an illegal expression for augmented assignment >>> __debug__ += 1 Traceback (most recent call last): SyntaxError: cannot assign to __debug__ >>> f() += 1 Traceback (most recent call last): -SyntaxError: cannot assign to function call +SyntaxError: 'function call' is an illegal expression for augmented assignment Test continue in finally in weird combinations. @@ -418,38 +635,6 @@ isn't, there should be a syntax error. ... SyntaxError: 'break' outside loop -This raises a SyntaxError, it used to raise a SystemError. -Context for this change can be found on issue #27514 - -In 2.5 there was a missing exception and an assert was triggered in a debug -build. The number of blocks must be greater than CO_MAXBLOCKS. SF #1565514 - - >>> while 1: - ... while 2: - ... while 3: - ... while 4: - ... while 5: - ... while 6: - ... while 8: - ... while 9: - ... while 10: - ... while 11: - ... while 12: - ... while 13: - ... while 14: - ... while 15: - ... while 16: - ... while 17: - ... while 18: - ... while 19: - ... while 20: - ... while 21: - ... while 22: - ... break - Traceback (most recent call last): - ... - SyntaxError: too many statically nested blocks - Misuse of the nonlocal and global statement can lead to a few unique syntax errors. >>> def f(): @@ -534,7 +719,7 @@ leading to spurious errors. ... pass Traceback (most recent call last): ... - SyntaxError: cannot assign to function call + SyntaxError: cannot assign to function call here. Maybe you meant '==' instead of '='? >>> if 1: ... pass @@ -542,7 +727,7 @@ leading to spurious errors. ... x() = 1 Traceback (most recent call last): ... - SyntaxError: cannot assign to function call + SyntaxError: cannot assign to function call here. Maybe you meant '==' instead of '='? >>> if 1: ... x() = 1 @@ -552,7 +737,7 @@ leading to spurious errors. ... pass Traceback (most recent call last): ... - SyntaxError: cannot assign to function call + SyntaxError: cannot assign to function call here. Maybe you meant '==' instead of '='? >>> if 1: ... pass @@ -562,7 +747,7 @@ leading to spurious errors. ... pass Traceback (most recent call last): ... - SyntaxError: cannot assign to function call + SyntaxError: cannot assign to function call here. Maybe you meant '==' instead of '='? >>> if 1: ... pass @@ -572,7 +757,342 @@ leading to spurious errors. ... x() = 1 Traceback (most recent call last): ... - SyntaxError: cannot assign to function call + SyntaxError: cannot assign to function call here. Maybe you meant '==' instead of '='? + + Missing ':' before suites: + + >>> def f() + ... pass + Traceback (most recent call last): + SyntaxError: expected ':' + + >>> class A + ... pass + Traceback (most recent call last): + SyntaxError: expected ':' + + >>> if 1 + ... pass + ... elif 1: + ... pass + ... else: + ... x() = 1 + Traceback (most recent call last): + SyntaxError: expected ':' + + >>> if 1: + ... pass + ... elif 1 + ... pass + ... else: + ... x() = 1 + Traceback (most recent call last): + SyntaxError: expected ':' + + >>> if 1: + ... pass + ... elif 1: + ... pass + ... else + ... x() = 1 + Traceback (most recent call last): + SyntaxError: expected ':' + + >>> for x in range(10) + ... pass + Traceback (most recent call last): + SyntaxError: expected ':' + + >>> while True + ... pass + Traceback (most recent call last): + SyntaxError: expected ':' + + >>> with blech as something + ... pass + Traceback (most recent call last): + SyntaxError: expected ':' + + >>> with blech + ... pass + Traceback (most recent call last): + SyntaxError: expected ':' + + >>> with blech, block as something + ... pass + Traceback (most recent call last): + SyntaxError: expected ':' + + >>> with blech, block as something, bluch + ... pass + Traceback (most recent call last): + SyntaxError: expected ':' + + >>> with (blech as something) + ... pass + Traceback (most recent call last): + SyntaxError: expected ':' + + >>> with (blech) + ... pass + Traceback (most recent call last): + SyntaxError: expected ':' + + >>> with (blech, block as something) + ... pass + Traceback (most recent call last): + SyntaxError: expected ':' + + >>> with (blech, block as something, bluch) + ... pass + Traceback (most recent call last): + SyntaxError: expected ':' + + >>> try + ... pass + Traceback (most recent call last): + SyntaxError: expected ':' + + >>> try: + ... pass + ... except + ... pass + Traceback (most recent call last): + SyntaxError: expected ':' + + >>> match x + ... case list(): + ... pass + Traceback (most recent call last): + SyntaxError: expected ':' + + >>> match x: + ... case list() + ... pass + Traceback (most recent call last): + SyntaxError: expected ':' + + >>> match x: + ... case [y] if y > 0 + ... pass + Traceback (most recent call last): + SyntaxError: expected ':' + + >>> if x = 3: + ... pass + Traceback (most recent call last): + SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='? + + >>> while x = 3: + ... pass + Traceback (most recent call last): + SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='? + + >>> if x.a = 3: + ... pass + Traceback (most recent call last): + SyntaxError: cannot assign to attribute here. Maybe you meant '==' instead of '='? + + >>> while x.a = 3: + ... pass + Traceback (most recent call last): + SyntaxError: cannot assign to attribute here. Maybe you meant '==' instead of '='? + +Custom error messages for try blocks that are not followed by except/finally + + >>> try: + ... x = 34 + ... + Traceback (most recent call last): + SyntaxError: expected 'except' or 'finally' block + +Ensure that early = are not matched by the parser as invalid comparisons + >>> f(2, 4, x=34); 1 $ 2 + Traceback (most recent call last): + SyntaxError: invalid syntax + + >>> dict(x=34); x $ y + Traceback (most recent call last): + SyntaxError: invalid syntax + + >>> dict(x=34, (x for x in range 10), 1); x $ y + Traceback (most recent call last): + SyntaxError: invalid syntax + + >>> dict(x=34, x=1, y=2); x $ y + Traceback (most recent call last): + SyntaxError: invalid syntax + +Incomplete dictionary literals + + >>> {1:2, 3:4, 5} + Traceback (most recent call last): + SyntaxError: ':' expected after dictionary key + + >>> {1:2, 3:4, 5:} + Traceback (most recent call last): + SyntaxError: expression expected after dictionary key and ':' + + >>> {1: *12+1, 23: 1} + Traceback (most recent call last): + SyntaxError: cannot use a starred expression in a dictionary value + + >>> {1: *12+1} + Traceback (most recent call last): + SyntaxError: cannot use a starred expression in a dictionary value + + >>> {1: 23, 1: *12+1} + Traceback (most recent call last): + SyntaxError: cannot use a starred expression in a dictionary value + + >>> {1:} + Traceback (most recent call last): + SyntaxError: expression expected after dictionary key and ':' + + # Ensure that the error is not raise for syntax errors that happen after sets + + >>> {1} $ + Traceback (most recent call last): + SyntaxError: invalid syntax + +Specialized indentation errors: + + >>> while condition: + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after 'while' statement on line 1 + + >>> for x in range(10): + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after 'for' statement on line 1 + + >>> for x in range(10): + ... pass + ... else: + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after 'else' statement on line 3 + + >>> async for x in range(10): + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after 'for' statement on line 1 + + >>> async for x in range(10): + ... pass + ... else: + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after 'else' statement on line 3 + + >>> if something: + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after 'if' statement on line 1 + + >>> if something: + ... pass + ... elif something_else: + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after 'elif' statement on line 3 + + >>> if something: + ... pass + ... elif something_else: + ... pass + ... else: + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after 'else' statement on line 5 + + >>> try: + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after 'try' statement on line 1 + + >>> try: + ... something() + ... except: + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after 'except' statement on line 3 + + >>> try: + ... something() + ... except A: + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after 'except' statement on line 3 + + >>> try: + ... something() + ... except A: + ... pass + ... finally: + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after 'finally' statement on line 5 + + >>> with A: + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after 'with' statement on line 1 + + >>> with A as a, B as b: + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after 'with' statement on line 1 + + >>> with (A as a, B as b): + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after 'with' statement on line 1 + + >>> async with A: + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after 'with' statement on line 1 + + >>> async with A as a, B as b: + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after 'with' statement on line 1 + + >>> async with (A as a, B as b): + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after 'with' statement on line 1 + + >>> def foo(x, /, y, *, z=2): + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after function definition on line 1 + + >>> class Blech(A): + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after class definition on line 1 + + >>> match something: + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after 'match' statement on line 1 + + >>> match something: + ... case []: + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after 'case' statement on line 2 + + >>> match something: + ... case []: + ... ... + ... case {}: + ... pass + Traceback (most recent call last): + IndentationError: expected an indented block after 'case' statement on line 4 Make sure that the old "raise X, Y[, Z]" form is gone: >>> raise X, Y @@ -584,27 +1104,108 @@ Make sure that the old "raise X, Y[, Z]" form is gone: ... SyntaxError: invalid syntax +Check that an multiple exception types with missing parentheses +raise a custom exception + + >>> try: + ... pass + ... except A, B: + ... pass + Traceback (most recent call last): + SyntaxError: multiple exception types must be parenthesized + + >>> try: + ... pass + ... except A, B, C: + ... pass + Traceback (most recent call last): + SyntaxError: multiple exception types must be parenthesized + + >>> try: + ... pass + ... except A, B, C as blech: + ... pass + Traceback (most recent call last): + SyntaxError: multiple exception types must be parenthesized + + >>> try: + ... pass + ... except A, B, C as blech: + ... pass + ... finally: + ... pass + Traceback (most recent call last): + SyntaxError: multiple exception types must be parenthesized + >>> f(a=23, a=234) Traceback (most recent call last): ... -SyntaxError: keyword argument repeated +SyntaxError: keyword argument repeated: a >>> {1, 2, 3} = 42 Traceback (most recent call last): -SyntaxError: cannot assign to set display +SyntaxError: cannot assign to set display here. Maybe you meant '==' instead of '='? >>> {1: 2, 3: 4} = 42 Traceback (most recent call last): -SyntaxError: cannot assign to dict display +SyntaxError: cannot assign to dict literal here. Maybe you meant '==' instead of '='? >>> f'{x}' = 42 Traceback (most recent call last): -SyntaxError: cannot assign to f-string expression +SyntaxError: cannot assign to f-string expression here. Maybe you meant '==' instead of '='? >>> f'{x}-{y}' = 42 Traceback (most recent call last): -SyntaxError: cannot assign to f-string expression +SyntaxError: cannot assign to f-string expression here. Maybe you meant '==' instead of '='? + +>>> (x, y, z=3, d, e) +Traceback (most recent call last): +SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='? + +>>> [x, y, z=3, d, e] +Traceback (most recent call last): +SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='? + +>>> [z=3] +Traceback (most recent call last): +SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='? + +>>> {x, y, z=3, d, e} +Traceback (most recent call last): +SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='? + +>>> {z=3} +Traceback (most recent call last): +SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='? + +>>> from t import x, +Traceback (most recent call last): +SyntaxError: trailing comma not allowed without surrounding parentheses + +>>> from t import x,y, +Traceback (most recent call last): +SyntaxError: trailing comma not allowed without surrounding parentheses + +# Check that we dont raise the "trailing comma" error if there is more +# input to the left of the valid part that we parsed. + +>>> from t import x,y, and 3 +Traceback (most recent call last): +SyntaxError: invalid syntax + +>>> (): int +Traceback (most recent call last): +SyntaxError: only single target (not tuple) can be annotated +>>> []: int +Traceback (most recent call last): +SyntaxError: only single target (not list) can be annotated +>>> (()): int +Traceback (most recent call last): +SyntaxError: only single target (not tuple) can be annotated +>>> ([]): int +Traceback (most recent call last): +SyntaxError: only single target (not list) can be annotated Corner-cases that used to fail to raise the correct error: @@ -634,6 +1235,47 @@ Corner-cases that used to crash: Traceback (most recent call last): SyntaxError: cannot assign to __debug__ + >>> import ä £ + Traceback (most recent call last): + SyntaxError: invalid character '£' (U+00A3) + + Invalid pattern matching constructs: + + >>> match ...: + ... case 42 as _: + ... ... + Traceback (most recent call last): + SyntaxError: cannot use '_' as a target + + >>> match ...: + ... case 42 as 1+2+4: + ... ... + Traceback (most recent call last): + SyntaxError: invalid pattern target + + >>> match ...: + ... case Foo(z=1, y=2, x): + ... ... + Traceback (most recent call last): + SyntaxError: positional patterns follow keyword patterns + + >>> match ...: + ... case Foo(a, z=1, y=2, x): + ... ... + Traceback (most recent call last): + SyntaxError: positional patterns follow keyword patterns + + >>> match ...: + ... case Foo(z=1, x, y=2): + ... ... + Traceback (most recent call last): + SyntaxError: positional patterns follow keyword patterns + + >>> match ...: + ... case C(a=b, c, d=e, f, g=h, i, j=k, ...): + ... ... + Traceback (most recent call last): + SyntaxError: positional patterns follow keyword patterns """ import re @@ -644,7 +1286,8 @@ from test import support class SyntaxTestCase(unittest.TestCase): def _check_error(self, code, errtext, - filename="", mode="exec", subclass=None, lineno=None, offset=None): + filename="", mode="exec", subclass=None, + lineno=None, offset=None, end_lineno=None, end_offset=None): """Check that compiling code raises SyntaxError with errtext. errtest is a regular expression that must be present in the @@ -658,20 +1301,63 @@ class SyntaxTestCase(unittest.TestCase): self.fail("SyntaxError is not a %s" % subclass.__name__) mo = re.search(errtext, str(err)) if mo is None: - self.fail("SyntaxError did not contain '%r'" % (errtext,)) + self.fail("SyntaxError did not contain %r" % (errtext,)) self.assertEqual(err.filename, filename) if lineno is not None: self.assertEqual(err.lineno, lineno) if offset is not None: self.assertEqual(err.offset, offset) + if end_lineno is not None: + self.assertEqual(err.end_lineno, end_lineno) + if end_offset is not None: + self.assertEqual(err.end_offset, end_offset) + else: self.fail("compile() did not raise SyntaxError") + def test_expression_with_assignment(self): + self._check_error( + "print(end1 + end2 = ' ')", + 'expression cannot contain assignment, perhaps you meant "=="?', + offset=7 + ) + + def test_curly_brace_after_primary_raises_immediately(self): + self._check_error("f{}", "invalid syntax", mode="single") + def test_assign_call(self): self._check_error("f() = 1", "assign") def test_assign_del(self): - self._check_error("del f()", "delete") + self._check_error("del (,)", "invalid syntax") + self._check_error("del 1", "cannot delete literal") + self._check_error("del (1, 2)", "cannot delete literal") + self._check_error("del None", "cannot delete None") + self._check_error("del *x", "cannot delete starred") + self._check_error("del (*x)", "cannot use starred expression") + self._check_error("del (*x,)", "cannot delete starred") + self._check_error("del [*x,]", "cannot delete starred") + self._check_error("del f()", "cannot delete function call") + self._check_error("del f(a, b)", "cannot delete function call") + self._check_error("del o.f()", "cannot delete function call") + self._check_error("del a[0]()", "cannot delete function call") + self._check_error("del x, f()", "cannot delete function call") + self._check_error("del f(), x", "cannot delete function call") + self._check_error("del [a, b, ((c), (d,), e.f())]", "cannot delete function call") + self._check_error("del (a if True else b)", "cannot delete conditional") + self._check_error("del +a", "cannot delete expression") + self._check_error("del a, +b", "cannot delete expression") + self._check_error("del a + b", "cannot delete expression") + self._check_error("del (a + b, c)", "cannot delete expression") + self._check_error("del (c[0], a + b)", "cannot delete expression") + self._check_error("del a.b.c + 2", "cannot delete expression") + self._check_error("del a.b.c[0] + 2", "cannot delete expression") + self._check_error("del (a, b, (c, d.e.f + 2))", "cannot delete expression") + self._check_error("del [a, b, (c, d.e.f[0] + 2)]", "cannot delete expression") + self._check_error("del (a := 5)", "cannot delete named expression") + # We don't have a special message for this, but make sure we don't + # report "cannot delete name" + self._check_error("del a += b", "invalid syntax") def test_global_param_err_first(self): source = """if 1: @@ -772,6 +1458,187 @@ class SyntaxTestCase(unittest.TestCase): "iterable argument unpacking follows " "keyword argument unpacking") + def test_generator_in_function_call(self): + self._check_error("foo(x, y for y in range(3) for z in range(2) if z , p)", + "Generator expression must be parenthesized", + lineno=1, end_lineno=1, offset=11, end_offset=53) + + def test_empty_line_after_linecont(self): + # See issue-40847 + s = r"""\ +pass + \ + +pass +""" + try: + compile(s, '', 'exec') + except SyntaxError: + self.fail("Empty line after a line continuation character is valid.") + + # See issue-46091 + s1 = r"""\ +def fib(n): + \ +'''Print a Fibonacci series up to n.''' + \ +a, b = 0, 1 +""" + s2 = r"""\ +def fib(n): + '''Print a Fibonacci series up to n.''' + a, b = 0, 1 +""" + try: + self.assertEqual(compile(s1, '', 'exec'), compile(s2, '', 'exec')) + except SyntaxError: + self.fail("Indented statement over multiple lines is valid") + + def test_continuation_bad_indentation(self): + # Check that code that breaks indentation across multiple lines raises a syntax error + + code = r"""\ +if x: + y = 1 + \ + foo = 1 + """ + + self.assertRaises(IndentationError, exec, code) + + @support.cpython_only + def test_nested_named_except_blocks(self): + code = "" + for i in range(12): + code += f"{' '*i}try:\n" + code += f"{' '*(i+1)}raise Exception\n" + code += f"{' '*i}except Exception as e:\n" + code += f"{' '*4*12}pass" + self._check_error(code, "too many statically nested blocks") + + def test_barry_as_flufl_with_syntax_errors(self): + # The "barry_as_flufl" rule can produce some "bugs-at-a-distance" if + # is reading the wrong token in the presence of syntax errors later + # in the file. See bpo-42214 for more information. + code = """ +def func1(): + if a != b: + raise ValueError + +def func2(): + try + return 1 + finally: + pass +""" + self._check_error(code, "expected ':'") + + def test_invalid_line_continuation_error_position(self): + self._check_error(r"a = 3 \ 4", + "unexpected character after line continuation character", + lineno=1, offset=8) + self._check_error('1,\\#\n2', + "unexpected character after line continuation character", + lineno=1, offset=4) + self._check_error('\nfgdfgf\n1,\\#\n2\n', + "unexpected character after line continuation character", + lineno=3, offset=4) + + def test_invalid_line_continuation_left_recursive(self): + # Check bpo-42218: SyntaxErrors following left-recursive rules + # (t_primary_raw in this case) need to be tested explicitly + self._check_error("A.\u018a\\ ", + "unexpected character after line continuation character") + self._check_error("A.\u03bc\\\n", + "unexpected EOF while parsing") + + def test_error_parenthesis(self): + for paren in "([{": + self._check_error(paren + "1 + 2", f"\\{paren}' was never closed") + + for paren in "([{": + self._check_error(f"a = {paren} 1, 2, 3\nb=3", f"\\{paren}' was never closed") + + for paren in ")]}": + self._check_error(paren + "1 + 2", f"unmatched '\\{paren}'") + + def test_match_call_does_not_raise_syntax_error(self): + code = """ +def match(x): + return 1+1 + +match(34) +""" + compile(code, "", "exec") + + def test_case_call_does_not_raise_syntax_error(self): + code = """ +def case(x): + return 1+1 + +case(34) +""" + compile(code, "", "exec") + + def test_multiline_compiler_error_points_to_the_end(self): + self._check_error( + "call(\na=1,\na=1\n)", + "keyword argument repeated", + lineno=3 + ) + + @support.cpython_only + def test_syntax_error_on_deeply_nested_blocks(self): + # This raises a SyntaxError, it used to raise a SystemError. Context + # for this change can be found on issue #27514 + + # In 2.5 there was a missing exception and an assert was triggered in a + # debug build. The number of blocks must be greater than CO_MAXBLOCKS. + # SF #1565514 + + source = """ +while 1: + while 2: + while 3: + while 4: + while 5: + while 6: + while 8: + while 9: + while 10: + while 11: + while 12: + while 13: + while 14: + while 15: + while 16: + while 17: + while 18: + while 19: + while 20: + while 21: + while 22: + break +""" + self._check_error(source, "too many statically nested blocks") + + @support.cpython_only + def test_error_on_parser_stack_overflow(self): + source = "-" * 100000 + "4" + for mode in ["exec", "eval", "single"]: + with self.subTest(mode=mode): + with self.assertRaises(MemoryError): + compile(source, "", mode) + + @support.cpython_only + def test_deep_invalid_rule(self): + # Check that a very deep invalid rule in the PEG + # parser doesn't have exponential backtracking. + source = "d{{{{{{{{{{{{{{{{{{{{{{{{{```{{{{{{{ef f():y" + with self.assertRaises(SyntaxError): + compile(source, "", "exec") + + def test_main(): support.run_unittest(SyntaxTestCase) from test import test_syntax From b490f5255bebb0d82075462a851cdc83067174f8 Mon Sep 17 00:00:00 2001 From: Myeongseon Choi Date: Tue, 12 Jul 2022 13:13:32 +0900 Subject: [PATCH 4/8] Update Lib/test/test_syntax.py to be runable --- Lib/test/test_syntax.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 190641b60..c668244b9 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -1315,6 +1315,8 @@ class SyntaxTestCase(unittest.TestCase): else: self.fail("compile() did not raise SyntaxError") + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_expression_with_assignment(self): self._check_error( "print(end1 + end2 = ' ')", @@ -1328,6 +1330,8 @@ class SyntaxTestCase(unittest.TestCase): def test_assign_call(self): self._check_error("f() = 1", "assign") + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_assign_del(self): self._check_error("del (,)", "invalid syntax") self._check_error("del 1", "cannot delete literal") @@ -1458,11 +1462,15 @@ class SyntaxTestCase(unittest.TestCase): "iterable argument unpacking follows " "keyword argument unpacking") + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_generator_in_function_call(self): self._check_error("foo(x, y for y in range(3) for z in range(2) if z , p)", "Generator expression must be parenthesized", lineno=1, end_lineno=1, offset=11, end_offset=53) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_empty_line_after_linecont(self): # See issue-40847 s = r"""\ @@ -1516,6 +1524,8 @@ if x: code += f"{' '*4*12}pass" self._check_error(code, "too many statically nested blocks") + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_barry_as_flufl_with_syntax_errors(self): # The "barry_as_flufl" rule can produce some "bugs-at-a-distance" if # is reading the wrong token in the presence of syntax errors later @@ -1544,6 +1554,7 @@ def func2(): "unexpected character after line continuation character", lineno=3, offset=4) + def test_invalid_line_continuation_left_recursive(self): # Check bpo-42218: SyntaxErrors following left-recursive rules # (t_primary_raw in this case) need to be tested explicitly @@ -1552,6 +1563,8 @@ def func2(): self._check_error("A.\u03bc\\\n", "unexpected EOF while parsing") + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_error_parenthesis(self): for paren in "([{": self._check_error(paren + "1 + 2", f"\\{paren}' was never closed") From e86a1bc92f527acf6e44861d378867349153af7e Mon Sep 17 00:00:00 2001 From: Myeongseon Choi Date: Tue, 12 Jul 2022 14:12:44 +0900 Subject: [PATCH 5/8] Copy Lib/test/test_cmd_line_script.py from CPython v3.10.5 --- Lib/test/test_cmd_line_script.py | 75 ++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py index 1f293f01e..826509fcc 100644 --- a/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py @@ -14,10 +14,11 @@ import io import textwrap from test import support +from test.support import import_helper +from test.support import os_helper from test.support.script_helper import ( make_pkg, make_script, make_zip_pkg, make_zip_script, assert_python_ok, assert_python_failure, spawn_python, kill_python) -from test.support import os_helper, import_helper verbose = support.verbose @@ -228,6 +229,19 @@ class CmdLineTest(unittest.TestCase): with os_helper.temp_dir() as script_dir: script_name = _make_test_script(script_dir, 'script') self._check_script(script_name, script_name, script_name, + script_dir, None, + importlib.machinery.SourceFileLoader, + expected_cwd=script_dir) + + def test_script_abspath(self): + # pass the script using the relative path, expect the absolute path + # in __file__ + with os_helper.temp_cwd() as script_dir: + self.assertTrue(os.path.isabs(script_dir), script_dir) + + script_name = _make_test_script(script_dir, 'script') + relative_name = os.path.basename(script_name) + self._check_script(relative_name, script_name, relative_name, script_dir, None, importlib.machinery.SourceFileLoader) @@ -399,14 +413,12 @@ class CmdLineTest(unittest.TestCase): script_name, script_name, script_dir, 'test_pkg', importlib.machinery.SourceFileLoader) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_issue8202_dash_c_file_ignored(self): # Make sure a "-c" file in the current directory # does not alter the value of sys.path[0] with os_helper.temp_dir() as script_dir: with os_helper.change_cwd(path=script_dir): - with open("-c", "w") as f: + with open("-c", "w", encoding="utf-8") as f: f.write("data") rc, out, err = assert_python_ok('-c', 'import sys; print("sys.path[0]==%r" % sys.path[0])', @@ -422,7 +434,7 @@ class CmdLineTest(unittest.TestCase): with os_helper.temp_dir() as script_dir: script_name = _make_test_script(script_dir, 'other') with os_helper.change_cwd(path=script_dir): - with open("-m", "w") as f: + with open("-m", "w", encoding="utf-8") as f: f.write("data") rc, out, err = assert_python_ok('-m', 'other', *example_args, __isolated=False) @@ -435,7 +447,7 @@ class CmdLineTest(unittest.TestCase): # will be failed. with os_helper.temp_dir() as script_dir: script_name = os.path.join(script_dir, "issue20884.py") - with open(script_name, "w", newline='\n') as f: + with open(script_name, "w", encoding="latin1", newline='\n') as f: f.write("#coding: iso-8859-1\n") f.write('"""\n') for _ in range(30): @@ -507,6 +519,16 @@ class CmdLineTest(unittest.TestCase): self.assertNotIn(b'is a package', err) self.assertNotIn(b'Traceback', err) + def test_hint_when_triying_to_import_a_py_file(self): + with os_helper.temp_dir() as script_dir, \ + os_helper.change_cwd(path=script_dir): + # Create invalid *.pyc as empty file + with open('asyncio.py', 'wb'): + pass + err = self.check_dash_m_failure('asyncio.py') + self.assertIn(b"Try using 'asyncio' instead " + b"of 'asyncio.py' as the module name", err) + def test_dash_m_init_traceback(self): # These were wrapped in an ImportError and tracebacks were # suppressed; see Issue 14285 @@ -546,7 +568,7 @@ class CmdLineTest(unittest.TestCase): script_name = _make_test_script(script_dir, 'script', script) exitcode, stdout, stderr = assert_python_failure(script_name) text = stderr.decode('ascii').split('\n') - self.assertEqual(len(text), 4) + self.assertEqual(len(text), 5) self.assertTrue(text[0].startswith('Traceback')) self.assertTrue(text[1].startswith(' File ')) self.assertTrue(text[3].startswith('NameError')) @@ -565,7 +587,7 @@ class CmdLineTest(unittest.TestCase): # Issue #16218 source = 'print(ascii(__file__))\n' - script_name = _make_test_script(os.curdir, name, source) + script_name = _make_test_script(os.getcwd(), name, source) self.addCleanup(os_helper.unlink, script_name) rc, stdout, stderr = assert_python_ok(script_name) self.assertEqual( @@ -574,10 +596,6 @@ class CmdLineTest(unittest.TestCase): 'stdout=%r stderr=%r' % (stdout, stderr)) self.assertEqual(0, rc) - # TODO: RUSTPYTHON - if sys.platform == "linux": - test_non_ascii = unittest.expectedFailure(test_non_ascii) - # TODO: RUSTPYTHON @unittest.expectedFailure def test_issue20500_exit_with_exception_value(self): @@ -596,7 +614,7 @@ class CmdLineTest(unittest.TestCase): script_name = _make_test_script(script_dir, 'script', script) exitcode, stdout, stderr = assert_python_failure(script_name) text = stderr.decode('ascii') - self.assertEqual(text, "some text") + self.assertEqual(text.rstrip(), "some text") # TODO: RUSTPYTHON @unittest.expectedFailure @@ -606,8 +624,8 @@ class CmdLineTest(unittest.TestCase): script_name = _make_test_script(script_dir, 'script', script) exitcode, stdout, stderr = assert_python_failure(script_name) text = io.TextIOWrapper(io.BytesIO(stderr), 'ascii').read() - # Confirm that the caret is located under the first 1 character - self.assertIn("\n 1 + 1 = 2\n ^", text) + # Confirm that the caret is located under the '=' sign + self.assertIn("\n ^^^^^\n", text) # TODO: RUSTPYTHON @unittest.expectedFailure @@ -620,8 +638,8 @@ class CmdLineTest(unittest.TestCase): script_name = _make_test_script(script_dir, 'script', script) exitcode, stdout, stderr = assert_python_failure(script_name) text = io.TextIOWrapper(io.BytesIO(stderr), 'ascii').read() - # Confirm that the caret is located under the first 1 character - self.assertIn("\n 1 + 1 = 2\n ^", text) + # Confirm that the caret starts under the first 1 character + self.assertIn("\n 1 + 1 = 2\n ^^^^^\n", text) # Try the same with a form feed at the start of the indented line script = ( @@ -632,7 +650,7 @@ class CmdLineTest(unittest.TestCase): exitcode, stdout, stderr = assert_python_failure(script_name) text = io.TextIOWrapper(io.BytesIO(stderr), "ascii").read() self.assertNotIn("\f", text) - self.assertIn("\n 1 + 1 = 2\n ^", text) + self.assertIn("\n 1 + 1 = 2\n ^^^^^\n", text) # TODO: RUSTPYTHON @unittest.expectedFailure @@ -644,7 +662,7 @@ class CmdLineTest(unittest.TestCase): self.assertEqual( stderr.splitlines()[-3:], [ - b' foo = f"""{}', + b' foo"""', b' ^', b'SyntaxError: f-string: empty expression not allowed', ], @@ -653,7 +671,7 @@ class CmdLineTest(unittest.TestCase): # TODO: RUSTPYTHON @unittest.expectedFailure def test_syntaxerror_invalid_escape_sequence_multi_line(self): - script = 'foo = """\\q\n"""\n' + script = 'foo = """\\q"""\n' with os_helper.temp_dir() as script_dir: script_name = _make_test_script(script_dir, 'script', script) exitcode, stdout, stderr = assert_python_failure( @@ -661,10 +679,9 @@ class CmdLineTest(unittest.TestCase): ) self.assertEqual( stderr.splitlines()[-3:], - [ - b' foo = """\\q', - b' ^', - b'SyntaxError: invalid escape sequence \\q', + [ b' foo = """\\q"""', + b' ^^^^^^^^', + b'SyntaxError: invalid escape sequence \'\\q\'' ], ) @@ -743,7 +760,7 @@ class CmdLineTest(unittest.TestCase): def test_nonexisting_script(self): # bpo-34783: "./python script.py" must not crash # if the script file doesn't exist. - # (Skip test for macOS framework builds because sys.excutable name + # (Skip test for macOS framework builds because sys.executable name # is not the actual Python executable file name. script = 'nonexistingscript.py' self.assertFalse(os.path.exists(script)) @@ -755,10 +772,10 @@ class CmdLineTest(unittest.TestCase): self.assertIn(": can't open file ", err) self.assertNotEqual(proc.returncode, 0) - -def test_main(): - support.run_unittest(CmdLineTest) +def tearDownModule(): support.reap_children() + if __name__ == '__main__': - test_main() + unittest.main() + From 9f29dc558aa6eecdc266e4ecd47d1480496d05d2 Mon Sep 17 00:00:00 2001 From: Myeongseon Choi Date: Tue, 12 Jul 2022 14:16:41 +0900 Subject: [PATCH 6/8] Update Lib/test/test_cmd_line_script.py to be runable --- Lib/test/test_cmd_line_script.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py index 826509fcc..88845d26b 100644 --- a/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py @@ -233,6 +233,8 @@ class CmdLineTest(unittest.TestCase): importlib.machinery.SourceFileLoader, expected_cwd=script_dir) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_script_abspath(self): # pass the script using the relative path, expect the absolute path # in __file__ @@ -413,6 +415,8 @@ class CmdLineTest(unittest.TestCase): script_name, script_name, script_dir, 'test_pkg', importlib.machinery.SourceFileLoader) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_issue8202_dash_c_file_ignored(self): # Make sure a "-c" file in the current directory # does not alter the value of sys.path[0] @@ -519,6 +523,8 @@ class CmdLineTest(unittest.TestCase): self.assertNotIn(b'is a package', err) self.assertNotIn(b'Traceback', err) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_hint_when_triying_to_import_a_py_file(self): with os_helper.temp_dir() as script_dir, \ os_helper.change_cwd(path=script_dir): @@ -596,8 +602,6 @@ class CmdLineTest(unittest.TestCase): 'stdout=%r stderr=%r' % (stdout, stderr)) self.assertEqual(0, rc) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_issue20500_exit_with_exception_value(self): script = textwrap.dedent("""\ import sys @@ -772,10 +776,14 @@ class CmdLineTest(unittest.TestCase): self.assertIn(": can't open file ", err) self.assertNotEqual(proc.returncode, 0) -def tearDownModule(): +# TODO: RUSTPYTHON +# def tearDownModule(): +def test_main(): + support.run_unittest(CmdLineTest) support.reap_children() if __name__ == '__main__': - unittest.main() - + # TODO: RUSTPYTHON + # unittest.main() + test_main() From 5ab55dc7cea964d55e042cbd1a3cce8e28a380a1 Mon Sep 17 00:00:00 2001 From: Myeongseon Choi Date: Tue, 12 Jul 2022 15:19:20 +0900 Subject: [PATCH 7/8] Add expectedFailure tag to test_non_ascii --- Lib/test/test_cmd_line_script.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py index 88845d26b..d467555d2 100644 --- a/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py @@ -579,6 +579,8 @@ class CmdLineTest(unittest.TestCase): self.assertTrue(text[1].startswith(' File ')) self.assertTrue(text[3].startswith('NameError')) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_non_ascii(self): # Mac OS X denies the creation of a file with an invalid UTF-8 name. # Windows allows creating a name with an arbitrary bytes name, but From 44004935b069f4607a7069fe0a73857dc518bea6 Mon Sep 17 00:00:00 2001 From: Myeongseon Choi Date: Wed, 13 Jul 2022 10:23:54 +0900 Subject: [PATCH 8/8] Specify expectedFailure of test_non_ascii in Lib/test/test_cmd_line_script.py --- Lib/test/test_cmd_line_script.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py index d467555d2..c12ef182f 100644 --- a/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py @@ -579,8 +579,6 @@ class CmdLineTest(unittest.TestCase): self.assertTrue(text[1].startswith(' File ')) self.assertTrue(text[3].startswith('NameError')) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_non_ascii(self): # Mac OS X denies the creation of a file with an invalid UTF-8 name. # Windows allows creating a name with an arbitrary bytes name, but @@ -604,6 +602,10 @@ class CmdLineTest(unittest.TestCase): 'stdout=%r stderr=%r' % (stdout, stderr)) self.assertEqual(0, rc) + # TODO: RUSTPYTHON + if sys.platform == "linux": + test_non_ascii = unittest.expectedFailure(test_non_ascii) + def test_issue20500_exit_with_exception_value(self): script = textwrap.dedent("""\ import sys