-
-
Notifications
You must be signed in to change notification settings - Fork 34.5k
bpo-5680: IDLE: Customize running a module. #13763
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
Changes from 1 commit
ffc22b8
36f7771
69cf1b6
dbdd580
f1e7c61
286e4c6
03e0f59
22bb3a2
35f0379
09f37d7
8d98622
8c8bb4d
845178b
31416fd
fbfca23
4da67c3
307ac6a
4a64274
18177da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -242,11 +242,11 @@ def test_entry_ok_helpsource(self): | |||||
| self.assertEqual(dialog.entry_ok(), result) | ||||||
|
|
||||||
|
|
||||||
| class CommandLineArgsTest(unittest.TestCase): | ||||||
| "Test CommandLineArgs subclass of Query." | ||||||
| class CustomRunCLIargsokTest(unittest.TestCase): | ||||||
| "Test cli_ok method of the CustomRun subclass of Query." | ||||||
|
|
||||||
| class Dummy_CommandLineArgs: | ||||||
| entry_ok = query.CommandLineArgs.entry_ok # Function being tested. | ||||||
| class Dummy_CustomRun: | ||||||
| cli_args_ok = query.CustomRun.cli_args_ok # Function being tested. | ||||||
| entry = Var() | ||||||
| entry_error = {} | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be simpler to make
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I used this in all the other functional method tests. The dummy classes have and do exactly what is needed to test the method. A unittest.mock would be much more complicated and difficult and would run much slower. Test speed is important. |
||||||
| def __init__(self, dummy_entry): | ||||||
|
|
@@ -256,22 +256,43 @@ def showerror(self, message): | |||||
| self.entry_error['text'] = message | ||||||
|
|
||||||
| def test_blank_args(self): | ||||||
| dialog = self.Dummy_CommandLineArgs(' ') | ||||||
| self.assertEqual(dialog.entry_ok(), None) | ||||||
| dialog = self.Dummy_CustomRun(' ') | ||||||
| self.assertEqual(dialog.cli_args_ok(), None) | ||||||
| self.assertIn('no arguments', dialog.entry_error['text']) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should use
Suggested change
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This assert is gone. |
||||||
|
|
||||||
| def test_invalid_args(self): | ||||||
| dialog = self.Dummy_CommandLineArgs("'no-closing-quote") | ||||||
| self.assertEqual(dialog.entry_ok(), None) | ||||||
| dialog = self.Dummy_CustomRun("'no-closing-quote") | ||||||
| self.assertEqual(dialog.cli_args_ok(), None) | ||||||
| self.assertIn('No closing', dialog.entry_error['text']) | ||||||
|
terryjreedy marked this conversation as resolved.
|
||||||
|
|
||||||
| def test_good_args(self): | ||||||
| dialog = self.Dummy_CommandLineArgs('-n 10 --verbose -p /path --name "my name"') | ||||||
| self.assertEqual(dialog.entry_ok(), | ||||||
| dialog = self.Dummy_CustomRun('-n 10 --verbose -p /path --name "my name"') | ||||||
| self.assertEqual(dialog.cli_args_ok(), | ||||||
| ['-n', '10', '--verbose', '-p', '/path', '--name', 'my name']) | ||||||
| self.assertEqual(dialog.entry_error['text'], '') | ||||||
|
|
||||||
|
|
||||||
| class CustomRunEntryokTest(unittest.TestCase): | ||||||
| "Test entry_ok method of the CustomRun subclass of Query." | ||||||
|
|
||||||
| class Dummy_CustomRun: | ||||||
| entry_ok = query.CustomRun.entry_ok | ||||||
| entry_error = {} | ||||||
| def cli_args_ok(self): | ||||||
| return self.cli_args | ||||||
| def restart_ok(self): | ||||||
| return self.restart | ||||||
|
|
||||||
| def test_entry_ok_customrun(self): | ||||||
| dialog = self.Dummy_CustomRun() | ||||||
| for dialog.restart in {True, False}: | ||||||
| for cli_args, result in ((None, None), | ||||||
| ('my arg', ('my arg', dialog.restart))): | ||||||
| with self.subTest(): | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||||||
| dialog.cli_args = cli_args | ||||||
| self.assertEqual(dialog.entry_ok(), result) | ||||||
|
|
||||||
|
|
||||||
| # GUI TESTS | ||||||
|
|
||||||
| class QueryGuiTest(unittest.TestCase): | ||||||
|
|
@@ -378,7 +399,7 @@ def test_click_help_source(self): | |||||
| del root | ||||||
|
|
||||||
|
|
||||||
| class CommandLineArgsGuiTest(unittest.TestCase): | ||||||
| class CustomRunGuiTest(unittest.TestCase): | ||||||
|
|
||||||
| @classmethod | ||||||
| def setUpClass(cls): | ||||||
|
|
@@ -387,11 +408,10 @@ def setUpClass(cls): | |||||
| def test_click_args(self): | ||||||
| root = Tk() | ||||||
| root.withdraw() | ||||||
| dialog = query.CommandLineArgs(root, 'Title', 'message', _utest=True) | ||||||
| Equal = self.assertEqual | ||||||
| dialog = query.CustomRun(root, 'Title', _utest=True) | ||||||
| dialog.entry.insert(0, 'okay') | ||||||
| dialog.button_ok.invoke() | ||||||
| self.assertEqual(dialog.result, ['okay']) | ||||||
| self.assertEqual(dialog.result, (['okay'], True)) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The should probably be a call to
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe invoke blocks until done. I have run this test at least 20 times without issue. event_generate is different (and flakey). There are 55 invokes in idle tests and my spot check of nearly 10 showed none followed by update. And I know of no resulting failures. |
||||||
| del dialog | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are this
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No. Gone. Added class attributes need deletion, but function locals already go on return, and there is no evident issue here with destroying first. |
||||||
| root.destroy() | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest cleaning up using root = Tk()
def cleanup():
root.update()
root.destroy()
self.addCleanup(cleanup)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I far prefer explicit calls. Update is not generally needed. update_idletasks is more often needed. I have not seen any of the tclerror messages that so indicate. |
||||||
| del root | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,8 +24,8 @@ | |
| import shlex | ||
| from sys import executable, platform # Platform is set for one test. | ||
|
|
||
| from tkinter import Toplevel, StringVar, W, E, S | ||
| from tkinter.ttk import Frame, Button, Entry, Label | ||
| from tkinter import Toplevel, StringVar, BooleanVar, W, E, S | ||
| from tkinter.ttk import Frame, Button, Entry, Label, Checkbutton | ||
| from tkinter import filedialog | ||
| from tkinter.font import Font | ||
|
|
||
|
|
@@ -84,7 +84,7 @@ def __init__(self, parent, title, message, *, text0='', used_names={}, | |
| self.deiconify() # Unhide now that geometry set. | ||
| self.wait_window() | ||
|
|
||
| def create_widgets(self): # Call from override, if any. | ||
| def create_widgets(self, ok_text='OK'): # Call from override, if any. | ||
|
csabella marked this conversation as resolved.
|
||
| # Bind to self widgets needed for entry_ok or unittest. | ||
| self.frame = frame = Frame(self, padding=10) | ||
| frame.grid(column=0, row=0, sticky='news') | ||
|
|
@@ -100,7 +100,7 @@ def create_widgets(self): # Call from override, if any. | |
| self.entry_error = Label(frame, text=' ', foreground='red', | ||
| font=self.error_font) | ||
| self.button_ok = Button( | ||
| frame, text='OK', default='active', command=self.ok) | ||
| frame, text=ok_text, default='active', command=self.ok) | ||
| self.button_cancel = Button( | ||
| frame, text='Cancel', command=self.cancel) | ||
|
|
||
|
|
@@ -303,16 +303,36 @@ def entry_ok(self): | |
| path = self.path_ok() | ||
| return None if name is None or path is None else (name, path) | ||
|
|
||
| class CommandLineArgs(Query): | ||
| "Get a list of parsed command line arguments." | ||
| # Used in runscript.run_module_arguments_event | ||
| class CustomRun(Query): | ||
| "Get arguments for custom run module." | ||
| # Used in runscript.run_custom_event | ||
|
|
||
| def __init__(self, parent, title, message, *, _htest=False, _utest=False): | ||
| super().__init__(parent, title, message, _htest=_htest, _utest=_utest) | ||
| def __init__(self, parent, title, *, cli_args='', | ||
| _htest=False, _utest=False): | ||
| """Get command line args for custom run environment. | ||
|
|
||
| def entry_ok(self): | ||
| "Return list of parsed arguments or None." | ||
| self.entry_error['text'] = '' | ||
| User enters the command line arugments for running the file. | ||
| """ | ||
| message = 'Command Line Arguments:' | ||
| super().__init__( | ||
| parent, title, message, text0=cli_args, | ||
| _htest=_htest, _utest=_utest) | ||
|
|
||
| def create_widgets(self): | ||
| super().create_widgets(ok_text='Run') | ||
| frame = self.frame | ||
| self.restartvar = BooleanVar(self, value=True) | ||
| restart = Checkbutton(frame, variable=self.restartvar, onvalue=True, | ||
| offvalue=False, text='Restart shell') | ||
| self.args_error = Label(frame, text=' ', foreground='red', | ||
| font=self.error_font) | ||
|
|
||
| restart.grid(column=0, row=4, columnspan=3, padx=5, sticky='w') | ||
| self.args_error.grid(column=0, row=12, columnspan=3, padx=5, | ||
| sticky='we') | ||
|
terryjreedy marked this conversation as resolved.
|
||
|
|
||
| def cli_args_ok(self): | ||
| "Validity check and parsing for command line arguments." | ||
| cli_args = self.entry.get().strip() | ||
| if not cli_args: | ||
| self.showerror('no arguments specified.') | ||
|
|
@@ -324,10 +344,20 @@ def entry_ok(self): | |
| return None | ||
| return lex | ||
|
|
||
| def restart_ok(self): | ||
| return self.restartvar.get() | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could add edits here for when
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No longer possible. |
||
|
|
||
| def entry_ok(self): | ||
| "Return apparently valid (lex, restart) or None" | ||
| self.entry_error['text'] = '' | ||
| lex = self.cli_args_ok() | ||
| restart = self.restart_ok() | ||
| return None if not lex else (lex, restart) | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| from unittest import main | ||
| main('idlelib.idle_test.test_query', verbosity=2, exit=False) | ||
|
|
||
| from idlelib.idle_test.htest import run | ||
| run(Query, HelpSource, CommandLineArgs) | ||
| run(Query, HelpSource, CustomRun) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,7 +18,7 @@ | |
| from idlelib.config import idleConf | ||
| from idlelib import macosx | ||
| from idlelib import pyshell | ||
| from idlelib.query import CommandLineArgs | ||
| from idlelib.query import CustomRun | ||
|
|
||
| indent_message = """Error: Inconsistent indentation detected! | ||
|
|
||
|
|
@@ -114,16 +114,14 @@ def run_module_event(self, event): | |
| else: | ||
| return self._run_module_event(event) | ||
|
|
||
| def run_module_arguments_event(self, event): | ||
| cli_args = CommandLineArgs( | ||
| event.widget, "Run with Arguments", | ||
| "Command line argunments:").result | ||
| def run_custom_event(self, event): | ||
| run_args = CustomRun(event.widget, "Customize Run").result | ||
| # User cancelled. | ||
| if not cli_args: | ||
| if not run_args: | ||
| return 'break' | ||
| return self._run_module_event(event, cli_args) | ||
| return self._run_module_event(event, cli_args=run_args[0], restart=run_args[1]) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed to pass run_args and unpack in _run_module. |
||
|
|
||
| def _run_module_event(self, event, cli_args=None): | ||
| def _run_module_event(self, event, *, cli_args=None, restart=True): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The "Run Module" and "Run Custom" should each have their own (simple) event handler, with the common code extracted into a common function used by both, which isn't an event handler.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. run_module_event, with initial '', is already the common code for run_module_event and run_custom_event. It is only an 'event handler' to implement the Mac-specific delayed call via an internal pseudo pseudoevent. I am assuming that someone tried calling it directly after the delay. As a separate issue, we might test whether the workaround is needed in 8.6. |
||
| """Run the module after setting up the environment. | ||
|
|
||
| First check the syntax. If OK, make sure the shell is active and | ||
|
|
@@ -140,21 +138,23 @@ def _run_module_event(self, event, cli_args=None): | |
| if not self.tabnanny(filename): | ||
| return 'break' | ||
| interp = self.shell.interp | ||
| if pyshell.use_subprocess: | ||
| if pyshell.use_subprocess and restart: | ||
| interp.restart_subprocess(with_cwd=False, filename= | ||
| self.editwin._filename_to_unicode(filename)) | ||
| dirname = os.path.dirname(filename) | ||
| # XXX Too often this discards arguments the user just set... | ||
| argv = [filename] | ||
| if cli_args: | ||
| argv += cli_args | ||
| # XXX Too often this discards arguments the user just set... | ||
| interp.runcommand(f"""if 1: | ||
| __file__ = {filename!r} | ||
| import sys as _sys | ||
| from os.path import basename as _basename | ||
| argv = {argv!r} | ||
| if (not _sys.argv or | ||
| _basename(_sys.argv[0]) != _basename(__file__)): | ||
| _sys.argv = {argv!r} | ||
| _basename(_sys.argv[0]) != _basename(__file__) or | ||
| len(argv) > 1): | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Checking the length so that, if there are additional args, they will replace what was already there. However, existing args won't be replaced by no args. Currently, F5 still runs with no args and restarts the shell, but that's something to keep in mind if F5 changes.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe that this can only matter if one used the deprecated -n option. I have no intention of changing F5 |
||
| _sys.argv = argv | ||
| import os as _os | ||
| _os.chdir({dirname!r}) | ||
| del _sys, _basename, _os | ||
|
|
||
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.
Why is this a class attribute? Besides being unclear, this also doesn't reflect the actual
CustomRunclass.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 wrote the query tests, and Cheryl followed the pattern I set. For testing with one instance at a time, or ever, class and instance attributes work the same. For attributes never rebound, I attached them to the class, especially if there was no other need for an init method. Very clear to me. However, I moved Var attributes to init when there is one as it saves a line.
The cli_args_ok method being tested accesses self.query. tk Vars and Entries both have a get method that does the same thing, so a mock Var replaces an Entry quite nicely.