Issue #28572: Add 10% to coverage of IDLE's test_configdialog.

Update and augment description of the configuration system.
This commit is contained in:
Terry Jan Reedy 2016-11-07 17:15:01 -05:00
parent f5df73025d
commit f46b7823e6
4 changed files with 179 additions and 73 deletions

View File

@ -4,44 +4,50 @@
# When IDLE starts, it will look in # When IDLE starts, it will look in
# the following two sets of files, in order: # the following two sets of files, in order:
# #
# default configuration # default configuration files in idlelib
# --------------------- # --------------------------------------
# config-main.def the default general config file # config-main.def default general config file
# config-extensions.def the default extension config file # config-extensions.def default extension config file
# config-highlight.def the default highlighting config file # config-highlight.def default highlighting config file
# config-keys.def the default keybinding config file # config-keys.def default keybinding config file
# #
# user configuration # user configuration files in ~/.idlerc
# ------------------- # -------------------------------------
# ~/.idlerc/config-main.cfg the user general config file # config-main.cfg user general config file
# ~/.idlerc/config-extensions.cfg the user extension config file # config-extensions.cfg user extension config file
# ~/.idlerc/config-highlight.cfg the user highlighting config file # config-highlight.cfg user highlighting config file
# ~/.idlerc/config-keys.cfg the user keybinding config file # config-keys.cfg user keybinding config file
# #
# On Windows2000 and Windows XP the .idlerc directory is at # On Windows, the default location of the home directory ('~' above)
# Documents and Settings\<username>\.idlerc # depends on the version. For Windows 10, it is C:\Users\<username>.
#
# On Windows98 it is at c:\.idlerc
# #
# Any options the user saves through the config dialog will be saved to # Any options the user saves through the config dialog will be saved to
# the relevant user config file. Reverting any general setting to the # the relevant user config file. Reverting any general or extension
# default causes that entry to be wiped from the user file and re-read # setting to the default causes that entry to be wiped from the user
# from the default file. User highlighting themes or keybinding sets are # file and re-read from the default file. This rule applies to each
# retained unless specifically deleted within the config dialog. Choosing # item, except that the three editor font items are saved as a group.
# one of the default themes or keysets just applies the relevant settings #
# from the default file. # User highlighting themes and keybinding sets must have (section) names
# distinct from the default names. All items are added and saved as a
# group. They are retained unless specifically deleted within the config
# dialog. Choosing one of the default themes or keysets just applies the
# relevant settings from the default file.
#
# Additional help sources are listed in the [HelpFiles] section below
# and should be viewable by a web browser (or the Windows Help viewer in
# the case of .chm files). These sources will be listed on the Help
# menu. The pattern, and two examples, are
# #
# Additional help sources are listed in the [HelpFiles] section and must be
# viewable by a web browser (or the Windows Help viewer in the case of .chm
# files). These sources will be listed on the Help menu. The pattern is
# <sequence_number = menu item;/path/to/help/source> # <sequence_number = menu item;/path/to/help/source>
# You can't use a semi-colon in a menu item or path. The path will be platform # 1 = IDLE;C:/Programs/Python36/Lib/idlelib/help.html
# specific because of path separators, drive specs etc. # 2 = Pillow;https://pillow.readthedocs.io/en/latest/
# #
# It is best to use the Configuration GUI to set up additional help sources! # You can't use a semi-colon in a menu item or path. The path will be
# Example: # platform specific because of path separators, drive specs etc.
#1 = My Extra Help Source;/usr/share/doc/foo/index.html #
#2 = Another Help Source;/path/to/another.pdf # The default files should not be edited except to add new sections to
# config-extensions.def for added extensions . The user files should be
# modified through the Settings dialog.
[General] [General]
editor-on-startup= 0 editor-on-startup= 0

View File

@ -1,13 +1,20 @@
"""Provides access to stored IDLE configuration information. """idlelib.config -- Manage IDLE configuration information.
Refer to the comments at the beginning of config-main.def for a description of The comments at the beginning of config-main.def describe the
the available configuration files and the design implemented to update user configuration files and the design implemented to update user
configuration information. In particular, user configuration choices which configuration information. In particular, user configuration choices
duplicate the defaults will be removed from the user's configuration files, which duplicate the defaults will be removed from the user's
and if a file becomes empty, it will be deleted. configuration files, and if a user file becomes empty, it will be
deleted.
The contents of the user files may be altered using the Options/Configure IDLE The configuration database maps options to values. Comceptually, the
menu to access the configuration GUI (configdialog.py), or manually. database keys are tuples (config-type, section, item). As implemented,
there are separate dicts for default and user values. Each has
config-type keys 'main', 'extensions', 'highlight', and 'keys'. The
value for each key is a ConfigParser instance that maps section and item
to values. For 'main' and 'extenstons', user values override
default values. For 'highlight' and 'keys', user sections augment the
default sections (and must, therefore, have distinct names).
Throughout this module there is an emphasis on returning useable defaults Throughout this module there is an emphasis on returning useable defaults
when a problem occurs in returning a requested configuration value back to when a problem occurs in returning a requested configuration value back to

View File

@ -392,28 +392,28 @@ class ConfigDialog(Toplevel):
text=' Additional Help Sources ') text=' Additional Help Sources ')
#frameRun #frameRun
labelRunChoiceTitle = Label(frameRun, text='At Startup') labelRunChoiceTitle = Label(frameRun, text='At Startup')
radioStartupEdit = Radiobutton( self.radioStartupEdit = Radiobutton(
frameRun, variable=self.startupEdit, value=1, frameRun, variable=self.startupEdit, value=1,
command=self.SetKeysType, text="Open Edit Window") text="Open Edit Window")
radioStartupShell = Radiobutton( self.radioStartupShell = Radiobutton(
frameRun, variable=self.startupEdit, value=0, frameRun, variable=self.startupEdit, value=0,
command=self.SetKeysType, text='Open Shell Window') text='Open Shell Window')
#frameSave #frameSave
labelRunSaveTitle = Label(frameSave, text='At Start of Run (F5) ') labelRunSaveTitle = Label(frameSave, text='At Start of Run (F5) ')
radioSaveAsk = Radiobutton( self.radioSaveAsk = Radiobutton(
frameSave, variable=self.autoSave, value=0, frameSave, variable=self.autoSave, value=0,
command=self.SetKeysType, text="Prompt to Save") text="Prompt to Save")
radioSaveAuto = Radiobutton( self.radioSaveAuto = Radiobutton(
frameSave, variable=self.autoSave, value=1, frameSave, variable=self.autoSave, value=1,
command=self.SetKeysType, text='No Prompt') text='No Prompt')
#frameWinSize #frameWinSize
labelWinSizeTitle = Label( labelWinSizeTitle = Label(
frameWinSize, text='Initial Window Size (in characters)') frameWinSize, text='Initial Window Size (in characters)')
labelWinWidthTitle = Label(frameWinSize, text='Width') labelWinWidthTitle = Label(frameWinSize, text='Width')
entryWinWidth = Entry( self.entryWinWidth = Entry(
frameWinSize, textvariable=self.winWidth, width=3) frameWinSize, textvariable=self.winWidth, width=3)
labelWinHeightTitle = Label(frameWinSize, text='Height') labelWinHeightTitle = Label(frameWinSize, text='Height')
entryWinHeight = Entry( self.entryWinHeight = Entry(
frameWinSize, textvariable=self.winHeight, width=3) frameWinSize, textvariable=self.winHeight, width=3)
#frameHelp #frameHelp
frameHelpList = Frame(frameHelp) frameHelpList = Frame(frameHelp)
@ -443,17 +443,17 @@ class ConfigDialog(Toplevel):
frameHelp.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH) frameHelp.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
#frameRun #frameRun
labelRunChoiceTitle.pack(side=LEFT, anchor=W, padx=5, pady=5) labelRunChoiceTitle.pack(side=LEFT, anchor=W, padx=5, pady=5)
radioStartupShell.pack(side=RIGHT, anchor=W, padx=5, pady=5) self.radioStartupShell.pack(side=RIGHT, anchor=W, padx=5, pady=5)
radioStartupEdit.pack(side=RIGHT, anchor=W, padx=5, pady=5) self.radioStartupEdit.pack(side=RIGHT, anchor=W, padx=5, pady=5)
#frameSave #frameSave
labelRunSaveTitle.pack(side=LEFT, anchor=W, padx=5, pady=5) labelRunSaveTitle.pack(side=LEFT, anchor=W, padx=5, pady=5)
radioSaveAuto.pack(side=RIGHT, anchor=W, padx=5, pady=5) self.radioSaveAuto.pack(side=RIGHT, anchor=W, padx=5, pady=5)
radioSaveAsk.pack(side=RIGHT, anchor=W, padx=5, pady=5) self.radioSaveAsk.pack(side=RIGHT, anchor=W, padx=5, pady=5)
#frameWinSize #frameWinSize
labelWinSizeTitle.pack(side=LEFT, anchor=W, padx=5, pady=5) labelWinSizeTitle.pack(side=LEFT, anchor=W, padx=5, pady=5)
entryWinHeight.pack(side=RIGHT, anchor=E, padx=10, pady=5) self.entryWinHeight.pack(side=RIGHT, anchor=E, padx=10, pady=5)
labelWinHeightTitle.pack(side=RIGHT, anchor=E, pady=5) labelWinHeightTitle.pack(side=RIGHT, anchor=E, pady=5)
entryWinWidth.pack(side=RIGHT, anchor=E, padx=10, pady=5) self.entryWinWidth.pack(side=RIGHT, anchor=E, padx=10, pady=5)
labelWinWidthTitle.pack(side=RIGHT, anchor=E, pady=5) labelWinWidthTitle.pack(side=RIGHT, anchor=E, pady=5)
#frameHelp #frameHelp
frameHelpListButtons.pack(side=RIGHT, padx=5, pady=5, fill=Y) frameHelpListButtons.pack(side=RIGHT, padx=5, pady=5, fill=Y)

View File

@ -1,30 +1,123 @@
'''Test idlelib.configdialog. """Test idlelib.configdialog.
Coverage: 46% just by creating dialog. Half the class creates dialog, half works with user customizations.
The other half is code for working with user customizations. Coverage: 46% just by creating dialog, 56% with current tests.
''' """
from idlelib.configdialog import ConfigDialog # always test import from idlelib.configdialog import ConfigDialog, idleConf # test import
from test.support import requires from test.support import requires
requires('gui') requires('gui')
from tkinter import Tk from tkinter import Tk
import unittest import unittest
import idlelib.config as config
class ConfigDialogTest(unittest.TestCase): # Tests should not depend on fortuitous user configurations.
# They must not affect actual user .cfg files.
# Use solution from test_config: empty parsers with no filename.
usercfg = idleConf.userCfg
testcfg = {
'main': config.IdleUserConfParser(''),
'highlight': config.IdleUserConfParser(''),
'keys': config.IdleUserConfParser(''),
'extensions': config.IdleUserConfParser(''),
}
@classmethod # ConfigDialog.changedItems is a 3-level hierarchical dictionary of
def setUpClass(cls): # pending changes that mirrors the multilevel user config dict.
cls.root = Tk() # For testing, record args in a list for comparison with expected.
cls.root.withdraw() changes = []
class TestDialog(ConfigDialog):
def AddChangedItem(self, *args):
changes.append(args)
@classmethod def setUpModule():
def tearDownClass(cls): global root, configure
cls.root.update_idletasks() idleConf.userCfg = testcfg
cls.root.destroy() root = Tk()
del cls.root root.withdraw()
configure = TestDialog(root, 'Test', _utest=True)
def test_configdialog(self):
d = ConfigDialog(self.root, 'Test', _utest=True) def tearDownModule():
d.remove_var_callbacks() global root, configure
idleConf.userCfg = testcfg
configure.remove_var_callbacks()
del configure
root.update_idletasks()
root.destroy()
del root
class FontTabTest(unittest.TestCase):
def setUp(self):
changes.clear()
def test_font(self):
configure.fontName.set('Test Font')
expected = [
('main', 'EditorWindow', 'font', 'Test Font'),
('main', 'EditorWindow', 'font-size', '10'),
('main', 'EditorWindow', 'font-bold', False)]
self.assertEqual(changes, expected)
changes.clear()
configure.fontSize.set(12)
expected = [
('main', 'EditorWindow', 'font', 'Test Font'),
('main', 'EditorWindow', 'font-size', '12'),
('main', 'EditorWindow', 'font-bold', False)]
self.assertEqual(changes, expected)
changes.clear()
configure.fontBold.set(True)
expected = [
('main', 'EditorWindow', 'font', 'Test Font'),
('main', 'EditorWindow', 'font-size', '12'),
('main', 'EditorWindow', 'font-bold', True)]
self.assertEqual(changes, expected)
#def test_sample(self): pass # TODO
def test_tabspace(self):
configure.spaceNum.set(6)
self.assertEqual(changes, [('main', 'Indent', 'num-spaces', 6)])
class HighlightTest(unittest.TestCase):
def setUp(self):
changes.clear()
#def test_colorchoose(self): pass # TODO
class KeysTest(unittest.TestCase):
def setUp(self):
changes.clear()
class GeneralTest(unittest.TestCase):
def setUp(self):
changes.clear()
def test_startup(self):
configure.radioStartupEdit.invoke()
self.assertEqual(changes,
[('main', 'General', 'editor-on-startup', 1)])
def test_autosave(self):
configure.radioSaveAuto.invoke()
self.assertEqual(changes, [('main', 'General', 'autosave', 1)])
def test_editor_size(self):
configure.entryWinHeight.insert(0, '1')
self.assertEqual(changes, [('main', 'EditorWindow', 'height', '140')])
changes.clear()
configure.entryWinWidth.insert(0, '1')
self.assertEqual(changes, [('main', 'EditorWindow', 'width', '180')])
#def test_help_sources(self): pass # TODO
if __name__ == '__main__': if __name__ == '__main__':