-
-
Notifications
You must be signed in to change notification settings - Fork 30.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-75710: IDLE: Add docstrings and tests to some editor.py functions #3669
base: main
Are you sure you want to change the base?
Conversation
I was trying to understand the editor functions accessed from configdialog so that I could test those and thought the best way to understand what was really happening was to document and test the functions being called. Some notes:
Sorry that this is still massive. I really tried to limit it to what I needed to look at. |
Lib/idlelib/idle_test/test_editor.py
Outdated
@@ -1,14 +1,432 @@ | |||
""" Test idlelib.editor. | |||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will add a coverage line so we know how far we yet have to go.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I later did, which is part of the merge conflict.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, part of the merge conflict.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing work adding so many tests for the editor window! I think these are sorely needed, and it's too bad they've been languishing here for a while now.
I've got a bunch of review comments, but overall this is a huge improvement, and I'd like to help get it through!
Lib/idlelib/editor.py
Outdated
|
||
This should be called before the keybindings are applied | ||
in ApplyKeyBindings() otherwise the old bindings will still exist. | ||
Note: this does not remove the Tk/Tcl keybindings attached to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add another blank line before the "Note:".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or leave out 'Note:'. I am going to revise these added docstrings.
Lib/idlelib/editor.py
Outdated
@@ -414,6 +414,21 @@ def set_line_and_column(self, event=None): | |||
|
|||
|
|||
def createmenubar(self): | |||
"""Populate the menu bar widget for the editor window. | |||
|
|||
Each option on the menubar is itself a cascade-type Menu widget |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO in this case, the rest of the doc-string after the first line should be a comment instead, since it describes the method's code rather than it's purpose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem goes deeper. There should be separate window-with-menu, frame, and text classes, and possibly an IDLE-menu class with a docstring that explains the menu system. And it maybe should be in mainmenu.py.
At present, the menu functions are scattered around this file. Right now, they should be at least gathered together in one section with one class-level block comment. Or maybe the added comment should be in mainmenu.py. This should be a separate PR.
Lib/idlelib/editor.py
Outdated
# Called from configdialog.py | ||
self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet() | ||
self.apply_bindings() | ||
for extensionName in self.get_standard_extension_names(): | ||
xkeydefs = idleConf.GetExtensionBindings(extensionName) | ||
if xkeydefs: | ||
self.apply_bindings(xkeydefs) | ||
#update menu accelerators | ||
# Update menu accelerators. | ||
# XXX - split into its own function and call it from here? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally, I don't like adding such comments to the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Such comments should have initials and at least a year. But yes, they can hang around for decades. In this case I agree, as a separate method is needed to group together menu methods. Understanding this code requires knowing the menu term definitions.
Lib/idlelib/editor.py
Outdated
"Update the additional help entries on the Help menu" | ||
"""Update the additional help entries on the Help menu. | ||
|
||
First the existing additional help entries are removed from |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this doc-string, I find the added description of how the method works entirely unnecessary, because it is already similarly detailed in comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed.
Lib/idlelib/editor.py
Outdated
"Create a callback with the helpfile value frozen at definition time" | ||
"""Create a callback with the helpfile value frozen at definition time. | ||
|
||
Args: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This style of describing args in doc-strings is non-standard. I think it would be shorter and simpler to just remove the "Args:" heading, and change the "Returns:" section into a single line of text.
Likewise for other methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Cheryl did this PR after we refactored configdialog. For that, we had some exceptionally wordy docstrings to keep things clear. This docstring should start with 'Return...'
Lib/idlelib/editor.py
Outdated
@@ -999,6 +1037,8 @@ def _close(self): | |||
if self.color: | |||
self.color.close(False) | |||
self.color = None | |||
# Allow code context to close its text.after calls. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does unbinding the event allow the code context to do anything?
Also this is surprising to find given the title of this PR. If kept, it should be mentioned in the description and in the final commit message as an additional fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed that revising _close, which has been somewhat problematical, is a separate issue.
Lib/idlelib/editor.py
Outdated
Args: | ||
menudefs: Menu and submenu names, underlines (shortcuts), | ||
and events which is a list of tuples of the form: | ||
[(menu1, [(submenu1a, '<<virtual event>>'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't the menu and sub-menu names in the examples be stings?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not be here. See previous comment about explaining menu terms once.
|
||
def setUpModule(): | ||
global root, editwin | ||
requires('gui') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it overly restrictive to require a GUI for all tests here? As an extreme example, testing prepstr()
certainly doesn't require a GUI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. I might be tempted to have separate test_editor_gui and test_editor_nogui files if not for the problem of coverage measurement. In any case, refactoring could isolate non-gui code for easier testing.
eq(w.menudict['file'].entrycget(3, 'label'), 'Recent Files') | ||
# No items added to helpmenu, so the length has no value. | ||
eq(w.base_helpmenu_length, None) | ||
w.fill_menus.assert_called_with() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If see this assertion and the next one as testing the implementation rather the behavior, so I suggest removing them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will check whether these should merely be deleted or replace with a behavior test.
# EditorWindow that create the menubar and submenus. | ||
# The class is mocked in order to prevent the functions | ||
# from being called automatically. | ||
w = cls.mock_editwin = mock.Mock(editor.EditorWindow) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do I understand correctly that this uses a mock mostly to avoid all of the setup in EditorWindow.init()? That could also be accomplished by patching that method (e.g. with mock.patch
) or by creating a sub-class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have elsewhere used a standalone class. In this case, a subclass would inherit menu_spec. I will look at how w is used.
Ping, @csabella? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am going to first edit the new docstrings as indicated in my reponses to Tal's comments, and make a separate PR.
Lib/idlelib/editor.py
Outdated
@@ -414,6 +414,21 @@ def set_line_and_column(self, event=None): | |||
|
|||
|
|||
def createmenubar(self): | |||
"""Populate the menu bar widget for the editor window. | |||
|
|||
Each option on the menubar is itself a cascade-type Menu widget |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem goes deeper. There should be separate window-with-menu, frame, and text classes, and possibly an IDLE-menu class with a docstring that explains the menu system. And it maybe should be in mainmenu.py.
At present, the menu functions are scattered around this file. Right now, they should be at least gathered together in one section with one class-level block comment. Or maybe the added comment should be in mainmenu.py. This should be a separate PR.
Lib/idlelib/editor.py
Outdated
|
||
This should be called before the keybindings are applied | ||
in ApplyKeyBindings() otherwise the old bindings will still exist. | ||
Note: this does not remove the Tk/Tcl keybindings attached to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or leave out 'Note:'. I am going to revise these added docstrings.
Lib/idlelib/editor.py
Outdated
# Called from configdialog.py | ||
self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet() | ||
self.apply_bindings() | ||
for extensionName in self.get_standard_extension_names(): | ||
xkeydefs = idleConf.GetExtensionBindings(extensionName) | ||
if xkeydefs: | ||
self.apply_bindings(xkeydefs) | ||
#update menu accelerators | ||
# Update menu accelerators. | ||
# XXX - split into its own function and call it from here? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Such comments should have initials and at least a year. But yes, they can hang around for decades. In this case I agree, as a separate method is needed to group together menu methods. Understanding this code requires knowing the menu term definitions.
Lib/idlelib/editor.py
Outdated
"Update the additional help entries on the Help menu" | ||
"""Update the additional help entries on the Help menu. | ||
|
||
First the existing additional help entries are removed from |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed.
Lib/idlelib/editor.py
Outdated
"Create a callback with the helpfile value frozen at definition time" | ||
"""Create a callback with the helpfile value frozen at definition time. | ||
|
||
Args: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Cheryl did this PR after we refactored configdialog. For that, we had some exceptionally wordy docstrings to keep things clear. This docstring should start with 'Return...'
Lib/idlelib/editor.py
Outdated
s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s) | ||
# Remove Key- from string. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first 2 added comments are helpful, the rest are obvious.
Lib/idlelib/idle_test/test_editor.py
Outdated
@@ -1,14 +1,432 @@ | |||
""" Test idlelib.editor. | |||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, part of the merge conflict.
|
||
def setUpModule(): | ||
global root, editwin | ||
requires('gui') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. I might be tempted to have separate test_editor_gui and test_editor_nogui files if not for the problem of coverage measurement. In any case, refactoring could isolate non-gui code for easier testing.
# EditorWindow that create the menubar and submenus. | ||
# The class is mocked in order to prevent the functions | ||
# from being called automatically. | ||
w = cls.mock_editwin = mock.Mock(editor.EditorWindow) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have elsewhere used a standalone class. In this case, a subclass would inherit menu_spec. I will look at how w is used.
eq(w.menudict['file'].entrycget(3, 'label'), 'Recent Files') | ||
# No items added to helpmenu, so the length has no value. | ||
eq(w.base_helpmenu_length, None) | ||
w.fill_menus.assert_called_with() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will check whether these should merely be deleted or replace with a behavior test.
When you're done making the requested changes, leave the comment: |
Whoops, by responding to Tal's comments as part of a new review, my responses got listed twice, once under Tal's comment where they below and then again separately, without the context. |
I minimally resolved the merge conflict in the web box so I could proceed. This did a full merge update rather than just updating the one file. There is still the redundancy of global requires gui and class requires gui to be fixed, and Tal's other conflicts. Since I had to do this, I will go ahead and edit everything on this PR. |
I stopped the Windows test failures by commenting out the failing lines with an explanation. The Ubuntu failure is
May do the same here. |
I think editor.py is more or less commit ready. Tal, review again or not as you wish. Test_editor is not ready. test_editor passes, but still needs requires('gui') restricted. I want to try Tal's suggested alternative. And also make sure that every test is actually testing. The test I revised was not, as the function supposedly being tested was mocked. test_mainmenu passes by itself, but test_idle fails with File "f:\dev\3x\lib\idlelib\idle_test\test_mainmenu.py", line 17, in test_default_keydefs 5 happens to be the length of keydefs used in test_editor, which run first, so the test change is somehow affecting mainmenu. This is probably via changing something in config. test_editor does not import config, but editor does. |
Additional test failure: Windows error box titled "Document Start Failure" with message "(X) boom". Test freezes until box is dismissed. I suspect a test of additional help resources needs mocks for both Windows and *nix. |
Commit extracted from PR python#3669. Will edits more later. Co-authored-by: Cheryl Sabella <cheryl.sabella@gmail.com>
…ythonGH-104446) Commit extracted from PR pythonGH-3669. Will edit more later. (cherry picked from commit 46f1c78) Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu> Co-authored-by: Cheryl Sabella <cheryl.sabella@gmail.com>
This PR is stale because it has been open for 30 days with no activity. |
Add docstrings and test for editor.py functions related to the reload from configdialog, creating the menubar, and applying keybindings.
https://bugs.python.org/issue31529