Skip to content
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

Open
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

csabella
Copy link
Contributor

@csabella csabella commented Sep 20, 2017

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

@csabella
Copy link
Contributor Author

csabella commented Sep 20, 2017

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:

  1. I haven't done ResetColorizer because that seemed like its own rabbithole.
  2. I had to add a windows-only test because os.startfile is a windows only function and wouldn't work on my system. Hope it works.
  3. I was getting the timer_event CodeContext errors when running the tests, so I added a line to _close() in editor.py to unbind that event before deleting the text instance and it made the error go away. If it's the right fix, you may want to pull that out into its own PR.

Sorry that this is still massive. I really tried to limit it to what I needed to look at.

@@ -1,14 +1,432 @@
""" Test idlelib.editor.
"""
Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

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.

Copy link
Contributor

@taleinat taleinat left a 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!


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
Copy link
Contributor

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:".

Copy link
Member

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.

@@ -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
Copy link
Contributor

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.

Copy link
Member

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.

# 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?
Copy link
Contributor

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.

Copy link
Member

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.

"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
Copy link
Contributor

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

"Create a callback with the helpfile value frozen at definition time"
"""Create a callback with the helpfile value frozen at definition time.

Args:
Copy link
Contributor

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.

Copy link
Member

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...'

@@ -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.
Copy link
Contributor

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.

Copy link
Member

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.

Args:
menudefs: Menu and submenu names, underlines (shortcuts),
and events which is a list of tuples of the form:
[(menu1, [(submenu1a, '<<virtual event>>'),
Copy link
Contributor

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?

Copy link
Member

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')
Copy link
Contributor

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.

Copy link
Member

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()
Copy link
Contributor

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.

Copy link
Member

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)
Copy link
Contributor

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.

Copy link
Member

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.

@taleinat
Copy link
Contributor

Ping, @csabella?

Copy link
Member

@terryjreedy terryjreedy left a 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.

@@ -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
Copy link
Member

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.


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
Copy link
Member

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.

# 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?
Copy link
Member

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.

"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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

"Create a callback with the helpfile value frozen at definition time"
"""Create a callback with the helpfile value frozen at definition time.

Args:
Copy link
Member

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...'

s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
# Remove Key- from string.
Copy link
Member

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.

@@ -1,14 +1,432 @@
""" Test idlelib.editor.
"""
Copy link
Member

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')
Copy link
Member

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)
Copy link
Member

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()
Copy link
Member

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.

@bedevere-bot
Copy link

When you're done making the requested changes, leave the comment: I have made the requested changes; please review again.

@terryjreedy
Copy link
Member

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.

@terryjreedy
Copy link
Member

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.

@terryjreedy
Copy link
Member

terryjreedy commented Sep 21, 2020

I stopped the Windows test failures by commenting out the failing lines with an explanation.

The Ubuntu failure is

  File "/home/runner/work/cpython/cpython-ro-srcdir/Lib/idlelib/idle_test/test_editor.py", line 422, in test__extra_help_callback_not_windows
    ehc = partial(w._EditorWindow__extra_help_callback, w)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
AttributeError: Mock object has no attribute '_EditorWindow__extra_help_callback'. Did you mean: '_EditorWindow__recent_file_callback'?

May do the same here.

@terryjreedy
Copy link
Member

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
self.assertGreaterEqual(len(mainmenu.default_keydefs), 50)
AssertionError: 5 not greater than or equal to 50

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.

@terryjreedy
Copy link
Member

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.

@github-actions github-actions bot added the stale Stale PR or inactive for long period of time. label Feb 23, 2022
@github-actions github-actions bot removed the stale Stale PR or inactive for long period of time. label Jul 29, 2022
@github-actions github-actions bot added the stale Stale PR or inactive for long period of time. label Sep 1, 2022
@hauntsaninja hauntsaninja changed the title bpo-31529: IDLE: Add docstrings and tests to some editor.py functions gh-75710: IDLE: Add docstrings and tests to some editor.py functions Jan 7, 2023
@github-actions github-actions bot removed the stale Stale PR or inactive for long period of time. label Jan 8, 2023
@github-actions github-actions bot added the stale Stale PR or inactive for long period of time. label Feb 7, 2023
@python python deleted a comment from github-actions bot May 13, 2023
@python python deleted a comment from github-actions bot May 13, 2023
@python python deleted a comment from github-actions bot May 13, 2023
terryjreedy added a commit to terryjreedy/cpython that referenced this pull request May 13, 2023
Commit extracted from PR python#3669.  Will edits more later.

Co-authored-by: Cheryl Sabella <cheryl.sabella@gmail.com>
terryjreedy added a commit that referenced this pull request May 13, 2023
Commit extracted from PR #3669.  Will edit more later.

Co-authored-by: Cheryl Sabella <cheryl.sabella@gmail.com>
miss-islington pushed a commit to miss-islington/cpython that referenced this pull request May 13, 2023
…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>
terryjreedy added a commit that referenced this pull request May 13, 2023
…H-104446) (#104450)

gh-75710: IDLE - add docstrings and comments to editor module (GH-104446)

Commit extracted from PR GH-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>
@github-actions github-actions bot removed the stale Stale PR or inactive for long period of time. label May 14, 2023
@github-actions
Copy link

This PR is stale because it has been open for 30 days with no activity.

@github-actions github-actions bot added the stale Stale PR or inactive for long period of time. label Jun 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
awaiting changes stale Stale PR or inactive for long period of time.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants