| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent bdee588 commit 7d6fc2b
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,4 +1,5 @@ | |||
| 1 | 1 | import functools | |
| 2 | + import time | ||
| 2 | 3 | import tkinter | |
| 3 | 4 | import unittest | |
| 4 | 5 | from test import support | |
@@ -45,6 +46,20 @@ def tearDown(self): | |||
| 45 | 46 | w.destroy() | |
| 46 | 47 | self.root.withdraw() | |
| 47 | 48 | ||
| 49 | + def require_mapped(self, widget, timeout=None): | ||
| 50 | + """Realize *widget*, or skip the test if the window manager will | ||
| 51 | + not map it (e.g. a tiling WM or a headless/contended display). | ||
| 52 | + | ||
| 53 | + Use this instead of a bare update() before querying realized | ||
| 54 | + geometry (winfo_width(), identify(), coords(), place_info(), ...). | ||
| 55 | + See gh-69134, gh-74941 and bpo-40722. | ||
| 56 | + """ | ||
| 57 | + if timeout is None: | ||
| 58 | + timeout = support.LOOPBACK_TIMEOUT | ||
| 59 | + if not wait_until_mapped(widget, timeout): | ||
| 60 | + self.skipTest('widget was not mapped by the window manager ' | ||
| 61 | + f'(timed out after {timeout:g}s)') | ||
| 62 | + | ||
| 48 | 63 | ||
| 49 | 64 | class AbstractDefaultRootTest: | |
| 50 | 65 | ||
@@ -78,6 +93,32 @@ def destroy_default_root(): | |||
| 78 | 93 | tkinter._default_root.destroy() | |
| 79 | 94 | tkinter._default_root = None | |
| 80 | 95 | ||
| 96 | + def wait_until_mapped(widget, timeout=None): | ||
| 97 | + """Wait until *widget* is actually mapped and laid out by the window | ||
| 98 | + manager, so that realized-geometry queries (winfo_width(), identify(), | ||
| 99 | + coords(), ...) return meaningful values. | ||
| 100 | + | ||
| 101 | + Return True once the widget is mapped with a non-trivial size, or False | ||
| 102 | + if that has not happened within *timeout* seconds (default: | ||
| 103 | + ``support.LOOPBACK_TIMEOUT``). Unlike Misc.wait_visibility(), this | ||
| 104 | + never blocks indefinitely, so it is safe under a window manager that | ||
| 105 | + never maps the window (see gh-69134, gh-74941, bpo-40722). | ||
| 106 | + """ | ||
| 107 | + if timeout is None: | ||
| 108 | + timeout = support.LOOPBACK_TIMEOUT | ||
| 109 | + deadline = time.monotonic() + timeout | ||
| 110 | + widget.update_idletasks() | ||
| 111 | + while True: | ||
| 112 | + widget.update() # drain pending Map/Configure events | ||
| 113 | + if (widget.winfo_ismapped() | ||
| 114 | + and widget.winfo_width() > 1 | ||
| 115 | + and widget.winfo_height() > 1): | ||
| 116 | + return True | ||
| 117 | + if time.monotonic() >= deadline: | ||
| 118 | + return False | ||
| 119 | + time.sleep(0.01) | ||
| 120 | + | ||
| 121 | + | ||
| 81 | 122 | def simulate_mouse_click(widget, x, y): | |
| 82 | 123 | """Generate proper events to click at the x, y position (tries to act | |
| 83 | 124 | like an X server).""" | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,6 +7,7 @@ | |||
| 7 | 7 | from test.test_tkinter.support import setUpModule # noqa: F401 | |
| 8 | 8 | from test.test_tkinter.support import (requires_tk, tk_version, | |
| 9 | 9 | get_tk_patchlevel, widget_eq, | |
| 10 | + wait_until_mapped, | ||
| 10 | 11 | AbstractDefaultRootTest) | |
| 11 | 12 | ||
| 12 | 13 | from test.test_tkinter.widget_tests import ( | |
@@ -754,10 +755,11 @@ def test_invoke(self): | |||
| 754 | 755 | def test_identify(self): | |
| 755 | 756 | widget = self.create() | |
| 756 | 757 | widget.pack() | |
| 757 | - widget.update_idletasks() | ||
| 758 | - # The empty string is returned for a point over no element. | ||
| 759 | - self.assertIn(widget.identify(5, 5), | ||
| 760 | - ('entry', 'buttonup', 'buttondown', 'none', '')) | ||
| 758 | + # Identifying the element under a point requires the widget to be | ||
| 759 | + # mapped with a real size. | ||
| 760 | + if wait_until_mapped(widget): | ||
| 761 | + self.assertIn(widget.identify(5, 5), | ||
| 762 | + ('entry', 'buttonup', 'buttondown', 'none')) | ||
| 761 | 763 | self.assertRaises(TclError, widget.identify, 'a', 'b') | |
| 762 | 764 | ||
| 763 | 765 | def test_scan(self): | |
@@ -2096,9 +2098,11 @@ def test_delta(self): | |||
| 2096 | 2098 | def test_identify(self): | |
| 2097 | 2099 | sb = self.create() | |
| 2098 | 2100 | sb.pack(fill='y', expand=True) | |
| 2099 | - sb.update_idletasks() | ||
| 2100 | - self.assertIn(sb.identify(5, 5), | ||
| 2101 | - ('arrow1', 'arrow2', 'slider', 'trough1', 'trough2', '')) | ||
| 2101 | + # Identifying the element under a point requires the widget to be | ||
| 2102 | + # mapped with a real size. | ||
| 2103 | + if wait_until_mapped(sb): | ||
| 2104 | + self.assertIn(sb.identify(5, 5), | ||
| 2105 | + ('arrow1', 'arrow2', 'slider', 'trough1', 'trough2')) | ||
| 2102 | 2106 | self.assertRaises(TclError, sb.identify, 'a', 'b') | |
| 2103 | 2107 | ||
| 2104 | 2108 | ||
@@ -2218,10 +2222,12 @@ def test_identify(self): | |||
| 2218 | 2222 | p, b, c = self.create2() | |
| 2219 | 2223 | p.configure(width=200, height=50) | |
| 2220 | 2224 | p.pack() | |
| 2221 | - p.update() | ||
| 2222 | - x, y = p.sash_coord(0) | ||
| 2223 | - # A point over the sash reports the sash. | ||
| 2224 | - self.assertIn('sash', p.identify(x + 1, y + 5)) | ||
| 2225 | + # Locating the sash requires the widget to be mapped with a real | ||
| 2226 | + # size; the rest of the checks do not. | ||
| 2227 | + if wait_until_mapped(p): | ||
| 2228 | + x, y = p.sash_coord(0) | ||
| 2229 | + # A point over the sash reports the sash. | ||
| 2230 | + self.assertIn('sash', p.identify(x + 1, y + 5)) | ||
| 2225 | 2231 | # A point over a pane reports nothing. | |
| 2226 | 2232 | self.assertFalse(p.identify(2, 2)) | |
| 2227 | 2233 | self.assertRaises(TclError, p.identify, 'a', 'b') | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -109,7 +109,7 @@ def check_positions(scale, scale_pos, label, label_pos): | |||
| 109 | 109 | def test_horizontal_range(self): | |
| 110 | 110 | lscale = ttk.LabeledScale(self.root, from_=0, to=10) | |
| 111 | 111 | lscale.pack() | |
| 112 | - lscale.update() | ||
| 112 | + self.require_mapped(lscale) | ||
| 113 | 113 | ||
| 114 | 114 | linfo_1 = lscale.label.place_info() | |
| 115 | 115 | prev_xcoord = lscale.scale.coords()[0] | |
@@ -138,7 +138,7 @@ def test_horizontal_range(self): | |||
| 138 | 138 | def test_variable_change(self): | |
| 139 | 139 | x = ttk.LabeledScale(self.root) | |
| 140 | 140 | x.pack() | |
| 141 | - x.update() | ||
| 141 | + self.require_mapped(x) | ||
| 142 | 142 | ||
| 143 | 143 | curr_xcoord = x.scale.coords()[0] | |
| 144 | 144 | newval = x.value + 1 | |
@@ -181,7 +181,7 @@ def test_resize(self): | |||
| 181 | 181 | x = ttk.LabeledScale(self.root) | |
| 182 | 182 | x.pack(expand=True, fill='both') | |
| 183 | 183 | gc_collect() # For PyPy or other GCs. | |
| 184 | - x.update() | ||
| 184 | + self.require_mapped(x) | ||
| 185 | 185 | ||
| 186 | 186 | width, height = x.master.winfo_width(), x.master.winfo_height() | |
| 187 | 187 | width_new, height_new = width * 2, height * 2 | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,7 +8,7 @@ | |||
| 8 | 8 | from test.test_tkinter.support import setUpModule # noqa: F401 | |
| 9 | 9 | from test.test_tkinter.support import ( | |
| 10 | 10 | AbstractTkTest, requires_tk, tk_version, get_tk_patchlevel, | |
| 11 | - simulate_mouse_click, AbstractDefaultRootTest) | ||
| 11 | + simulate_mouse_click, wait_until_mapped, AbstractDefaultRootTest) | ||
| 12 | 12 | from test.test_tkinter.widget_tests import (add_configure_tests, | |
| 13 | 13 | AbstractWidgetTest, StandardOptionsTests, IntegerSizeTests, PixelSizeTests) | |
| 14 | 14 | ||
@@ -78,11 +78,13 @@ def setUp(self): | |||
| 78 | 78 | self.widget.pack() | |
| 79 | 79 | ||
| 80 | 80 | def test_identify(self): | |
| 81 | - self.widget.update() | ||
| 82 | - self.assertEqual(self.widget.identify( | ||
| 83 | - int(self.widget.winfo_width() / 2), | ||
| 84 | - int(self.widget.winfo_height() / 2) | ||
| 85 | - ), "label") | ||
| 81 | + # Identifying the element under a point requires the widget to be | ||
| 82 | + # mapped with a real size; the rest of the checks do not. | ||
| 83 | + if wait_until_mapped(self.widget): | ||
| 84 | + self.assertEqual(self.widget.identify( | ||
| 85 | + int(self.widget.winfo_width() / 2), | ||
| 86 | + int(self.widget.winfo_height() / 2) | ||
| 87 | + ), "label") | ||
| 86 | 88 | self.assertEqual(self.widget.identify(-1, -1), "") | |
| 87 | 89 | ||
| 88 | 90 | self.assertRaises(tkinter.TclError, self.widget.identify, None, 5) | |
@@ -385,9 +387,11 @@ def test_identify(self): | |||
| 385 | 387 | self.skipTest('Test does not work on macOS Tk 9.') | |
| 386 | 388 | # https://core.tcl-lang.org/tk/tktview/8b49e9cfa6 | |
| 387 | 389 | self.entry.pack() | |
| 388 | - self.root.update() | ||
| 389 | 390 | ||
| 390 | - self.assertIn(self.entry.identify(5, 5), self.IDENTIFY_AS) | ||
| 391 | + # Identifying the element under a point requires the widget to be | ||
| 392 | + # mapped with a real size; the rest of the checks do not. | ||
| 393 | + if wait_until_mapped(self.entry): | ||
| 394 | + self.assertIn(self.entry.identify(5, 5), self.IDENTIFY_AS) | ||
| 391 | 395 | self.assertEqual(self.entry.identify(-1, -1), "") | |
| 392 | 396 | ||
| 393 | 397 | self.assertRaises(tkinter.TclError, self.entry.identify, None, 5) | |
@@ -506,7 +510,7 @@ def test_virtual_event(self): | |||
| 506 | 510 | self.combo.bind('<<ComboboxSelected>>', | |
| 507 | 511 | lambda evt: success.append(True)) | |
| 508 | 512 | self.combo.pack() | |
| 509 | - self.combo.update() | ||
| 513 | + self.require_mapped(self.combo) | ||
| 510 | 514 | ||
| 511 | 515 | height = self.combo.winfo_height() | |
| 512 | 516 | self._show_drop_down_listbox() | |
@@ -525,7 +529,7 @@ def test_configure_postcommand(self): | |||
| 525 | 529 | ||
| 526 | 530 | self.combo['postcommand'] = lambda: success.append(True) | |
| 527 | 531 | self.combo.pack() | |
| 528 | - self.combo.update() | ||
| 532 | + self.require_mapped(self.combo) | ||
| 529 | 533 | ||
| 530 | 534 | self._show_drop_down_listbox() | |
| 531 | 535 | self.assertTrue(success) | |
@@ -875,8 +879,10 @@ def test_get(self): | |||
| 875 | 879 | else: | |
| 876 | 880 | conv = float | |
| 877 | 881 | ||
| 878 | - scale_width = self.scale.winfo_width() | ||
| 879 | - self.assertEqual(self.scale.get(scale_width, 0), self.scale['to']) | ||
| 882 | + # Reading the value at the far edge needs the realized width. | ||
| 883 | + if wait_until_mapped(self.scale): | ||
| 884 | + scale_width = self.scale.winfo_width() | ||
| 885 | + self.assertEqual(self.scale.get(scale_width, 0), self.scale['to']) | ||
| 880 | 886 | ||
| 881 | 887 | self.assertEqual(conv(self.scale.get(0, 0)), conv(self.scale['from'])) | |
| 882 | 888 | self.assertEqual(self.scale.get(), self.scale['value']) | |
@@ -918,7 +924,10 @@ def test_set(self): | |||
| 918 | 924 | # nevertheless, note that the max/min values we can get specifying | |
| 919 | 925 | # x, y coords are the ones according to the current range | |
| 920 | 926 | self.assertEqual(conv(self.scale.get(0, 0)), min) | |
| 921 | - self.assertEqual(conv(self.scale.get(self.scale.winfo_width(), 0)), max) | ||
| 927 | + # Reading the value at the far edge needs the realized width. | ||
| 928 | + if wait_until_mapped(self.scale): | ||
| 929 | + self.assertEqual( | ||
| 930 | + conv(self.scale.get(self.scale.winfo_width(), 0)), max) | ||
| 922 | 931 | ||
| 923 | 932 | self.assertRaises(tkinter.TclError, self.scale.set, None) | |
| 924 | 933 | ||
@@ -1269,6 +1278,7 @@ def create(self, **kwargs): | |||
| 1269 | 1278 | return ttk.Spinbox(self.root, **kwargs) | |
| 1270 | 1279 | ||
| 1271 | 1280 | def _click_increment_arrow(self): | |
| 1281 | + self.require_mapped(self.spin) | ||
| 1272 | 1282 | width = self.spin.winfo_width() | |
| 1273 | 1283 | height = self.spin.winfo_height() | |
| 1274 | 1284 | x = width - 5 | |
@@ -1279,6 +1289,7 @@ def _click_increment_arrow(self): | |||
| 1279 | 1289 | self.spin.update_idletasks() | |
| 1280 | 1290 | ||
| 1281 | 1291 | def _click_decrement_arrow(self): | |
| 1292 | + self.require_mapped(self.spin) | ||
| 1282 | 1293 | width = self.spin.winfo_width() | |
| 1283 | 1294 | height = self.spin.winfo_height() | |
| 1284 | 1295 | x = width - 5 | |
| Back | FazBrowse Home | New Git URL |
0 commit comments