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