htest.py 13.6 KB
Newer Older
1
'''Run human tests of Idle's window, dialog, and popup widgets.
2

3
run(*tests)
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Create a master Tk window.  Within that, run each callable in tests
after finding the matching test spec in this file.  If tests is empty,
run an htest for each spec dict in this file after finding the matching
callable in the module named in the spec.  Close the window to skip or
end the test.

In a tested module, let X be a global name bound to a callable (class
or function) whose .__name__ attrubute is also X (the usual situation).
The first parameter of X must be 'parent'.  When called, the parent
argument will be the root window.  X must create a child Toplevel
window (or subclass thereof).  The Toplevel may be a test widget or
dialog, in which case the callable is the corresonding class.  Or the
Toplevel may contain the widget to be tested or set up a context in
which a test widget is invoked.  In this latter case, the callable is a
wrapper function that sets up the Toplevel and other objects.  Wrapper
function names, such as _editor_window', should start with '_'.

21

22
End the module with
23 24 25 26 27 28

if __name__ == '__main__':
    <unittest, if there is one>
    from idlelib.idle_test.htest import run
    run(X)

29 30 31 32 33 34 35 36 37 38 39 40
To have wrapper functions and test invocation code ignored by coveragepy
reports, put '# htest #' on the def statement header line.

def _wrapper(parent):  # htest #

Also make sure that the 'if __name__' line matches the above.  Then have
make sure that .coveragerc includes the following.

[report]
exclude_lines =
    .*# htest #
    if __name__ == .__main__.:
41

42 43 44 45 46 47
(The "." instead of "'" is intentional and necessary.)


To run any X, this file must contain a matching instance of the
following template, with X.__name__ prepended to '_spec'.
When all tests are run, the prefix is use to get X.
48

49 50 51 52 53
_spec = {
    'file': '',
    'kwds': {'title': ''},
    'msg': ""
    }
54

55 56 57 58
file (no .py): run() imports file.py.
kwds: augmented with {'parent':root} and passed to X as **kwds.
title: an example kwd; some widgets need this, delete if not.
msg: master window hints about testing the widget.
59

60 61

Modules and classes not being tested at the moment:
62 63 64 65
PyShell.PyShellEditorWindow
Debugger.Debugger
AutoCompleteWindow.AutoCompleteWindow
OutputWindow.OutputWindow (indirectly being tested with grep test)
66
'''
67

68
from importlib import import_module
69
from idlelib.macosxSupport import _initializeTkVariantTests
70 71 72 73
import tkinter as tk

AboutDialog_spec = {
    'file': 'aboutDialog',
74 75 76 77 78
    'kwds': {'title': 'aboutDialog test',
             '_htest': True,
             },
    'msg': "Test every button. Ensure Python, TK and IDLE versions "
           "are correctly displayed.\n [Close] to exit.",
79
    }
80 81 82 83 84 85

_calltip_window_spec = {
    'file': 'CallTipWindow',
    'kwds': {},
    'msg': "Typing '(' should display a calltip.\n"
           "Typing ') should hide the calltip.\n"
86 87
    }

88 89 90 91
_class_browser_spec = {
    'file': 'ClassBrowser',
    'kwds': {},
    'msg': "Inspect names of module, class(with superclass if "
92
           "applicable), methods and functions.\nToggle nested items.\n"
93 94 95
           "Double clicking on items prints a traceback for an exception "
           "that is ignored."
    }
96 97 98 99 100 101 102 103 104
ConfigExtensionsDialog_spec = {
    'file': 'configDialog',
    'kwds': {'title': 'Test Extension Configuration',
             '_htest': True,},
    'msg': "IDLE extensions dialog.\n"
           "\n[Ok] to close the dialog.[Apply] to apply the settings and "
           "and [Cancel] to revert all changes.\nRe-run the test to ensure "
           "changes made have persisted."
    }
105

106 107 108 109 110 111 112 113 114
_color_delegator_spec = {
    'file': 'ColorDelegator',
    'kwds': {},
    'msg': "The text is sample Python code.\n"
           "Ensure components like comments, keywords, builtins,\n"
           "string, definitions, and break are correctly colored.\n"
           "The default color scheme is in idlelib/config-highlight.def"
    }

115 116
ConfigDialog_spec = {
    'file': 'configDialog',
117
    'kwds': {'title': 'ConfigDialogTest',
118 119 120 121 122 123 124 125 126 127 128 129
             '_htest': True,},
    'msg': "IDLE preferences dialog.\n"
           "In the 'Fonts/Tabs' tab, changing font face, should update the "
           "font face of the text in the area below it.\nIn the "
           "'Highlighting' tab, try different color schemes. Clicking "
           "items in the sample program should update the choices above it."
           "\nIn the 'Keys' and 'General' tab, test settings of interest."
           "\n[Ok] to close the dialog.[Apply] to apply the settings and "
           "and [Cancel] to revert all changes.\nRe-run the test to ensure "
           "changes made have persisted."
    }

130
# TODO Improve message
131 132
_dyn_option_menu_spec = {
    'file': 'dynOptionMenuWidget',
133
    'kwds': {},
134 135 136
    'msg': "Select one of the many options in the 'old option set'.\n"
           "Click the button to change the option set.\n"
           "Select one of the many options in the 'new option set'."
137 138
    }

139
# TODO edit wrapper
140 141 142
_editor_window_spec = {
   'file': 'EditorWindow',
    'kwds': {},
143 144
    'msg': "Test editor functions of interest.\n"
           "Best to close editor first."
145
    }
146

147 148 149
GetCfgSectionNameDialog_spec = {
    'file': 'configSectionNameDialog',
    'kwds': {'title':'Get Name',
150 151 152
             'message':'Enter something',
             'used_names': {'abc'},
             '_htest': True},
153
    'msg': "After the text entered with [Ok] is stripped, <nothing>, "
154
           "'abc', or more that 30 chars are errors.\n"
155 156 157
           "Close 'Get Name' with a valid entry (printed to Shell), "
           "[Cancel], or [X]",
    }
158

159 160 161 162 163 164 165 166 167 168
GetHelpSourceDialog_spec = {
    'file': 'configHelpSourceEdit',
    'kwds': {'title': 'Get helpsource',
             '_htest': True},
    'msg': "Enter menu item name and help file path\n "
           "<nothing> and more than 30 chars are invalid menu item names.\n"
           "<nothing>, file does not exist are invalid path items.\n"
           "Test for incomplete web address for help file path.\n"
           "A valid entry will be printed to shell with [0k].\n"
           "[Cancel] will print None to shell",
169 170
    }

171 172 173 174 175 176 177 178 179 180 181 182
# Update once issue21519 is resolved.
GetKeysDialog_spec = {
    'file': 'keybindingDialog',
    'kwds': {'title': 'Test keybindings',
             'action': 'find-again',
             'currentKeySequences': [''] ,
             '_htest': True,
             },
    'msg': "Test for different key modifier sequences.\n"
           "<nothing> is invalid.\n"
           "No modifier key is invalid.\n"
           "Shift key with [a-z],[0-9], function key, move key, tab, space"
183
           "is invalid.\nNo validity checking if advanced key binding "
184 185 186
           "entry is used."
    }

187 188 189 190 191 192 193 194 195 196
_grep_dialog_spec = {
    'file': 'GrepDialog',
    'kwds': {},
    'msg': "Click the 'Show GrepDialog' button.\n"
           "Test the various 'Find-in-files' functions.\n"
           "The results should be displayed in a new '*Output*' window.\n"
           "'Right-click'->'Goto file/line' anywhere in the search results "
           "should open that file \nin a new EditorWindow."
    }

197 198 199 200 201 202 203 204 205 206 207
_io_binding_spec = {
    'file': 'IOBinding',
    'kwds': {},
    'msg': "Test the following bindings\n"
           "<Control-o> to display open window from file dialog.\n"
           "<Control-s> to save the file\n"
    }

_multi_call_spec = {
    'file': 'MultiCall',
    'kwds': {},
208 209 210 211 212
    'msg': "The following actions should trigger a print to console or IDLE"
           " Shell.\nEntering and leaving the text area, key entry, "
           "<Control-Key>,\n<Alt-Key-a>, <Control-Key-a>, "
           "<Alt-Control-Key-a>, \n<Control-Button-1>, <Alt-Button-1> and "
           "focusing out of the window\nare sequences to be tested."
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
    }

_multistatus_bar_spec = {
    'file': 'MultiStatusBar',
    'kwds': {},
    'msg': "Ensure presence of multi-status bar below text area.\n"
           "Click 'Update Status' to change the multi-status text"
    }

_object_browser_spec = {
    'file': 'ObjectBrowser',
    'kwds': {},
    'msg': "Double click on items upto the lowest level.\n"
           "Attributes of the objects and related information "
           "will be displayed side-by-side at each level."
    }

_path_browser_spec = {
    'file': 'PathBrowser',
    'kwds': {},
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
    'msg': "Test for correct display of all paths in sys.path.\n"
           "Toggle nested items upto the lowest level.\n"
           "Double clicking on an item prints a traceback\n"
           "for an exception that is ignored."
    }

_percolator_spec = {
    'file': 'Percolator',
    'kwds': {},
    'msg': "There are two tracers which can be toggled using a checkbox.\n"
           "Toggling a tracer 'on' by checking it should print tracer"
           "output to the console or to the IDLE shell.\n"
           "If both the tracers are 'on', the output from the tracer which "
           "was switched 'on' later, should be printed first\n"
           "Test for actions like text entry, and removal."
248 249
    }

250 251 252 253 254
_replace_dialog_spec = {
    'file': 'ReplaceDialog',
    'kwds': {},
    'msg': "Click the 'Replace' button.\n"
           "Test various replace options in the 'Replace dialog'.\n"
255
           "Click [Close] or [X] to close the 'Replace Dialog'."
256 257 258 259 260 261 262
    }

_search_dialog_spec = {
    'file': 'SearchDialog',
    'kwds': {},
    'msg': "Click the 'Search' button.\n"
           "Test various search options in the 'Search dialog'.\n"
263
           "Click [Close] or [X] to close the 'Search Dialog'."
264 265
    }

266 267 268 269
_scrolled_list_spec = {
    'file': 'ScrolledList',
    'kwds': {},
    'msg': "You should see a scrollable list of items\n"
270 271 272 273 274
           "Selecting (clicking) or double clicking an item "
           "prints the name to the console or Idle shell.\n"
           "Right clicking an item will display a popup."
    }

275 276 277 278 279 280 281
show_idlehelp_spec = {
    'file': 'help',
    'kwds': {},
    'msg': "If the help text displays, this works.\n"
           "Text is selectable. Window is scrollable."
    }

282 283 284 285 286 287
_stack_viewer_spec = {
    'file': 'StackViewer',
    'kwds': {},
    'msg': "A stacktrace for a NameError exception.\n"
           "Expand 'idlelib ...' and '<locals>'.\n"
           "Check that exc_value, exc_tb, and exc_type are correct.\n"
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
    }

_tabbed_pages_spec = {
    'file': 'tabbedpages',
    'kwds': {},
    'msg': "Toggle between the two tabs 'foo' and 'bar'\n"
           "Add a tab by entering a suitable name for it.\n"
           "Remove an existing tab by entering its name.\n"
           "Remove all existing tabs.\n"
           "<nothing> is an invalid add page and remove page name.\n"
    }

TextViewer_spec = {
    'file': 'textView',
    'kwds': {'title': 'Test textView',
             'text':'The quick brown fox jumps over the lazy dog.\n'*35,
             '_htest': True},
    'msg': "Test for read-only property of text.\n"
           "Text is selectable. Window is scrollable.",
     }

_tooltip_spec = {
    'file': 'ToolTip',
    'kwds': {},
    'msg': "Place mouse cursor over both the buttons\n"
           "A tooltip should appear with some text."
    }

_tree_widget_spec = {
    'file': 'TreeWidget',
    'kwds': {},
319
    'msg': "The canvas is scrollable.\n"
320 321 322
           "Click on folders upto to the lowest level."
    }

323 324 325 326 327 328 329 330 331
_undo_delegator_spec = {
    'file': 'UndoDelegator',
    'kwds': {},
    'msg': "Click [Undo] to undo any action.\n"
           "Click [Redo] to redo any action.\n"
           "Click [Dump] to dump the current state "
           "by printing to the console or the IDLE shell.\n"
    }

332 333 334
_widget_redirector_spec = {
    'file': 'WidgetRedirector',
    'kwds': {},
335 336
    'msg': "Every text insert should be printed to the console."
           "or the IDLE shell."
337 338
    }

339
def run(*tests):
340
    root = tk.Tk()
341 342 343 344 345 346 347 348 349 350 351 352 353 354
    root.title('IDLE htest')
    root.resizable(0, 0)
    _initializeTkVariantTests(root)

    # a scrollable Label like constant width text widget.
    frameLabel = tk.Frame(root, padx=10)
    frameLabel.pack()
    text = tk.Text(frameLabel, wrap='word')
    text.configure(bg=root.cget('bg'), relief='flat', height=4, width=70)
    scrollbar = tk.Scrollbar(frameLabel, command=text.yview)
    text.config(yscrollcommand=scrollbar.set)
    scrollbar.pack(side='right', fill='y', expand=False)
    text.pack(side='left', fill='both', expand=True)

355 356 357 358 359 360
    test_list = [] # List of tuples of the form (spec, callable widget)
    if tests:
        for test in tests:
            test_spec = globals()[test.__name__ + '_spec']
            test_spec['name'] = test.__name__
            test_list.append((test_spec,  test))
361 362 363 364 365 366 367 368
    else:
        for k, d in globals().items():
            if k.endswith('_spec'):
                test_name = k[:-5]
                test_spec = d
                test_spec['name'] = test_name
                mod = import_module('idlelib.' + test_spec['file'])
                test = getattr(mod, test_name)
369
                test_list.append((test_spec, test))
370 371 372 373 374 375

    test_name = tk.StringVar('')
    callable_object = None
    test_kwds = None

    def next():
376 377

        nonlocal test_name, callable_object, test_kwds
378 379
        if len(test_list) == 1:
            next_button.pack_forget()
380 381 382 383
        test_spec, callable_object = test_list.pop()
        test_kwds = test_spec['kwds']
        test_kwds['parent'] = root
        test_name.set('Test ' + test_spec['name'])
384

385 386 387 388
        text.configure(state='normal') # enable text editing
        text.delete('1.0','end')
        text.insert("1.0",test_spec['msg'])
        text.configure(state='disabled') # preserve read-only property
389 390

    def run_test():
391
        widget = callable_object(**test_kwds)
392 393 394 395 396
        try:
            print(widget.result)
        except AttributeError:
            pass

397 398 399 400 401 402 403 404
    button = tk.Button(root, textvariable=test_name, command=run_test)
    button.pack()
    next_button = tk.Button(root, text="Next", command=next)
    next_button.pack()

    next()

    root.mainloop()
405 406

if __name__ == '__main__':
407
    run()