]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-128846: Make tkinter tests robust against Tcl/Tk 9.0 (GH-153484)
authorSerhiy Storchaka <storchaka@gmail.com>
Sat, 11 Jul 2026 04:25:46 +0000 (07:25 +0300)
committerGitHub <noreply@github.com>
Sat, 11 Jul 2026 04:25:46 +0000 (07:25 +0300)
Backport the test-only parts of the Tcl/Tk 8.7/9.0/9.1 test adaptations so
that test_tkinter and test_ttk pass when Python 3.13 is built against
Tcl/Tk 9.0 (for example the Homebrew build used on macOS), while still
passing against the Tcl/Tk 8.6 that 3.13 ships.

Combines the test changes from:

* gh-124111: Update tkinter for compatibility with Tcl/Tk 9.0.0 (GH-124156)
* gh-124111: test macOS CI with Tk 9 (GH-137424)
* gh-145736: Fix Tkinter tests for Tk 8.7, 9.0 and 9.1 (GH-145738)

The corresponding non-test changes (Modules/_tkinter.c, Lib/tkinter/ttk.py,
PCbuild and CI configuration) are not backported.

Lib/test/test_tkinter/test_misc.py
Lib/test/test_tkinter/test_widgets.py
Lib/test/test_tkinter/widget_tests.py
Lib/test/test_ttk/test_style.py
Lib/test/test_ttk/test_widgets.py

index 24e76885da3be659ca63a1a0fe133ccf2a2c4802..968e2d0db6b28558f99c2bd32199be80e8daa4b3 100644 (file)
@@ -110,9 +110,10 @@ class MiscTest(AbstractTkTest, unittest.TestCase):
         f.tk_busy_forget()
         self.assertFalse(f.tk_busy_status())
         self.assertFalse(f.tk_busy_current())
-        with self.assertRaisesRegex(TclError, "can't find busy window"):
+        errmsg = r"can(no|')t find busy window.*"
+        with self.assertRaisesRegex(TclError, errmsg):
             f.tk_busy_configure()
-        with self.assertRaisesRegex(TclError, "can't find busy window"):
+        with self.assertRaisesRegex(TclError, errmsg):
             f.tk_busy_forget()
 
     @requires_tk(8, 6, 6)
@@ -131,7 +132,8 @@ class MiscTest(AbstractTkTest, unittest.TestCase):
         self.assertEqual(f.tk_busy_configure('cursor')[4], 'heart')
 
         f.tk_busy_forget()
-        with self.assertRaisesRegex(TclError, "can't find busy window"):
+        errmsg = r"can(no|')t find busy window.*"
+        with self.assertRaisesRegex(TclError, errmsg):
             f.tk_busy_cget('cursor')
 
     def test_tk_setPalette(self):
index 4f37ceccbc560108a50948e503c762c4f2e1bc5d..017d8bc73a3a9e98b7f0afc7da6089f40c5f0baa 100644 (file)
@@ -9,9 +9,13 @@ from test.test_tkinter.support import (requires_tk, tk_version,
                                   get_tk_patchlevel, widget_eq,
                                   wait_until_mapped,
                                   AbstractDefaultRootTest)
+
 from test.test_tkinter.widget_tests import (
-    add_standard_options,
-    AbstractWidgetTest, StandardOptionsTests, IntegerSizeTests, PixelSizeTests)
+    add_configure_tests,
+    AbstractWidgetTest,
+    StandardOptionsTests,
+    IntegerSizeTests,
+    PixelSizeTests)
 
 requires('gui')
 
@@ -22,9 +26,13 @@ EXPECTED_SCREEN_DISTANCE_OR_EMPTY_ERRMSG = '(bad|expected) screen distance (or "
 def float_round(x):
     return float(round(x))
 
-
 class AbstractToplevelTest(AbstractWidgetTest, PixelSizeTests):
-    _conv_pad_pixels = False
+    _no_round = {'padx', 'pady'}
+    if tk_version < (8, 7):
+        _clipped = {'highlightthickness'}
+    else:
+        _clipped = {'borderwidth', 'height', 'highlightthickness', 'padx',
+                    'pady', 'width'}
 
     def test_configure_class(self):
         widget = self.create()
@@ -60,7 +68,7 @@ class AbstractToplevelTest(AbstractWidgetTest, PixelSizeTests):
         self.assertEqual(widget2['visual'], 'default')
 
 
-@add_standard_options(StandardOptionsTests)
+@add_configure_tests(StandardOptionsTests)
 class ToplevelTest(AbstractToplevelTest, unittest.TestCase):
     OPTIONS = (
         'background', 'backgroundimage', 'borderwidth',
@@ -103,7 +111,7 @@ class ToplevelTest(AbstractToplevelTest, unittest.TestCase):
             self.assertEqual(widget2['use'], wid)
 
 
-@add_standard_options(StandardOptionsTests)
+@add_configure_tests(StandardOptionsTests)
 class FrameTest(AbstractToplevelTest, unittest.TestCase):
     OPTIONS = (
         'background', 'backgroundimage', 'borderwidth',
@@ -116,7 +124,7 @@ class FrameTest(AbstractToplevelTest, unittest.TestCase):
         return tkinter.Frame(self.root, **kwargs)
 
 
-@add_standard_options(StandardOptionsTests)
+@add_configure_tests(StandardOptionsTests)
 class LabelFrameTest(AbstractToplevelTest, unittest.TestCase):
     OPTIONS = (
         'background', 'borderwidth',
@@ -143,15 +151,24 @@ class LabelFrameTest(AbstractToplevelTest, unittest.TestCase):
         self.checkParam(widget, 'labelwidget', label, expected='.foo')
         label.destroy()
 
-
+# Label, Button, Checkbutton, Radiobutton, MenuButton
 class AbstractLabelTest(AbstractWidgetTest, IntegerSizeTests):
-    _conv_pixels = False
-    _clip_highlightthickness = tk_version >= (8, 7)
-    _clip_pad = tk_version >= (8, 7)
-    _clip_borderwidth = tk_version >= (8, 7)
-
-
-@add_standard_options(StandardOptionsTests)
+    _rounds_pixels = False
+    if tk_version < (8, 7):
+        _clipped = {}
+    elif tk_version < (9, 0):
+        _clipped = {'borderwidth', 'height', 'highlightthickness', 'padx', 'pady', 'width'}
+    else:
+        _clipped = {'borderwidth', 'height', 'highlightthickness',
+                    'insertborderwidth', 'padx', 'pady', 'width'}
+
+    def setUp(self):
+        super().setUp()
+        if tk_version[:2] == (9, 0) and get_tk_patchlevel(self.root) < (9, 0, 2):
+            self._clipped = self._clipped - {'height', 'width'}
+
+
+@add_configure_tests(StandardOptionsTests)
 class LabelTest(AbstractLabelTest, unittest.TestCase):
     OPTIONS = (
         'activebackground', 'activeforeground', 'anchor',
@@ -167,7 +184,7 @@ class LabelTest(AbstractLabelTest, unittest.TestCase):
         return tkinter.Label(self.root, **kwargs)
 
 
-@add_standard_options(StandardOptionsTests)
+@add_configure_tests(StandardOptionsTests)
 class ButtonTest(AbstractLabelTest, unittest.TestCase):
     OPTIONS = (
         'activebackground', 'activeforeground', 'anchor',
@@ -179,6 +196,11 @@ class ButtonTest(AbstractLabelTest, unittest.TestCase):
         'repeatdelay', 'repeatinterval',
         'state', 'takefocus', 'text', 'textvariable',
         'underline', 'width', 'wraplength')
+    if tk_version < (8, 7):
+        _clipped = {}
+    else:
+        _clipped = {'borderwidth', 'height', 'highlightthickness',
+                    'padx', 'pady', 'width'}
 
     def create(self, **kwargs):
         return tkinter.Button(self.root, **kwargs)
@@ -205,7 +227,7 @@ class ButtonTest(AbstractLabelTest, unittest.TestCase):
         widget.flash()  # No exception.
 
 
-@add_standard_options(StandardOptionsTests)
+@add_configure_tests(StandardOptionsTests)
 class CheckbuttonTest(AbstractLabelTest, unittest.TestCase):
     OPTIONS = (
         'activebackground', 'activeforeground', 'anchor',
@@ -293,7 +315,7 @@ class CheckbuttonTest(AbstractLabelTest, unittest.TestCase):
         widget.flash()  # No exception.
 
 
-@add_standard_options(StandardOptionsTests)
+@add_configure_tests(StandardOptionsTests)
 class RadiobuttonTest(AbstractLabelTest, unittest.TestCase):
     OPTIONS = (
         'activebackground', 'activeforeground', 'anchor',
@@ -338,7 +360,7 @@ class RadiobuttonTest(AbstractLabelTest, unittest.TestCase):
         widget.flash()  # No exception.
 
 
-@add_standard_options(StandardOptionsTests)
+@add_configure_tests(StandardOptionsTests)
 class MenubuttonTest(AbstractLabelTest, unittest.TestCase):
     OPTIONS = (
         'activebackground', 'activeforeground', 'anchor',
@@ -351,10 +373,18 @@ class MenubuttonTest(AbstractLabelTest, unittest.TestCase):
         'takefocus', 'text', 'textvariable',
         'underline', 'width', 'wraplength',
     )
-    _conv_pixels = round
-    _clip_highlightthickness = True
-    _clip_pad = True
-    _clip_borderwidth = False
+    _rounds_pixels = (tk_version < (9, 0))
+    if tk_version < (8, 7):
+        _clipped = {'highlightthickness', 'padx', 'pady'}
+    elif tk_version < (9, 0):
+        _clipped = {'borderwidth', 'highlightthickness', 'padx', 'pady'}
+    else:
+        _clipped = {'borderwidth', 'highlightthickness', 'insertborderwidth', 'padx', 'pady'}
+
+    def setUp(self):
+        super().setUp()
+        if tk_version[:2] == (9, 0) and get_tk_patchlevel(self.root) < (9, 0, 1):
+            self._clipped = self._clipped - {'borderwidth'}
 
     def create(self, **kwargs):
         return tkinter.Menubutton(self.root, **kwargs)
@@ -366,13 +396,20 @@ class MenubuttonTest(AbstractLabelTest, unittest.TestCase):
 
     def test_configure_height(self):
         widget = self.create()
-        self.checkIntegerParam(widget, 'height', 100, -100, 0, conv=str)
+        if tk_version < (8, 7) or (tk_version[:2] == (9, 0) and get_tk_patchlevel(self.root) < (9, 0, 1)):
+            conv = str
+        else:
+            conv = False
+        self.checkIntegerParam(widget, 'height', 100, -100, 0, conv=conv)
 
     def test_configure_image(self):
         widget = self.create()
         image = tkinter.PhotoImage(master=self.root, name='image1')
         self.checkParam(widget, 'image', image, conv=str)
-        errmsg = 'image "spam" doesn\'t exist'
+        if tk_version < (8, 7):
+            errmsg = 'image "spam" doesn\'t exist'
+        else:
+            errmsg = 'image "spam" does not exist'
         with self.assertRaises(tkinter.TclError) as cm:
             widget['image'] = 'spam'
         if errmsg is not None:
@@ -390,7 +427,11 @@ class MenubuttonTest(AbstractLabelTest, unittest.TestCase):
 
     def test_configure_width(self):
         widget = self.create()
-        self.checkIntegerParam(widget, 'width', 402, -402, 0, conv=str)
+        if tk_version < (8, 7) or (tk_version[:2] == (9, 0) and get_tk_patchlevel(self.root) < (9, 0, 1)):
+            conv = str
+        else:
+            conv = False
+        self.checkIntegerParam(widget, 'width', 402, -402, 0, conv=conv)
 
 
 class OptionMenuTest(MenubuttonTest, unittest.TestCase):
@@ -402,9 +443,14 @@ class OptionMenuTest(MenubuttonTest, unittest.TestCase):
         with self.assertRaisesRegex(TclError, r"^unknown option -image$"):
             tkinter.OptionMenu(self.root, None, 'b', image='')
 
-
-@add_standard_options(IntegerSizeTests, StandardOptionsTests)
+@add_configure_tests(IntegerSizeTests, StandardOptionsTests)
 class EntryTest(AbstractWidgetTest, unittest.TestCase):
+    if tk_version < (8, 7):
+        _clipped = {'highlightthickness'}
+    else:
+        _clipped = {'borderwidth', 'highlightthickness', 'insertborderwidth',
+                    'insertwidth', 'selectborderwidth'}
+
     OPTIONS = (
         'background', 'borderwidth', 'cursor',
         'disabledbackground', 'disabledforeground',
@@ -431,14 +477,18 @@ class EntryTest(AbstractWidgetTest, unittest.TestCase):
         self.checkPixelsParam(widget, 'insertborderwidth',
                               0, 1.3, 2.6, 6, -2, '10p')
         # insertborderwidth is bounded above by a half of insertwidth.
-        self.checkParam(widget, 'insertborderwidth', 60, expected=100//2)
+        expected =  100 // 2 if tk_version < (8, 7) else 60
+        self.checkParam(widget, 'insertborderwidth', 60, expected=expected)
 
     def test_configure_insertwidth(self):
         widget = self.create()
-        self.checkPixelsParam(widget, 'insertwidth', 1.3, 3.6, '10p')
-        self.checkParam(widget, 'insertwidth', 0.1, expected=2)
-        self.checkParam(widget, 'insertwidth', -2, expected=2)
-        self.checkParam(widget, 'insertwidth', 0.9, expected=1)
+        self.checkPixelsParam(widget, 'insertwidth', 1.3, 3.6, 0.9, '10p')
+        if tk_version < (8, 7):
+            self.checkParam(widget, 'insertwidth', 0, expected=2)
+            self.checkParam(widget, 'insertwidth', 0.1, expected=2)
+            self.checkParam(widget, 'insertwidth', -2, expected=2)
+        else:
+            self.checkPixelsParam(widget, 'insertwidth', 0, 0.1, -2)
 
     def test_configure_invalidcommand(self):
         widget = self.create()
@@ -537,7 +587,7 @@ class EntryTest(AbstractWidgetTest, unittest.TestCase):
                                widget.select_range, 'xyz', 'end')
 
 
-@add_standard_options(StandardOptionsTests)
+@add_configure_tests(StandardOptionsTests)
 class SpinboxTest(EntryTest, unittest.TestCase):
     OPTIONS = (
         'activebackground', 'background', 'borderwidth',
@@ -622,11 +672,22 @@ class SpinboxTest(EntryTest, unittest.TestCase):
         # XXX
         widget = self.create()
         self.assertEqual(widget['values'], '')
-        self.checkParam(widget, 'values', 'mon tue wed thur')
+        if tk_version < (8, 7) or (tk_version[:2] == (9, 0) and get_tk_patchlevel(self.root) < (9, 0, 1)):
+            expected = 'mon tue wed thur'
+        else:
+            expected = ('mon', 'tue', 'wed', 'thur')
+        self.checkParam(widget, 'values', 'mon tue wed thur',
+                        expected=expected)
         self.checkParam(widget, 'values', ('mon', 'tue', 'wed', 'thur'),
-                        expected='mon tue wed thur')
+                        expected=expected)
+
+        if tk_version < (8, 7) or (tk_version[:2] == (9, 0) and get_tk_patchlevel(self.root) < (9, 0, 1)):
+            expected = '42 3.14 {} {any string}'
+        else:
+            expected = (42, 3.14, '', 'any string')
         self.checkParam(widget, 'values', (42, 3.14, '', 'any string'),
-                        expected='42 3.14 {} {any string}')
+                        expected=expected)
+
         self.checkParam(widget, 'values', '')
 
     def test_configure_wrap(self):
@@ -707,7 +768,7 @@ class SpinboxTest(EntryTest, unittest.TestCase):
         self.assertRaises(TypeError, widget.scan_mark)
 
 
-@add_standard_options(StandardOptionsTests)
+@add_configure_tests(StandardOptionsTests)
 class TextTest(AbstractWidgetTest, unittest.TestCase):
     OPTIONS = (
         'autoseparators', 'background', 'blockcursor', 'borderwidth',
@@ -722,6 +783,20 @@ class TextTest(AbstractWidgetTest, unittest.TestCase):
         'tabs', 'tabstyle', 'takefocus', 'undo', 'width', 'wrap',
         'xscrollcommand', 'yscrollcommand',
     )
+    _no_round = {'selectborderwidth'}
+    if tk_version < (9, 0):
+        _clipped = {'highlightthickness', 'spacing1', 'spacing2', 'spacing3'}
+    else:
+        _clipped = {'borderwidth', 'height', 'highlightthickness',
+                    'insertborderwidth', 'insertwidth', 'padx', 'pady',
+                    'selectborderwidth', 'spacing1', 'spacing2', 'spacing3'}
+
+    def setUp(self):
+        super().setUp()
+        if tk_version[:2] == (9, 0) and get_tk_patchlevel(self.root) < (9, 0, 2):
+            self._clipped = self._clipped - {'borderwidth', 'height', 'padx', 'pady'}
+        if tk_version[:2] == (9, 0) and get_tk_patchlevel(self.root) < (9, 0, 1):
+            self._clipped = self._clipped - {'insertborderwidth', 'insertwidth', 'selectborderwidth'}
 
     def create(self, **kwargs):
         return tkinter.Text(self.root, **kwargs)
@@ -750,8 +825,11 @@ class TextTest(AbstractWidgetTest, unittest.TestCase):
     def test_configure_height(self):
         widget = self.create()
         self.checkPixelsParam(widget, 'height', 100, 101.2, 102.6, '3c')
-        self.checkParam(widget, 'height', -100, expected=1)
-        self.checkParam(widget, 'height', 0, expected=1)
+        if tk_version < (9, 0):
+            self.checkParam(widget, 'height', 0, expected=1)
+            self.checkParam(widget, 'height', -100, expected=1)
+        else:
+            self.checkPixelsParam(widget, 'height', 0, -100)
 
     def test_configure_maxundo(self):
         widget = self.create()
@@ -767,25 +845,17 @@ class TextTest(AbstractWidgetTest, unittest.TestCase):
         self.checkEnumParam(widget, 'insertunfocussed',
                             'hollow', 'none', 'solid')
 
-    def test_configure_selectborderwidth(self):
-        widget = self.create()
-        self.checkPixelsParam(widget, 'selectborderwidth',
-                              1.3, 2.6, -2, '10p', conv=False)
-
     def test_configure_spacing1(self):
         widget = self.create()
-        self.checkPixelsParam(widget, 'spacing1', 20, 21.4, 22.6, '0.5c')
-        self.checkParam(widget, 'spacing1', -5, expected=0)
+        self.checkPixelsParam(widget, 'spacing1', 20, 21.4, 22.6, -5, '0.5c')
 
     def test_configure_spacing2(self):
         widget = self.create()
-        self.checkPixelsParam(widget, 'spacing2', 5, 6.4, 7.6, '0.1c')
-        self.checkParam(widget, 'spacing2', -1, expected=0)
+        self.checkPixelsParam(widget, 'spacing2', 5, 6.4, 7.6, -1, '0.1c')
 
     def test_configure_spacing3(self):
         widget = self.create()
-        self.checkPixelsParam(widget, 'spacing3', 20, 21.4, 22.6, '0.5c')
-        self.checkParam(widget, 'spacing3', -10, expected=0)
+        self.checkPixelsParam(widget, 'spacing3', 20, 21.4, 22.6, -10, '0.5c')
 
     def test_configure_startline(self):
         widget = self.create()
@@ -844,7 +914,7 @@ class TextTest(AbstractWidgetTest, unittest.TestCase):
         self.assertRaises(TypeError, widget.bbox, '1.1', 'end')
 
 
-@add_standard_options(PixelSizeTests, StandardOptionsTests)
+@add_configure_tests(PixelSizeTests, StandardOptionsTests)
 class CanvasTest(AbstractWidgetTest, unittest.TestCase):
     OPTIONS = (
         'background', 'borderwidth',
@@ -858,10 +928,22 @@ class CanvasTest(AbstractWidgetTest, unittest.TestCase):
         'xscrollcommand', 'xscrollincrement',
         'yscrollcommand', 'yscrollincrement', 'width',
     )
-
-    _conv_pixels = round
+    if tk_version < (8, 7):
+        _clipped = {'highlightthickness'}
+    else:
+        _clipped = {'borderwidth', 'height', 'highlightthickness',
+                    'insertborderwidth', 'insertwidth', 'selectborderwidth',
+                    'width', 'xscrollincrement', 'yscrollincrement'}
     _stringify = True
 
+    def setUp(self):
+        super().setUp()
+        if tk_version[:2] == (9, 0) and get_tk_patchlevel(self.root) < (9, 0, 1):
+            self._rounds_pixels = True
+            self._no_round = {'borderwidth', 'height', 'highlightthickness',
+                              'width', 'xscrollincrement', 'yscrollincrement'}
+            self._clipped = self._clipped - {'insertborderwidth', 'insertwidth', 'selectborderwidth'}
+
     def create(self, **kwargs):
         return tkinter.Canvas(self.root, **kwargs)
 
@@ -1008,7 +1090,6 @@ class CanvasTest(AbstractWidgetTest, unittest.TestCase):
 
     def test_create_polygon(self):
         c = self.create()
-        tk87 = tk_version >= (8, 7)
         # In Tk < 8.7 polygons are filled, but has no outline by default.
         # This affects its size, so always explicitly specify outline.
         i1 = c.create_polygon(20, 30, 40, 50, 60, 10, outline='red')
@@ -1602,7 +1683,7 @@ class CanvasTest(AbstractWidgetTest, unittest.TestCase):
         self.assertRaises(TypeError, c.tag_unbind, item)
 
 
-@add_standard_options(IntegerSizeTests, StandardOptionsTests)
+@add_configure_tests(IntegerSizeTests, StandardOptionsTests)
 class ListboxTest(AbstractWidgetTest, unittest.TestCase):
     OPTIONS = (
         'activestyle', 'background', 'borderwidth', 'cursor',
@@ -1614,6 +1695,10 @@ class ListboxTest(AbstractWidgetTest, unittest.TestCase):
         'selectmode', 'setgrid', 'state',
         'takefocus', 'width', 'xscrollcommand', 'yscrollcommand',
     )
+    if tk_version < (8, 7):
+        _clipped = {'highlightthickness'}
+    else:
+        _clipped = {'borderwidth', 'highlightthickness', 'selectborderwidth'}
 
     def create(self, **kwargs):
         return tkinter.Listbox(self.root, **kwargs)
@@ -1857,7 +1942,7 @@ class ListboxTest(AbstractWidgetTest, unittest.TestCase):
         self.assertEqual(lb.curselection(), (2,))
 
 
-@add_standard_options(PixelSizeTests, StandardOptionsTests)
+@add_configure_tests(PixelSizeTests, StandardOptionsTests)
 class ScaleTest(AbstractWidgetTest, unittest.TestCase):
     OPTIONS = (
         'activebackground', 'background', 'bigincrement', 'borderwidth',
@@ -1868,6 +1953,7 @@ class ScaleTest(AbstractWidgetTest, unittest.TestCase):
         'resolution', 'showvalue', 'sliderlength', 'sliderrelief', 'state',
         'takefocus', 'tickinterval', 'to', 'troughcolor', 'variable', 'width',
     )
+    _clipped = {'highlightthickness'}
     default_orient = 'vertical'
 
     def create(self, **kwargs):
@@ -1933,7 +2019,7 @@ class ScaleTest(AbstractWidgetTest, unittest.TestCase):
         self.assertRaises(TclError, widget.identify, 'a', 'b')
 
 
-@add_standard_options(PixelSizeTests, StandardOptionsTests)
+@add_configure_tests(PixelSizeTests, StandardOptionsTests)
 class ScrollbarTest(AbstractWidgetTest, unittest.TestCase):
     OPTIONS = (
         'activebackground', 'activerelief',
@@ -1944,7 +2030,13 @@ class ScrollbarTest(AbstractWidgetTest, unittest.TestCase):
         'repeatdelay', 'repeatinterval',
         'takefocus', 'troughcolor', 'width',
     )
-    _conv_pixels = round
+    if tk_version < (8, 7):
+        _clipped = {'highlightthickness'}
+    elif tk_version < (9, 0):
+        _clipped = {'borderwidth', 'elementborderwidth', 'highlightthickness'}
+    else:
+        _clipped = {'borderwidth', 'elementborderwidth', 'highlightthickness', 'width'}
+    _clipped_to_default = {'elementborderwidth'}
     _stringify = True
     default_orient = 'vertical'
 
@@ -1953,9 +2045,7 @@ class ScrollbarTest(AbstractWidgetTest, unittest.TestCase):
 
     def test_configure_elementborderwidth(self):
         widget = self.create()
-        self.checkPixelsParam(widget, 'elementborderwidth', 4.3, 5.6, '1m')
-        expected = self._default_pixels if tk_version >= (8, 7) else -2
-        self.checkParam(widget, 'elementborderwidth', -2, expected=expected)
+        self.checkPixelsParam(widget, 'elementborderwidth', 4.3, 5.6, -2, '1m')
 
     def test_configure_orient(self):
         widget = self.create()
@@ -2012,7 +2102,7 @@ class ScrollbarTest(AbstractWidgetTest, unittest.TestCase):
         self.assertRaises(TclError, sb.identify, 'a', 'b')
 
 
-@add_standard_options(StandardOptionsTests)
+@add_configure_tests(PixelSizeTests, StandardOptionsTests)
 class PanedWindowTest(AbstractWidgetTest, unittest.TestCase):
     OPTIONS = (
         'background', 'borderwidth', 'cursor',
@@ -2023,6 +2113,9 @@ class PanedWindowTest(AbstractWidgetTest, unittest.TestCase):
         'sashcursor', 'sashpad', 'sashrelief', 'sashwidth',
         'showhandle', 'width',
     )
+    _no_round = {'handlesize', 'height', 'proxyborderwidth', 'sashwidth',
+                 'selectborderwidth', 'width'}
+    _clipped = {}
     default_orient = 'horizontal'
 
     def create(self, **kwargs):
@@ -2034,13 +2127,7 @@ class PanedWindowTest(AbstractWidgetTest, unittest.TestCase):
 
     def test_configure_handlesize(self):
         widget = self.create()
-        self.checkPixelsParam(widget, 'handlesize', 8, 9.4, 10.6, -3, '2m',
-                              conv=False)
-
-    def test_configure_height(self):
-        widget = self.create()
-        self.checkPixelsParam(widget, 'height', 100, 101.2, 102.6, -100, 0, '1i',
-                              conv=False)
+        self.checkPixelsParam(widget, 'handlesize', 8, 9.4, 10.6, -3, '2m')
 
     def test_configure_opaqueresize(self):
         widget = self.create()
@@ -2055,8 +2142,7 @@ class PanedWindowTest(AbstractWidgetTest, unittest.TestCase):
     def test_configure_proxyborderwidth(self):
         widget = self.create()
         self.checkPixelsParam(widget, 'proxyborderwidth',
-                              0, 1.3, 2.9, 6, -2, '10p',
-                              conv=False)
+                              0, 1.3, 2.9, 6, -2, '10p')
 
     @requires_tk(8, 6, 5)
     def test_configure_proxyrelief(self):
@@ -2078,18 +2164,12 @@ class PanedWindowTest(AbstractWidgetTest, unittest.TestCase):
 
     def test_configure_sashwidth(self):
         widget = self.create()
-        self.checkPixelsParam(widget, 'sashwidth', 10, 11.1, 15.6, -3, '1m',
-                              conv=False)
+        self.checkPixelsParam(widget, 'sashwidth', 10, 11.1, 15.6, -3, '1m')
 
     def test_configure_showhandle(self):
         widget = self.create()
         self.checkBooleanParam(widget, 'showhandle')
 
-    def test_configure_width(self):
-        widget = self.create()
-        self.checkPixelsParam(widget, 'width', 402, 403.4, 404.6, -402, 0, '5i',
-                              conv=False)
-
     def create2(self):
         p = self.create()
         b = tkinter.Button(p)
@@ -2222,13 +2302,13 @@ class PanedWindowTest(AbstractWidgetTest, unittest.TestCase):
 
     def test_paneconfigure_padx(self):
         p, b, c = self.create2()
-        self.check_paneconfigure(p, b, 'padx', 1.3, 1)
+        self.check_paneconfigure(p, b, 'padx', 1.3, 1 if tk_version < (9, 0) else 1.3)
         self.check_paneconfigure_bad(p, b, 'padx',
                 EXPECTED_SCREEN_DISTANCE_ERRMSG.format('badValue'))
 
     def test_paneconfigure_pady(self):
         p, b, c = self.create2()
-        self.check_paneconfigure(p, b, 'pady', 1.3, 1)
+        self.check_paneconfigure(p, b, 'pady', 1.3, 1 if tk_version < (9, 0) else 1.3)
         self.check_paneconfigure_bad(p, b, 'pady',
                 EXPECTED_SCREEN_DISTANCE_ERRMSG.format('badValue'))
 
@@ -2254,17 +2334,17 @@ class PanedWindowTest(AbstractWidgetTest, unittest.TestCase):
                 EXPECTED_SCREEN_DISTANCE_OR_EMPTY_ERRMSG.format('badValue'))
 
 
-@add_standard_options(StandardOptionsTests)
+@add_configure_tests(StandardOptionsTests)
 class MenuTest(AbstractWidgetTest, unittest.TestCase):
     OPTIONS = (
         'activebackground', 'activeborderwidth', 'activeforeground',
-        'activerelief',
-        'background', 'borderwidth', 'cursor',
+        'activerelief', 'background', 'borderwidth', 'cursor',
         'disabledforeground', 'font', 'foreground',
         'postcommand', 'relief', 'selectcolor', 'takefocus',
         'tearoff', 'tearoffcommand', 'title', 'type',
     )
-    _conv_pixels = False
+    _rounds_pixels = False
+    _clipped = {}
 
     def create(self, **kwargs):
         return tkinter.Menu(self.root, **kwargs)
@@ -2519,7 +2599,7 @@ class MenuTest(AbstractWidgetTest, unittest.TestCase):
                                m.entrycget, 0, 'onvalue')
 
 
-@add_standard_options(PixelSizeTests, StandardOptionsTests)
+@add_configure_tests(PixelSizeTests, StandardOptionsTests)
 class MessageTest(AbstractWidgetTest, unittest.TestCase):
     OPTIONS = (
         'anchor', 'aspect', 'background', 'borderwidth',
@@ -2528,11 +2608,12 @@ class MessageTest(AbstractWidgetTest, unittest.TestCase):
         'justify', 'padx', 'pady', 'relief',
         'takefocus', 'text', 'textvariable', 'width',
     )
-    _conv_pad_pixels = False
-    if tk_version >= (8, 7):
-        _conv_pixels = False
-    _clip_pad = tk_version >= (8, 7)
-    _clip_borderwidth = tk_version >= (8, 7)
+    _no_round = {'padx', 'pady'}
+    if tk_version < (8, 7):
+        _clipped = {'highlightthickness'}
+    else:
+        _clipped = {'borderwidth', 'highlightthickness', 'padx', 'pady', 'width'}
+    _clipped_to_default = {'padx', 'pady'}
 
     def create(self, **kwargs):
         return tkinter.Message(self.root, **kwargs)
@@ -2541,26 +2622,6 @@ class MessageTest(AbstractWidgetTest, unittest.TestCase):
         widget = self.create()
         self.checkIntegerParam(widget, 'aspect', 250, 0, -300)
 
-    def test_configure_padx(self):
-        widget = self.create()
-        self.checkPixelsParam(widget, 'padx', 3, 4.4, 5.6, '12m',
-                              conv=self._conv_pad_pixels)
-        expected = self._default_pixels if self._clip_pad else -2
-        self.checkParam(widget, 'padx', -2, expected=expected)
-
-    def test_configure_pady(self):
-        widget = self.create()
-        self.checkPixelsParam(widget, 'pady', 3, 4.4, 5.6, '12m',
-                              conv=self._conv_pad_pixels)
-        expected = self._default_pixels if self._clip_pad else -2
-        self.checkParam(widget, 'pady', -2, expected=expected)
-
-    def test_configure_width(self):
-        widget = self.create()
-        self.checkPixelsParam(widget, 'width', 402, 403.4, 404.6, 0, '5i')
-        expected = 0 if tk_version >= (8, 7) else -402
-        self.checkParam(widget, 'width', -402, expected=expected)
-
 
 class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):
 
index efa9299d2f8b1b8a1677015e4ad67268b5ff6f2b..3453678271cffe79f0899d8c6b6a644621bddc8d 100644 (file)
@@ -6,7 +6,6 @@ from test.test_tkinter.support import (AbstractTkTest, requires_tk, tk_version,
                                   pixels_conv, tcl_obj_eq)
 import test.support
 
-
 _sentinel = object()
 
 # Abbreviated option names accepted by Tk in addition to the full names.
@@ -23,14 +22,12 @@ _OPTION_ALIASES = {
 # borderwidth = bd
 
 class AbstractWidgetTest(AbstractTkTest):
-    _default_pixels = '' if tk_version >= (9, 0) else -1 if tk_version >= (8, 7) else ''
-    _conv_pixels = round
-    _conv_pad_pixels = None
-    _stringify = False
-    _clip_highlightthickness = True
-    _clip_pad = False
-    _clip_borderwidth = False
+    _default_pixels = '' if tk_version >= (9, 0) else -1  # Value for unset pixel options.
+    _rounds_pixels = (tk_version < (9, 0))  # True if some pixel options are rounded.
+    _no_round = set()      # Pixel options which are not rounded nonetheless
+    _stringify = False     # Whether to convert tuples to strings
     _allow_empty_justify = False
+    _clipped_to_default = set()
 
     @property
     def scaling(self):
@@ -57,6 +54,12 @@ class AbstractWidgetTest(AbstractTkTest):
         widget[name] = value
         if expected is _sentinel:
             expected = value
+            if name in self._clipped:
+                if not isinstance(expected, str) and expected < 0:
+                    if tk_version >= (8, 7) and name in self._clipped_to_default:
+                        expected = self._default_pixels
+                    else:
+                        expected = 0
         if conv:
             expected = conv(expected)
         if self._stringify or not self.wantobjects:
@@ -153,17 +156,23 @@ class AbstractWidgetTest(AbstractTkTest):
             errmsg = 'bad' + errmsg2
         self.checkInvalidParam(widget, name, 'spam', errmsg=errmsg)
 
-    def checkPixelsParam(self, widget, name, *values,
-                         conv=None, **kwargs):
+    def checkPixelsParam(self, widget, name, *values, conv=None, **kwargs):
         if conv is None:
-            conv = self._conv_pixels
+            if self._rounds_pixels and name not in self._no_round:
+                conv = round
+        alow_neg = tk_version < (9, 1)
         for value in values:
             expected = _sentinel
             conv1 = conv
             if isinstance(value, str):
+                if not getattr(self, '_converts_pixels', True):
+                    conv1 = str
                 if conv1 and conv1 is not str:
                     expected = pixels_conv(value) * self.scaling
                     conv1 = round
+            elif not alow_neg and isinstance(value, (int, float)) and value < 0:
+                self.checkInvalidParam(widget, name, value)
+                continue
             self.checkParam(widget, name, value, expected=expected,
                             conv=conv1, **kwargs)
         errmsg = '(bad|expected) screen distance ((or "" )?but got )?"{}"'
@@ -185,8 +194,12 @@ class AbstractWidgetTest(AbstractTkTest):
     def checkImageParam(self, widget, name):
         image = tkinter.PhotoImage(master=self.root, name='image1')
         self.checkParam(widget, name, image, conv=str)
+        if tk_version < (8, 7):
+            errmsg = 'image "spam" doesn\'t exist'
+        else:
+            errmsg = 'image "spam" does not exist'
         self.checkInvalidParam(widget, name, 'spam',
-                errmsg='image "spam" doesn\'t exist')
+                               errmsg=errmsg)
         widget[name] = ''
 
     def checkVariableParam(self, widget, name, var):
@@ -249,31 +262,76 @@ class AbstractWidgetTest(AbstractTkTest):
                     print('%s.__init__ docstring doesn\'t contain "%s"'
                           % (type(widget).__name__, key))
 
+class PixelOptionsTests:
+    """Standard options that accept all formats acceptable to Tk_GetPixels.
 
-class StandardOptionsTests:
-    STANDARD_OPTIONS = (
-        'activebackground', 'activeborderwidth', 'activeforeground', 'anchor',
-        'background', 'bitmap', 'borderwidth', 'compound', 'cursor',
-        'disabledforeground', 'exportselection', 'font', 'foreground',
-        'highlightbackground', 'highlightcolor', 'highlightthickness',
-        'image', 'insertbackground', 'insertborderwidth',
-        'insertofftime', 'insertontime', 'insertwidth',
-        'jump', 'justify', 'orient', 'padx', 'pady', 'relief',
-        'repeatdelay', 'repeatinterval',
-        'selectbackground', 'selectborderwidth', 'selectforeground',
-        'setgrid', 'takefocus', 'text', 'textvariable', 'troughcolor',
-        'underline', 'wraplength', 'xscrollcommand', 'yscrollcommand',
-    )
-
-    def test_configure_activebackground(self):
-        widget = self.create()
-        self.checkColorParam(widget, 'activebackground')
+    In addition to numbers, these options can be set with distances
+    specified as a string consisting of a number followed by a single
+    character giving the unit of distance. The allowed units are:
+    millimeters ('m'), centimeters ('c'), inches ('i') or points ('p').
+    In Tk 9 a cget call for one of these options returns a Tcl_Obj of
+    type "pixels", whose string representation is the distance string
+    passed to configure.
+    """
+    PIXEL_OPTIONS = ('activeborderwidth', 'borderwidth', 'highlightthickness',
+      'insertborderwidth', 'insertwidth', 'padx', 'pady', 'selectborderwidth')
 
     def test_configure_activeborderwidth(self):
         widget = self.create()
         self.checkPixelsParam(widget, 'activeborderwidth',
                               0, 1.3, 2.9, 6, -2, '10p')
 
+    def test_configure_borderwidth(self):
+        widget = self.create()
+        self.checkPixelsParam(widget, 'borderwidth',
+                              0, 1.3, 2.6, 6, -2, '10p')
+
+        if 'bd' in self.OPTIONS:
+            self.checkPixelsParam(widget, 'bd', 0, 1.3, 2.6, 6, '10p')
+            self.checkParam(widget, 'bd', -2, expected=expected)
+
+    def test_configure_highlightthickness(self):
+        widget = self.create()
+        self.checkPixelsParam(widget, 'highlightthickness',
+                              0, 1.3, 2.6, 6, -2, '10p')
+
+    def test_configure_insertborderwidth(self):
+        widget = self.create()
+        self.checkPixelsParam(widget, 'insertborderwidth', 0, 1.3, 2.6, 6, -2, '10p')
+
+    def test_configure_insertwidth(self):
+        widget = self.create()
+        self.checkPixelsParam(widget, 'insertwidth', 1.3, 2.6, -2, '10p')
+
+    def test_configure_padx(self):
+        widget = self.create()
+        self.checkPixelsParam(widget, 'padx', 3, 4.4, 5.6, -2, '12m')
+
+    def test_configure_pady(self):
+        widget = self.create()
+        self.checkPixelsParam(widget, 'pady', 3, 4.4, 5.6, -2, '12m')
+
+    def test_configure_selectborderwidth(self):
+        widget = self.create()
+        self.checkPixelsParam(widget, 'selectborderwidth', 1.3, 2.6, -2, '10p')
+
+
+class StandardOptionsTests(PixelOptionsTests):
+
+    STANDARD_OPTIONS = ( 'activebackground', 'activeforeground',
+    'anchor', 'background', 'bitmap', 'compound', 'cursor',
+    'disabledforeground', 'exportselection', 'font', 'foreground',
+    'highlightbackground', 'highlightcolor', 'image',
+    'insertbackground', 'insertofftime', 'insertontime', 'jump',
+    'justify', 'orient', 'relief', 'repeatdelay', 'repeatinterval',
+    'selectbackground', 'selectforeground', 'setgrid', 'takefocus',
+    'text', 'textvariable', 'troughcolor', 'underline', 'wraplength',
+    'xscrollcommand', 'yscrollcommand', ) + PixelOptionsTests.PIXEL_OPTIONS
+
+    def test_configure_activebackground(self):
+        widget = self.create()
+        self.checkColorParam(widget, 'activebackground')
+
     def test_configure_activeforeground(self):
         widget = self.create()
         self.checkColorParam(widget, 'activeforeground')
@@ -311,18 +369,6 @@ class StandardOptionsTests:
             self.checkInvalidParam(widget, 'bitmap', 'spam',
                     errmsg='bitmap "spam" not defined')
 
-    def test_configure_borderwidth(self):
-        widget = self.create()
-        self.checkPixelsParam(widget, 'borderwidth',
-                              0, 1.3, 2.6, 6, '10p')
-        expected = 0 if self._clip_borderwidth else -2
-        self.checkParam(widget, 'borderwidth', -2, expected=expected,
-                        conv=self._conv_pixels)
-        if 'bd' in self.OPTIONS:
-            self.checkPixelsParam(widget, 'bd', 0, 1.3, 2.6, 6, '10p')
-            self.checkParam(widget, 'bd', -2, expected=expected,
-                            conv=self._conv_pixels)
-
     def test_configure_compound(self):
         widget = self.create()
         self.checkEnumParam(widget, 'compound',
@@ -346,8 +392,8 @@ class StandardOptionsTests:
                         '-Adobe-Helvetica-Medium-R-Normal--*-120-*-*-*-*-*-*')
         is_ttk = widget.__class__.__module__ == 'tkinter.ttk'
         if not is_ttk:
-            self.checkInvalidParam(widget, 'font', '',
-                                   errmsg='font "" doesn\'t exist')
+            errmsg = 'font "" does ?n[o\']t exist'
+            self.checkInvalidParam(widget, 'font', '', errmsg=errmsg)
 
     def test_configure_foreground(self):
         widget = self.create()
@@ -363,14 +409,6 @@ class StandardOptionsTests:
         widget = self.create()
         self.checkColorParam(widget, 'highlightcolor')
 
-    def test_configure_highlightthickness(self):
-        widget = self.create()
-        self.checkPixelsParam(widget, 'highlightthickness',
-                              0, 1.3, 2.6, 6, '10p')
-        expected = 0 if self._clip_highlightthickness else -2
-        self.checkParam(widget, 'highlightthickness', -2, expected=expected,
-                        conv=self._conv_pixels)
-
     def test_configure_image(self):
         widget = self.create()
         self.checkImageParam(widget, 'image')
@@ -379,11 +417,6 @@ class StandardOptionsTests:
         widget = self.create()
         self.checkColorParam(widget, 'insertbackground')
 
-    def test_configure_insertborderwidth(self):
-        widget = self.create()
-        self.checkPixelsParam(widget, 'insertborderwidth',
-                              0, 1.3, 2.6, 6, -2, '10p')
-
     def test_configure_insertofftime(self):
         widget = self.create()
         self.checkIntegerParam(widget, 'insertofftime', 100)
@@ -392,10 +425,6 @@ class StandardOptionsTests:
         widget = self.create()
         self.checkIntegerParam(widget, 'insertontime', 100)
 
-    def test_configure_insertwidth(self):
-        widget = self.create()
-        self.checkPixelsParam(widget, 'insertwidth', 1.3, 2.6, -2, '10p')
-
     def test_configure_jump(self):
         widget = self.create()
         self.checkBooleanParam(widget, 'jump')
@@ -413,22 +442,6 @@ class StandardOptionsTests:
         self.assertEqual(str(widget['orient']), self.default_orient)
         self.checkEnumParam(widget, 'orient', 'horizontal', 'vertical')
 
-    def test_configure_padx(self):
-        widget = self.create()
-        self.checkPixelsParam(widget, 'padx', 3, 4.4, 5.6, '12m',
-                              conv=self._conv_pad_pixels)
-        expected = 0 if self._clip_pad else -2
-        self.checkParam(widget, 'padx', -2, expected=expected,
-                        conv=self._conv_pad_pixels)
-
-    def test_configure_pady(self):
-        widget = self.create()
-        self.checkPixelsParam(widget, 'pady', 3, 4.4, 5.6, '12m',
-                              conv=self._conv_pad_pixels)
-        expected = 0 if self._clip_pad else -2
-        self.checkParam(widget, 'pady', -2, expected=expected,
-                        conv=self._conv_pad_pixels)
-
     @requires_tk(8, 7)
     def test_configure_placeholder(self):
         widget = self.create()
@@ -455,10 +468,6 @@ class StandardOptionsTests:
         widget = self.create()
         self.checkColorParam(widget, 'selectbackground')
 
-    def test_configure_selectborderwidth(self):
-        widget = self.create()
-        self.checkPixelsParam(widget, 'selectborderwidth', 1.3, 2.6, -2, '10p')
-
     def test_configure_selectforeground(self):
         widget = self.create()
         self.checkColorParam(widget, 'selectforeground')
@@ -568,6 +577,7 @@ class StandardOptionsTests:
 
 
 class IntegerSizeTests:
+    """ Tests widgets which only accept integral width and height."""
     def test_configure_height(self):
         widget = self.create()
         self.checkIntegerParam(widget, 'height', 100, -100, 0)
@@ -578,6 +588,7 @@ class IntegerSizeTests:
 
 
 class PixelSizeTests:
+    """ Tests widgets which accept screen distances for width and height."""
     def test_configure_height(self):
         widget = self.create()
         self.checkPixelsParam(widget, 'height', 100, 101.2, 102.6, -100, 0, '3c')
@@ -587,7 +598,7 @@ class PixelSizeTests:
         self.checkPixelsParam(widget, 'width', 402, 403.4, 404.6, -402, 0, '5i')
 
 
-def add_standard_options(*source_classes):
+def add_configure_tests(*source_classes):
     # This decorator adds test_configure_xxx methods from source classes for
     # every xxx option in the OPTIONS class attribute if they are not defined
     # explicitly.
index fec878aca78e668a1844f056ef1ef59112bb99e1..56d8a5931fdfa5ce1bdbb6075958c5cd2faa875c 100644 (file)
@@ -222,7 +222,8 @@ class StyleTest(AbstractTkTest, unittest.TestCase):
         style = self.style
         with self.assertRaises(IndexError):
             style.element_create('plain.newelem', 'from')
-        with self.assertRaisesRegex(TclError, 'theme "spam" doesn\'t exist'):
+        with self.assertRaisesRegex(TclError,
+            'theme "spam" (does not|doesn\'t) exist'):
             style.element_create('plain.newelem', 'from', 'spam')
 
     def test_element_create_image(self):
index 858516a6c5f363c033c6352dd3b54f9f7969df9c..7210ffde040cdc71597df94e56a964d0d941668e 100644 (file)
@@ -9,7 +9,7 @@ from test.test_tkinter.support import setUpModule  # noqa: F401
 from test.test_tkinter.support import (
     AbstractTkTest, requires_tk, tk_version, get_tk_patchlevel,
     simulate_mouse_click, wait_until_mapped, AbstractDefaultRootTest)
-from test.test_tkinter.widget_tests import (add_standard_options,
+from test.test_tkinter.widget_tests import (add_configure_tests,
     AbstractWidgetTest, StandardOptionsTests, IntegerSizeTests, PixelSizeTests)
 
 requires('gui')
@@ -128,10 +128,11 @@ class WidgetTest(AbstractTkTest, unittest.TestCase):
 
 
 class AbstractToplevelTest(AbstractWidgetTest, PixelSizeTests):
-    _conv_pixels = False
+    _rounds_pixels = False
+    _clipped = {}
 
 
-@add_standard_options(StandardTtkOptionsTests)
+@add_configure_tests(StandardTtkOptionsTests)
 class FrameTest(AbstractToplevelTest, unittest.TestCase):
     OPTIONS = (
         'borderwidth', 'class', 'cursor', 'height',
@@ -143,7 +144,7 @@ class FrameTest(AbstractToplevelTest, unittest.TestCase):
         return ttk.Frame(self.root, **kwargs)
 
 
-@add_standard_options(StandardTtkOptionsTests)
+@add_configure_tests(StandardTtkOptionsTests)
 class LabelFrameTest(AbstractToplevelTest, unittest.TestCase):
     OPTIONS = (
         'borderwidth', 'class', 'cursor', 'height',
@@ -171,6 +172,8 @@ class LabelFrameTest(AbstractToplevelTest, unittest.TestCase):
 
 class AbstractLabelTest(AbstractWidgetTest):
     _allow_empty_justify = True
+    _rounds_pixels = False
+    _clipped = {}
 
     def checkImageParam(self, widget, name):
         image = tkinter.PhotoImage(master=self.root, name='image1')
@@ -182,8 +185,11 @@ class AbstractLabelTest(AbstractWidgetTest):
                         expected=('image1', 'active', 'image2'))
         self.checkParam(widget, name, 'image1 active image2',
                         expected=('image1', 'active', 'image2'))
-        self.checkInvalidParam(widget, name, 'spam',
-                errmsg='image "spam" doesn\'t exist')
+        if tk_version < (8, 7):
+            errmsg = 'image "spam" doesn\'t exist'
+        else:
+            errmsg = 'image "spam" does not exist'
+        self.checkInvalidParam(widget, name, 'spam', errmsg=errmsg)
 
     def test_configure_compound(self):
         values = ('none', 'text', 'image', 'center', 'top', 'bottom', 'left', 'right')
@@ -199,7 +205,7 @@ class AbstractLabelTest(AbstractWidgetTest):
         self.checkParams(widget, 'width', 402, -402, 0)
 
 
-@add_standard_options(StandardTtkOptionsTests)
+@add_configure_tests(StandardTtkOptionsTests)
 class LabelTest(AbstractLabelTest, unittest.TestCase):
     OPTIONS = (
         'anchor', 'background', 'borderwidth',
@@ -217,7 +223,7 @@ class LabelTest(AbstractLabelTest, unittest.TestCase):
     test_configure_justify = StandardOptionsTests.test_configure_justify
 
 
-@add_standard_options(StandardTtkOptionsTests)
+@add_configure_tests(StandardTtkOptionsTests)
 class ButtonTest(AbstractLabelTest, unittest.TestCase):
     OPTIONS = (
         'class', 'command', 'compound', 'cursor', 'default',
@@ -242,7 +248,7 @@ class ButtonTest(AbstractLabelTest, unittest.TestCase):
         self.assertTrue(success)
 
 
-@add_standard_options(StandardTtkOptionsTests)
+@add_configure_tests(StandardTtkOptionsTests)
 class CheckbuttonTest(AbstractLabelTest, unittest.TestCase):
     OPTIONS = (
         'class', 'command', 'compound', 'cursor',
@@ -329,7 +335,7 @@ class CheckbuttonTest(AbstractLabelTest, unittest.TestCase):
         self.assertEqual(len(set(variables)), len(buttons), variables)
 
 
-@add_standard_options(IntegerSizeTests, StandardTtkOptionsTests)
+@add_configure_tests(IntegerSizeTests, StandardTtkOptionsTests)
 class EntryTest(AbstractWidgetTest, unittest.TestCase):
     OPTIONS = (
         'background', 'class', 'cursor',
@@ -339,6 +345,8 @@ class EntryTest(AbstractWidgetTest, unittest.TestCase):
         'show', 'state', 'style', 'takefocus', 'textvariable',
         'validate', 'validatecommand', 'width', 'xscrollcommand',
     )
+    _rounds_pixels = False
+    _clipped = {}
     # bpo-27313: macOS Tk/Tcl may or may not report 'Entry.field'.
     IDENTIFY_AS = {'Entry.field', 'textarea'}
 
@@ -374,7 +382,12 @@ class EntryTest(AbstractWidgetTest, unittest.TestCase):
         self.assertRaises(tkinter.TclError, self.entry.bbox, None)
 
     def test_identify(self):
+        if (tk_version >= (9, 0) and sys.platform == 'darwin'
+                and isinstance(self.entry, ttk.Combobox)):
+            self.skipTest('Test does not work on macOS Tk 9.')
+            # https://core.tcl-lang.org/tk/tktview/8b49e9cfa6
         self.entry.pack()
+        self.root.update()
 
         # Identifying the element under a point requires the widget to be
         # mapped with a real size; the rest of the checks do not.
@@ -455,7 +468,7 @@ class EntryTest(AbstractWidgetTest, unittest.TestCase):
         self.assertEqual(self.entry.state(), ())
 
 
-@add_standard_options(IntegerSizeTests, StandardTtkOptionsTests)
+@add_configure_tests(IntegerSizeTests, StandardTtkOptionsTests)
 class ComboboxTest(EntryTest, unittest.TestCase):
     OPTIONS = (
         'background', 'class', 'cursor', 'exportselection',
@@ -484,11 +497,14 @@ class ComboboxTest(EntryTest, unittest.TestCase):
         x, y = width - 5, 5
         if sys.platform != 'darwin':  # there's no down arrow on macOS
             self.assertRegex(self.combo.identify(x, y), r'.*downarrow\Z')
-        self.combo.event_generate('<ButtonPress-1>', x=x, y=y)
+        self.combo.event_generate('<Button-1>', x=x, y=y)
         self.combo.event_generate('<ButtonRelease-1>', x=x, y=y)
-        self.combo.update_idletasks()
 
     def test_virtual_event(self):
+        if (tk_version >= (9, 0) and sys.platform == 'darwin'
+                and isinstance(self.entry, ttk.Combobox)):
+            self.skipTest('Test does not work on macOS Tk 9.')
+            # https://core.tcl-lang.org/tk/tktview/8b49e9cfa6
         success = []
 
         self.combo['values'] = [1]
@@ -506,6 +522,10 @@ class ComboboxTest(EntryTest, unittest.TestCase):
         self.assertTrue(success)
 
     def test_configure_postcommand(self):
+        if (tk_version >= (9, 0) and sys.platform == 'darwin'
+                and isinstance(self.entry, ttk.Combobox)):
+            self.skipTest('Test does not work on macOS Tk 9.')
+            # https://core.tcl-lang.org/tk/tktview/8b49e9cfa6
         success = []
 
         self.combo['postcommand'] = lambda: success.append(True)
@@ -581,12 +601,14 @@ class ComboboxTest(EntryTest, unittest.TestCase):
         combo2.destroy()
 
 
-@add_standard_options(IntegerSizeTests, StandardTtkOptionsTests)
+@add_configure_tests(IntegerSizeTests, StandardTtkOptionsTests)
 class PanedWindowTest(AbstractWidgetTest, unittest.TestCase):
     OPTIONS = (
         'class', 'cursor', 'height',
         'orient', 'style', 'takefocus', 'width',
     )
+    _rounds_pixels = False
+    _clipped = {}
 
     def setUp(self):
         super().setUp()
@@ -717,7 +739,7 @@ class PanedWindowTest(AbstractWidgetTest, unittest.TestCase):
         self.assertIsInstance(self.paned.sashpos(0), int)
 
 
-@add_standard_options(StandardTtkOptionsTests)
+@add_configure_tests(StandardTtkOptionsTests)
 class RadiobuttonTest(AbstractLabelTest, unittest.TestCase):
     OPTIONS = (
         'class', 'command', 'compound', 'cursor',
@@ -772,7 +794,7 @@ class RadiobuttonTest(AbstractLabelTest, unittest.TestCase):
         self.assertEqual(str(cbtn['variable']), str(cbtn2['variable']))
 
 
-@add_standard_options(StandardTtkOptionsTests)
+@add_configure_tests(StandardTtkOptionsTests)
 class MenubuttonTest(AbstractLabelTest, unittest.TestCase):
     OPTIONS = (
         'class', 'compound', 'cursor', 'direction',
@@ -797,13 +819,14 @@ class MenubuttonTest(AbstractLabelTest, unittest.TestCase):
         menu.destroy()
 
 
-@add_standard_options(StandardTtkOptionsTests)
+@add_configure_tests(StandardTtkOptionsTests)
 class ScaleTest(AbstractWidgetTest, unittest.TestCase):
     OPTIONS = (
         'class', 'command', 'cursor', 'from', 'length',
         'orient', 'state', 'style', 'takefocus', 'to', 'value', 'variable',
     )
-    _conv_pixels = False
+    _rounds_pixels = False
+    _clipped = {}
     default_orient = 'horizontal'
 
     def setUp(self):
@@ -910,7 +933,7 @@ class ScaleTest(AbstractWidgetTest, unittest.TestCase):
         self.assertRaises(tkinter.TclError, self.scale.set, None)
 
 
-@add_standard_options(StandardTtkOptionsTests)
+@add_configure_tests(StandardTtkOptionsTests)
 class ProgressbarTest(AbstractWidgetTest, unittest.TestCase):
     OPTIONS = (
         'anchor', 'class', 'cursor', 'font', 'foreground', 'justify',
@@ -918,7 +941,8 @@ class ProgressbarTest(AbstractWidgetTest, unittest.TestCase):
         'mode', 'maximum', 'phase', 'text', 'wraplength',
         'style', 'takefocus', 'value', 'variable',
     )
-    _conv_pixels = False
+    _rounds_pixels = False
+    _clipped = {}
     _allow_empty_justify = True
     default_orient = 'horizontal'
 
@@ -984,24 +1008,27 @@ class ProgressbarTest(AbstractWidgetTest, unittest.TestCase):
 
 @unittest.skipIf(sys.platform == 'darwin',
                  'ttk.Scrollbar is special on MacOSX')
-@add_standard_options(StandardTtkOptionsTests)
+@add_configure_tests(StandardTtkOptionsTests)
 class ScrollbarTest(AbstractWidgetTest, unittest.TestCase):
     OPTIONS = (
         'class', 'command', 'cursor', 'orient', 'style', 'takefocus',
     )
+    _rounds_pixels = False
+    _clipped = {}
     default_orient = 'vertical'
 
     def create(self, **kwargs):
         return ttk.Scrollbar(self.root, **kwargs)
 
 
-@add_standard_options(StandardTtkOptionsTests)
+@add_configure_tests(StandardTtkOptionsTests)
 class NotebookTest(AbstractWidgetTest, unittest.TestCase):
     OPTIONS = (
         'class', 'cursor', 'height', 'padding', 'style', 'takefocus', 'width',
     )
-    if tk_version >= (8, 7):
-        _conv_pixels = False
+    _rounds_pixels = (tk_version < (9,0))
+    _converts_pixels = False
+    _clipped = {}
 
     def setUp(self):
         super().setUp()
@@ -1019,14 +1046,14 @@ class NotebookTest(AbstractWidgetTest, unittest.TestCase):
         if get_tk_patchlevel(self.root) < (8, 6, 15):
             self.checkIntegerParam(widget, 'height', 402, -402, 0)
         else:
-            self.checkPixelsParam(widget, 'height', '10c', 402, -402, 0, conv=False)
+            self.checkPixelsParam(widget, 'height', '10c', 402, -402, 0)
 
     def test_configure_width(self):
         widget = self.create()
         if get_tk_patchlevel(self.root) < (8, 6, 15):
             self.checkIntegerParam(widget, 'width', 402, -402, 0)
         else:
-            self.checkPixelsParam(widget, 'width', '10c', 402, -402, 0, conv=False)
+            self.checkPixelsParam(widget, 'width', '10c', 402, -402, 0)
 
     def test_tab_identifiers(self):
         self.nb.forget(0)
@@ -1234,7 +1261,7 @@ class NotebookTest(AbstractWidgetTest, unittest.TestCase):
         self.assertEqual(self.nb.select(), str(self.child2))
 
 
-@add_standard_options(IntegerSizeTests, StandardTtkOptionsTests)
+@add_configure_tests(IntegerSizeTests, StandardTtkOptionsTests)
 class SpinboxTest(EntryTest, unittest.TestCase):
     OPTIONS = (
         'background', 'class', 'command', 'cursor', 'exportselection',
@@ -1426,7 +1453,7 @@ class SpinboxTest(EntryTest, unittest.TestCase):
         spin2.destroy()
 
 
-@add_standard_options(StandardTtkOptionsTests)
+@add_configure_tests(StandardTtkOptionsTests)
 class TreeviewTest(AbstractWidgetTest, unittest.TestCase):
     OPTIONS = (
         'class', 'columns', 'cursor', 'displaycolumns',
@@ -1434,6 +1461,8 @@ class TreeviewTest(AbstractWidgetTest, unittest.TestCase):
         'style', 'takefocus', 'titlecolumns', 'titleitems',
         'xscrollcommand', 'yscrollcommand',
     )
+    _rounds_pixels = False
+    _clipped = {}
 
     def setUp(self):
         super().setUp()
@@ -1469,8 +1498,10 @@ class TreeviewTest(AbstractWidgetTest, unittest.TestCase):
 
     def test_configure_height(self):
         widget = self.create()
-        self.checkPixelsParam(widget, 'height', 100, -100, 0, '3c', conv=False)
-        self.checkPixelsParam(widget, 'height', 101.2, 102.6, conv=False)
+        self.checkPixelsParam(widget, 'height', 100, -100, 0, '3c',
+                                  conv=False)
+        self.checkPixelsParam(widget, 'height', 101.2, 102.6, '3c',
+                                  conv=False)
 
     def test_configure_selectmode(self):
         widget = self.create()
@@ -2086,24 +2117,28 @@ class TreeviewTest(AbstractWidgetTest, unittest.TestCase):
         self.assertEqual(self.tv.tag_has('tag3'), ())
 
 
-@add_standard_options(StandardTtkOptionsTests)
+@add_configure_tests(StandardTtkOptionsTests)
 class SeparatorTest(AbstractWidgetTest, unittest.TestCase):
     OPTIONS = (
         'class', 'cursor', 'orient', 'style', 'takefocus',
         # 'state'?
     )
+    _rounds_pixels = False
+    _clipped = {}
     default_orient = 'horizontal'
 
     def create(self, **kwargs):
         return ttk.Separator(self.root, **kwargs)
 
 
-@add_standard_options(StandardTtkOptionsTests)
+@add_configure_tests(StandardTtkOptionsTests)
 class SizegripTest(AbstractWidgetTest, unittest.TestCase):
     OPTIONS = (
         'class', 'cursor', 'style', 'takefocus',
         # 'state'?
     )
+    _rounds_pixels = False
+    _clipped = {}
 
     def create(self, **kwargs):
         return ttk.Sizegrip(self.root, **kwargs)