(root)/
Python-3.11.7/
Lib/
idlelib/
idle_test/
test_pyparse.py
       1  "Test pyparse, coverage 96%."
       2  
       3  from idlelib import pyparse
       4  import unittest
       5  from collections import namedtuple
       6  
       7  
       8  class ESC[4;38;5;81mParseMapTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
       9  
      10      def test_parsemap(self):
      11          keepwhite = {ord(c): ord(c) for c in ' \t\n\r'}
      12          mapping = pyparse.ParseMap(keepwhite)
      13          self.assertEqual(mapping[ord('\t')], ord('\t'))
      14          self.assertEqual(mapping[ord('a')], ord('x'))
      15          self.assertEqual(mapping[1000], ord('x'))
      16  
      17      def test_trans(self):
      18          # trans is the production instance of ParseMap, used in _study1
      19          parser = pyparse.Parser(4, 4)
      20          self.assertEqual('\t a([{b}])b"c\'d\n'.translate(pyparse.trans),
      21                           'xxx(((x)))x"x\'x\n')
      22  
      23  
      24  class ESC[4;38;5;81mPyParseTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
      25  
      26      @classmethod
      27      def setUpClass(cls):
      28          cls.parser = pyparse.Parser(indentwidth=4, tabwidth=4)
      29  
      30      @classmethod
      31      def tearDownClass(cls):
      32          del cls.parser
      33  
      34      def test_init(self):
      35          self.assertEqual(self.parser.indentwidth, 4)
      36          self.assertEqual(self.parser.tabwidth, 4)
      37  
      38      def test_set_code(self):
      39          eq = self.assertEqual
      40          p = self.parser
      41          setcode = p.set_code
      42  
      43          # Not empty and doesn't end with newline.
      44          with self.assertRaises(AssertionError):
      45              setcode('a')
      46  
      47          tests = ('',
      48                   'a\n')
      49  
      50          for string in tests:
      51              with self.subTest(string=string):
      52                  setcode(string)
      53                  eq(p.code, string)
      54                  eq(p.study_level, 0)
      55  
      56      def test_find_good_parse_start(self):
      57          eq = self.assertEqual
      58          p = self.parser
      59          setcode = p.set_code
      60          start = p.find_good_parse_start
      61          def char_in_string_false(index): return False
      62  
      63          # First line starts with 'def' and ends with ':', then 0 is the pos.
      64          setcode('def spam():\n')
      65          eq(start(char_in_string_false), 0)
      66  
      67          # First line begins with a keyword in the list and ends
      68          # with an open brace, then 0 is the pos.  This is how
      69          # hyperparser calls this function as the newline is not added
      70          # in the editor, but rather on the call to setcode.
      71          setcode('class spam( ' + ' \n')
      72          eq(start(char_in_string_false), 0)
      73  
      74          # Split def across lines.
      75          setcode('"""This is a module docstring"""\n'
      76                  'class C:\n'
      77                  '    def __init__(self, a,\n'
      78                  '                 b=True):\n'
      79                  '        pass\n'
      80                  )
      81          pos0, pos = 33, 42  # Start of 'class...', '    def' lines.
      82  
      83          # Passing no value or non-callable should fail (issue 32989).
      84          with self.assertRaises(TypeError):
      85              start()
      86          with self.assertRaises(TypeError):
      87              start(False)
      88  
      89          # Make text look like a string.  This returns pos as the start
      90          # position, but it's set to None.
      91          self.assertIsNone(start(is_char_in_string=lambda index: True))
      92  
      93          # Make all text look like it's not in a string.  This means that it
      94          # found a good start position.
      95          eq(start(char_in_string_false), pos)
      96  
      97          # If the beginning of the def line is not in a string, then it
      98          # returns that as the index.
      99          eq(start(is_char_in_string=lambda index: index > pos), pos)
     100          # If the beginning of the def line is in a string, then it
     101          # looks for a previous index.
     102          eq(start(is_char_in_string=lambda index: index >= pos), pos0)
     103          # If everything before the 'def' is in a string, then returns None.
     104          # The non-continuation def line returns 44 (see below).
     105          eq(start(is_char_in_string=lambda index: index < pos), None)
     106  
     107          # Code without extra line break in def line - mostly returns the same
     108          # values.
     109          setcode('"""This is a module docstring"""\n'
     110                  'class C:\n'
     111                  '    def __init__(self, a, b=True):\n'
     112                  '        pass\n'
     113                  )  # Does not affect class, def positions.
     114          eq(start(char_in_string_false), pos)
     115          eq(start(is_char_in_string=lambda index: index > pos), pos)
     116          eq(start(is_char_in_string=lambda index: index >= pos), pos0)
     117          # When the def line isn't split, this returns which doesn't match the
     118          # split line test.
     119          eq(start(is_char_in_string=lambda index: index < pos), pos)
     120  
     121      def test_set_lo(self):
     122          code = (
     123                  '"""This is a module docstring"""\n'
     124                  'class C:\n'
     125                  '    def __init__(self, a,\n'
     126                  '                 b=True):\n'
     127                  '        pass\n'
     128                  )
     129          pos = 42
     130          p = self.parser
     131          p.set_code(code)
     132  
     133          # Previous character is not a newline.
     134          with self.assertRaises(AssertionError):
     135              p.set_lo(5)
     136  
     137          # A value of 0 doesn't change self.code.
     138          p.set_lo(0)
     139          self.assertEqual(p.code, code)
     140  
     141          # An index that is preceded by a newline.
     142          p.set_lo(pos)
     143          self.assertEqual(p.code, code[pos:])
     144  
     145      def test_study1(self):
     146          eq = self.assertEqual
     147          p = self.parser
     148          setcode = p.set_code
     149          study = p._study1
     150  
     151          (NONE, BACKSLASH, FIRST, NEXT, BRACKET) = range(5)
     152          TestInfo = namedtuple('TestInfo', ['string', 'goodlines',
     153                                             'continuation'])
     154          tests = (
     155              TestInfo('', [0], NONE),
     156              # Docstrings.
     157              TestInfo('"""This is a complete docstring."""\n', [0, 1], NONE),
     158              TestInfo("'''This is a complete docstring.'''\n", [0, 1], NONE),
     159              TestInfo('"""This is a continued docstring.\n', [0, 1], FIRST),
     160              TestInfo("'''This is a continued docstring.\n", [0, 1], FIRST),
     161              TestInfo('"""Closing quote does not match."\n', [0, 1], FIRST),
     162              TestInfo('"""Bracket in docstring [\n', [0, 1], FIRST),
     163              TestInfo("'''Incomplete two line docstring.\n\n", [0, 2], NEXT),
     164              # Single-quoted strings.
     165              TestInfo('"This is a complete string."\n', [0, 1], NONE),
     166              TestInfo('"This is an incomplete string.\n', [0, 1], NONE),
     167              TestInfo("'This is more incomplete.\n\n", [0, 1, 2], NONE),
     168              # Comment (backslash does not continue comments).
     169              TestInfo('# Comment\\\n', [0, 1], NONE),
     170              # Brackets.
     171              TestInfo('("""Complete string in bracket"""\n', [0, 1], BRACKET),
     172              TestInfo('("""Open string in bracket\n', [0, 1], FIRST),
     173              TestInfo('a = (1 + 2) - 5 *\\\n', [0, 1], BACKSLASH),  # No bracket.
     174              TestInfo('\n   def function1(self, a,\n                 b):\n',
     175                       [0, 1, 3], NONE),
     176              TestInfo('\n   def function1(self, a,\\\n', [0, 1, 2], BRACKET),
     177              TestInfo('\n   def function1(self, a,\n', [0, 1, 2], BRACKET),
     178              TestInfo('())\n', [0, 1], NONE),                    # Extra closer.
     179              TestInfo(')(\n', [0, 1], BRACKET),                  # Extra closer.
     180              # For the mismatched example, it doesn't look like continuation.
     181              TestInfo('{)(]\n', [0, 1], NONE),                   # Mismatched.
     182              )
     183  
     184          for test in tests:
     185              with self.subTest(string=test.string):
     186                  setcode(test.string)  # resets study_level
     187                  study()
     188                  eq(p.study_level, 1)
     189                  eq(p.goodlines, test.goodlines)
     190                  eq(p.continuation, test.continuation)
     191  
     192          # Called again, just returns without reprocessing.
     193          self.assertIsNone(study())
     194  
     195      def test_get_continuation_type(self):
     196          eq = self.assertEqual
     197          p = self.parser
     198          setcode = p.set_code
     199          gettype = p.get_continuation_type
     200  
     201          (NONE, BACKSLASH, FIRST, NEXT, BRACKET) = range(5)
     202          TestInfo = namedtuple('TestInfo', ['string', 'continuation'])
     203          tests = (
     204              TestInfo('', NONE),
     205              TestInfo('"""This is a continuation docstring.\n', FIRST),
     206              TestInfo("'''This is a multiline-continued docstring.\n\n", NEXT),
     207              TestInfo('a = (1 + 2) - 5 *\\\n', BACKSLASH),
     208              TestInfo('\n   def function1(self, a,\\\n', BRACKET)
     209              )
     210  
     211          for test in tests:
     212              with self.subTest(string=test.string):
     213                  setcode(test.string)
     214                  eq(gettype(), test.continuation)
     215  
     216      def test_study2(self):
     217          eq = self.assertEqual
     218          p = self.parser
     219          setcode = p.set_code
     220          study = p._study2
     221  
     222          TestInfo = namedtuple('TestInfo', ['string', 'start', 'end', 'lastch',
     223                                             'openbracket', 'bracketing'])
     224          tests = (
     225              TestInfo('', 0, 0, '', None, ((0, 0),)),
     226              TestInfo("'''This is a multiline continuation docstring.\n\n",
     227                       0, 48, "'", None, ((0, 0), (0, 1), (48, 0))),
     228              TestInfo(' # Comment\\\n',
     229                       0, 12, '', None, ((0, 0), (1, 1), (12, 0))),
     230              # A comment without a space is a special case
     231              TestInfo(' #Comment\\\n',
     232                       0, 0, '', None, ((0, 0),)),
     233              # Backslash continuation.
     234              TestInfo('a = (1 + 2) - 5 *\\\n',
     235                       0, 19, '*', None, ((0, 0), (4, 1), (11, 0))),
     236              # Bracket continuation with close.
     237              TestInfo('\n   def function1(self, a,\n                 b):\n',
     238                       1, 48, ':', None, ((1, 0), (17, 1), (46, 0))),
     239              # Bracket continuation with unneeded backslash.
     240              TestInfo('\n   def function1(self, a,\\\n',
     241                       1, 28, ',', 17, ((1, 0), (17, 1))),
     242              # Bracket continuation.
     243              TestInfo('\n   def function1(self, a,\n',
     244                       1, 27, ',', 17, ((1, 0), (17, 1))),
     245              # Bracket continuation with comment at end of line with text.
     246              TestInfo('\n   def function1(self, a,  # End of line comment.\n',
     247                       1, 51, ',', 17, ((1, 0), (17, 1), (28, 2), (51, 1))),
     248              # Multi-line statement with comment line in between code lines.
     249              TestInfo('  a = ["first item",\n  # Comment line\n    "next item",\n',
     250                       0, 55, ',', 6, ((0, 0), (6, 1), (7, 2), (19, 1),
     251                                       (23, 2), (38, 1), (42, 2), (53, 1))),
     252              TestInfo('())\n',
     253                       0, 4, ')', None, ((0, 0), (0, 1), (2, 0), (3, 0))),
     254              TestInfo(')(\n', 0, 3, '(', 1, ((0, 0), (1, 0), (1, 1))),
     255              # Wrong closers still decrement stack level.
     256              TestInfo('{)(]\n',
     257                       0, 5, ']', None, ((0, 0), (0, 1), (2, 0), (2, 1), (4, 0))),
     258              # Character after backslash.
     259              TestInfo(':\\a\n', 0, 4, '\\a', None, ((0, 0),)),
     260              TestInfo('\n', 0, 0, '', None, ((0, 0),)),
     261              )
     262  
     263          for test in tests:
     264              with self.subTest(string=test.string):
     265                  setcode(test.string)
     266                  study()
     267                  eq(p.study_level, 2)
     268                  eq(p.stmt_start, test.start)
     269                  eq(p.stmt_end, test.end)
     270                  eq(p.lastch, test.lastch)
     271                  eq(p.lastopenbracketpos, test.openbracket)
     272                  eq(p.stmt_bracketing, test.bracketing)
     273  
     274          # Called again, just returns without reprocessing.
     275          self.assertIsNone(study())
     276  
     277      def test_get_num_lines_in_stmt(self):
     278          eq = self.assertEqual
     279          p = self.parser
     280          setcode = p.set_code
     281          getlines = p.get_num_lines_in_stmt
     282  
     283          TestInfo = namedtuple('TestInfo', ['string', 'lines'])
     284          tests = (
     285              TestInfo('[x for x in a]\n', 1),      # Closed on one line.
     286              TestInfo('[x\nfor x in a\n', 2),      # Not closed.
     287              TestInfo('[x\\\nfor x in a\\\n', 2),  # "", unneeded backslashes.
     288              TestInfo('[x\nfor x in a\n]\n', 3),   # Closed on multi-line.
     289              TestInfo('\n"""Docstring comment L1"""\nL2\nL3\nL4\n', 1),
     290              TestInfo('\n"""Docstring comment L1\nL2"""\nL3\nL4\n', 1),
     291              TestInfo('\n"""Docstring comment L1\\\nL2\\\nL3\\\nL4\\\n', 4),
     292              TestInfo('\n\n"""Docstring comment L1\\\nL2\\\nL3\\\nL4\\\n"""\n', 5)
     293              )
     294  
     295          # Blank string doesn't have enough elements in goodlines.
     296          setcode('')
     297          with self.assertRaises(IndexError):
     298              getlines()
     299  
     300          for test in tests:
     301              with self.subTest(string=test.string):
     302                  setcode(test.string)
     303                  eq(getlines(), test.lines)
     304  
     305      def test_compute_bracket_indent(self):
     306          eq = self.assertEqual
     307          p = self.parser
     308          setcode = p.set_code
     309          indent = p.compute_bracket_indent
     310  
     311          TestInfo = namedtuple('TestInfo', ['string', 'spaces'])
     312          tests = (
     313              TestInfo('def function1(self, a,\n', 14),
     314              # Characters after bracket.
     315              TestInfo('\n    def function1(self, a,\n', 18),
     316              TestInfo('\n\tdef function1(self, a,\n', 18),
     317              # No characters after bracket.
     318              TestInfo('\n    def function1(\n', 8),
     319              TestInfo('\n\tdef function1(\n', 8),
     320              TestInfo('\n    def function1(  \n', 8),  # Ignore extra spaces.
     321              TestInfo('[\n"first item",\n  # Comment line\n    "next item",\n', 0),
     322              TestInfo('[\n  "first item",\n  # Comment line\n    "next item",\n', 2),
     323              TestInfo('["first item",\n  # Comment line\n    "next item",\n', 1),
     324              TestInfo('(\n', 4),
     325              TestInfo('(a\n', 1),
     326               )
     327  
     328          # Must be C_BRACKET continuation type.
     329          setcode('def function1(self, a, b):\n')
     330          with self.assertRaises(AssertionError):
     331              indent()
     332  
     333          for test in tests:
     334              setcode(test.string)
     335              eq(indent(), test.spaces)
     336  
     337      def test_compute_backslash_indent(self):
     338          eq = self.assertEqual
     339          p = self.parser
     340          setcode = p.set_code
     341          indent = p.compute_backslash_indent
     342  
     343          # Must be C_BACKSLASH continuation type.
     344          errors = (('def function1(self, a, b\\\n'),  # Bracket.
     345                    ('    """ (\\\n'),                 # Docstring.
     346                    ('a = #\\\n'),                     # Inline comment.
     347                    )
     348          for string in errors:
     349              with self.subTest(string=string):
     350                  setcode(string)
     351                  with self.assertRaises(AssertionError):
     352                      indent()
     353  
     354          TestInfo = namedtuple('TestInfo', ('string', 'spaces'))
     355          tests = (TestInfo('a = (1 + 2) - 5 *\\\n', 4),
     356                   TestInfo('a = 1 + 2 - 5 *\\\n', 4),
     357                   TestInfo('    a = 1 + 2 - 5 *\\\n', 8),
     358                   TestInfo('  a = "spam"\\\n', 6),
     359                   TestInfo('  a = \\\n"a"\\\n', 4),
     360                   TestInfo('  a = #\\\n"a"\\\n', 5),
     361                   TestInfo('a == \\\n', 2),
     362                   TestInfo('a != \\\n', 2),
     363                   # Difference between containing = and those not.
     364                   TestInfo('\\\n', 2),
     365                   TestInfo('    \\\n', 6),
     366                   TestInfo('\t\\\n', 6),
     367                   TestInfo('a\\\n', 3),
     368                   TestInfo('{}\\\n', 4),
     369                   TestInfo('(1 + 2) - 5 *\\\n', 3),
     370                   )
     371          for test in tests:
     372              with self.subTest(string=test.string):
     373                  setcode(test.string)
     374                  eq(indent(), test.spaces)
     375  
     376      def test_get_base_indent_string(self):
     377          eq = self.assertEqual
     378          p = self.parser
     379          setcode = p.set_code
     380          baseindent = p.get_base_indent_string
     381  
     382          TestInfo = namedtuple('TestInfo', ['string', 'indent'])
     383          tests = (TestInfo('', ''),
     384                   TestInfo('def a():\n', ''),
     385                   TestInfo('\tdef a():\n', '\t'),
     386                   TestInfo('    def a():\n', '    '),
     387                   TestInfo('    def a(\n', '    '),
     388                   TestInfo('\t\n    def a(\n', '    '),
     389                   TestInfo('\t\n    # Comment.\n', '    '),
     390                   )
     391  
     392          for test in tests:
     393              with self.subTest(string=test.string):
     394                  setcode(test.string)
     395                  eq(baseindent(), test.indent)
     396  
     397      def test_is_block_opener(self):
     398          yes = self.assertTrue
     399          no = self.assertFalse
     400          p = self.parser
     401          setcode = p.set_code
     402          opener = p.is_block_opener
     403  
     404          TestInfo = namedtuple('TestInfo', ['string', 'assert_'])
     405          tests = (
     406              TestInfo('def a():\n', yes),
     407              TestInfo('\n   def function1(self, a,\n                 b):\n', yes),
     408              TestInfo(':\n', yes),
     409              TestInfo('a:\n', yes),
     410              TestInfo('):\n', yes),
     411              TestInfo('(:\n', yes),
     412              TestInfo('":\n', no),
     413              TestInfo('\n   def function1(self, a,\n', no),
     414              TestInfo('def function1(self, a):\n    pass\n', no),
     415              TestInfo('# A comment:\n', no),
     416              TestInfo('"""A docstring:\n', no),
     417              TestInfo('"""A docstring:\n', no),
     418              )
     419  
     420          for test in tests:
     421              with self.subTest(string=test.string):
     422                  setcode(test.string)
     423                  test.assert_(opener())
     424  
     425      def test_is_block_closer(self):
     426          yes = self.assertTrue
     427          no = self.assertFalse
     428          p = self.parser
     429          setcode = p.set_code
     430          closer = p.is_block_closer
     431  
     432          TestInfo = namedtuple('TestInfo', ['string', 'assert_'])
     433          tests = (
     434              TestInfo('return\n', yes),
     435              TestInfo('\tbreak\n', yes),
     436              TestInfo('  continue\n', yes),
     437              TestInfo('     raise\n', yes),
     438              TestInfo('pass    \n', yes),
     439              TestInfo('pass\t\n', yes),
     440              TestInfo('return #\n', yes),
     441              TestInfo('raised\n', no),
     442              TestInfo('returning\n', no),
     443              TestInfo('# return\n', no),
     444              TestInfo('"""break\n', no),
     445              TestInfo('"continue\n', no),
     446              TestInfo('def function1(self, a):\n    pass\n', yes),
     447              )
     448  
     449          for test in tests:
     450              with self.subTest(string=test.string):
     451                  setcode(test.string)
     452                  test.assert_(closer())
     453  
     454      def test_get_last_stmt_bracketing(self):
     455          eq = self.assertEqual
     456          p = self.parser
     457          setcode = p.set_code
     458          bracketing = p.get_last_stmt_bracketing
     459  
     460          TestInfo = namedtuple('TestInfo', ['string', 'bracket'])
     461          tests = (
     462              TestInfo('', ((0, 0),)),
     463              TestInfo('a\n', ((0, 0),)),
     464              TestInfo('()()\n', ((0, 0), (0, 1), (2, 0), (2, 1), (4, 0))),
     465              TestInfo('(\n)()\n', ((0, 0), (0, 1), (3, 0), (3, 1), (5, 0))),
     466              TestInfo('()\n()\n', ((3, 0), (3, 1), (5, 0))),
     467              TestInfo('()(\n)\n', ((0, 0), (0, 1), (2, 0), (2, 1), (5, 0))),
     468              TestInfo('(())\n', ((0, 0), (0, 1), (1, 2), (3, 1), (4, 0))),
     469              TestInfo('(\n())\n', ((0, 0), (0, 1), (2, 2), (4, 1), (5, 0))),
     470              # Same as matched test.
     471              TestInfo('{)(]\n', ((0, 0), (0, 1), (2, 0), (2, 1), (4, 0))),
     472              TestInfo('(((())\n',
     473                       ((0, 0), (0, 1), (1, 2), (2, 3), (3, 4), (5, 3), (6, 2))),
     474              )
     475  
     476          for test in tests:
     477              with self.subTest(string=test.string):
     478                  setcode(test.string)
     479                  eq(bracketing(), test.bracket)
     480  
     481  
     482  if __name__ == '__main__':
     483      unittest.main(verbosity=2)