17db96d56Sopenharmony_ci"Test searchengine, coverage 99%." 27db96d56Sopenharmony_ci 37db96d56Sopenharmony_cifrom idlelib import searchengine as se 47db96d56Sopenharmony_ciimport unittest 57db96d56Sopenharmony_ci# from test.support import requires 67db96d56Sopenharmony_cifrom tkinter import BooleanVar, StringVar, TclError # ,Tk, Text 77db96d56Sopenharmony_cifrom tkinter import messagebox 87db96d56Sopenharmony_cifrom idlelib.idle_test.mock_tk import Var, Mbox 97db96d56Sopenharmony_cifrom idlelib.idle_test.mock_tk import Text as mockText 107db96d56Sopenharmony_ciimport re 117db96d56Sopenharmony_ci 127db96d56Sopenharmony_ci# With mock replacements, the module does not use any gui widgets. 137db96d56Sopenharmony_ci# The use of tk.Text is avoided (for now, until mock Text is improved) 147db96d56Sopenharmony_ci# by patching instances with an index function returning what is needed. 157db96d56Sopenharmony_ci# This works because mock Text.get does not use .index. 167db96d56Sopenharmony_ci# The tkinter imports are used to restore searchengine. 177db96d56Sopenharmony_ci 187db96d56Sopenharmony_cidef setUpModule(): 197db96d56Sopenharmony_ci # Replace s-e module tkinter imports other than non-gui TclError. 207db96d56Sopenharmony_ci se.BooleanVar = Var 217db96d56Sopenharmony_ci se.StringVar = Var 227db96d56Sopenharmony_ci se.messagebox = Mbox 237db96d56Sopenharmony_ci 247db96d56Sopenharmony_cidef tearDownModule(): 257db96d56Sopenharmony_ci # Restore 'just in case', though other tests should also replace. 267db96d56Sopenharmony_ci se.BooleanVar = BooleanVar 277db96d56Sopenharmony_ci se.StringVar = StringVar 287db96d56Sopenharmony_ci se.messagebox = messagebox 297db96d56Sopenharmony_ci 307db96d56Sopenharmony_ci 317db96d56Sopenharmony_ciclass Mock: 327db96d56Sopenharmony_ci def __init__(self, *args, **kwargs): pass 337db96d56Sopenharmony_ci 347db96d56Sopenharmony_ciclass GetTest(unittest.TestCase): 357db96d56Sopenharmony_ci # SearchEngine.get returns singleton created & saved on first call. 367db96d56Sopenharmony_ci def test_get(self): 377db96d56Sopenharmony_ci saved_Engine = se.SearchEngine 387db96d56Sopenharmony_ci se.SearchEngine = Mock # monkey-patch class 397db96d56Sopenharmony_ci try: 407db96d56Sopenharmony_ci root = Mock() 417db96d56Sopenharmony_ci engine = se.get(root) 427db96d56Sopenharmony_ci self.assertIsInstance(engine, se.SearchEngine) 437db96d56Sopenharmony_ci self.assertIs(root._searchengine, engine) 447db96d56Sopenharmony_ci self.assertIs(se.get(root), engine) 457db96d56Sopenharmony_ci finally: 467db96d56Sopenharmony_ci se.SearchEngine = saved_Engine # restore class to module 477db96d56Sopenharmony_ci 487db96d56Sopenharmony_ciclass GetLineColTest(unittest.TestCase): 497db96d56Sopenharmony_ci # Test simple text-independent helper function 507db96d56Sopenharmony_ci def test_get_line_col(self): 517db96d56Sopenharmony_ci self.assertEqual(se.get_line_col('1.0'), (1, 0)) 527db96d56Sopenharmony_ci self.assertEqual(se.get_line_col('1.11'), (1, 11)) 537db96d56Sopenharmony_ci 547db96d56Sopenharmony_ci self.assertRaises(ValueError, se.get_line_col, ('1.0 lineend')) 557db96d56Sopenharmony_ci self.assertRaises(ValueError, se.get_line_col, ('end')) 567db96d56Sopenharmony_ci 577db96d56Sopenharmony_ciclass GetSelectionTest(unittest.TestCase): 587db96d56Sopenharmony_ci # Test text-dependent helper function. 597db96d56Sopenharmony_ci## # Need gui for text.index('sel.first/sel.last/insert'). 607db96d56Sopenharmony_ci## @classmethod 617db96d56Sopenharmony_ci## def setUpClass(cls): 627db96d56Sopenharmony_ci## requires('gui') 637db96d56Sopenharmony_ci## cls.root = Tk() 647db96d56Sopenharmony_ci## 657db96d56Sopenharmony_ci## @classmethod 667db96d56Sopenharmony_ci## def tearDownClass(cls): 677db96d56Sopenharmony_ci## cls.root.destroy() 687db96d56Sopenharmony_ci## del cls.root 697db96d56Sopenharmony_ci 707db96d56Sopenharmony_ci def test_get_selection(self): 717db96d56Sopenharmony_ci # text = Text(master=self.root) 727db96d56Sopenharmony_ci text = mockText() 737db96d56Sopenharmony_ci text.insert('1.0', 'Hello World!') 747db96d56Sopenharmony_ci 757db96d56Sopenharmony_ci # fix text.index result when called in get_selection 767db96d56Sopenharmony_ci def sel(s): 777db96d56Sopenharmony_ci # select entire text, cursor irrelevant 787db96d56Sopenharmony_ci if s == 'sel.first': return '1.0' 797db96d56Sopenharmony_ci if s == 'sel.last': return '1.12' 807db96d56Sopenharmony_ci raise TclError 817db96d56Sopenharmony_ci text.index = sel # replaces .tag_add('sel', '1.0, '1.12') 827db96d56Sopenharmony_ci self.assertEqual(se.get_selection(text), ('1.0', '1.12')) 837db96d56Sopenharmony_ci 847db96d56Sopenharmony_ci def mark(s): 857db96d56Sopenharmony_ci # no selection, cursor after 'Hello' 867db96d56Sopenharmony_ci if s == 'insert': return '1.5' 877db96d56Sopenharmony_ci raise TclError 887db96d56Sopenharmony_ci text.index = mark # replaces .mark_set('insert', '1.5') 897db96d56Sopenharmony_ci self.assertEqual(se.get_selection(text), ('1.5', '1.5')) 907db96d56Sopenharmony_ci 917db96d56Sopenharmony_ci 927db96d56Sopenharmony_ciclass ReverseSearchTest(unittest.TestCase): 937db96d56Sopenharmony_ci # Test helper function that searches backwards within a line. 947db96d56Sopenharmony_ci def test_search_reverse(self): 957db96d56Sopenharmony_ci Equal = self.assertEqual 967db96d56Sopenharmony_ci line = "Here is an 'is' test text." 977db96d56Sopenharmony_ci prog = re.compile('is') 987db96d56Sopenharmony_ci Equal(se.search_reverse(prog, line, len(line)).span(), (12, 14)) 997db96d56Sopenharmony_ci Equal(se.search_reverse(prog, line, 14).span(), (12, 14)) 1007db96d56Sopenharmony_ci Equal(se.search_reverse(prog, line, 13).span(), (5, 7)) 1017db96d56Sopenharmony_ci Equal(se.search_reverse(prog, line, 7).span(), (5, 7)) 1027db96d56Sopenharmony_ci Equal(se.search_reverse(prog, line, 6), None) 1037db96d56Sopenharmony_ci 1047db96d56Sopenharmony_ci 1057db96d56Sopenharmony_ciclass SearchEngineTest(unittest.TestCase): 1067db96d56Sopenharmony_ci # Test class methods that do not use Text widget. 1077db96d56Sopenharmony_ci 1087db96d56Sopenharmony_ci def setUp(self): 1097db96d56Sopenharmony_ci self.engine = se.SearchEngine(root=None) 1107db96d56Sopenharmony_ci # Engine.root is only used to create error message boxes. 1117db96d56Sopenharmony_ci # The mock replacement ignores the root argument. 1127db96d56Sopenharmony_ci 1137db96d56Sopenharmony_ci def test_is_get(self): 1147db96d56Sopenharmony_ci engine = self.engine 1157db96d56Sopenharmony_ci Equal = self.assertEqual 1167db96d56Sopenharmony_ci 1177db96d56Sopenharmony_ci Equal(engine.getpat(), '') 1187db96d56Sopenharmony_ci engine.setpat('hello') 1197db96d56Sopenharmony_ci Equal(engine.getpat(), 'hello') 1207db96d56Sopenharmony_ci 1217db96d56Sopenharmony_ci Equal(engine.isre(), False) 1227db96d56Sopenharmony_ci engine.revar.set(1) 1237db96d56Sopenharmony_ci Equal(engine.isre(), True) 1247db96d56Sopenharmony_ci 1257db96d56Sopenharmony_ci Equal(engine.iscase(), False) 1267db96d56Sopenharmony_ci engine.casevar.set(1) 1277db96d56Sopenharmony_ci Equal(engine.iscase(), True) 1287db96d56Sopenharmony_ci 1297db96d56Sopenharmony_ci Equal(engine.isword(), False) 1307db96d56Sopenharmony_ci engine.wordvar.set(1) 1317db96d56Sopenharmony_ci Equal(engine.isword(), True) 1327db96d56Sopenharmony_ci 1337db96d56Sopenharmony_ci Equal(engine.iswrap(), True) 1347db96d56Sopenharmony_ci engine.wrapvar.set(0) 1357db96d56Sopenharmony_ci Equal(engine.iswrap(), False) 1367db96d56Sopenharmony_ci 1377db96d56Sopenharmony_ci Equal(engine.isback(), False) 1387db96d56Sopenharmony_ci engine.backvar.set(1) 1397db96d56Sopenharmony_ci Equal(engine.isback(), True) 1407db96d56Sopenharmony_ci 1417db96d56Sopenharmony_ci def test_setcookedpat(self): 1427db96d56Sopenharmony_ci engine = self.engine 1437db96d56Sopenharmony_ci engine.setcookedpat(r'\s') 1447db96d56Sopenharmony_ci self.assertEqual(engine.getpat(), r'\s') 1457db96d56Sopenharmony_ci engine.revar.set(1) 1467db96d56Sopenharmony_ci engine.setcookedpat(r'\s') 1477db96d56Sopenharmony_ci self.assertEqual(engine.getpat(), r'\\s') 1487db96d56Sopenharmony_ci 1497db96d56Sopenharmony_ci def test_getcookedpat(self): 1507db96d56Sopenharmony_ci engine = self.engine 1517db96d56Sopenharmony_ci Equal = self.assertEqual 1527db96d56Sopenharmony_ci 1537db96d56Sopenharmony_ci Equal(engine.getcookedpat(), '') 1547db96d56Sopenharmony_ci engine.setpat('hello') 1557db96d56Sopenharmony_ci Equal(engine.getcookedpat(), 'hello') 1567db96d56Sopenharmony_ci engine.wordvar.set(True) 1577db96d56Sopenharmony_ci Equal(engine.getcookedpat(), r'\bhello\b') 1587db96d56Sopenharmony_ci engine.wordvar.set(False) 1597db96d56Sopenharmony_ci 1607db96d56Sopenharmony_ci engine.setpat(r'\s') 1617db96d56Sopenharmony_ci Equal(engine.getcookedpat(), r'\\s') 1627db96d56Sopenharmony_ci engine.revar.set(True) 1637db96d56Sopenharmony_ci Equal(engine.getcookedpat(), r'\s') 1647db96d56Sopenharmony_ci 1657db96d56Sopenharmony_ci def test_getprog(self): 1667db96d56Sopenharmony_ci engine = self.engine 1677db96d56Sopenharmony_ci Equal = self.assertEqual 1687db96d56Sopenharmony_ci 1697db96d56Sopenharmony_ci engine.setpat('Hello') 1707db96d56Sopenharmony_ci temppat = engine.getprog() 1717db96d56Sopenharmony_ci Equal(temppat.pattern, re.compile('Hello', re.IGNORECASE).pattern) 1727db96d56Sopenharmony_ci engine.casevar.set(1) 1737db96d56Sopenharmony_ci temppat = engine.getprog() 1747db96d56Sopenharmony_ci Equal(temppat.pattern, re.compile('Hello').pattern, 0) 1757db96d56Sopenharmony_ci 1767db96d56Sopenharmony_ci engine.setpat('') 1777db96d56Sopenharmony_ci Equal(engine.getprog(), None) 1787db96d56Sopenharmony_ci Equal(Mbox.showerror.message, 1797db96d56Sopenharmony_ci 'Error: Empty regular expression') 1807db96d56Sopenharmony_ci engine.setpat('+') 1817db96d56Sopenharmony_ci engine.revar.set(1) 1827db96d56Sopenharmony_ci Equal(engine.getprog(), None) 1837db96d56Sopenharmony_ci Equal(Mbox.showerror.message, 1847db96d56Sopenharmony_ci 'Error: nothing to repeat\nPattern: +\nOffset: 0') 1857db96d56Sopenharmony_ci 1867db96d56Sopenharmony_ci def test_report_error(self): 1877db96d56Sopenharmony_ci showerror = Mbox.showerror 1887db96d56Sopenharmony_ci Equal = self.assertEqual 1897db96d56Sopenharmony_ci pat = '[a-z' 1907db96d56Sopenharmony_ci msg = 'unexpected end of regular expression' 1917db96d56Sopenharmony_ci 1927db96d56Sopenharmony_ci Equal(self.engine.report_error(pat, msg), None) 1937db96d56Sopenharmony_ci Equal(showerror.title, 'Regular expression error') 1947db96d56Sopenharmony_ci expected_message = ("Error: " + msg + "\nPattern: [a-z") 1957db96d56Sopenharmony_ci Equal(showerror.message, expected_message) 1967db96d56Sopenharmony_ci 1977db96d56Sopenharmony_ci Equal(self.engine.report_error(pat, msg, 5), None) 1987db96d56Sopenharmony_ci Equal(showerror.title, 'Regular expression error') 1997db96d56Sopenharmony_ci expected_message += "\nOffset: 5" 2007db96d56Sopenharmony_ci Equal(showerror.message, expected_message) 2017db96d56Sopenharmony_ci 2027db96d56Sopenharmony_ci 2037db96d56Sopenharmony_ciclass SearchTest(unittest.TestCase): 2047db96d56Sopenharmony_ci # Test that search_text makes right call to right method. 2057db96d56Sopenharmony_ci 2067db96d56Sopenharmony_ci @classmethod 2077db96d56Sopenharmony_ci def setUpClass(cls): 2087db96d56Sopenharmony_ci## requires('gui') 2097db96d56Sopenharmony_ci## cls.root = Tk() 2107db96d56Sopenharmony_ci## cls.text = Text(master=cls.root) 2117db96d56Sopenharmony_ci cls.text = mockText() 2127db96d56Sopenharmony_ci test_text = ( 2137db96d56Sopenharmony_ci 'First line\n' 2147db96d56Sopenharmony_ci 'Line with target\n' 2157db96d56Sopenharmony_ci 'Last line\n') 2167db96d56Sopenharmony_ci cls.text.insert('1.0', test_text) 2177db96d56Sopenharmony_ci cls.pat = re.compile('target') 2187db96d56Sopenharmony_ci 2197db96d56Sopenharmony_ci cls.engine = se.SearchEngine(None) 2207db96d56Sopenharmony_ci cls.engine.search_forward = lambda *args: ('f', args) 2217db96d56Sopenharmony_ci cls.engine.search_backward = lambda *args: ('b', args) 2227db96d56Sopenharmony_ci 2237db96d56Sopenharmony_ci## @classmethod 2247db96d56Sopenharmony_ci## def tearDownClass(cls): 2257db96d56Sopenharmony_ci## cls.root.destroy() 2267db96d56Sopenharmony_ci## del cls.root 2277db96d56Sopenharmony_ci 2287db96d56Sopenharmony_ci def test_search(self): 2297db96d56Sopenharmony_ci Equal = self.assertEqual 2307db96d56Sopenharmony_ci engine = self.engine 2317db96d56Sopenharmony_ci search = engine.search_text 2327db96d56Sopenharmony_ci text = self.text 2337db96d56Sopenharmony_ci pat = self.pat 2347db96d56Sopenharmony_ci 2357db96d56Sopenharmony_ci engine.patvar.set(None) 2367db96d56Sopenharmony_ci #engine.revar.set(pat) 2377db96d56Sopenharmony_ci Equal(search(text), None) 2387db96d56Sopenharmony_ci 2397db96d56Sopenharmony_ci def mark(s): 2407db96d56Sopenharmony_ci # no selection, cursor after 'Hello' 2417db96d56Sopenharmony_ci if s == 'insert': return '1.5' 2427db96d56Sopenharmony_ci raise TclError 2437db96d56Sopenharmony_ci text.index = mark 2447db96d56Sopenharmony_ci Equal(search(text, pat), ('f', (text, pat, 1, 5, True, False))) 2457db96d56Sopenharmony_ci engine.wrapvar.set(False) 2467db96d56Sopenharmony_ci Equal(search(text, pat), ('f', (text, pat, 1, 5, False, False))) 2477db96d56Sopenharmony_ci engine.wrapvar.set(True) 2487db96d56Sopenharmony_ci engine.backvar.set(True) 2497db96d56Sopenharmony_ci Equal(search(text, pat), ('b', (text, pat, 1, 5, True, False))) 2507db96d56Sopenharmony_ci engine.backvar.set(False) 2517db96d56Sopenharmony_ci 2527db96d56Sopenharmony_ci def sel(s): 2537db96d56Sopenharmony_ci if s == 'sel.first': return '2.10' 2547db96d56Sopenharmony_ci if s == 'sel.last': return '2.16' 2557db96d56Sopenharmony_ci raise TclError 2567db96d56Sopenharmony_ci text.index = sel 2577db96d56Sopenharmony_ci Equal(search(text, pat), ('f', (text, pat, 2, 16, True, False))) 2587db96d56Sopenharmony_ci Equal(search(text, pat, True), ('f', (text, pat, 2, 10, True, True))) 2597db96d56Sopenharmony_ci engine.backvar.set(True) 2607db96d56Sopenharmony_ci Equal(search(text, pat), ('b', (text, pat, 2, 10, True, False))) 2617db96d56Sopenharmony_ci Equal(search(text, pat, True), ('b', (text, pat, 2, 16, True, True))) 2627db96d56Sopenharmony_ci 2637db96d56Sopenharmony_ci 2647db96d56Sopenharmony_ciclass ForwardBackwardTest(unittest.TestCase): 2657db96d56Sopenharmony_ci # Test that search_forward method finds the target. 2667db96d56Sopenharmony_ci## @classmethod 2677db96d56Sopenharmony_ci## def tearDownClass(cls): 2687db96d56Sopenharmony_ci## cls.root.destroy() 2697db96d56Sopenharmony_ci## del cls.root 2707db96d56Sopenharmony_ci 2717db96d56Sopenharmony_ci @classmethod 2727db96d56Sopenharmony_ci def setUpClass(cls): 2737db96d56Sopenharmony_ci cls.engine = se.SearchEngine(None) 2747db96d56Sopenharmony_ci## requires('gui') 2757db96d56Sopenharmony_ci## cls.root = Tk() 2767db96d56Sopenharmony_ci## cls.text = Text(master=cls.root) 2777db96d56Sopenharmony_ci cls.text = mockText() 2787db96d56Sopenharmony_ci # search_backward calls index('end-1c') 2797db96d56Sopenharmony_ci cls.text.index = lambda index: '4.0' 2807db96d56Sopenharmony_ci test_text = ( 2817db96d56Sopenharmony_ci 'First line\n' 2827db96d56Sopenharmony_ci 'Line with target\n' 2837db96d56Sopenharmony_ci 'Last line\n') 2847db96d56Sopenharmony_ci cls.text.insert('1.0', test_text) 2857db96d56Sopenharmony_ci cls.pat = re.compile('target') 2867db96d56Sopenharmony_ci cls.res = (2, (10, 16)) # line, slice indexes of 'target' 2877db96d56Sopenharmony_ci cls.failpat = re.compile('xyz') # not in text 2887db96d56Sopenharmony_ci cls.emptypat = re.compile(r'\w*') # empty match possible 2897db96d56Sopenharmony_ci 2907db96d56Sopenharmony_ci def make_search(self, func): 2917db96d56Sopenharmony_ci def search(pat, line, col, wrap, ok=0): 2927db96d56Sopenharmony_ci res = func(self.text, pat, line, col, wrap, ok) 2937db96d56Sopenharmony_ci # res is (line, matchobject) or None 2947db96d56Sopenharmony_ci return (res[0], res[1].span()) if res else res 2957db96d56Sopenharmony_ci return search 2967db96d56Sopenharmony_ci 2977db96d56Sopenharmony_ci def test_search_forward(self): 2987db96d56Sopenharmony_ci # search for non-empty match 2997db96d56Sopenharmony_ci Equal = self.assertEqual 3007db96d56Sopenharmony_ci forward = self.make_search(self.engine.search_forward) 3017db96d56Sopenharmony_ci pat = self.pat 3027db96d56Sopenharmony_ci Equal(forward(pat, 1, 0, True), self.res) 3037db96d56Sopenharmony_ci Equal(forward(pat, 3, 0, True), self.res) # wrap 3047db96d56Sopenharmony_ci Equal(forward(pat, 3, 0, False), None) # no wrap 3057db96d56Sopenharmony_ci Equal(forward(pat, 2, 10, False), self.res) 3067db96d56Sopenharmony_ci 3077db96d56Sopenharmony_ci Equal(forward(self.failpat, 1, 0, True), None) 3087db96d56Sopenharmony_ci Equal(forward(self.emptypat, 2, 9, True, ok=True), (2, (9, 9))) 3097db96d56Sopenharmony_ci #Equal(forward(self.emptypat, 2, 9, True), self.res) 3107db96d56Sopenharmony_ci # While the initial empty match is correctly ignored, skipping 3117db96d56Sopenharmony_ci # the rest of the line and returning (3, (0,4)) seems buggy - tjr. 3127db96d56Sopenharmony_ci Equal(forward(self.emptypat, 2, 10, True), self.res) 3137db96d56Sopenharmony_ci 3147db96d56Sopenharmony_ci def test_search_backward(self): 3157db96d56Sopenharmony_ci # search for non-empty match 3167db96d56Sopenharmony_ci Equal = self.assertEqual 3177db96d56Sopenharmony_ci backward = self.make_search(self.engine.search_backward) 3187db96d56Sopenharmony_ci pat = self.pat 3197db96d56Sopenharmony_ci Equal(backward(pat, 3, 5, True), self.res) 3207db96d56Sopenharmony_ci Equal(backward(pat, 2, 0, True), self.res) # wrap 3217db96d56Sopenharmony_ci Equal(backward(pat, 2, 0, False), None) # no wrap 3227db96d56Sopenharmony_ci Equal(backward(pat, 2, 16, False), self.res) 3237db96d56Sopenharmony_ci 3247db96d56Sopenharmony_ci Equal(backward(self.failpat, 3, 9, True), None) 3257db96d56Sopenharmony_ci Equal(backward(self.emptypat, 2, 10, True, ok=True), (2, (9,9))) 3267db96d56Sopenharmony_ci # Accepted because 9 < 10, not because ok=True. 3277db96d56Sopenharmony_ci # It is not clear that ok=True is useful going back - tjr 3287db96d56Sopenharmony_ci Equal(backward(self.emptypat, 2, 9, True), (2, (5, 9))) 3297db96d56Sopenharmony_ci 3307db96d56Sopenharmony_ci 3317db96d56Sopenharmony_ciif __name__ == '__main__': 3327db96d56Sopenharmony_ci unittest.main(verbosity=2) 333