mirror of
https://github.com/python/cpython
synced 2026-10-23 07:34:49 +03:00
* gh-155648: In IDLE tests, call unittest.main without exit arg
DD bug 73: In idlelib.idle_test, test_xyz.py files should end with
if __name__ == '__main__':
unittest.main(verbosity=2)
The default exit is True. This need not and should not be added.
5 files add the confusing equivalent exit=2 ("why the weird value?"),
4 files add exit=False. This is nonsensical when there is nothing more to run;
main will immediately exit anyway. When running a test file from an IDLE editor,
this argument has no visible effect. However, a Claude-based
bug finder claims that in other circumstances (such as a program running
the test in a shell), the good test may falsely fail. Even if this is
not true, it can only confuse a reader.
(The only place in idlelib for exit=False is in idlelib/abc.py files where the unittest
is followed by an htest. The default exit=True exits the process, skipping the htest.)
As part of editing the discussion of this in idle_test/htest.py, I clarified other things.
444 lines
15 KiB
Python
444 lines
15 KiB
Python
"""Run human tests of Idle's window, dialog, and popup widgets.
|
|
|
|
The main function, `run(*tests)`, is defined at the end of this file.
|
|
Argument `tests` is a possibly empty tuple of callables defined in some
|
|
idlelib.abc module (or possibly modules). Its steps:
|
|
1. Create a master Tk() htest window. Within that window ...
|
|
2a. If tuple `tests` is not empty, run was likely called from one
|
|
module. Run each callable in `tests` after finding the matching
|
|
callable_spec test spec in this file.
|
|
2b. If tests is empty, run was likely called from this file.
|
|
Run an htest for each spec dict in this file after finding the
|
|
matching callable in the module named in the spec.
|
|
3. Close the master window to end testing.
|
|
|
|
In a tested module, let X be a global name bound to a callable (class
|
|
or function) whose .__name__ attribute (its `class` or `def` definition
|
|
name) is also X. X must expect exactly 1 positional argument, a
|
|
parent toplevel window. Run passes the htest window. X must create a
|
|
child Toplevel(parent/master). The callable may be either a runtime
|
|
object or a wrapper function written just for the test. In the latter
|
|
case, its name should start with '_' and be lowercase (such as '_ttt').
|
|
|
|
End the module with
|
|
```
|
|
if __name__ == '__main__':
|
|
from unittest import main
|
|
main("idlelib.idle_test.test_xyz", verbosity=2, exit=False)
|
|
|
|
from idlelib.idle_test.htest import run
|
|
run(callable)
|
|
```
|
|
Replace 'xyz' as appropriate and 'callable' with the callable name or
|
|
comma-separated names (multiple names is rare). 'exit=False' is needed
|
|
for the htest to run.
|
|
|
|
To have wrapper functions ignored by coverage reports, tag the def
|
|
header like so: "def _wrapper(root): # htest #". Use the same tag
|
|
for htest-only lines in the main code. To ignore the 'if __name__'
|
|
statement, match the example above. Add the below to coveragerc.
|
|
```
|
|
[report]
|
|
exclude_lines =
|
|
.*# htest #
|
|
if __name__ == .__main__.:
|
|
```
|
|
|
|
To run any X, this file must contain a matching instance of the
|
|
following template, with X.__name__ prepended to '_spec'.
|
|
|
|
callable_spec = {
|
|
'file': '',
|
|
'kwds': {'title': ''},
|
|
'msg': ""
|
|
}
|
|
|
|
file (no .py): run() imports file.py.
|
|
kwds: run() augments with {'parent':root} and passes to X as **kwds.
|
|
title: an example kwd; some widgets need this, delete line if not.
|
|
msg: master window hints about testing the widget.
|
|
|
|
TODO test these modules and classes:
|
|
autocomplete_w.AutoCompleteWindow
|
|
debugger.Debugger
|
|
outwin.OutputWindow (indirectly being tested with grep test)
|
|
pyshell.PyShellEditorWindow
|
|
"""
|
|
|
|
import idlelib.pyshell # Set Windows DPI awareness before Tk().
|
|
from importlib import import_module
|
|
import textwrap
|
|
import tkinter as tk
|
|
from tkinter.ttk import Scrollbar
|
|
tk.NoDefaultRoot()
|
|
|
|
AboutDialog_spec = {
|
|
'file': 'help_about',
|
|
'kwds': {'title': 'help_about test',
|
|
'_htest': True,
|
|
},
|
|
'msg': "Click on URL to open in default browser.\n"
|
|
"Verify x.y.z versions and test each button, including Close.\n "
|
|
}
|
|
|
|
# TODO implement ^\; adding '<Control-Key-\\>' to function does not work.
|
|
_calltip_window_spec = {
|
|
'file': 'calltip_w',
|
|
'kwds': {},
|
|
'msg': "Typing '(' should display a calltip.\n"
|
|
"Typing ') should hide the calltip.\n"
|
|
"So should moving cursor out of argument area.\n"
|
|
"Force-open-calltip does not work here.\n"
|
|
}
|
|
|
|
_color_delegator_spec = {
|
|
'file': 'colorizer',
|
|
'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"
|
|
}
|
|
|
|
ConfigDialog_spec = {
|
|
'file': 'configdialog',
|
|
'kwds': {'title': 'ConfigDialogTest',
|
|
'_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', 'General' and 'Extensions' tabs, 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."
|
|
}
|
|
|
|
CustomRun_spec = {
|
|
'file': 'query',
|
|
'kwds': {'title': 'Customize query.py Run',
|
|
'_htest': True},
|
|
'msg': "Enter with <Return> or [OK]. Print valid entry to Shell\n"
|
|
"Arguments are parsed into a list\n"
|
|
"Mode is currently restart True or False\n"
|
|
"Close dialog with valid entry, <Escape>, [Cancel], [X]"
|
|
}
|
|
|
|
_debug_object_browser_spec = {
|
|
'file': 'debugobj',
|
|
'kwds': {},
|
|
'msg': "Double click on items up to the lowest level.\n"
|
|
"Attributes of the objects and related information "
|
|
"will be displayed side-by-side at each level."
|
|
}
|
|
|
|
# TODO Improve message
|
|
_dyn_option_menu_spec = {
|
|
'file': 'dynoption',
|
|
'kwds': {},
|
|
'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'."
|
|
}
|
|
|
|
# TODO edit wrapper
|
|
_editor_window_spec = {
|
|
'file': 'editor',
|
|
'kwds': {},
|
|
'msg': "Test editor functions of interest.\n"
|
|
"Best to close editor first."
|
|
}
|
|
|
|
GetKeysWindow_spec = {
|
|
'file': 'config_key',
|
|
'kwds': {'title': 'Test keybindings',
|
|
'action': 'find-again',
|
|
'current_key_sequences': [['<Control-Key-g>', '<Key-F3>', '<Control-Key-G>']],
|
|
'_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 "
|
|
"is invalid.\nNo validity checking if advanced key binding "
|
|
"entry is used."
|
|
}
|
|
|
|
_grep_dialog_spec = {
|
|
'file': 'grep',
|
|
'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'->'Go to file/line' in the search results\n "
|
|
"should open that file in a new EditorWindow."
|
|
}
|
|
|
|
HelpSource_spec = {
|
|
'file': 'query',
|
|
'kwds': {'title': 'Help name and source',
|
|
'menuitem': 'test',
|
|
'filepath': __file__,
|
|
'used_names': {'abc'},
|
|
'_htest': True},
|
|
'msg': "Enter menu item name and help file path\n"
|
|
"'', > than 30 chars, and 'abc' are invalid menu item names.\n"
|
|
"'' and file does not exist are invalid path items.\n"
|
|
"Any url ('www...', 'http...') is accepted.\n"
|
|
"Test Browse with and without path, as cannot unittest.\n"
|
|
"[Ok] or <Return> prints valid entry to shell\n"
|
|
"<Escape>, [Cancel], or [X] prints None to shell"
|
|
}
|
|
|
|
_io_binding_spec = {
|
|
'file': 'iomenu',
|
|
'kwds': {},
|
|
'msg': "Test the following bindings.\n"
|
|
"<Control-o> to open file from dialog.\n"
|
|
"Edit the file.\n"
|
|
"<Control-p> to print the file.\n"
|
|
"<Control-s> to save the file.\n"
|
|
"<Alt-s> to save-as another file.\n"
|
|
"<Control-c> to save-copy-as another file.\n"
|
|
"Check that changes were saved by opening the file elsewhere."
|
|
}
|
|
|
|
_multi_call_spec = {
|
|
'file': 'multicall',
|
|
'kwds': {},
|
|
'msg': "The following should trigger a print to console or IDLE Shell.\n"
|
|
"Entering 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 elsewhere."
|
|
}
|
|
|
|
_module_browser_spec = {
|
|
'file': 'browser',
|
|
'kwds': {},
|
|
'msg': textwrap.dedent("""
|
|
Inspect names of module, class(with superclass if applicable),
|
|
methods and functions. Toggle nested items. Double clicking
|
|
on items prints a traceback for an exception that is ignored.""")
|
|
}
|
|
|
|
_multistatus_bar_spec = {
|
|
'file': 'statusbar',
|
|
'kwds': {},
|
|
'msg': "Ensure presence of multi-status bar below text area.\n"
|
|
"Click 'Update Status' to change the status text"
|
|
}
|
|
|
|
PathBrowser_spec = {
|
|
'file': 'pathbrowser',
|
|
'kwds': {'_htest': True},
|
|
'msg': "Test for correct display of all paths in sys.path.\n"
|
|
"Toggle nested items out to 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."
|
|
}
|
|
|
|
Query_spec = {
|
|
'file': 'query',
|
|
'kwds': {'title': 'Query',
|
|
'message': 'Enter something',
|
|
'text0': 'Go',
|
|
'_htest': True},
|
|
'msg': "Enter with <Return> or [Ok]. Print valid entry to Shell\n"
|
|
"Blank line, after stripping, is ignored\n"
|
|
"Close dialog with valid entry, <Escape>, [Cancel], [X]"
|
|
}
|
|
|
|
|
|
_replace_dialog_spec = {
|
|
'file': 'replace',
|
|
'kwds': {},
|
|
'msg': "Click the 'Replace' button.\n"
|
|
"Test various replace options in the 'Replace dialog'.\n"
|
|
"Click [Close] or [X] to close the 'Replace Dialog'."
|
|
}
|
|
|
|
_scrolled_list_spec = {
|
|
'file': 'scrolledlist',
|
|
'kwds': {},
|
|
'msg': "You should see a scrollable list of items\n"
|
|
"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."
|
|
}
|
|
|
|
_search_dialog_spec = {
|
|
'file': 'search',
|
|
'kwds': {},
|
|
'msg': "Click the 'Search' button.\n"
|
|
"Test various search options in the 'Search dialog'.\n"
|
|
"Click [Close] or [X] to close the 'Search Dialog'."
|
|
}
|
|
|
|
_searchbase_spec = {
|
|
'file': 'searchbase',
|
|
'kwds': {},
|
|
'msg': "Check the appearance of the base search dialog\n"
|
|
"Its only action is to close."
|
|
}
|
|
|
|
show_idlehelp_spec = {
|
|
'file': 'help',
|
|
'kwds': {},
|
|
'msg': "If the help text displays, this works.\n"
|
|
"Text is selectable. Window is scrollable."
|
|
}
|
|
|
|
_sidebar_number_scrolling_spec = {
|
|
'file': 'sidebar',
|
|
'kwds': {},
|
|
'msg': textwrap.dedent("""\
|
|
1. Click on the line numbers and drag down below the edge of the
|
|
window, moving the mouse a bit and then leaving it there for a
|
|
while. The text and line numbers should gradually scroll down,
|
|
with the selection updated continuously.
|
|
|
|
2. With the lines still selected, click on a line number above
|
|
or below the selected lines. Only the line whose number was
|
|
clicked should be selected.
|
|
|
|
3. Repeat step #1, dragging to above the window. The text and
|
|
line numbers should gradually scroll up, with the selection
|
|
updated continuously.
|
|
|
|
4. Repeat step #2, clicking a line number below the selection."""),
|
|
}
|
|
|
|
_stackbrowser_spec = {
|
|
'file': 'stackviewer',
|
|
'kwds': {},
|
|
'msg': "A stacktrace for a NameError exception.\n"
|
|
"Should have NameError and 1 traceback line."
|
|
}
|
|
|
|
_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': 'tree',
|
|
'kwds': {},
|
|
'msg': "The canvas is scrollable.\n"
|
|
"Click on folders up to the lowest level."
|
|
}
|
|
|
|
_undo_delegator_spec = {
|
|
'file': 'undo',
|
|
'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"
|
|
}
|
|
|
|
ViewWindow_spec = {
|
|
'file': 'textview',
|
|
'kwds': {'title': 'Test textview',
|
|
'contents': 'The quick brown fox jumps over the lazy dog.\n'*35,
|
|
'_htest': True},
|
|
'msg': "Test for read-only property of text.\n"
|
|
"Select text, scroll window, close"
|
|
}
|
|
|
|
_widget_redirector_spec = {
|
|
'file': 'redirector',
|
|
'kwds': {},
|
|
'msg': "Every text insert should be printed to the console "
|
|
"or the IDLE shell."
|
|
}
|
|
|
|
def run(*tests):
|
|
"Run callables in tests."
|
|
root = tk.Tk()
|
|
root.title('IDLE htest')
|
|
root.resizable(0, 0)
|
|
|
|
# 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 = 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)
|
|
|
|
test_list = [] # Make list of (spec, callable) tuples.
|
|
if tests:
|
|
for test in tests:
|
|
test_spec = globals()[test.__name__ + '_spec']
|
|
test_spec['name'] = test.__name__
|
|
test_list.append((test_spec, test))
|
|
else:
|
|
for key, dic in globals().items():
|
|
if key.endswith('_spec'):
|
|
test_name = key[:-5]
|
|
test_spec = dic
|
|
test_spec['name'] = test_name
|
|
mod = import_module('idlelib.' + test_spec['file'])
|
|
test = getattr(mod, test_name)
|
|
test_list.append((test_spec, test))
|
|
test_list.reverse() # So can pop in proper order in next_test.
|
|
|
|
test_name = tk.StringVar(root)
|
|
callable_object = None
|
|
test_kwds = None
|
|
|
|
def next_test():
|
|
nonlocal test_name, callable_object, test_kwds
|
|
if len(test_list) == 1:
|
|
next_button.pack_forget()
|
|
test_spec, callable_object = test_list.pop()
|
|
test_kwds = test_spec['kwds']
|
|
test_name.set('Test ' + test_spec['name'])
|
|
|
|
text['state'] = 'normal' # Enable text replacement.
|
|
text.delete('1.0', 'end')
|
|
text.insert("1.0", test_spec['msg'])
|
|
text['state'] = 'disabled' # Restore read-only property.
|
|
|
|
def run_test(_=None):
|
|
widget = callable_object(root, **test_kwds)
|
|
try:
|
|
print(widget.result) # Only true for query classes(?).
|
|
except AttributeError:
|
|
pass
|
|
|
|
def close(_=None):
|
|
root.destroy()
|
|
|
|
button = tk.Button(root, textvariable=test_name,
|
|
default='active', command=run_test)
|
|
next_button = tk.Button(root, text="Next", command=next_test)
|
|
button.pack()
|
|
next_button.pack()
|
|
next_button.focus_set()
|
|
root.bind('<Key-Return>', run_test)
|
|
root.bind('<Key-Escape>', close)
|
|
|
|
next_test()
|
|
root.mainloop()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
run()
|