test_hyperparser.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. "Test hyperparser, coverage 98%."
  2. from idlelib.hyperparser import HyperParser
  3. import unittest
  4. from test.support import requires
  5. from tkinter import Tk, Text
  6. from idlelib.editor import EditorWindow
  7. class DummyEditwin:
  8. def __init__(self, text):
  9. self.text = text
  10. self.indentwidth = 8
  11. self.tabwidth = 8
  12. self.prompt_last_line = '>>>'
  13. self.num_context_lines = 50, 500, 1000
  14. _build_char_in_string_func = EditorWindow._build_char_in_string_func
  15. is_char_in_string = EditorWindow.is_char_in_string
  16. class HyperParserTest(unittest.TestCase):
  17. code = (
  18. '"""This is a module docstring"""\n'
  19. '# this line is a comment\n'
  20. 'x = "this is a string"\n'
  21. "y = 'this is also a string'\n"
  22. 'l = [i for i in range(10)]\n'
  23. 'm = [py*py for # comment\n'
  24. ' py in l]\n'
  25. 'x.__len__\n'
  26. "z = ((r'asdf')+('a')))\n"
  27. '[x for x in\n'
  28. 'for = False\n'
  29. 'cliché = "this is a string with unicode, what a cliché"'
  30. )
  31. @classmethod
  32. def setUpClass(cls):
  33. requires('gui')
  34. cls.root = Tk()
  35. cls.root.withdraw()
  36. cls.text = Text(cls.root)
  37. cls.editwin = DummyEditwin(cls.text)
  38. @classmethod
  39. def tearDownClass(cls):
  40. del cls.text, cls.editwin
  41. cls.root.destroy()
  42. del cls.root
  43. def setUp(self):
  44. self.text.insert('insert', self.code)
  45. def tearDown(self):
  46. self.text.delete('1.0', 'end')
  47. self.editwin.prompt_last_line = '>>>'
  48. def get_parser(self, index):
  49. """
  50. Return a parser object with index at 'index'
  51. """
  52. return HyperParser(self.editwin, index)
  53. def test_init(self):
  54. """
  55. test corner cases in the init method
  56. """
  57. with self.assertRaises(ValueError) as ve:
  58. self.text.tag_add('console', '1.0', '1.end')
  59. p = self.get_parser('1.5')
  60. self.assertIn('precedes', str(ve.exception))
  61. # test without ps1
  62. self.editwin.prompt_last_line = ''
  63. # number of lines lesser than 50
  64. p = self.get_parser('end')
  65. self.assertEqual(p.rawtext, self.text.get('1.0', 'end'))
  66. # number of lines greater than 50
  67. self.text.insert('end', self.text.get('1.0', 'end')*4)
  68. p = self.get_parser('54.5')
  69. def test_is_in_string(self):
  70. get = self.get_parser
  71. p = get('1.0')
  72. self.assertFalse(p.is_in_string())
  73. p = get('1.4')
  74. self.assertTrue(p.is_in_string())
  75. p = get('2.3')
  76. self.assertFalse(p.is_in_string())
  77. p = get('3.3')
  78. self.assertFalse(p.is_in_string())
  79. p = get('3.7')
  80. self.assertTrue(p.is_in_string())
  81. p = get('4.6')
  82. self.assertTrue(p.is_in_string())
  83. p = get('12.54')
  84. self.assertTrue(p.is_in_string())
  85. def test_is_in_code(self):
  86. get = self.get_parser
  87. p = get('1.0')
  88. self.assertTrue(p.is_in_code())
  89. p = get('1.1')
  90. self.assertFalse(p.is_in_code())
  91. p = get('2.5')
  92. self.assertFalse(p.is_in_code())
  93. p = get('3.4')
  94. self.assertTrue(p.is_in_code())
  95. p = get('3.6')
  96. self.assertFalse(p.is_in_code())
  97. p = get('4.14')
  98. self.assertFalse(p.is_in_code())
  99. def test_get_surrounding_bracket(self):
  100. get = self.get_parser
  101. def without_mustclose(parser):
  102. # a utility function to get surrounding bracket
  103. # with mustclose=False
  104. return parser.get_surrounding_brackets(mustclose=False)
  105. def with_mustclose(parser):
  106. # a utility function to get surrounding bracket
  107. # with mustclose=True
  108. return parser.get_surrounding_brackets(mustclose=True)
  109. p = get('3.2')
  110. self.assertIsNone(with_mustclose(p))
  111. self.assertIsNone(without_mustclose(p))
  112. p = get('5.6')
  113. self.assertTupleEqual(without_mustclose(p), ('5.4', '5.25'))
  114. self.assertTupleEqual(without_mustclose(p), with_mustclose(p))
  115. p = get('5.23')
  116. self.assertTupleEqual(without_mustclose(p), ('5.21', '5.24'))
  117. self.assertTupleEqual(without_mustclose(p), with_mustclose(p))
  118. p = get('6.15')
  119. self.assertTupleEqual(without_mustclose(p), ('6.4', '6.end'))
  120. self.assertIsNone(with_mustclose(p))
  121. p = get('9.end')
  122. self.assertIsNone(with_mustclose(p))
  123. self.assertIsNone(without_mustclose(p))
  124. def test_get_expression(self):
  125. get = self.get_parser
  126. p = get('4.2')
  127. self.assertEqual(p.get_expression(), 'y ')
  128. p = get('4.7')
  129. with self.assertRaises(ValueError) as ve:
  130. p.get_expression()
  131. self.assertIn('is inside a code', str(ve.exception))
  132. p = get('5.25')
  133. self.assertEqual(p.get_expression(), 'range(10)')
  134. p = get('6.7')
  135. self.assertEqual(p.get_expression(), 'py')
  136. p = get('6.8')
  137. self.assertEqual(p.get_expression(), '')
  138. p = get('7.9')
  139. self.assertEqual(p.get_expression(), 'py')
  140. p = get('8.end')
  141. self.assertEqual(p.get_expression(), 'x.__len__')
  142. p = get('9.13')
  143. self.assertEqual(p.get_expression(), "r'asdf'")
  144. p = get('9.17')
  145. with self.assertRaises(ValueError) as ve:
  146. p.get_expression()
  147. self.assertIn('is inside a code', str(ve.exception))
  148. p = get('10.0')
  149. self.assertEqual(p.get_expression(), '')
  150. p = get('10.6')
  151. self.assertEqual(p.get_expression(), '')
  152. p = get('10.11')
  153. self.assertEqual(p.get_expression(), '')
  154. p = get('11.3')
  155. self.assertEqual(p.get_expression(), '')
  156. p = get('11.11')
  157. self.assertEqual(p.get_expression(), 'False')
  158. p = get('12.6')
  159. self.assertEqual(p.get_expression(), 'cliché')
  160. def test_eat_identifier(self):
  161. def is_valid_id(candidate):
  162. result = HyperParser._eat_identifier(candidate, 0, len(candidate))
  163. if result == len(candidate):
  164. return True
  165. elif result == 0:
  166. return False
  167. else:
  168. err_msg = "Unexpected result: {} (expected 0 or {}".format(
  169. result, len(candidate)
  170. )
  171. raise Exception(err_msg)
  172. # invalid first character which is valid elsewhere in an identifier
  173. self.assertFalse(is_valid_id('2notid'))
  174. # ASCII-only valid identifiers
  175. self.assertTrue(is_valid_id('valid_id'))
  176. self.assertTrue(is_valid_id('_valid_id'))
  177. self.assertTrue(is_valid_id('valid_id_'))
  178. self.assertTrue(is_valid_id('_2valid_id'))
  179. # keywords which should be "eaten"
  180. self.assertTrue(is_valid_id('True'))
  181. self.assertTrue(is_valid_id('False'))
  182. self.assertTrue(is_valid_id('None'))
  183. # keywords which should not be "eaten"
  184. self.assertFalse(is_valid_id('for'))
  185. self.assertFalse(is_valid_id('import'))
  186. self.assertFalse(is_valid_id('return'))
  187. # valid unicode identifiers
  188. self.assertTrue(is_valid_id('cliche'))
  189. self.assertTrue(is_valid_id('cliché'))
  190. self.assertTrue(is_valid_id('a٢'))
  191. # invalid unicode identifiers
  192. self.assertFalse(is_valid_id('2a'))
  193. self.assertFalse(is_valid_id('٢a'))
  194. self.assertFalse(is_valid_id('a²'))
  195. # valid identifier after "punctuation"
  196. self.assertEqual(HyperParser._eat_identifier('+ var', 0, 5), len('var'))
  197. self.assertEqual(HyperParser._eat_identifier('+var', 0, 4), len('var'))
  198. self.assertEqual(HyperParser._eat_identifier('.var', 0, 4), len('var'))
  199. # invalid identifiers
  200. self.assertFalse(is_valid_id('+'))
  201. self.assertFalse(is_valid_id(' '))
  202. self.assertFalse(is_valid_id(':'))
  203. self.assertFalse(is_valid_id('?'))
  204. self.assertFalse(is_valid_id('^'))
  205. self.assertFalse(is_valid_id('\\'))
  206. self.assertFalse(is_valid_id('"'))
  207. self.assertFalse(is_valid_id('"a string"'))
  208. def test_eat_identifier_various_lengths(self):
  209. eat_id = HyperParser._eat_identifier
  210. for length in range(1, 21):
  211. self.assertEqual(eat_id('a' * length, 0, length), length)
  212. self.assertEqual(eat_id('é' * length, 0, length), length)
  213. self.assertEqual(eat_id('a' + '2' * (length - 1), 0, length), length)
  214. self.assertEqual(eat_id('é' + '2' * (length - 1), 0, length), length)
  215. self.assertEqual(eat_id('é' + 'a' * (length - 1), 0, length), length)
  216. self.assertEqual(eat_id('é' * (length - 1) + 'a', 0, length), length)
  217. self.assertEqual(eat_id('+' * length, 0, length), 0)
  218. self.assertEqual(eat_id('2' + 'a' * (length - 1), 0, length), 0)
  219. self.assertEqual(eat_id('2' + 'é' * (length - 1), 0, length), 0)
  220. if __name__ == '__main__':
  221. unittest.main(verbosity=2)