From 77adbcb9c21339a8b95f73a6d667f2b41a943a38 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 9 Jul 2026 12:18:56 +0300 Subject: [PATCH] gh-69134: Harden NotebookTest.test_traversal identify(5, 5) could run before the notebook reached its requested size, so the pixel fell outside the first tab and returned ''. Guard it with a new opt-in wait_until_mapped(full_size=True). Co-Authored-By: Claude Opus 4.8 --- Lib/test/test_tkinter/support.py | 19 ++++++++++++++----- Lib/test/test_ttk/test_widgets.py | 8 ++++++-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_tkinter/support.py b/Lib/test/test_tkinter/support.py index df0cca95a33a18..31feee2b8a40ee 100644 --- a/Lib/test/test_tkinter/support.py +++ b/Lib/test/test_tkinter/support.py @@ -93,7 +93,7 @@ def destroy_default_root(): tkinter._default_root.destroy() tkinter._default_root = None -def wait_until_mapped(widget, timeout=None): +def wait_until_mapped(widget, timeout=None, *, full_size=False): """Wait until *widget* is actually mapped and laid out by the window manager, so that realized-geometry queries (winfo_width(), identify(), coords(), ...) return meaningful values. @@ -103,6 +103,10 @@ def wait_until_mapped(widget, timeout=None): ``support.LOOPBACK_TIMEOUT``). Unlike Misc.wait_visibility(), this never blocks indefinitely, so it is safe under a window manager that never maps the window (see gh-69134, gh-74941, bpo-40722). + + If *full_size* is true, also wait until the realized size reaches the + requested size, so that per-pixel queries near an edge (e.g. identify()) + are reliable even under load. """ if timeout is None: timeout = support.LOOPBACK_TIMEOUT @@ -110,10 +114,15 @@ def wait_until_mapped(widget, timeout=None): widget.update_idletasks() while True: widget.update() # drain pending Map/Configure events - if (widget.winfo_ismapped() - and widget.winfo_width() > 1 - and widget.winfo_height() > 1): - return True + if widget.winfo_ismapped(): + if full_size: + w_ok = widget.winfo_width() >= widget.winfo_reqwidth() > 1 + h_ok = widget.winfo_height() >= widget.winfo_reqheight() > 1 + else: + w_ok = widget.winfo_width() > 1 + h_ok = widget.winfo_height() > 1 + if w_ok and h_ok: + return True if time.monotonic() >= deadline: return False time.sleep(0.01) diff --git a/Lib/test/test_ttk/test_widgets.py b/Lib/test/test_ttk/test_widgets.py index d0849bf8eb676f..b86d56f256e9e8 100644 --- a/Lib/test/test_ttk/test_widgets.py +++ b/Lib/test/test_ttk/test_widgets.py @@ -1224,7 +1224,10 @@ def test_traversal(self): focus_identify_as = 'focus' else: focus_identify_as = 'focus' if tk_version < (8, 7) else 'padding' - self.assertEqual(self.nb.identify(5, 5), focus_identify_as) + # identify() at (5, 5) needs the tab realized there; under focus + # contention the mapped size can lag, so wait for the full size. + if wait_until_mapped(self.nb, full_size=True): + self.assertEqual(self.nb.identify(5, 5), focus_identify_as) simulate_mouse_click(self.nb, 5, 5) self.nb.focus_force() self.nb.event_generate('') @@ -1240,7 +1243,8 @@ def test_traversal(self): self.nb.tab(self.child2, text='e', underline=0) self.nb.enable_traversal() self.nb.focus_force() - self.assertEqual(self.nb.identify(5, 5), focus_identify_as) + if wait_until_mapped(self.nb, full_size=True): + self.assertEqual(self.nb.identify(5, 5), focus_identify_as) simulate_mouse_click(self.nb, 5, 5) # on macOS Emacs-style keyboard shortcuts are region-dependent; # let's use the regular arrow keys instead