ยปCore Development>Code coverage>Lib/idlelib/idle_test/test_searchengine.py

Python code coverage for Lib/idlelib/idle_test/test_searchengine.py

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