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