mirror of
				https://github.com/python/cpython.git
				synced 2025-10-25 10:44:55 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			329 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			329 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| '''Test functions and SearchEngine class in SearchEngine.py.'''
 | |
| 
 | |
| # With mock replacements, the module does not use any gui widgets.
 | |
| # The use of tk.Text is avoided (for now, until mock Text is improved)
 | |
| # by patching instances with an index function returning what is needed.
 | |
| # This works because mock Text.get does not use .index.
 | |
| 
 | |
| import re
 | |
| import unittest
 | |
| from test.support import requires
 | |
| from tkinter import  BooleanVar, StringVar, TclError  # ,Tk, Text
 | |
| import tkinter.messagebox as tkMessageBox
 | |
| from idlelib import SearchEngine as se
 | |
| from idlelib.idle_test.mock_tk import Var, Mbox
 | |
| from idlelib.idle_test.mock_tk import Text as mockText
 | |
| 
 | |
| def setUpModule():
 | |
|     # Replace s-e module tkinter imports other than non-gui TclError.
 | |
|     se.BooleanVar = Var
 | |
|     se.StringVar = Var
 | |
|     se.tkMessageBox = Mbox
 | |
| 
 | |
| def tearDownModule():
 | |
|     # Restore 'just in case', though other tests should also replace.
 | |
|     se.BooleanVar = BooleanVar
 | |
|     se.StringVar = StringVar
 | |
|     se.tkMessageBox = tkMessageBox
 | |
| 
 | |
| 
 | |
| class Mock:
 | |
|     def __init__(self, *args, **kwargs): pass
 | |
| 
 | |
| class GetTest(unittest.TestCase):
 | |
|     # SearchEngine.get returns singleton created & saved on first call.
 | |
|     def test_get(self):
 | |
|         saved_Engine = se.SearchEngine
 | |
|         se.SearchEngine = Mock  # monkey-patch class
 | |
|         try:
 | |
|             root = Mock()
 | |
|             engine = se.get(root)
 | |
|             self.assertIsInstance(engine, se.SearchEngine)
 | |
|             self.assertIs(root._searchengine, engine)
 | |
|             self.assertIs(se.get(root), engine)
 | |
|         finally:
 | |
|             se.SearchEngine = saved_Engine  # restore class to module
 | |
| 
 | |
| class GetLineColTest(unittest.TestCase):
 | |
|     #  Test simple text-independent helper function
 | |
|     def test_get_line_col(self):
 | |
|         self.assertEqual(se.get_line_col('1.0'), (1, 0))
 | |
|         self.assertEqual(se.get_line_col('1.11'), (1, 11))
 | |
| 
 | |
|         self.assertRaises(ValueError, se.get_line_col, ('1.0 lineend'))
 | |
|         self.assertRaises(ValueError, se.get_line_col, ('end'))
 | |
| 
 | |
| class GetSelectionTest(unittest.TestCase):
 | |
|     # Test text-dependent helper function.
 | |
| ##    # Need gui for text.index('sel.first/sel.last/insert').
 | |
| ##    @classmethod
 | |
| ##    def setUpClass(cls):
 | |
| ##        requires('gui')
 | |
| ##        cls.root = Tk()
 | |
| ##
 | |
| ##    @classmethod
 | |
| ##    def tearDownClass(cls):
 | |
| ##        cls.root.destroy()
 | |
| ##        del cls.root
 | |
| 
 | |
|     def test_get_selection(self):
 | |
|         # text = Text(master=self.root)
 | |
|         text = mockText()
 | |
|         text.insert('1.0',  'Hello World!')
 | |
| 
 | |
|         # fix text.index result when called in get_selection
 | |
|         def sel(s):
 | |
|             # select entire text, cursor irrelevant
 | |
|             if s == 'sel.first': return '1.0'
 | |
|             if s == 'sel.last': return '1.12'
 | |
|             raise TclError
 | |
|         text.index = sel  # replaces .tag_add('sel', '1.0, '1.12')
 | |
|         self.assertEqual(se.get_selection(text), ('1.0', '1.12'))
 | |
| 
 | |
|         def mark(s):
 | |
|             # no selection, cursor after 'Hello'
 | |
|             if s == 'insert': return '1.5'
 | |
|             raise TclError
 | |
|         text.index = mark  # replaces .mark_set('insert', '1.5')
 | |
|         self.assertEqual(se.get_selection(text), ('1.5', '1.5'))
 | |
| 
 | |
| 
 | |
| class ReverseSearchTest(unittest.TestCase):
 | |
|     # Test helper function that searches backwards within a line.
 | |
|     def test_search_reverse(self):
 | |
|         Equal = self.assertEqual
 | |
|         line = "Here is an 'is' test text."
 | |
|         prog = re.compile('is')
 | |
|         Equal(se.search_reverse(prog, line, len(line)).span(), (12, 14))
 | |
|         Equal(se.search_reverse(prog, line, 14).span(), (12, 14))
 | |
|         Equal(se.search_reverse(prog, line, 13).span(), (5, 7))
 | |
|         Equal(se.search_reverse(prog, line, 7).span(), (5, 7))
 | |
|         Equal(se.search_reverse(prog, line, 6), None)
 | |
| 
 | |
| 
 | |
| class SearchEngineTest(unittest.TestCase):
 | |
|     # Test class methods that do not use Text widget.
 | |
| 
 | |
|     def setUp(self):
 | |
|         self.engine = se.SearchEngine(root=None)
 | |
|         # Engine.root is only used to create error message boxes.
 | |
|         # The mock replacement ignores the root argument.
 | |
| 
 | |
|     def test_is_get(self):
 | |
|         engine = self.engine
 | |
|         Equal = self.assertEqual
 | |
| 
 | |
|         Equal(engine.getpat(), '')
 | |
|         engine.setpat('hello')
 | |
|         Equal(engine.getpat(), 'hello')
 | |
| 
 | |
|         Equal(engine.isre(), False)
 | |
|         engine.revar.set(1)
 | |
|         Equal(engine.isre(), True)
 | |
| 
 | |
|         Equal(engine.iscase(), False)
 | |
|         engine.casevar.set(1)
 | |
|         Equal(engine.iscase(), True)
 | |
| 
 | |
|         Equal(engine.isword(), False)
 | |
|         engine.wordvar.set(1)
 | |
|         Equal(engine.isword(), True)
 | |
| 
 | |
|         Equal(engine.iswrap(), True)
 | |
|         engine.wrapvar.set(0)
 | |
|         Equal(engine.iswrap(), False)
 | |
| 
 | |
|         Equal(engine.isback(), False)
 | |
|         engine.backvar.set(1)
 | |
|         Equal(engine.isback(), True)
 | |
| 
 | |
|     def test_setcookedpat(self):
 | |
|         engine = self.engine
 | |
|         engine.setcookedpat('\s')
 | |
|         self.assertEqual(engine.getpat(), '\s')
 | |
|         engine.revar.set(1)
 | |
|         engine.setcookedpat('\s')
 | |
|         self.assertEqual(engine.getpat(), r'\\s')
 | |
| 
 | |
|     def test_getcookedpat(self):
 | |
|         engine = self.engine
 | |
|         Equal = self.assertEqual
 | |
| 
 | |
|         Equal(engine.getcookedpat(), '')
 | |
|         engine.setpat('hello')
 | |
|         Equal(engine.getcookedpat(), 'hello')
 | |
|         engine.wordvar.set(True)
 | |
|         Equal(engine.getcookedpat(), r'\bhello\b')
 | |
|         engine.wordvar.set(False)
 | |
| 
 | |
|         engine.setpat('\s')
 | |
|         Equal(engine.getcookedpat(), r'\\s')
 | |
|         engine.revar.set(True)
 | |
|         Equal(engine.getcookedpat(), '\s')
 | |
| 
 | |
|     def test_getprog(self):
 | |
|         engine = self.engine
 | |
|         Equal = self.assertEqual
 | |
| 
 | |
|         engine.setpat('Hello')
 | |
|         temppat = engine.getprog()
 | |
|         Equal(temppat.pattern, re.compile('Hello', re.IGNORECASE).pattern)
 | |
|         engine.casevar.set(1)
 | |
|         temppat = engine.getprog()
 | |
|         Equal(temppat.pattern, re.compile('Hello').pattern, 0)
 | |
| 
 | |
|         engine.setpat('')
 | |
|         Equal(engine.getprog(), None)
 | |
|         engine.setpat('+')
 | |
|         engine.revar.set(1)
 | |
|         Equal(engine.getprog(), None)
 | |
|         self.assertEqual(Mbox.showerror.message,
 | |
|                          'Error: nothing to repeat at position 0\nPattern: +')
 | |
| 
 | |
|     def test_report_error(self):
 | |
|         showerror = Mbox.showerror
 | |
|         Equal = self.assertEqual
 | |
|         pat = '[a-z'
 | |
|         msg = 'unexpected end of regular expression'
 | |
| 
 | |
|         Equal(self.engine.report_error(pat, msg), None)
 | |
|         Equal(showerror.title, 'Regular expression error')
 | |
|         expected_message = ("Error: " + msg + "\nPattern: [a-z")
 | |
|         Equal(showerror.message, expected_message)
 | |
| 
 | |
|         Equal(self.engine.report_error(pat, msg, 5), None)
 | |
|         Equal(showerror.title, 'Regular expression error')
 | |
|         expected_message += "\nOffset: 5"
 | |
|         Equal(showerror.message, expected_message)
 | |
| 
 | |
| 
 | |
| class SearchTest(unittest.TestCase):
 | |
|     # Test that search_text makes right call to right method.
 | |
| 
 | |
|     @classmethod
 | |
|     def setUpClass(cls):
 | |
| ##        requires('gui')
 | |
| ##        cls.root = Tk()
 | |
| ##        cls.text = Text(master=cls.root)
 | |
|         cls.text = mockText()
 | |
|         test_text = (
 | |
|             'First line\n'
 | |
|             'Line with target\n'
 | |
|             'Last line\n')
 | |
|         cls.text.insert('1.0', test_text)
 | |
|         cls.pat = re.compile('target')
 | |
| 
 | |
|         cls.engine = se.SearchEngine(None)
 | |
|         cls.engine.search_forward = lambda *args: ('f', args)
 | |
|         cls.engine.search_backward = lambda *args: ('b', args)
 | |
| 
 | |
| ##    @classmethod
 | |
| ##    def tearDownClass(cls):
 | |
| ##        cls.root.destroy()
 | |
| ##        del cls.root
 | |
| 
 | |
|     def test_search(self):
 | |
|         Equal = self.assertEqual
 | |
|         engine = self.engine
 | |
|         search = engine.search_text
 | |
|         text = self.text
 | |
|         pat = self.pat
 | |
| 
 | |
|         engine.patvar.set(None)
 | |
|         #engine.revar.set(pat)
 | |
|         Equal(search(text), None)
 | |
| 
 | |
|         def mark(s):
 | |
|             # no selection, cursor after 'Hello'
 | |
|             if s == 'insert': return '1.5'
 | |
|             raise TclError
 | |
|         text.index = mark
 | |
|         Equal(search(text, pat), ('f', (text, pat, 1, 5, True, False)))
 | |
|         engine.wrapvar.set(False)
 | |
|         Equal(search(text, pat), ('f', (text, pat, 1, 5, False, False)))
 | |
|         engine.wrapvar.set(True)
 | |
|         engine.backvar.set(True)
 | |
|         Equal(search(text, pat), ('b', (text, pat, 1, 5, True, False)))
 | |
|         engine.backvar.set(False)
 | |
| 
 | |
|         def sel(s):
 | |
|             if s == 'sel.first': return '2.10'
 | |
|             if s == 'sel.last': return '2.16'
 | |
|             raise TclError
 | |
|         text.index = sel
 | |
|         Equal(search(text, pat), ('f', (text, pat, 2, 16, True, False)))
 | |
|         Equal(search(text, pat, True), ('f', (text, pat, 2, 10, True, True)))
 | |
|         engine.backvar.set(True)
 | |
|         Equal(search(text, pat), ('b', (text, pat, 2, 10, True, False)))
 | |
|         Equal(search(text, pat, True), ('b', (text, pat, 2, 16, True, True)))
 | |
| 
 | |
| 
 | |
| class ForwardBackwardTest(unittest.TestCase):
 | |
|     # Test that search_forward method finds the target.
 | |
| ##    @classmethod
 | |
| ##    def tearDownClass(cls):
 | |
| ##        cls.root.destroy()
 | |
| ##        del cls.root
 | |
| 
 | |
|     @classmethod
 | |
|     def setUpClass(cls):
 | |
|         cls.engine = se.SearchEngine(None)
 | |
| ##        requires('gui')
 | |
| ##        cls.root = Tk()
 | |
| ##        cls.text = Text(master=cls.root)
 | |
|         cls.text = mockText()
 | |
|         # search_backward calls index('end-1c')
 | |
|         cls.text.index = lambda index: '4.0'
 | |
|         test_text = (
 | |
|             'First line\n'
 | |
|             'Line with target\n'
 | |
|             'Last line\n')
 | |
|         cls.text.insert('1.0', test_text)
 | |
|         cls.pat = re.compile('target')
 | |
|         cls.res = (2, (10, 16))  # line, slice indexes of 'target'
 | |
|         cls.failpat = re.compile('xyz')  # not in text
 | |
|         cls.emptypat = re.compile('\w*')  # empty match possible
 | |
| 
 | |
|     def make_search(self, func):
 | |
|         def search(pat, line, col, wrap, ok=0):
 | |
|             res = func(self.text, pat, line, col, wrap, ok)
 | |
|             # res is (line, matchobject) or None
 | |
|             return (res[0], res[1].span()) if res else res
 | |
|         return search
 | |
| 
 | |
|     def test_search_forward(self):
 | |
|         # search for non-empty match
 | |
|         Equal = self.assertEqual
 | |
|         forward = self.make_search(self.engine.search_forward)
 | |
|         pat = self.pat
 | |
|         Equal(forward(pat, 1, 0, True), self.res)
 | |
|         Equal(forward(pat, 3, 0, True), self.res)  # wrap
 | |
|         Equal(forward(pat, 3, 0, False), None)  # no wrap
 | |
|         Equal(forward(pat, 2, 10, False), self.res)
 | |
| 
 | |
|         Equal(forward(self.failpat, 1, 0, True), None)
 | |
|         Equal(forward(self.emptypat, 2,  9, True, ok=True), (2, (9, 9)))
 | |
|         #Equal(forward(self.emptypat, 2, 9, True), self.res)
 | |
|         # While the initial empty match is correctly ignored, skipping
 | |
|         # the rest of the line and returning (3, (0,4)) seems buggy - tjr.
 | |
|         Equal(forward(self.emptypat, 2, 10, True), self.res)
 | |
| 
 | |
|     def test_search_backward(self):
 | |
|         # search for non-empty match
 | |
|         Equal = self.assertEqual
 | |
|         backward = self.make_search(self.engine.search_backward)
 | |
|         pat = self.pat
 | |
|         Equal(backward(pat, 3, 5, True), self.res)
 | |
|         Equal(backward(pat, 2, 0, True), self.res)  # wrap
 | |
|         Equal(backward(pat, 2, 0, False), None)  # no wrap
 | |
|         Equal(backward(pat, 2, 16, False), self.res)
 | |
| 
 | |
|         Equal(backward(self.failpat, 3, 9, True), None)
 | |
|         Equal(backward(self.emptypat, 2,  10, True, ok=True), (2, (9,9)))
 | |
|         # Accepted because 9 < 10, not because ok=True.
 | |
|         # It is not clear that ok=True is useful going back - tjr
 | |
|         Equal(backward(self.emptypat, 2, 9, True), (2, (5, 9)))
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     unittest.main(verbosity=2, exit=2)
 | 
