Skip to content

Commit 3a04598

Browse files
csabellaserhiy-storchaka
authored andcommitted
bpo-32857: Raise error when tkinter after_cancel() is called with None. (GH-5701) (GH-6620)
(cherry picked from commit 74382a3)
1 parent 4a1bc26 commit 3a04598

File tree

3 files changed

+129
-3
lines changed

3 files changed

+129
-3
lines changed

Lib/lib-tk/Tkinter.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,7 @@ def after(self, ms, func=None, *args):
586586
if not func:
587587
# I'd rather use time.sleep(ms*0.001)
588588
self.tk.call('after', ms)
589+
return None
589590
else:
590591
def callit():
591592
try:
@@ -609,11 +610,13 @@ def after_cancel(self, id):
609610
"""Cancel scheduling of function identified with ID.
610611
611612
Identifier returned by after or after_idle must be
612-
given as first parameter."""
613+
given as first parameter.
614+
"""
615+
if not id:
616+
raise ValueError('id must be a valid identifier returned from '
617+
'after or after_idle')
613618
try:
614619
data = self.tk.call('after', 'info', id)
615-
# In Tk 8.3, splitlist returns: (script, type)
616-
# In Tk 8.4, splitlist may return (script, type) or (script,)
617620
script = self.tk.splitlist(data)[0]
618621
self.deletecommand(script)
619622
except TclError:
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import unittest
2+
import Tkinter as tkinter
3+
from test.test_support import requires, run_unittest
4+
from test_ttk.support import AbstractTkTest
5+
6+
requires('gui')
7+
8+
class MiscTest(AbstractTkTest, unittest.TestCase):
9+
10+
def test_after(self):
11+
root = self.root
12+
cbcount = {'count': 0}
13+
14+
def callback(start=0, step=1):
15+
cbcount['count'] = start + step
16+
17+
# Without function, sleeps for ms.
18+
self.assertIsNone(root.after(1))
19+
20+
# Set up with callback with no args.
21+
cbcount['count'] = 0
22+
timer1 = root.after(0, callback)
23+
self.assertIn(timer1, root.tk.call('after', 'info'))
24+
(script, _) = root.tk.splitlist(root.tk.call('after', 'info', timer1))
25+
root.update() # Process all pending events.
26+
self.assertEqual(cbcount['count'], 1)
27+
with self.assertRaises(tkinter.TclError):
28+
root.tk.call(script)
29+
30+
# Set up with callback with args.
31+
cbcount['count'] = 0
32+
timer1 = root.after(0, callback, 42, 11)
33+
root.update() # Process all pending events.
34+
self.assertEqual(cbcount['count'], 53)
35+
36+
# Cancel before called.
37+
timer1 = root.after(1000, callback)
38+
self.assertIn(timer1, root.tk.call('after', 'info'))
39+
(script, _) = root.tk.splitlist(root.tk.call('after', 'info', timer1))
40+
root.after_cancel(timer1) # Cancel this event.
41+
self.assertEqual(cbcount['count'], 53)
42+
with self.assertRaises(tkinter.TclError):
43+
root.tk.call(script)
44+
45+
def test_after_idle(self):
46+
root = self.root
47+
cbcount = {'count': 0}
48+
49+
def callback(start=0, step=1):
50+
cbcount['count'] = start + step
51+
52+
# Set up with callback with no args.
53+
cbcount['count'] = 0
54+
idle1 = root.after_idle(callback)
55+
self.assertIn(idle1, root.tk.call('after', 'info'))
56+
(script, _) = root.tk.splitlist(root.tk.call('after', 'info', idle1))
57+
root.update_idletasks() # Process all pending events.
58+
self.assertEqual(cbcount['count'], 1)
59+
with self.assertRaises(tkinter.TclError):
60+
root.tk.call(script)
61+
62+
# Set up with callback with args.
63+
cbcount['count'] = 0
64+
idle1 = root.after_idle(callback, 42, 11)
65+
root.update_idletasks() # Process all pending events.
66+
self.assertEqual(cbcount['count'], 53)
67+
68+
# Cancel before called.
69+
idle1 = root.after_idle(callback)
70+
self.assertIn(idle1, root.tk.call('after', 'info'))
71+
(script, _) = root.tk.splitlist(root.tk.call('after', 'info', idle1))
72+
root.after_cancel(idle1) # Cancel this event.
73+
self.assertEqual(cbcount['count'], 53)
74+
with self.assertRaises(tkinter.TclError):
75+
root.tk.call(script)
76+
77+
def test_after_cancel(self):
78+
root = self.root
79+
cbcount = {'count': 0}
80+
81+
def callback():
82+
cbcount['count'] += 1
83+
84+
timer1 = root.after(5000, callback)
85+
idle1 = root.after_idle(callback)
86+
87+
# No value for id raises a ValueError.
88+
with self.assertRaises(ValueError):
89+
root.after_cancel(None)
90+
91+
# Cancel timer event.
92+
cbcount['count'] = 0
93+
(script, _) = root.tk.splitlist(root.tk.call('after', 'info', timer1))
94+
root.tk.call(script)
95+
self.assertEqual(cbcount['count'], 1)
96+
root.after_cancel(timer1)
97+
with self.assertRaises(tkinter.TclError):
98+
root.tk.call(script)
99+
self.assertEqual(cbcount['count'], 1)
100+
with self.assertRaises(tkinter.TclError):
101+
root.tk.call('after', 'info', timer1)
102+
103+
# Cancel same event - nothing happens.
104+
root.after_cancel(timer1)
105+
106+
# Cancel idle event.
107+
cbcount['count'] = 0
108+
(script, _) = root.tk.splitlist(root.tk.call('after', 'info', idle1))
109+
root.tk.call(script)
110+
self.assertEqual(cbcount['count'], 1)
111+
root.after_cancel(idle1)
112+
with self.assertRaises(tkinter.TclError):
113+
root.tk.call(script)
114+
self.assertEqual(cbcount['count'], 1)
115+
with self.assertRaises(tkinter.TclError):
116+
root.tk.call('after', 'info', idle1)
117+
118+
119+
tests_gui = (MiscTest, )
120+
121+
if __name__ == "__main__":
122+
run_unittest(*tests_gui)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
In :mod:`tkinter`, ``after_cancel(None)`` now raises a :exc:`ValueError` instead of canceling the first scheduled function. Patch by Cheryl Sabella.

0 commit comments

Comments
 (0)