Coverage for idle_test/tkinter_testing_utils.py: 31%

23 statements  

« prev     ^ index     » next       coverage.py v7.2.5, created at 2023-05-11 13:22 -0700

1"""Utilities for testing with Tkinter""" 

2import functools 

3 

4 

5def run_in_tk_mainloop(delay=1): 

6 """Decorator for running a test method with a real Tk mainloop. 

7 

8 This starts a Tk mainloop before running the test, and stops it 

9 at the end. This is faster and more robust than the common 

10 alternative method of calling .update() and/or .update_idletasks(). 

11 

12 Test methods using this must be written as generator functions, 

13 using "yield" to allow the mainloop to process events and "after" 

14 callbacks, and then continue the test from that point. 

15 

16 The delay argument is passed into root.after(...) calls as the number 

17 of ms to wait before passing execution back to the generator function. 

18 

19 This also assumes that the test class has a .root attribute, 

20 which is a tkinter.Tk object. 

21 

22 For example (from test_sidebar.py): 

23 

24 @run_test_with_tk_mainloop() 

25 def test_single_empty_input(self): 

26 self.do_input('\n') 

27 yield 

28 self.assert_sidebar_lines_end_with(['>>>', '>>>']) 

29 """ 

30 def decorator(test_method): 

31 @functools.wraps(test_method) 

32 def new_test_method(self): 

33 test_generator = test_method(self) 

34 root = self.root 

35 # Exceptions raised by self.assert...() need to be raised 

36 # outside of the after() callback in order for the test 

37 # harness to capture them. 

38 exception = None 

39 def after_callback(): 

40 nonlocal exception 

41 try: 

42 next(test_generator) 

43 except StopIteration: 

44 root.quit() 

45 except Exception as exc: 

46 exception = exc 

47 root.quit() 

48 else: 

49 # Schedule the Tk mainloop to call this function again, 

50 # using a robust method of ensuring that it gets a 

51 # chance to process queued events before doing so. 

52 # See: https://stackoverflow.com/q/18499082#comment65004099_38817470 

53 root.after(delay, root.after_idle, after_callback) 

54 root.after(0, root.after_idle, after_callback) 

55 root.mainloop() 

56 

57 if exception: 

58 raise exception 

59 

60 return new_test_method 

61 

62 return decorator