bpo-35771: IDLE: Fix flaky tool-tip hover delay tests (GH-15634)

Extending the hover delay in test_tooltip should avoid spurious test_idle failures.
One longer delay instead of two shorter delays results in a net speedup.
This commit is contained in:
Tal Einat 2019-09-03 08:17:00 +03:00 committed by Terry Jan Reedy
parent efa3b51fd0
commit 132acaba5a
4 changed files with 68 additions and 48 deletions

View File

@ -3,6 +3,9 @@ Released on 2019-10-20?
====================================== ======================================
bpo-35771: To avoid occasional spurious test_idle failures on slower
machines, increase the ``hover_delay`` in test_tooltip.
bpo-37824: Properly handle user input warnings in IDLE shell. bpo-37824: Properly handle user input warnings in IDLE shell.
Cease turning SyntaxWarnings into SyntaxErrors. Cease turning SyntaxWarnings into SyntaxErrors.

View File

@ -1,3 +1,10 @@
"""Test tooltip, coverage 100%.
Coverage is 100% after excluding 6 lines with "# pragma: no cover".
They involve TclErrors that either should or should not happen in a
particular situation, and which are 'pass'ed if they do.
"""
from idlelib.tooltip import TooltipBase, Hovertip from idlelib.tooltip import TooltipBase, Hovertip
from test.support import requires from test.support import requires
requires('gui') requires('gui')
@ -12,16 +19,13 @@ def setUpModule():
global root global root
root = Tk() root = Tk()
def root_update():
global root
root.update()
def tearDownModule(): def tearDownModule():
global root global root
root.update_idletasks() root.update_idletasks()
root.destroy() root.destroy()
del root del root
def add_call_counting(func): def add_call_counting(func):
@wraps(func) @wraps(func)
def wrapped_func(*args, **kwargs): def wrapped_func(*args, **kwargs):
@ -65,22 +69,25 @@ class HovertipTest(unittest.TestCase):
def setUp(self): def setUp(self):
self.top, self.button = _make_top_and_button(self) self.top, self.button = _make_top_and_button(self)
def is_tipwindow_shown(self, tooltip):
return tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()
def test_showtip(self): def test_showtip(self):
tooltip = Hovertip(self.button, 'ToolTip text') tooltip = Hovertip(self.button, 'ToolTip text')
self.addCleanup(tooltip.hidetip) self.addCleanup(tooltip.hidetip)
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) self.assertFalse(self.is_tipwindow_shown(tooltip))
tooltip.showtip() tooltip.showtip()
self.assertTrue(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) self.assertTrue(self.is_tipwindow_shown(tooltip))
def test_showtip_twice(self): def test_showtip_twice(self):
tooltip = Hovertip(self.button, 'ToolTip text') tooltip = Hovertip(self.button, 'ToolTip text')
self.addCleanup(tooltip.hidetip) self.addCleanup(tooltip.hidetip)
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) self.assertFalse(self.is_tipwindow_shown(tooltip))
tooltip.showtip() tooltip.showtip()
self.assertTrue(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) self.assertTrue(self.is_tipwindow_shown(tooltip))
orig_tipwindow = tooltip.tipwindow orig_tipwindow = tooltip.tipwindow
tooltip.showtip() tooltip.showtip()
self.assertTrue(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) self.assertTrue(self.is_tipwindow_shown(tooltip))
self.assertIs(tooltip.tipwindow, orig_tipwindow) self.assertIs(tooltip.tipwindow, orig_tipwindow)
def test_hidetip(self): def test_hidetip(self):
@ -88,59 +95,67 @@ class HovertipTest(unittest.TestCase):
self.addCleanup(tooltip.hidetip) self.addCleanup(tooltip.hidetip)
tooltip.showtip() tooltip.showtip()
tooltip.hidetip() tooltip.hidetip()
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) self.assertFalse(self.is_tipwindow_shown(tooltip))
def test_showtip_on_mouse_enter_no_delay(self): def test_showtip_on_mouse_enter_no_delay(self):
tooltip = Hovertip(self.button, 'ToolTip text', hover_delay=None) tooltip = Hovertip(self.button, 'ToolTip text', hover_delay=None)
self.addCleanup(tooltip.hidetip) self.addCleanup(tooltip.hidetip)
tooltip.showtip = add_call_counting(tooltip.showtip) tooltip.showtip = add_call_counting(tooltip.showtip)
root_update() root.update()
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) self.assertFalse(self.is_tipwindow_shown(tooltip))
self.button.event_generate('<Enter>', x=0, y=0) self.button.event_generate('<Enter>', x=0, y=0)
root_update() root.update()
self.assertTrue(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) self.assertTrue(self.is_tipwindow_shown(tooltip))
self.assertGreater(len(tooltip.showtip.call_args_list), 0) self.assertGreater(len(tooltip.showtip.call_args_list), 0)
def test_showtip_on_mouse_enter_hover_delay(self): def test_hover_with_delay(self):
tooltip = Hovertip(self.button, 'ToolTip text', hover_delay=50) # Run multiple tests requiring an actual delay simultaneously.
self.addCleanup(tooltip.hidetip)
tooltip.showtip = add_call_counting(tooltip.showtip) # Test #1: A hover tip with a non-zero delay appears after the delay.
root_update() tooltip1 = Hovertip(self.button, 'ToolTip text', hover_delay=100)
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) self.addCleanup(tooltip1.hidetip)
tooltip1.showtip = add_call_counting(tooltip1.showtip)
root.update()
self.assertFalse(self.is_tipwindow_shown(tooltip1))
self.button.event_generate('<Enter>', x=0, y=0) self.button.event_generate('<Enter>', x=0, y=0)
root_update() root.update()
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) self.assertFalse(self.is_tipwindow_shown(tooltip1))
time.sleep(0.1)
root_update() # Test #2: A hover tip with a non-zero delay doesn't appear when
self.assertTrue(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) # the mouse stops hovering over the base widget before the delay
self.assertGreater(len(tooltip.showtip.call_args_list), 0) # expires.
tooltip2 = Hovertip(self.button, 'ToolTip text', hover_delay=100)
self.addCleanup(tooltip2.hidetip)
tooltip2.showtip = add_call_counting(tooltip2.showtip)
root.update()
self.button.event_generate('<Enter>', x=0, y=0)
root.update()
self.button.event_generate('<Leave>', x=0, y=0)
root.update()
time.sleep(0.15)
root.update()
# Test #1 assertions.
self.assertTrue(self.is_tipwindow_shown(tooltip1))
self.assertGreater(len(tooltip1.showtip.call_args_list), 0)
# Test #2 assertions.
self.assertFalse(self.is_tipwindow_shown(tooltip2))
self.assertEqual(tooltip2.showtip.call_args_list, [])
def test_hidetip_on_mouse_leave(self): def test_hidetip_on_mouse_leave(self):
tooltip = Hovertip(self.button, 'ToolTip text', hover_delay=None) tooltip = Hovertip(self.button, 'ToolTip text', hover_delay=None)
self.addCleanup(tooltip.hidetip) self.addCleanup(tooltip.hidetip)
tooltip.showtip = add_call_counting(tooltip.showtip) tooltip.showtip = add_call_counting(tooltip.showtip)
root_update() root.update()
self.button.event_generate('<Enter>', x=0, y=0) self.button.event_generate('<Enter>', x=0, y=0)
root_update() root.update()
self.button.event_generate('<Leave>', x=0, y=0) self.button.event_generate('<Leave>', x=0, y=0)
root_update() root.update()
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()) self.assertFalse(self.is_tipwindow_shown(tooltip))
self.assertGreater(len(tooltip.showtip.call_args_list), 0) self.assertGreater(len(tooltip.showtip.call_args_list), 0)
def test_dont_show_on_mouse_leave_before_delay(self):
tooltip = Hovertip(self.button, 'ToolTip text', hover_delay=50)
self.addCleanup(tooltip.hidetip)
tooltip.showtip = add_call_counting(tooltip.showtip)
root_update()
self.button.event_generate('<Enter>', x=0, y=0)
root_update()
self.button.event_generate('<Leave>', x=0, y=0)
root_update()
time.sleep(0.1)
root_update()
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
self.assertEqual(tooltip.showtip.call_args_list, [])
if __name__ == '__main__': if __name__ == '__main__':
unittest.main(verbosity=2) unittest.main(verbosity=2)

View File

@ -75,7 +75,7 @@ class TooltipBase(object):
if tw: if tw:
try: try:
tw.destroy() tw.destroy()
except TclError: except TclError: # pragma: no cover
pass pass
@ -103,8 +103,8 @@ class OnHoverTooltipBase(TooltipBase):
def __del__(self): def __del__(self):
try: try:
self.anchor_widget.unbind("<Enter>", self._id1) self.anchor_widget.unbind("<Enter>", self._id1)
self.anchor_widget.unbind("<Leave>", self._id2) self.anchor_widget.unbind("<Leave>", self._id2) # pragma: no cover
self.anchor_widget.unbind("<Button>", self._id3) self.anchor_widget.unbind("<Button>", self._id3) # pragma: no cover
except TclError: except TclError:
pass pass
super(OnHoverTooltipBase, self).__del__() super(OnHoverTooltipBase, self).__del__()
@ -137,7 +137,7 @@ class OnHoverTooltipBase(TooltipBase):
"""hide the tooltip""" """hide the tooltip"""
try: try:
self.unschedule() self.unschedule()
except TclError: except TclError: # pragma: no cover
pass pass
super(OnHoverTooltipBase, self).hidetip() super(OnHoverTooltipBase, self).hidetip()

View File

@ -0,0 +1,2 @@
To avoid occasional spurious test_idle failures on slower machines,
increase the ``hover_delay`` in test_tooltip.