From 1c01dafc2b102743448408a5bf70b47c538e153c Mon Sep 17 00:00:00 2001 From: Julian Gilbey Date: Thu, 21 Nov 2024 09:24:01 +0000 Subject: [PATCH 1/3] Fix tests so they work with Python 3.12/3.13 --- test/failing_examples.py | 2 - test/test_python_errors.py | 95 ++++++++++++++++++++++++++++++-------- 2 files changed, 75 insertions(+), 22 deletions(-) diff --git a/test/failing_examples.py b/test/failing_examples.py index 09714d3..386f590 100644 --- a/test/failing_examples.py +++ b/test/failing_examples.py @@ -227,8 +227,6 @@ def build_nested(code, depth, base='def f():\n'): "f'{1;1}'", "f'{a;}'", "f'{b\"\" \"\"}'", - # f-string expression part cannot include a backslash - r'''f"{'\n'}"''', 'async def foo():\n yield x\n return 1', 'async def foo():\n yield x\n return 1', diff --git a/test/test_python_errors.py b/test/test_python_errors.py index 9686d14..850f583 100644 --- a/test/test_python_errors.py +++ b/test/test_python_errors.py @@ -105,7 +105,7 @@ def _get_actual_exception(code): # It's as simple as either an error or not. warnings.filterwarnings('ignore', category=SyntaxWarning) try: - compile(code, '', 'exec') + compiled = compile(code, '', 'exec') except (SyntaxError, IndentationError) as e: wanted = e.__class__.__name__ + ': ' + e.msg line_nr = e.lineno @@ -115,28 +115,73 @@ def _get_actual_exception(code): wanted = 'SyntaxError: (value error) ' + str(e) line_nr = None else: - assert False, "The piece of code should raise an exception." + # In Python 3.12+, some invalid f-strings compile OK but + # only raise an exception when they are evaluated. + try: + eval(compiled) + except ValueError as e: + wanted = 'SyntaxError: (value error) ' + str(e) + line_nr = None + except (ImportError, ModuleNotFoundError): + # This comes from 'from .__future__ import whatever' + # in Python 3.13+ + wanted = 'SyntaxError: future feature whatever is not defined' + line_nr = None + else: + assert False, "The piece of code should raise an exception." # SyntaxError - if wanted == 'SyntaxError: assignment to keyword': + # Some errors have changed error message in later versions of Python, + # and we give a translation table here. We deal with special cases + # below. + translations = { + 'SyntaxError: f-string: unterminated string': + 'SyntaxError: EOL while scanning string literal', + "SyntaxError: f-string: expecting '}'": + 'SyntaxError: EOL while scanning string literal', + 'SyntaxError: f-string: empty expression not allowed': + 'SyntaxError: invalid syntax', + "SyntaxError: f-string expression part cannot include '#'": + 'SyntaxError: invalid syntax', + "SyntaxError: f-string: single '}' is not allowed": + 'SyntaxError: invalid syntax', + 'SyntaxError: cannot use starred expression here': + "SyntaxError: can't use starred expression here", + 'SyntaxError: f-string: cannot use starred expression here': + "SyntaxError: f-string: can't use starred expression here", + 'SyntaxError: unterminated string literal': + 'SyntaxError: EOL while scanning string literal', + 'SyntaxError: parameter without a default follows parameter with a default': + 'SyntaxError: non-default argument follows default argument', + "SyntaxError: 'yield from' outside function": + "SyntaxError: 'yield' outside function", + "SyntaxError: f-string: valid expression required before '}'": + 'SyntaxError: invalid syntax', + "SyntaxError: '{' was never closed": + 'SyntaxError: invalid syntax', + "SyntaxError: f-string: invalid conversion character 'b': expected 's', 'r', or 'a'": + "SyntaxError: f-string: invalid conversion character: expected 's', 'r', or 'a'", + "SyntaxError: (value error) Invalid format specifier ' 5' for object of type 'int'": + 'SyntaxError: f-string: expressions nested too deeply', + "SyntaxError: f-string: expecting a valid expression after '{'": + 'SyntaxError: f-string: invalid syntax', + "SyntaxError: f-string: expecting '=', or '!', or ':', or '}'": + 'SyntaxError: f-string: invalid syntax', + "SyntaxError: f-string: expecting '=', or '!', or ':', or '}'": + 'SyntaxError: f-string: invalid syntax', + } + + if wanted in translations: + wanted = translations[wanted] + elif wanted == 'SyntaxError: assignment to keyword': return [wanted, "SyntaxError: can't assign to keyword", 'SyntaxError: cannot assign to __debug__'], line_nr - elif wanted == 'SyntaxError: f-string: unterminated string': - wanted = 'SyntaxError: EOL while scanning string literal' elif wanted == 'SyntaxError: f-string expression part cannot include a backslash': return [ wanted, "SyntaxError: EOL while scanning string literal", "SyntaxError: unexpected character after line continuation character", ], line_nr - elif wanted == "SyntaxError: f-string: expecting '}'": - wanted = 'SyntaxError: EOL while scanning string literal' - elif wanted == 'SyntaxError: f-string: empty expression not allowed': - wanted = 'SyntaxError: invalid syntax' - elif wanted == "SyntaxError: f-string expression part cannot include '#'": - wanted = 'SyntaxError: invalid syntax' - elif wanted == "SyntaxError: f-string: single '}' is not allowed": - wanted = 'SyntaxError: invalid syntax' elif "Maybe you meant '==' instead of '='?" in wanted: wanted = wanted.removesuffix(" here. Maybe you meant '==' instead of '='?") elif re.match( @@ -148,18 +193,28 @@ def _get_actual_exception(code): wanted, ): wanted = 'SyntaxError: EOF while scanning triple-quoted string literal' - elif wanted == 'SyntaxError: cannot use starred expression here': - wanted = "SyntaxError: can't use starred expression here" - elif wanted == 'SyntaxError: f-string: cannot use starred expression here': - wanted = "SyntaxError: f-string: can't use starred expression here" elif re.match( r"IndentationError: expected an indented block after '[^']*' statement on line \d", wanted, ): wanted = 'IndentationError: expected an indented block' - elif wanted == 'SyntaxError: unterminated string literal': - wanted = 'SyntaxError: EOL while scanning string literal' - return [wanted], line_nr + # The following two errors are produced for both some f-strings and + # some non-f-strings in Python 3.13: + elif wanted == "SyntaxError: can't use starred expression here": + wanted = [ + "SyntaxError: can't use starred expression here", + "SyntaxError: f-string: can't use starred expression here" + ] + elif wanted == 'SyntaxError: cannot mix bytes and nonbytes literals': + wanted = [ + 'SyntaxError: cannot mix bytes and nonbytes literals', + 'SyntaxError: f-string: cannot mix bytes and nonbytes literals' + ] + + if isinstance(wanted, list): + return wanted, line_nr + else: + return [wanted], line_nr def test_default_except_error_postition(): From c792ae546c433cf5339b4ebaf6134d4703ba714c Mon Sep 17 00:00:00 2001 From: Julian Gilbey Date: Fri, 22 Nov 2024 09:44:49 +0000 Subject: [PATCH 2/3] Add Python 3.12 and 3.13 to test matrix --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e51315e..8a4cf92 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,7 +29,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] experimental: [false] steps: - uses: actions/checkout@v2 From 06db036e23aec10cf15098ba3836d9f6d74bec9e Mon Sep 17 00:00:00 2001 From: Julian Gilbey Date: Fri, 22 Nov 2024 11:30:37 +0000 Subject: [PATCH 3/3] Conditionally include failing examples rather than handle them in the testing code --- test/failing_examples.py | 16 ++++++++++++++-- test/test_python_errors.py | 17 ++--------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/test/failing_examples.py b/test/failing_examples.py index 386f590..a555fde 100644 --- a/test/failing_examples.py +++ b/test/failing_examples.py @@ -29,7 +29,6 @@ def build_nested(code, depth, base='def f():\n'): 'from foo import a,', 'from __future__ import whatever', 'from __future__ import braces', - 'from .__future__ import whatever', 'def f(x=3, y): pass', 'lambda x=3, y: x', '__debug__ = 1', @@ -216,7 +215,6 @@ def build_nested(code, depth, base='def f():\n'): 'f"{\'\\\'}"', 'f"{#}"', "f'{1!b}'", - "f'{1:{5:{3}}}'", "f'{'", "f'{'", "f'}'", @@ -411,3 +409,17 @@ def y(): FAILING_EXAMPLES += [ "f'{1=!b}'", ] + +if sys.version_info[:2] < (3, 12): + FAILING_EXAMPLES += [ + # f-string expression part cannot include a backslash before 3.12 + r'''f"{'\n'}"''', + # this compiles successfully but fails when evaluated in 3.12 + "f'{1:{5:{3}}}'", + ] + +if sys.version_info[:2] < (3, 13): + # this compiles successfully but fails when evaluated in 3.13 + FAILING_EXAMPLES += [ + 'from .__future__ import whatever', + ] diff --git a/test/test_python_errors.py b/test/test_python_errors.py index 850f583..0c89016 100644 --- a/test/test_python_errors.py +++ b/test/test_python_errors.py @@ -105,7 +105,7 @@ def _get_actual_exception(code): # It's as simple as either an error or not. warnings.filterwarnings('ignore', category=SyntaxWarning) try: - compiled = compile(code, '', 'exec') + compile(code, '', 'exec') except (SyntaxError, IndentationError) as e: wanted = e.__class__.__name__ + ': ' + e.msg line_nr = e.lineno @@ -115,20 +115,7 @@ def _get_actual_exception(code): wanted = 'SyntaxError: (value error) ' + str(e) line_nr = None else: - # In Python 3.12+, some invalid f-strings compile OK but - # only raise an exception when they are evaluated. - try: - eval(compiled) - except ValueError as e: - wanted = 'SyntaxError: (value error) ' + str(e) - line_nr = None - except (ImportError, ModuleNotFoundError): - # This comes from 'from .__future__ import whatever' - # in Python 3.13+ - wanted = 'SyntaxError: future feature whatever is not defined' - line_nr = None - else: - assert False, "The piece of code should raise an exception." + assert False, "The piece of code should raise an exception." # SyntaxError # Some errors have changed error message in later versions of Python,