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