python-3.6.zip added from Github

README.cosmo contains the necessary links.
This commit is contained in:
ahgamut 2021-08-08 09:38:33 +05:30 committed by Justine Tunney
parent 75fc601ff5
commit 0c4c56ff39
4219 changed files with 1968626 additions and 0 deletions

View file

@ -0,0 +1,238 @@
README FOR IDLE TESTS IN IDLELIB.IDLE_TEST
0. Quick Start
Automated unit tests were added in 3.3 for Python 3.x.
To run the tests from a command line:
python -m test.test_idle
Human-mediated tests were added later in 3.4.
python -m idlelib.idle_test.htest
1. Test Files
The idle directory, idlelib, has over 60 xyz.py files. The idle_test
subdirectory contains test_xyz.py for each implementation file xyz.py.
To add a test for abc.py, open idle_test/template.py and immediately
Save As test_abc.py. Insert 'abc' on the first line, and replace
'zzdummy' with 'abc.
Remove the imports of requires and tkinter if not needed. Otherwise,
add to the tkinter imports as needed.
Add a prefix to 'Test' for the initial test class. The template class
contains code needed or possibly needed for gui tests. See the next
section if doing gui tests. If not, and not needed for further classes,
this code can be removed.
Add the following at the end of abc.py. If an htest was added first,
insert the import and main lines before the htest lines.
if __name__ == "__main__":
from unittest import main
main('idlelib.idle_test.test_abc', verbosity=2, exit=False)
The ', exit=False' is only needed if an htest follows.
2. GUI Tests
When run as part of the Python test suite, Idle GUI tests need to run
test.support.requires('gui'). A test is a GUI test if it creates a
tkinter.Tk root or master object either directly or indirectly by
instantiating a tkinter or idle class. GUI tests cannot run in test
processes that either have no graphical environment available or are not
allowed to use it.
To guard a module consisting entirely of GUI tests, start with
from test.support import requires
requires('gui')
To guard a test class, put "requires('gui')" in its setUpClass function.
The template.py file does this.
To avoid interfering with other GUI tests, all GUI objects must be
destroyed and deleted by the end of the test. The Tk root created in a
setUpX function should be destroyed in the corresponding tearDownX and
the module or class attribute deleted. Others widgets should descend
from the single root and the attributes deleted BEFORE root is
destroyed. See https://bugs.python.org/issue20567.
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = tk.Tk()
cls.text = tk.Text(root)
@classmethod
def tearDownClass(cls):
del cls.text
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
The update_idletasks call is sometimes needed to prevent the following
warning either when running a test alone or as part of the test suite
(#27196). It should not hurt if not needed.
can't invoke "event" command: application has been destroyed
...
"ttk::ThemeChanged"
If a test creates instance 'e' of EditorWindow, call 'e._close()' before
or as the first part of teardown. The effect of omitting this depends
on the later shutdown. Then enable the after_cancel loop in the
template. This prevents messages like the following.
bgerror failed to handle background error.
Original error: invalid command name "106096696timer_event"
Error in bgerror: can't invoke "tk" command: application has been destroyed
Requires('gui') causes the test(s) it guards to be skipped if any of
these conditions are met:
- The tests are being run by regrtest.py, and it was started without
enabling the "gui" resource with the "-u" command line option.
- The tests are being run on Windows by a service that is not allowed
to interact with the graphical environment.
- The tests are being run on Linux and X Windows is not available.
- The tests are being run on Mac OSX in a process that cannot make a
window manager connection.
- tkinter.Tk cannot be successfully instantiated for some reason.
- test.support.use_resources has been set by something other than
regrtest.py and does not contain "gui".
Tests of non-GUI operations should avoid creating tk widgets. Incidental
uses of tk variables and messageboxes can be replaced by the mock
classes in idle_test/mock_tk.py. The mock text handles some uses of the
tk Text widget.
3. Running Unit Tests
Assume that xyz.py and test_xyz.py both end with a unittest.main() call.
Running either from an Idle editor runs all tests in the test_xyz file
with the version of Python running Idle. Test output appears in the
Shell window. The 'verbosity=2' option lists all test methods in the
file, which is appropriate when developing tests. The 'exit=False'
option is needed in xyx.py files when an htest follows.
The following command lines also run all test methods, including
GUI tests, in test_xyz.py. (Both '-m idlelib' and '-m idlelib.idle'
start Idle and so cannot run tests.)
python -m idlelib.xyz
python -m idlelib.idle_test.test_xyz
The following runs all idle_test/test_*.py tests interactively.
>>> import unittest
>>> unittest.main('idlelib.idle_test', verbosity=2)
The following run all Idle tests at a command line. Option '-v' is the
same as 'verbosity=2'.
python -m unittest -v idlelib.idle_test
python -m test -v -ugui test_idle
python -m test.test_idle
The idle tests are 'discovered' by
idlelib.idle_test.__init__.load_tests, which is also imported into
test.test_idle. Normally, neither file should be changed when working on
individual test modules. The third command runs unittest indirectly
through regrtest. The same happens when the entire test suite is run
with 'python -m test'. So that command must work for buildbots to stay
green. Idle tests must not disturb the environment in a way that makes
other tests fail (issue 18081).
To run an individual Testcase or test method, extend the dotted name
given to unittest on the command line or use the test -m option. The
latter allows use of other regrtest options. When using the latter,
all components of the pattern must be present, but any can be replaced
by '*'.
python -m unittest -v idlelib.idle_test.test_xyz.Test_case.test_meth
python -m test -m idlelib.idle_test.text_xyz.Test_case.test_meth test_idle
The test suite can be run in an IDLE user process from Shell.
>>> import test.autotest # Issue 25588, 2017/10/13, 3.6.4, 3.7.0a2.
There are currently failures not usually present, and this does not
work when run from the editor.
4. Human-mediated Tests
Human-mediated tests are widget tests that cannot be automated but need
human verification. They are contained in idlelib/idle_test/htest.py,
which has instructions. (Some modules need an auxiliary function,
identified with "# htest # on the header line.) The set is about
complete, though some tests need improvement. To run all htests, run the
htest file from an editor or from the command line with:
python -m idlelib.idle_test.htest
5. Test Coverage
Install the coverage package into your Python 3.6 site-packages
directory. (Its exact location depends on the OS).
> python3 -m pip install coverage
(On Windows, replace 'python3 with 'py -3.6' or perhaps just 'python'.)
The problem with running coverage with repository python is that
coverage uses absolute imports for its submodules, hence it needs to be
in a directory in sys.path. One solution: copy the package to the
directory containing the cpython repository. Call it 'dev'. Then run
coverage either directly or from a script in that directory so that
'dev' is prepended to sys.path.
Either edit or add dev/.coveragerc so it looks something like this.
---
# .coveragerc sets coverage options.
[run]
branch = True
[report]
# Regexes for lines to exclude from consideration
exclude_lines =
# Don't complain if non-runnable code isn't run:
if 0:
if __name__ == .__main__.:
.*# htest #
if not _utest:
if _htest:
---
The additions for IDLE are 'branch = True', to test coverage both ways,
and the last three exclude lines, to exclude things peculiar to IDLE
that are not executed during tests.
A script like the following cover.bat (for Windows) is very handy.
---
@echo off
rem Usage: cover filename [test_ suffix] # proper case required by coverage
rem filename without .py, 2nd parameter if test is not test_filename
setlocal
set py=f:\dev\3x\pcbuild\win32\python_d.exe
set src=idlelib.%1
if "%2" EQU "" set tst=f:/dev/3x/Lib/idlelib/idle_test/test_%1.py
if "%2" NEQ "" set tst=f:/dev/ex/Lib/idlelib/idle_test/test_%2.py
%py% -m coverage run --pylib --source=%src% %tst%
%py% -m coverage report --show-missing
%py% -m coverage html
start htmlcov\3x_Lib_idlelib_%1_py.html
rem Above opens new report; htmlcov\index.html displays report index
---
The second parameter was added for tests of module x not named test_x.
(There were several before modules were renamed, now only one is left.)

View file

@ -0,0 +1,17 @@
'''idlelib.idle_test is a private implementation of test.test_idle,
which tests the IDLE application as part of the stdlib test suite.
Run IDLE tests alone with "python -m test.test_idle".
Starting with Python 3.6, IDLE requires tcl/tk 8.5 or later.
This package and its contained modules are subject to change and
any direct use is at your own risk.
'''
from os.path import dirname
def load_tests(loader, standard_tests, pattern):
this_dir = dirname(__file__)
top_dir = dirname(dirname(this_dir))
package_tests = loader.discover(start_dir=this_dir, pattern='test*.py',
top_level_dir=top_dir)
standard_tests.addTests(package_tests)
return standard_tests

View file

@ -0,0 +1,415 @@
'''Run human tests of Idle's window, dialog, and popup widgets.
run(*tests)
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__ attribute 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 '_'.
End the module with
if __name__ == '__main__':
<unittest, if there is one>
from idlelib.idle_test.htest import run
run(X)
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__.:
(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.
_spec = {
'file': '',
'kwds': {'title': ''},
'msg': ""
}
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.
Modules and classes not being tested at the moment:
pyshell.PyShellEditorWindow
debugger.Debugger
autocomplete_w.AutoCompleteWindow
outwin.OutputWindow (indirectly being tested with grep test)
'''
import idlelib.pyshell # Set Windows DPI awareness before Tk().
from importlib import import_module
import tkinter as tk
from tkinter.ttk import Scrollbar
tk.NoDefaultRoot()
AboutDialog_spec = {
'file': 'help_about',
'kwds': {'title': 'help_about test',
'_htest': True,
},
'msg': "Test every button. Ensure Python, TK and IDLE versions "
"are correctly displayed.\n [Close] to exit.",
}
# 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"
}
_module_browser_spec = {
'file': 'browser',
'kwds': {},
'msg': "Inspect names of module, class(with superclass if "
"applicable), methods and functions.\nToggle nested items.\n"
"Double clicking on items prints a traceback for an exception "
"that is ignored."
}
_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."
}
# 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."
}
# Update once issue21519 is resolved.
GetKeysDialog_spec = {
'file': 'config_key',
'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 "
"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' anywhere in the search results "
"should open that file \nin 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"
"[Cancel] or <Escape> 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 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."
}
_multistatus_bar_spec = {
'file': 'statusbar',
'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': 'debugobj',
'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': {},
'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."
}
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'."
}
_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."
}
_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."
}
show_idlehelp_spec = {
'file': 'help',
'kwds': {},
'msg': "If the help text displays, this works.\n"
"Text is selectable. Window is scrollable."
}
_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"
}
_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 upto 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',
'text': '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):
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 = [] # 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))
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)
test_list.append((test_spec, 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_kwds['parent'] = root
test_name.set('Test ' + test_spec['name'])
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
def run_test(_=None):
widget = callable_object(**test_kwds)
try:
print(widget.result)
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()

View file

@ -0,0 +1,60 @@
'''Mock classes that imitate idlelib modules or classes.
Attributes and methods will be added as needed for tests.
'''
from idlelib.idle_test.mock_tk import Text
class Func:
'''Record call, capture args, return/raise result set by test.
When mock function is called, set or use attributes:
self.called - increment call number even if no args, kwds passed.
self.args - capture positional arguments.
self.kwds - capture keyword arguments.
self.result - return or raise value set in __init__.
self.return_self - return self instead, to mock query class return.
Most common use will probably be to mock instance methods.
Given class instance, can set and delete as instance attribute.
Mock_tk.Var and Mbox_func are special variants of this.
'''
def __init__(self, result=None, return_self=False):
self.called = 0
self.result = result
self.return_self = return_self
self.args = None
self.kwds = None
def __call__(self, *args, **kwds):
self.called += 1
self.args = args
self.kwds = kwds
if isinstance(self.result, BaseException):
raise self.result
elif self.return_self:
return self
else:
return self.result
class Editor:
'''Minimally imitate editor.EditorWindow class.
'''
def __init__(self, flist=None, filename=None, key=None, root=None):
self.text = Text()
self.undo = UndoDelegator()
def get_selection_indices(self):
first = self.text.index('1.0')
last = self.text.index('end')
return first, last
class UndoDelegator:
'''Minimally imitate undo.UndoDelegator class.
'''
# A real undo block is only needed for user interaction.
def undo_block_start(*args):
pass
def undo_block_stop(*args):
pass

View file

@ -0,0 +1,303 @@
"""Classes that replace tkinter gui objects used by an object being tested.
A gui object is anything with a master or parent parameter, which is
typically required in spite of what the doc strings say.
"""
class Event:
'''Minimal mock with attributes for testing event handlers.
This is not a gui object, but is used as an argument for callbacks
that access attributes of the event passed. If a callback ignores
the event, other than the fact that is happened, pass 'event'.
Keyboard, mouse, window, and other sources generate Event instances.
Event instances have the following attributes: serial (number of
event), time (of event), type (of event as number), widget (in which
event occurred), and x,y (position of mouse). There are other
attributes for specific events, such as keycode for key events.
tkinter.Event.__doc__ has more but is still not complete.
'''
def __init__(self, **kwds):
"Create event with attributes needed for test"
self.__dict__.update(kwds)
class Var:
"Use for String/Int/BooleanVar: incomplete"
def __init__(self, master=None, value=None, name=None):
self.master = master
self.value = value
self.name = name
def set(self, value):
self.value = value
def get(self):
return self.value
class Mbox_func:
"""Generic mock for messagebox functions, which all have the same signature.
Instead of displaying a message box, the mock's call method saves the
arguments as instance attributes, which test functions can then examime.
The test can set the result returned to ask function
"""
def __init__(self, result=None):
self.result = result # Return None for all show funcs
def __call__(self, title, message, *args, **kwds):
# Save all args for possible examination by tester
self.title = title
self.message = message
self.args = args
self.kwds = kwds
return self.result # Set by tester for ask functions
class Mbox:
"""Mock for tkinter.messagebox with an Mbox_func for each function.
This module was 'tkMessageBox' in 2.x; hence the 'import as' in 3.x.
Example usage in test_module.py for testing functions in module.py:
---
from idlelib.idle_test.mock_tk import Mbox
import module
orig_mbox = module.tkMessageBox
showerror = Mbox.showerror # example, for attribute access in test methods
class Test(unittest.TestCase):
@classmethod
def setUpClass(cls):
module.tkMessageBox = Mbox
@classmethod
def tearDownClass(cls):
module.tkMessageBox = orig_mbox
---
For 'ask' functions, set func.result return value before calling the method
that uses the message function. When tkMessageBox functions are the
only gui alls in a method, this replacement makes the method gui-free,
"""
askokcancel = Mbox_func() # True or False
askquestion = Mbox_func() # 'yes' or 'no'
askretrycancel = Mbox_func() # True or False
askyesno = Mbox_func() # True or False
askyesnocancel = Mbox_func() # True, False, or None
showerror = Mbox_func() # None
showinfo = Mbox_func() # None
showwarning = Mbox_func() # None
from _tkinter import TclError
class Text:
"""A semi-functional non-gui replacement for tkinter.Text text editors.
The mock's data model is that a text is a list of \n-terminated lines.
The mock adds an empty string at the beginning of the list so that the
index of actual lines start at 1, as with Tk. The methods never see this.
Tk initializes files with a terminal \n that cannot be deleted. It is
invisible in the sense that one cannot move the cursor beyond it.
This class is only tested (and valid) with strings of ascii chars.
For testing, we are not concerned with Tk Text's treatment of,
for instance, 0-width characters or character + accent.
"""
def __init__(self, master=None, cnf={}, **kw):
'''Initialize mock, non-gui, text-only Text widget.
At present, all args are ignored. Almost all affect visual behavior.
There are just a few Text-only options that affect text behavior.
'''
self.data = ['', '\n']
def index(self, index):
"Return string version of index decoded according to current text."
return "%s.%s" % self._decode(index, endflag=1)
def _decode(self, index, endflag=0):
"""Return a (line, char) tuple of int indexes into self.data.
This implements .index without converting the result back to a string.
The result is constrained by the number of lines and linelengths of
self.data. For many indexes, the result is initially (1, 0).
The input index may have any of several possible forms:
* line.char float: converted to 'line.char' string;
* 'line.char' string, where line and char are decimal integers;
* 'line.char lineend', where lineend='lineend' (and char is ignored);
* 'line.end', where end='end' (same as above);
* 'insert', the positions before terminal \n;
* 'end', whose meaning depends on the endflag passed to ._endex.
* 'sel.first' or 'sel.last', where sel is a tag -- not implemented.
"""
if isinstance(index, (float, bytes)):
index = str(index)
try:
index=index.lower()
except AttributeError:
raise TclError('bad text index "%s"' % index) from None
lastline = len(self.data) - 1 # same as number of text lines
if index == 'insert':
return lastline, len(self.data[lastline]) - 1
elif index == 'end':
return self._endex(endflag)
line, char = index.split('.')
line = int(line)
# Out of bounds line becomes first or last ('end') index
if line < 1:
return 1, 0
elif line > lastline:
return self._endex(endflag)
linelength = len(self.data[line]) -1 # position before/at \n
if char.endswith(' lineend') or char == 'end':
return line, linelength
# Tk requires that ignored chars before ' lineend' be valid int
# Out of bounds char becomes first or last index of line
char = int(char)
if char < 0:
char = 0
elif char > linelength:
char = linelength
return line, char
def _endex(self, endflag):
'''Return position for 'end' or line overflow corresponding to endflag.
-1: position before terminal \n; for .insert(), .delete
0: position after terminal \n; for .get, .delete index 1
1: same viewed as beginning of non-existent next line (for .index)
'''
n = len(self.data)
if endflag == 1:
return n, 0
else:
n -= 1
return n, len(self.data[n]) + endflag
def insert(self, index, chars):
"Insert chars before the character at index."
if not chars: # ''.splitlines() is [], not ['']
return
chars = chars.splitlines(True)
if chars[-1][-1] == '\n':
chars.append('')
line, char = self._decode(index, -1)
before = self.data[line][:char]
after = self.data[line][char:]
self.data[line] = before + chars[0]
self.data[line+1:line+1] = chars[1:]
self.data[line+len(chars)-1] += after
def get(self, index1, index2=None):
"Return slice from index1 to index2 (default is 'index1+1')."
startline, startchar = self._decode(index1)
if index2 is None:
endline, endchar = startline, startchar+1
else:
endline, endchar = self._decode(index2)
if startline == endline:
return self.data[startline][startchar:endchar]
else:
lines = [self.data[startline][startchar:]]
for i in range(startline+1, endline):
lines.append(self.data[i])
lines.append(self.data[endline][:endchar])
return ''.join(lines)
def delete(self, index1, index2=None):
'''Delete slice from index1 to index2 (default is 'index1+1').
Adjust default index2 ('index+1) for line ends.
Do not delete the terminal \n at the very end of self.data ([-1][-1]).
'''
startline, startchar = self._decode(index1, -1)
if index2 is None:
if startchar < len(self.data[startline])-1:
# not deleting \n
endline, endchar = startline, startchar+1
elif startline < len(self.data) - 1:
# deleting non-terminal \n, convert 'index1+1 to start of next line
endline, endchar = startline+1, 0
else:
# do not delete terminal \n if index1 == 'insert'
return
else:
endline, endchar = self._decode(index2, -1)
# restricting end position to insert position excludes terminal \n
if startline == endline and startchar < endchar:
self.data[startline] = self.data[startline][:startchar] + \
self.data[startline][endchar:]
elif startline < endline:
self.data[startline] = self.data[startline][:startchar] + \
self.data[endline][endchar:]
startline += 1
for i in range(startline, endline+1):
del self.data[startline]
def compare(self, index1, op, index2):
line1, char1 = self._decode(index1)
line2, char2 = self._decode(index2)
if op == '<':
return line1 < line2 or line1 == line2 and char1 < char2
elif op == '<=':
return line1 < line2 or line1 == line2 and char1 <= char2
elif op == '>':
return line1 > line2 or line1 == line2 and char1 > char2
elif op == '>=':
return line1 > line2 or line1 == line2 and char1 >= char2
elif op == '==':
return line1 == line2 and char1 == char2
elif op == '!=':
return line1 != line2 or char1 != char2
else:
raise TclError('''bad comparison operator "%s": '''
'''must be <, <=, ==, >=, >, or !=''' % op)
# The following Text methods normally do something and return None.
# Whether doing nothing is sufficient for a test will depend on the test.
def mark_set(self, name, index):
"Set mark *name* before the character at index."
pass
def mark_unset(self, *markNames):
"Delete all marks in markNames."
def tag_remove(self, tagName, index1, index2=None):
"Remove tag tagName from all characters between index1 and index2."
pass
# The following Text methods affect the graphics screen and return None.
# Doing nothing should always be sufficient for tests.
def scan_dragto(self, x, y):
"Adjust the view of the text according to scan_mark"
def scan_mark(self, x, y):
"Remember the current X, Y coordinates."
def see(self, index):
"Scroll screen to make the character at INDEX is visible."
pass
# The following is a Misc method inherited by Text.
# It should properly go in a Misc mock, but is included here for now.
def bind(sequence=None, func=None, add=None):
"Bind to this widget at event sequence a call to function func."
pass
class Entry:
"Mock for tkinter.Entry."
def focus_set(self):
pass

View file

@ -0,0 +1,30 @@
"Test , coverage %."
from idlelib import zzdummy
import unittest
from test.support import requires
from tkinter import Tk
class Test(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
@classmethod
def tearDownClass(cls):
cls.root.update_idletasks()
## for id in cls.root.tk.call('after', 'info'):
## cls.root.after_cancel(id) # Need for EditorWindow.
cls.root.destroy()
del cls.root
def test_init(self):
self.assertTrue(True)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,144 @@
"Test autocomplete, coverage 57%."
import unittest
from test.support import requires
from tkinter import Tk, Text
import idlelib.autocomplete as ac
import idlelib.autocomplete_w as acw
from idlelib.idle_test.mock_idle import Func
from idlelib.idle_test.mock_tk import Event
class DummyEditwin:
def __init__(self, root, text):
self.root = root
self.text = text
self.indentwidth = 8
self.tabwidth = 8
self.context_use_ps1 = True
class AutoCompleteTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.text = Text(cls.root)
cls.editor = DummyEditwin(cls.root, cls.text)
@classmethod
def tearDownClass(cls):
del cls.editor, cls.text
cls.root.destroy()
del cls.root
def setUp(self):
self.editor.text.delete('1.0', 'end')
self.autocomplete = ac.AutoComplete(self.editor)
def test_init(self):
self.assertEqual(self.autocomplete.editwin, self.editor)
def test_make_autocomplete_window(self):
testwin = self.autocomplete._make_autocomplete_window()
self.assertIsInstance(testwin, acw.AutoCompleteWindow)
def test_remove_autocomplete_window(self):
self.autocomplete.autocompletewindow = (
self.autocomplete._make_autocomplete_window())
self.autocomplete._remove_autocomplete_window()
self.assertIsNone(self.autocomplete.autocompletewindow)
def test_force_open_completions_event(self):
# Test that force_open_completions_event calls _open_completions
o_cs = Func()
self.autocomplete.open_completions = o_cs
self.autocomplete.force_open_completions_event('event')
self.assertEqual(o_cs.args, (True, False, True))
def test_try_open_completions_event(self):
Equal = self.assertEqual
autocomplete = self.autocomplete
trycompletions = self.autocomplete.try_open_completions_event
o_c_l = Func()
autocomplete._open_completions_later = o_c_l
# _open_completions_later should not be called with no text in editor
trycompletions('event')
Equal(o_c_l.args, None)
# _open_completions_later should be called with COMPLETE_ATTRIBUTES (1)
self.text.insert('1.0', 're.')
trycompletions('event')
Equal(o_c_l.args, (False, False, False, 1))
# _open_completions_later should be called with COMPLETE_FILES (2)
self.text.delete('1.0', 'end')
self.text.insert('1.0', '"./Lib/')
trycompletions('event')
Equal(o_c_l.args, (False, False, False, 2))
def test_autocomplete_event(self):
Equal = self.assertEqual
autocomplete = self.autocomplete
# Test that the autocomplete event is ignored if user is pressing a
# modifier key in addition to the tab key
ev = Event(mc_state=True)
self.assertIsNone(autocomplete.autocomplete_event(ev))
del ev.mc_state
# Test that tab after whitespace is ignored.
self.text.insert('1.0', ' """Docstring.\n ')
self.assertIsNone(autocomplete.autocomplete_event(ev))
self.text.delete('1.0', 'end')
# If autocomplete window is open, complete() method is called
self.text.insert('1.0', 're.')
# This must call autocomplete._make_autocomplete_window()
Equal(self.autocomplete.autocomplete_event(ev), 'break')
# If autocomplete window is not active or does not exist,
# open_completions is called. Return depends on its return.
autocomplete._remove_autocomplete_window()
o_cs = Func() # .result = None
autocomplete.open_completions = o_cs
Equal(self.autocomplete.autocomplete_event(ev), None)
Equal(o_cs.args, (False, True, True))
o_cs.result = True
Equal(self.autocomplete.autocomplete_event(ev), 'break')
Equal(o_cs.args, (False, True, True))
def test_open_completions_later(self):
# Test that autocomplete._delayed_completion_id is set
pass
def test_delayed_open_completions(self):
# Test that autocomplete._delayed_completion_id set to None and that
# open_completions only called if insertion index is the same as
# _delayed_completion_index
pass
def test_open_completions(self):
# Test completions of files and attributes as well as non-completion
# of errors
pass
def test_fetch_completions(self):
# Test that fetch_completions returns 2 lists:
# For attribute completion, a large list containing all variables, and
# a small list containing non-private variables.
# For file completion, a large list containing all files in the path,
# and a small list containing files that do not start with '.'
pass
def test_get_entity(self):
# Test that a name is in the namespace of sys.modules and
# __main__.__dict__
pass
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,32 @@
"Test autocomplete_w, coverage 11%."
import unittest
from test.support import requires
from tkinter import Tk, Text
import idlelib.autocomplete_w as acw
class AutoCompleteWindowTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.text = Text(cls.root)
cls.acw = acw.AutoCompleteWindow(cls.text)
@classmethod
def tearDownClass(cls):
del cls.text, cls.acw
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
def test_init(self):
self.assertEqual(self.acw.widget, self.text)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,155 @@
"Test autoexpand, coverage 100%."
from idlelib.autoexpand import AutoExpand
import unittest
from test.support import requires
from tkinter import Text, Tk
class Dummy_Editwin:
# AutoExpand.__init__ only needs .text
def __init__(self, text):
self.text = text
class AutoExpandTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.tk = Tk()
cls.text = Text(cls.tk)
cls.auto_expand = AutoExpand(Dummy_Editwin(cls.text))
cls.auto_expand.bell = lambda: None
# If mock_tk.Text._decode understood indexes 'insert' with suffixed 'linestart',
# 'wordstart', and 'lineend', used by autoexpand, we could use the following
# to run these test on non-gui machines (but check bell).
## try:
## requires('gui')
## #raise ResourceDenied() # Uncomment to test mock.
## except ResourceDenied:
## from idlelib.idle_test.mock_tk import Text
## cls.text = Text()
## cls.text.bell = lambda: None
## else:
## from tkinter import Tk, Text
## cls.tk = Tk()
## cls.text = Text(cls.tk)
@classmethod
def tearDownClass(cls):
del cls.text, cls.auto_expand
if hasattr(cls, 'tk'):
cls.tk.destroy()
del cls.tk
def tearDown(self):
self.text.delete('1.0', 'end')
def test_get_prevword(self):
text = self.text
previous = self.auto_expand.getprevword
equal = self.assertEqual
equal(previous(), '')
text.insert('insert', 't')
equal(previous(), 't')
text.insert('insert', 'his')
equal(previous(), 'this')
text.insert('insert', ' ')
equal(previous(), '')
text.insert('insert', 'is')
equal(previous(), 'is')
text.insert('insert', '\nsample\nstring')
equal(previous(), 'string')
text.delete('3.0', 'insert')
equal(previous(), '')
text.delete('1.0', 'end')
equal(previous(), '')
def test_before_only(self):
previous = self.auto_expand.getprevword
expand = self.auto_expand.expand_word_event
equal = self.assertEqual
self.text.insert('insert', 'ab ac bx ad ab a')
equal(self.auto_expand.getwords(), ['ab', 'ad', 'ac', 'a'])
expand('event')
equal(previous(), 'ab')
expand('event')
equal(previous(), 'ad')
expand('event')
equal(previous(), 'ac')
expand('event')
equal(previous(), 'a')
def test_after_only(self):
# Also add punctuation 'noise' that should be ignored.
text = self.text
previous = self.auto_expand.getprevword
expand = self.auto_expand.expand_word_event
equal = self.assertEqual
text.insert('insert', 'a, [ab] ac: () bx"" cd ac= ad ya')
text.mark_set('insert', '1.1')
equal(self.auto_expand.getwords(), ['ab', 'ac', 'ad', 'a'])
expand('event')
equal(previous(), 'ab')
expand('event')
equal(previous(), 'ac')
expand('event')
equal(previous(), 'ad')
expand('event')
equal(previous(), 'a')
def test_both_before_after(self):
text = self.text
previous = self.auto_expand.getprevword
expand = self.auto_expand.expand_word_event
equal = self.assertEqual
text.insert('insert', 'ab xy yz\n')
text.insert('insert', 'a ac by ac')
text.mark_set('insert', '2.1')
equal(self.auto_expand.getwords(), ['ab', 'ac', 'a'])
expand('event')
equal(previous(), 'ab')
expand('event')
equal(previous(), 'ac')
expand('event')
equal(previous(), 'a')
def test_other_expand_cases(self):
text = self.text
expand = self.auto_expand.expand_word_event
equal = self.assertEqual
# no expansion candidate found
equal(self.auto_expand.getwords(), [])
equal(expand('event'), 'break')
text.insert('insert', 'bx cy dz a')
equal(self.auto_expand.getwords(), [])
# reset state by successfully expanding once
# move cursor to another position and expand again
text.insert('insert', 'ac xy a ac ad a')
text.mark_set('insert', '1.7')
expand('event')
initial_state = self.auto_expand.state
text.mark_set('insert', '1.end')
expand('event')
new_state = self.auto_expand.state
self.assertNotEqual(initial_state, new_state)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,248 @@
"Test browser, coverage 90%."
from idlelib import browser
from test.support import requires
import unittest
from unittest import mock
from idlelib.idle_test.mock_idle import Func
from collections import deque
import os.path
from idlelib import _pyclbr as pyclbr
from tkinter import Tk
from idlelib.tree import TreeNode
class ModuleBrowserTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.mb = browser.ModuleBrowser(cls.root, __file__, _utest=True)
@classmethod
def tearDownClass(cls):
cls.mb.close()
cls.root.update_idletasks()
cls.root.destroy()
del cls.root, cls.mb
def test_init(self):
mb = self.mb
eq = self.assertEqual
eq(mb.path, __file__)
eq(pyclbr._modules, {})
self.assertIsInstance(mb.node, TreeNode)
self.assertIsNotNone(browser.file_open)
def test_settitle(self):
mb = self.mb
self.assertIn(os.path.basename(__file__), mb.top.title())
self.assertEqual(mb.top.iconname(), 'Module Browser')
def test_rootnode(self):
mb = self.mb
rn = mb.rootnode()
self.assertIsInstance(rn, browser.ModuleBrowserTreeItem)
def test_close(self):
mb = self.mb
mb.top.destroy = Func()
mb.node.destroy = Func()
mb.close()
self.assertTrue(mb.top.destroy.called)
self.assertTrue(mb.node.destroy.called)
del mb.top.destroy, mb.node.destroy
# Nested tree same as in test_pyclbr.py except for supers on C0. C1.
mb = pyclbr
module, fname = 'test', 'test.py'
f0 = mb.Function(module, 'f0', fname, 1)
f1 = mb._nest_function(f0, 'f1', 2)
f2 = mb._nest_function(f1, 'f2', 3)
c1 = mb._nest_class(f0, 'c1', 5)
C0 = mb.Class(module, 'C0', ['base'], fname, 6)
F1 = mb._nest_function(C0, 'F1', 8)
C1 = mb._nest_class(C0, 'C1', 11, [''])
C2 = mb._nest_class(C1, 'C2', 12)
F3 = mb._nest_function(C2, 'F3', 14)
mock_pyclbr_tree = {'f0': f0, 'C0': C0}
# Adjust C0.name, C1.name so tests do not depend on order.
browser.transform_children(mock_pyclbr_tree, 'test') # C0(base)
browser.transform_children(C0.children) # C1()
# The class below checks that the calls above are correct
# and that duplicate calls have no effect.
class TransformChildrenTest(unittest.TestCase):
def test_transform_module_children(self):
eq = self.assertEqual
transform = browser.transform_children
# Parameter matches tree module.
tcl = list(transform(mock_pyclbr_tree, 'test'))
eq(tcl, [f0, C0])
eq(tcl[0].name, 'f0')
eq(tcl[1].name, 'C0(base)')
# Check that second call does not change suffix.
tcl = list(transform(mock_pyclbr_tree, 'test'))
eq(tcl[1].name, 'C0(base)')
# Nothing to traverse if parameter name isn't same as tree module.
tcl = list(transform(mock_pyclbr_tree, 'different name'))
eq(tcl, [])
def test_transform_node_children(self):
eq = self.assertEqual
transform = browser.transform_children
# Class with two children, one name altered.
tcl = list(transform(C0.children))
eq(tcl, [F1, C1])
eq(tcl[0].name, 'F1')
eq(tcl[1].name, 'C1()')
tcl = list(transform(C0.children))
eq(tcl[1].name, 'C1()')
# Function with two children.
eq(list(transform(f0.children)), [f1, c1])
class ModuleBrowserTreeItemTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.mbt = browser.ModuleBrowserTreeItem(fname)
def test_init(self):
self.assertEqual(self.mbt.file, fname)
def test_gettext(self):
self.assertEqual(self.mbt.GetText(), fname)
def test_geticonname(self):
self.assertEqual(self.mbt.GetIconName(), 'python')
def test_isexpandable(self):
self.assertTrue(self.mbt.IsExpandable())
def test_listchildren(self):
save_rex = browser.pyclbr.readmodule_ex
save_tc = browser.transform_children
browser.pyclbr.readmodule_ex = Func(result=mock_pyclbr_tree)
browser.transform_children = Func(result=[f0, C0])
try:
self.assertEqual(self.mbt.listchildren(), [f0, C0])
finally:
browser.pyclbr.readmodule_ex = save_rex
browser.transform_children = save_tc
def test_getsublist(self):
mbt = self.mbt
mbt.listchildren = Func(result=[f0, C0])
sub0, sub1 = mbt.GetSubList()
del mbt.listchildren
self.assertIsInstance(sub0, browser.ChildBrowserTreeItem)
self.assertIsInstance(sub1, browser.ChildBrowserTreeItem)
self.assertEqual(sub0.name, 'f0')
self.assertEqual(sub1.name, 'C0(base)')
@mock.patch('idlelib.browser.file_open')
def test_ondoubleclick(self, fopen):
mbt = self.mbt
with mock.patch('os.path.exists', return_value=False):
mbt.OnDoubleClick()
fopen.assert_not_called()
with mock.patch('os.path.exists', return_value=True):
mbt.OnDoubleClick()
fopen.assert_called()
fopen.called_with(fname)
class ChildBrowserTreeItemTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
CBT = browser.ChildBrowserTreeItem
cls.cbt_f1 = CBT(f1)
cls.cbt_C1 = CBT(C1)
cls.cbt_F1 = CBT(F1)
@classmethod
def tearDownClass(cls):
del cls.cbt_C1, cls.cbt_f1, cls.cbt_F1
def test_init(self):
eq = self.assertEqual
eq(self.cbt_C1.name, 'C1()')
self.assertFalse(self.cbt_C1.isfunction)
eq(self.cbt_f1.name, 'f1')
self.assertTrue(self.cbt_f1.isfunction)
def test_gettext(self):
self.assertEqual(self.cbt_C1.GetText(), 'class C1()')
self.assertEqual(self.cbt_f1.GetText(), 'def f1(...)')
def test_geticonname(self):
self.assertEqual(self.cbt_C1.GetIconName(), 'folder')
self.assertEqual(self.cbt_f1.GetIconName(), 'python')
def test_isexpandable(self):
self.assertTrue(self.cbt_C1.IsExpandable())
self.assertTrue(self.cbt_f1.IsExpandable())
self.assertFalse(self.cbt_F1.IsExpandable())
def test_getsublist(self):
eq = self.assertEqual
CBT = browser.ChildBrowserTreeItem
f1sublist = self.cbt_f1.GetSubList()
self.assertIsInstance(f1sublist[0], CBT)
eq(len(f1sublist), 1)
eq(f1sublist[0].name, 'f2')
eq(self.cbt_F1.GetSubList(), [])
@mock.patch('idlelib.browser.file_open')
def test_ondoubleclick(self, fopen):
goto = fopen.return_value.gotoline = mock.Mock()
self.cbt_F1.OnDoubleClick()
fopen.assert_called()
goto.assert_called()
goto.assert_called_with(self.cbt_F1.obj.lineno)
# Failure test would have to raise OSError or AttributeError.
class NestedChildrenTest(unittest.TestCase):
"Test that all the nodes in a nested tree are added to the BrowserTree."
def test_nested(self):
queue = deque()
actual_names = []
# The tree items are processed in breadth first order.
# Verify that processing each sublist hits every node and
# in the right order.
expected_names = ['f0', 'C0(base)',
'f1', 'c1', 'F1', 'C1()',
'f2', 'C2',
'F3']
CBT = browser.ChildBrowserTreeItem
queue.extend((CBT(f0), CBT(C0)))
while queue:
cb = queue.popleft()
sublist = cb.GetSubList()
queue.extend(sublist)
self.assertIn(cb.name, cb.GetText())
self.assertIn(cb.GetIconName(), ('python', 'folder'))
self.assertIs(cb.IsExpandable(), sublist != [])
actual_names.append(cb.name)
self.assertEqual(actual_names, expected_names)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,216 @@
"Test calltip, coverage 60%"
from idlelib import calltip
import unittest
import textwrap
import types
default_tip = calltip._default_callable_argspec
# Test Class TC is used in multiple get_argspec test methods
class TC():
'doc'
tip = "(ai=None, *b)"
def __init__(self, ai=None, *b): 'doc'
__init__.tip = "(self, ai=None, *b)"
def t1(self): 'doc'
t1.tip = "(self)"
def t2(self, ai, b=None): 'doc'
t2.tip = "(self, ai, b=None)"
def t3(self, ai, *args): 'doc'
t3.tip = "(self, ai, *args)"
def t4(self, *args): 'doc'
t4.tip = "(self, *args)"
def t5(self, ai, b=None, *args, **kw): 'doc'
t5.tip = "(self, ai, b=None, *args, **kw)"
def t6(no, self): 'doc'
t6.tip = "(no, self)"
def __call__(self, ci): 'doc'
__call__.tip = "(self, ci)"
# attaching .tip to wrapped methods does not work
@classmethod
def cm(cls, a): 'doc'
@staticmethod
def sm(b): 'doc'
tc = TC()
signature = calltip.get_argspec # 2.7 and 3.x use different functions
class Get_signatureTest(unittest.TestCase):
# The signature function must return a string, even if blank.
# Test a variety of objects to be sure that none cause it to raise
# (quite aside from getting as correct an answer as possible).
# The tests of builtins may break if inspect or the docstrings change,
# but a red buildbot is better than a user crash (as has happened).
# For a simple mismatch, change the expected output to the actual.
def test_builtins(self):
# Python class that inherits builtin methods
class List(list): "List() doc"
# Simulate builtin with no docstring for default tip test
class SB: __call__ = None
def gtest(obj, out):
self.assertEqual(signature(obj), out)
if List.__doc__ is not None:
gtest(List, List.__doc__) # This and append_doc changed in 3.7.
gtest(list.__new__,
'(*args, **kwargs)\n'
'Create and return a new object.'
' See help(type) for accurate signature.')
gtest(list.__init__,
'(self, /, *args, **kwargs)'
+ calltip._argument_positional + '\n' +
'Initialize self. See help(type(self)) for accurate signature.')
append_doc = "L.append(object) -> None -- append object to end"
gtest(list.append, append_doc)
gtest([].append, append_doc)
gtest(List.append, append_doc)
gtest(types.MethodType, "method(function, instance)")
gtest(SB(), default_tip)
import re
p = re.compile('')
gtest(re.sub, '''\
(pattern, repl, string, count=0, flags=0)
Return the string obtained by replacing the leftmost
non-overlapping occurrences of the pattern in string by the
replacement repl. repl can be either a string or a callable;
if a string, backslash escapes in it are processed. If it is
a callable, it's passed the match object and must return''')
gtest(p.sub, '''\
(repl, string, count=0)
Return the string obtained by replacing the leftmost \
non-overlapping occurrences o...''')
def test_signature_wrap(self):
if textwrap.TextWrapper.__doc__ is not None:
self.assertEqual(signature(textwrap.TextWrapper), '''\
(width=70, initial_indent='', subsequent_indent='', expand_tabs=True,
replace_whitespace=True, fix_sentence_endings=False, break_long_words=True,
drop_whitespace=True, break_on_hyphens=True, tabsize=8, *, max_lines=None,
placeholder=' [...]')''')
def test_docline_truncation(self):
def f(): pass
f.__doc__ = 'a'*300
self.assertEqual(signature(f), '()\n' + 'a' * (calltip._MAX_COLS-3) + '...')
def test_multiline_docstring(self):
# Test fewer lines than max.
self.assertEqual(signature(range),
"range(stop) -> range object\n"
"range(start, stop[, step]) -> range object")
# Test max lines
self.assertEqual(signature(bytes), '''\
bytes(iterable_of_ints) -> bytes
bytes(string, encoding[, errors]) -> bytes
bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer
bytes(int) -> bytes object of size given by the parameter initialized with null bytes
bytes() -> empty bytes object''')
# Test more than max lines
def f(): pass
f.__doc__ = 'a\n' * 15
self.assertEqual(signature(f), '()' + '\na' * calltip._MAX_LINES)
def test_functions(self):
def t1(): 'doc'
t1.tip = "()"
def t2(a, b=None): 'doc'
t2.tip = "(a, b=None)"
def t3(a, *args): 'doc'
t3.tip = "(a, *args)"
def t4(*args): 'doc'
t4.tip = "(*args)"
def t5(a, b=None, *args, **kw): 'doc'
t5.tip = "(a, b=None, *args, **kw)"
doc = '\ndoc' if t1.__doc__ is not None else ''
for func in (t1, t2, t3, t4, t5, TC):
self.assertEqual(signature(func), func.tip + doc)
def test_methods(self):
doc = '\ndoc' if TC.__doc__ is not None else ''
for meth in (TC.t1, TC.t2, TC.t3, TC.t4, TC.t5, TC.t6, TC.__call__):
self.assertEqual(signature(meth), meth.tip + doc)
self.assertEqual(signature(TC.cm), "(a)" + doc)
self.assertEqual(signature(TC.sm), "(b)" + doc)
def test_bound_methods(self):
# test that first parameter is correctly removed from argspec
doc = '\ndoc' if TC.__doc__ is not None else ''
for meth, mtip in ((tc.t1, "()"), (tc.t4, "(*args)"),
(tc.t6, "(self)"), (tc.__call__, '(ci)'),
(tc, '(ci)'), (TC.cm, "(a)"),):
self.assertEqual(signature(meth), mtip + doc)
def test_starred_parameter(self):
# test that starred first parameter is *not* removed from argspec
class C:
def m1(*args): pass
c = C()
for meth, mtip in ((C.m1, '(*args)'), (c.m1, "(*args)"),):
self.assertEqual(signature(meth), mtip)
def test_invalid_method_signature(self):
class C:
def m2(**kwargs): pass
class Test:
def __call__(*, a): pass
mtip = calltip._invalid_method
self.assertEqual(signature(C().m2), mtip)
self.assertEqual(signature(Test()), mtip)
def test_non_ascii_name(self):
# test that re works to delete a first parameter name that
# includes non-ascii chars, such as various forms of A.
uni = "(A\u0391\u0410\u05d0\u0627\u0905\u1e00\u3042, a)"
assert calltip._first_param.sub('', uni) == '(a)'
def test_no_docstring(self):
def nd(s):
pass
TC.nd = nd
self.assertEqual(signature(nd), "(s)")
self.assertEqual(signature(TC.nd), "(s)")
self.assertEqual(signature(tc.nd), "()")
def test_attribute_exception(self):
class NoCall:
def __getattr__(self, name):
raise BaseException
class CallA(NoCall):
def __call__(oui, a, b, c):
pass
class CallB(NoCall):
def __call__(self, ci):
pass
for meth, mtip in ((NoCall, default_tip), (CallA, default_tip),
(NoCall(), ''), (CallA(), '(a, b, c)'),
(CallB(), '(ci)')):
self.assertEqual(signature(meth), mtip)
def test_non_callables(self):
for obj in (0, 0.0, '0', b'0', [], {}):
self.assertEqual(signature(obj), '')
class Get_entityTest(unittest.TestCase):
def test_bad_entity(self):
self.assertIsNone(calltip.get_entity('1/0'))
def test_good_entity(self):
self.assertIs(calltip.get_entity('int'), int)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,29 @@
"Test calltip_w, coverage 18%."
from idlelib import calltip_w
import unittest
from test.support import requires
from tkinter import Tk, Text
class CallTipWindowTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.text = Text(cls.root)
cls.calltip = calltip_w.CalltipWindow(cls.text)
@classmethod
def tearDownClass(cls):
cls.root.update_idletasks()
cls.root.destroy()
del cls.text, cls.root
def test_init(self):
self.assertEqual(self.calltip.anchor_widget, self.text)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,403 @@
"Test codecontext, coverage 100%"
from idlelib import codecontext
import unittest
from test.support import requires
from tkinter import Tk, Frame, Text, TclError
from unittest import mock
import re
from idlelib import config
usercfg = codecontext.idleConf.userCfg
testcfg = {
'main': config.IdleUserConfParser(''),
'highlight': config.IdleUserConfParser(''),
'keys': config.IdleUserConfParser(''),
'extensions': config.IdleUserConfParser(''),
}
code_sample = """\
class C1():
# Class comment.
def __init__(self, a, b):
self.a = a
self.b = b
def compare(self):
if a > b:
return a
elif a < b:
return b
else:
return None
"""
class DummyEditwin:
def __init__(self, root, frame, text):
self.root = root
self.top = root
self.text_frame = frame
self.text = text
class CodeContextTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
root = cls.root = Tk()
root.withdraw()
frame = cls.frame = Frame(root)
text = cls.text = Text(frame)
text.insert('1.0', code_sample)
# Need to pack for creation of code context text widget.
frame.pack(side='left', fill='both', expand=1)
text.pack(side='top', fill='both', expand=1)
cls.editor = DummyEditwin(root, frame, text)
codecontext.idleConf.userCfg = testcfg
@classmethod
def tearDownClass(cls):
codecontext.idleConf.userCfg = usercfg
cls.editor.text.delete('1.0', 'end')
del cls.editor, cls.frame, cls.text
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
def setUp(self):
self.text.yview(0)
self.cc = codecontext.CodeContext(self.editor)
def tearDown(self):
if self.cc.context:
self.cc.context.destroy()
# Explicitly call __del__ to remove scheduled scripts.
self.cc.__del__()
del self.cc.context, self.cc
def test_init(self):
eq = self.assertEqual
ed = self.editor
cc = self.cc
eq(cc.editwin, ed)
eq(cc.text, ed.text)
eq(cc.textfont, ed.text['font'])
self.assertIsNone(cc.context)
eq(cc.info, [(0, -1, '', False)])
eq(cc.topvisible, 1)
eq(self.root.tk.call('after', 'info', self.cc.t1)[1], 'timer')
eq(self.root.tk.call('after', 'info', self.cc.t2)[1], 'timer')
def test_del(self):
self.cc.__del__()
with self.assertRaises(TclError) as msg:
self.root.tk.call('after', 'info', self.cc.t1)
self.assertIn("doesn't exist", msg)
with self.assertRaises(TclError) as msg:
self.root.tk.call('after', 'info', self.cc.t2)
self.assertIn("doesn't exist", msg)
# For coverage on the except. Have to delete because the
# above Tcl error is caught by after_cancel.
del self.cc.t1, self.cc.t2
self.cc.__del__()
def test_reload(self):
codecontext.CodeContext.reload()
self.assertEqual(self.cc.colors, {'background': 'lightgray',
'foreground': '#000000'})
self.assertEqual(self.cc.context_depth, 15)
def test_toggle_code_context_event(self):
eq = self.assertEqual
cc = self.cc
toggle = cc.toggle_code_context_event
# Make sure code context is off.
if cc.context:
toggle()
# Toggle on.
eq(toggle(), 'break')
self.assertIsNotNone(cc.context)
eq(cc.context['font'], cc.textfont)
eq(cc.context['fg'], cc.colors['foreground'])
eq(cc.context['bg'], cc.colors['background'])
eq(cc.context.get('1.0', 'end-1c'), '')
# Toggle off.
eq(toggle(), 'break')
self.assertIsNone(cc.context)
def test_get_context(self):
eq = self.assertEqual
gc = self.cc.get_context
# stopline must be greater than 0.
with self.assertRaises(AssertionError):
gc(1, stopline=0)
eq(gc(3), ([(2, 0, 'class C1():', 'class')], 0))
# Don't return comment.
eq(gc(4), ([(2, 0, 'class C1():', 'class')], 0))
# Two indentation levels and no comment.
eq(gc(5), ([(2, 0, 'class C1():', 'class'),
(4, 4, ' def __init__(self, a, b):', 'def')], 0))
# Only one 'def' is returned, not both at the same indent level.
eq(gc(10), ([(2, 0, 'class C1():', 'class'),
(7, 4, ' def compare(self):', 'def'),
(8, 8, ' if a > b:', 'if')], 0))
# With 'elif', also show the 'if' even though it's at the same level.
eq(gc(11), ([(2, 0, 'class C1():', 'class'),
(7, 4, ' def compare(self):', 'def'),
(8, 8, ' if a > b:', 'if'),
(10, 8, ' elif a < b:', 'elif')], 0))
# Set stop_line to not go back to first line in source code.
# Return includes stop_line.
eq(gc(11, stopline=2), ([(2, 0, 'class C1():', 'class'),
(7, 4, ' def compare(self):', 'def'),
(8, 8, ' if a > b:', 'if'),
(10, 8, ' elif a < b:', 'elif')], 0))
eq(gc(11, stopline=3), ([(7, 4, ' def compare(self):', 'def'),
(8, 8, ' if a > b:', 'if'),
(10, 8, ' elif a < b:', 'elif')], 4))
eq(gc(11, stopline=8), ([(8, 8, ' if a > b:', 'if'),
(10, 8, ' elif a < b:', 'elif')], 8))
# Set stop_indent to test indent level to stop at.
eq(gc(11, stopindent=4), ([(7, 4, ' def compare(self):', 'def'),
(8, 8, ' if a > b:', 'if'),
(10, 8, ' elif a < b:', 'elif')], 4))
# Check that the 'if' is included.
eq(gc(11, stopindent=8), ([(8, 8, ' if a > b:', 'if'),
(10, 8, ' elif a < b:', 'elif')], 8))
def test_update_code_context(self):
eq = self.assertEqual
cc = self.cc
# Ensure code context is active.
if not cc.context:
cc.toggle_code_context_event()
# Invoke update_code_context without scrolling - nothing happens.
self.assertIsNone(cc.update_code_context())
eq(cc.info, [(0, -1, '', False)])
eq(cc.topvisible, 1)
# Scroll down to line 1.
cc.text.yview(1)
cc.update_code_context()
eq(cc.info, [(0, -1, '', False)])
eq(cc.topvisible, 2)
eq(cc.context.get('1.0', 'end-1c'), '')
# Scroll down to line 2.
cc.text.yview(2)
cc.update_code_context()
eq(cc.info, [(0, -1, '', False), (2, 0, 'class C1():', 'class')])
eq(cc.topvisible, 3)
eq(cc.context.get('1.0', 'end-1c'), 'class C1():')
# Scroll down to line 3. Since it's a comment, nothing changes.
cc.text.yview(3)
cc.update_code_context()
eq(cc.info, [(0, -1, '', False), (2, 0, 'class C1():', 'class')])
eq(cc.topvisible, 4)
eq(cc.context.get('1.0', 'end-1c'), 'class C1():')
# Scroll down to line 4.
cc.text.yview(4)
cc.update_code_context()
eq(cc.info, [(0, -1, '', False),
(2, 0, 'class C1():', 'class'),
(4, 4, ' def __init__(self, a, b):', 'def')])
eq(cc.topvisible, 5)
eq(cc.context.get('1.0', 'end-1c'), 'class C1():\n'
' def __init__(self, a, b):')
# Scroll down to line 11. Last 'def' is removed.
cc.text.yview(11)
cc.update_code_context()
eq(cc.info, [(0, -1, '', False),
(2, 0, 'class C1():', 'class'),
(7, 4, ' def compare(self):', 'def'),
(8, 8, ' if a > b:', 'if'),
(10, 8, ' elif a < b:', 'elif')])
eq(cc.topvisible, 12)
eq(cc.context.get('1.0', 'end-1c'), 'class C1():\n'
' def compare(self):\n'
' if a > b:\n'
' elif a < b:')
# No scroll. No update, even though context_depth changed.
cc.update_code_context()
cc.context_depth = 1
eq(cc.info, [(0, -1, '', False),
(2, 0, 'class C1():', 'class'),
(7, 4, ' def compare(self):', 'def'),
(8, 8, ' if a > b:', 'if'),
(10, 8, ' elif a < b:', 'elif')])
eq(cc.topvisible, 12)
eq(cc.context.get('1.0', 'end-1c'), 'class C1():\n'
' def compare(self):\n'
' if a > b:\n'
' elif a < b:')
# Scroll up.
cc.text.yview(5)
cc.update_code_context()
eq(cc.info, [(0, -1, '', False),
(2, 0, 'class C1():', 'class'),
(4, 4, ' def __init__(self, a, b):', 'def')])
eq(cc.topvisible, 6)
# context_depth is 1.
eq(cc.context.get('1.0', 'end-1c'), ' def __init__(self, a, b):')
def test_jumptoline(self):
eq = self.assertEqual
cc = self.cc
jump = cc.jumptoline
if not cc.context:
cc.toggle_code_context_event()
# Empty context.
cc.text.yview(f'{2}.0')
cc.update_code_context()
eq(cc.topvisible, 2)
cc.context.mark_set('insert', '1.5')
jump()
eq(cc.topvisible, 1)
# 4 lines of context showing.
cc.text.yview(f'{12}.0')
cc.update_code_context()
eq(cc.topvisible, 12)
cc.context.mark_set('insert', '3.0')
jump()
eq(cc.topvisible, 8)
# More context lines than limit.
cc.context_depth = 2
cc.text.yview(f'{12}.0')
cc.update_code_context()
eq(cc.topvisible, 12)
cc.context.mark_set('insert', '1.0')
jump()
eq(cc.topvisible, 8)
@mock.patch.object(codecontext.CodeContext, 'update_code_context')
def test_timer_event(self, mock_update):
# Ensure code context is not active.
if self.cc.context:
self.cc.toggle_code_context_event()
self.cc.timer_event()
mock_update.assert_not_called()
# Activate code context.
self.cc.toggle_code_context_event()
self.cc.timer_event()
mock_update.assert_called()
def test_config_timer_event(self):
eq = self.assertEqual
cc = self.cc
save_font = cc.text['font']
save_colors = codecontext.CodeContext.colors
test_font = 'FakeFont'
test_colors = {'background': '#222222', 'foreground': '#ffff00'}
# Ensure code context is not active.
if cc.context:
cc.toggle_code_context_event()
# Nothing updates on inactive code context.
cc.text['font'] = test_font
codecontext.CodeContext.colors = test_colors
cc.config_timer_event()
eq(cc.textfont, save_font)
eq(cc.contextcolors, save_colors)
# Activate code context, but no change to font or color.
cc.toggle_code_context_event()
cc.text['font'] = save_font
codecontext.CodeContext.colors = save_colors
cc.config_timer_event()
eq(cc.textfont, save_font)
eq(cc.contextcolors, save_colors)
eq(cc.context['font'], save_font)
eq(cc.context['background'], save_colors['background'])
eq(cc.context['foreground'], save_colors['foreground'])
# Active code context, change font.
cc.text['font'] = test_font
cc.config_timer_event()
eq(cc.textfont, test_font)
eq(cc.contextcolors, save_colors)
eq(cc.context['font'], test_font)
eq(cc.context['background'], save_colors['background'])
eq(cc.context['foreground'], save_colors['foreground'])
# Active code context, change color.
cc.text['font'] = save_font
codecontext.CodeContext.colors = test_colors
cc.config_timer_event()
eq(cc.textfont, save_font)
eq(cc.contextcolors, test_colors)
eq(cc.context['font'], save_font)
eq(cc.context['background'], test_colors['background'])
eq(cc.context['foreground'], test_colors['foreground'])
codecontext.CodeContext.colors = save_colors
cc.config_timer_event()
class HelperFunctionText(unittest.TestCase):
def test_get_spaces_firstword(self):
get = codecontext.get_spaces_firstword
test_lines = (
(' first word', (' ', 'first')),
('\tfirst word', ('\t', 'first')),
(' \u19D4\u19D2: ', (' ', '\u19D4\u19D2')),
('no spaces', ('', 'no')),
('', ('', '')),
('# TEST COMMENT', ('', '')),
(' (continuation)', (' ', ''))
)
for line, expected_output in test_lines:
self.assertEqual(get(line), expected_output)
# Send the pattern in the call.
self.assertEqual(get(' (continuation)',
c=re.compile(r'^(\s*)([^\s]*)')),
(' ', '(continuation)'))
def test_get_line_info(self):
eq = self.assertEqual
gli = codecontext.get_line_info
lines = code_sample.splitlines()
# Line 1 is not a BLOCKOPENER.
eq(gli(lines[0]), (codecontext.INFINITY, '', False))
# Line 2 is a BLOCKOPENER without an indent.
eq(gli(lines[1]), (0, 'class C1():', 'class'))
# Line 3 is not a BLOCKOPENER and does not return the indent level.
eq(gli(lines[2]), (codecontext.INFINITY, ' # Class comment.', False))
# Line 4 is a BLOCKOPENER and is indented.
eq(gli(lines[3]), (4, ' def __init__(self, a, b):', 'def'))
# Line 8 is a different BLOCKOPENER and is indented.
eq(gli(lines[7]), (8, ' if a > b:', 'if'))
# Test tab.
eq(gli('\tif a == b:'), (1, '\tif a == b:', 'if'))
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,53 @@
"Test colorizer, coverage 25%."
from idlelib import colorizer
from test.support import requires
from tkinter import Tk, Text
import unittest
class FunctionTest(unittest.TestCase):
def test_any(self):
self.assertTrue(colorizer.any('test', ('a', 'b')))
def test_make_pat(self):
self.assertTrue(colorizer.make_pat())
class ColorConfigTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.text = Text(cls.root)
@classmethod
def tearDownClass(cls):
del cls.text
cls.root.destroy()
del cls.root
def test_colorizer(self):
colorizer.color_config(self.text)
class ColorDelegatorTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
@classmethod
def tearDownClass(cls):
cls.root.destroy()
del cls.root
def test_colorizer(self):
colorizer.ColorDelegator()
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,822 @@
"""Test config, coverage 93%.
(100% for IdleConfParser, IdleUserConfParser*, ConfigChanges).
* Exception is OSError clause in Save method.
Much of IdleConf is also exercised by ConfigDialog and test_configdialog.
"""
from idlelib import config
import sys
import os
import tempfile
from test.support import captured_stderr, findfile
import unittest
from unittest import mock
import idlelib
from idlelib.idle_test.mock_idle import Func
# Tests should not depend on fortuitous user configurations.
# They must not affect actual user .cfg files.
# Replace user parsers with empty parsers that cannot be saved
# due to getting '' as the filename when created.
idleConf = config.idleConf
usercfg = idleConf.userCfg
testcfg = {}
usermain = testcfg['main'] = config.IdleUserConfParser('')
userhigh = testcfg['highlight'] = config.IdleUserConfParser('')
userkeys = testcfg['keys'] = config.IdleUserConfParser('')
userextn = testcfg['extensions'] = config.IdleUserConfParser('')
def setUpModule():
idleConf.userCfg = testcfg
idlelib.testing = True
def tearDownModule():
idleConf.userCfg = usercfg
idlelib.testing = False
class IdleConfParserTest(unittest.TestCase):
"""Test that IdleConfParser works"""
config = """
[one]
one = false
two = true
three = 10
[two]
one = a string
two = true
three = false
"""
def test_get(self):
parser = config.IdleConfParser('')
parser.read_string(self.config)
eq = self.assertEqual
# Test with type argument.
self.assertIs(parser.Get('one', 'one', type='bool'), False)
self.assertIs(parser.Get('one', 'two', type='bool'), True)
eq(parser.Get('one', 'three', type='int'), 10)
eq(parser.Get('two', 'one'), 'a string')
self.assertIs(parser.Get('two', 'two', type='bool'), True)
self.assertIs(parser.Get('two', 'three', type='bool'), False)
# Test without type should fallback to string.
eq(parser.Get('two', 'two'), 'true')
eq(parser.Get('two', 'three'), 'false')
# If option not exist, should return None, or default.
self.assertIsNone(parser.Get('not', 'exist'))
eq(parser.Get('not', 'exist', default='DEFAULT'), 'DEFAULT')
def test_get_option_list(self):
parser = config.IdleConfParser('')
parser.read_string(self.config)
get_list = parser.GetOptionList
self.assertCountEqual(get_list('one'), ['one', 'two', 'three'])
self.assertCountEqual(get_list('two'), ['one', 'two', 'three'])
self.assertEqual(get_list('not exist'), [])
def test_load_nothing(self):
parser = config.IdleConfParser('')
parser.Load()
self.assertEqual(parser.sections(), [])
def test_load_file(self):
# Borrow test/cfgparser.1 from test_configparser.
config_path = findfile('cfgparser.1')
parser = config.IdleConfParser(config_path)
parser.Load()
self.assertEqual(parser.Get('Foo Bar', 'foo'), 'newbar')
self.assertEqual(parser.GetOptionList('Foo Bar'), ['foo'])
class IdleUserConfParserTest(unittest.TestCase):
"""Test that IdleUserConfParser works"""
def new_parser(self, path=''):
return config.IdleUserConfParser(path)
def test_set_option(self):
parser = self.new_parser()
parser.add_section('Foo')
# Setting new option in existing section should return True.
self.assertTrue(parser.SetOption('Foo', 'bar', 'true'))
# Setting existing option with same value should return False.
self.assertFalse(parser.SetOption('Foo', 'bar', 'true'))
# Setting exiting option with new value should return True.
self.assertTrue(parser.SetOption('Foo', 'bar', 'false'))
self.assertEqual(parser.Get('Foo', 'bar'), 'false')
# Setting option in new section should create section and return True.
self.assertTrue(parser.SetOption('Bar', 'bar', 'true'))
self.assertCountEqual(parser.sections(), ['Bar', 'Foo'])
self.assertEqual(parser.Get('Bar', 'bar'), 'true')
def test_remove_option(self):
parser = self.new_parser()
parser.AddSection('Foo')
parser.SetOption('Foo', 'bar', 'true')
self.assertTrue(parser.RemoveOption('Foo', 'bar'))
self.assertFalse(parser.RemoveOption('Foo', 'bar'))
self.assertFalse(parser.RemoveOption('Not', 'Exist'))
def test_add_section(self):
parser = self.new_parser()
self.assertEqual(parser.sections(), [])
# Should not add duplicate section.
# Configparser raises DuplicateError, IdleParser not.
parser.AddSection('Foo')
parser.AddSection('Foo')
parser.AddSection('Bar')
self.assertCountEqual(parser.sections(), ['Bar', 'Foo'])
def test_remove_empty_sections(self):
parser = self.new_parser()
parser.AddSection('Foo')
parser.AddSection('Bar')
parser.SetOption('Idle', 'name', 'val')
self.assertCountEqual(parser.sections(), ['Bar', 'Foo', 'Idle'])
parser.RemoveEmptySections()
self.assertEqual(parser.sections(), ['Idle'])
def test_is_empty(self):
parser = self.new_parser()
parser.AddSection('Foo')
parser.AddSection('Bar')
self.assertTrue(parser.IsEmpty())
self.assertEqual(parser.sections(), [])
parser.SetOption('Foo', 'bar', 'false')
parser.AddSection('Bar')
self.assertFalse(parser.IsEmpty())
self.assertCountEqual(parser.sections(), ['Foo'])
def test_remove_file(self):
with tempfile.TemporaryDirectory() as tdir:
path = os.path.join(tdir, 'test.cfg')
parser = self.new_parser(path)
parser.RemoveFile() # Should not raise exception.
parser.AddSection('Foo')
parser.SetOption('Foo', 'bar', 'true')
parser.Save()
self.assertTrue(os.path.exists(path))
parser.RemoveFile()
self.assertFalse(os.path.exists(path))
def test_save(self):
with tempfile.TemporaryDirectory() as tdir:
path = os.path.join(tdir, 'test.cfg')
parser = self.new_parser(path)
parser.AddSection('Foo')
parser.SetOption('Foo', 'bar', 'true')
# Should save to path when config is not empty.
self.assertFalse(os.path.exists(path))
parser.Save()
self.assertTrue(os.path.exists(path))
# Should remove the file from disk when config is empty.
parser.remove_section('Foo')
parser.Save()
self.assertFalse(os.path.exists(path))
class IdleConfTest(unittest.TestCase):
"""Test for idleConf"""
@classmethod
def setUpClass(cls):
cls.config_string = {}
conf = config.IdleConf(_utest=True)
if __name__ != '__main__':
idle_dir = os.path.dirname(__file__)
else:
idle_dir = os.path.abspath(sys.path[0])
for ctype in conf.config_types:
config_path = os.path.join(idle_dir, '../config-%s.def' % ctype)
with open(config_path, 'r') as f:
cls.config_string[ctype] = f.read()
cls.orig_warn = config._warn
config._warn = Func()
@classmethod
def tearDownClass(cls):
config._warn = cls.orig_warn
def new_config(self, _utest=False):
return config.IdleConf(_utest=_utest)
def mock_config(self):
"""Return a mocked idleConf
Both default and user config used the same config-*.def
"""
conf = config.IdleConf(_utest=True)
for ctype in conf.config_types:
conf.defaultCfg[ctype] = config.IdleConfParser('')
conf.defaultCfg[ctype].read_string(self.config_string[ctype])
conf.userCfg[ctype] = config.IdleUserConfParser('')
conf.userCfg[ctype].read_string(self.config_string[ctype])
return conf
@unittest.skipIf(sys.platform.startswith('win'), 'this is test for unix system')
def test_get_user_cfg_dir_unix(self):
"Test to get user config directory under unix"
conf = self.new_config(_utest=True)
# Check normal way should success
with mock.patch('os.path.expanduser', return_value='/home/foo'):
with mock.patch('os.path.exists', return_value=True):
self.assertEqual(conf.GetUserCfgDir(), '/home/foo/.idlerc')
# Check os.getcwd should success
with mock.patch('os.path.expanduser', return_value='~'):
with mock.patch('os.getcwd', return_value='/home/foo/cpython'):
with mock.patch('os.mkdir'):
self.assertEqual(conf.GetUserCfgDir(),
'/home/foo/cpython/.idlerc')
# Check user dir not exists and created failed should raise SystemExit
with mock.patch('os.path.join', return_value='/path/not/exists'):
with self.assertRaises(SystemExit):
with self.assertRaises(FileNotFoundError):
conf.GetUserCfgDir()
@unittest.skipIf(not sys.platform.startswith('win'), 'this is test for Windows system')
def test_get_user_cfg_dir_windows(self):
"Test to get user config directory under Windows"
conf = self.new_config(_utest=True)
# Check normal way should success
with mock.patch('os.path.expanduser', return_value='C:\\foo'):
with mock.patch('os.path.exists', return_value=True):
self.assertEqual(conf.GetUserCfgDir(), 'C:\\foo\\.idlerc')
# Check os.getcwd should success
with mock.patch('os.path.expanduser', return_value='~'):
with mock.patch('os.getcwd', return_value='C:\\foo\\cpython'):
with mock.patch('os.mkdir'):
self.assertEqual(conf.GetUserCfgDir(),
'C:\\foo\\cpython\\.idlerc')
# Check user dir not exists and created failed should raise SystemExit
with mock.patch('os.path.join', return_value='/path/not/exists'):
with self.assertRaises(SystemExit):
with self.assertRaises(FileNotFoundError):
conf.GetUserCfgDir()
def test_create_config_handlers(self):
conf = self.new_config(_utest=True)
# Mock out idle_dir
idle_dir = '/home/foo'
with mock.patch.dict({'__name__': '__foo__'}):
with mock.patch('os.path.dirname', return_value=idle_dir):
conf.CreateConfigHandlers()
# Check keys are equal
self.assertCountEqual(conf.defaultCfg.keys(), conf.config_types)
self.assertCountEqual(conf.userCfg.keys(), conf.config_types)
# Check conf parser are correct type
for default_parser in conf.defaultCfg.values():
self.assertIsInstance(default_parser, config.IdleConfParser)
for user_parser in conf.userCfg.values():
self.assertIsInstance(user_parser, config.IdleUserConfParser)
# Check config path are correct
for config_type, parser in conf.defaultCfg.items():
self.assertEqual(parser.file,
os.path.join(idle_dir, 'config-%s.def' % config_type))
for config_type, parser in conf.userCfg.items():
self.assertEqual(parser.file,
os.path.join(conf.userdir, 'config-%s.cfg' % config_type))
def test_load_cfg_files(self):
conf = self.new_config(_utest=True)
# Borrow test/cfgparser.1 from test_configparser.
config_path = findfile('cfgparser.1')
conf.defaultCfg['foo'] = config.IdleConfParser(config_path)
conf.userCfg['foo'] = config.IdleUserConfParser(config_path)
# Load all config from path
conf.LoadCfgFiles()
eq = self.assertEqual
# Check defaultCfg is loaded
eq(conf.defaultCfg['foo'].Get('Foo Bar', 'foo'), 'newbar')
eq(conf.defaultCfg['foo'].GetOptionList('Foo Bar'), ['foo'])
# Check userCfg is loaded
eq(conf.userCfg['foo'].Get('Foo Bar', 'foo'), 'newbar')
eq(conf.userCfg['foo'].GetOptionList('Foo Bar'), ['foo'])
def test_save_user_cfg_files(self):
conf = self.mock_config()
with mock.patch('idlelib.config.IdleUserConfParser.Save') as m:
conf.SaveUserCfgFiles()
self.assertEqual(m.call_count, len(conf.userCfg))
def test_get_option(self):
conf = self.mock_config()
eq = self.assertEqual
eq(conf.GetOption('main', 'EditorWindow', 'width'), '80')
eq(conf.GetOption('main', 'EditorWindow', 'width', type='int'), 80)
with mock.patch('idlelib.config._warn') as _warn:
eq(conf.GetOption('main', 'EditorWindow', 'font', type='int'), None)
eq(conf.GetOption('main', 'EditorWindow', 'NotExists'), None)
eq(conf.GetOption('main', 'EditorWindow', 'NotExists', default='NE'), 'NE')
eq(_warn.call_count, 4)
def test_set_option(self):
conf = self.mock_config()
conf.SetOption('main', 'Foo', 'bar', 'newbar')
self.assertEqual(conf.GetOption('main', 'Foo', 'bar'), 'newbar')
def test_get_section_list(self):
conf = self.mock_config()
self.assertCountEqual(
conf.GetSectionList('default', 'main'),
['General', 'EditorWindow', 'PyShell', 'Indent', 'Theme',
'Keys', 'History', 'HelpFiles'])
self.assertCountEqual(
conf.GetSectionList('user', 'main'),
['General', 'EditorWindow', 'PyShell', 'Indent', 'Theme',
'Keys', 'History', 'HelpFiles'])
with self.assertRaises(config.InvalidConfigSet):
conf.GetSectionList('foobar', 'main')
with self.assertRaises(config.InvalidConfigType):
conf.GetSectionList('default', 'notexists')
def test_get_highlight(self):
conf = self.mock_config()
eq = self.assertEqual
eq(conf.GetHighlight('IDLE Classic', 'normal'), {'foreground': '#000000',
'background': '#ffffff'})
eq(conf.GetHighlight('IDLE Classic', 'normal', 'fg'), '#000000')
eq(conf.GetHighlight('IDLE Classic', 'normal', 'bg'), '#ffffff')
with self.assertRaises(config.InvalidFgBg):
conf.GetHighlight('IDLE Classic', 'normal', 'fb')
# Test cursor (this background should be normal-background)
eq(conf.GetHighlight('IDLE Classic', 'cursor'), {'foreground': 'black',
'background': '#ffffff'})
# Test get user themes
conf.SetOption('highlight', 'Foobar', 'normal-foreground', '#747474')
conf.SetOption('highlight', 'Foobar', 'normal-background', '#171717')
with mock.patch('idlelib.config._warn'):
eq(conf.GetHighlight('Foobar', 'normal'), {'foreground': '#747474',
'background': '#171717'})
def test_get_theme_dict(self):
"XXX: NOT YET DONE"
conf = self.mock_config()
# These two should be the same
self.assertEqual(
conf.GetThemeDict('default', 'IDLE Classic'),
conf.GetThemeDict('user', 'IDLE Classic'))
with self.assertRaises(config.InvalidTheme):
conf.GetThemeDict('bad', 'IDLE Classic')
def test_get_current_theme_and_keys(self):
conf = self.mock_config()
self.assertEqual(conf.CurrentTheme(), conf.current_colors_and_keys('Theme'))
self.assertEqual(conf.CurrentKeys(), conf.current_colors_and_keys('Keys'))
def test_current_colors_and_keys(self):
conf = self.mock_config()
self.assertEqual(conf.current_colors_and_keys('Theme'), 'IDLE Classic')
def test_default_keys(self):
current_platform = sys.platform
conf = self.new_config(_utest=True)
sys.platform = 'win32'
self.assertEqual(conf.default_keys(), 'IDLE Classic Windows')
sys.platform = 'darwin'
self.assertEqual(conf.default_keys(), 'IDLE Classic OSX')
sys.platform = 'some-linux'
self.assertEqual(conf.default_keys(), 'IDLE Modern Unix')
# Restore platform
sys.platform = current_platform
def test_get_extensions(self):
userextn.read_string('''
[ZzDummy]
enable = True
[DISABLE]
enable = False
''')
eq = self.assertEqual
iGE = idleConf.GetExtensions
eq(iGE(shell_only=True), [])
eq(iGE(), ['ZzDummy'])
eq(iGE(editor_only=True), ['ZzDummy'])
eq(iGE(active_only=False), ['ZzDummy', 'DISABLE'])
eq(iGE(active_only=False, editor_only=True), ['ZzDummy', 'DISABLE'])
userextn.remove_section('ZzDummy')
userextn.remove_section('DISABLE')
def test_remove_key_bind_names(self):
conf = self.mock_config()
self.assertCountEqual(
conf.RemoveKeyBindNames(conf.GetSectionList('default', 'extensions')),
['AutoComplete', 'CodeContext', 'FormatParagraph', 'ParenMatch', 'ZzDummy'])
def test_get_extn_name_for_event(self):
userextn.read_string('''
[ZzDummy]
enable = True
''')
eq = self.assertEqual
eq(idleConf.GetExtnNameForEvent('z-in'), 'ZzDummy')
eq(idleConf.GetExtnNameForEvent('z-out'), None)
userextn.remove_section('ZzDummy')
def test_get_extension_keys(self):
userextn.read_string('''
[ZzDummy]
enable = True
''')
self.assertEqual(idleConf.GetExtensionKeys('ZzDummy'),
{'<<z-in>>': ['<Control-Shift-KeyRelease-Insert>']})
userextn.remove_section('ZzDummy')
# need option key test
## key = ['<Option-Key-2>'] if sys.platform == 'darwin' else ['<Alt-Key-2>']
## eq(conf.GetExtensionKeys('ZoomHeight'), {'<<zoom-height>>': key})
def test_get_extension_bindings(self):
userextn.read_string('''
[ZzDummy]
enable = True
''')
eq = self.assertEqual
iGEB = idleConf.GetExtensionBindings
eq(iGEB('NotExists'), {})
expect = {'<<z-in>>': ['<Control-Shift-KeyRelease-Insert>'],
'<<z-out>>': ['<Control-Shift-KeyRelease-Delete>']}
eq(iGEB('ZzDummy'), expect)
userextn.remove_section('ZzDummy')
def test_get_keybinding(self):
conf = self.mock_config()
eq = self.assertEqual
eq(conf.GetKeyBinding('IDLE Modern Unix', '<<copy>>'),
['<Control-Shift-Key-C>', '<Control-Key-Insert>'])
eq(conf.GetKeyBinding('IDLE Classic Unix', '<<copy>>'),
['<Alt-Key-w>', '<Meta-Key-w>'])
eq(conf.GetKeyBinding('IDLE Classic Windows', '<<copy>>'),
['<Control-Key-c>', '<Control-Key-C>'])
eq(conf.GetKeyBinding('IDLE Classic Mac', '<<copy>>'), ['<Command-Key-c>'])
eq(conf.GetKeyBinding('IDLE Classic OSX', '<<copy>>'), ['<Command-Key-c>'])
# Test keybinding not exists
eq(conf.GetKeyBinding('NOT EXISTS', '<<copy>>'), [])
eq(conf.GetKeyBinding('IDLE Modern Unix', 'NOT EXISTS'), [])
def test_get_current_keyset(self):
current_platform = sys.platform
conf = self.mock_config()
# Ensure that platform isn't darwin
sys.platform = 'some-linux'
self.assertEqual(conf.GetCurrentKeySet(), conf.GetKeySet(conf.CurrentKeys()))
# This should not be the same, since replace <Alt- to <Option-.
# Above depended on config-extensions.def having Alt keys,
# which is no longer true.
# sys.platform = 'darwin'
# self.assertNotEqual(conf.GetCurrentKeySet(), conf.GetKeySet(conf.CurrentKeys()))
# Restore platform
sys.platform = current_platform
def test_get_keyset(self):
conf = self.mock_config()
# Conflic with key set, should be disable to ''
conf.defaultCfg['extensions'].add_section('Foobar')
conf.defaultCfg['extensions'].add_section('Foobar_cfgBindings')
conf.defaultCfg['extensions'].set('Foobar', 'enable', 'True')
conf.defaultCfg['extensions'].set('Foobar_cfgBindings', 'newfoo', '<Key-F3>')
self.assertEqual(conf.GetKeySet('IDLE Modern Unix')['<<newfoo>>'], '')
def test_is_core_binding(self):
# XXX: Should move out the core keys to config file or other place
conf = self.mock_config()
self.assertTrue(conf.IsCoreBinding('copy'))
self.assertTrue(conf.IsCoreBinding('cut'))
self.assertTrue(conf.IsCoreBinding('del-word-right'))
self.assertFalse(conf.IsCoreBinding('not-exists'))
def test_extra_help_source_list(self):
# Test GetExtraHelpSourceList and GetAllExtraHelpSourcesList in same
# place to prevent prepare input data twice.
conf = self.mock_config()
# Test default with no extra help source
self.assertEqual(conf.GetExtraHelpSourceList('default'), [])
self.assertEqual(conf.GetExtraHelpSourceList('user'), [])
with self.assertRaises(config.InvalidConfigSet):
self.assertEqual(conf.GetExtraHelpSourceList('bad'), [])
self.assertCountEqual(
conf.GetAllExtraHelpSourcesList(),
conf.GetExtraHelpSourceList('default') + conf.GetExtraHelpSourceList('user'))
# Add help source to user config
conf.userCfg['main'].SetOption('HelpFiles', '4', 'Python;https://python.org') # This is bad input
conf.userCfg['main'].SetOption('HelpFiles', '3', 'Python:https://python.org') # This is bad input
conf.userCfg['main'].SetOption('HelpFiles', '2', 'Pillow;https://pillow.readthedocs.io/en/latest/')
conf.userCfg['main'].SetOption('HelpFiles', '1', 'IDLE;C:/Programs/Python36/Lib/idlelib/help.html')
self.assertEqual(conf.GetExtraHelpSourceList('user'),
[('IDLE', 'C:/Programs/Python36/Lib/idlelib/help.html', '1'),
('Pillow', 'https://pillow.readthedocs.io/en/latest/', '2'),
('Python', 'https://python.org', '4')])
self.assertCountEqual(
conf.GetAllExtraHelpSourcesList(),
conf.GetExtraHelpSourceList('default') + conf.GetExtraHelpSourceList('user'))
def test_get_font(self):
from test.support import requires
from tkinter import Tk
from tkinter.font import Font
conf = self.mock_config()
requires('gui')
root = Tk()
root.withdraw()
f = Font.actual(Font(name='TkFixedFont', exists=True, root=root))
self.assertEqual(
conf.GetFont(root, 'main', 'EditorWindow'),
(f['family'], 10 if f['size'] <= 0 else f['size'], f['weight']))
# Cleanup root
root.destroy()
del root
def test_get_core_keys(self):
conf = self.mock_config()
eq = self.assertEqual
eq(conf.GetCoreKeys()['<<center-insert>>'], ['<Control-l>'])
eq(conf.GetCoreKeys()['<<copy>>'], ['<Control-c>', '<Control-C>'])
eq(conf.GetCoreKeys()['<<history-next>>'], ['<Alt-n>'])
eq(conf.GetCoreKeys('IDLE Classic Windows')['<<center-insert>>'],
['<Control-Key-l>', '<Control-Key-L>'])
eq(conf.GetCoreKeys('IDLE Classic OSX')['<<copy>>'], ['<Command-Key-c>'])
eq(conf.GetCoreKeys('IDLE Classic Unix')['<<history-next>>'],
['<Alt-Key-n>', '<Meta-Key-n>'])
eq(conf.GetCoreKeys('IDLE Modern Unix')['<<history-next>>'],
['<Alt-Key-n>', '<Meta-Key-n>'])
class CurrentColorKeysTest(unittest.TestCase):
""" Test colorkeys function with user config [Theme] and [Keys] patterns.
colorkeys = config.IdleConf.current_colors_and_keys
Test all patterns written by IDLE and some errors
Item 'default' should really be 'builtin' (versus 'custom).
"""
colorkeys = idleConf.current_colors_and_keys
default_theme = 'IDLE Classic'
default_keys = idleConf.default_keys()
def test_old_builtin_theme(self):
# On initial installation, user main is blank.
self.assertEqual(self.colorkeys('Theme'), self.default_theme)
# For old default, name2 must be blank.
usermain.read_string('''
[Theme]
default = True
''')
# IDLE omits 'name' for default old builtin theme.
self.assertEqual(self.colorkeys('Theme'), self.default_theme)
# IDLE adds 'name' for non-default old builtin theme.
usermain['Theme']['name'] = 'IDLE New'
self.assertEqual(self.colorkeys('Theme'), 'IDLE New')
# Erroneous non-default old builtin reverts to default.
usermain['Theme']['name'] = 'non-existent'
self.assertEqual(self.colorkeys('Theme'), self.default_theme)
usermain.remove_section('Theme')
def test_new_builtin_theme(self):
# IDLE writes name2 for new builtins.
usermain.read_string('''
[Theme]
default = True
name2 = IDLE Dark
''')
self.assertEqual(self.colorkeys('Theme'), 'IDLE Dark')
# Leftover 'name', not removed, is ignored.
usermain['Theme']['name'] = 'IDLE New'
self.assertEqual(self.colorkeys('Theme'), 'IDLE Dark')
# Erroneous non-default new builtin reverts to default.
usermain['Theme']['name2'] = 'non-existent'
self.assertEqual(self.colorkeys('Theme'), self.default_theme)
usermain.remove_section('Theme')
def test_user_override_theme(self):
# Erroneous custom name (no definition) reverts to default.
usermain.read_string('''
[Theme]
default = False
name = Custom Dark
''')
self.assertEqual(self.colorkeys('Theme'), self.default_theme)
# Custom name is valid with matching Section name.
userhigh.read_string('[Custom Dark]\na=b')
self.assertEqual(self.colorkeys('Theme'), 'Custom Dark')
# Name2 is ignored.
usermain['Theme']['name2'] = 'non-existent'
self.assertEqual(self.colorkeys('Theme'), 'Custom Dark')
usermain.remove_section('Theme')
userhigh.remove_section('Custom Dark')
def test_old_builtin_keys(self):
# On initial installation, user main is blank.
self.assertEqual(self.colorkeys('Keys'), self.default_keys)
# For old default, name2 must be blank, name is always used.
usermain.read_string('''
[Keys]
default = True
name = IDLE Classic Unix
''')
self.assertEqual(self.colorkeys('Keys'), 'IDLE Classic Unix')
# Erroneous non-default old builtin reverts to default.
usermain['Keys']['name'] = 'non-existent'
self.assertEqual(self.colorkeys('Keys'), self.default_keys)
usermain.remove_section('Keys')
def test_new_builtin_keys(self):
# IDLE writes name2 for new builtins.
usermain.read_string('''
[Keys]
default = True
name2 = IDLE Modern Unix
''')
self.assertEqual(self.colorkeys('Keys'), 'IDLE Modern Unix')
# Leftover 'name', not removed, is ignored.
usermain['Keys']['name'] = 'IDLE Classic Unix'
self.assertEqual(self.colorkeys('Keys'), 'IDLE Modern Unix')
# Erroneous non-default new builtin reverts to default.
usermain['Keys']['name2'] = 'non-existent'
self.assertEqual(self.colorkeys('Keys'), self.default_keys)
usermain.remove_section('Keys')
def test_user_override_keys(self):
# Erroneous custom name (no definition) reverts to default.
usermain.read_string('''
[Keys]
default = False
name = Custom Keys
''')
self.assertEqual(self.colorkeys('Keys'), self.default_keys)
# Custom name is valid with matching Section name.
userkeys.read_string('[Custom Keys]\na=b')
self.assertEqual(self.colorkeys('Keys'), 'Custom Keys')
# Name2 is ignored.
usermain['Keys']['name2'] = 'non-existent'
self.assertEqual(self.colorkeys('Keys'), 'Custom Keys')
usermain.remove_section('Keys')
userkeys.remove_section('Custom Keys')
class ChangesTest(unittest.TestCase):
empty = {'main':{}, 'highlight':{}, 'keys':{}, 'extensions':{}}
def load(self): # Test_add_option verifies that this works.
changes = self.changes
changes.add_option('main', 'Msec', 'mitem', 'mval')
changes.add_option('highlight', 'Hsec', 'hitem', 'hval')
changes.add_option('keys', 'Ksec', 'kitem', 'kval')
return changes
loaded = {'main': {'Msec': {'mitem': 'mval'}},
'highlight': {'Hsec': {'hitem': 'hval'}},
'keys': {'Ksec': {'kitem':'kval'}},
'extensions': {}}
def setUp(self):
self.changes = config.ConfigChanges()
def test_init(self):
self.assertEqual(self.changes, self.empty)
def test_add_option(self):
changes = self.load()
self.assertEqual(changes, self.loaded)
changes.add_option('main', 'Msec', 'mitem', 'mval')
self.assertEqual(changes, self.loaded)
def test_save_option(self): # Static function does not touch changes.
save_option = self.changes.save_option
self.assertTrue(save_option('main', 'Indent', 'what', '0'))
self.assertFalse(save_option('main', 'Indent', 'what', '0'))
self.assertEqual(usermain['Indent']['what'], '0')
self.assertTrue(save_option('main', 'Indent', 'use-spaces', '0'))
self.assertEqual(usermain['Indent']['use-spaces'], '0')
self.assertTrue(save_option('main', 'Indent', 'use-spaces', '1'))
self.assertFalse(usermain.has_option('Indent', 'use-spaces'))
usermain.remove_section('Indent')
def test_save_added(self):
changes = self.load()
self.assertTrue(changes.save_all())
self.assertEqual(usermain['Msec']['mitem'], 'mval')
self.assertEqual(userhigh['Hsec']['hitem'], 'hval')
self.assertEqual(userkeys['Ksec']['kitem'], 'kval')
changes.add_option('main', 'Msec', 'mitem', 'mval')
self.assertFalse(changes.save_all())
usermain.remove_section('Msec')
userhigh.remove_section('Hsec')
userkeys.remove_section('Ksec')
def test_save_help(self):
# Any change to HelpFiles overwrites entire section.
changes = self.changes
changes.save_option('main', 'HelpFiles', 'IDLE', 'idledoc')
changes.add_option('main', 'HelpFiles', 'ELDI', 'codeldi')
changes.save_all()
self.assertFalse(usermain.has_option('HelpFiles', 'IDLE'))
self.assertTrue(usermain.has_option('HelpFiles', 'ELDI'))
def test_save_default(self): # Cover 2nd and 3rd false branches.
changes = self.changes
changes.add_option('main', 'Indent', 'use-spaces', '1')
# save_option returns False; cfg_type_changed remains False.
# TODO: test that save_all calls usercfg Saves.
def test_delete_section(self):
changes = self.load()
changes.delete_section('main', 'fake') # Test no exception.
self.assertEqual(changes, self.loaded) # Test nothing deleted.
for cfgtype, section in (('main', 'Msec'), ('keys', 'Ksec')):
testcfg[cfgtype].SetOption(section, 'name', 'value')
changes.delete_section(cfgtype, section)
with self.assertRaises(KeyError):
changes[cfgtype][section] # Test section gone from changes
testcfg[cfgtype][section] # and from mock userCfg.
# TODO test for save call.
def test_clear(self):
changes = self.load()
changes.clear()
self.assertEqual(changes, self.empty)
class WarningTest(unittest.TestCase):
def test_warn(self):
Equal = self.assertEqual
config._warned = set()
with captured_stderr() as stderr:
config._warn('warning', 'key')
Equal(config._warned, {('warning','key')})
Equal(stderr.getvalue(), 'warning'+'\n')
with captured_stderr() as stderr:
config._warn('warning', 'key')
Equal(stderr.getvalue(), '')
with captured_stderr() as stderr:
config._warn('warn2', 'yek')
Equal(config._warned, {('warning','key'), ('warn2','yek')})
Equal(stderr.getvalue(), 'warn2'+'\n')
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,99 @@
"Test config_key, coverage 75%"
from idlelib import config_key
from test.support import requires
import unittest
from tkinter import Tk
from idlelib.idle_test.mock_idle import Func
from idlelib.idle_test.mock_tk import Mbox_func
class ValidationTest(unittest.TestCase):
"Test validation methods: OK, KeysOK, bind_ok."
class Validator(config_key.GetKeysDialog):
def __init__(self, *args, **kwargs):
config_key.GetKeysDialog.__init__(self, *args, **kwargs)
class listKeysFinal:
get = Func()
self.listKeysFinal = listKeysFinal
GetModifiers = Func()
showerror = Mbox_func()
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
keylist = [['<Key-F12>'], ['<Control-Key-x>', '<Control-Key-X>']]
cls.dialog = cls.Validator(
cls.root, 'Title', '<<Test>>', keylist, _utest=True)
@classmethod
def tearDownClass(cls):
cls.dialog.Cancel()
cls.root.update_idletasks()
cls.root.destroy()
del cls.dialog, cls.root
def setUp(self):
self.dialog.showerror.message = ''
# A test that needs a particular final key value should set it.
# A test that sets a non-blank modifier list should reset it to [].
def test_ok_empty(self):
self.dialog.keyString.set(' ')
self.dialog.OK()
self.assertEqual(self.dialog.result, '')
self.assertEqual(self.dialog.showerror.message, 'No key specified.')
def test_ok_good(self):
self.dialog.keyString.set('<Key-F11>')
self.dialog.listKeysFinal.get.result = 'F11'
self.dialog.OK()
self.assertEqual(self.dialog.result, '<Key-F11>')
self.assertEqual(self.dialog.showerror.message, '')
def test_keys_no_ending(self):
self.assertFalse(self.dialog.KeysOK('<Control-Shift'))
self.assertIn('Missing the final', self.dialog.showerror.message)
def test_keys_no_modifier_bad(self):
self.dialog.listKeysFinal.get.result = 'A'
self.assertFalse(self.dialog.KeysOK('<Key-A>'))
self.assertIn('No modifier', self.dialog.showerror.message)
def test_keys_no_modifier_ok(self):
self.dialog.listKeysFinal.get.result = 'F11'
self.assertTrue(self.dialog.KeysOK('<Key-F11>'))
self.assertEqual(self.dialog.showerror.message, '')
def test_keys_shift_bad(self):
self.dialog.listKeysFinal.get.result = 'a'
self.dialog.GetModifiers.result = ['Shift']
self.assertFalse(self.dialog.KeysOK('<a>'))
self.assertIn('shift modifier', self.dialog.showerror.message)
self.dialog.GetModifiers.result = []
def test_keys_dup(self):
for mods, final, seq in (([], 'F12', '<Key-F12>'),
(['Control'], 'x', '<Control-Key-x>'),
(['Control'], 'X', '<Control-Key-X>')):
with self.subTest(m=mods, f=final, s=seq):
self.dialog.listKeysFinal.get.result = final
self.dialog.GetModifiers.result = mods
self.assertFalse(self.dialog.KeysOK(seq))
self.assertIn('already in use', self.dialog.showerror.message)
self.dialog.GetModifiers.result = []
def test_bind_ok(self):
self.assertTrue(self.dialog.bind_ok('<Control-Shift-Key-a>'))
self.assertEqual(self.dialog.showerror.message, '')
def test_bind_not_ok(self):
self.assertFalse(self.dialog.bind_ok('<Control-Shift>'))
self.assertIn('not accepted', self.dialog.showerror.message)
if __name__ == '__main__':
unittest.main(verbosity=2)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,29 @@
"Test debugger, coverage 19%"
from idlelib import debugger
import unittest
from test.support import requires
requires('gui')
from tkinter import Tk
class NameSpaceTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.root = Tk()
cls.root.withdraw()
@classmethod
def tearDownClass(cls):
cls.root.destroy()
del cls.root
def test_init(self):
debugger.NamespaceViewer(self.root, 'Test')
# Other classes are Idb, Debugger, and StackViewer.
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,29 @@
"Test debugger_r, coverage 30%."
from idlelib import debugger_r
import unittest
from test.support import requires
from tkinter import Tk
class Test(unittest.TestCase):
## @classmethod
## def setUpClass(cls):
## requires('gui')
## cls.root = Tk()
##
## @classmethod
## def tearDownClass(cls):
## cls.root.destroy()
## del cls.root
def test_init(self):
self.assertTrue(True) # Get coverage of import
# Classes GUIProxy, IdbAdapter, FrameProxy, CodeProxy, DictProxy,
# GUIAdapter, IdbProxy plus 7 module functions.
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,57 @@
"Test debugobj, coverage 40%."
from idlelib import debugobj
import unittest
class ObjectTreeItemTest(unittest.TestCase):
def test_init(self):
ti = debugobj.ObjectTreeItem('label', 22)
self.assertEqual(ti.labeltext, 'label')
self.assertEqual(ti.object, 22)
self.assertEqual(ti.setfunction, None)
class ClassTreeItemTest(unittest.TestCase):
def test_isexpandable(self):
ti = debugobj.ClassTreeItem('label', 0)
self.assertTrue(ti.IsExpandable())
class AtomicObjectTreeItemTest(unittest.TestCase):
def test_isexpandable(self):
ti = debugobj.AtomicObjectTreeItem('label', 0)
self.assertFalse(ti.IsExpandable())
class SequenceTreeItemTest(unittest.TestCase):
def test_isexpandable(self):
ti = debugobj.SequenceTreeItem('label', ())
self.assertFalse(ti.IsExpandable())
ti = debugobj.SequenceTreeItem('label', (1,))
self.assertTrue(ti.IsExpandable())
def test_keys(self):
ti = debugobj.SequenceTreeItem('label', 'abc')
self.assertEqual(list(ti.keys()), [0, 1, 2])
class DictTreeItemTest(unittest.TestCase):
def test_isexpandable(self):
ti = debugobj.DictTreeItem('label', {})
self.assertFalse(ti.IsExpandable())
ti = debugobj.DictTreeItem('label', {1:1})
self.assertTrue(ti.IsExpandable())
def test_keys(self):
ti = debugobj.DictTreeItem('label', {1:1, 0:0, 2:2})
self.assertEqual(ti.keys(), [0, 1, 2])
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,22 @@
"Test debugobj_r, coverage 56%."
from idlelib import debugobj_r
import unittest
class WrappedObjectTreeItemTest(unittest.TestCase):
def test_getattr(self):
ti = debugobj_r.WrappedObjectTreeItem(list)
self.assertEqual(ti.append, list.append)
class StubObjectTreeItemTest(unittest.TestCase):
def test_init(self):
ti = debugobj_r.StubObjectTreeItem('socket', 1111)
self.assertEqual(ti.sockio, 'socket')
self.assertEqual(ti.oid, 1111)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,44 @@
"Test delegator, coverage 100%."
from idlelib.delegator import Delegator
import unittest
class DelegatorTest(unittest.TestCase):
def test_mydel(self):
# Test a simple use scenario.
# Initialize an int delegator.
mydel = Delegator(int)
self.assertIs(mydel.delegate, int)
self.assertEqual(mydel._Delegator__cache, set())
# Trying to access a non-attribute of int fails.
self.assertRaises(AttributeError, mydel.__getattr__, 'xyz')
# Add real int attribute 'bit_length' by accessing it.
bl = mydel.bit_length
self.assertIs(bl, int.bit_length)
self.assertIs(mydel.__dict__['bit_length'], int.bit_length)
self.assertEqual(mydel._Delegator__cache, {'bit_length'})
# Add attribute 'numerator'.
mydel.numerator
self.assertEqual(mydel._Delegator__cache, {'bit_length', 'numerator'})
# Delete 'numerator'.
del mydel.numerator
self.assertNotIn('numerator', mydel.__dict__)
# The current implementation leaves it in the name cache.
# self.assertIn('numerator', mydel._Delegator__cache)
# However, this is not required and not part of the specification
# Change delegate to float, first resetting the attributes.
mydel.setdelegate(float) # calls resetcache
self.assertNotIn('bit_length', mydel.__dict__)
self.assertEqual(mydel._Delegator__cache, set())
self.assertIs(mydel.delegate, float)
if __name__ == '__main__':
unittest.main(verbosity=2, exit=2)

View file

@ -0,0 +1,74 @@
'''Test (selected) IDLE Edit menu items.
Edit modules have their own test files
'''
from test.support import requires
requires('gui')
import tkinter as tk
from tkinter import ttk
import unittest
from idlelib import pyshell
class PasteTest(unittest.TestCase):
'''Test pasting into widgets that allow pasting.
On X11, replacing selections requires tk fix.
'''
@classmethod
def setUpClass(cls):
cls.root = root = tk.Tk()
cls.root.withdraw()
pyshell.fix_x11_paste(root)
cls.text = tk.Text(root)
cls.entry = tk.Entry(root)
cls.tentry = ttk.Entry(root)
cls.spin = tk.Spinbox(root)
root.clipboard_clear()
root.clipboard_append('two')
@classmethod
def tearDownClass(cls):
del cls.text, cls.entry, cls.tentry
cls.root.clipboard_clear()
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
def test_paste_text(self):
"Test pasting into text with and without a selection."
text = self.text
for tag, ans in ('', 'onetwo\n'), ('sel', 'two\n'):
with self.subTest(tag=tag, ans=ans):
text.delete('1.0', 'end')
text.insert('1.0', 'one', tag)
text.event_generate('<<Paste>>')
self.assertEqual(text.get('1.0', 'end'), ans)
def test_paste_entry(self):
"Test pasting into an entry with and without a selection."
# Generated <<Paste>> fails for tk entry without empty select
# range for 'no selection'. Live widget works fine.
for entry in self.entry, self.tentry:
for end, ans in (0, 'onetwo'), ('end', 'two'):
with self.subTest(entry=entry, end=end, ans=ans):
entry.delete(0, 'end')
entry.insert(0, 'one')
entry.select_range(0, end)
entry.event_generate('<<Paste>>')
self.assertEqual(entry.get(), ans)
def test_paste_spin(self):
"Test pasting into a spinbox with and without a selection."
# See note above for entry.
spin = self.spin
for end, ans in (0, 'onetwo'), ('end', 'two'):
with self.subTest(end=end, ans=ans):
spin.delete(0, 'end')
spin.insert(0, 'one')
spin.selection('range', 0, end) # see note
spin.event_generate('<<Paste>>')
self.assertEqual(spin.get(), ans)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,46 @@
"Test editor, coverage 35%."
from idlelib import editor
import unittest
from test.support import requires
from tkinter import Tk
Editor = editor.EditorWindow
class EditorWindowTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
@classmethod
def tearDownClass(cls):
cls.root.update_idletasks()
for id in cls.root.tk.call('after', 'info'):
cls.root.after_cancel(id)
cls.root.destroy()
del cls.root
def test_init(self):
e = Editor(root=self.root)
self.assertEqual(e.root, self.root)
e._close()
class EditorFunctionTest(unittest.TestCase):
def test_filename_to_unicode(self):
func = Editor._filename_to_unicode
class dummy():
filesystemencoding = 'utf-8'
pairs = (('abc', 'abc'), ('a\U00011111c', 'a\ufffdc'),
(b'abc', 'abc'), (b'a\xf0\x91\x84\x91c', 'a\ufffdc'))
for inp, out in pairs:
self.assertEqual(func(dummy, inp), out)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,33 @@
"Test filelist, coverage 19%."
from idlelib import filelist
import unittest
from test.support import requires
from tkinter import Tk
class FileListTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
@classmethod
def tearDownClass(cls):
cls.root.update_idletasks()
for id in cls.root.tk.call('after', 'info'):
cls.root.after_cancel(id)
cls.root.destroy()
del cls.root
def test_new_empty(self):
flist = filelist.FileList(self.root)
self.assertEqual(flist.root, self.root)
e = flist.new()
self.assertEqual(type(e), flist.EditorWindow)
e._close()
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,86 @@
""" !Changing this line will break Test_findfile.test_found!
Non-gui unit tests for grep.GrepDialog methods.
dummy_command calls grep_it calls findfiles.
An exception raised in one method will fail callers.
Otherwise, tests are mostly independent.
Currently only test grep_it, coverage 51%.
"""
from idlelib.grep import GrepDialog
import unittest
from test.support import captured_stdout
from idlelib.idle_test.mock_tk import Var
import re
class Dummy_searchengine:
'''GrepDialog.__init__ calls parent SearchDiabolBase which attaches the
passed in SearchEngine instance as attribute 'engine'. Only a few of the
many possible self.engine.x attributes are needed here.
'''
def getpat(self):
return self._pat
searchengine = Dummy_searchengine()
class Dummy_grep:
# Methods tested
#default_command = GrepDialog.default_command
grep_it = GrepDialog.grep_it
findfiles = GrepDialog.findfiles
# Other stuff needed
recvar = Var(False)
engine = searchengine
def close(self): # gui method
pass
grep = Dummy_grep()
class FindfilesTest(unittest.TestCase):
# findfiles is really a function, not a method, could be iterator
# test that filename return filename
# test that idlelib has many .py files
# test that recursive flag adds idle_test .py files
pass
class Grep_itTest(unittest.TestCase):
# Test captured reports with 0 and some hits.
# Should test file names, but Windows reports have mixed / and \ separators
# from incomplete replacement, so 'later'.
def report(self, pat):
grep.engine._pat = pat
with captured_stdout() as s:
grep.grep_it(re.compile(pat), __file__)
lines = s.getvalue().split('\n')
lines.pop() # remove bogus '' after last \n
return lines
def test_unfound(self):
pat = 'xyz*'*7
lines = self.report(pat)
self.assertEqual(len(lines), 2)
self.assertIn(pat, lines[0])
self.assertEqual(lines[1], 'No hits.')
def test_found(self):
pat = '""" !Changing this line will break Test_findfile.test_found!'
lines = self.report(pat)
self.assertEqual(len(lines), 5)
self.assertIn(pat, lines[0])
self.assertIn('py: 1:', lines[1]) # line number 1
self.assertIn('2', lines[3]) # hits found 2
self.assertTrue(lines[4].startswith('(Hint:'))
class Default_commandTest(unittest.TestCase):
# To write this, move outwin import to top of GrepDialog
# so it can be replaced by captured_stdout in class setup/teardown.
pass
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,34 @@
"Test help, coverage 87%."
from idlelib import help
import unittest
from test.support import requires
requires('gui')
from os.path import abspath, dirname, join
from tkinter import Tk
class HelpFrameTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
"By itself, this tests that file parsed without exception."
cls.root = root = Tk()
root.withdraw()
helpfile = join(dirname(dirname(abspath(__file__))), 'help.html')
cls.frame = help.HelpFrame(root, helpfile)
@classmethod
def tearDownClass(cls):
del cls.frame
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
def test_line1(self):
text = self.frame.text
self.assertEqual(text.get('1.0', '1.end'), ' IDLE ')
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,180 @@
"""Test help_about, coverage 100%.
help_about.build_bits branches on sys.platform='darwin'.
'100% combines coverage on Mac and others.
"""
from idlelib import help_about
import unittest
from test.support import requires, findfile
from tkinter import Tk, TclError
from idlelib.idle_test.mock_idle import Func
from idlelib.idle_test.mock_tk import Mbox_func
from idlelib import textview
import os.path
from platform import python_version
About = help_about.AboutDialog
class LiveDialogTest(unittest.TestCase):
"""Simulate user clicking buttons other than [Close].
Test that invoked textview has text from source.
"""
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.dialog = About(cls.root, 'About IDLE', _utest=True)
@classmethod
def tearDownClass(cls):
del cls.dialog
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
def test_build_bits(self):
self.assertIn(help_about.build_bits(), ('32', '64'))
def test_dialog_title(self):
"""Test about dialog title"""
self.assertEqual(self.dialog.title(), 'About IDLE')
def test_dialog_logo(self):
"""Test about dialog logo."""
path, file = os.path.split(self.dialog.icon_image['file'])
fn, ext = os.path.splitext(file)
self.assertEqual(fn, 'idle_48')
def test_printer_buttons(self):
"""Test buttons whose commands use printer function."""
dialog = self.dialog
button_sources = [(dialog.py_license, license, 'license'),
(dialog.py_copyright, copyright, 'copyright'),
(dialog.py_credits, credits, 'credits')]
for button, printer, name in button_sources:
with self.subTest(name=name):
printer._Printer__setup()
button.invoke()
get = dialog._current_textview.viewframe.textframe.text.get
lines = printer._Printer__lines
self.assertEqual(lines[0], get('1.0', '1.end'))
self.assertEqual(lines[1], get('2.0', '2.end'))
dialog._current_textview.destroy()
def test_file_buttons(self):
"""Test buttons that display files."""
dialog = self.dialog
button_sources = [(self.dialog.readme, 'README.txt', 'readme'),
(self.dialog.idle_news, 'NEWS.txt', 'news'),
(self.dialog.idle_credits, 'CREDITS.txt', 'credits')]
for button, filename, name in button_sources:
with self.subTest(name=name):
button.invoke()
fn = findfile(filename, subdir='idlelib')
get = dialog._current_textview.viewframe.textframe.text.get
with open(fn, encoding='utf-8') as f:
self.assertEqual(f.readline().strip(), get('1.0', '1.end'))
f.readline()
self.assertEqual(f.readline().strip(), get('3.0', '3.end'))
dialog._current_textview.destroy()
class DefaultTitleTest(unittest.TestCase):
"Test default title."
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.dialog = About(cls.root, _utest=True)
@classmethod
def tearDownClass(cls):
del cls.dialog
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
def test_dialog_title(self):
"""Test about dialog title"""
self.assertEqual(self.dialog.title(),
f'About IDLE {python_version()}'
f' ({help_about.build_bits()} bit)')
class CloseTest(unittest.TestCase):
"""Simulate user clicking [Close] button"""
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.dialog = About(cls.root, 'About IDLE', _utest=True)
@classmethod
def tearDownClass(cls):
del cls.dialog
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
def test_close(self):
self.assertEqual(self.dialog.winfo_class(), 'Toplevel')
self.dialog.button_ok.invoke()
with self.assertRaises(TclError):
self.dialog.winfo_class()
class Dummy_about_dialog():
# Dummy class for testing file display functions.
idle_credits = About.show_idle_credits
idle_readme = About.show_readme
idle_news = About.show_idle_news
# Called by the above
display_file_text = About.display_file_text
_utest = True
class DisplayFileTest(unittest.TestCase):
"""Test functions that display files.
While somewhat redundant with gui-based test_file_dialog,
these unit tests run on all buildbots, not just a few.
"""
dialog = Dummy_about_dialog()
@classmethod
def setUpClass(cls):
cls.orig_error = textview.showerror
cls.orig_view = textview.view_text
cls.error = Mbox_func()
cls.view = Func()
textview.showerror = cls.error
textview.view_text = cls.view
@classmethod
def tearDownClass(cls):
textview.showerror = cls.orig_error
textview.view_text = cls.orig_view
def test_file_display(self):
for handler in (self.dialog.idle_credits,
self.dialog.idle_readme,
self.dialog.idle_news):
self.error.message = ''
self.view.called = False
with self.subTest(handler=handler):
handler()
self.assertEqual(self.error.message, '')
self.assertEqual(self.view.called, True)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,172 @@
" Test history, coverage 100%."
from idlelib.history import History
import unittest
from test.support import requires
import tkinter as tk
from tkinter import Text as tkText
from idlelib.idle_test.mock_tk import Text as mkText
from idlelib.config import idleConf
line1 = 'a = 7'
line2 = 'b = a'
class StoreTest(unittest.TestCase):
'''Tests History.__init__ and History.store with mock Text'''
@classmethod
def setUpClass(cls):
cls.text = mkText()
cls.history = History(cls.text)
def tearDown(self):
self.text.delete('1.0', 'end')
self.history.history = []
def test_init(self):
self.assertIs(self.history.text, self.text)
self.assertEqual(self.history.history, [])
self.assertIsNone(self.history.prefix)
self.assertIsNone(self.history.pointer)
self.assertEqual(self.history.cyclic,
idleConf.GetOption("main", "History", "cyclic", 1, "bool"))
def test_store_short(self):
self.history.store('a')
self.assertEqual(self.history.history, [])
self.history.store(' a ')
self.assertEqual(self.history.history, [])
def test_store_dup(self):
self.history.store(line1)
self.assertEqual(self.history.history, [line1])
self.history.store(line2)
self.assertEqual(self.history.history, [line1, line2])
self.history.store(line1)
self.assertEqual(self.history.history, [line2, line1])
def test_store_reset(self):
self.history.prefix = line1
self.history.pointer = 0
self.history.store(line2)
self.assertIsNone(self.history.prefix)
self.assertIsNone(self.history.pointer)
class TextWrapper:
def __init__(self, master):
self.text = tkText(master=master)
self._bell = False
def __getattr__(self, name):
return getattr(self.text, name)
def bell(self):
self._bell = True
class FetchTest(unittest.TestCase):
'''Test History.fetch with wrapped tk.Text.
'''
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = tk.Tk()
cls.root.withdraw()
def setUp(self):
self.text = text = TextWrapper(self.root)
text.insert('1.0', ">>> ")
text.mark_set('iomark', '1.4')
text.mark_gravity('iomark', 'left')
self.history = History(text)
self.history.history = [line1, line2]
@classmethod
def tearDownClass(cls):
cls.root.destroy()
del cls.root
def fetch_test(self, reverse, line, prefix, index, *, bell=False):
# Perform one fetch as invoked by Alt-N or Alt-P
# Test the result. The line test is the most important.
# The last two are diagnostic of fetch internals.
History = self.history
History.fetch(reverse)
Equal = self.assertEqual
Equal(self.text.get('iomark', 'end-1c'), line)
Equal(self.text._bell, bell)
if bell:
self.text._bell = False
Equal(History.prefix, prefix)
Equal(History.pointer, index)
Equal(self.text.compare("insert", '==', "end-1c"), 1)
def test_fetch_prev_cyclic(self):
prefix = ''
test = self.fetch_test
test(True, line2, prefix, 1)
test(True, line1, prefix, 0)
test(True, prefix, None, None, bell=True)
def test_fetch_next_cyclic(self):
prefix = ''
test = self.fetch_test
test(False, line1, prefix, 0)
test(False, line2, prefix, 1)
test(False, prefix, None, None, bell=True)
# Prefix 'a' tests skip line2, which starts with 'b'
def test_fetch_prev_prefix(self):
prefix = 'a'
self.text.insert('iomark', prefix)
self.fetch_test(True, line1, prefix, 0)
self.fetch_test(True, prefix, None, None, bell=True)
def test_fetch_next_prefix(self):
prefix = 'a'
self.text.insert('iomark', prefix)
self.fetch_test(False, line1, prefix, 0)
self.fetch_test(False, prefix, None, None, bell=True)
def test_fetch_prev_noncyclic(self):
prefix = ''
self.history.cyclic = False
test = self.fetch_test
test(True, line2, prefix, 1)
test(True, line1, prefix, 0)
test(True, line1, prefix, 0, bell=True)
def test_fetch_next_noncyclic(self):
prefix = ''
self.history.cyclic = False
test = self.fetch_test
test(False, prefix, None, None, bell=True)
test(True, line2, prefix, 1)
test(False, prefix, None, None, bell=True)
test(False, prefix, None, None, bell=True)
def test_fetch_cursor_move(self):
# Move cursor after fetch
self.history.fetch(reverse=True) # initialization
self.text.mark_set('insert', 'iomark')
self.fetch_test(True, line2, None, None, bell=True)
def test_fetch_edit(self):
# Edit after fetch
self.history.fetch(reverse=True) # initialization
self.text.delete('iomark', 'insert', )
self.text.insert('iomark', 'a =')
self.fetch_test(True, line1, 'a =', 0) # prefix is reset
def test_history_prev_next(self):
# Minimally test functions bound to events
self.history.history_prev('dummy event')
self.assertEqual(self.history.pointer, 1)
self.history.history_next('dummy event')
self.assertEqual(self.history.pointer, None)
if __name__ == '__main__':
unittest.main(verbosity=2, exit=2)

View file

@ -0,0 +1,276 @@
"Test hyperparser, coverage 98%."
from idlelib.hyperparser import HyperParser
import unittest
from test.support import requires
from tkinter import Tk, Text
from idlelib.editor import EditorWindow
class DummyEditwin:
def __init__(self, text):
self.text = text
self.indentwidth = 8
self.tabwidth = 8
self.context_use_ps1 = True
self.num_context_lines = 50, 500, 1000
_build_char_in_string_func = EditorWindow._build_char_in_string_func
is_char_in_string = EditorWindow.is_char_in_string
class HyperParserTest(unittest.TestCase):
code = (
'"""This is a module docstring"""\n'
'# this line is a comment\n'
'x = "this is a string"\n'
"y = 'this is also a string'\n"
'l = [i for i in range(10)]\n'
'm = [py*py for # comment\n'
' py in l]\n'
'x.__len__\n'
"z = ((r'asdf')+('a')))\n"
'[x for x in\n'
'for = False\n'
'cliché = "this is a string with unicode, what a cliché"'
)
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.text = Text(cls.root)
cls.editwin = DummyEditwin(cls.text)
@classmethod
def tearDownClass(cls):
del cls.text, cls.editwin
cls.root.destroy()
del cls.root
def setUp(self):
self.text.insert('insert', self.code)
def tearDown(self):
self.text.delete('1.0', 'end')
self.editwin.context_use_ps1 = True
def get_parser(self, index):
"""
Return a parser object with index at 'index'
"""
return HyperParser(self.editwin, index)
def test_init(self):
"""
test corner cases in the init method
"""
with self.assertRaises(ValueError) as ve:
self.text.tag_add('console', '1.0', '1.end')
p = self.get_parser('1.5')
self.assertIn('precedes', str(ve.exception))
# test without ps1
self.editwin.context_use_ps1 = False
# number of lines lesser than 50
p = self.get_parser('end')
self.assertEqual(p.rawtext, self.text.get('1.0', 'end'))
# number of lines greater than 50
self.text.insert('end', self.text.get('1.0', 'end')*4)
p = self.get_parser('54.5')
def test_is_in_string(self):
get = self.get_parser
p = get('1.0')
self.assertFalse(p.is_in_string())
p = get('1.4')
self.assertTrue(p.is_in_string())
p = get('2.3')
self.assertFalse(p.is_in_string())
p = get('3.3')
self.assertFalse(p.is_in_string())
p = get('3.7')
self.assertTrue(p.is_in_string())
p = get('4.6')
self.assertTrue(p.is_in_string())
p = get('12.54')
self.assertTrue(p.is_in_string())
def test_is_in_code(self):
get = self.get_parser
p = get('1.0')
self.assertTrue(p.is_in_code())
p = get('1.1')
self.assertFalse(p.is_in_code())
p = get('2.5')
self.assertFalse(p.is_in_code())
p = get('3.4')
self.assertTrue(p.is_in_code())
p = get('3.6')
self.assertFalse(p.is_in_code())
p = get('4.14')
self.assertFalse(p.is_in_code())
def test_get_surrounding_bracket(self):
get = self.get_parser
def without_mustclose(parser):
# a utility function to get surrounding bracket
# with mustclose=False
return parser.get_surrounding_brackets(mustclose=False)
def with_mustclose(parser):
# a utility function to get surrounding bracket
# with mustclose=True
return parser.get_surrounding_brackets(mustclose=True)
p = get('3.2')
self.assertIsNone(with_mustclose(p))
self.assertIsNone(without_mustclose(p))
p = get('5.6')
self.assertTupleEqual(without_mustclose(p), ('5.4', '5.25'))
self.assertTupleEqual(without_mustclose(p), with_mustclose(p))
p = get('5.23')
self.assertTupleEqual(without_mustclose(p), ('5.21', '5.24'))
self.assertTupleEqual(without_mustclose(p), with_mustclose(p))
p = get('6.15')
self.assertTupleEqual(without_mustclose(p), ('6.4', '6.end'))
self.assertIsNone(with_mustclose(p))
p = get('9.end')
self.assertIsNone(with_mustclose(p))
self.assertIsNone(without_mustclose(p))
def test_get_expression(self):
get = self.get_parser
p = get('4.2')
self.assertEqual(p.get_expression(), 'y ')
p = get('4.7')
with self.assertRaises(ValueError) as ve:
p.get_expression()
self.assertIn('is inside a code', str(ve.exception))
p = get('5.25')
self.assertEqual(p.get_expression(), 'range(10)')
p = get('6.7')
self.assertEqual(p.get_expression(), 'py')
p = get('6.8')
self.assertEqual(p.get_expression(), '')
p = get('7.9')
self.assertEqual(p.get_expression(), 'py')
p = get('8.end')
self.assertEqual(p.get_expression(), 'x.__len__')
p = get('9.13')
self.assertEqual(p.get_expression(), "r'asdf'")
p = get('9.17')
with self.assertRaises(ValueError) as ve:
p.get_expression()
self.assertIn('is inside a code', str(ve.exception))
p = get('10.0')
self.assertEqual(p.get_expression(), '')
p = get('10.6')
self.assertEqual(p.get_expression(), '')
p = get('10.11')
self.assertEqual(p.get_expression(), '')
p = get('11.3')
self.assertEqual(p.get_expression(), '')
p = get('11.11')
self.assertEqual(p.get_expression(), 'False')
p = get('12.6')
self.assertEqual(p.get_expression(), 'cliché')
def test_eat_identifier(self):
def is_valid_id(candidate):
result = HyperParser._eat_identifier(candidate, 0, len(candidate))
if result == len(candidate):
return True
elif result == 0:
return False
else:
err_msg = "Unexpected result: {} (expected 0 or {}".format(
result, len(candidate)
)
raise Exception(err_msg)
# invalid first character which is valid elsewhere in an identifier
self.assertFalse(is_valid_id('2notid'))
# ASCII-only valid identifiers
self.assertTrue(is_valid_id('valid_id'))
self.assertTrue(is_valid_id('_valid_id'))
self.assertTrue(is_valid_id('valid_id_'))
self.assertTrue(is_valid_id('_2valid_id'))
# keywords which should be "eaten"
self.assertTrue(is_valid_id('True'))
self.assertTrue(is_valid_id('False'))
self.assertTrue(is_valid_id('None'))
# keywords which should not be "eaten"
self.assertFalse(is_valid_id('for'))
self.assertFalse(is_valid_id('import'))
self.assertFalse(is_valid_id('return'))
# valid unicode identifiers
self.assertTrue(is_valid_id('cliche'))
self.assertTrue(is_valid_id('cliché'))
self.assertTrue(is_valid_id(''))
# invalid unicode identifiers
self.assertFalse(is_valid_id('2a'))
self.assertFalse(is_valid_id('٢a'))
self.assertFalse(is_valid_id(''))
# valid identifier after "punctuation"
self.assertEqual(HyperParser._eat_identifier('+ var', 0, 5), len('var'))
self.assertEqual(HyperParser._eat_identifier('+var', 0, 4), len('var'))
self.assertEqual(HyperParser._eat_identifier('.var', 0, 4), len('var'))
# invalid identifiers
self.assertFalse(is_valid_id('+'))
self.assertFalse(is_valid_id(' '))
self.assertFalse(is_valid_id(':'))
self.assertFalse(is_valid_id('?'))
self.assertFalse(is_valid_id('^'))
self.assertFalse(is_valid_id('\\'))
self.assertFalse(is_valid_id('"'))
self.assertFalse(is_valid_id('"a string"'))
def test_eat_identifier_various_lengths(self):
eat_id = HyperParser._eat_identifier
for length in range(1, 21):
self.assertEqual(eat_id('a' * length, 0, length), length)
self.assertEqual(eat_id('é' * length, 0, length), length)
self.assertEqual(eat_id('a' + '2' * (length - 1), 0, length), length)
self.assertEqual(eat_id('é' + '2' * (length - 1), 0, length), length)
self.assertEqual(eat_id('é' + 'a' * (length - 1), 0, length), length)
self.assertEqual(eat_id('é' * (length - 1) + 'a', 0, length), length)
self.assertEqual(eat_id('+' * length, 0, length), 0)
self.assertEqual(eat_id('2' + 'a' * (length - 1), 0, length), 0)
self.assertEqual(eat_id('2' + 'é' * (length - 1), 0, length), 0)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,37 @@
"Test , coverage 16%."
from idlelib import iomenu
import unittest
from test.support import requires
from tkinter import Tk
from idlelib.editor import EditorWindow
class IOBindigTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.editwin = EditorWindow(root=cls.root)
@classmethod
def tearDownClass(cls):
cls.editwin._close()
del cls.editwin
cls.root.update_idletasks()
for id in cls.root.tk.call('after', 'info'):
cls.root.after_cancel(id) # Need for EditorWindow.
cls.root.destroy()
del cls.root
def test_init(self):
io = iomenu.IOBinding(self.editwin)
self.assertIs(io.editwin, self.editwin)
io.close
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,104 @@
"Test macosx, coverage 45% on Windows."
from idlelib import macosx
import unittest
from test.support import requires
import tkinter as tk
import unittest.mock as mock
from idlelib.filelist import FileList
mactypes = {'carbon', 'cocoa', 'xquartz'}
nontypes = {'other'}
alltypes = mactypes | nontypes
class InitTktypeTest(unittest.TestCase):
"Test _init_tk_type."
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = tk.Tk()
cls.root.withdraw()
cls.orig_platform = macosx.platform
@classmethod
def tearDownClass(cls):
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
macosx.platform = cls.orig_platform
def test_init_sets_tktype(self):
"Test that _init_tk_type sets _tk_type according to platform."
for platform, types in ('darwin', alltypes), ('other', nontypes):
with self.subTest(platform=platform):
macosx.platform = platform
macosx._tk_type == None
macosx._init_tk_type()
self.assertIn(macosx._tk_type, types)
class IsTypeTkTest(unittest.TestCase):
"Test each of the four isTypeTk predecates."
isfuncs = ((macosx.isAquaTk, ('carbon', 'cocoa')),
(macosx.isCarbonTk, ('carbon')),
(macosx.isCocoaTk, ('cocoa')),
(macosx.isXQuartz, ('xquartz')),
)
@mock.patch('idlelib.macosx._init_tk_type')
def test_is_calls_init(self, mockinit):
"Test that each isTypeTk calls _init_tk_type when _tk_type is None."
macosx._tk_type = None
for func, whentrue in self.isfuncs:
with self.subTest(func=func):
func()
self.assertTrue(mockinit.called)
mockinit.reset_mock()
def test_isfuncs(self):
"Test that each isTypeTk return correct bool."
for func, whentrue in self.isfuncs:
for tktype in alltypes:
with self.subTest(func=func, whentrue=whentrue, tktype=tktype):
macosx._tk_type = tktype
(self.assertTrue if tktype in whentrue else self.assertFalse)\
(func())
class SetupTest(unittest.TestCase):
"Test setupApp."
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = tk.Tk()
cls.root.withdraw()
def cmd(tkpath, func):
assert isinstance(tkpath, str)
assert isinstance(func, type(cmd))
cls.root.createcommand = cmd
@classmethod
def tearDownClass(cls):
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
@mock.patch('idlelib.macosx.overrideRootMenu') #27312
def test_setupapp(self, overrideRootMenu):
"Call setupApp with each possible graphics type."
root = self.root
flist = FileList(root)
for tktype in alltypes:
with self.subTest(tktype=tktype):
macosx._tk_type = tktype
macosx.setupApp(root, flist)
if tktype in ('carbon', 'cocoa'):
self.assertTrue(overrideRootMenu.called)
overrideRootMenu.reset_mock()
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,21 @@
"Test mainmenu, coverage 100%."
# Reported as 88%; mocking turtledemo absence would have no point.
from idlelib import mainmenu
import unittest
class MainMenuTest(unittest.TestCase):
def test_menudefs(self):
actual = [item[0] for item in mainmenu.menudefs]
expect = ['file', 'edit', 'format', 'run', 'shell',
'debug', 'options', 'window', 'help']
self.assertEqual(actual, expect)
def test_default_keydefs(self):
self.assertGreaterEqual(len(mainmenu.default_keydefs), 50)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,40 @@
"Test multicall, coverage 33%."
from idlelib import multicall
import unittest
from test.support import requires
from tkinter import Tk, Text
class MultiCallTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.mc = multicall.MultiCallCreator(Text)
@classmethod
def tearDownClass(cls):
del cls.mc
cls.root.update_idletasks()
## for id in cls.root.tk.call('after', 'info'):
## cls.root.after_cancel(id) # Need for EditorWindow.
cls.root.destroy()
del cls.root
def test_creator(self):
mc = self.mc
self.assertIs(multicall._multicall_dict[Text], mc)
self.assertTrue(issubclass(mc, Text))
mc2 = multicall.MultiCallCreator(Text)
self.assertIs(mc, mc2)
def test_init(self):
mctext = self.mc(self.root)
self.assertIsInstance(mctext._MultiCall__binders, list)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,171 @@
"Test outwin, coverage 76%."
from idlelib import outwin
import unittest
from test.support import requires
from tkinter import Tk, Text
from idlelib.idle_test.mock_tk import Mbox_func
from idlelib.idle_test.mock_idle import Func
from unittest import mock
class OutputWindowTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
root = cls.root = Tk()
root.withdraw()
w = cls.window = outwin.OutputWindow(None, None, None, root)
cls.text = w.text = Text(root)
@classmethod
def tearDownClass(cls):
cls.window.close()
del cls.text, cls.window
cls.root.destroy()
del cls.root
def setUp(self):
self.text.delete('1.0', 'end')
def test_ispythonsource(self):
# OutputWindow overrides ispythonsource to always return False.
w = self.window
self.assertFalse(w.ispythonsource('test.txt'))
self.assertFalse(w.ispythonsource(__file__))
def test_window_title(self):
self.assertEqual(self.window.top.title(), 'Output')
def test_maybesave(self):
w = self.window
eq = self.assertEqual
w.get_saved = Func()
w.get_saved.result = False
eq(w.maybesave(), 'no')
eq(w.get_saved.called, 1)
w.get_saved.result = True
eq(w.maybesave(), 'yes')
eq(w.get_saved.called, 2)
del w.get_saved
def test_write(self):
eq = self.assertEqual
delete = self.text.delete
get = self.text.get
write = self.window.write
# Test bytes.
b = b'Test bytes.'
eq(write(b), len(b))
eq(get('1.0', '1.end'), b.decode())
# No new line - insert stays on same line.
delete('1.0', 'end')
test_text = 'test text'
eq(write(test_text), len(test_text))
eq(get('1.0', '1.end'), 'test text')
eq(get('insert linestart', 'insert lineend'), 'test text')
# New line - insert moves to next line.
delete('1.0', 'end')
test_text = 'test text\n'
eq(write(test_text), len(test_text))
eq(get('1.0', '1.end'), 'test text')
eq(get('insert linestart', 'insert lineend'), '')
# Text after new line is tagged for second line of Text widget.
delete('1.0', 'end')
test_text = 'test text\nLine 2'
eq(write(test_text), len(test_text))
eq(get('1.0', '1.end'), 'test text')
eq(get('2.0', '2.end'), 'Line 2')
eq(get('insert linestart', 'insert lineend'), 'Line 2')
# Test tags.
delete('1.0', 'end')
test_text = 'test text\n'
test_text2 = 'Line 2\n'
eq(write(test_text, tags='mytag'), len(test_text))
eq(write(test_text2, tags='secondtag'), len(test_text2))
eq(get('mytag.first', 'mytag.last'), test_text)
eq(get('secondtag.first', 'secondtag.last'), test_text2)
eq(get('1.0', '1.end'), test_text.rstrip('\n'))
eq(get('2.0', '2.end'), test_text2.rstrip('\n'))
def test_writelines(self):
eq = self.assertEqual
get = self.text.get
writelines = self.window.writelines
writelines(('Line 1\n', 'Line 2\n', 'Line 3\n'))
eq(get('1.0', '1.end'), 'Line 1')
eq(get('2.0', '2.end'), 'Line 2')
eq(get('3.0', '3.end'), 'Line 3')
eq(get('insert linestart', 'insert lineend'), '')
def test_goto_file_line(self):
eq = self.assertEqual
w = self.window
text = self.text
w.flist = mock.Mock()
gfl = w.flist.gotofileline = Func()
showerror = w.showerror = Mbox_func()
# No file/line number.
w.write('Not a file line')
self.assertIsNone(w.goto_file_line())
eq(gfl.called, 0)
eq(showerror.title, 'No special line')
# Current file/line number.
w.write(f'{str(__file__)}: 42: spam\n')
w.write(f'{str(__file__)}: 21: spam')
self.assertIsNone(w.goto_file_line())
eq(gfl.args, (str(__file__), 21))
# Previous line has file/line number.
text.delete('1.0', 'end')
w.write(f'{str(__file__)}: 42: spam\n')
w.write('Not a file line')
self.assertIsNone(w.goto_file_line())
eq(gfl.args, (str(__file__), 42))
del w.flist.gotofileline, w.showerror
class ModuleFunctionTest(unittest.TestCase):
@classmethod
def setUp(cls):
outwin.file_line_progs = None
def test_compile_progs(self):
outwin.compile_progs()
for pat, regex in zip(outwin.file_line_pats, outwin.file_line_progs):
self.assertEqual(regex.pattern, pat)
@mock.patch('builtins.open')
def test_file_line_helper(self, mock_open):
flh = outwin.file_line_helper
test_lines = (
(r'foo file "testfile1", line 42, bar', ('testfile1', 42)),
(r'foo testfile2(21) bar', ('testfile2', 21)),
(r' testfile3 : 42: foo bar\n', (' testfile3 ', 42)),
(r'foo testfile4.py :1: ', ('foo testfile4.py ', 1)),
('testfile5: \u19D4\u19D2: ', ('testfile5', 42)),
(r'testfile6: 42', None), # only one `:`
(r'testfile7 42 text', None) # no separators
)
for line, expected_output in test_lines:
self.assertEqual(flh(line), expected_output)
if expected_output:
mock_open.assert_called_with(expected_output[0], 'r')
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,379 @@
"Test paragraph, coverage 76%."
from idlelib import paragraph as pg
import unittest
from test.support import requires
from tkinter import Tk, Text
from idlelib.editor import EditorWindow
class Is_Get_Test(unittest.TestCase):
"""Test the is_ and get_ functions"""
test_comment = '# This is a comment'
test_nocomment = 'This is not a comment'
trailingws_comment = '# This is a comment '
leadingws_comment = ' # This is a comment'
leadingws_nocomment = ' This is not a comment'
def test_is_all_white(self):
self.assertTrue(pg.is_all_white(''))
self.assertTrue(pg.is_all_white('\t\n\r\f\v'))
self.assertFalse(pg.is_all_white(self.test_comment))
def test_get_indent(self):
Equal = self.assertEqual
Equal(pg.get_indent(self.test_comment), '')
Equal(pg.get_indent(self.trailingws_comment), '')
Equal(pg.get_indent(self.leadingws_comment), ' ')
Equal(pg.get_indent(self.leadingws_nocomment), ' ')
def test_get_comment_header(self):
Equal = self.assertEqual
# Test comment strings
Equal(pg.get_comment_header(self.test_comment), '#')
Equal(pg.get_comment_header(self.trailingws_comment), '#')
Equal(pg.get_comment_header(self.leadingws_comment), ' #')
# Test non-comment strings
Equal(pg.get_comment_header(self.leadingws_nocomment), ' ')
Equal(pg.get_comment_header(self.test_nocomment), '')
class FindTest(unittest.TestCase):
"""Test the find_paragraph function in paragraph module.
Using the runcase() function, find_paragraph() is called with 'mark' set at
multiple indexes before and inside the test paragraph.
It appears that code with the same indentation as a quoted string is grouped
as part of the same paragraph, which is probably incorrect behavior.
"""
@classmethod
def setUpClass(cls):
from idlelib.idle_test.mock_tk import Text
cls.text = Text()
def runcase(self, inserttext, stopline, expected):
# Check that find_paragraph returns the expected paragraph when
# the mark index is set to beginning, middle, end of each line
# up to but not including the stop line
text = self.text
text.insert('1.0', inserttext)
for line in range(1, stopline):
linelength = int(text.index("%d.end" % line).split('.')[1])
for col in (0, linelength//2, linelength):
tempindex = "%d.%d" % (line, col)
self.assertEqual(pg.find_paragraph(text, tempindex), expected)
text.delete('1.0', 'end')
def test_find_comment(self):
comment = (
"# Comment block with no blank lines before\n"
"# Comment line\n"
"\n")
self.runcase(comment, 3, ('1.0', '3.0', '#', comment[0:58]))
comment = (
"\n"
"# Comment block with whitespace line before and after\n"
"# Comment line\n"
"\n")
self.runcase(comment, 4, ('2.0', '4.0', '#', comment[1:70]))
comment = (
"\n"
" # Indented comment block with whitespace before and after\n"
" # Comment line\n"
"\n")
self.runcase(comment, 4, ('2.0', '4.0', ' #', comment[1:82]))
comment = (
"\n"
"# Single line comment\n"
"\n")
self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:23]))
comment = (
"\n"
" # Single line comment with leading whitespace\n"
"\n")
self.runcase(comment, 3, ('2.0', '3.0', ' #', comment[1:51]))
comment = (
"\n"
"# Comment immediately followed by code\n"
"x = 42\n"
"\n")
self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:40]))
comment = (
"\n"
" # Indented comment immediately followed by code\n"
"x = 42\n"
"\n")
self.runcase(comment, 3, ('2.0', '3.0', ' #', comment[1:53]))
comment = (
"\n"
"# Comment immediately followed by indented code\n"
" x = 42\n"
"\n")
self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:49]))
def test_find_paragraph(self):
teststring = (
'"""String with no blank lines before\n'
'String line\n'
'"""\n'
'\n')
self.runcase(teststring, 4, ('1.0', '4.0', '', teststring[0:53]))
teststring = (
"\n"
'"""String with whitespace line before and after\n'
'String line.\n'
'"""\n'
'\n')
self.runcase(teststring, 5, ('2.0', '5.0', '', teststring[1:66]))
teststring = (
'\n'
' """Indented string with whitespace before and after\n'
' Comment string.\n'
' """\n'
'\n')
self.runcase(teststring, 5, ('2.0', '5.0', ' ', teststring[1:85]))
teststring = (
'\n'
'"""Single line string."""\n'
'\n')
self.runcase(teststring, 3, ('2.0', '3.0', '', teststring[1:27]))
teststring = (
'\n'
' """Single line string with leading whitespace."""\n'
'\n')
self.runcase(teststring, 3, ('2.0', '3.0', ' ', teststring[1:55]))
class ReformatFunctionTest(unittest.TestCase):
"""Test the reformat_paragraph function without the editor window."""
def test_reformat_paragraph(self):
Equal = self.assertEqual
reform = pg.reformat_paragraph
hw = "O hello world"
Equal(reform(' ', 1), ' ')
Equal(reform("Hello world", 20), "Hello world")
# Test without leading newline
Equal(reform(hw, 1), "O\nhello\nworld")
Equal(reform(hw, 6), "O\nhello\nworld")
Equal(reform(hw, 7), "O hello\nworld")
Equal(reform(hw, 12), "O hello\nworld")
Equal(reform(hw, 13), "O hello world")
# Test with leading newline
hw = "\nO hello world"
Equal(reform(hw, 1), "\nO\nhello\nworld")
Equal(reform(hw, 6), "\nO\nhello\nworld")
Equal(reform(hw, 7), "\nO hello\nworld")
Equal(reform(hw, 12), "\nO hello\nworld")
Equal(reform(hw, 13), "\nO hello world")
class ReformatCommentTest(unittest.TestCase):
"""Test the reformat_comment function without the editor window."""
def test_reformat_comment(self):
Equal = self.assertEqual
# reformat_comment formats to a minimum of 20 characters
test_string = (
" \"\"\"this is a test of a reformat for a triple quoted string"
" will it reformat to less than 70 characters for me?\"\"\"")
result = pg.reformat_comment(test_string, 70, " ")
expected = (
" \"\"\"this is a test of a reformat for a triple quoted string will it\n"
" reformat to less than 70 characters for me?\"\"\"")
Equal(result, expected)
test_comment = (
"# this is a test of a reformat for a triple quoted string will "
"it reformat to less than 70 characters for me?")
result = pg.reformat_comment(test_comment, 70, "#")
expected = (
"# this is a test of a reformat for a triple quoted string will it\n"
"# reformat to less than 70 characters for me?")
Equal(result, expected)
class FormatClassTest(unittest.TestCase):
def test_init_close(self):
instance = pg.FormatParagraph('editor')
self.assertEqual(instance.editwin, 'editor')
instance.close()
self.assertEqual(instance.editwin, None)
# For testing format_paragraph_event, Initialize FormatParagraph with
# a mock Editor with .text and .get_selection_indices. The text must
# be a Text wrapper that adds two methods
# A real EditorWindow creates unneeded, time-consuming baggage and
# sometimes emits shutdown warnings like this:
# "warning: callback failed in WindowList <class '_tkinter.TclError'>
# : invalid command name ".55131368.windows".
# Calling EditorWindow._close in tearDownClass prevents this but causes
# other problems (windows left open).
class TextWrapper:
def __init__(self, master):
self.text = Text(master=master)
def __getattr__(self, name):
return getattr(self.text, name)
def undo_block_start(self): pass
def undo_block_stop(self): pass
class Editor:
def __init__(self, root):
self.text = TextWrapper(root)
get_selection_indices = EditorWindow. get_selection_indices
class FormatEventTest(unittest.TestCase):
"""Test the formatting of text inside a Text widget.
This is done with FormatParagraph.format.paragraph_event,
which calls functions in the module as appropriate.
"""
test_string = (
" '''this is a test of a reformat for a triple "
"quoted string will it reformat to less than 70 "
"characters for me?'''\n")
multiline_test_string = (
" '''The first line is under the max width.\n"
" The second line's length is way over the max width. It goes "
"on and on until it is over 100 characters long.\n"
" Same thing with the third line. It is also way over the max "
"width, but FormatParagraph will fix it.\n"
" '''\n")
multiline_test_comment = (
"# The first line is under the max width.\n"
"# The second line's length is way over the max width. It goes on "
"and on until it is over 100 characters long.\n"
"# Same thing with the third line. It is also way over the max "
"width, but FormatParagraph will fix it.\n"
"# The fourth line is short like the first line.")
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
editor = Editor(root=cls.root)
cls.text = editor.text.text # Test code does not need the wrapper.
cls.formatter = pg.FormatParagraph(editor).format_paragraph_event
# Sets the insert mark just after the re-wrapped and inserted text.
@classmethod
def tearDownClass(cls):
del cls.text, cls.formatter
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
def test_short_line(self):
self.text.insert('1.0', "Short line\n")
self.formatter("Dummy")
self.assertEqual(self.text.get('1.0', 'insert'), "Short line\n" )
self.text.delete('1.0', 'end')
def test_long_line(self):
text = self.text
# Set cursor ('insert' mark) to '1.0', within text.
text.insert('1.0', self.test_string)
text.mark_set('insert', '1.0')
self.formatter('ParameterDoesNothing', limit=70)
result = text.get('1.0', 'insert')
# find function includes \n
expected = (
" '''this is a test of a reformat for a triple quoted string will it\n"
" reformat to less than 70 characters for me?'''\n") # yes
self.assertEqual(result, expected)
text.delete('1.0', 'end')
# Select from 1.11 to line end.
text.insert('1.0', self.test_string)
text.tag_add('sel', '1.11', '1.end')
self.formatter('ParameterDoesNothing', limit=70)
result = text.get('1.0', 'insert')
# selection excludes \n
expected = (
" '''this is a test of a reformat for a triple quoted string will it reformat\n"
" to less than 70 characters for me?'''") # no
self.assertEqual(result, expected)
text.delete('1.0', 'end')
def test_multiple_lines(self):
text = self.text
# Select 2 long lines.
text.insert('1.0', self.multiline_test_string)
text.tag_add('sel', '2.0', '4.0')
self.formatter('ParameterDoesNothing', limit=70)
result = text.get('2.0', 'insert')
expected = (
" The second line's length is way over the max width. It goes on and\n"
" on until it is over 100 characters long. Same thing with the third\n"
" line. It is also way over the max width, but FormatParagraph will\n"
" fix it.\n")
self.assertEqual(result, expected)
text.delete('1.0', 'end')
def test_comment_block(self):
text = self.text
# Set cursor ('insert') to '1.0', within block.
text.insert('1.0', self.multiline_test_comment)
self.formatter('ParameterDoesNothing', limit=70)
result = text.get('1.0', 'insert')
expected = (
"# The first line is under the max width. The second line's length is\n"
"# way over the max width. It goes on and on until it is over 100\n"
"# characters long. Same thing with the third line. It is also way over\n"
"# the max width, but FormatParagraph will fix it. The fourth line is\n"
"# short like the first line.\n")
self.assertEqual(result, expected)
text.delete('1.0', 'end')
# Select line 2, verify line 1 unaffected.
text.insert('1.0', self.multiline_test_comment)
text.tag_add('sel', '2.0', '3.0')
self.formatter('ParameterDoesNothing', limit=70)
result = text.get('1.0', 'insert')
expected = (
"# The first line is under the max width.\n"
"# The second line's length is way over the max width. It goes on and\n"
"# on until it is over 100 characters long.\n")
self.assertEqual(result, expected)
text.delete('1.0', 'end')
# The following block worked with EditorWindow but fails with the mock.
# Lines 2 and 3 get pasted together even though the previous block left
# the previous line alone. More investigation is needed.
## # Select lines 3 and 4
## text.insert('1.0', self.multiline_test_comment)
## text.tag_add('sel', '3.0', '5.0')
## self.formatter('ParameterDoesNothing')
## result = text.get('3.0', 'insert')
## expected = (
##"# Same thing with the third line. It is also way over the max width,\n"
##"# but FormatParagraph will fix it. The fourth line is short like the\n"
##"# first line.\n")
## self.assertEqual(result, expected)
## text.delete('1.0', 'end')
if __name__ == '__main__':
unittest.main(verbosity=2, exit=2)

View file

@ -0,0 +1,112 @@
"""Test parenmatch, coverage 91%.
This must currently be a gui test because ParenMatch methods use
several text methods not defined on idlelib.idle_test.mock_tk.Text.
"""
from idlelib.parenmatch import ParenMatch
from test.support import requires
requires('gui')
import unittest
from unittest.mock import Mock
from tkinter import Tk, Text
class DummyEditwin:
def __init__(self, text):
self.text = text
self.indentwidth = 8
self.tabwidth = 8
self.context_use_ps1 = True
class ParenMatchTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.root = Tk()
cls.root.withdraw()
cls.text = Text(cls.root)
cls.editwin = DummyEditwin(cls.text)
cls.editwin.text_frame = Mock()
@classmethod
def tearDownClass(cls):
del cls.text, cls.editwin
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
def tearDown(self):
self.text.delete('1.0', 'end')
def get_parenmatch(self):
pm = ParenMatch(self.editwin)
pm.bell = lambda: None
return pm
def test_paren_styles(self):
"""
Test ParenMatch with each style.
"""
text = self.text
pm = self.get_parenmatch()
for style, range1, range2 in (
('opener', ('1.10', '1.11'), ('1.10', '1.11')),
('default',('1.10', '1.11'),('1.10', '1.11')),
('parens', ('1.14', '1.15'), ('1.15', '1.16')),
('expression', ('1.10', '1.15'), ('1.10', '1.16'))):
with self.subTest(style=style):
text.delete('1.0', 'end')
pm.STYLE = style
text.insert('insert', 'def foobar(a, b')
pm.flash_paren_event('event')
self.assertIn('<<parenmatch-check-restore>>', text.event_info())
if style == 'parens':
self.assertTupleEqual(text.tag_nextrange('paren', '1.0'),
('1.10', '1.11'))
self.assertTupleEqual(
text.tag_prevrange('paren', 'end'), range1)
text.insert('insert', ')')
pm.restore_event()
self.assertNotIn('<<parenmatch-check-restore>>',
text.event_info())
self.assertEqual(text.tag_prevrange('paren', 'end'), ())
pm.paren_closed_event('event')
self.assertTupleEqual(
text.tag_prevrange('paren', 'end'), range2)
def test_paren_corner(self):
"""
Test corner cases in flash_paren_event and paren_closed_event.
These cases force conditional expression and alternate paths.
"""
text = self.text
pm = self.get_parenmatch()
text.insert('insert', '# this is a commen)')
pm.paren_closed_event('event')
text.insert('insert', '\ndef')
pm.flash_paren_event('event')
pm.paren_closed_event('event')
text.insert('insert', ' a, *arg)')
pm.paren_closed_event('event')
def test_handle_restore_timer(self):
pm = self.get_parenmatch()
pm.restore_event = Mock()
pm.handle_restore_timer(0)
self.assertTrue(pm.restore_event.called)
pm.restore_event.reset_mock()
pm.handle_restore_timer(1)
self.assertFalse(pm.restore_event.called)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,86 @@
"Test pathbrowser, coverage 95%."
from idlelib import pathbrowser
import unittest
from test.support import requires
from tkinter import Tk
import os.path
import pyclbr # for _modules
import sys # for sys.path
from idlelib.idle_test.mock_idle import Func
import idlelib # for __file__
from idlelib import browser
from idlelib.tree import TreeNode
class PathBrowserTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.pb = pathbrowser.PathBrowser(cls.root, _utest=True)
@classmethod
def tearDownClass(cls):
cls.pb.close()
cls.root.update_idletasks()
cls.root.destroy()
del cls.root, cls.pb
def test_init(self):
pb = self.pb
eq = self.assertEqual
eq(pb.master, self.root)
eq(pyclbr._modules, {})
self.assertIsInstance(pb.node, TreeNode)
self.assertIsNotNone(browser.file_open)
def test_settitle(self):
pb = self.pb
self.assertEqual(pb.top.title(), 'Path Browser')
self.assertEqual(pb.top.iconname(), 'Path Browser')
def test_rootnode(self):
pb = self.pb
rn = pb.rootnode()
self.assertIsInstance(rn, pathbrowser.PathBrowserTreeItem)
def test_close(self):
pb = self.pb
pb.top.destroy = Func()
pb.node.destroy = Func()
pb.close()
self.assertTrue(pb.top.destroy.called)
self.assertTrue(pb.node.destroy.called)
del pb.top.destroy, pb.node.destroy
class DirBrowserTreeItemTest(unittest.TestCase):
def test_DirBrowserTreeItem(self):
# Issue16226 - make sure that getting a sublist works
d = pathbrowser.DirBrowserTreeItem('')
d.GetSubList()
self.assertEqual('', d.GetText())
dir = os.path.split(os.path.abspath(idlelib.__file__))[0]
self.assertEqual(d.ispackagedir(dir), True)
self.assertEqual(d.ispackagedir(dir + '/Icons'), False)
class PathBrowserTreeItemTest(unittest.TestCase):
def test_PathBrowserTreeItem(self):
p = pathbrowser.PathBrowserTreeItem()
self.assertEqual(p.GetText(), 'sys.path')
sub = p.GetSubList()
self.assertEqual(len(sub), len(sys.path))
self.assertEqual(type(sub[0]), pathbrowser.DirBrowserTreeItem)
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)

View file

@ -0,0 +1,118 @@
"Test percolator, coverage 100%."
from idlelib.percolator import Percolator, Delegator
import unittest
from test.support import requires
requires('gui')
from tkinter import Text, Tk, END
class MyFilter(Delegator):
def __init__(self):
Delegator.__init__(self, None)
def insert(self, *args):
self.insert_called_with = args
self.delegate.insert(*args)
def delete(self, *args):
self.delete_called_with = args
self.delegate.delete(*args)
def uppercase_insert(self, index, chars, tags=None):
chars = chars.upper()
self.delegate.insert(index, chars)
def lowercase_insert(self, index, chars, tags=None):
chars = chars.lower()
self.delegate.insert(index, chars)
def dont_insert(self, index, chars, tags=None):
pass
class PercolatorTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.root = Tk()
cls.text = Text(cls.root)
@classmethod
def tearDownClass(cls):
del cls.text
cls.root.destroy()
del cls.root
def setUp(self):
self.percolator = Percolator(self.text)
self.filter_one = MyFilter()
self.filter_two = MyFilter()
self.percolator.insertfilter(self.filter_one)
self.percolator.insertfilter(self.filter_two)
def tearDown(self):
self.percolator.close()
self.text.delete('1.0', END)
def test_insertfilter(self):
self.assertIsNotNone(self.filter_one.delegate)
self.assertEqual(self.percolator.top, self.filter_two)
self.assertEqual(self.filter_two.delegate, self.filter_one)
self.assertEqual(self.filter_one.delegate, self.percolator.bottom)
def test_removefilter(self):
filter_three = MyFilter()
self.percolator.removefilter(self.filter_two)
self.assertEqual(self.percolator.top, self.filter_one)
self.assertIsNone(self.filter_two.delegate)
filter_three = MyFilter()
self.percolator.insertfilter(self.filter_two)
self.percolator.insertfilter(filter_three)
self.percolator.removefilter(self.filter_one)
self.assertEqual(self.percolator.top, filter_three)
self.assertEqual(filter_three.delegate, self.filter_two)
self.assertEqual(self.filter_two.delegate, self.percolator.bottom)
self.assertIsNone(self.filter_one.delegate)
def test_insert(self):
self.text.insert('insert', 'foo')
self.assertEqual(self.text.get('1.0', END), 'foo\n')
self.assertTupleEqual(self.filter_one.insert_called_with,
('insert', 'foo', None))
def test_modify_insert(self):
self.filter_one.insert = self.filter_one.uppercase_insert
self.text.insert('insert', 'bAr')
self.assertEqual(self.text.get('1.0', END), 'BAR\n')
def test_modify_chain_insert(self):
filter_three = MyFilter()
self.percolator.insertfilter(filter_three)
self.filter_two.insert = self.filter_two.uppercase_insert
self.filter_one.insert = self.filter_one.lowercase_insert
self.text.insert('insert', 'BaR')
self.assertEqual(self.text.get('1.0', END), 'bar\n')
def test_dont_insert(self):
self.filter_one.insert = self.filter_one.dont_insert
self.text.insert('insert', 'foo bar')
self.assertEqual(self.text.get('1.0', END), '\n')
self.filter_one.insert = self.filter_one.dont_insert
self.text.insert('insert', 'foo bar')
self.assertEqual(self.text.get('1.0', END), '\n')
def test_without_filter(self):
self.text.insert('insert', 'hello')
self.assertEqual(self.text.get('1.0', 'end'), 'hello\n')
def test_delete(self):
self.text.insert('insert', 'foo')
self.text.delete('1.0', '1.2')
self.assertEqual(self.text.get('1.0', END), 'o\n')
self.assertTupleEqual(self.filter_one.delete_called_with,
('1.0', '1.2'))
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,466 @@
"Test pyparse, coverage 96%."
from idlelib import pyparse
import unittest
from collections import namedtuple
class ParseMapTest(unittest.TestCase):
def test_parsemap(self):
keepwhite = {ord(c): ord(c) for c in ' \t\n\r'}
mapping = pyparse.ParseMap(keepwhite)
self.assertEqual(mapping[ord('\t')], ord('\t'))
self.assertEqual(mapping[ord('a')], ord('x'))
self.assertEqual(mapping[1000], ord('x'))
def test_trans(self):
# trans is the production instance of ParseMap, used in _study1
parser = pyparse.Parser(4, 4)
self.assertEqual('\t a([{b}])b"c\'d\n'.translate(pyparse.trans),
'xxx(((x)))x"x\'x\n')
class PyParseTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.parser = pyparse.Parser(indentwidth=4, tabwidth=4)
@classmethod
def tearDownClass(cls):
del cls.parser
def test_init(self):
self.assertEqual(self.parser.indentwidth, 4)
self.assertEqual(self.parser.tabwidth, 4)
def test_set_code(self):
eq = self.assertEqual
p = self.parser
setcode = p.set_code
# Not empty and doesn't end with newline.
with self.assertRaises(AssertionError):
setcode('a')
tests = ('',
'a\n')
for string in tests:
with self.subTest(string=string):
setcode(string)
eq(p.code, string)
eq(p.study_level, 0)
def test_find_good_parse_start(self):
eq = self.assertEqual
p = self.parser
setcode = p.set_code
start = p.find_good_parse_start
# Split def across lines.
setcode('"""This is a module docstring"""\n'
'class C():\n'
' def __init__(self, a,\n'
' b=True):\n'
' pass\n'
)
# No value sent for is_char_in_string().
self.assertIsNone(start())
# Make text look like a string. This returns pos as the start
# position, but it's set to None.
self.assertIsNone(start(is_char_in_string=lambda index: True))
# Make all text look like it's not in a string. This means that it
# found a good start position.
eq(start(is_char_in_string=lambda index: False), 44)
# If the beginning of the def line is not in a string, then it
# returns that as the index.
eq(start(is_char_in_string=lambda index: index > 44), 44)
# If the beginning of the def line is in a string, then it
# looks for a previous index.
eq(start(is_char_in_string=lambda index: index >= 44), 33)
# If everything before the 'def' is in a string, then returns None.
# The non-continuation def line returns 44 (see below).
eq(start(is_char_in_string=lambda index: index < 44), None)
# Code without extra line break in def line - mostly returns the same
# values.
setcode('"""This is a module docstring"""\n'
'class C():\n'
' def __init__(self, a, b=True):\n'
' pass\n'
)
eq(start(is_char_in_string=lambda index: False), 44)
eq(start(is_char_in_string=lambda index: index > 44), 44)
eq(start(is_char_in_string=lambda index: index >= 44), 33)
# When the def line isn't split, this returns which doesn't match the
# split line test.
eq(start(is_char_in_string=lambda index: index < 44), 44)
def test_set_lo(self):
code = (
'"""This is a module docstring"""\n'
'class C():\n'
' def __init__(self, a,\n'
' b=True):\n'
' pass\n'
)
p = self.parser
p.set_code(code)
# Previous character is not a newline.
with self.assertRaises(AssertionError):
p.set_lo(5)
# A value of 0 doesn't change self.code.
p.set_lo(0)
self.assertEqual(p.code, code)
# An index that is preceded by a newline.
p.set_lo(44)
self.assertEqual(p.code, code[44:])
def test_study1(self):
eq = self.assertEqual
p = self.parser
setcode = p.set_code
study = p._study1
(NONE, BACKSLASH, FIRST, NEXT, BRACKET) = range(5)
TestInfo = namedtuple('TestInfo', ['string', 'goodlines',
'continuation'])
tests = (
TestInfo('', [0], NONE),
# Docstrings.
TestInfo('"""This is a complete docstring."""\n', [0, 1], NONE),
TestInfo("'''This is a complete docstring.'''\n", [0, 1], NONE),
TestInfo('"""This is a continued docstring.\n', [0, 1], FIRST),
TestInfo("'''This is a continued docstring.\n", [0, 1], FIRST),
TestInfo('"""Closing quote does not match."\n', [0, 1], FIRST),
TestInfo('"""Bracket in docstring [\n', [0, 1], FIRST),
TestInfo("'''Incomplete two line docstring.\n\n", [0, 2], NEXT),
# Single-quoted strings.
TestInfo('"This is a complete string."\n', [0, 1], NONE),
TestInfo('"This is an incomplete string.\n', [0, 1], NONE),
TestInfo("'This is more incomplete.\n\n", [0, 1, 2], NONE),
# Comment (backslash does not continue comments).
TestInfo('# Comment\\\n', [0, 1], NONE),
# Brackets.
TestInfo('("""Complete string in bracket"""\n', [0, 1], BRACKET),
TestInfo('("""Open string in bracket\n', [0, 1], FIRST),
TestInfo('a = (1 + 2) - 5 *\\\n', [0, 1], BACKSLASH), # No bracket.
TestInfo('\n def function1(self, a,\n b):\n',
[0, 1, 3], NONE),
TestInfo('\n def function1(self, a,\\\n', [0, 1, 2], BRACKET),
TestInfo('\n def function1(self, a,\n', [0, 1, 2], BRACKET),
TestInfo('())\n', [0, 1], NONE), # Extra closer.
TestInfo(')(\n', [0, 1], BRACKET), # Extra closer.
# For the mismatched example, it doesn't look like contination.
TestInfo('{)(]\n', [0, 1], NONE), # Mismatched.
)
for test in tests:
with self.subTest(string=test.string):
setcode(test.string) # resets study_level
study()
eq(p.study_level, 1)
eq(p.goodlines, test.goodlines)
eq(p.continuation, test.continuation)
# Called again, just returns without reprocessing.
self.assertIsNone(study())
def test_get_continuation_type(self):
eq = self.assertEqual
p = self.parser
setcode = p.set_code
gettype = p.get_continuation_type
(NONE, BACKSLASH, FIRST, NEXT, BRACKET) = range(5)
TestInfo = namedtuple('TestInfo', ['string', 'continuation'])
tests = (
TestInfo('', NONE),
TestInfo('"""This is a continuation docstring.\n', FIRST),
TestInfo("'''This is a multiline-continued docstring.\n\n", NEXT),
TestInfo('a = (1 + 2) - 5 *\\\n', BACKSLASH),
TestInfo('\n def function1(self, a,\\\n', BRACKET)
)
for test in tests:
with self.subTest(string=test.string):
setcode(test.string)
eq(gettype(), test.continuation)
def test_study2(self):
eq = self.assertEqual
p = self.parser
setcode = p.set_code
study = p._study2
TestInfo = namedtuple('TestInfo', ['string', 'start', 'end', 'lastch',
'openbracket', 'bracketing'])
tests = (
TestInfo('', 0, 0, '', None, ((0, 0),)),
TestInfo("'''This is a multiline continutation docstring.\n\n",
0, 49, "'", None, ((0, 0), (0, 1), (49, 0))),
TestInfo(' # Comment\\\n',
0, 12, '', None, ((0, 0), (1, 1), (12, 0))),
# A comment without a space is a special case
TestInfo(' #Comment\\\n',
0, 0, '', None, ((0, 0),)),
# Backslash continuation.
TestInfo('a = (1 + 2) - 5 *\\\n',
0, 19, '*', None, ((0, 0), (4, 1), (11, 0))),
# Bracket continuation with close.
TestInfo('\n def function1(self, a,\n b):\n',
1, 48, ':', None, ((1, 0), (17, 1), (46, 0))),
# Bracket continuation with unneeded backslash.
TestInfo('\n def function1(self, a,\\\n',
1, 28, ',', 17, ((1, 0), (17, 1))),
# Bracket continuation.
TestInfo('\n def function1(self, a,\n',
1, 27, ',', 17, ((1, 0), (17, 1))),
# Bracket continuation with comment at end of line with text.
TestInfo('\n def function1(self, a, # End of line comment.\n',
1, 51, ',', 17, ((1, 0), (17, 1), (28, 2), (51, 1))),
# Multi-line statement with comment line in between code lines.
TestInfo(' a = ["first item",\n # Comment line\n "next item",\n',
0, 55, ',', 6, ((0, 0), (6, 1), (7, 2), (19, 1),
(23, 2), (38, 1), (42, 2), (53, 1))),
TestInfo('())\n',
0, 4, ')', None, ((0, 0), (0, 1), (2, 0), (3, 0))),
TestInfo(')(\n', 0, 3, '(', 1, ((0, 0), (1, 0), (1, 1))),
# Wrong closers still decrement stack level.
TestInfo('{)(]\n',
0, 5, ']', None, ((0, 0), (0, 1), (2, 0), (2, 1), (4, 0))),
# Character after backslash.
TestInfo(':\\a\n', 0, 4, '\\a', None, ((0, 0),)),
TestInfo('\n', 0, 0, '', None, ((0, 0),)),
)
for test in tests:
with self.subTest(string=test.string):
setcode(test.string)
study()
eq(p.study_level, 2)
eq(p.stmt_start, test.start)
eq(p.stmt_end, test.end)
eq(p.lastch, test.lastch)
eq(p.lastopenbracketpos, test.openbracket)
eq(p.stmt_bracketing, test.bracketing)
# Called again, just returns without reprocessing.
self.assertIsNone(study())
def test_get_num_lines_in_stmt(self):
eq = self.assertEqual
p = self.parser
setcode = p.set_code
getlines = p.get_num_lines_in_stmt
TestInfo = namedtuple('TestInfo', ['string', 'lines'])
tests = (
TestInfo('[x for x in a]\n', 1), # Closed on one line.
TestInfo('[x\nfor x in a\n', 2), # Not closed.
TestInfo('[x\\\nfor x in a\\\n', 2), # "", uneeded backslashes.
TestInfo('[x\nfor x in a\n]\n', 3), # Closed on multi-line.
TestInfo('\n"""Docstring comment L1"""\nL2\nL3\nL4\n', 1),
TestInfo('\n"""Docstring comment L1\nL2"""\nL3\nL4\n', 1),
TestInfo('\n"""Docstring comment L1\\\nL2\\\nL3\\\nL4\\\n', 4),
TestInfo('\n\n"""Docstring comment L1\\\nL2\\\nL3\\\nL4\\\n"""\n', 5)
)
# Blank string doesn't have enough elements in goodlines.
setcode('')
with self.assertRaises(IndexError):
getlines()
for test in tests:
with self.subTest(string=test.string):
setcode(test.string)
eq(getlines(), test.lines)
def test_compute_bracket_indent(self):
eq = self.assertEqual
p = self.parser
setcode = p.set_code
indent = p.compute_bracket_indent
TestInfo = namedtuple('TestInfo', ['string', 'spaces'])
tests = (
TestInfo('def function1(self, a,\n', 14),
# Characters after bracket.
TestInfo('\n def function1(self, a,\n', 18),
TestInfo('\n\tdef function1(self, a,\n', 18),
# No characters after bracket.
TestInfo('\n def function1(\n', 8),
TestInfo('\n\tdef function1(\n', 8),
TestInfo('\n def function1( \n', 8), # Ignore extra spaces.
TestInfo('[\n"first item",\n # Comment line\n "next item",\n', 0),
TestInfo('[\n "first item",\n # Comment line\n "next item",\n', 2),
TestInfo('["first item",\n # Comment line\n "next item",\n', 1),
TestInfo('(\n', 4),
TestInfo('(a\n', 1),
)
# Must be C_BRACKET continuation type.
setcode('def function1(self, a, b):\n')
with self.assertRaises(AssertionError):
indent()
for test in tests:
setcode(test.string)
eq(indent(), test.spaces)
def test_compute_backslash_indent(self):
eq = self.assertEqual
p = self.parser
setcode = p.set_code
indent = p.compute_backslash_indent
# Must be C_BACKSLASH continuation type.
errors = (('def function1(self, a, b\\\n'), # Bracket.
(' """ (\\\n'), # Docstring.
('a = #\\\n'), # Inline comment.
)
for string in errors:
with self.subTest(string=string):
setcode(string)
with self.assertRaises(AssertionError):
indent()
TestInfo = namedtuple('TestInfo', ('string', 'spaces'))
tests = (TestInfo('a = (1 + 2) - 5 *\\\n', 4),
TestInfo('a = 1 + 2 - 5 *\\\n', 4),
TestInfo(' a = 1 + 2 - 5 *\\\n', 8),
TestInfo(' a = "spam"\\\n', 6),
TestInfo(' a = \\\n"a"\\\n', 4),
TestInfo(' a = #\\\n"a"\\\n', 5),
TestInfo('a == \\\n', 2),
TestInfo('a != \\\n', 2),
# Difference between containing = and those not.
TestInfo('\\\n', 2),
TestInfo(' \\\n', 6),
TestInfo('\t\\\n', 6),
TestInfo('a\\\n', 3),
TestInfo('{}\\\n', 4),
TestInfo('(1 + 2) - 5 *\\\n', 3),
)
for test in tests:
with self.subTest(string=test.string):
setcode(test.string)
eq(indent(), test.spaces)
def test_get_base_indent_string(self):
eq = self.assertEqual
p = self.parser
setcode = p.set_code
baseindent = p.get_base_indent_string
TestInfo = namedtuple('TestInfo', ['string', 'indent'])
tests = (TestInfo('', ''),
TestInfo('def a():\n', ''),
TestInfo('\tdef a():\n', '\t'),
TestInfo(' def a():\n', ' '),
TestInfo(' def a(\n', ' '),
TestInfo('\t\n def a(\n', ' '),
TestInfo('\t\n # Comment.\n', ' '),
)
for test in tests:
with self.subTest(string=test.string):
setcode(test.string)
eq(baseindent(), test.indent)
def test_is_block_opener(self):
yes = self.assertTrue
no = self.assertFalse
p = self.parser
setcode = p.set_code
opener = p.is_block_opener
TestInfo = namedtuple('TestInfo', ['string', 'assert_'])
tests = (
TestInfo('def a():\n', yes),
TestInfo('\n def function1(self, a,\n b):\n', yes),
TestInfo(':\n', yes),
TestInfo('a:\n', yes),
TestInfo('):\n', yes),
TestInfo('(:\n', yes),
TestInfo('":\n', no),
TestInfo('\n def function1(self, a,\n', no),
TestInfo('def function1(self, a):\n pass\n', no),
TestInfo('# A comment:\n', no),
TestInfo('"""A docstring:\n', no),
TestInfo('"""A docstring:\n', no),
)
for test in tests:
with self.subTest(string=test.string):
setcode(test.string)
test.assert_(opener())
def test_is_block_closer(self):
yes = self.assertTrue
no = self.assertFalse
p = self.parser
setcode = p.set_code
closer = p.is_block_closer
TestInfo = namedtuple('TestInfo', ['string', 'assert_'])
tests = (
TestInfo('return\n', yes),
TestInfo('\tbreak\n', yes),
TestInfo(' continue\n', yes),
TestInfo(' raise\n', yes),
TestInfo('pass \n', yes),
TestInfo('pass\t\n', yes),
TestInfo('return #\n', yes),
TestInfo('raised\n', no),
TestInfo('returning\n', no),
TestInfo('# return\n', no),
TestInfo('"""break\n', no),
TestInfo('"continue\n', no),
TestInfo('def function1(self, a):\n pass\n', yes),
)
for test in tests:
with self.subTest(string=test.string):
setcode(test.string)
test.assert_(closer())
def test_get_last_stmt_bracketing(self):
eq = self.assertEqual
p = self.parser
setcode = p.set_code
bracketing = p.get_last_stmt_bracketing
TestInfo = namedtuple('TestInfo', ['string', 'bracket'])
tests = (
TestInfo('', ((0, 0),)),
TestInfo('a\n', ((0, 0),)),
TestInfo('()()\n', ((0, 0), (0, 1), (2, 0), (2, 1), (4, 0))),
TestInfo('(\n)()\n', ((0, 0), (0, 1), (3, 0), (3, 1), (5, 0))),
TestInfo('()\n()\n', ((3, 0), (3, 1), (5, 0))),
TestInfo('()(\n)\n', ((0, 0), (0, 1), (2, 0), (2, 1), (5, 0))),
TestInfo('(())\n', ((0, 0), (0, 1), (1, 2), (3, 1), (4, 0))),
TestInfo('(\n())\n', ((0, 0), (0, 1), (2, 2), (4, 1), (5, 0))),
# Same as matched test.
TestInfo('{)(]\n', ((0, 0), (0, 1), (2, 0), (2, 1), (4, 0))),
TestInfo('(((())\n',
((0, 0), (0, 1), (1, 2), (2, 3), (3, 4), (5, 3), (6, 2))),
)
for test in tests:
with self.subTest(string=test.string):
setcode(test.string)
eq(bracketing(), test.bracket)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,42 @@
"Test pyshell, coverage 12%."
# Plus coverage of test_warning. Was 20% with test_openshell.
from idlelib import pyshell
import unittest
from test.support import requires
from tkinter import Tk
class PyShellFileListTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
@classmethod
def tearDownClass(cls):
#cls.root.update_idletasks()
## for id in cls.root.tk.call('after', 'info'):
## cls.root.after_cancel(id) # Need for EditorWindow.
cls.root.destroy()
del cls.root
def test_init(self):
psfl = pyshell.PyShellFileList(self.root)
self.assertEqual(psfl.EditorWindow, pyshell.PyShellEditorWindow)
self.assertIsNone(psfl.pyshell)
# The following sometimes causes 'invalid command name "109734456recolorize"'.
# Uncommenting after_cancel above prevents this, but results in
# TclError: bad window path name ".!listedtoplevel.!frame.text"
# which is normally prevented by after_cancel.
## def test_openshell(self):
## pyshell.use_subprocess = False
## ps = pyshell.PyShellFileList(self.root).open_shell()
## self.assertIsInstance(ps, pyshell.PyShell)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,352 @@
"""Test query, coverage 91%).
Non-gui tests for Query, SectionName, ModuleName, and HelpSource use
dummy versions that extract the non-gui methods and add other needed
attributes. GUI tests create an instance of each class and simulate
entries and button clicks. Subclass tests only target the new code in
the subclass definition.
The appearance of the widgets is checked by the Query and
HelpSource htests. These are run by running query.py.
"""
from idlelib import query
import unittest
from test.support import requires
from tkinter import Tk
import sys
from unittest import mock
from idlelib.idle_test.mock_tk import Var
# NON-GUI TESTS
class QueryTest(unittest.TestCase):
"Test Query base class."
class Dummy_Query:
# Test the following Query methods.
entry_ok = query.Query.entry_ok
ok = query.Query.ok
cancel = query.Query.cancel
# Add attributes and initialization needed for tests.
entry = Var()
entry_error = {}
def __init__(self, dummy_entry):
self.entry.set(dummy_entry)
self.entry_error['text'] = ''
self.result = None
self.destroyed = False
def showerror(self, message):
self.entry_error['text'] = message
def destroy(self):
self.destroyed = True
def test_entry_ok_blank(self):
dialog = self.Dummy_Query(' ')
self.assertEqual(dialog.entry_ok(), None)
self.assertEqual((dialog.result, dialog.destroyed), (None, False))
self.assertIn('blank line', dialog.entry_error['text'])
def test_entry_ok_good(self):
dialog = self.Dummy_Query(' good ')
Equal = self.assertEqual
Equal(dialog.entry_ok(), 'good')
Equal((dialog.result, dialog.destroyed), (None, False))
Equal(dialog.entry_error['text'], '')
def test_ok_blank(self):
dialog = self.Dummy_Query('')
dialog.entry.focus_set = mock.Mock()
self.assertEqual(dialog.ok(), None)
self.assertTrue(dialog.entry.focus_set.called)
del dialog.entry.focus_set
self.assertEqual((dialog.result, dialog.destroyed), (None, False))
def test_ok_good(self):
dialog = self.Dummy_Query('good')
self.assertEqual(dialog.ok(), None)
self.assertEqual((dialog.result, dialog.destroyed), ('good', True))
def test_cancel(self):
dialog = self.Dummy_Query('does not matter')
self.assertEqual(dialog.cancel(), None)
self.assertEqual((dialog.result, dialog.destroyed), (None, True))
class SectionNameTest(unittest.TestCase):
"Test SectionName subclass of Query."
class Dummy_SectionName:
entry_ok = query.SectionName.entry_ok # Function being tested.
used_names = ['used']
entry = Var()
entry_error = {}
def __init__(self, dummy_entry):
self.entry.set(dummy_entry)
self.entry_error['text'] = ''
def showerror(self, message):
self.entry_error['text'] = message
def test_blank_section_name(self):
dialog = self.Dummy_SectionName(' ')
self.assertEqual(dialog.entry_ok(), None)
self.assertIn('no name', dialog.entry_error['text'])
def test_used_section_name(self):
dialog = self.Dummy_SectionName('used')
self.assertEqual(dialog.entry_ok(), None)
self.assertIn('use', dialog.entry_error['text'])
def test_long_section_name(self):
dialog = self.Dummy_SectionName('good'*8)
self.assertEqual(dialog.entry_ok(), None)
self.assertIn('longer than 30', dialog.entry_error['text'])
def test_good_section_name(self):
dialog = self.Dummy_SectionName(' good ')
self.assertEqual(dialog.entry_ok(), 'good')
self.assertEqual(dialog.entry_error['text'], '')
class ModuleNameTest(unittest.TestCase):
"Test ModuleName subclass of Query."
class Dummy_ModuleName:
entry_ok = query.ModuleName.entry_ok # Function being tested.
text0 = ''
entry = Var()
entry_error = {}
def __init__(self, dummy_entry):
self.entry.set(dummy_entry)
self.entry_error['text'] = ''
def showerror(self, message):
self.entry_error['text'] = message
def test_blank_module_name(self):
dialog = self.Dummy_ModuleName(' ')
self.assertEqual(dialog.entry_ok(), None)
self.assertIn('no name', dialog.entry_error['text'])
def test_bogus_module_name(self):
dialog = self.Dummy_ModuleName('__name_xyz123_should_not_exist__')
self.assertEqual(dialog.entry_ok(), None)
self.assertIn('not found', dialog.entry_error['text'])
def test_c_source_name(self):
dialog = self.Dummy_ModuleName('itertools')
self.assertEqual(dialog.entry_ok(), None)
self.assertIn('source-based', dialog.entry_error['text'])
def test_good_module_name(self):
dialog = self.Dummy_ModuleName('idlelib')
self.assertTrue(dialog.entry_ok().endswith('__init__.py'))
self.assertEqual(dialog.entry_error['text'], '')
# 3 HelpSource test classes each test one function.
orig_platform = query.platform
class HelpsourceBrowsefileTest(unittest.TestCase):
"Test browse_file method of ModuleName subclass of Query."
class Dummy_HelpSource:
browse_file = query.HelpSource.browse_file
pathvar = Var()
def test_file_replaces_path(self):
dialog = self.Dummy_HelpSource()
# Path is widget entry, either '' or something.
# Func return is file dialog return, either '' or something.
# Func return should override widget entry.
# We need all 4 combinations to test all (most) code paths.
for path, func, result in (
('', lambda a,b,c:'', ''),
('', lambda a,b,c: __file__, __file__),
('htest', lambda a,b,c:'', 'htest'),
('htest', lambda a,b,c: __file__, __file__)):
with self.subTest():
dialog.pathvar.set(path)
dialog.askfilename = func
dialog.browse_file()
self.assertEqual(dialog.pathvar.get(), result)
class HelpsourcePathokTest(unittest.TestCase):
"Test path_ok method of HelpSource subclass of Query."
class Dummy_HelpSource:
path_ok = query.HelpSource.path_ok
path = Var()
path_error = {}
def __init__(self, dummy_path):
self.path.set(dummy_path)
self.path_error['text'] = ''
def showerror(self, message, widget=None):
self.path_error['text'] = message
@classmethod
def tearDownClass(cls):
query.platform = orig_platform
def test_path_ok_blank(self):
dialog = self.Dummy_HelpSource(' ')
self.assertEqual(dialog.path_ok(), None)
self.assertIn('no help file', dialog.path_error['text'])
def test_path_ok_bad(self):
dialog = self.Dummy_HelpSource(__file__ + 'bad-bad-bad')
self.assertEqual(dialog.path_ok(), None)
self.assertIn('not exist', dialog.path_error['text'])
def test_path_ok_web(self):
dialog = self.Dummy_HelpSource('')
Equal = self.assertEqual
for url in 'www.py.org', 'http://py.org':
with self.subTest():
dialog.path.set(url)
self.assertEqual(dialog.path_ok(), url)
self.assertEqual(dialog.path_error['text'], '')
def test_path_ok_file(self):
dialog = self.Dummy_HelpSource('')
for platform, prefix in ('darwin', 'file://'), ('other', ''):
with self.subTest():
query.platform = platform
dialog.path.set(__file__)
self.assertEqual(dialog.path_ok(), prefix + __file__)
self.assertEqual(dialog.path_error['text'], '')
class HelpsourceEntryokTest(unittest.TestCase):
"Test entry_ok method of HelpSource subclass of Query."
class Dummy_HelpSource:
entry_ok = query.HelpSource.entry_ok
entry_error = {}
path_error = {}
def item_ok(self):
return self.name
def path_ok(self):
return self.path
def test_entry_ok_helpsource(self):
dialog = self.Dummy_HelpSource()
for name, path, result in ((None, None, None),
(None, 'doc.txt', None),
('doc', None, None),
('doc', 'doc.txt', ('doc', 'doc.txt'))):
with self.subTest():
dialog.name, dialog.path = name, path
self.assertEqual(dialog.entry_ok(), result)
# GUI TESTS
class QueryGuiTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = root = Tk()
cls.root.withdraw()
cls.dialog = query.Query(root, 'TEST', 'test', _utest=True)
cls.dialog.destroy = mock.Mock()
@classmethod
def tearDownClass(cls):
del cls.dialog.destroy
del cls.dialog
cls.root.destroy()
del cls.root
def setUp(self):
self.dialog.entry.delete(0, 'end')
self.dialog.result = None
self.dialog.destroy.reset_mock()
def test_click_ok(self):
dialog = self.dialog
dialog.entry.insert(0, 'abc')
dialog.button_ok.invoke()
self.assertEqual(dialog.result, 'abc')
self.assertTrue(dialog.destroy.called)
def test_click_blank(self):
dialog = self.dialog
dialog.button_ok.invoke()
self.assertEqual(dialog.result, None)
self.assertFalse(dialog.destroy.called)
def test_click_cancel(self):
dialog = self.dialog
dialog.entry.insert(0, 'abc')
dialog.button_cancel.invoke()
self.assertEqual(dialog.result, None)
self.assertTrue(dialog.destroy.called)
class SectionnameGuiTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
def test_click_section_name(self):
root = Tk()
root.withdraw()
dialog = query.SectionName(root, 'T', 't', {'abc'}, _utest=True)
Equal = self.assertEqual
self.assertEqual(dialog.used_names, {'abc'})
dialog.entry.insert(0, 'okay')
dialog.button_ok.invoke()
self.assertEqual(dialog.result, 'okay')
del dialog
root.destroy()
del root
class ModulenameGuiTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
def test_click_module_name(self):
root = Tk()
root.withdraw()
dialog = query.ModuleName(root, 'T', 't', 'idlelib', _utest=True)
self.assertEqual(dialog.text0, 'idlelib')
self.assertEqual(dialog.entry.get(), 'idlelib')
dialog.button_ok.invoke()
self.assertTrue(dialog.result.endswith('__init__.py'))
del dialog
root.destroy()
del root
class HelpsourceGuiTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
def test_click_help_source(self):
root = Tk()
root.withdraw()
dialog = query.HelpSource(root, 'T', menuitem='__test__',
filepath=__file__, _utest=True)
Equal = self.assertEqual
Equal(dialog.entry.get(), '__test__')
Equal(dialog.path.get(), __file__)
dialog.button_ok.invoke()
prefix = "file://" if sys.platform == 'darwin' else ''
Equal(dialog.result, ('__test__', prefix + __file__))
del dialog
root.destroy()
del root
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)

View file

@ -0,0 +1,122 @@
"Test redirector, coverage 100%."
from idlelib.redirector import WidgetRedirector
import unittest
from test.support import requires
from tkinter import Tk, Text, TclError
from idlelib.idle_test.mock_idle import Func
class InitCloseTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.text = Text(cls.root)
@classmethod
def tearDownClass(cls):
del cls.text
cls.root.destroy()
del cls.root
def test_init(self):
redir = WidgetRedirector(self.text)
self.assertEqual(redir.widget, self.text)
self.assertEqual(redir.tk, self.text.tk)
self.assertRaises(TclError, WidgetRedirector, self.text)
redir.close() # restore self.tk, self.text
def test_close(self):
redir = WidgetRedirector(self.text)
redir.register('insert', Func)
redir.close()
self.assertEqual(redir._operations, {})
self.assertFalse(hasattr(self.text, 'widget'))
class WidgetRedirectorTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.text = Text(cls.root)
@classmethod
def tearDownClass(cls):
del cls.text
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
def setUp(self):
self.redir = WidgetRedirector(self.text)
self.func = Func()
self.orig_insert = self.redir.register('insert', self.func)
self.text.insert('insert', 'asdf') # leaves self.text empty
def tearDown(self):
self.text.delete('1.0', 'end')
self.redir.close()
def test_repr(self): # partly for 100% coverage
self.assertIn('Redirector', repr(self.redir))
self.assertIn('Original', repr(self.orig_insert))
def test_register(self):
self.assertEqual(self.text.get('1.0', 'end'), '\n')
self.assertEqual(self.func.args, ('insert', 'asdf'))
self.assertIn('insert', self.redir._operations)
self.assertIn('insert', self.text.__dict__)
self.assertEqual(self.text.insert, self.func)
def test_original_command(self):
self.assertEqual(self.orig_insert.operation, 'insert')
self.assertEqual(self.orig_insert.tk_call, self.text.tk.call)
self.orig_insert('insert', 'asdf')
self.assertEqual(self.text.get('1.0', 'end'), 'asdf\n')
def test_unregister(self):
self.assertIsNone(self.redir.unregister('invalid operation name'))
self.assertEqual(self.redir.unregister('insert'), self.func)
self.assertNotIn('insert', self.redir._operations)
self.assertNotIn('insert', self.text.__dict__)
def test_unregister_no_attribute(self):
del self.text.insert
self.assertEqual(self.redir.unregister('insert'), self.func)
def test_dispatch_intercept(self):
self.func.__init__(True)
self.assertTrue(self.redir.dispatch('insert', False))
self.assertFalse(self.func.args[0])
def test_dispatch_bypass(self):
self.orig_insert('insert', 'asdf')
# tk.call returns '' where Python would return None
self.assertEqual(self.redir.dispatch('delete', '1.0', 'end'), '')
self.assertEqual(self.text.get('1.0', 'end'), '\n')
def test_dispatch_error(self):
self.func.__init__(TclError())
self.assertEqual(self.redir.dispatch('insert', False), '')
self.assertEqual(self.redir.dispatch('invalid'), '')
def test_command_dispatch(self):
# Test that .__init__ causes redirection of tk calls
# through redir.dispatch
self.root.call(self.text._w, 'insert', 'hello')
self.assertEqual(self.func.args, ('hello',))
self.assertEqual(self.text.get('1.0', 'end'), '\n')
# Ensure that called through redir .dispatch and not through
# self.text.insert by having mock raise TclError.
self.func.__init__(TclError())
self.assertEqual(self.root.call(self.text._w, 'insert', 'boo'), '')
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,294 @@
"Test replace, coverage 78%."
from idlelib.replace import ReplaceDialog
import unittest
from test.support import requires
requires('gui')
from tkinter import Tk, Text
from unittest.mock import Mock
from idlelib.idle_test.mock_tk import Mbox
import idlelib.searchengine as se
orig_mbox = se.tkMessageBox
showerror = Mbox.showerror
class ReplaceDialogTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.root = Tk()
cls.root.withdraw()
se.tkMessageBox = Mbox
cls.engine = se.SearchEngine(cls.root)
cls.dialog = ReplaceDialog(cls.root, cls.engine)
cls.dialog.bell = lambda: None
cls.dialog.ok = Mock()
cls.text = Text(cls.root)
cls.text.undo_block_start = Mock()
cls.text.undo_block_stop = Mock()
cls.dialog.text = cls.text
@classmethod
def tearDownClass(cls):
se.tkMessageBox = orig_mbox
del cls.text, cls.dialog, cls.engine
cls.root.destroy()
del cls.root
def setUp(self):
self.text.insert('insert', 'This is a sample sTring')
def tearDown(self):
self.engine.patvar.set('')
self.dialog.replvar.set('')
self.engine.wordvar.set(False)
self.engine.casevar.set(False)
self.engine.revar.set(False)
self.engine.wrapvar.set(True)
self.engine.backvar.set(False)
showerror.title = ''
showerror.message = ''
self.text.delete('1.0', 'end')
def test_replace_simple(self):
# Test replace function with all options at default setting.
# Wrap around - True
# Regular Expression - False
# Match case - False
# Match word - False
# Direction - Forwards
text = self.text
equal = self.assertEqual
pv = self.engine.patvar
rv = self.dialog.replvar
replace = self.dialog.replace_it
# test accessor method
self.engine.setpat('asdf')
equal(self.engine.getpat(), pv.get())
# text found and replaced
pv.set('a')
rv.set('asdf')
replace()
equal(text.get('1.8', '1.12'), 'asdf')
# don't "match word" case
text.mark_set('insert', '1.0')
pv.set('is')
rv.set('hello')
replace()
equal(text.get('1.2', '1.7'), 'hello')
# don't "match case" case
pv.set('string')
rv.set('world')
replace()
equal(text.get('1.23', '1.28'), 'world')
# without "regular expression" case
text.mark_set('insert', 'end')
text.insert('insert', '\nline42:')
before_text = text.get('1.0', 'end')
pv.set(r'[a-z][\d]+')
replace()
after_text = text.get('1.0', 'end')
equal(before_text, after_text)
# test with wrap around selected and complete a cycle
text.mark_set('insert', '1.9')
pv.set('i')
rv.set('j')
replace()
equal(text.get('1.8'), 'i')
equal(text.get('2.1'), 'j')
replace()
equal(text.get('2.1'), 'j')
equal(text.get('1.8'), 'j')
before_text = text.get('1.0', 'end')
replace()
after_text = text.get('1.0', 'end')
equal(before_text, after_text)
# text not found
before_text = text.get('1.0', 'end')
pv.set('foobar')
replace()
after_text = text.get('1.0', 'end')
equal(before_text, after_text)
# test access method
self.dialog.find_it(0)
def test_replace_wrap_around(self):
text = self.text
equal = self.assertEqual
pv = self.engine.patvar
rv = self.dialog.replvar
replace = self.dialog.replace_it
self.engine.wrapvar.set(False)
# replace candidate found both after and before 'insert'
text.mark_set('insert', '1.4')
pv.set('i')
rv.set('j')
replace()
equal(text.get('1.2'), 'i')
equal(text.get('1.5'), 'j')
replace()
equal(text.get('1.2'), 'i')
equal(text.get('1.20'), 'j')
replace()
equal(text.get('1.2'), 'i')
# replace candidate found only before 'insert'
text.mark_set('insert', '1.8')
pv.set('is')
before_text = text.get('1.0', 'end')
replace()
after_text = text.get('1.0', 'end')
equal(before_text, after_text)
def test_replace_whole_word(self):
text = self.text
equal = self.assertEqual
pv = self.engine.patvar
rv = self.dialog.replvar
replace = self.dialog.replace_it
self.engine.wordvar.set(True)
pv.set('is')
rv.set('hello')
replace()
equal(text.get('1.0', '1.4'), 'This')
equal(text.get('1.5', '1.10'), 'hello')
def test_replace_match_case(self):
equal = self.assertEqual
text = self.text
pv = self.engine.patvar
rv = self.dialog.replvar
replace = self.dialog.replace_it
self.engine.casevar.set(True)
before_text = self.text.get('1.0', 'end')
pv.set('this')
rv.set('that')
replace()
after_text = self.text.get('1.0', 'end')
equal(before_text, after_text)
pv.set('This')
replace()
equal(text.get('1.0', '1.4'), 'that')
def test_replace_regex(self):
equal = self.assertEqual
text = self.text
pv = self.engine.patvar
rv = self.dialog.replvar
replace = self.dialog.replace_it
self.engine.revar.set(True)
before_text = text.get('1.0', 'end')
pv.set(r'[a-z][\d]+')
rv.set('hello')
replace()
after_text = text.get('1.0', 'end')
equal(before_text, after_text)
text.insert('insert', '\nline42')
replace()
equal(text.get('2.0', '2.8'), 'linhello')
pv.set('')
replace()
self.assertIn('error', showerror.title)
self.assertIn('Empty', showerror.message)
pv.set(r'[\d')
replace()
self.assertIn('error', showerror.title)
self.assertIn('Pattern', showerror.message)
showerror.title = ''
showerror.message = ''
pv.set('[a]')
rv.set('test\\')
replace()
self.assertIn('error', showerror.title)
self.assertIn('Invalid Replace Expression', showerror.message)
# test access method
self.engine.setcookedpat("?")
equal(pv.get(), "\\?")
def test_replace_backwards(self):
equal = self.assertEqual
text = self.text
pv = self.engine.patvar
rv = self.dialog.replvar
replace = self.dialog.replace_it
self.engine.backvar.set(True)
text.insert('insert', '\nis as ')
pv.set('is')
rv.set('was')
replace()
equal(text.get('1.2', '1.4'), 'is')
equal(text.get('2.0', '2.3'), 'was')
replace()
equal(text.get('1.5', '1.8'), 'was')
replace()
equal(text.get('1.2', '1.5'), 'was')
def test_replace_all(self):
text = self.text
pv = self.engine.patvar
rv = self.dialog.replvar
replace_all = self.dialog.replace_all
text.insert('insert', '\n')
text.insert('insert', text.get('1.0', 'end')*100)
pv.set('is')
rv.set('was')
replace_all()
self.assertNotIn('is', text.get('1.0', 'end'))
self.engine.revar.set(True)
pv.set('')
replace_all()
self.assertIn('error', showerror.title)
self.assertIn('Empty', showerror.message)
pv.set('[s][T]')
rv.set('\\')
replace_all()
self.engine.revar.set(False)
pv.set('text which is not present')
rv.set('foobar')
replace_all()
def test_default_command(self):
text = self.text
pv = self.engine.patvar
rv = self.dialog.replvar
replace_find = self.dialog.default_command
equal = self.assertEqual
pv.set('This')
rv.set('was')
replace_find()
equal(text.get('sel.first', 'sel.last'), 'was')
self.engine.revar.set(True)
pv.set('')
replace_find()
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,29 @@
"Test rpc, coverage 20%."
from idlelib import rpc
import unittest
class CodePicklerTest(unittest.TestCase):
def test_pickle_unpickle(self):
def f(): return a + b + c
func, (cbytes,) = rpc.pickle_code(f.__code__)
self.assertIs(func, rpc.unpickle_code)
self.assertIn(b'test_rpc.py', cbytes)
code = rpc.unpickle_code(cbytes)
self.assertEqual(code.co_names, ('a', 'b', 'c'))
def test_code_pickler(self):
self.assertIn(type((lambda:None).__code__),
rpc.CodePickler.dispatch_table)
def test_dumps(self):
def f(): pass
# The main test here is that pickling code does not raise.
self.assertIn(b'test_rpc.py', rpc.dumps(f.__code__))
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,53 @@
"Test rstrip, coverage 100%."
from idlelib import rstrip
import unittest
from idlelib.idle_test.mock_idle import Editor
class rstripTest(unittest.TestCase):
def test_rstrip_line(self):
editor = Editor()
text = editor.text
do_rstrip = rstrip.Rstrip(editor).do_rstrip
do_rstrip()
self.assertEqual(text.get('1.0', 'insert'), '')
text.insert('1.0', ' ')
do_rstrip()
self.assertEqual(text.get('1.0', 'insert'), '')
text.insert('1.0', ' \n')
do_rstrip()
self.assertEqual(text.get('1.0', 'insert'), '\n')
def test_rstrip_multiple(self):
editor = Editor()
# Comment above, uncomment 3 below to test with real Editor & Text.
#from idlelib.editor import EditorWindow as Editor
#from tkinter import Tk
#editor = Editor(root=Tk())
text = editor.text
do_rstrip = rstrip.Rstrip(editor).do_rstrip
original = (
"Line with an ending tab \n"
"Line ending in 5 spaces \n"
"Linewithnospaces\n"
" indented line\n"
" indented line with trailing space \n"
" ")
stripped = (
"Line with an ending tab\n"
"Line ending in 5 spaces\n"
"Linewithnospaces\n"
" indented line\n"
" indented line with trailing space\n")
text.insert('1.0', original)
do_rstrip()
self.assertEqual(text.get('1.0', 'insert'), stripped)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,264 @@
"Test run, coverage 42%."
from idlelib import run
import unittest
from unittest import mock
from test.support import captured_stderr
import io
class RunTest(unittest.TestCase):
def test_print_exception_unhashable(self):
class UnhashableException(Exception):
def __eq__(self, other):
return True
ex1 = UnhashableException('ex1')
ex2 = UnhashableException('ex2')
try:
raise ex2 from ex1
except UnhashableException:
try:
raise ex1
except UnhashableException:
with captured_stderr() as output:
with mock.patch.object(run,
'cleanup_traceback') as ct:
ct.side_effect = lambda t, e: t
run.print_exception()
tb = output.getvalue().strip().splitlines()
self.assertEqual(11, len(tb))
self.assertIn('UnhashableException: ex2', tb[3])
self.assertIn('UnhashableException: ex1', tb[10])
# PseudoFile tests.
class S(str):
def __str__(self):
return '%s:str' % type(self).__name__
def __unicode__(self):
return '%s:unicode' % type(self).__name__
def __len__(self):
return 3
def __iter__(self):
return iter('abc')
def __getitem__(self, *args):
return '%s:item' % type(self).__name__
def __getslice__(self, *args):
return '%s:slice' % type(self).__name__
class MockShell:
def __init__(self):
self.reset()
def write(self, *args):
self.written.append(args)
def readline(self):
return self.lines.pop()
def close(self):
pass
def reset(self):
self.written = []
def push(self, lines):
self.lines = list(lines)[::-1]
class PseudeInputFilesTest(unittest.TestCase):
def test_misc(self):
shell = MockShell()
f = run.PseudoInputFile(shell, 'stdin', 'utf-8')
self.assertIsInstance(f, io.TextIOBase)
self.assertEqual(f.encoding, 'utf-8')
self.assertIsNone(f.errors)
self.assertIsNone(f.newlines)
self.assertEqual(f.name, '<stdin>')
self.assertFalse(f.closed)
self.assertTrue(f.isatty())
self.assertTrue(f.readable())
self.assertFalse(f.writable())
self.assertFalse(f.seekable())
def test_unsupported(self):
shell = MockShell()
f = run.PseudoInputFile(shell, 'stdin', 'utf-8')
self.assertRaises(OSError, f.fileno)
self.assertRaises(OSError, f.tell)
self.assertRaises(OSError, f.seek, 0)
self.assertRaises(OSError, f.write, 'x')
self.assertRaises(OSError, f.writelines, ['x'])
def test_read(self):
shell = MockShell()
f = run.PseudoInputFile(shell, 'stdin', 'utf-8')
shell.push(['one\n', 'two\n', ''])
self.assertEqual(f.read(), 'one\ntwo\n')
shell.push(['one\n', 'two\n', ''])
self.assertEqual(f.read(-1), 'one\ntwo\n')
shell.push(['one\n', 'two\n', ''])
self.assertEqual(f.read(None), 'one\ntwo\n')
shell.push(['one\n', 'two\n', 'three\n', ''])
self.assertEqual(f.read(2), 'on')
self.assertEqual(f.read(3), 'e\nt')
self.assertEqual(f.read(10), 'wo\nthree\n')
shell.push(['one\n', 'two\n'])
self.assertEqual(f.read(0), '')
self.assertRaises(TypeError, f.read, 1.5)
self.assertRaises(TypeError, f.read, '1')
self.assertRaises(TypeError, f.read, 1, 1)
def test_readline(self):
shell = MockShell()
f = run.PseudoInputFile(shell, 'stdin', 'utf-8')
shell.push(['one\n', 'two\n', 'three\n', 'four\n'])
self.assertEqual(f.readline(), 'one\n')
self.assertEqual(f.readline(-1), 'two\n')
self.assertEqual(f.readline(None), 'three\n')
shell.push(['one\ntwo\n'])
self.assertEqual(f.readline(), 'one\n')
self.assertEqual(f.readline(), 'two\n')
shell.push(['one', 'two', 'three'])
self.assertEqual(f.readline(), 'one')
self.assertEqual(f.readline(), 'two')
shell.push(['one\n', 'two\n', 'three\n'])
self.assertEqual(f.readline(2), 'on')
self.assertEqual(f.readline(1), 'e')
self.assertEqual(f.readline(1), '\n')
self.assertEqual(f.readline(10), 'two\n')
shell.push(['one\n', 'two\n'])
self.assertEqual(f.readline(0), '')
self.assertRaises(TypeError, f.readlines, 1.5)
self.assertRaises(TypeError, f.readlines, '1')
self.assertRaises(TypeError, f.readlines, 1, 1)
def test_readlines(self):
shell = MockShell()
f = run.PseudoInputFile(shell, 'stdin', 'utf-8')
shell.push(['one\n', 'two\n', ''])
self.assertEqual(f.readlines(), ['one\n', 'two\n'])
shell.push(['one\n', 'two\n', ''])
self.assertEqual(f.readlines(-1), ['one\n', 'two\n'])
shell.push(['one\n', 'two\n', ''])
self.assertEqual(f.readlines(None), ['one\n', 'two\n'])
shell.push(['one\n', 'two\n', ''])
self.assertEqual(f.readlines(0), ['one\n', 'two\n'])
shell.push(['one\n', 'two\n', ''])
self.assertEqual(f.readlines(3), ['one\n'])
shell.push(['one\n', 'two\n', ''])
self.assertEqual(f.readlines(4), ['one\n', 'two\n'])
shell.push(['one\n', 'two\n', ''])
self.assertRaises(TypeError, f.readlines, 1.5)
self.assertRaises(TypeError, f.readlines, '1')
self.assertRaises(TypeError, f.readlines, 1, 1)
def test_close(self):
shell = MockShell()
f = run.PseudoInputFile(shell, 'stdin', 'utf-8')
shell.push(['one\n', 'two\n', ''])
self.assertFalse(f.closed)
self.assertEqual(f.readline(), 'one\n')
f.close()
self.assertFalse(f.closed)
self.assertEqual(f.readline(), 'two\n')
self.assertRaises(TypeError, f.close, 1)
class PseudeOutputFilesTest(unittest.TestCase):
def test_misc(self):
shell = MockShell()
f = run.PseudoOutputFile(shell, 'stdout', 'utf-8')
self.assertIsInstance(f, io.TextIOBase)
self.assertEqual(f.encoding, 'utf-8')
self.assertIsNone(f.errors)
self.assertIsNone(f.newlines)
self.assertEqual(f.name, '<stdout>')
self.assertFalse(f.closed)
self.assertTrue(f.isatty())
self.assertFalse(f.readable())
self.assertTrue(f.writable())
self.assertFalse(f.seekable())
def test_unsupported(self):
shell = MockShell()
f = run.PseudoOutputFile(shell, 'stdout', 'utf-8')
self.assertRaises(OSError, f.fileno)
self.assertRaises(OSError, f.tell)
self.assertRaises(OSError, f.seek, 0)
self.assertRaises(OSError, f.read, 0)
self.assertRaises(OSError, f.readline, 0)
def test_write(self):
shell = MockShell()
f = run.PseudoOutputFile(shell, 'stdout', 'utf-8')
f.write('test')
self.assertEqual(shell.written, [('test', 'stdout')])
shell.reset()
f.write('t\xe8st')
self.assertEqual(shell.written, [('t\xe8st', 'stdout')])
shell.reset()
f.write(S('t\xe8st'))
self.assertEqual(shell.written, [('t\xe8st', 'stdout')])
self.assertEqual(type(shell.written[0][0]), str)
shell.reset()
self.assertRaises(TypeError, f.write)
self.assertEqual(shell.written, [])
self.assertRaises(TypeError, f.write, b'test')
self.assertRaises(TypeError, f.write, 123)
self.assertEqual(shell.written, [])
self.assertRaises(TypeError, f.write, 'test', 'spam')
self.assertEqual(shell.written, [])
def test_writelines(self):
shell = MockShell()
f = run.PseudoOutputFile(shell, 'stdout', 'utf-8')
f.writelines([])
self.assertEqual(shell.written, [])
shell.reset()
f.writelines(['one\n', 'two'])
self.assertEqual(shell.written,
[('one\n', 'stdout'), ('two', 'stdout')])
shell.reset()
f.writelines(['on\xe8\n', 'tw\xf2'])
self.assertEqual(shell.written,
[('on\xe8\n', 'stdout'), ('tw\xf2', 'stdout')])
shell.reset()
f.writelines([S('t\xe8st')])
self.assertEqual(shell.written, [('t\xe8st', 'stdout')])
self.assertEqual(type(shell.written[0][0]), str)
shell.reset()
self.assertRaises(TypeError, f.writelines)
self.assertEqual(shell.written, [])
self.assertRaises(TypeError, f.writelines, 123)
self.assertEqual(shell.written, [])
self.assertRaises(TypeError, f.writelines, [b'test'])
self.assertRaises(TypeError, f.writelines, [123])
self.assertEqual(shell.written, [])
self.assertRaises(TypeError, f.writelines, [], [])
self.assertEqual(shell.written, [])
def test_close(self):
shell = MockShell()
f = run.PseudoOutputFile(shell, 'stdout', 'utf-8')
self.assertFalse(f.closed)
f.write('test')
f.close()
self.assertTrue(f.closed)
self.assertRaises(ValueError, f.write, 'x')
self.assertEqual(shell.written, [('test', 'stdout')])
f.close()
self.assertRaises(TypeError, f.close, 1)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,33 @@
"Test runscript, coverage 16%."
from idlelib import runscript
import unittest
from test.support import requires
from tkinter import Tk
from idlelib.editor import EditorWindow
class ScriptBindingTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
@classmethod
def tearDownClass(cls):
cls.root.update_idletasks()
for id in cls.root.tk.call('after', 'info'):
cls.root.after_cancel(id) # Need for EditorWindow.
cls.root.destroy()
del cls.root
def test_init(self):
ew = EditorWindow(root=self.root)
sb = runscript.ScriptBinding(ew)
ew._close()
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,27 @@
"Test scrolledlist, coverage 38%."
from idlelib.scrolledlist import ScrolledList
import unittest
from test.support import requires
requires('gui')
from tkinter import Tk
class ScrolledListTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.root = Tk()
@classmethod
def tearDownClass(cls):
cls.root.destroy()
del cls.root
def test_init(self):
ScrolledList(self.root)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,80 @@
"Test search, coverage 69%."
from idlelib import search
import unittest
from test.support import requires
requires('gui')
from tkinter import Tk, Text, BooleanVar
from idlelib import searchengine
# Does not currently test the event handler wrappers.
# A usage test should simulate clicks and check highlighting.
# Tests need to be coordinated with SearchDialogBase tests
# to avoid duplication.
class SearchDialogTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.root = Tk()
@classmethod
def tearDownClass(cls):
cls.root.destroy()
del cls.root
def setUp(self):
self.engine = searchengine.SearchEngine(self.root)
self.dialog = search.SearchDialog(self.root, self.engine)
self.dialog.bell = lambda: None
self.text = Text(self.root)
self.text.insert('1.0', 'Hello World!')
def test_find_again(self):
# Search for various expressions
text = self.text
self.engine.setpat('')
self.assertFalse(self.dialog.find_again(text))
self.dialog.bell = lambda: None
self.engine.setpat('Hello')
self.assertTrue(self.dialog.find_again(text))
self.engine.setpat('Goodbye')
self.assertFalse(self.dialog.find_again(text))
self.engine.setpat('World!')
self.assertTrue(self.dialog.find_again(text))
self.engine.setpat('Hello World!')
self.assertTrue(self.dialog.find_again(text))
# Regular expression
self.engine.revar = BooleanVar(self.root, True)
self.engine.setpat('W[aeiouy]r')
self.assertTrue(self.dialog.find_again(text))
def test_find_selection(self):
# Select some text and make sure it's found
text = self.text
# Add additional line to find
self.text.insert('2.0', 'Hello World!')
text.tag_add('sel', '1.0', '1.4') # Select 'Hello'
self.assertTrue(self.dialog.find_selection(text))
text.tag_remove('sel', '1.0', 'end')
text.tag_add('sel', '1.6', '1.11') # Select 'World!'
self.assertTrue(self.dialog.find_selection(text))
text.tag_remove('sel', '1.0', 'end')
text.tag_add('sel', '1.0', '1.11') # Select 'Hello World!'
self.assertTrue(self.dialog.find_selection(text))
# Remove additional line
text.delete('2.0', 'end')
if __name__ == '__main__':
unittest.main(verbosity=2, exit=2)

View file

@ -0,0 +1,156 @@
"Test searchbase, coverage 98%."
# The only thing not covered is inconsequential --
# testing skipping of suite when self.needwrapbutton is false.
import unittest
from test.support import requires
from tkinter import Tk, Frame ##, BooleanVar, StringVar
from idlelib import searchengine as se
from idlelib import searchbase as sdb
from idlelib.idle_test.mock_idle import Func
## from idlelib.idle_test.mock_tk import Var
# The ## imports above & following could help make some tests gui-free.
# However, they currently make radiobutton tests fail.
##def setUpModule():
## # Replace tk objects used to initialize se.SearchEngine.
## se.BooleanVar = Var
## se.StringVar = Var
##
##def tearDownModule():
## se.BooleanVar = BooleanVar
## se.StringVar = StringVar
class SearchDialogBaseTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
@classmethod
def tearDownClass(cls):
cls.root.destroy()
del cls.root
def setUp(self):
self.engine = se.SearchEngine(self.root) # None also seems to work
self.dialog = sdb.SearchDialogBase(root=self.root, engine=self.engine)
def tearDown(self):
self.dialog.close()
def test_open_and_close(self):
# open calls create_widgets, which needs default_command
self.dialog.default_command = None
# Since text parameter of .open is not used in base class,
# pass dummy 'text' instead of tk.Text().
self.dialog.open('text')
self.assertEqual(self.dialog.top.state(), 'normal')
self.dialog.close()
self.assertEqual(self.dialog.top.state(), 'withdrawn')
self.dialog.open('text', searchphrase="hello")
self.assertEqual(self.dialog.ent.get(), 'hello')
self.dialog.close()
def test_create_widgets(self):
self.dialog.create_entries = Func()
self.dialog.create_option_buttons = Func()
self.dialog.create_other_buttons = Func()
self.dialog.create_command_buttons = Func()
self.dialog.default_command = None
self.dialog.create_widgets()
self.assertTrue(self.dialog.create_entries.called)
self.assertTrue(self.dialog.create_option_buttons.called)
self.assertTrue(self.dialog.create_other_buttons.called)
self.assertTrue(self.dialog.create_command_buttons.called)
def test_make_entry(self):
equal = self.assertEqual
self.dialog.row = 0
self.dialog.top = self.root
entry, label = self.dialog.make_entry("Test:", 'hello')
equal(label['text'], 'Test:')
self.assertIn(entry.get(), 'hello')
egi = entry.grid_info()
equal(int(egi['row']), 0)
equal(int(egi['column']), 1)
equal(int(egi['rowspan']), 1)
equal(int(egi['columnspan']), 1)
equal(self.dialog.row, 1)
def test_create_entries(self):
self.dialog.top = self.root
self.dialog.row = 0
self.engine.setpat('hello')
self.dialog.create_entries()
self.assertIn(self.dialog.ent.get(), 'hello')
def test_make_frame(self):
self.dialog.row = 0
self.dialog.top = self.root
frame, label = self.dialog.make_frame()
self.assertEqual(label, '')
self.assertIsInstance(frame, Frame)
frame, label = self.dialog.make_frame('testlabel')
self.assertEqual(label['text'], 'testlabel')
self.assertIsInstance(frame, Frame)
def btn_test_setup(self, meth):
self.dialog.top = self.root
self.dialog.row = 0
return meth()
def test_create_option_buttons(self):
e = self.engine
for state in (0, 1):
for var in (e.revar, e.casevar, e.wordvar, e.wrapvar):
var.set(state)
frame, options = self.btn_test_setup(
self.dialog.create_option_buttons)
for spec, button in zip (options, frame.pack_slaves()):
var, label = spec
self.assertEqual(button['text'], label)
self.assertEqual(var.get(), state)
def test_create_other_buttons(self):
for state in (False, True):
var = self.engine.backvar
var.set(state)
frame, others = self.btn_test_setup(
self.dialog.create_other_buttons)
buttons = frame.pack_slaves()
for spec, button in zip(others, buttons):
val, label = spec
self.assertEqual(button['text'], label)
if val == state:
# hit other button, then this one
# indexes depend on button order
self.assertEqual(var.get(), state)
def test_make_button(self):
self.dialog.top = self.root
self.dialog.buttonframe = Frame(self.dialog.top)
btn = self.dialog.make_button('Test', self.dialog.close)
self.assertEqual(btn['text'], 'Test')
def test_create_command_buttons(self):
self.dialog.top = self.root
self.dialog.create_command_buttons()
# Look for close button command in buttonframe
closebuttoncommand = ''
for child in self.dialog.buttonframe.winfo_children():
if child['text'] == 'close':
closebuttoncommand = child['command']
self.assertIn('close', closebuttoncommand)
if __name__ == '__main__':
unittest.main(verbosity=2, exit=2)

View file

@ -0,0 +1,330 @@
"Test searchengine, coverage 99%."
from idlelib import searchengine as se
import unittest
# from test.support import requires
from tkinter import BooleanVar, StringVar, TclError # ,Tk, Text
import tkinter.messagebox as tkMessageBox
from idlelib.idle_test.mock_tk import Var, Mbox
from idlelib.idle_test.mock_tk import Text as mockText
import re
# With mock replacements, the module does not use any gui widgets.
# The use of tk.Text is avoided (for now, until mock Text is improved)
# by patching instances with an index function returning what is needed.
# This works because mock Text.get does not use .index.
# The tkinter imports are used to restore searchengine.
def setUpModule():
# Replace s-e module tkinter imports other than non-gui TclError.
se.BooleanVar = Var
se.StringVar = Var
se.tkMessageBox = Mbox
def tearDownModule():
# Restore 'just in case', though other tests should also replace.
se.BooleanVar = BooleanVar
se.StringVar = StringVar
se.tkMessageBox = tkMessageBox
class Mock:
def __init__(self, *args, **kwargs): pass
class GetTest(unittest.TestCase):
# SearchEngine.get returns singleton created & saved on first call.
def test_get(self):
saved_Engine = se.SearchEngine
se.SearchEngine = Mock # monkey-patch class
try:
root = Mock()
engine = se.get(root)
self.assertIsInstance(engine, se.SearchEngine)
self.assertIs(root._searchengine, engine)
self.assertIs(se.get(root), engine)
finally:
se.SearchEngine = saved_Engine # restore class to module
class GetLineColTest(unittest.TestCase):
# Test simple text-independent helper function
def test_get_line_col(self):
self.assertEqual(se.get_line_col('1.0'), (1, 0))
self.assertEqual(se.get_line_col('1.11'), (1, 11))
self.assertRaises(ValueError, se.get_line_col, ('1.0 lineend'))
self.assertRaises(ValueError, se.get_line_col, ('end'))
class GetSelectionTest(unittest.TestCase):
# Test text-dependent helper function.
## # Need gui for text.index('sel.first/sel.last/insert').
## @classmethod
## def setUpClass(cls):
## requires('gui')
## cls.root = Tk()
##
## @classmethod
## def tearDownClass(cls):
## cls.root.destroy()
## del cls.root
def test_get_selection(self):
# text = Text(master=self.root)
text = mockText()
text.insert('1.0', 'Hello World!')
# fix text.index result when called in get_selection
def sel(s):
# select entire text, cursor irrelevant
if s == 'sel.first': return '1.0'
if s == 'sel.last': return '1.12'
raise TclError
text.index = sel # replaces .tag_add('sel', '1.0, '1.12')
self.assertEqual(se.get_selection(text), ('1.0', '1.12'))
def mark(s):
# no selection, cursor after 'Hello'
if s == 'insert': return '1.5'
raise TclError
text.index = mark # replaces .mark_set('insert', '1.5')
self.assertEqual(se.get_selection(text), ('1.5', '1.5'))
class ReverseSearchTest(unittest.TestCase):
# Test helper function that searches backwards within a line.
def test_search_reverse(self):
Equal = self.assertEqual
line = "Here is an 'is' test text."
prog = re.compile('is')
Equal(se.search_reverse(prog, line, len(line)).span(), (12, 14))
Equal(se.search_reverse(prog, line, 14).span(), (12, 14))
Equal(se.search_reverse(prog, line, 13).span(), (5, 7))
Equal(se.search_reverse(prog, line, 7).span(), (5, 7))
Equal(se.search_reverse(prog, line, 6), None)
class SearchEngineTest(unittest.TestCase):
# Test class methods that do not use Text widget.
def setUp(self):
self.engine = se.SearchEngine(root=None)
# Engine.root is only used to create error message boxes.
# The mock replacement ignores the root argument.
def test_is_get(self):
engine = self.engine
Equal = self.assertEqual
Equal(engine.getpat(), '')
engine.setpat('hello')
Equal(engine.getpat(), 'hello')
Equal(engine.isre(), False)
engine.revar.set(1)
Equal(engine.isre(), True)
Equal(engine.iscase(), False)
engine.casevar.set(1)
Equal(engine.iscase(), True)
Equal(engine.isword(), False)
engine.wordvar.set(1)
Equal(engine.isword(), True)
Equal(engine.iswrap(), True)
engine.wrapvar.set(0)
Equal(engine.iswrap(), False)
Equal(engine.isback(), False)
engine.backvar.set(1)
Equal(engine.isback(), True)
def test_setcookedpat(self):
engine = self.engine
engine.setcookedpat(r'\s')
self.assertEqual(engine.getpat(), r'\s')
engine.revar.set(1)
engine.setcookedpat(r'\s')
self.assertEqual(engine.getpat(), r'\\s')
def test_getcookedpat(self):
engine = self.engine
Equal = self.assertEqual
Equal(engine.getcookedpat(), '')
engine.setpat('hello')
Equal(engine.getcookedpat(), 'hello')
engine.wordvar.set(True)
Equal(engine.getcookedpat(), r'\bhello\b')
engine.wordvar.set(False)
engine.setpat(r'\s')
Equal(engine.getcookedpat(), r'\\s')
engine.revar.set(True)
Equal(engine.getcookedpat(), r'\s')
def test_getprog(self):
engine = self.engine
Equal = self.assertEqual
engine.setpat('Hello')
temppat = engine.getprog()
Equal(temppat.pattern, re.compile('Hello', re.IGNORECASE).pattern)
engine.casevar.set(1)
temppat = engine.getprog()
Equal(temppat.pattern, re.compile('Hello').pattern, 0)
engine.setpat('')
Equal(engine.getprog(), None)
engine.setpat('+')
engine.revar.set(1)
Equal(engine.getprog(), None)
self.assertEqual(Mbox.showerror.message,
'Error: nothing to repeat at position 0\nPattern: +')
def test_report_error(self):
showerror = Mbox.showerror
Equal = self.assertEqual
pat = '[a-z'
msg = 'unexpected end of regular expression'
Equal(self.engine.report_error(pat, msg), None)
Equal(showerror.title, 'Regular expression error')
expected_message = ("Error: " + msg + "\nPattern: [a-z")
Equal(showerror.message, expected_message)
Equal(self.engine.report_error(pat, msg, 5), None)
Equal(showerror.title, 'Regular expression error')
expected_message += "\nOffset: 5"
Equal(showerror.message, expected_message)
class SearchTest(unittest.TestCase):
# Test that search_text makes right call to right method.
@classmethod
def setUpClass(cls):
## requires('gui')
## cls.root = Tk()
## cls.text = Text(master=cls.root)
cls.text = mockText()
test_text = (
'First line\n'
'Line with target\n'
'Last line\n')
cls.text.insert('1.0', test_text)
cls.pat = re.compile('target')
cls.engine = se.SearchEngine(None)
cls.engine.search_forward = lambda *args: ('f', args)
cls.engine.search_backward = lambda *args: ('b', args)
## @classmethod
## def tearDownClass(cls):
## cls.root.destroy()
## del cls.root
def test_search(self):
Equal = self.assertEqual
engine = self.engine
search = engine.search_text
text = self.text
pat = self.pat
engine.patvar.set(None)
#engine.revar.set(pat)
Equal(search(text), None)
def mark(s):
# no selection, cursor after 'Hello'
if s == 'insert': return '1.5'
raise TclError
text.index = mark
Equal(search(text, pat), ('f', (text, pat, 1, 5, True, False)))
engine.wrapvar.set(False)
Equal(search(text, pat), ('f', (text, pat, 1, 5, False, False)))
engine.wrapvar.set(True)
engine.backvar.set(True)
Equal(search(text, pat), ('b', (text, pat, 1, 5, True, False)))
engine.backvar.set(False)
def sel(s):
if s == 'sel.first': return '2.10'
if s == 'sel.last': return '2.16'
raise TclError
text.index = sel
Equal(search(text, pat), ('f', (text, pat, 2, 16, True, False)))
Equal(search(text, pat, True), ('f', (text, pat, 2, 10, True, True)))
engine.backvar.set(True)
Equal(search(text, pat), ('b', (text, pat, 2, 10, True, False)))
Equal(search(text, pat, True), ('b', (text, pat, 2, 16, True, True)))
class ForwardBackwardTest(unittest.TestCase):
# Test that search_forward method finds the target.
## @classmethod
## def tearDownClass(cls):
## cls.root.destroy()
## del cls.root
@classmethod
def setUpClass(cls):
cls.engine = se.SearchEngine(None)
## requires('gui')
## cls.root = Tk()
## cls.text = Text(master=cls.root)
cls.text = mockText()
# search_backward calls index('end-1c')
cls.text.index = lambda index: '4.0'
test_text = (
'First line\n'
'Line with target\n'
'Last line\n')
cls.text.insert('1.0', test_text)
cls.pat = re.compile('target')
cls.res = (2, (10, 16)) # line, slice indexes of 'target'
cls.failpat = re.compile('xyz') # not in text
cls.emptypat = re.compile(r'\w*') # empty match possible
def make_search(self, func):
def search(pat, line, col, wrap, ok=0):
res = func(self.text, pat, line, col, wrap, ok)
# res is (line, matchobject) or None
return (res[0], res[1].span()) if res else res
return search
def test_search_forward(self):
# search for non-empty match
Equal = self.assertEqual
forward = self.make_search(self.engine.search_forward)
pat = self.pat
Equal(forward(pat, 1, 0, True), self.res)
Equal(forward(pat, 3, 0, True), self.res) # wrap
Equal(forward(pat, 3, 0, False), None) # no wrap
Equal(forward(pat, 2, 10, False), self.res)
Equal(forward(self.failpat, 1, 0, True), None)
Equal(forward(self.emptypat, 2, 9, True, ok=True), (2, (9, 9)))
#Equal(forward(self.emptypat, 2, 9, True), self.res)
# While the initial empty match is correctly ignored, skipping
# the rest of the line and returning (3, (0,4)) seems buggy - tjr.
Equal(forward(self.emptypat, 2, 10, True), self.res)
def test_search_backward(self):
# search for non-empty match
Equal = self.assertEqual
backward = self.make_search(self.engine.search_backward)
pat = self.pat
Equal(backward(pat, 3, 5, True), self.res)
Equal(backward(pat, 2, 0, True), self.res) # wrap
Equal(backward(pat, 2, 0, False), None) # no wrap
Equal(backward(pat, 2, 16, False), self.res)
Equal(backward(self.failpat, 3, 9, True), None)
Equal(backward(self.emptypat, 2, 10, True, ok=True), (2, (9,9)))
# Accepted because 9 < 10, not because ok=True.
# It is not clear that ok=True is useful going back - tjr
Equal(backward(self.emptypat, 2, 9, True), (2, (5, 9)))
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,509 @@
from collections import namedtuple
from tkinter import Text, Tk
import unittest
from unittest.mock import Mock, NonCallableMagicMock, patch, sentinel, ANY
from test.support import requires
from idlelib.config import idleConf
from idlelib.squeezer import count_lines_with_wrapping, ExpandingButton, \
Squeezer
from idlelib import macosx
from idlelib.textview import view_text
from idlelib.tooltip import Hovertip
from idlelib.pyshell import PyShell
SENTINEL_VALUE = sentinel.SENTINEL_VALUE
def get_test_tk_root(test_instance):
"""Helper for tests: Create a root Tk object."""
requires('gui')
root = Tk()
root.withdraw()
def cleanup_root():
root.update_idletasks()
root.destroy()
test_instance.addCleanup(cleanup_root)
return root
class CountLinesTest(unittest.TestCase):
"""Tests for the count_lines_with_wrapping function."""
def check(self, expected, text, linewidth, tabwidth):
return self.assertEqual(
expected,
count_lines_with_wrapping(text, linewidth, tabwidth),
)
def test_count_empty(self):
"""Test with an empty string."""
self.assertEqual(count_lines_with_wrapping(""), 0)
def test_count_begins_with_empty_line(self):
"""Test with a string which begins with a newline."""
self.assertEqual(count_lines_with_wrapping("\ntext"), 2)
def test_count_ends_with_empty_line(self):
"""Test with a string which ends with a newline."""
self.assertEqual(count_lines_with_wrapping("text\n"), 1)
def test_count_several_lines(self):
"""Test with several lines of text."""
self.assertEqual(count_lines_with_wrapping("1\n2\n3\n"), 3)
def test_tab_width(self):
"""Test with various tab widths and line widths."""
self.check(expected=1, text='\t' * 1, linewidth=8, tabwidth=4)
self.check(expected=1, text='\t' * 2, linewidth=8, tabwidth=4)
self.check(expected=2, text='\t' * 3, linewidth=8, tabwidth=4)
self.check(expected=2, text='\t' * 4, linewidth=8, tabwidth=4)
self.check(expected=3, text='\t' * 5, linewidth=8, tabwidth=4)
# test longer lines and various tab widths
self.check(expected=4, text='\t' * 10, linewidth=12, tabwidth=4)
self.check(expected=10, text='\t' * 10, linewidth=12, tabwidth=8)
self.check(expected=2, text='\t' * 4, linewidth=10, tabwidth=3)
# test tabwidth=1
self.check(expected=2, text='\t' * 9, linewidth=5, tabwidth=1)
self.check(expected=2, text='\t' * 10, linewidth=5, tabwidth=1)
self.check(expected=3, text='\t' * 11, linewidth=5, tabwidth=1)
# test for off-by-one errors
self.check(expected=2, text='\t' * 6, linewidth=12, tabwidth=4)
self.check(expected=3, text='\t' * 6, linewidth=11, tabwidth=4)
self.check(expected=2, text='\t' * 6, linewidth=13, tabwidth=4)
class SqueezerTest(unittest.TestCase):
"""Tests for the Squeezer class."""
def make_mock_editor_window(self):
"""Create a mock EditorWindow instance."""
editwin = NonCallableMagicMock()
# isinstance(editwin, PyShell) must be true for Squeezer to enable
# auto-squeezing; in practice this will always be true
editwin.__class__ = PyShell
return editwin
def make_squeezer_instance(self, editor_window=None):
"""Create an actual Squeezer instance with a mock EditorWindow."""
if editor_window is None:
editor_window = self.make_mock_editor_window()
return Squeezer(editor_window)
def test_count_lines(self):
"""Test Squeezer.count_lines() with various inputs.
This checks that Squeezer.count_lines() calls the
count_lines_with_wrapping() function with the appropriate parameters.
"""
for tabwidth, linewidth in [(4, 80), (1, 79), (8, 80), (3, 120)]:
self._test_count_lines_helper(linewidth=linewidth,
tabwidth=tabwidth)
def _prepare_mock_editwin_for_count_lines(self, editwin,
linewidth, tabwidth):
"""Prepare a mock EditorWindow object for Squeezer.count_lines."""
CHAR_WIDTH = 10
BORDER_WIDTH = 2
PADDING_WIDTH = 1
# Prepare all the required functionality on the mock EditorWindow object
# so that the calculations in Squeezer.count_lines() can run.
editwin.get_tk_tabwidth.return_value = tabwidth
editwin.text.winfo_width.return_value = \
linewidth * CHAR_WIDTH + 2 * (BORDER_WIDTH + PADDING_WIDTH)
text_opts = {
'border': BORDER_WIDTH,
'padx': PADDING_WIDTH,
'font': None,
}
editwin.text.cget = lambda opt: text_opts[opt]
# monkey-path tkinter.font.Font with a mock object, so that
# Font.measure('0') returns CHAR_WIDTH
mock_font = Mock()
def measure(char):
if char == '0':
return CHAR_WIDTH
raise ValueError("measure should only be called on '0'!")
mock_font.return_value.measure = measure
patcher = patch('idlelib.squeezer.Font', mock_font)
patcher.start()
self.addCleanup(patcher.stop)
def _test_count_lines_helper(self, linewidth, tabwidth):
"""Helper for test_count_lines."""
editwin = self.make_mock_editor_window()
self._prepare_mock_editwin_for_count_lines(editwin, linewidth, tabwidth)
squeezer = self.make_squeezer_instance(editwin)
mock_count_lines = Mock(return_value=SENTINEL_VALUE)
text = 'TEXT'
with patch('idlelib.squeezer.count_lines_with_wrapping',
mock_count_lines):
self.assertIs(squeezer.count_lines(text), SENTINEL_VALUE)
mock_count_lines.assert_called_with(text, linewidth, tabwidth)
def test_init(self):
"""Test the creation of Squeezer instances."""
editwin = self.make_mock_editor_window()
squeezer = self.make_squeezer_instance(editwin)
self.assertIs(squeezer.editwin, editwin)
self.assertEqual(squeezer.expandingbuttons, [])
def test_write_no_tags(self):
"""Test Squeezer's overriding of the EditorWindow's write() method."""
editwin = self.make_mock_editor_window()
for text in ['', 'TEXT', 'LONG TEXT' * 1000, 'MANY_LINES\n' * 100]:
editwin.write = orig_write = Mock(return_value=SENTINEL_VALUE)
squeezer = self.make_squeezer_instance(editwin)
self.assertEqual(squeezer.editwin.write(text, ()), SENTINEL_VALUE)
self.assertEqual(orig_write.call_count, 1)
orig_write.assert_called_with(text, ())
self.assertEqual(len(squeezer.expandingbuttons), 0)
def test_write_not_stdout(self):
"""Test Squeezer's overriding of the EditorWindow's write() method."""
for text in ['', 'TEXT', 'LONG TEXT' * 1000, 'MANY_LINES\n' * 100]:
editwin = self.make_mock_editor_window()
editwin.write.return_value = SENTINEL_VALUE
orig_write = editwin.write
squeezer = self.make_squeezer_instance(editwin)
self.assertEqual(squeezer.editwin.write(text, "stderr"),
SENTINEL_VALUE)
self.assertEqual(orig_write.call_count, 1)
orig_write.assert_called_with(text, "stderr")
self.assertEqual(len(squeezer.expandingbuttons), 0)
def test_write_stdout(self):
"""Test Squeezer's overriding of the EditorWindow's write() method."""
editwin = self.make_mock_editor_window()
self._prepare_mock_editwin_for_count_lines(editwin,
linewidth=80, tabwidth=8)
for text in ['', 'TEXT']:
editwin.write = orig_write = Mock(return_value=SENTINEL_VALUE)
squeezer = self.make_squeezer_instance(editwin)
squeezer.auto_squeeze_min_lines = 50
self.assertEqual(squeezer.editwin.write(text, "stdout"),
SENTINEL_VALUE)
self.assertEqual(orig_write.call_count, 1)
orig_write.assert_called_with(text, "stdout")
self.assertEqual(len(squeezer.expandingbuttons), 0)
for text in ['LONG TEXT' * 1000, 'MANY_LINES\n' * 100]:
editwin.write = orig_write = Mock(return_value=SENTINEL_VALUE)
squeezer = self.make_squeezer_instance(editwin)
squeezer.auto_squeeze_min_lines = 50
self.assertEqual(squeezer.editwin.write(text, "stdout"), None)
self.assertEqual(orig_write.call_count, 0)
self.assertEqual(len(squeezer.expandingbuttons), 1)
def test_auto_squeeze(self):
"""Test that the auto-squeezing creates an ExpandingButton properly."""
root = get_test_tk_root(self)
text_widget = Text(root)
text_widget.mark_set("iomark", "1.0")
editwin = self.make_mock_editor_window()
editwin.text = text_widget
squeezer = self.make_squeezer_instance(editwin)
squeezer.auto_squeeze_min_lines = 5
squeezer.count_lines = Mock(return_value=6)
editwin.write('TEXT\n'*6, "stdout")
self.assertEqual(text_widget.get('1.0', 'end'), '\n')
self.assertEqual(len(squeezer.expandingbuttons), 1)
def test_squeeze_current_text_event(self):
"""Test the squeeze_current_text event."""
root = get_test_tk_root(self)
# squeezing text should work for both stdout and stderr
for tag_name in ["stdout", "stderr"]:
text_widget = Text(root)
text_widget.mark_set("iomark", "1.0")
editwin = self.make_mock_editor_window()
editwin.text = editwin.per.bottom = text_widget
squeezer = self.make_squeezer_instance(editwin)
squeezer.count_lines = Mock(return_value=6)
# prepare some text in the Text widget
text_widget.insert("1.0", "SOME\nTEXT\n", tag_name)
text_widget.mark_set("insert", "1.0")
self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n')
self.assertEqual(len(squeezer.expandingbuttons), 0)
# test squeezing the current text
retval = squeezer.squeeze_current_text_event(event=Mock())
self.assertEqual(retval, "break")
self.assertEqual(text_widget.get('1.0', 'end'), '\n\n')
self.assertEqual(len(squeezer.expandingbuttons), 1)
self.assertEqual(squeezer.expandingbuttons[0].s, 'SOME\nTEXT')
# test that expanding the squeezed text works and afterwards the
# Text widget contains the original text
squeezer.expandingbuttons[0].expand(event=Mock())
self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n')
self.assertEqual(len(squeezer.expandingbuttons), 0)
def test_squeeze_current_text_event_no_allowed_tags(self):
"""Test that the event doesn't squeeze text without a relevant tag."""
root = get_test_tk_root(self)
text_widget = Text(root)
text_widget.mark_set("iomark", "1.0")
editwin = self.make_mock_editor_window()
editwin.text = editwin.per.bottom = text_widget
squeezer = self.make_squeezer_instance(editwin)
squeezer.count_lines = Mock(return_value=6)
# prepare some text in the Text widget
text_widget.insert("1.0", "SOME\nTEXT\n", "TAG")
text_widget.mark_set("insert", "1.0")
self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n')
self.assertEqual(len(squeezer.expandingbuttons), 0)
# test squeezing the current text
retval = squeezer.squeeze_current_text_event(event=Mock())
self.assertEqual(retval, "break")
self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n')
self.assertEqual(len(squeezer.expandingbuttons), 0)
def test_squeeze_text_before_existing_squeezed_text(self):
"""Test squeezing text before existing squeezed text."""
root = get_test_tk_root(self)
text_widget = Text(root)
text_widget.mark_set("iomark", "1.0")
editwin = self.make_mock_editor_window()
editwin.text = editwin.per.bottom = text_widget
squeezer = self.make_squeezer_instance(editwin)
squeezer.count_lines = Mock(return_value=6)
# prepare some text in the Text widget and squeeze it
text_widget.insert("1.0", "SOME\nTEXT\n", "stdout")
text_widget.mark_set("insert", "1.0")
squeezer.squeeze_current_text_event(event=Mock())
self.assertEqual(len(squeezer.expandingbuttons), 1)
# test squeezing the current text
text_widget.insert("1.0", "MORE\nSTUFF\n", "stdout")
text_widget.mark_set("insert", "1.0")
retval = squeezer.squeeze_current_text_event(event=Mock())
self.assertEqual(retval, "break")
self.assertEqual(text_widget.get('1.0', 'end'), '\n\n\n')
self.assertEqual(len(squeezer.expandingbuttons), 2)
self.assertTrue(text_widget.compare(
squeezer.expandingbuttons[0],
'<',
squeezer.expandingbuttons[1],
))
GetOptionSignature = namedtuple('GetOptionSignature',
'configType section option default type warn_on_default raw')
@classmethod
def _make_sig(cls, configType, section, option, default=sentinel.NOT_GIVEN,
type=sentinel.NOT_GIVEN,
warn_on_default=sentinel.NOT_GIVEN,
raw=sentinel.NOT_GIVEN):
return cls.GetOptionSignature(configType, section, option, default,
type, warn_on_default, raw)
@classmethod
def get_GetOption_signature(cls, mock_call_obj):
args, kwargs = mock_call_obj[-2:]
return cls._make_sig(*args, **kwargs)
def test_reload(self):
"""Test the reload() class-method."""
self.assertIsInstance(Squeezer.auto_squeeze_min_lines, int)
idleConf.SetOption('main', 'PyShell', 'auto-squeeze-min-lines', '42')
Squeezer.reload()
self.assertEqual(Squeezer.auto_squeeze_min_lines, 42)
class ExpandingButtonTest(unittest.TestCase):
"""Tests for the ExpandingButton class."""
# In these tests the squeezer instance is a mock, but actual tkinter
# Text and Button instances are created.
def make_mock_squeezer(self):
"""Helper for tests: Create a mock Squeezer object."""
root = get_test_tk_root(self)
squeezer = Mock()
squeezer.editwin.text = Text(root)
# Set default values for the configuration settings
squeezer.auto_squeeze_min_lines = 50
return squeezer
@patch('idlelib.squeezer.Hovertip', autospec=Hovertip)
def test_init(self, MockHovertip):
"""Test the simplest creation of an ExpandingButton."""
squeezer = self.make_mock_squeezer()
text_widget = squeezer.editwin.text
expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)
self.assertEqual(expandingbutton.s, 'TEXT')
# check that the underlying tkinter.Button is properly configured
self.assertEqual(expandingbutton.master, text_widget)
self.assertTrue('50 lines' in expandingbutton.cget('text'))
# check that the text widget still contains no text
self.assertEqual(text_widget.get('1.0', 'end'), '\n')
# check that the mouse events are bound
self.assertIn('<Double-Button-1>', expandingbutton.bind())
right_button_code = '<Button-%s>' % ('2' if macosx.isAquaTk() else '3')
self.assertIn(right_button_code, expandingbutton.bind())
# check that ToolTip was called once, with appropriate values
self.assertEqual(MockHovertip.call_count, 1)
MockHovertip.assert_called_with(expandingbutton, ANY, hover_delay=ANY)
# check that 'right-click' appears in the tooltip text
tooltip_text = MockHovertip.call_args[0][1]
self.assertIn('right-click', tooltip_text.lower())
def test_expand(self):
"""Test the expand event."""
squeezer = self.make_mock_squeezer()
expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)
# insert the button into the text widget
# (this is normally done by the Squeezer class)
text_widget = expandingbutton.text
text_widget.window_create("1.0", window=expandingbutton)
# set base_text to the text widget, so that changes are actually made
# to it (by ExpandingButton) and we can inspect these changes afterwards
expandingbutton.base_text = expandingbutton.text
# trigger the expand event
retval = expandingbutton.expand(event=Mock())
self.assertEqual(retval, None)
# check that the text was inserted into the text widget
self.assertEqual(text_widget.get('1.0', 'end'), 'TEXT\n')
# check that the 'TAGS' tag was set on the inserted text
text_end_index = text_widget.index('end-1c')
self.assertEqual(text_widget.get('1.0', text_end_index), 'TEXT')
self.assertEqual(text_widget.tag_nextrange('TAGS', '1.0'),
('1.0', text_end_index))
# check that the button removed itself from squeezer.expandingbuttons
self.assertEqual(squeezer.expandingbuttons.remove.call_count, 1)
squeezer.expandingbuttons.remove.assert_called_with(expandingbutton)
def test_expand_dangerous_oupput(self):
"""Test that expanding very long output asks user for confirmation."""
squeezer = self.make_mock_squeezer()
text = 'a' * 10**5
expandingbutton = ExpandingButton(text, 'TAGS', 50, squeezer)
expandingbutton.set_is_dangerous()
self.assertTrue(expandingbutton.is_dangerous)
# insert the button into the text widget
# (this is normally done by the Squeezer class)
text_widget = expandingbutton.text
text_widget.window_create("1.0", window=expandingbutton)
# set base_text to the text widget, so that changes are actually made
# to it (by ExpandingButton) and we can inspect these changes afterwards
expandingbutton.base_text = expandingbutton.text
# patch the message box module to always return False
with patch('idlelib.squeezer.tkMessageBox') as mock_msgbox:
mock_msgbox.askokcancel.return_value = False
mock_msgbox.askyesno.return_value = False
# trigger the expand event
retval = expandingbutton.expand(event=Mock())
# check that the event chain was broken and no text was inserted
self.assertEqual(retval, 'break')
self.assertEqual(expandingbutton.text.get('1.0', 'end-1c'), '')
# patch the message box module to always return True
with patch('idlelib.squeezer.tkMessageBox') as mock_msgbox:
mock_msgbox.askokcancel.return_value = True
mock_msgbox.askyesno.return_value = True
# trigger the expand event
retval = expandingbutton.expand(event=Mock())
# check that the event chain wasn't broken and the text was inserted
self.assertEqual(retval, None)
self.assertEqual(expandingbutton.text.get('1.0', 'end-1c'), text)
def test_copy(self):
"""Test the copy event."""
# testing with the actual clipboard proved problematic, so this test
# replaces the clipboard manipulation functions with mocks and checks
# that they are called appropriately
squeezer = self.make_mock_squeezer()
expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)
expandingbutton.clipboard_clear = Mock()
expandingbutton.clipboard_append = Mock()
# trigger the copy event
retval = expandingbutton.copy(event=Mock())
self.assertEqual(retval, None)
# check that the expanding button called clipboard_clear() and
# clipboard_append('TEXT') once each
self.assertEqual(expandingbutton.clipboard_clear.call_count, 1)
self.assertEqual(expandingbutton.clipboard_append.call_count, 1)
expandingbutton.clipboard_append.assert_called_with('TEXT')
def test_view(self):
"""Test the view event."""
squeezer = self.make_mock_squeezer()
expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)
expandingbutton.selection_own = Mock()
with patch('idlelib.squeezer.view_text', autospec=view_text)\
as mock_view_text:
# trigger the view event
expandingbutton.view(event=Mock())
# check that the expanding button called view_text
self.assertEqual(mock_view_text.call_count, 1)
# check that the proper text was passed
self.assertEqual(mock_view_text.call_args[0][2], 'TEXT')
def test_rmenu(self):
"""Test the context menu."""
squeezer = self.make_mock_squeezer()
expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)
with patch('tkinter.Menu') as mock_Menu:
mock_menu = Mock()
mock_Menu.return_value = mock_menu
mock_event = Mock()
mock_event.x = 10
mock_event.y = 10
expandingbutton.context_menu_event(event=mock_event)
self.assertEqual(mock_menu.add_command.call_count,
len(expandingbutton.rmenu_specs))
for label, *data in expandingbutton.rmenu_specs:
mock_menu.add_command.assert_any_call(label=label, command=ANY)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,47 @@
"Test stackviewer, coverage 63%."
from idlelib import stackviewer
import unittest
from test.support import requires
from tkinter import Tk
from idlelib.tree import TreeNode, ScrolledCanvas
import sys
class StackBrowserTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
svs = stackviewer.sys
try:
abc
except NameError:
svs.last_type, svs.last_value, svs.last_traceback = (
sys.exc_info())
requires('gui')
cls.root = Tk()
cls.root.withdraw()
@classmethod
def tearDownClass(cls):
svs = stackviewer.sys
del svs.last_traceback, svs.last_type, svs.last_value
cls.root.update_idletasks()
## for id in cls.root.tk.call('after', 'info'):
## cls.root.after_cancel(id) # Need for EditorWindow.
cls.root.destroy()
del cls.root
def test_init(self):
sb = stackviewer.StackBrowser(self.root)
isi = self.assertIsInstance
isi(stackviewer.sc, ScrolledCanvas)
isi(stackviewer.item, stackviewer.StackTreeItem)
isi(stackviewer.node, TreeNode)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,41 @@
"Test statusbar, coverage 100%."
from idlelib import statusbar
import unittest
from test.support import requires
from tkinter import Tk
class Test(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
@classmethod
def tearDownClass(cls):
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
def test_init(self):
bar = statusbar.MultiStatusBar(self.root)
self.assertEqual(bar.labels, {})
def test_set_label(self):
bar = statusbar.MultiStatusBar(self.root)
bar.set_label('left', text='sometext', width=10)
self.assertIn('left', bar.labels)
left = bar.labels['left']
self.assertEqual(left['text'], 'sometext')
self.assertEqual(left['width'], 10)
bar.set_label('left', text='revised text')
self.assertEqual(left['text'], 'revised text')
bar.set_label('right', text='correct text')
self.assertEqual(bar.labels['right']['text'], 'correct text')
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,236 @@
''' Test mock_tk.Text class against tkinter.Text class
Run same tests with both by creating a mixin class.
'''
import unittest
from test.support import requires
from _tkinter import TclError
class TextTest(object):
"Define items common to both sets of tests."
hw = 'hello\nworld' # Several tests insert this after initialization.
hwn = hw+'\n' # \n present at initialization, before insert
# setUpClass defines cls.Text and maybe cls.root.
# setUp defines self.text from Text and maybe root.
def test_init(self):
self.assertEqual(self.text.get('1.0'), '\n')
self.assertEqual(self.text.get('end'), '')
def test_index_empty(self):
index = self.text.index
for dex in (-1.0, 0.3, '1.-1', '1.0', '1.0 lineend', '1.end', '1.33',
'insert'):
self.assertEqual(index(dex), '1.0')
for dex in 'end', 2.0, '2.1', '33.44':
self.assertEqual(index(dex), '2.0')
def test_index_data(self):
index = self.text.index
self.text.insert('1.0', self.hw)
for dex in -1.0, 0.3, '1.-1', '1.0':
self.assertEqual(index(dex), '1.0')
for dex in '1.0 lineend', '1.end', '1.33':
self.assertEqual(index(dex), '1.5')
for dex in 'end', '33.44':
self.assertEqual(index(dex), '3.0')
def test_get(self):
get = self.text.get
Equal = self.assertEqual
self.text.insert('1.0', self.hw)
Equal(get('end'), '')
Equal(get('end', 'end'), '')
Equal(get('1.0'), 'h')
Equal(get('1.0', '1.1'), 'h')
Equal(get('1.0', '1.3'), 'hel')
Equal(get('1.1', '1.3'), 'el')
Equal(get('1.0', '1.0 lineend'), 'hello')
Equal(get('1.0', '1.10'), 'hello')
Equal(get('1.0 lineend'), '\n')
Equal(get('1.1', '2.3'), 'ello\nwor')
Equal(get('1.0', '2.5'), self.hw)
Equal(get('1.0', 'end'), self.hwn)
Equal(get('0.0', '5.0'), self.hwn)
def test_insert(self):
insert = self.text.insert
get = self.text.get
Equal = self.assertEqual
insert('1.0', self.hw)
Equal(get('1.0', 'end'), self.hwn)
insert('1.0', '') # nothing
Equal(get('1.0', 'end'), self.hwn)
insert('1.0', '*')
Equal(get('1.0', 'end'), '*hello\nworld\n')
insert('1.0 lineend', '*')
Equal(get('1.0', 'end'), '*hello*\nworld\n')
insert('2.3', '*')
Equal(get('1.0', 'end'), '*hello*\nwor*ld\n')
insert('end', 'x')
Equal(get('1.0', 'end'), '*hello*\nwor*ldx\n')
insert('1.4', 'x\n')
Equal(get('1.0', 'end'), '*helx\nlo*\nwor*ldx\n')
def test_no_delete(self):
# if index1 == 'insert' or 'end' or >= end, there is no deletion
delete = self.text.delete
get = self.text.get
Equal = self.assertEqual
self.text.insert('1.0', self.hw)
delete('insert')
Equal(get('1.0', 'end'), self.hwn)
delete('end')
Equal(get('1.0', 'end'), self.hwn)
delete('insert', 'end')
Equal(get('1.0', 'end'), self.hwn)
delete('insert', '5.5')
Equal(get('1.0', 'end'), self.hwn)
delete('1.4', '1.0')
Equal(get('1.0', 'end'), self.hwn)
delete('1.4', '1.4')
Equal(get('1.0', 'end'), self.hwn)
def test_delete_char(self):
delete = self.text.delete
get = self.text.get
Equal = self.assertEqual
self.text.insert('1.0', self.hw)
delete('1.0')
Equal(get('1.0', '1.end'), 'ello')
delete('1.0', '1.1')
Equal(get('1.0', '1.end'), 'llo')
# delete \n and combine 2 lines into 1
delete('1.end')
Equal(get('1.0', '1.end'), 'lloworld')
self.text.insert('1.3', '\n')
delete('1.10')
Equal(get('1.0', '1.end'), 'lloworld')
self.text.insert('1.3', '\n')
delete('1.3', '2.0')
Equal(get('1.0', '1.end'), 'lloworld')
def test_delete_slice(self):
delete = self.text.delete
get = self.text.get
Equal = self.assertEqual
self.text.insert('1.0', self.hw)
delete('1.0', '1.0 lineend')
Equal(get('1.0', 'end'), '\nworld\n')
delete('1.0', 'end')
Equal(get('1.0', 'end'), '\n')
self.text.insert('1.0', self.hw)
delete('1.0', '2.0')
Equal(get('1.0', 'end'), 'world\n')
delete('1.0', 'end')
Equal(get('1.0', 'end'), '\n')
self.text.insert('1.0', self.hw)
delete('1.2', '2.3')
Equal(get('1.0', 'end'), 'held\n')
def test_multiple_lines(self): # insert and delete
self.text.insert('1.0', 'hello')
self.text.insert('1.3', '1\n2\n3\n4\n5')
self.assertEqual(self.text.get('1.0', 'end'), 'hel1\n2\n3\n4\n5lo\n')
self.text.delete('1.3', '5.1')
self.assertEqual(self.text.get('1.0', 'end'), 'hello\n')
def test_compare(self):
compare = self.text.compare
Equal = self.assertEqual
# need data so indexes not squished to 1,0
self.text.insert('1.0', 'First\nSecond\nThird\n')
self.assertRaises(TclError, compare, '2.2', 'op', '2.2')
for op, less1, less0, equal, greater0, greater1 in (
('<', True, True, False, False, False),
('<=', True, True, True, False, False),
('>', False, False, False, True, True),
('>=', False, False, True, True, True),
('==', False, False, True, False, False),
('!=', True, True, False, True, True),
):
Equal(compare('1.1', op, '2.2'), less1, op)
Equal(compare('2.1', op, '2.2'), less0, op)
Equal(compare('2.2', op, '2.2'), equal, op)
Equal(compare('2.3', op, '2.2'), greater0, op)
Equal(compare('3.3', op, '2.2'), greater1, op)
class MockTextTest(TextTest, unittest.TestCase):
@classmethod
def setUpClass(cls):
from idlelib.idle_test.mock_tk import Text
cls.Text = Text
def setUp(self):
self.text = self.Text()
def test_decode(self):
# test endflags (-1, 0) not tested by test_index (which uses +1)
decode = self.text._decode
Equal = self.assertEqual
self.text.insert('1.0', self.hw)
Equal(decode('end', -1), (2, 5))
Equal(decode('3.1', -1), (2, 5))
Equal(decode('end', 0), (2, 6))
Equal(decode('3.1', 0), (2, 6))
class TkTextTest(TextTest, unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
from tkinter import Tk, Text
cls.Text = Text
cls.root = Tk()
@classmethod
def tearDownClass(cls):
cls.root.destroy()
del cls.root
def setUp(self):
self.text = self.Text(self.root)
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)

View file

@ -0,0 +1,181 @@
"""Test textview, coverage 100%.
Since all methods and functions create (or destroy) a ViewWindow, which
is a widget containing a widget, etcetera, all tests must be gui tests.
Using mock Text would not change this. Other mocks are used to retrieve
information about calls.
"""
from idlelib import textview as tv
import unittest
from test.support import requires
requires('gui')
import os
from tkinter import Tk
from tkinter.ttk import Button
from idlelib.idle_test.mock_idle import Func
from idlelib.idle_test.mock_tk import Mbox_func
def setUpModule():
global root
root = Tk()
root.withdraw()
def tearDownModule():
global root
root.update_idletasks()
root.destroy()
del root
# If we call ViewWindow or wrapper functions with defaults
# modal=True, _utest=False, test hangs on call to wait_window.
# Have also gotten tk error 'can't invoke "event" command'.
class VW(tv.ViewWindow): # Used in ViewWindowTest.
transient = Func()
grab_set = Func()
wait_window = Func()
# Call wrapper class VW with mock wait_window.
class ViewWindowTest(unittest.TestCase):
def setUp(self):
VW.transient.__init__()
VW.grab_set.__init__()
VW.wait_window.__init__()
def test_init_modal(self):
view = VW(root, 'Title', 'test text')
self.assertTrue(VW.transient.called)
self.assertTrue(VW.grab_set.called)
self.assertTrue(VW.wait_window.called)
view.ok()
def test_init_nonmodal(self):
view = VW(root, 'Title', 'test text', modal=False)
self.assertFalse(VW.transient.called)
self.assertFalse(VW.grab_set.called)
self.assertFalse(VW.wait_window.called)
view.ok()
def test_ok(self):
view = VW(root, 'Title', 'test text', modal=False)
view.destroy = Func()
view.ok()
self.assertTrue(view.destroy.called)
del view.destroy # Unmask real function.
view.destroy()
class TextFrameTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.root = root = Tk()
root.withdraw()
cls.frame = tv.TextFrame(root, 'test text')
@classmethod
def tearDownClass(cls):
del cls.frame
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
def test_line1(self):
get = self.frame.text.get
self.assertEqual(get('1.0', '1.end'), 'test text')
# Call ViewWindow with modal=False.
class ViewFunctionTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.orig_error = tv.showerror
tv.showerror = Mbox_func()
@classmethod
def tearDownClass(cls):
tv.showerror = cls.orig_error
del cls.orig_error
def test_view_text(self):
view = tv.view_text(root, 'Title', 'test text', modal=False)
self.assertIsInstance(view, tv.ViewWindow)
self.assertIsInstance(view.viewframe, tv.ViewFrame)
view.viewframe.ok()
def test_view_file(self):
view = tv.view_file(root, 'Title', __file__, 'ascii', modal=False)
self.assertIsInstance(view, tv.ViewWindow)
self.assertIsInstance(view.viewframe, tv.ViewFrame)
get = view.viewframe.textframe.text.get
self.assertIn('Test', get('1.0', '1.end'))
view.ok()
def test_bad_file(self):
# Mock showerror will be used; view_file will return None.
view = tv.view_file(root, 'Title', 'abc.xyz', 'ascii', modal=False)
self.assertIsNone(view)
self.assertEqual(tv.showerror.title, 'File Load Error')
def test_bad_encoding(self):
p = os.path
fn = p.abspath(p.join(p.dirname(__file__), '..', 'CREDITS.txt'))
view = tv.view_file(root, 'Title', fn, 'ascii', modal=False)
self.assertIsNone(view)
self.assertEqual(tv.showerror.title, 'Unicode Decode Error')
def test_nowrap(self):
view = tv.view_text(root, 'Title', 'test', modal=False, wrap='none')
text_widget = view.viewframe.textframe.text
self.assertEqual(text_widget.cget('wrap'), 'none')
# Call ViewWindow with _utest=True.
class ButtonClickTest(unittest.TestCase):
def setUp(self):
self.view = None
self.called = False
def tearDown(self):
if self.view:
self.view.destroy()
def test_view_text_bind_with_button(self):
def _command():
self.called = True
self.view = tv.view_text(root, 'TITLE_TEXT', 'COMMAND', _utest=True)
button = Button(root, text='BUTTON', command=_command)
button.invoke()
self.addCleanup(button.destroy)
self.assertEqual(self.called, True)
self.assertEqual(self.view.title(), 'TITLE_TEXT')
self.assertEqual(self.view.viewframe.textframe.text.get('1.0', '1.end'),
'COMMAND')
def test_view_file_bind_with_button(self):
def _command():
self.called = True
self.view = tv.view_file(root, 'TITLE_FILE', __file__,
encoding='ascii', _utest=True)
button = Button(root, text='BUTTON', command=_command)
button.invoke()
self.addCleanup(button.destroy)
self.assertEqual(self.called, True)
self.assertEqual(self.view.title(), 'TITLE_FILE')
get = self.view.viewframe.textframe.text.get
with open(__file__) as f:
self.assertEqual(get('1.0', '1.end'), f.readline().strip())
f.readline()
self.assertEqual(get('3.0', '3.end'), f.readline().strip())
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,146 @@
from idlelib.tooltip import TooltipBase, Hovertip
from test.support import requires
requires('gui')
from functools import wraps
import time
from tkinter import Button, Tk, Toplevel
import unittest
def setUpModule():
global root
root = Tk()
def root_update():
global root
root.update()
def tearDownModule():
global root
root.update_idletasks()
root.destroy()
del root
def add_call_counting(func):
@wraps(func)
def wrapped_func(*args, **kwargs):
wrapped_func.call_args_list.append((args, kwargs))
return func(*args, **kwargs)
wrapped_func.call_args_list = []
return wrapped_func
def _make_top_and_button(testobj):
global root
top = Toplevel(root)
testobj.addCleanup(top.destroy)
top.title("Test tooltip")
button = Button(top, text='ToolTip test button')
button.pack()
testobj.addCleanup(button.destroy)
top.lift()
return top, button
class ToolTipBaseTest(unittest.TestCase):
def setUp(self):
self.top, self.button = _make_top_and_button(self)
def test_base_class_is_unusable(self):
global root
top = Toplevel(root)
self.addCleanup(top.destroy)
button = Button(top, text='ToolTip test button')
button.pack()
self.addCleanup(button.destroy)
with self.assertRaises(NotImplementedError):
tooltip = TooltipBase(button)
tooltip.showtip()
class HovertipTest(unittest.TestCase):
def setUp(self):
self.top, self.button = _make_top_and_button(self)
def test_showtip(self):
tooltip = Hovertip(self.button, 'ToolTip text')
self.addCleanup(tooltip.hidetip)
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
tooltip.showtip()
self.assertTrue(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
def test_showtip_twice(self):
tooltip = Hovertip(self.button, 'ToolTip text')
self.addCleanup(tooltip.hidetip)
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
tooltip.showtip()
self.assertTrue(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
orig_tipwindow = tooltip.tipwindow
tooltip.showtip()
self.assertTrue(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
self.assertIs(tooltip.tipwindow, orig_tipwindow)
def test_hidetip(self):
tooltip = Hovertip(self.button, 'ToolTip text')
self.addCleanup(tooltip.hidetip)
tooltip.showtip()
tooltip.hidetip()
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
def test_showtip_on_mouse_enter_no_delay(self):
tooltip = Hovertip(self.button, 'ToolTip text', hover_delay=None)
self.addCleanup(tooltip.hidetip)
tooltip.showtip = add_call_counting(tooltip.showtip)
root_update()
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
self.button.event_generate('<Enter>', x=0, y=0)
root_update()
self.assertTrue(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
self.assertGreater(len(tooltip.showtip.call_args_list), 0)
def test_showtip_on_mouse_enter_hover_delay(self):
tooltip = Hovertip(self.button, 'ToolTip text', hover_delay=50)
self.addCleanup(tooltip.hidetip)
tooltip.showtip = add_call_counting(tooltip.showtip)
root_update()
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
self.button.event_generate('<Enter>', x=0, y=0)
root_update()
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
time.sleep(0.1)
root_update()
self.assertTrue(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
self.assertGreater(len(tooltip.showtip.call_args_list), 0)
def test_hidetip_on_mouse_leave(self):
tooltip = Hovertip(self.button, 'ToolTip text', hover_delay=None)
self.addCleanup(tooltip.hidetip)
tooltip.showtip = add_call_counting(tooltip.showtip)
root_update()
self.button.event_generate('<Enter>', x=0, y=0)
root_update()
self.button.event_generate('<Leave>', x=0, y=0)
root_update()
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
self.assertGreater(len(tooltip.showtip.call_args_list), 0)
def test_dont_show_on_mouse_leave_before_delay(self):
tooltip = Hovertip(self.button, 'ToolTip text', hover_delay=50)
self.addCleanup(tooltip.hidetip)
tooltip.showtip = add_call_counting(tooltip.showtip)
root_update()
self.button.event_generate('<Enter>', x=0, y=0)
root_update()
self.button.event_generate('<Leave>', x=0, y=0)
root_update()
time.sleep(0.1)
root_update()
self.assertFalse(tooltip.tipwindow and tooltip.tipwindow.winfo_viewable())
self.assertEqual(tooltip.showtip.call_args_list, [])
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,33 @@
"Test tree. coverage 56%."
from idlelib import tree
import unittest
from test.support import requires
requires('gui')
from tkinter import Tk
class TreeTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.root = Tk()
cls.root.withdraw()
@classmethod
def tearDownClass(cls):
cls.root.destroy()
del cls.root
def test_init(self):
# Start with code slightly adapted from htest.
sc = tree.ScrolledCanvas(
self.root, bg="white", highlightthickness=0, takefocus=1)
sc.frame.pack(expand=1, fill="both", side='left')
item = tree.FileTreeItem(tree.ICONDIR)
node = tree.TreeNode(sc.canvas, None, item)
node.expand()
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,135 @@
"Test undo, coverage 77%."
# Only test UndoDelegator so far.
from idlelib.undo import UndoDelegator
import unittest
from test.support import requires
requires('gui')
from unittest.mock import Mock
from tkinter import Text, Tk
from idlelib.percolator import Percolator
class UndoDelegatorTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.root = Tk()
cls.text = Text(cls.root)
cls.percolator = Percolator(cls.text)
@classmethod
def tearDownClass(cls):
cls.percolator.redir.close()
del cls.percolator, cls.text
cls.root.destroy()
del cls.root
def setUp(self):
self.delegator = UndoDelegator()
self.delegator.bell = Mock()
self.percolator.insertfilter(self.delegator)
def tearDown(self):
self.percolator.removefilter(self.delegator)
self.text.delete('1.0', 'end')
self.delegator.resetcache()
def test_undo_event(self):
text = self.text
text.insert('insert', 'foobar')
text.insert('insert', 'h')
text.event_generate('<<undo>>')
self.assertEqual(text.get('1.0', 'end'), '\n')
text.insert('insert', 'foo')
text.insert('insert', 'bar')
text.delete('1.2', '1.4')
text.insert('insert', 'hello')
text.event_generate('<<undo>>')
self.assertEqual(text.get('1.0', '1.4'), 'foar')
text.event_generate('<<undo>>')
self.assertEqual(text.get('1.0', '1.6'), 'foobar')
text.event_generate('<<undo>>')
self.assertEqual(text.get('1.0', '1.3'), 'foo')
text.event_generate('<<undo>>')
self.delegator.undo_event('event')
self.assertTrue(self.delegator.bell.called)
def test_redo_event(self):
text = self.text
text.insert('insert', 'foo')
text.insert('insert', 'bar')
text.delete('1.0', '1.3')
text.event_generate('<<undo>>')
text.event_generate('<<redo>>')
self.assertEqual(text.get('1.0', '1.3'), 'bar')
text.event_generate('<<redo>>')
self.assertTrue(self.delegator.bell.called)
def test_dump_event(self):
"""
Dump_event cannot be tested directly without changing
environment variables. So, test statements in dump_event
indirectly
"""
text = self.text
d = self.delegator
text.insert('insert', 'foo')
text.insert('insert', 'bar')
text.delete('1.2', '1.4')
self.assertTupleEqual((d.pointer, d.can_merge), (3, True))
text.event_generate('<<undo>>')
self.assertTupleEqual((d.pointer, d.can_merge), (2, False))
def test_get_set_saved(self):
# test the getter method get_saved
# test the setter method set_saved
# indirectly test check_saved
d = self.delegator
self.assertTrue(d.get_saved())
self.text.insert('insert', 'a')
self.assertFalse(d.get_saved())
d.saved_change_hook = Mock()
d.set_saved(True)
self.assertEqual(d.pointer, d.saved)
self.assertTrue(d.saved_change_hook.called)
d.set_saved(False)
self.assertEqual(d.saved, -1)
self.assertTrue(d.saved_change_hook.called)
def test_undo_start_stop(self):
# test the undo_block_start and undo_block_stop methods
text = self.text
text.insert('insert', 'foo')
self.delegator.undo_block_start()
text.insert('insert', 'bar')
text.insert('insert', 'bar')
self.delegator.undo_block_stop()
self.assertEqual(text.get('1.0', '1.3'), 'foo')
# test another code path
self.delegator.undo_block_start()
text.insert('insert', 'bar')
self.delegator.undo_block_stop()
self.assertEqual(text.get('1.0', '1.3'), 'foo')
def test_addcmd(self):
text = self.text
# when number of undo operations exceeds max_undo
self.delegator.max_undo = max_undo = 10
for i in range(max_undo + 10):
text.insert('insert', 'foo')
self.assertLessEqual(len(self.delegator.undolist), max_undo)
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)

View file

@ -0,0 +1,73 @@
'''Test warnings replacement in pyshell.py and run.py.
This file could be expanded to include traceback overrides
(in same two modules). If so, change name.
Revise if output destination changes (http://bugs.python.org/issue18318).
Make sure warnings module is left unaltered (http://bugs.python.org/issue18081).
'''
from idlelib import run
from idlelib import pyshell as shell
import unittest
from test.support import captured_stderr
import warnings
# Try to capture default showwarning before Idle modules are imported.
showwarning = warnings.showwarning
# But if we run this file within idle, we are in the middle of the run.main loop
# and default showwarnings has already been replaced.
running_in_idle = 'idle' in showwarning.__name__
# The following was generated from pyshell.idle_formatwarning
# and checked as matching expectation.
idlemsg = '''
Warning (from warnings module):
File "test_warning.py", line 99
Line of code
UserWarning: Test
'''
shellmsg = idlemsg + ">>> "
class RunWarnTest(unittest.TestCase):
@unittest.skipIf(running_in_idle, "Does not work when run within Idle.")
def test_showwarnings(self):
self.assertIs(warnings.showwarning, showwarning)
run.capture_warnings(True)
self.assertIs(warnings.showwarning, run.idle_showwarning_subproc)
run.capture_warnings(False)
self.assertIs(warnings.showwarning, showwarning)
def test_run_show(self):
with captured_stderr() as f:
run.idle_showwarning_subproc(
'Test', UserWarning, 'test_warning.py', 99, f, 'Line of code')
# The following uses .splitlines to erase line-ending differences
self.assertEqual(idlemsg.splitlines(), f.getvalue().splitlines())
class ShellWarnTest(unittest.TestCase):
@unittest.skipIf(running_in_idle, "Does not work when run within Idle.")
def test_showwarnings(self):
self.assertIs(warnings.showwarning, showwarning)
shell.capture_warnings(True)
self.assertIs(warnings.showwarning, shell.idle_showwarning)
shell.capture_warnings(False)
self.assertIs(warnings.showwarning, showwarning)
def test_idle_formatter(self):
# Will fail if format changed without regenerating idlemsg
s = shell.idle_formatwarning(
'Test', UserWarning, 'test_warning.py', 99, 'Line of code')
self.assertEqual(idlemsg, s)
def test_shell_show(self):
with captured_stderr() as f:
shell.idle_showwarning(
'Test', UserWarning, 'test_warning.py', 99, f, 'Line of code')
self.assertEqual(shellmsg.splitlines(), f.getvalue().splitlines())
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,45 @@
"Test window, coverage 47%."
from idlelib import window
import unittest
from test.support import requires
from tkinter import Tk
class WindowListTest(unittest.TestCase):
def test_init(self):
wl = window.WindowList()
self.assertEqual(wl.dict, {})
self.assertEqual(wl.callbacks, [])
# Further tests need mock Window.
class ListedToplevelTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
window.registry = set()
requires('gui')
cls.root = Tk()
cls.root.withdraw()
@classmethod
def tearDownClass(cls):
window.registry = window.WindowList()
cls.root.update_idletasks()
## for id in cls.root.tk.call('after', 'info'):
## cls.root.after_cancel(id) # Need for EditorWindow.
cls.root.destroy()
del cls.root
def test_init(self):
win = window.ListedToplevel(self.root)
self.assertIn(win, window.registry)
self.assertEqual(win.focused_widget, win)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,39 @@
"Test zoomheight, coverage 66%."
# Some code is system dependent.
from idlelib import zoomheight
import unittest
from test.support import requires
from tkinter import Tk
from idlelib.editor import EditorWindow
class Test(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.editwin = EditorWindow(root=cls.root)
@classmethod
def tearDownClass(cls):
cls.editwin._close()
cls.root.update_idletasks()
for id in cls.root.tk.call('after', 'info'):
cls.root.after_cancel(id) # Need for EditorWindow.
cls.root.destroy()
del cls.root
def test_init(self):
zoom = zoomheight.ZoomHeight(self.editwin)
self.assertIs(zoom.editwin, self.editwin)
def test_zoom_height_event(self):
zoom = zoomheight.ZoomHeight(self.editwin)
zoom.zoom_height_event()
if __name__ == '__main__':
unittest.main(verbosity=2)