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