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        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),  # "", uneeded 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
482if __name__ == '__main__':
483    unittest.main(verbosity=2)
484