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