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