summaryrefslogtreecommitdiffstats
path: root/Lib/idlelib/idle_test
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/idlelib/idle_test')
-rw-r--r--Lib/idlelib/idle_test/README.txt155
-rw-r--r--Lib/idlelib/idle_test/htest.py401
-rw-r--r--Lib/idlelib/idle_test/mock_idle.py28
-rw-r--r--Lib/idlelib/idle_test/mock_tk.py29
-rw-r--r--Lib/idlelib/idle_test/test_autocomplete.py143
-rw-r--r--Lib/idlelib/idle_test/test_autoexpand.py141
-rw-r--r--Lib/idlelib/idle_test/test_calltips.py26
-rw-r--r--Lib/idlelib/idle_test/test_configdialog.py32
-rw-r--r--Lib/idlelib/idle_test/test_editor.py16
-rw-r--r--Lib/idlelib/idle_test/test_formatparagraph.py12
-rw-r--r--Lib/idlelib/idle_test/test_hyperparser.py273
-rw-r--r--Lib/idlelib/idle_test/test_io.py233
-rw-r--r--Lib/idlelib/idle_test/test_parenmatch.py109
-rw-r--r--Lib/idlelib/idle_test/test_pathbrowser.py17
-rw-r--r--Lib/idlelib/idle_test/test_searchdialogbase.py165
-rw-r--r--Lib/idlelib/idle_test/test_searchengine.py2
-rw-r--r--Lib/idlelib/idle_test/test_text.py1
-rw-r--r--Lib/idlelib/idle_test/test_textview.py97
-rw-r--r--Lib/idlelib/idle_test/test_warning.py9
-rw-r--r--Lib/idlelib/idle_test/test_widgetredir.py122
20 files changed, 1926 insertions, 85 deletions
diff --git a/Lib/idlelib/idle_test/README.txt b/Lib/idlelib/idle_test/README.txt
index 6b92483..2339926 100644
--- a/Lib/idlelib/idle_test/README.txt
+++ b/Lib/idlelib/idle_test/README.txt
@@ -1,14 +1,24 @@
README FOR IDLE TESTS IN IDLELIB.IDLE_TEST
+0. Quick Start
+
+Automated unit tests were added in 2.7 for Python 2.x and 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 2.7 and 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 should contain a test_xyy.py for each. (For test modules, make
-'xyz' lower case, and possibly shorten it.) Each file should start with the
-something like the following template, with the blanks after after '.' and 'as',
-and before and after '_' filled in.
----
+subdirectory should contain a test_xyz.py for each, where 'xyz' is lowercased
+even if xyz.py is not. Here is a possible template, with the blanks after after
+'.' and 'as', and before and after '_' to be filled in.
+
import unittest
from test.support import requires
import idlelib. as
@@ -18,34 +28,33 @@ class _Test(unittest.TestCase):
def test_(self):
if __name__ == '__main__':
- unittest.main(verbosity=2, exit=2)
----
-Idle tests are run with unittest; do not use regrtest's test_main.
+ unittest.main(verbosity=2)
+
+Add the following at the end of xyy.py, with the appropriate name added after
+'test_'. Some files already have something like this for htest. If so, insert
+the import and unittest.main lines before the htest lines.
-Once test_xyy is written, the following should go at the end of xyy.py,
-with xyz (lowercased) added after 'test_'.
----
if __name__ == "__main__":
- from test import support; support.use_resources = ['gui']
import unittest
unittest.main('idlelib.idle_test.test_', verbosity=2, exit=False)
----
-2. Gui Tests
-Gui tests need 'requires' and 'use_resources' from test.support
-(test.test_support in 2.7). A test is a gui test if it creates a Tk root or
-master object either directly or indirectly by instantiating a tkinter or
-idle class. For the benefit of buildbot machines that do not have a graphics
-screen, gui tests must be 'guarded' by "requires('gui')" in a setUp
-function or method. This will typically be setUpClass.
+2. GUI Tests
+
+When run as part of the Python test suite, Idle gui tests need to run
+test.support.requires('gui') (test.test_support in 2.7). A test is a gui test
+if it creates a Tk root or master object either directly or indirectly by
+instantiating a tkinter or idle class. For the benefit of test processes that
+either have no graphical environment available or are not allowed to use it, gui
+tests must be 'guarded' by "requires('gui')" in a setUp function or method.
+This will typically be setUpClass.
+
+To avoid interfering with other gui tests, all gui objects must be destroyed and
+deleted by the end of the test. Widgets, such as a Tk root, created in a setUpX
+function, should be destroyed in the corresponding tearDownX. Module and class
+widget attributes should also be deleted..
-To avoid interfering with other gui tests, all gui objects must be destroyed
-and deleted by the end of the test. If a widget, such as a Tk root, is created
-in a setUpX function, destroy it in the corresponding tearDownX. For module
-and class attributes, also delete the widget.
----
@classmethod
def setUpClass(cls):
requires('gui')
@@ -55,56 +64,80 @@ and class attributes, also delete the widget.
def tearDownClass(cls):
cls.root.destroy()
del cls.root
----
-
-Support.requires('gui') returns true if it is either called in a main module
-(which never happens on buildbots) or if use_resources contains 'gui'.
-Use_resources is set by test.regrtest but not by unittest. So when running
-tests in another module with unittest, we set it ourselves, as in the xyz.py
-template above.
-
-Since non-gui tests always run, but gui tests only sometimes, tests of non-gui
-operations should best avoid needing a gui. Methods that make incidental use of
-tkinter (tk) variables and messageboxes can do this by using the mock classes in
-idle_test/mock_tk.py. There is also a mock text that will handle some uses of the
-tk Text widget.
-
-
-3. Running Tests
-
-Assume that xyz.py and test_xyz.py end with the "if __name__" statements given
-above. In Idle, pressing F5 in an editor window with either loaded will run all
-tests in the test_xyz file with the version of Python running Idle. The test
-report and any tracebacks will appear in the Shell window. The options in these
-"if __name__" statements are appropriate for developers running (as opposed to
-importing) either of the files during development: verbosity=2 lists all test
-methods in the file; exit=False avoids a spurious sys.exit traceback that would
-otherwise occur when running in Idle. The following command lines also run
-all test methods, including gui tests, in test_xyz.py. (The exceptions are that
-idlelib and idlelib.idle start Idle and idlelib.PyShell should (issue 18330).)
-
-python -m idlelib.xyz # With the capitalization of the xyz module
+
+
+Requires('gui') causes the test(s) it guards to be skipped if any of
+a few 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 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
-To run all idle_test/test_*.py tests, either interactively
-('>>>', with unittest imported) or from a command line, use one of the
-following. (Notes: unittest does not run gui tests; in 2.7, 'test ' (with the
-space) is 'test.regrtest '; where present, -v and -ugui can be omitted.)
+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'. (For 2.7, replace 'test' in the second line with
+'test.regrtest'.)
->>> unittest.main('idlelib.idle_test', verbosity=2, exit=False)
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 runs
+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. (But gui tests will not this way.)
+unittest on the command line.
python -m unittest -v idlelib.idle_test.test_xyz.Test_case.test_meth
+
+
+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
diff --git a/Lib/idlelib/idle_test/htest.py b/Lib/idlelib/idle_test/htest.py
new file mode 100644
index 0000000..3e24518
--- /dev/null
+++ b/Lib/idlelib/idle_test/htest.py
@@ -0,0 +1,401 @@
+'''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__ attrubute is also X (the usual situation).
+The first parameter of X must be 'parent'. When called, the parent
+argument will be the root window. X must create a child Toplevel
+window (or subclass thereof). The Toplevel may be a test widget or
+dialog, in which case the callable is the corresonding class. Or the
+Toplevel may contain the widget to be tested or set up a context in
+which a test widget is invoked. In this latter case, the callable is a
+wrapper function that sets up the Toplevel and other objects. Wrapper
+function names, such as _editor_window', should start with '_'.
+
+
+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
+AutoCompleteWindow.AutoCompleteWindow
+OutputWindow.OutputWindow (indirectly being tested with grep test)
+'''
+
+from importlib import import_module
+from idlelib.macosxSupport import _initializeTkVariantTests
+import tkinter as tk
+
+AboutDialog_spec = {
+ 'file': 'aboutDialog',
+ 'kwds': {'title': 'aboutDialog test',
+ '_htest': True,
+ },
+ 'msg': "Test every button. Ensure Python, TK and IDLE versions "
+ "are correctly displayed.\n [Close] to exit.",
+ }
+
+_calltip_window_spec = {
+ 'file': 'CallTipWindow',
+ 'kwds': {},
+ 'msg': "Typing '(' should display a calltip.\n"
+ "Typing ') should hide the calltip.\n"
+ }
+
+_class_browser_spec = {
+ 'file': 'ClassBrowser',
+ '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': 'ColorDelegator',
+ 'kwds': {},
+ 'msg': "The text is sample Python code.\n"
+ "Ensure components like comments, keywords, builtins,\n"
+ "string, definitions, and break are correctly colored.\n"
+ "The default color scheme is in idlelib/config-highlight.def"
+ }
+
+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': 'dynOptionMenuWidget',
+ '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': 'EditorWindow',
+ 'kwds': {},
+ 'msg': "Test editor functions of interest.\n"
+ "Best to close editor first."
+ }
+
+GetCfgSectionNameDialog_spec = {
+ 'file': 'configSectionNameDialog',
+ 'kwds': {'title':'Get Name',
+ 'message':'Enter something',
+ 'used_names': {'abc'},
+ '_htest': True},
+ 'msg': "After the text entered with [Ok] is stripped, <nothing>, "
+ "'abc', or more that 30 chars are errors.\n"
+ "Close 'Get Name' with a valid entry (printed to Shell), "
+ "[Cancel], or [X]",
+ }
+
+GetHelpSourceDialog_spec = {
+ 'file': 'configHelpSourceEdit',
+ 'kwds': {'title': 'Get helpsource',
+ '_htest': True},
+ 'msg': "Enter menu item name and help file path\n "
+ "<nothing> and more than 30 chars are invalid menu item names.\n"
+ "<nothing>, file does not exist are invalid path items.\n"
+ "Test for incomplete web address for help file path.\n"
+ "A valid entry will be printed to shell with [0k].\n"
+ "[Cancel] will print None to shell",
+ }
+
+# Update once issue21519 is resolved.
+GetKeysDialog_spec = {
+ 'file': 'keybindingDialog',
+ 'kwds': {'title': 'Test keybindings',
+ 'action': 'find-again',
+ 'currentKeySequences': [''] ,
+ '_htest': True,
+ },
+ 'msg': "Test for different key modifier sequences.\n"
+ "<nothing> is invalid.\n"
+ "No modifier key is invalid.\n"
+ "Shift key with [a-z],[0-9], function key, move key, tab, space"
+ "is invalid.\nNo validity checking if advanced key binding "
+ "entry is used."
+ }
+
+_grep_dialog_spec = {
+ 'file': 'GrepDialog',
+ 'kwds': {},
+ 'msg': "Click the 'Show GrepDialog' button.\n"
+ "Test the various 'Find-in-files' functions.\n"
+ "The results should be displayed in a new '*Output*' window.\n"
+ "'Right-click'->'Goto file/line' anywhere in the search results "
+ "should open that file \nin a new EditorWindow."
+ }
+
+_io_binding_spec = {
+ 'file': 'IOBinding',
+ 'kwds': {},
+ 'msg': "Test the following bindings.\n"
+ "<Control-o> to open file from dialog.\n"
+ "Edit the file.\n"
+ "<Control-s> to save the 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': 'MultiStatusBar',
+ 'kwds': {},
+ 'msg': "Ensure presence of multi-status bar below text area.\n"
+ "Click 'Update Status' to change the multi-status text"
+ }
+
+_object_browser_spec = {
+ 'file': 'ObjectBrowser',
+ 'kwds': {},
+ 'msg': "Double click on items upto the lowest level.\n"
+ "Attributes of the objects and related information "
+ "will be displayed side-by-side at each level."
+ }
+
+_path_browser_spec = {
+ 'file': 'PathBrowser',
+ 'kwds': {},
+ '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."
+ }
+
+_replace_dialog_spec = {
+ 'file': 'ReplaceDialog',
+ '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': 'SearchDialog',
+ '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'."
+ }
+
+_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"
+ }
+
+_tabbed_pages_spec = {
+ 'file': 'tabbedpages',
+ 'kwds': {},
+ 'msg': "Toggle between the two tabs 'foo' and 'bar'\n"
+ "Add a tab by entering a suitable name for it.\n"
+ "Remove an existing tab by entering its name.\n"
+ "Remove all existing tabs.\n"
+ "<nothing> is an invalid add page and remove page name.\n"
+ }
+
+TextViewer_spec = {
+ 'file': 'textView',
+ 'kwds': {'title': 'Test textView',
+ 'text':'The quick brown fox jumps over the lazy dog.\n'*35,
+ '_htest': True},
+ 'msg': "Test for read-only property of text.\n"
+ "Text is selectable. Window is scrollable.",
+ }
+
+_tooltip_spec = {
+ 'file': 'ToolTip',
+ 'kwds': {},
+ 'msg': "Place mouse cursor over both the buttons\n"
+ "A tooltip should appear with some text."
+ }
+
+_tree_widget_spec = {
+ 'file': 'TreeWidget',
+ 'kwds': {},
+ 'msg': "The canvas is scrollable.\n"
+ "Click on folders upto to the lowest level."
+ }
+
+_undo_delegator_spec = {
+ 'file': 'UndoDelegator',
+ 'kwds': {},
+ 'msg': "Click [Undo] to undo any action.\n"
+ "Click [Redo] to redo any action.\n"
+ "Click [Dump] to dump the current state "
+ "by printing to the console or the IDLE shell.\n"
+ }
+
+_widget_redirector_spec = {
+ 'file': 'WidgetRedirector',
+ '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)
+ _initializeTkVariantTests(root)
+
+ # a scrollable Label like constant width text widget.
+ frameLabel = tk.Frame(root, padx=10)
+ frameLabel.pack()
+ text = tk.Text(frameLabel, wrap='word')
+ text.configure(bg=root.cget('bg'), relief='flat', height=4, width=70)
+ scrollbar = tk.Scrollbar(frameLabel, command=text.yview)
+ text.config(yscrollcommand=scrollbar.set)
+ scrollbar.pack(side='right', fill='y', expand=False)
+ text.pack(side='left', fill='both', expand=True)
+
+ 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('')
+ callable_object = None
+ test_kwds = None
+
+ def next():
+
+ 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():
+ widget = callable_object(**test_kwds)
+ try:
+ print(widget.result)
+ except AttributeError:
+ pass
+
+ button = tk.Button(root, textvariable=test_name, command=run_test)
+ button.pack()
+ next_button = tk.Button(root, text="Next", command=next)
+ next_button.pack()
+
+ next()
+
+ root.mainloop()
+
+if __name__ == '__main__':
+ run()
diff --git a/Lib/idlelib/idle_test/mock_idle.py b/Lib/idlelib/idle_test/mock_idle.py
index c364a24..1672a34 100644
--- a/Lib/idlelib/idle_test/mock_idle.py
+++ b/Lib/idlelib/idle_test/mock_idle.py
@@ -5,6 +5,33 @@ Attributes and methods will be added as needed for tests.
from idlelib.idle_test.mock_tk import Text
+class Func:
+ '''Mock function captures args and returns result set by test.
+
+ Attributes:
+ self.called - records call even if no args, kwds passed.
+ self.result - set by init, returned by call.
+ self.args - captures positional arguments.
+ self.kwds - captures keyword arguments.
+
+ Most common use will probably be to mock methods.
+ Mock_tk.Var and Mbox_func are special variants of this.
+ '''
+ def __init__(self, result=None):
+ self.called = False
+ self.result = result
+ self.args = None
+ self.kwds = None
+ def __call__(self, *args, **kwds):
+ self.called = True
+ self.args = args
+ self.kwds = kwds
+ if isinstance(self.result, BaseException):
+ raise self.result
+ else:
+ return self.result
+
+
class Editor:
'''Minimally imitate EditorWindow.EditorWindow class.
'''
@@ -17,6 +44,7 @@ class Editor:
last = self.text.index('end')
return first, last
+
class UndoDelegator:
'''Minimally imitate UndoDelegator,UndoDelegator class.
'''
diff --git a/Lib/idlelib/idle_test/mock_tk.py b/Lib/idlelib/idle_test/mock_tk.py
index 762bbc9..86fe848 100644
--- a/Lib/idlelib/idle_test/mock_tk.py
+++ b/Lib/idlelib/idle_test/mock_tk.py
@@ -1,9 +1,27 @@
"""Classes that replace tkinter gui objects used by an object being tested.
-A gui object is anything with a master or parent paramenter, which is typically
-required in spite of what the doc strings say.
+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):
@@ -20,9 +38,10 @@ class Mbox_func:
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):
- self.result = None # The return for all show funcs
+ 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
@@ -97,7 +116,7 @@ class Text:
"""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 contrained by the number of lines and linelengths of
+ 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:
diff --git a/Lib/idlelib/idle_test/test_autocomplete.py b/Lib/idlelib/idle_test/test_autocomplete.py
new file mode 100644
index 0000000..3a2192e
--- /dev/null
+++ b/Lib/idlelib/idle_test/test_autocomplete.py
@@ -0,0 +1,143 @@
+import unittest
+from test.support import requires
+from tkinter import Tk, Text
+
+import idlelib.AutoComplete as ac
+import idlelib.AutoCompleteWindow as acw
+import idlelib.macosxSupport as mac
+from idlelib.idle_test.mock_idle import Func
+from idlelib.idle_test.mock_tk import Event
+
+class AutoCompleteWindow:
+ def complete():
+ return
+
+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()
+ mac.setupApp(cls.root, None)
+ cls.text = Text(cls.root)
+ cls.editor = DummyEditwin(cls.root, cls.text)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.root.destroy()
+ del cls.text
+ del cls.editor
+ 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
+
+ # 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)
diff --git a/Lib/idlelib/idle_test/test_autoexpand.py b/Lib/idlelib/idle_test/test_autoexpand.py
new file mode 100644
index 0000000..7ca941e
--- /dev/null
+++ b/Lib/idlelib/idle_test/test_autoexpand.py
@@ -0,0 +1,141 @@
+"""Unit tests for idlelib.AutoExpand"""
+import unittest
+from test.support import requires
+from tkinter import Text, Tk
+#from idlelib.idle_test.mock_tk import Text
+from idlelib.AutoExpand import AutoExpand
+
+
+class Dummy_Editwin:
+ # AutoExpand.__init__ only needs .text
+ def __init__(self, text):
+ self.text = text
+
+class AutoExpandTest(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ if 'tkinter' in str(Text):
+ requires('gui')
+ cls.tk = Tk()
+ cls.text = Text(cls.tk)
+ else:
+ cls.text = Text()
+ cls.auto_expand = AutoExpand(Dummy_Editwin(cls.text))
+
+ @classmethod
+ def tearDownClass(cls):
+ if hasattr(cls, 'tk'):
+ cls.tk.destroy()
+ del cls.tk
+ del cls.text, cls.auto_expand
+
+ 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)
diff --git a/Lib/idlelib/idle_test/test_calltips.py b/Lib/idlelib/idle_test/test_calltips.py
index f363764..b2a733c 100644
--- a/Lib/idlelib/idle_test/test_calltips.py
+++ b/Lib/idlelib/idle_test/test_calltips.py
@@ -52,11 +52,12 @@ class Get_signatureTest(unittest.TestCase):
def gtest(obj, out):
self.assertEqual(signature(obj), out)
- gtest(List, List.__doc__)
+ if List.__doc__ is not None:
+ gtest(List, List.__doc__)
gtest(list.__new__,
- 'T.__new__(S, ...) -> a new object with type S, a subtype of T')
+ 'Create and return a new object. See help(type) for accurate signature.')
gtest(list.__init__,
- 'x.__init__(...) initializes x; see help(type(x)) for signature')
+ '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)
@@ -66,10 +67,12 @@ class Get_signatureTest(unittest.TestCase):
gtest(SB(), default_tip)
def test_signature_wrap(self):
- self.assertEqual(signature(textwrap.TextWrapper), '''\
+ 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)''')
+ drop_whitespace=True, break_on_hyphens=True, tabsize=8, *, max_lines=None,
+ placeholder=' [...]')''')
def test_docline_truncation(self):
def f(): pass
@@ -107,20 +110,23 @@ bytes() -> empty bytes object''')
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 + '\ndoc')
+ 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 + "\ndoc")
- self.assertEqual(signature(TC.cm), "(a)\ndoc")
- self.assertEqual(signature(TC.sm), "(b)\ndoc")
+ 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 + "\ndoc")
+ self.assertEqual(signature(meth), mtip + doc)
def test_starred_parameter(self):
# test that starred first parameter is *not* removed from argspec
diff --git a/Lib/idlelib/idle_test/test_configdialog.py b/Lib/idlelib/idle_test/test_configdialog.py
new file mode 100644
index 0000000..6883123
--- /dev/null
+++ b/Lib/idlelib/idle_test/test_configdialog.py
@@ -0,0 +1,32 @@
+'''Unittests for idlelib/configHandler.py
+
+Coverage: 46% just by creating dialog. The other half is change code.
+
+'''
+import unittest
+from test.support import requires
+from tkinter import Tk
+from idlelib.configDialog import ConfigDialog
+from idlelib.macosxSupport import _initializeTkVariantTests
+
+
+class ConfigDialogTest(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ requires('gui')
+ cls.root = Tk()
+ _initializeTkVariantTests(cls.root)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.root.destroy()
+ del cls.root
+
+ def test_dialog(self):
+ d=ConfigDialog(self.root, 'Test', _utest=True)
+ d.destroy()
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/Lib/idlelib/idle_test/test_editor.py b/Lib/idlelib/idle_test/test_editor.py
new file mode 100644
index 0000000..a31d26d
--- /dev/null
+++ b/Lib/idlelib/idle_test/test_editor.py
@@ -0,0 +1,16 @@
+import unittest
+from tkinter import Tk, Text
+from idlelib.EditorWindow import EditorWindow
+from test.support import requires
+
+class Editor_func_test(unittest.TestCase):
+ def test_filename_to_unicode(self):
+ func = EditorWindow._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)
diff --git a/Lib/idlelib/idle_test/test_formatparagraph.py b/Lib/idlelib/idle_test/test_formatparagraph.py
index f4a7c2d..f6039e6 100644
--- a/Lib/idlelib/idle_test/test_formatparagraph.py
+++ b/Lib/idlelib/idle_test/test_formatparagraph.py
@@ -2,7 +2,7 @@
import unittest
from idlelib import FormatParagraph as fp
from idlelib.EditorWindow import EditorWindow
-from tkinter import Tk, Text, TclError
+from tkinter import Tk, Text
from test.support import requires
@@ -293,7 +293,7 @@ class FormatEventTest(unittest.TestCase):
# 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')
+ self.formatter('ParameterDoesNothing', limit=70)
result = text.get('1.0', 'insert')
# find function includes \n
expected = (
@@ -305,7 +305,7 @@ class FormatEventTest(unittest.TestCase):
# 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')
+ self.formatter('ParameterDoesNothing', limit=70)
result = text.get('1.0', 'insert')
# selection excludes \n
expected = (
@@ -319,7 +319,7 @@ class FormatEventTest(unittest.TestCase):
# Select 2 long lines.
text.insert('1.0', self.multiline_test_string)
text.tag_add('sel', '2.0', '4.0')
- self.formatter('ParameterDoesNothing')
+ 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"
@@ -334,7 +334,7 @@ class FormatEventTest(unittest.TestCase):
# Set cursor ('insert') to '1.0', within block.
text.insert('1.0', self.multiline_test_comment)
- self.formatter('ParameterDoesNothing')
+ 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"
@@ -348,7 +348,7 @@ class FormatEventTest(unittest.TestCase):
# 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')
+ self.formatter('ParameterDoesNothing', limit=70)
result = text.get('1.0', 'insert')
expected = (
"# The first line is under the max width.\n"
diff --git a/Lib/idlelib/idle_test/test_hyperparser.py b/Lib/idlelib/idle_test/test_hyperparser.py
new file mode 100644
index 0000000..edfc783
--- /dev/null
+++ b/Lib/idlelib/idle_test/test_hyperparser.py
@@ -0,0 +1,273 @@
+"""Unittest for idlelib.HyperParser"""
+import unittest
+from test.support import requires
+from tkinter import Tk, Text
+from idlelib.EditorWindow import EditorWindow
+from idlelib.HyperParser import HyperParser
+
+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.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('a٢'))
+
+ # invalid unicode identifiers
+ self.assertFalse(is_valid_id('2a'))
+ self.assertFalse(is_valid_id('٢a'))
+ self.assertFalse(is_valid_id('a²'))
+
+ # 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)
diff --git a/Lib/idlelib/idle_test/test_io.py b/Lib/idlelib/idle_test/test_io.py
new file mode 100644
index 0000000..e0e3b98
--- /dev/null
+++ b/Lib/idlelib/idle_test/test_io.py
@@ -0,0 +1,233 @@
+import unittest
+import io
+from idlelib.PyShell import PseudoInputFile, PseudoOutputFile
+
+
+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 PseudeOutputFilesTest(unittest.TestCase):
+ def test_misc(self):
+ shell = MockShell()
+ f = 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 = 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 = 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 = 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 = 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)
+
+
+class PseudeInputFilesTest(unittest.TestCase):
+ def test_misc(self):
+ shell = MockShell()
+ f = 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 = 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 = 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 = 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 = 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 = 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)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Lib/idlelib/idle_test/test_parenmatch.py b/Lib/idlelib/idle_test/test_parenmatch.py
new file mode 100644
index 0000000..9aba4be
--- /dev/null
+++ b/Lib/idlelib/idle_test/test_parenmatch.py
@@ -0,0 +1,109 @@
+"""Test idlelib.ParenMatch."""
+# This must currently be a gui test because ParenMatch methods use
+# several text methods not defined on idlelib.idle_test.mock_tk.Text.
+
+import unittest
+from unittest.mock import Mock
+from test.support import requires
+from tkinter import Tk, Text
+from idlelib.ParenMatch import ParenMatch
+
+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):
+ requires('gui')
+ cls.root = Tk()
+ 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.destroy()
+ del cls.root
+
+ def tearDown(self):
+ self.text.delete('1.0', 'end')
+
+ def test_paren_expression(self):
+ """
+ Test ParenMatch with 'expression' style.
+ """
+ text = self.text
+ pm = ParenMatch(self.editwin)
+ pm.set_style('expression')
+
+ text.insert('insert', 'def foobar(a, b')
+ pm.flash_paren_event('event')
+ self.assertIn('<<parenmatch-check-restore>>', text.event_info())
+ self.assertTupleEqual(text.tag_prevrange('paren', 'end'),
+ ('1.10', '1.15'))
+ text.insert('insert', ')')
+ pm.restore_event()
+ self.assertNotIn('<<parenmatch-check-restore>>', text.event_info())
+ self.assertEqual(text.tag_prevrange('paren', 'end'), ())
+
+ # paren_closed_event can only be tested as below
+ pm.paren_closed_event('event')
+ self.assertTupleEqual(text.tag_prevrange('paren', 'end'),
+ ('1.10', '1.16'))
+
+ def test_paren_default(self):
+ """
+ Test ParenMatch with 'default' style.
+ """
+ text = self.text
+ pm = ParenMatch(self.editwin)
+ pm.set_style('default')
+
+ text.insert('insert', 'def foobar(a, b')
+ pm.flash_paren_event('event')
+ self.assertIn('<<parenmatch-check-restore>>', text.event_info())
+ self.assertTupleEqual(text.tag_prevrange('paren', 'end'),
+ ('1.10', '1.11'))
+ text.insert('insert', ')')
+ pm.restore_event()
+ self.assertNotIn('<<parenmatch-check-restore>>', text.event_info())
+ self.assertEqual(text.tag_prevrange('paren', 'end'), ())
+
+ 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 = ParenMatch(self.editwin)
+
+ text.insert('insert', '# this is a commen)')
+ self.assertIsNone(pm.paren_closed_event('event'))
+
+ text.insert('insert', '\ndef')
+ self.assertIsNone(pm.flash_paren_event('event'))
+ self.assertIsNone(pm.paren_closed_event('event'))
+
+ text.insert('insert', ' a, *arg)')
+ self.assertIsNone(pm.paren_closed_event('event'))
+
+ def test_handle_restore_timer(self):
+ pm = ParenMatch(self.editwin)
+ 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)
diff --git a/Lib/idlelib/idle_test/test_pathbrowser.py b/Lib/idlelib/idle_test/test_pathbrowser.py
index 7ad7c97..afb886f 100644
--- a/Lib/idlelib/idle_test/test_pathbrowser.py
+++ b/Lib/idlelib/idle_test/test_pathbrowser.py
@@ -1,5 +1,8 @@
import unittest
-import idlelib.PathBrowser as PathBrowser
+import os
+import sys
+import idlelib
+from idlelib import PathBrowser
class PathBrowserTest(unittest.TestCase):
@@ -7,6 +10,18 @@ class PathBrowserTest(unittest.TestCase):
# 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)
+
+ 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)
diff --git a/Lib/idlelib/idle_test/test_searchdialogbase.py b/Lib/idlelib/idle_test/test_searchdialogbase.py
new file mode 100644
index 0000000..8036b91
--- /dev/null
+++ b/Lib/idlelib/idle_test/test_searchdialogbase.py
@@ -0,0 +1,165 @@
+'''Unittests for idlelib/SearchDialogBase.py
+
+Coverage: 99%. 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, Toplevel, Frame ##, BooleanVar, StringVar
+from idlelib import SearchEngine as se
+from idlelib import SearchDialogBase 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 = Toplevel(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.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 = Toplevel(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 = Toplevel(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)
+ if state == 1:
+ button.deselect()
+ else:
+ button.select()
+ self.assertEqual(var.get(), 1 - 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)
+ buttons[val].select()
+ self.assertEqual(var.get(), 1 - state)
+ buttons[1-val].select()
+ self.assertEqual(var.get(), state)
+
+ def test_make_button(self):
+ self.dialog.top = Toplevel(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.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)
diff --git a/Lib/idlelib/idle_test/test_searchengine.py b/Lib/idlelib/idle_test/test_searchengine.py
index 129a5a3..c7792fb 100644
--- a/Lib/idlelib/idle_test/test_searchengine.py
+++ b/Lib/idlelib/idle_test/test_searchengine.py
@@ -7,7 +7,7 @@
import re
import unittest
-from test.support import requires
+# from test.support import requires
from tkinter import BooleanVar, StringVar, TclError # ,Tk, Text
import tkinter.messagebox as tkMessageBox
from idlelib import SearchEngine as se
diff --git a/Lib/idlelib/idle_test/test_text.py b/Lib/idlelib/idle_test/test_text.py
index 5ac2fd7..7e823df 100644
--- a/Lib/idlelib/idle_test/test_text.py
+++ b/Lib/idlelib/idle_test/test_text.py
@@ -3,7 +3,6 @@ import unittest
from test.support import requires
from _tkinter import TclError
-import tkinter as tk
class TextTest(object):
diff --git a/Lib/idlelib/idle_test/test_textview.py b/Lib/idlelib/idle_test/test_textview.py
new file mode 100644
index 0000000..68e5b82
--- /dev/null
+++ b/Lib/idlelib/idle_test/test_textview.py
@@ -0,0 +1,97 @@
+'''Test the functions and main class method of textView.py.
+
+Since all methods and functions create (or destroy) a TextViewer, which
+is a widget containing multiple widgets, all tests must be gui tests.
+Using mock Text would not change this. Other mocks are used to retrieve
+information about calls.
+
+The coverage is essentially 100%.
+'''
+from test.support import requires
+requires('gui')
+
+import unittest
+import os
+from tkinter import Tk
+from idlelib import textView as tv
+from idlelib.idle_test.mock_idle import Func
+from idlelib.idle_test.mock_tk import Mbox
+
+def setUpModule():
+ global root
+ root = Tk()
+
+def tearDownModule():
+ global root
+ root.destroy() # pyflakes falsely sees root as undefined
+ del root
+
+
+class TV(tv.TextViewer): # used by TextViewTest
+ transient = Func()
+ grab_set = Func()
+ wait_window = Func()
+
+class TextViewTest(unittest.TestCase):
+
+ def setUp(self):
+ TV.transient.__init__()
+ TV.grab_set.__init__()
+ TV.wait_window.__init__()
+
+ def test_init_modal(self):
+ view = TV(root, 'Title', 'test text')
+ self.assertTrue(TV.transient.called)
+ self.assertTrue(TV.grab_set.called)
+ self.assertTrue(TV.wait_window.called)
+ view.Ok()
+
+ def test_init_nonmodal(self):
+ view = TV(root, 'Title', 'test text', modal=False)
+ self.assertFalse(TV.transient.called)
+ self.assertFalse(TV.grab_set.called)
+ self.assertFalse(TV.wait_window.called)
+ view.Ok()
+
+ def test_ok(self):
+ view = TV(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 textviewTest(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ cls.orig_mbox = tv.tkMessageBox
+ tv.tkMessageBox = Mbox
+
+ @classmethod
+ def tearDownClass(cls):
+ tv.tkMessageBox = cls.orig_mbox
+ del cls.orig_mbox
+
+ def test_view_text(self):
+ # If modal True, tkinter will error with 'can't invoke "event" command'
+ view = tv.view_text(root, 'Title', 'test text', modal=False)
+ self.assertIsInstance(view, tv.TextViewer)
+
+ def test_view_file(self):
+ test_dir = os.path.dirname(__file__)
+ testfile = os.path.join(test_dir, 'test_textview.py')
+ view = tv.view_file(root, 'Title', testfile, modal=False)
+ self.assertIsInstance(view, tv.TextViewer)
+ self.assertIn('Test', view.textView.get('1.0', '1.end'))
+ view.Ok()
+
+ # Mock messagebox will be used and view_file will not return anything
+ testfile = os.path.join(test_dir, '../notthere.py')
+ view = tv.view_file(root, 'Title', testfile, modal=False)
+ self.assertIsNone(view)
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/Lib/idlelib/idle_test/test_warning.py b/Lib/idlelib/idle_test/test_warning.py
index 18627dd..54ac993 100644
--- a/Lib/idlelib/idle_test/test_warning.py
+++ b/Lib/idlelib/idle_test/test_warning.py
@@ -68,6 +68,15 @@ class ShellWarnTest(unittest.TestCase):
'Test', UserWarning, 'test_warning.py', 99, f, 'Line of code')
self.assertEqual(shellmsg.splitlines(), f.getvalue().splitlines())
+class ImportWarnTest(unittest.TestCase):
+ def test_idlever(self):
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ import idlelib.idlever
+ self.assertEqual(len(w), 1)
+ self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
+ self.assertIn("version", str(w[-1].message))
+
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)
diff --git a/Lib/idlelib/idle_test/test_widgetredir.py b/Lib/idlelib/idle_test/test_widgetredir.py
new file mode 100644
index 0000000..6440561
--- /dev/null
+++ b/Lib/idlelib/idle_test/test_widgetredir.py
@@ -0,0 +1,122 @@
+"""Unittest for idlelib.WidgetRedirector
+
+100% coverage
+"""
+from test.support import requires
+import unittest
+from idlelib.idle_test.mock_idle import Func
+from tkinter import Tk, Text, TclError
+from idlelib.WidgetRedirector import WidgetRedirector
+
+
+class InitCloseTest(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ requires('gui')
+ cls.tk = Tk()
+ cls.text = Text(cls.tk)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.text.destroy()
+ cls.tk.destroy()
+ del cls.text, cls.tk
+
+ 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.tk = Tk()
+ cls.text = Text(cls.tk)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.text.destroy()
+ cls.tk.destroy()
+ del cls.text, cls.tk
+
+ 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.tk.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.tk.call(self.text._w, 'insert', 'boo'), '')
+
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)