From a14c5fc8370d6080298a232e498346029fe10040 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 22 Jul 2026 04:16:50 +0300 Subject: [PATCH] gh-153741: Harden IDLE GUI tests for newer Tk and display scaling (GH-153742) In test_sidebar.test_mousewheel, only use the X11 / events when testing on x11 Tk before 8.7. Otherwise, use as on other systems. In test_configdialog, assert the 'disabled' state flag alone instead of the exact ttk state tuple (which transient pointer states break), and restore the tests' method masks with addCleanup(). (cherry picked from commit a2581ebc7a93ec573f64d3a5e29cbd9053de7a1f) Co-authored-by: Serhiy Storchaka Co-authored-by: Claude Opus 4.8 (1M context) --- Lib/idlelib/idle_test/test_configdialog.py | 63 +++++++++++----------- Lib/idlelib/idle_test/test_sidebar.py | 10 ++-- 2 files changed, 38 insertions(+), 35 deletions(-) diff --git a/Lib/idlelib/idle_test/test_configdialog.py b/Lib/idlelib/idle_test/test_configdialog.py index c2870442d5a6d3..8e435da0fa8898 100644 --- a/Lib/idlelib/idle_test/test_configdialog.py +++ b/Lib/idlelib/idle_test/test_configdialog.py @@ -245,6 +245,7 @@ def test_font_set(self): def test_set_samples(self): d = self.page + self.addCleanup(setattr, d, 'set_samples', Func()) # Re-mask for other tests. del d.set_samples # Unmask method for test orig_samples = d.font_sample, d.highlight_sample d.font_sample, d.highlight_sample = {}, {} @@ -258,7 +259,6 @@ def test_set_samples(self): self.assertTrue(d.font_sample == d.highlight_sample == expected) d.font_sample, d.highlight_sample = orig_samples - d.set_samples = Func() # Re-mask for other tests. class HighPageTest(unittest.TestCase): @@ -308,7 +308,7 @@ def test_load_theme_cfg(self): # builtinlist sets variable builtin_name to the CurrentTheme default. eq(d.builtin_name.get(), 'IDLE Classic') eq(d.custom_name.get(), '- no custom themes -') - eq(d.custom_theme_on.state(), ('disabled',)) + self.assertIn('disabled', d.custom_theme_on.state()) eq(d.set_theme_type.called, 1) eq(d.paint_theme_sample.called, 1) eq(d.set_highlight_target.called, 1) @@ -496,6 +496,7 @@ def test_highlight_sample_b1_motion(self): def test_set_theme_type(self): eq = self.assertEqual d = self.page + self.addCleanup(setattr, d, 'set_theme_type', Func()) # Re-mask method. del d.set_theme_type # Builtin theme selected. @@ -503,16 +504,15 @@ def test_set_theme_type(self): d.set_theme_type() eq(d.builtinlist['state'], NORMAL) eq(d.customlist['state'], DISABLED) - eq(d.button_delete_custom.state(), ('disabled',)) + self.assertIn('disabled', d.button_delete_custom.state()) # Custom theme selected. d.theme_source.set(False) d.set_theme_type() eq(d.builtinlist['state'], DISABLED) - eq(d.custom_theme_on.state(), ('selected',)) + self.assertNotIn('disabled', d.custom_theme_on.state()) eq(d.customlist['state'], NORMAL) - eq(d.button_delete_custom.state(), ()) - d.set_theme_type = Func() + self.assertNotIn('disabled', d.button_delete_custom.state()) def test_get_color(self): eq = self.assertEqual @@ -638,24 +638,23 @@ def test_create_new_and_save_new(self): def test_set_highlight_target(self): eq = self.assertEqual d = self.page + self.addCleanup(setattr, d, 'set_highlight_target', Func()) # Re-mask method. del d.set_highlight_target # Target is cursor. d.highlight_target.set('Cursor') - eq(d.fg_on.state(), ('disabled', 'selected')) - eq(d.bg_on.state(), ('disabled',)) + self.assertIn('disabled', d.fg_on.state()) + self.assertIn('disabled', d.bg_on.state()) self.assertTrue(d.fg_bg_toggle) eq(d.set_color_sample.called, 1) # Target is not cursor. d.highlight_target.set('Comment') - eq(d.fg_on.state(), ('selected',)) - eq(d.bg_on.state(), ()) + self.assertNotIn('disabled', d.fg_on.state()) + self.assertNotIn('disabled', d.bg_on.state()) self.assertTrue(d.fg_bg_toggle) eq(d.set_color_sample.called, 2) - d.set_highlight_target = Func() - def test_set_color_sample_binding(self): d = self.page scs = d.set_color_sample @@ -668,6 +667,7 @@ def test_set_color_sample_binding(self): def test_set_color_sample(self): d = self.page + self.addCleanup(setattr, d, 'set_color_sample', Func()) # Re-mask method. del d.set_color_sample d.highlight_target.set('Selected Text') d.fg_bg_toggle.set(True) @@ -675,7 +675,6 @@ def test_set_color_sample(self): self.assertEqual( d.style.lookup(d.frame_color_set['style'], 'background'), d.highlight_sample.tag_cget('hilite', 'foreground')) - d.set_color_sample = Func() def test_paint_theme_sample(self): eq = self.assertEqual @@ -746,7 +745,7 @@ def test_delete_custom(self): eq(yesno.called, 2) self.assertNotIn(theme_name, highpage) eq(idleConf.GetSectionList('user', 'highlight'), [theme_name2]) - eq(d.custom_theme_on.state(), ()) + self.assertNotIn('disabled', d.custom_theme_on.state()) eq(d.custom_name.get(), theme_name2) eq(dialog.deactivate_current_config.called, 1) eq(dialog.activate_config_changes.called, 1) @@ -759,7 +758,7 @@ def test_delete_custom(self): eq(yesno.called, 3) self.assertNotIn(theme_name, highpage) eq(idleConf.GetSectionList('user', 'highlight'), []) - eq(d.custom_theme_on.state(), ('disabled',)) + self.assertIn('disabled', d.custom_theme_on.state()) eq(d.custom_name.get(), '- no custom themes -') eq(dialog.deactivate_current_config.called, 2) eq(dialog.activate_config_changes.called, 2) @@ -810,7 +809,7 @@ def test_load_key_cfg(self): # builtinlist sets variable builtin_name to the CurrentKeys default. eq(d.builtin_name.get(), 'IDLE Classic OSX') eq(d.custom_name.get(), '- no custom keys -') - eq(d.custom_keyset_on.state(), ('disabled',)) + self.assertIn('disabled', d.custom_keyset_on.state()) eq(d.set_keys_type.called, 1) eq(d.load_keys_list.called, 1) eq(d.load_keys_list.args, ('IDLE Classic OSX', )) @@ -931,6 +930,7 @@ def test_keybinding(self): def test_set_keys_type(self): eq = self.assertEqual d = self.page + self.addCleanup(setattr, d, 'set_keys_type', Func()) # Re-mask method. del d.set_keys_type # Builtin keyset selected. @@ -938,16 +938,15 @@ def test_set_keys_type(self): d.set_keys_type() eq(d.builtinlist['state'], NORMAL) eq(d.customlist['state'], DISABLED) - eq(d.button_delete_custom_keys.state(), ('disabled',)) + self.assertIn('disabled', d.button_delete_custom_keys.state()) # Custom keyset selected. d.keyset_source.set(False) d.set_keys_type() eq(d.builtinlist['state'], DISABLED) - eq(d.custom_keyset_on.state(), ('selected',)) + self.assertNotIn('disabled', d.custom_keyset_on.state()) eq(d.customlist['state'], NORMAL) - eq(d.button_delete_custom_keys.state(), ()) - d.set_keys_type = Func() + self.assertNotIn('disabled', d.button_delete_custom_keys.state()) def test_get_new_keys(self): eq = self.assertEqual @@ -1044,7 +1043,7 @@ def test_on_bindingslist_select(self): b.event_generate('', x=x, y=y) b.event_generate('', x=x, y=y) self.assertEqual(b.get('anchor'), 'find') - self.assertEqual(d.button_new_keys.state(), ()) + self.assertNotIn('disabled', d.button_new_keys.state()) def test_create_new_key_set_and_save_new_key_set(self): eq = self.assertEqual @@ -1081,6 +1080,7 @@ def test_load_keys_list(self): eq = self.assertEqual d = self.page gks = idleConf.GetKeySet = Func() + self.addCleanup(setattr, d, 'load_keys_list', Func()) # Re-mask method. del d.load_keys_list b = d.bindingslist @@ -1116,7 +1116,6 @@ def test_load_keys_list(self): eq(b.get(0, 'end'), expected) eq(b.get('anchor'), 'spam - ') eq(b.curselection(), (2, )) - d.load_keys_list = Func() del idleConf.GetKeySet @@ -1157,7 +1156,7 @@ def test_delete_custom_keys(self): eq(yesno.called, 2) self.assertNotIn(keyset_name, keyspage) eq(idleConf.GetSectionList('user', 'keys'), [keyset_name2]) - eq(d.custom_keyset_on.state(), ()) + self.assertNotIn('disabled', d.custom_keyset_on.state()) eq(d.custom_name.get(), keyset_name2) eq(dialog.deactivate_current_config.called, 1) eq(dialog.activate_config_changes.called, 1) @@ -1170,7 +1169,7 @@ def test_delete_custom_keys(self): eq(yesno.called, 3) self.assertNotIn(keyset_name, keyspage) eq(idleConf.GetSectionList('user', 'keys'), []) - eq(d.custom_keyset_on.state(), ('disabled',)) + self.assertIn('disabled', d.custom_keyset_on.state()) eq(d.custom_name.get(), '- no custom keys -') eq(dialog.deactivate_current_config.called, 2) eq(dialog.activate_config_changes.called, 2) @@ -1366,25 +1365,25 @@ def test_set_add_delete_state(self): # Call with 0 items, 1 unselected item, 1 selected item. eq = self.assertEqual fr = self.frame + self.addCleanup(setattr, fr, 'set_add_delete_state', Func()) # Re-mask method. del fr.set_add_delete_state # Unmask method. sad = fr.set_add_delete_state h = fr.helplist h.delete(0, 'end') sad() - eq(fr.button_helplist_edit.state(), ('disabled',)) - eq(fr.button_helplist_remove.state(), ('disabled',)) + self.assertIn('disabled', fr.button_helplist_edit.state()) + self.assertIn('disabled', fr.button_helplist_remove.state()) h.insert(0, 'source') sad() - eq(fr.button_helplist_edit.state(), ('disabled',)) - eq(fr.button_helplist_remove.state(), ('disabled',)) + self.assertIn('disabled', fr.button_helplist_edit.state()) + self.assertIn('disabled', fr.button_helplist_remove.state()) h.selection_set(0) sad() - eq(fr.button_helplist_edit.state(), ()) - eq(fr.button_helplist_remove.state(), ()) - fr.set_add_delete_state = Func() # Mask method. + self.assertNotIn('disabled', fr.button_helplist_edit.state()) + self.assertNotIn('disabled', fr.button_helplist_remove.state()) def test_helplist_item_add(self): # Call without and twice with HelpSource result. @@ -1462,6 +1461,7 @@ def test_helplist_item_remove(self): def test_update_help_changes(self): fr = self.frame + self.addCleanup(setattr, fr, 'update_help_changes', Func()) # Re-mask method. del fr.update_help_changes fr.user_helplist.clear() fr.user_helplist.append(('name1', 'file1')) @@ -1470,7 +1470,6 @@ def test_update_help_changes(self): fr.update_help_changes() self.assertEqual(mainpage['HelpFiles'], {'1': 'name1;file1', '2': 'name2;file2'}) - fr.update_help_changes = Func() class VarTraceTest(unittest.TestCase): diff --git a/Lib/idlelib/idle_test/test_sidebar.py b/Lib/idlelib/idle_test/test_sidebar.py index 4b3429a270d7b8..25723b0a6abdf7 100644 --- a/Lib/idlelib/idle_test/test_sidebar.py +++ b/Lib/idlelib/idle_test/test_sidebar.py @@ -689,11 +689,15 @@ def test_mousewheel(self): last_lineno = get_end_linenumber(text) self.assertIsNotNone(text.dlineinfo(text.index(f'{last_lineno}.0'))) - # Delta for , whose meaning is platform-dependent. + # Simulate a mouse wheel notch. Tk 8.7 replaced the X11 + # / wheel events with (whose delta is + # platform-dependent); older Tk on X11 still uses the button events. + x11_buttons = (sidebar.canvas._windowingsystem == 'x11' + and tk.TkVersion < 8.7) delta = 1 if sidebar.canvas._windowingsystem == 'aqua' else 120 # Scroll up. - if sidebar.canvas._windowingsystem == 'x11': + if x11_buttons: sidebar.canvas.event_generate('', x=0, y=0) else: sidebar.canvas.event_generate('', x=0, y=0, delta=delta) @@ -701,7 +705,7 @@ def test_mousewheel(self): self.assertIsNone(text.dlineinfo(text.index(f'{last_lineno}.0'))) # Scroll back down. - if sidebar.canvas._windowingsystem == 'x11': + if x11_buttons: sidebar.canvas.event_generate('', x=0, y=0) else: sidebar.canvas.event_generate('', x=0, y=0, delta=-delta)