Skip to content

Commit 132acab

Browse files
taleinatterryjreedy
authored andcommitted
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.
1 parent efa3b51 commit 132acab

File tree

4 files changed

+68
-48
lines changed

4 files changed

+68
-48
lines changed

Lib/idlelib/NEWS.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ Released on 2019-10-20?
33
======================================
44

55

6+
bpo-35771: To avoid occasional spurious test_idle failures on slower
7+
machines, increase the ``hover_delay`` in test_tooltip.
8+
69
bpo-37824: Properly handle user input warnings in IDLE shell.
710
Cease turning SyntaxWarnings into SyntaxErrors.
811

Lib/idlelib/idle_test/test_tooltip.py

Lines changed: 59 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
"""Test tooltip, coverage 100%.
2+
3+
Coverage is 100% after excluding 6 lines with "# pragma: no cover".
4+
They involve TclErrors that either should or should not happen in a
5+
particular situation, and which are 'pass'ed if they do.
6+
"""
7+
18
from idlelib.tooltip import TooltipBase, Hovertip
29
from test.support import requires
310
requires('gui')
@@ -12,16 +19,13 @@ def setUpModule():
1219
global root
1320
root = Tk()
1421

15-
def root_update():
16-
global root
17-
root.update()
18-
1922
def tearDownModule():
2023
global root
2124
root.update_idletasks()
2225
root.destroy()
2326
del root
2427

28+
2529
def add_call_counting(func):
2630
@wraps(func)
2731
def wrapped_func(*args, **kwargs):
@@ -65,82 +69,93 @@ class HovertipTest(unittest.TestCase):
6569
def setUp(self):
6670
self.top, self.button = _make_top_and_button(self)
6771

72+
def is_tipwindow_shown(self, tooltip):
73+
return tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()
74+
6875
def test_showtip(self):
6976
tooltip = Hovertip(self.button, 'ToolTip text')
7077
self.addCleanup(tooltip.hidetip)
71-
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
78+
self.assertFalse(self.is_tipwindow_shown(tooltip))
7279
tooltip.showtip()
73-
self.assertTrue(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
80+
self.assertTrue(self.is_tipwindow_shown(tooltip))
7481

7582
def test_showtip_twice(self):
7683
tooltip = Hovertip(self.button, 'ToolTip text')
7784
self.addCleanup(tooltip.hidetip)
78-
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
85+
self.assertFalse(self.is_tipwindow_shown(tooltip))
7986
tooltip.showtip()
80-
self.assertTrue(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
87+
self.assertTrue(self.is_tipwindow_shown(tooltip))
8188
orig_tipwindow = tooltip.tipwindow
8289
tooltip.showtip()
83-
self.assertTrue(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
90+
self.assertTrue(self.is_tipwindow_shown(tooltip))
8491
self.assertIs(tooltip.tipwindow, orig_tipwindow)
8592

8693
def test_hidetip(self):
8794
tooltip = Hovertip(self.button, 'ToolTip text')
8895
self.addCleanup(tooltip.hidetip)
8996
tooltip.showtip()
9097
tooltip.hidetip()
91-
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
98+
self.assertFalse(self.is_tipwindow_shown(tooltip))
9299

93100
def test_showtip_on_mouse_enter_no_delay(self):
94101
tooltip = Hovertip(self.button, 'ToolTip text', hover_delay=None)
95102
self.addCleanup(tooltip.hidetip)
96103
tooltip.showtip = add_call_counting(tooltip.showtip)
97-
root_update()
98-
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
104+
root.update()
105+
self.assertFalse(self.is_tipwindow_shown(tooltip))
99106
self.button.event_generate('<Enter>', x=0, y=0)
100-
root_update()
101-
self.assertTrue(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
107+
root.update()
108+
self.assertTrue(self.is_tipwindow_shown(tooltip))
102109
self.assertGreater(len(tooltip.showtip.call_args_list), 0)
103110

104-
def test_showtip_on_mouse_enter_hover_delay(self):
105-
tooltip = Hovertip(self.button, 'ToolTip text', hover_delay=50)
106-
self.addCleanup(tooltip.hidetip)
107-
tooltip.showtip = add_call_counting(tooltip.showtip)
108-
root_update()
109-
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
111+
def test_hover_with_delay(self):
112+
# Run multiple tests requiring an actual delay simultaneously.
113+
114+
# Test #1: A hover tip with a non-zero delay appears after the delay.
115+
tooltip1 = Hovertip(self.button, 'ToolTip text', hover_delay=100)
116+
self.addCleanup(tooltip1.hidetip)
117+
tooltip1.showtip = add_call_counting(tooltip1.showtip)
118+
root.update()
119+
self.assertFalse(self.is_tipwindow_shown(tooltip1))
110120
self.button.event_generate('<Enter>', x=0, y=0)
111-
root_update()
112-
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
113-
time.sleep(0.1)
114-
root_update()
115-
self.assertTrue(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
116-
self.assertGreater(len(tooltip.showtip.call_args_list), 0)
121+
root.update()
122+
self.assertFalse(self.is_tipwindow_shown(tooltip1))
123+
124+
# Test #2: A hover tip with a non-zero delay doesn't appear when
125+
# the mouse stops hovering over the base widget before the delay
126+
# expires.
127+
tooltip2 = Hovertip(self.button, 'ToolTip text', hover_delay=100)
128+
self.addCleanup(tooltip2.hidetip)
129+
tooltip2.showtip = add_call_counting(tooltip2.showtip)
130+
root.update()
131+
self.button.event_generate('<Enter>', x=0, y=0)
132+
root.update()
133+
self.button.event_generate('<Leave>', x=0, y=0)
134+
root.update()
135+
136+
time.sleep(0.15)
137+
root.update()
138+
139+
# Test #1 assertions.
140+
self.assertTrue(self.is_tipwindow_shown(tooltip1))
141+
self.assertGreater(len(tooltip1.showtip.call_args_list), 0)
142+
143+
# Test #2 assertions.
144+
self.assertFalse(self.is_tipwindow_shown(tooltip2))
145+
self.assertEqual(tooltip2.showtip.call_args_list, [])
117146

118147
def test_hidetip_on_mouse_leave(self):
119148
tooltip = Hovertip(self.button, 'ToolTip text', hover_delay=None)
120149
self.addCleanup(tooltip.hidetip)
121150
tooltip.showtip = add_call_counting(tooltip.showtip)
122-
root_update()
151+
root.update()
123152
self.button.event_generate('<Enter>', x=0, y=0)
124-
root_update()
153+
root.update()
125154
self.button.event_generate('<Leave>', x=0, y=0)
126-
root_update()
127-
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
155+
root.update()
156+
self.assertFalse(self.is_tipwindow_shown(tooltip))
128157
self.assertGreater(len(tooltip.showtip.call_args_list), 0)
129158

130-
def test_dont_show_on_mouse_leave_before_delay(self):
131-
tooltip = Hovertip(self.button, 'ToolTip text', hover_delay=50)
132-
self.addCleanup(tooltip.hidetip)
133-
tooltip.showtip = add_call_counting(tooltip.showtip)
134-
root_update()
135-
self.button.event_generate('<Enter>', x=0, y=0)
136-
root_update()
137-
self.button.event_generate('<Leave>', x=0, y=0)
138-
root_update()
139-
time.sleep(0.1)
140-
root_update()
141-
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
142-
self.assertEqual(tooltip.showtip.call_args_list, [])
143-
144159

145160
if __name__ == '__main__':
146161
unittest.main(verbosity=2)

Lib/idlelib/tooltip.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def hidetip(self):
7575
if tw:
7676
try:
7777
tw.destroy()
78-
except TclError:
78+
except TclError: # pragma: no cover
7979
pass
8080

8181

@@ -103,8 +103,8 @@ def __init__(self, anchor_widget, hover_delay=1000):
103103
def __del__(self):
104104
try:
105105
self.anchor_widget.unbind("<Enter>", self._id1)
106-
self.anchor_widget.unbind("<Leave>", self._id2)
107-
self.anchor_widget.unbind("<Button>", self._id3)
106+
self.anchor_widget.unbind("<Leave>", self._id2) # pragma: no cover
107+
self.anchor_widget.unbind("<Button>", self._id3) # pragma: no cover
108108
except TclError:
109109
pass
110110
super(OnHoverTooltipBase, self).__del__()
@@ -137,7 +137,7 @@ def hidetip(self):
137137
"""hide the tooltip"""
138138
try:
139139
self.unschedule()
140-
except TclError:
140+
except TclError: # pragma: no cover
141141
pass
142142
super(OnHoverTooltipBase, self).hidetip()
143143

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
To avoid occasional spurious test_idle failures on slower machines,
2+
increase the ``hover_delay`` in test_tooltip.

0 commit comments

Comments
 (0)