diff options
author | Eric V. Smith <eric@trueblade.com> | 2016-09-10 01:56:20 (GMT) |
---|---|---|
committer | Eric V. Smith <eric@trueblade.com> | 2016-09-10 01:56:20 (GMT) |
commit | 451d0e38fcf50d976236d7d00ccfe8c1a2305086 (patch) | |
tree | 29e15833e76714f8f0f8b906871b82c8c1a42967 /Lib/test | |
parent | 052828db1538bf0d42d7e256da13c6e183974a13 (diff) | |
download | cpython-451d0e38fcf50d976236d7d00ccfe8c1a2305086.zip cpython-451d0e38fcf50d976236d7d00ccfe8c1a2305086.tar.gz cpython-451d0e38fcf50d976236d7d00ccfe8c1a2305086.tar.bz2 |
Issue 27948: Allow backslashes in the literal string portion of f-strings, but not in the expressions. Also, require expressions to begin and end with literal curly braces.
Diffstat (limited to 'Lib/test')
-rw-r--r-- | Lib/test/libregrtest/save_env.py | 2 | ||||
-rw-r--r-- | Lib/test/test_faulthandler.py | 4 | ||||
-rw-r--r-- | Lib/test/test_fstring.py | 132 | ||||
-rw-r--r-- | Lib/test/test_tools/test_unparse.py | 10 | ||||
-rw-r--r-- | Lib/test/test_traceback.py | 28 |
5 files changed, 108 insertions, 68 deletions
diff --git a/Lib/test/libregrtest/save_env.py b/Lib/test/libregrtest/save_env.py index eefbc14..96ad3af 100644 --- a/Lib/test/libregrtest/save_env.py +++ b/Lib/test/libregrtest/save_env.py @@ -280,6 +280,6 @@ class saved_test_environment: print(f"Warning -- {name} was modified by {self.testname}", file=sys.stderr, flush=True) if self.verbose > 1: - print(f" Before: {original}""\n"f" After: {current} ", + print(f" Before: {original}\n After: {current} ", file=sys.stderr, flush=True) return False diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py index d2bd2d2..22ccbc9 100644 --- a/Lib/test/test_faulthandler.py +++ b/Lib/test/test_faulthandler.py @@ -735,11 +735,11 @@ class FaultHandlerTests(unittest.TestCase): ('EXCEPTION_INT_DIVIDE_BY_ZERO', 'int divide by zero'), ('EXCEPTION_STACK_OVERFLOW', 'stack overflow'), ): - self.check_windows_exception(""" + self.check_windows_exception(f""" import faulthandler faulthandler.enable() faulthandler._raise_exception(faulthandler._{exc}) - """.format(exc=exc), + """, 3, name) diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py index 2ba1b21..e61f635 100644 --- a/Lib/test/test_fstring.py +++ b/Lib/test/test_fstring.py @@ -119,6 +119,14 @@ f'{a * x()}'""" self.assertEqual(f'a}}', 'a}') self.assertEqual(f'}}b', '}b') self.assertEqual(f'a}}b', 'a}b') + self.assertEqual(f'{{}}', '{}') + self.assertEqual(f'a{{}}', 'a{}') + self.assertEqual(f'{{b}}', '{b}') + self.assertEqual(f'{{}}c', '{}c') + self.assertEqual(f'a{{b}}', 'a{b}') + self.assertEqual(f'a{{}}c', 'a{}c') + self.assertEqual(f'{{b}}c', '{b}c') + self.assertEqual(f'a{{b}}c', 'a{b}c') self.assertEqual(f'{{{10}', '{10') self.assertEqual(f'}}{10}', '}10') @@ -302,56 +310,79 @@ f'{a * x()}'""" ["f'{\n}'", ]) - def test_no_backslashes(self): - # See issue 27921 - - # These should work, but currently don't - self.assertAllRaise(SyntaxError, 'backslashes not allowed', - [r"f'\t'", - r"f'{2}\t'", - r"f'{2}\t{3}'", - r"f'\t{3}'", - - r"f'\N{GREEK CAPITAL LETTER DELTA}'", - r"f'{2}\N{GREEK CAPITAL LETTER DELTA}'", - r"f'{2}\N{GREEK CAPITAL LETTER DELTA}{3}'", - r"f'\N{GREEK CAPITAL LETTER DELTA}{3}'", - - r"f'\u0394'", - r"f'{2}\u0394'", - r"f'{2}\u0394{3}'", - r"f'\u0394{3}'", - - r"f'\U00000394'", - r"f'{2}\U00000394'", - r"f'{2}\U00000394{3}'", - r"f'\U00000394{3}'", - - r"f'\x20'", - r"f'{2}\x20'", - r"f'{2}\x20{3}'", - r"f'\x20{3}'", - - r"f'2\x20'", - r"f'2\x203'", - r"f'2\x203'", + def test_backslashes_in_string_part(self): + self.assertEqual(f'\t', '\t') + self.assertEqual(r'\t', '\\t') + self.assertEqual(rf'\t', '\\t') + self.assertEqual(f'{2}\t', '2\t') + self.assertEqual(f'{2}\t{3}', '2\t3') + self.assertEqual(f'\t{3}', '\t3') + + self.assertEqual(f'\u0394', '\u0394') + self.assertEqual(r'\u0394', '\\u0394') + self.assertEqual(rf'\u0394', '\\u0394') + self.assertEqual(f'{2}\u0394', '2\u0394') + self.assertEqual(f'{2}\u0394{3}', '2\u03943') + self.assertEqual(f'\u0394{3}', '\u03943') + + self.assertEqual(f'\U00000394', '\u0394') + self.assertEqual(r'\U00000394', '\\U00000394') + self.assertEqual(rf'\U00000394', '\\U00000394') + self.assertEqual(f'{2}\U00000394', '2\u0394') + self.assertEqual(f'{2}\U00000394{3}', '2\u03943') + self.assertEqual(f'\U00000394{3}', '\u03943') + + self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}', '\u0394') + self.assertEqual(f'{2}\N{GREEK CAPITAL LETTER DELTA}', '2\u0394') + self.assertEqual(f'{2}\N{GREEK CAPITAL LETTER DELTA}{3}', '2\u03943') + self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}{3}', '\u03943') + self.assertEqual(f'2\N{GREEK CAPITAL LETTER DELTA}', '2\u0394') + self.assertEqual(f'2\N{GREEK CAPITAL LETTER DELTA}3', '2\u03943') + self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}3', '\u03943') + + self.assertEqual(f'\x20', ' ') + self.assertEqual(r'\x20', '\\x20') + self.assertEqual(rf'\x20', '\\x20') + self.assertEqual(f'{2}\x20', '2 ') + self.assertEqual(f'{2}\x20{3}', '2 3') + self.assertEqual(f'\x20{3}', ' 3') + + self.assertEqual(f'2\x20', '2 ') + self.assertEqual(f'2\x203', '2 3') + self.assertEqual(f'\x203', ' 3') + + def test_misformed_unicode_character_name(self): + # These test are needed because unicode names are parsed + # differently inside f-strings. + self.assertAllRaise(SyntaxError, r"\(unicode error\) 'unicodeescape' codec can't decode bytes in position .*: malformed \\N character escape", + [r"f'\N'", + r"f'\N{'", + r"f'\N{GREEK CAPITAL LETTER DELTA'", + + # Here are the non-f-string versions, + # which should give the same errors. + r"'\N'", + r"'\N{'", + r"'\N{GREEK CAPITAL LETTER DELTA'", ]) - # And these don't work now, and shouldn't work in the future. - self.assertAllRaise(SyntaxError, 'backslashes not allowed', + def test_no_backslashes_in_expression_part(self): + self.assertAllRaise(SyntaxError, 'f-string expression part cannot include a backslash', [r"f'{\'a\'}'", r"f'{\t3}'", + r"f'{\}'", + r"rf'{\'a\'}'", + r"rf'{\t3}'", + r"rf'{\}'", + r"""rf'{"\N{LEFT CURLY BRACKET}"}'""", ]) - # add this when backslashes are allowed again. see issue 27921 - # these test will be needed because unicode names will be parsed - # differently once backslashes are allowed inside expressions - ## def test_misformed_unicode_character_name(self): - ## self.assertAllRaise(SyntaxError, 'xx', - ## [r"f'\N'", - ## [r"f'\N{'", - ## [r"f'\N{GREEK CAPITAL LETTER DELTA'", - ## ]) + def test_no_escapes_for_braces(self): + # \x7b is '{'. Make sure it doesn't start an expression. + self.assertEqual(f'\x7b2}}', '{2}') + self.assertEqual(f'\x7b2', '{2') + self.assertEqual(f'\u007b2', '{2') + self.assertEqual(f'\N{LEFT CURLY BRACKET}2\N{RIGHT CURLY BRACKET}', '{2}') def test_newlines_in_expressions(self): self.assertEqual(f'{0}', '0') @@ -509,6 +540,14 @@ f'{a * x()}'""" "ruf''", "FUR''", "Fur''", + "fb''", + "fB''", + "Fb''", + "FB''", + "bf''", + "bF''", + "Bf''", + "BF''", ]) def test_leading_trailing_spaces(self): @@ -551,8 +590,8 @@ f'{a * x()}'""" self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character', ["f'{3!g}'", "f'{3!A}'", - "f'{3!A}'", - "f'{3!A}'", + "f'{3!3}'", + "f'{3!G}'", "f'{3!!}'", "f'{3!:}'", "f'{3! s}'", # no space before conversion char @@ -601,6 +640,7 @@ f'{a * x()}'""" "f'{3!s:3'", "f'x{'", "f'x{x'", + "f'{x'", "f'{3:s'", "f'{{{'", "f'{{}}{'", diff --git a/Lib/test/test_tools/test_unparse.py b/Lib/test/test_tools/test_unparse.py index ed0001a..65dee1b 100644 --- a/Lib/test/test_tools/test_unparse.py +++ b/Lib/test/test_tools/test_unparse.py @@ -285,12 +285,12 @@ class DirectoryTestCase(ASTTestCase): if test.support.verbose: print('Testing %s' % filename) - # it's very much a hack that I'm skipping these files, but - # I can't figure out why they fail. I'll fix it when I - # address issue #27948. - if os.path.basename(filename) in ('test_fstring.py', 'test_traceback.py'): + # Some f-strings are not correctly round-tripped by + # Tools/parser/unparse.py. See issue 28002 for details. + # We need to skip files that contain such f-strings. + if os.path.basename(filename) in ('test_fstring.py', ): if test.support.verbose: - print(f'Skipping {filename}: see issue 27921') + print(f'Skipping {filename}: see issue 28002') continue with self.subTest(filename=filename): diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 446b91e..037d883 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -326,13 +326,13 @@ class TracebackFormatTests(unittest.TestCase): lineno_f = f.__code__.co_firstlineno result_f = ( 'Traceback (most recent call last):\n' - f' File "{__file__}", line {lineno_f+5}, in _check_recursive_traceback_display''\n' + f' File "{__file__}", line {lineno_f+5}, in _check_recursive_traceback_display\n' ' f()\n' - f' File "{__file__}", line {lineno_f+1}, in f''\n' + f' File "{__file__}", line {lineno_f+1}, in f\n' ' f()\n' - f' File "{__file__}", line {lineno_f+1}, in f''\n' + f' File "{__file__}", line {lineno_f+1}, in f\n' ' f()\n' - f' File "{__file__}", line {lineno_f+1}, in f''\n' + f' File "{__file__}", line {lineno_f+1}, in f\n' ' f()\n' # XXX: The following line changes depending on whether the tests # are run through the interactive interpreter or with -m @@ -371,20 +371,20 @@ class TracebackFormatTests(unittest.TestCase): lineno_g = g.__code__.co_firstlineno result_g = ( - f' File "{__file__}", line {lineno_g+2}, in g''\n' + f' File "{__file__}", line {lineno_g+2}, in g\n' ' return g(count-1)\n' - f' File "{__file__}", line {lineno_g+2}, in g''\n' + f' File "{__file__}", line {lineno_g+2}, in g\n' ' return g(count-1)\n' - f' File "{__file__}", line {lineno_g+2}, in g''\n' + f' File "{__file__}", line {lineno_g+2}, in g\n' ' return g(count-1)\n' ' [Previous line repeated 6 more times]\n' - f' File "{__file__}", line {lineno_g+3}, in g''\n' + f' File "{__file__}", line {lineno_g+3}, in g\n' ' raise ValueError\n' 'ValueError\n' ) tb_line = ( 'Traceback (most recent call last):\n' - f' File "{__file__}", line {lineno_g+7}, in _check_recursive_traceback_display''\n' + f' File "{__file__}", line {lineno_g+7}, in _check_recursive_traceback_display\n' ' g()\n' ) expected = (tb_line + result_g).splitlines() @@ -408,16 +408,16 @@ class TracebackFormatTests(unittest.TestCase): lineno_h = h.__code__.co_firstlineno result_h = ( 'Traceback (most recent call last):\n' - f' File "{__file__}", line {lineno_h+7}, in _check_recursive_traceback_display''\n' + f' File "{__file__}", line {lineno_h+7}, in _check_recursive_traceback_display\n' ' h()\n' - f' File "{__file__}", line {lineno_h+2}, in h''\n' + f' File "{__file__}", line {lineno_h+2}, in h\n' ' return h(count-1)\n' - f' File "{__file__}", line {lineno_h+2}, in h''\n' + f' File "{__file__}", line {lineno_h+2}, in h\n' ' return h(count-1)\n' - f' File "{__file__}", line {lineno_h+2}, in h''\n' + f' File "{__file__}", line {lineno_h+2}, in h\n' ' return h(count-1)\n' ' [Previous line repeated 6 more times]\n' - f' File "{__file__}", line {lineno_h+3}, in h''\n' + f' File "{__file__}", line {lineno_h+3}, in h\n' ' g()\n' ) expected = (result_h + result_g).splitlines() |