gh-154357: Fix tkinter, ttk and IDLE tests depending on the window manager - #154370
Conversation
Tk delivers generated keyboard events to the window which has the focus. While the root window is visible, the window manager can take the focus back from the dialog, and the events are dropped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
This fixes the reported issue, but there are still some flaky tests under ======================================================================
FAIL: test_wm_stackorder (test.test_tkinter.test_misc.WmTest.test_wm_stackorder)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/a/cpython/Lib/test/test_tkinter/test_misc.py", line 1335, in test_wm_stackorder
self.assertGreater(names.index(str(t1)), names.index(str(t2)))
~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 1 not greater than 2
============================================================================================================================================
FAIL: test_focus (test.test_tkinter.test_misc.EventTest.test_focus)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/a/cpython/Lib/test/test_tkinter/test_misc.py", line 1364, in test_focus
self.assertEqual(len(events), 1, events)
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 2 != 1 : [<FocusIn event>, <FocusIn event>]
====================================================================== |
The window manager can reorder the toplevel windows while they are still being mapped, and can take the focus back while events are processed. Wait until the windows are mapped before changing the stacking order, and force the focus right before generating a keyboard event. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
I cannot reproduce the duplicate |
|
I tested on WSL2, so my window manager environment may not be standard. It's Weston. In I applied your patch for fixing ======================================================================
FAIL: test_wm_stackorder (test.test_tkinter.test_misc.WmTest.test_wm_stackorder)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/a/cpython/Lib/test/test_tkinter/test_misc.py", line 1336, in test_wm_stackorder
self.assertGreater(names.index(str(t1)), names.index(str(t2)))
~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 0 not greater than 2
----------------------------------------------------------------------If I remove that assertion, the following assertions can still fail: self.assertIs(t1.wm_stackorder('isabove', t2), True)
self.assertIs(t1.wm_stackorder('isbelow', t2), False)
self.assertIs(t2.wm_stackorder('isbelow', t1), True)And ======================================================================
FAIL: test_focus_methods (test.test_tkinter.test_misc.MiscTest.test_focus_methods)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/a/cpython/Lib/test/test_tkinter/test_misc.py", line 519, in test_focus_methods
self.assertIs(self.root.focus_get(), b)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: None is not <tkinter.Button object .+frame.+button>
---------------------------------------------------------------------- |
Some window managers ignore lift() or reorder the toplevel windows while they are being mapped, and can take the focus away from the application. In test_wm_stackorder, make the windows override-redirect on X11, so that the requested stacking order is honored regardless of the window manager. In test_focus_methods, focus_get() and focus_displayof() return None when the application does not hold the focus; check them only when it does, and test the window-manager-independent focus_lastfor() too. Force the focus right before generating the event in test_type_ahead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Thank you, this was a key point. I was able to reproduce most (not all) issues on Weston. Can you please test the PR and check if anything remains? |
|
Thanks! :)
Details----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/a/cpython/Lib/test/test_tkinter/test_tkinter_pipe.py", line 24, in test_tkinter_pipe_buffered
self.assertEqual(stdout.split(), ['hello', 'goodbye'])
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: Lists differ: ['Ctrl', 'click', 'to', 'launch', 'VS', 'Co[37 chars]bye'] != ['hello', 'goodbye']
First differing element 0:
'Ctrl'
'hello'
First list contains 8 additional elements.
First extra element 2:
'to'
+ ['hello', 'goodbye']
- ['Ctrl',
- 'click',
- 'to',
- 'launch',
- 'VS',
- 'Code',
- 'Native',
- 'REPL',
- 'hello',
- 'goodbye']
======================================================================
FAIL: test_tkinter_pipe_unbuffered (test.test_tkinter.test_tkinter_pipe.TkinterPipeTest.test_tkinter_pipe_unbuffered)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/a/cpython/Lib/test/test_tkinter/test_tkinter_pipe.py", line 39, in test_tkinter_pipe_unbuffered
self.assertEqual(stdout.strip(), 'hello')
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 'Ctrl click to launch VS Code Native REPL' != 'hello'
- Ctrl click to launch VS Code Native REPL
+ hello |
|
Could you please provide more details about test_focus? I cannot reproduce this. test_tkinter_pipe_unbuffered is a different issue. |
|
For test_focus, the traceback is the same as above: ======================================================================
FAIL: test_focus (test.test_tkinter.test_misc.EventTest.test_focus)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/a/cpython/Lib/test/test_tkinter/test_misc.py", line 1376, in test_focus
self.assertEqual(len(events), 1, events)
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 2 != 1 : [<FocusIn event>, <FocusIn event>]
---------------------------------------------------------------------- |
|
Could you please comment out this assertion and check if the test passes? If it fails, try to change the next line |
All focus events seen by bindings are generated by Tk itself: one in focus_force(), and one more when the real X focus event arrives and the window manager had taken the focus away in between. Only the first event, generated by focus_force(), is checked.
|
Yeah, test_focus passes after I commenting out that assertion. |
|
Thank you for your help. |
After a test which left the root window without content, the window manager can resize it to its minimum size. Tk then remembers that size as if it were set by "wm geometry" and no longer resizes the toplevel to fit the content of the following test.
update() between focus_force() and event_generate() gives the window manager a chance to take the focus away. Tk then drops the generated key event, because it delivers it to the window which has the focus.
|
Thanks @serhiy-storchaka for the PR 🌮🎉.. I'm working now to backport this PR to: 3.13, 3.14, 3.15. |
|
Sorry, @serhiy-storchaka, I could not cleanly backport this to |
|
Sorry, @serhiy-storchaka, I could not cleanly backport this to |
|
Sorry, @serhiy-storchaka, I could not cleanly backport this to |
|
GH-157063 is a backport of this pull request to the 3.15 branch. |
|
GH-157064 is a backport of this pull request to the 3.14 branch. |
|
GH-157065 is a backport of this pull request to the 3.13 branch. |
|
…ndow manager (GH-154370) (GH-157065) The window manager can take the focus from the application, ignore lift() and resize a toplevel on its own. * Hide the root window in the dialog tests, so that it does not compete for the focus. * Take the focus right before generating a key event. * Tolerate additional focus events. * Do not check focus_get() and focus_displayof() without the focus. * Resize the toplevel to fit its content in wait_until_mapped(). (cherry picked from commit 3b56438)
…ndow manager (GH-154370) (GH-157064) The window manager can take the focus from the application, ignore lift() and resize a toplevel on its own. * Hide the root window in the dialog tests, so that it does not compete for the focus. * Take the focus right before generating a key event. * Tolerate additional focus events. * Do not check focus_get() and focus_displayof() without the focus. * Resize the toplevel to fit its content in wait_until_mapped(). (cherry picked from commit 3b56438)
The window manager can take the focus from the application, ignore
lift()and resize a toplevel on its own. Tk delivers generated keyboard events to the window which has the focus, so a test which lost the focus does not get the event at all, its binding never fires, and it fails with an unset result.AbstractTkTest.setUp()deiconifies the root window, and waiting until the dialog has the focus is not enough, because it is lost again a moment later. The dialogs are toplevels of their own and do not need a visible root.update()in between.focus_force()sets the focus synchronously, but theupdate()is where Tk processes the realFocusOutand forgets which window has the focus.focus_get()andfocus_displayof()when the application does not have the focus.test_wm_stackorder, and wait until they are mapped before lifting them.wait_until_mapped(), if the window manager has resized it. Tk remembers a size set by the window manager as if it were set bywm geometryand no longer resizes the toplevel to fit its content, which madeNotebookTestfail after a test which left the root window empty.This fixes 9 failing tests in
test_simpledialog, flaky tests intest_filedialog,test_misc,test_ttkand IDLEtest_configdialogon X11 with KWin. Most failures do not reproduce without a window manager, which is why the buildbots do not show them.test_boundary_values_acceptedfails #154357