Closed vstinner closed 2 days ago
Reproducer with less tests:
./python -m test -u all,-gui test_idle test_pyrepl
test_idle
sets PyOS_InputHook in:
(gdb) where
#0 0x00007fffe96b927a in EnableEventHook () at ./Modules/_tkinter.c:3380
#1 0x00007fffe96be707 in Tkapp_New (screenName=screenName@entry=0x0, className=className@entry=0x7fffe9cbf588 "Tk",
interactive=interactive@entry=0, wantobjects=wantobjects@entry=1, wantTk=wantTk@entry=0, sync=sync@entry=0, use=0x0)
at ./Modules/_tkinter.c:736
#2 0x00007fffe96be8f5 in _tkinter_create_impl (module=module@entry=<module at remote 0x7fffe96f5a90>, screenName=screenName@entry=0x0,
baseName=baseName@entry=0x7fffe8389328 "__main__", className=className@entry=0x7fffe9cbf588 "Tk", interactive=interactive@entry=0,
wantobjects=wantobjects@entry=1, wantTk=0, sync=0, use=0x0) at ./Modules/_tkinter.c:3176
#3 0x00007fffe96bed69 in _tkinter_create (module=<module at remote 0x7fffe96f5a90>, args=args@entry=0x7ffff7fba430, nargs=nargs@entry=8)
at ./Modules/clinic/_tkinter.c.h:820
(gdb) py-bt
Traceback (most recent call first):
<built-in method create of module object at remote 0x7fffe96f5a90>
File "/home/vstinner/python/main/Lib/tkinter/__init__.py", line 2459, in __init__
self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use)
File "/home/vstinner/python/main/Lib/tkinter/__init__.py", line 2572, in Tcl
return Tk(screenName, baseName, className, useTk)
File "/home/vstinner/python/main/Lib/idlelib/run.py", line 94, in <module>
tcl = tkinter.Tcl()
<built-in method exec of module object at remote 0x7ffff7c94770>
(...)
cc @pablogsal @ambv @Yhg1s
I cannot reproduce on Windows. After some time, the test call above hung until I hit return, then it was a success.
The apparently offending chain of events is that test_idle runs (idlelib.idle_test.)test_run which imports idlelib.run which calls tkinter.Tcl which at top level calls into _tkinter which sets tcl to get tk events. Idlelib.run is the only place IDLE calls tkinter.Tcl, but I suspect that calls to _tkinter from tkinter.tk do the same. However, IDLE test that call tk() do so explicitly and later call root.destroy(). Since Tcl.destroy exists, a fix might be to add the following top level cleanup function near the top of test_run.
def tearDownModule():
run.tcl.destroy()
I'm off to bed.
If I just add tearDownModule() to Lib/idlelib/idle_test/test_run.py, I get:
ERROR: tearDownModule (idlelib.idle_test.test_run)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/vstinner/python/main/Lib/idlelib/idle_test/test_run.py", line 433, in tearDownModule
run.tcl.destroy()
~~~~~~~~~~~~~~~^^
File "/home/vstinner/python/main/Lib/tkinter/__init__.py", line 2503, in destroy
self.tk.call('destroy', self._w)
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
_tkinter.TclError: invalid command name "destroy"
Tcl() returns a Tk object created with useTk False instead of True. So the tkinter destroy method is the one called normally.
def Tcl(screenName=None, baseName=None, className='Tk', useTk=False):
return Tk(screenName, baseName, className, useTk)
But this and perhaps other methods are not valid for such object. This is a tkinter bug.
Alternate try: in idlelib.run, replace lines 94-100
tcl = tkinter.Tcl()
def handle_tk_events(tcl=tcl):
"""Process any tk events that are ready to be dispatched if tkinter
has been imported, a tcl interpreter has been created and tk has been
loaded."""
tcl.eval("update")
with
if idlelib.testing:
def handle_tk_events(): pass
else:
tcl = tkinter.Tcl()
def handle_tk_events(tcl=tcl):
"""Process any tk events that are ready to be dispatched if tkinter
has been imported, a tcl interpreter has been created and tk has been
loaded."""
tcl.eval("update")
This might not be a permanent solution but should work. I cannot test for several hours.
I wrote a fix based on @terryjreedy's recipe and it works for me!
Thank you @terryjreedy for the fix!
If I ever want to unittest run's handling of interactive tkinter calls, I will ask Serhiy to add a DisableEventHook to _tkinter, if not there already, and make it accessible from Python code, perhaps by making Tcl a subclass that defines a destroy method that works.
Command:
gdb:
Linked PRs