text: '''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)
  