• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'''Run human tests of Idle's window, dialog, and popup widgets.
2
3run(*tests)
4Create a master Tk window.  Within that, run each callable in tests
5after finding the matching test spec in this file.  If tests is empty,
6run an htest for each spec dict in this file after finding the matching
7callable in the module named in the spec.  Close the window to skip or
8end the test.
9
10In a tested module, let X be a global name bound to a callable (class
11or function) whose .__name__ attribute is also X (the usual situation).
12The first parameter of X must be 'parent'.  When called, the parent
13argument will be the root window.  X must create a child Toplevel
14window (or subclass thereof).  The Toplevel may be a test widget or
15dialog, in which case the callable is the corresonding class.  Or the
16Toplevel may contain the widget to be tested or set up a context in
17which a test widget is invoked.  In this latter case, the callable is a
18wrapper function that sets up the Toplevel and other objects.  Wrapper
19function names, such as _editor_window', should start with '_'.
20
21
22End the module with
23
24if __name__ == '__main__':
25    <unittest, if there is one>
26    from idlelib.idle_test.htest import run
27    run(X)
28
29To have wrapper functions and test invocation code ignored by coveragepy
30reports, put '# htest #' on the def statement header line.
31
32def _wrapper(parent):  # htest #
33
34Also make sure that the 'if __name__' line matches the above.  Then have
35make sure that .coveragerc includes the following.
36
37[report]
38exclude_lines =
39    .*# htest #
40    if __name__ == .__main__.:
41
42(The "." instead of "'" is intentional and necessary.)
43
44
45To run any X, this file must contain a matching instance of the
46following template, with X.__name__ prepended to '_spec'.
47When all tests are run, the prefix is use to get X.
48
49_spec = {
50    'file': '',
51    'kwds': {'title': ''},
52    'msg': ""
53    }
54
55file (no .py): run() imports file.py.
56kwds: augmented with {'parent':root} and passed to X as **kwds.
57title: an example kwd; some widgets need this, delete if not.
58msg: master window hints about testing the widget.
59
60
61Modules and classes not being tested at the moment:
62pyshell.PyShellEditorWindow
63debugger.Debugger
64autocomplete_w.AutoCompleteWindow
65outwin.OutputWindow (indirectly being tested with grep test)
66'''
67
68import idlelib.pyshell  # Set Windows DPI awareness before Tk().
69from importlib import import_module
70import tkinter as tk
71from tkinter.ttk import Scrollbar
72tk.NoDefaultRoot()
73
74AboutDialog_spec = {
75    'file': 'help_about',
76    'kwds': {'title': 'help_about test',
77             '_htest': True,
78             },
79    'msg': "Test every button. Ensure Python, TK and IDLE versions "
80           "are correctly displayed.\n [Close] to exit.",
81    }
82
83# TODO implement ^\; adding '<Control-Key-\\>' to function does not work.
84_calltip_window_spec = {
85    'file': 'calltip_w',
86    'kwds': {},
87    'msg': "Typing '(' should display a calltip.\n"
88           "Typing ') should hide the calltip.\n"
89           "So should moving cursor out of argument area.\n"
90           "Force-open-calltip does not work here.\n"
91    }
92
93_module_browser_spec = {
94    'file': 'browser',
95    'kwds': {},
96    'msg': "Inspect names of module, class(with superclass if "
97           "applicable), methods and functions.\nToggle nested items.\n"
98           "Double clicking on items prints a traceback for an exception "
99           "that is ignored."
100    }
101
102_color_delegator_spec = {
103    'file': 'colorizer',
104    'kwds': {},
105    'msg': "The text is sample Python code.\n"
106           "Ensure components like comments, keywords, builtins,\n"
107           "string, definitions, and break are correctly colored.\n"
108           "The default color scheme is in idlelib/config-highlight.def"
109    }
110
111ConfigDialog_spec = {
112    'file': 'configdialog',
113    'kwds': {'title': 'ConfigDialogTest',
114             '_htest': True,},
115    'msg': "IDLE preferences dialog.\n"
116           "In the 'Fonts/Tabs' tab, changing font face, should update the "
117           "font face of the text in the area below it.\nIn the "
118           "'Highlighting' tab, try different color schemes. Clicking "
119           "items in the sample program should update the choices above it."
120           "\nIn the 'Keys', 'General' and 'Extensions' tabs, test settings "
121           "of interest."
122           "\n[Ok] to close the dialog.[Apply] to apply the settings and "
123           "and [Cancel] to revert all changes.\nRe-run the test to ensure "
124           "changes made have persisted."
125    }
126
127# TODO Improve message
128_dyn_option_menu_spec = {
129    'file': 'dynoption',
130    'kwds': {},
131    'msg': "Select one of the many options in the 'old option set'.\n"
132           "Click the button to change the option set.\n"
133           "Select one of the many options in the 'new option set'."
134    }
135
136# TODO edit wrapper
137_editor_window_spec = {
138   'file': 'editor',
139    'kwds': {},
140    'msg': "Test editor functions of interest.\n"
141           "Best to close editor first."
142    }
143
144GetKeysDialog_spec = {
145    'file': 'config_key',
146    'kwds': {'title': 'Test keybindings',
147             'action': 'find-again',
148             'current_key_sequences': [['<Control-Key-g>', '<Key-F3>', '<Control-Key-G>']],
149             '_htest': True,
150             },
151    'msg': "Test for different key modifier sequences.\n"
152           "<nothing> is invalid.\n"
153           "No modifier key is invalid.\n"
154           "Shift key with [a-z],[0-9], function key, move key, tab, space "
155           "is invalid.\nNo validity checking if advanced key binding "
156           "entry is used."
157    }
158
159_grep_dialog_spec = {
160    'file': 'grep',
161    'kwds': {},
162    'msg': "Click the 'Show GrepDialog' button.\n"
163           "Test the various 'Find-in-files' functions.\n"
164           "The results should be displayed in a new '*Output*' window.\n"
165           "'Right-click'->'Go to file/line' anywhere in the search results "
166           "should open that file \nin a new EditorWindow."
167    }
168
169HelpSource_spec = {
170    'file': 'query',
171    'kwds': {'title': 'Help name and source',
172             'menuitem': 'test',
173             'filepath': __file__,
174             'used_names': {'abc'},
175             '_htest': True},
176    'msg': "Enter menu item name and help file path\n"
177           "'', > than 30 chars, and 'abc' are invalid menu item names.\n"
178           "'' and file does not exist are invalid path items.\n"
179           "Any url ('www...', 'http...') is accepted.\n"
180           "Test Browse with and without path, as cannot unittest.\n"
181           "[Ok] or <Return> prints valid entry to shell\n"
182           "[Cancel] or <Escape> prints None to shell"
183    }
184
185_io_binding_spec = {
186    'file': 'iomenu',
187    'kwds': {},
188    'msg': "Test the following bindings.\n"
189           "<Control-o> to open file from dialog.\n"
190           "Edit the file.\n"
191           "<Control-p> to print the file.\n"
192           "<Control-s> to save the file.\n"
193           "<Alt-s> to save-as another file.\n"
194           "<Control-c> to save-copy-as another file.\n"
195           "Check that changes were saved by opening the file elsewhere."
196    }
197
198_multi_call_spec = {
199    'file': 'multicall',
200    'kwds': {},
201    'msg': "The following actions should trigger a print to console or IDLE"
202           " Shell.\nEntering and leaving the text area, key entry, "
203           "<Control-Key>,\n<Alt-Key-a>, <Control-Key-a>, "
204           "<Alt-Control-Key-a>, \n<Control-Button-1>, <Alt-Button-1> and "
205           "focusing out of the window\nare sequences to be tested."
206    }
207
208_multistatus_bar_spec = {
209    'file': 'statusbar',
210    'kwds': {},
211    'msg': "Ensure presence of multi-status bar below text area.\n"
212           "Click 'Update Status' to change the multi-status text"
213    }
214
215_object_browser_spec = {
216    'file': 'debugobj',
217    'kwds': {},
218    'msg': "Double click on items upto the lowest level.\n"
219           "Attributes of the objects and related information "
220           "will be displayed side-by-side at each level."
221    }
222
223_path_browser_spec = {
224    'file': 'pathbrowser',
225    'kwds': {},
226    'msg': "Test for correct display of all paths in sys.path.\n"
227           "Toggle nested items upto the lowest level.\n"
228           "Double clicking on an item prints a traceback\n"
229           "for an exception that is ignored."
230    }
231
232_percolator_spec = {
233    'file': 'percolator',
234    'kwds': {},
235    'msg': "There are two tracers which can be toggled using a checkbox.\n"
236           "Toggling a tracer 'on' by checking it should print tracer "
237           "output to the console or to the IDLE shell.\n"
238           "If both the tracers are 'on', the output from the tracer which "
239           "was switched 'on' later, should be printed first\n"
240           "Test for actions like text entry, and removal."
241    }
242
243Query_spec = {
244    'file': 'query',
245    'kwds': {'title': 'Query',
246             'message': 'Enter something',
247             'text0': 'Go',
248             '_htest': True},
249    'msg': "Enter with <Return> or [Ok].  Print valid entry to Shell\n"
250           "Blank line, after stripping, is ignored\n"
251           "Close dialog with valid entry, <Escape>, [Cancel], [X]"
252    }
253
254
255_replace_dialog_spec = {
256    'file': 'replace',
257    'kwds': {},
258    'msg': "Click the 'Replace' button.\n"
259           "Test various replace options in the 'Replace dialog'.\n"
260           "Click [Close] or [X] to close the 'Replace Dialog'."
261    }
262
263_search_dialog_spec = {
264    'file': 'search',
265    'kwds': {},
266    'msg': "Click the 'Search' button.\n"
267           "Test various search options in the 'Search dialog'.\n"
268           "Click [Close] or [X] to close the 'Search Dialog'."
269    }
270
271_searchbase_spec = {
272    'file': 'searchbase',
273    'kwds': {},
274    'msg': "Check the appearance of the base search dialog\n"
275           "Its only action is to close."
276    }
277
278_scrolled_list_spec = {
279    'file': 'scrolledlist',
280    'kwds': {},
281    'msg': "You should see a scrollable list of items\n"
282           "Selecting (clicking) or double clicking an item "
283           "prints the name to the console or Idle shell.\n"
284           "Right clicking an item will display a popup."
285    }
286
287show_idlehelp_spec = {
288    'file': 'help',
289    'kwds': {},
290    'msg': "If the help text displays, this works.\n"
291           "Text is selectable. Window is scrollable."
292    }
293
294_stack_viewer_spec = {
295    'file': 'stackviewer',
296    'kwds': {},
297    'msg': "A stacktrace for a NameError exception.\n"
298           "Expand 'idlelib ...' and '<locals>'.\n"
299           "Check that exc_value, exc_tb, and exc_type are correct.\n"
300    }
301
302_tooltip_spec = {
303    'file': 'tooltip',
304    'kwds': {},
305    'msg': "Place mouse cursor over both the buttons\n"
306           "A tooltip should appear with some text."
307    }
308
309_tree_widget_spec = {
310    'file': 'tree',
311    'kwds': {},
312    'msg': "The canvas is scrollable.\n"
313           "Click on folders upto to the lowest level."
314    }
315
316_undo_delegator_spec = {
317    'file': 'undo',
318    'kwds': {},
319    'msg': "Click [Undo] to undo any action.\n"
320           "Click [Redo] to redo any action.\n"
321           "Click [Dump] to dump the current state "
322           "by printing to the console or the IDLE shell.\n"
323    }
324
325ViewWindow_spec = {
326    'file': 'textview',
327    'kwds': {'title': 'Test textview',
328             'text': 'The quick brown fox jumps over the lazy dog.\n'*35,
329             '_htest': True},
330    'msg': "Test for read-only property of text.\n"
331           "Select text, scroll window, close"
332     }
333
334_widget_redirector_spec = {
335    'file': 'redirector',
336    'kwds': {},
337    'msg': "Every text insert should be printed to the console "
338           "or the IDLE shell."
339    }
340
341def run(*tests):
342    root = tk.Tk()
343    root.title('IDLE htest')
344    root.resizable(0, 0)
345
346    # a scrollable Label like constant width text widget.
347    frameLabel = tk.Frame(root, padx=10)
348    frameLabel.pack()
349    text = tk.Text(frameLabel, wrap='word')
350    text.configure(bg=root.cget('bg'), relief='flat', height=4, width=70)
351    scrollbar = Scrollbar(frameLabel, command=text.yview)
352    text.config(yscrollcommand=scrollbar.set)
353    scrollbar.pack(side='right', fill='y', expand=False)
354    text.pack(side='left', fill='both', expand=True)
355
356    test_list = [] # List of tuples of the form (spec, callable widget)
357    if tests:
358        for test in tests:
359            test_spec = globals()[test.__name__ + '_spec']
360            test_spec['name'] = test.__name__
361            test_list.append((test_spec,  test))
362    else:
363        for k, d in globals().items():
364            if k.endswith('_spec'):
365                test_name = k[:-5]
366                test_spec = d
367                test_spec['name'] = test_name
368                mod = import_module('idlelib.' + test_spec['file'])
369                test = getattr(mod, test_name)
370                test_list.append((test_spec, test))
371
372    test_name = tk.StringVar(root)
373    callable_object = None
374    test_kwds = None
375
376    def next_test():
377
378        nonlocal test_name, callable_object, test_kwds
379        if len(test_list) == 1:
380            next_button.pack_forget()
381        test_spec, callable_object = test_list.pop()
382        test_kwds = test_spec['kwds']
383        test_kwds['parent'] = root
384        test_name.set('Test ' + test_spec['name'])
385
386        text.configure(state='normal') # enable text editing
387        text.delete('1.0','end')
388        text.insert("1.0",test_spec['msg'])
389        text.configure(state='disabled') # preserve read-only property
390
391    def run_test(_=None):
392        widget = callable_object(**test_kwds)
393        try:
394            print(widget.result)
395        except AttributeError:
396            pass
397
398    def close(_=None):
399        root.destroy()
400
401    button = tk.Button(root, textvariable=test_name,
402                       default='active', command=run_test)
403    next_button = tk.Button(root, text="Next", command=next_test)
404    button.pack()
405    next_button.pack()
406    next_button.focus_set()
407    root.bind('<Key-Return>', run_test)
408    root.bind('<Key-Escape>', close)
409
410    next_test()
411    root.mainloop()
412
413if __name__ == '__main__':
414    run()
415