• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"Test searchengine, coverage 99%."
2
3from idlelib import searchengine as se
4import unittest
5# from test.support import requires
6from tkinter import  BooleanVar, StringVar, TclError  # ,Tk, Text
7import tkinter.messagebox as tkMessageBox
8from idlelib.idle_test.mock_tk import Var, Mbox
9from idlelib.idle_test.mock_tk import Text as mockText
10import re
11
12# With mock replacements, the module does not use any gui widgets.
13# The use of tk.Text is avoided (for now, until mock Text is improved)
14# by patching instances with an index function returning what is needed.
15# This works because mock Text.get does not use .index.
16# The tkinter imports are used to restore searchengine.
17
18def setUpModule():
19    # Replace s-e module tkinter imports other than non-gui TclError.
20    se.BooleanVar = Var
21    se.StringVar = Var
22    se.tkMessageBox = Mbox
23
24def tearDownModule():
25    # Restore 'just in case', though other tests should also replace.
26    se.BooleanVar = BooleanVar
27    se.StringVar = StringVar
28    se.tkMessageBox = tkMessageBox
29
30
31class Mock:
32    def __init__(self, *args, **kwargs): pass
33
34class GetTest(unittest.TestCase):
35    # SearchEngine.get returns singleton created & saved on first call.
36    def test_get(self):
37        saved_Engine = se.SearchEngine
38        se.SearchEngine = Mock  # monkey-patch class
39        try:
40            root = Mock()
41            engine = se.get(root)
42            self.assertIsInstance(engine, se.SearchEngine)
43            self.assertIs(root._searchengine, engine)
44            self.assertIs(se.get(root), engine)
45        finally:
46            se.SearchEngine = saved_Engine  # restore class to module
47
48class GetLineColTest(unittest.TestCase):
49    #  Test simple text-independent helper function
50    def test_get_line_col(self):
51        self.assertEqual(se.get_line_col('1.0'), (1, 0))
52        self.assertEqual(se.get_line_col('1.11'), (1, 11))
53
54        self.assertRaises(ValueError, se.get_line_col, ('1.0 lineend'))
55        self.assertRaises(ValueError, se.get_line_col, ('end'))
56
57class GetSelectionTest(unittest.TestCase):
58    # Test text-dependent helper function.
59##    # Need gui for text.index('sel.first/sel.last/insert').
60##    @classmethod
61##    def setUpClass(cls):
62##        requires('gui')
63##        cls.root = Tk()
64##
65##    @classmethod
66##    def tearDownClass(cls):
67##        cls.root.destroy()
68##        del cls.root
69
70    def test_get_selection(self):
71        # text = Text(master=self.root)
72        text = mockText()
73        text.insert('1.0',  'Hello World!')
74
75        # fix text.index result when called in get_selection
76        def sel(s):
77            # select entire text, cursor irrelevant
78            if s == 'sel.first': return '1.0'
79            if s == 'sel.last': return '1.12'
80            raise TclError
81        text.index = sel  # replaces .tag_add('sel', '1.0, '1.12')
82        self.assertEqual(se.get_selection(text), ('1.0', '1.12'))
83
84        def mark(s):
85            # no selection, cursor after 'Hello'
86            if s == 'insert': return '1.5'
87            raise TclError
88        text.index = mark  # replaces .mark_set('insert', '1.5')
89        self.assertEqual(se.get_selection(text), ('1.5', '1.5'))
90
91
92class ReverseSearchTest(unittest.TestCase):
93    # Test helper function that searches backwards within a line.
94    def test_search_reverse(self):
95        Equal = self.assertEqual
96        line = "Here is an 'is' test text."
97        prog = re.compile('is')
98        Equal(se.search_reverse(prog, line, len(line)).span(), (12, 14))
99        Equal(se.search_reverse(prog, line, 14).span(), (12, 14))
100        Equal(se.search_reverse(prog, line, 13).span(), (5, 7))
101        Equal(se.search_reverse(prog, line, 7).span(), (5, 7))
102        Equal(se.search_reverse(prog, line, 6), None)
103
104
105class SearchEngineTest(unittest.TestCase):
106    # Test class methods that do not use Text widget.
107
108    def setUp(self):
109        self.engine = se.SearchEngine(root=None)
110        # Engine.root is only used to create error message boxes.
111        # The mock replacement ignores the root argument.
112
113    def test_is_get(self):
114        engine = self.engine
115        Equal = self.assertEqual
116
117        Equal(engine.getpat(), '')
118        engine.setpat('hello')
119        Equal(engine.getpat(), 'hello')
120
121        Equal(engine.isre(), False)
122        engine.revar.set(1)
123        Equal(engine.isre(), True)
124
125        Equal(engine.iscase(), False)
126        engine.casevar.set(1)
127        Equal(engine.iscase(), True)
128
129        Equal(engine.isword(), False)
130        engine.wordvar.set(1)
131        Equal(engine.isword(), True)
132
133        Equal(engine.iswrap(), True)
134        engine.wrapvar.set(0)
135        Equal(engine.iswrap(), False)
136
137        Equal(engine.isback(), False)
138        engine.backvar.set(1)
139        Equal(engine.isback(), True)
140
141    def test_setcookedpat(self):
142        engine = self.engine
143        engine.setcookedpat(r'\s')
144        self.assertEqual(engine.getpat(), r'\s')
145        engine.revar.set(1)
146        engine.setcookedpat(r'\s')
147        self.assertEqual(engine.getpat(), r'\\s')
148
149    def test_getcookedpat(self):
150        engine = self.engine
151        Equal = self.assertEqual
152
153        Equal(engine.getcookedpat(), '')
154        engine.setpat('hello')
155        Equal(engine.getcookedpat(), 'hello')
156        engine.wordvar.set(True)
157        Equal(engine.getcookedpat(), r'\bhello\b')
158        engine.wordvar.set(False)
159
160        engine.setpat(r'\s')
161        Equal(engine.getcookedpat(), r'\\s')
162        engine.revar.set(True)
163        Equal(engine.getcookedpat(), r'\s')
164
165    def test_getprog(self):
166        engine = self.engine
167        Equal = self.assertEqual
168
169        engine.setpat('Hello')
170        temppat = engine.getprog()
171        Equal(temppat.pattern, re.compile('Hello', re.IGNORECASE).pattern)
172        engine.casevar.set(1)
173        temppat = engine.getprog()
174        Equal(temppat.pattern, re.compile('Hello').pattern, 0)
175
176        engine.setpat('')
177        Equal(engine.getprog(), None)
178        engine.setpat('+')
179        engine.revar.set(1)
180        Equal(engine.getprog(), None)
181        self.assertEqual(Mbox.showerror.message,
182                         'Error: nothing to repeat at position 0\nPattern: +')
183
184    def test_report_error(self):
185        showerror = Mbox.showerror
186        Equal = self.assertEqual
187        pat = '[a-z'
188        msg = 'unexpected end of regular expression'
189
190        Equal(self.engine.report_error(pat, msg), None)
191        Equal(showerror.title, 'Regular expression error')
192        expected_message = ("Error: " + msg + "\nPattern: [a-z")
193        Equal(showerror.message, expected_message)
194
195        Equal(self.engine.report_error(pat, msg, 5), None)
196        Equal(showerror.title, 'Regular expression error')
197        expected_message += "\nOffset: 5"
198        Equal(showerror.message, expected_message)
199
200
201class SearchTest(unittest.TestCase):
202    # Test that search_text makes right call to right method.
203
204    @classmethod
205    def setUpClass(cls):
206##        requires('gui')
207##        cls.root = Tk()
208##        cls.text = Text(master=cls.root)
209        cls.text = mockText()
210        test_text = (
211            'First line\n'
212            'Line with target\n'
213            'Last line\n')
214        cls.text.insert('1.0', test_text)
215        cls.pat = re.compile('target')
216
217        cls.engine = se.SearchEngine(None)
218        cls.engine.search_forward = lambda *args: ('f', args)
219        cls.engine.search_backward = lambda *args: ('b', args)
220
221##    @classmethod
222##    def tearDownClass(cls):
223##        cls.root.destroy()
224##        del cls.root
225
226    def test_search(self):
227        Equal = self.assertEqual
228        engine = self.engine
229        search = engine.search_text
230        text = self.text
231        pat = self.pat
232
233        engine.patvar.set(None)
234        #engine.revar.set(pat)
235        Equal(search(text), None)
236
237        def mark(s):
238            # no selection, cursor after 'Hello'
239            if s == 'insert': return '1.5'
240            raise TclError
241        text.index = mark
242        Equal(search(text, pat), ('f', (text, pat, 1, 5, True, False)))
243        engine.wrapvar.set(False)
244        Equal(search(text, pat), ('f', (text, pat, 1, 5, False, False)))
245        engine.wrapvar.set(True)
246        engine.backvar.set(True)
247        Equal(search(text, pat), ('b', (text, pat, 1, 5, True, False)))
248        engine.backvar.set(False)
249
250        def sel(s):
251            if s == 'sel.first': return '2.10'
252            if s == 'sel.last': return '2.16'
253            raise TclError
254        text.index = sel
255        Equal(search(text, pat), ('f', (text, pat, 2, 16, True, False)))
256        Equal(search(text, pat, True), ('f', (text, pat, 2, 10, True, True)))
257        engine.backvar.set(True)
258        Equal(search(text, pat), ('b', (text, pat, 2, 10, True, False)))
259        Equal(search(text, pat, True), ('b', (text, pat, 2, 16, True, True)))
260
261
262class ForwardBackwardTest(unittest.TestCase):
263    # Test that search_forward method finds the target.
264##    @classmethod
265##    def tearDownClass(cls):
266##        cls.root.destroy()
267##        del cls.root
268
269    @classmethod
270    def setUpClass(cls):
271        cls.engine = se.SearchEngine(None)
272##        requires('gui')
273##        cls.root = Tk()
274##        cls.text = Text(master=cls.root)
275        cls.text = mockText()
276        # search_backward calls index('end-1c')
277        cls.text.index = lambda index: '4.0'
278        test_text = (
279            'First line\n'
280            'Line with target\n'
281            'Last line\n')
282        cls.text.insert('1.0', test_text)
283        cls.pat = re.compile('target')
284        cls.res = (2, (10, 16))  # line, slice indexes of 'target'
285        cls.failpat = re.compile('xyz')  # not in text
286        cls.emptypat = re.compile(r'\w*')  # empty match possible
287
288    def make_search(self, func):
289        def search(pat, line, col, wrap, ok=0):
290            res = func(self.text, pat, line, col, wrap, ok)
291            # res is (line, matchobject) or None
292            return (res[0], res[1].span()) if res else res
293        return search
294
295    def test_search_forward(self):
296        # search for non-empty match
297        Equal = self.assertEqual
298        forward = self.make_search(self.engine.search_forward)
299        pat = self.pat
300        Equal(forward(pat, 1, 0, True), self.res)
301        Equal(forward(pat, 3, 0, True), self.res)  # wrap
302        Equal(forward(pat, 3, 0, False), None)  # no wrap
303        Equal(forward(pat, 2, 10, False), self.res)
304
305        Equal(forward(self.failpat, 1, 0, True), None)
306        Equal(forward(self.emptypat, 2,  9, True, ok=True), (2, (9, 9)))
307        #Equal(forward(self.emptypat, 2, 9, True), self.res)
308        # While the initial empty match is correctly ignored, skipping
309        # the rest of the line and returning (3, (0,4)) seems buggy - tjr.
310        Equal(forward(self.emptypat, 2, 10, True), self.res)
311
312    def test_search_backward(self):
313        # search for non-empty match
314        Equal = self.assertEqual
315        backward = self.make_search(self.engine.search_backward)
316        pat = self.pat
317        Equal(backward(pat, 3, 5, True), self.res)
318        Equal(backward(pat, 2, 0, True), self.res)  # wrap
319        Equal(backward(pat, 2, 0, False), None)  # no wrap
320        Equal(backward(pat, 2, 16, False), self.res)
321
322        Equal(backward(self.failpat, 3, 9, True), None)
323        Equal(backward(self.emptypat, 2,  10, True, ok=True), (2, (9,9)))
324        # Accepted because 9 < 10, not because ok=True.
325        # It is not clear that ok=True is useful going back - tjr
326        Equal(backward(self.emptypat, 2, 9, True), (2, (5, 9)))
327
328
329if __name__ == '__main__':
330    unittest.main(verbosity=2)
331