Home | History | Annotate | Download | only in idle_test
      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