From aeabb7a6c993241fde63958372d86d79b0e9044b Mon Sep 17 00:00:00 2001 From: Cheryl Sabella Date: Sun, 4 Mar 2018 05:41:47 -0500 Subject: [PATCH] bpo-32857: Raise error when tkinter after_cancel() is called with None. (GH-5701) (cherry picked from commit 74382a3f175ac285cc924a73fd758e8dc3cc41bb) --- Lib/lib-tk/Tkinter.py | 9 +- Lib/lib-tk/test/test_tkinter/test_misc.py | 122 ++++++++++++++++++ .../2018-02-16-14-37-14.bpo-32857.-XljAx.rst | 1 + 3 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 Lib/lib-tk/test/test_tkinter/test_misc.py create mode 100644 Misc/NEWS.d/next/Library/2018-02-16-14-37-14.bpo-32857.-XljAx.rst diff --git a/Lib/lib-tk/Tkinter.py b/Lib/lib-tk/Tkinter.py index b226fd5f35297b..f46642f5bb99db 100644 --- a/Lib/lib-tk/Tkinter.py +++ b/Lib/lib-tk/Tkinter.py @@ -586,6 +586,7 @@ def after(self, ms, func=None, *args): if not func: # I'd rather use time.sleep(ms*0.001) self.tk.call('after', ms) + return None else: def callit(): try: @@ -609,11 +610,13 @@ def after_cancel(self, id): """Cancel scheduling of function identified with ID. Identifier returned by after or after_idle must be - given as first parameter.""" + given as first parameter. + """ + if not id: + raise ValueError('id must be a valid identifier returned from ' + 'after or after_idle') try: data = self.tk.call('after', 'info', id) - # In Tk 8.3, splitlist returns: (script, type) - # In Tk 8.4, splitlist may return (script, type) or (script,) script = self.tk.splitlist(data)[0] self.deletecommand(script) except TclError: diff --git a/Lib/lib-tk/test/test_tkinter/test_misc.py b/Lib/lib-tk/test/test_tkinter/test_misc.py new file mode 100644 index 00000000000000..796269ede409d0 --- /dev/null +++ b/Lib/lib-tk/test/test_tkinter/test_misc.py @@ -0,0 +1,122 @@ +import unittest +import Tkinter as tkinter +from test.test_support import requires, run_unittest +from test_ttk.support import AbstractTkTest + +requires('gui') + +class MiscTest(AbstractTkTest, unittest.TestCase): + + def test_after(self): + root = self.root + cbcount = {'count': 0} + + def callback(start=0, step=1): + cbcount['count'] = start + step + + # Without function, sleeps for ms. + self.assertIsNone(root.after(1)) + + # Set up with callback with no args. + cbcount['count'] = 0 + timer1 = root.after(0, callback) + self.assertIn(timer1, root.tk.call('after', 'info')) + (script, _) = root.tk.splitlist(root.tk.call('after', 'info', timer1)) + root.update() # Process all pending events. + self.assertEqual(cbcount['count'], 1) + with self.assertRaises(tkinter.TclError): + root.tk.call(script) + + # Set up with callback with args. + cbcount['count'] = 0 + timer1 = root.after(0, callback, 42, 11) + root.update() # Process all pending events. + self.assertEqual(cbcount['count'], 53) + + # Cancel before called. + timer1 = root.after(1000, callback) + self.assertIn(timer1, root.tk.call('after', 'info')) + (script, _) = root.tk.splitlist(root.tk.call('after', 'info', timer1)) + root.after_cancel(timer1) # Cancel this event. + self.assertEqual(cbcount['count'], 53) + with self.assertRaises(tkinter.TclError): + root.tk.call(script) + + def test_after_idle(self): + root = self.root + cbcount = {'count': 0} + + def callback(start=0, step=1): + cbcount['count'] = start + step + + # Set up with callback with no args. + cbcount['count'] = 0 + idle1 = root.after_idle(callback) + self.assertIn(idle1, root.tk.call('after', 'info')) + (script, _) = root.tk.splitlist(root.tk.call('after', 'info', idle1)) + root.update_idletasks() # Process all pending events. + self.assertEqual(cbcount['count'], 1) + with self.assertRaises(tkinter.TclError): + root.tk.call(script) + + # Set up with callback with args. + cbcount['count'] = 0 + idle1 = root.after_idle(callback, 42, 11) + root.update_idletasks() # Process all pending events. + self.assertEqual(cbcount['count'], 53) + + # Cancel before called. + idle1 = root.after_idle(callback) + self.assertIn(idle1, root.tk.call('after', 'info')) + (script, _) = root.tk.splitlist(root.tk.call('after', 'info', idle1)) + root.after_cancel(idle1) # Cancel this event. + self.assertEqual(cbcount['count'], 53) + with self.assertRaises(tkinter.TclError): + root.tk.call(script) + + def test_after_cancel(self): + root = self.root + cbcount = {'count': 0} + + def callback(): + cbcount['count'] += 1 + + timer1 = root.after(5000, callback) + idle1 = root.after_idle(callback) + + # No value for id raises a ValueError. + with self.assertRaises(ValueError): + root.after_cancel(None) + + # Cancel timer event. + cbcount['count'] = 0 + (script, _) = root.tk.splitlist(root.tk.call('after', 'info', timer1)) + root.tk.call(script) + self.assertEqual(cbcount['count'], 1) + root.after_cancel(timer1) + with self.assertRaises(tkinter.TclError): + root.tk.call(script) + self.assertEqual(cbcount['count'], 1) + with self.assertRaises(tkinter.TclError): + root.tk.call('after', 'info', timer1) + + # Cancel same event - nothing happens. + root.after_cancel(timer1) + + # Cancel idle event. + cbcount['count'] = 0 + (script, _) = root.tk.splitlist(root.tk.call('after', 'info', idle1)) + root.tk.call(script) + self.assertEqual(cbcount['count'], 1) + root.after_cancel(idle1) + with self.assertRaises(tkinter.TclError): + root.tk.call(script) + self.assertEqual(cbcount['count'], 1) + with self.assertRaises(tkinter.TclError): + root.tk.call('after', 'info', idle1) + + +tests_gui = (MiscTest, ) + +if __name__ == "__main__": + run_unittest(*tests_gui) diff --git a/Misc/NEWS.d/next/Library/2018-02-16-14-37-14.bpo-32857.-XljAx.rst b/Misc/NEWS.d/next/Library/2018-02-16-14-37-14.bpo-32857.-XljAx.rst new file mode 100644 index 00000000000000..4ebbde4d194611 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-02-16-14-37-14.bpo-32857.-XljAx.rst @@ -0,0 +1 @@ +In :mod:`tkinter`, ``after_cancel(None)`` now raises a :exc:`ValueError` instead of canceling the first scheduled function. Patch by Cheryl Sabella.