[Python-checkins] bpo-42681: Fix test_curses failures related to color pairs (GH-24089)

serhiy-storchaka webhook-mailer at python.org
Tue Jan 5 02:13:23 EST 2021


https://github.com/python/cpython/commit/59f9b4e4509be67494f3d45489fa55523175ff69
commit: 59f9b4e4509be67494f3d45489fa55523175ff69
branch: master
author: Serhiy Storchaka <storchaka at gmail.com>
committer: serhiy-storchaka <storchaka at gmail.com>
date: 2021-01-05T09:13:15+02:00
summary:

bpo-42681: Fix test_curses failures related to color pairs (GH-24089)

On ncurses 6.1 pair numbers are limited by SHORT_MAX-1, even
with extended color support.

Improve error reporting and tests for color functions.

files:
M Lib/test/test_curses.py
M Modules/_cursesmodule.c

diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py
index 7b40d71474210..f2cad05b2c92e 100644
--- a/Lib/test/test_curses.py
+++ b/Lib/test/test_curses.py
@@ -47,6 +47,7 @@ def wrapped(self, *args, **kwargs):
     return wrapped
 
 term = os.environ.get('TERM')
+SHORT_MAX = 0x7fff
 
 # If newterm was supported we could use it instead of initscr and not exit
 @unittest.skipIf(not term or term == 'unknown',
@@ -327,11 +328,20 @@ def bad_colors2(self):
     def bad_pairs(self):
         return (-1, -2**31 - 1, 2**31, -2**63 - 1, 2**63, 2**64)
 
+    def test_start_color(self):
+        if not curses.has_colors():
+            self.skipTest('requires colors support')
+        curses.start_color()
+        if verbose:
+            print(f'COLORS = {curses.COLORS}', file=sys.stderr)
+            print(f'COLOR_PAIRS = {curses.COLOR_PAIRS}', file=sys.stderr)
+
     @requires_colors
     def test_color_content(self):
         self.assertEqual(curses.color_content(curses.COLOR_BLACK), (0, 0, 0))
         curses.color_content(0)
-        curses.color_content(curses.COLORS - 1)
+        maxcolor = curses.COLORS - 1
+        curses.color_content(maxcolor)
 
         for color in self.bad_colors():
             self.assertRaises(ValueError, curses.color_content, color)
@@ -352,11 +362,12 @@ def test_init_color(self):
         curses.init_color(0, 1000, 1000, 1000)
         self.assertEqual(curses.color_content(0), (1000, 1000, 1000))
 
-        old = curses.color_content(curses.COLORS - 1)
-        curses.init_color(curses.COLORS - 1, *old)
-        self.addCleanup(curses.init_color, curses.COLORS - 1, *old)
-        curses.init_color(curses.COLORS - 1, 0, 500, 1000)
-        self.assertEqual(curses.color_content(curses.COLORS - 1), (0, 500, 1000))
+        maxcolor = curses.COLORS - 1
+        old = curses.color_content(maxcolor)
+        curses.init_color(maxcolor, *old)
+        self.addCleanup(curses.init_color, maxcolor, *old)
+        curses.init_color(maxcolor, 0, 500, 1000)
+        self.assertEqual(curses.color_content(maxcolor), (0, 500, 1000))
 
         for color in self.bad_colors():
             self.assertRaises(ValueError, curses.init_color, color, 0, 0, 0)
@@ -365,13 +376,25 @@ def test_init_color(self):
             self.assertRaises(ValueError, curses.init_color, 0, 0, comp, 0)
             self.assertRaises(ValueError, curses.init_color, 0, 0, 0, comp)
 
+    def get_pair_limit(self):
+        pair_limit = curses.COLOR_PAIRS
+        if hasattr(curses, 'ncurses_version'):
+            if curses.has_extended_color_support():
+                pair_limit += 2*curses.COLORS + 1
+            if (not curses.has_extended_color_support()
+                    or (6, 1) <= curses.ncurses_version < (6, 2)):
+                pair_limit = min(pair_limit, SHORT_MAX)
+        return pair_limit
+
     @requires_colors
     def test_pair_content(self):
         if not hasattr(curses, 'use_default_colors'):
             self.assertEqual(curses.pair_content(0),
                              (curses.COLOR_WHITE, curses.COLOR_BLACK))
         curses.pair_content(0)
-        curses.pair_content(curses.COLOR_PAIRS - 1)
+        maxpair = self.get_pair_limit() - 1
+        if maxpair > 0:
+            curses.pair_content(maxpair)
 
         for pair in self.bad_pairs():
             self.assertRaises(ValueError, curses.pair_content, pair)
@@ -384,11 +407,15 @@ def test_init_pair(self):
 
         curses.init_pair(1, 0, 0)
         self.assertEqual(curses.pair_content(1), (0, 0))
-        curses.init_pair(1, curses.COLORS - 1, curses.COLORS - 1)
-        self.assertEqual(curses.pair_content(1),
-                         (curses.COLORS - 1, curses.COLORS - 1))
-        curses.init_pair(curses.COLOR_PAIRS - 1, 2, 3)
-        self.assertEqual(curses.pair_content(curses.COLOR_PAIRS - 1), (2, 3))
+        maxcolor = curses.COLORS - 1
+        curses.init_pair(1, maxcolor, 0)
+        self.assertEqual(curses.pair_content(1), (maxcolor, 0))
+        curses.init_pair(1, 0, maxcolor)
+        self.assertEqual(curses.pair_content(1), (0, maxcolor))
+        maxpair = self.get_pair_limit() - 1
+        if maxpair > 1:
+            curses.init_pair(maxpair, 0, 0)
+            self.assertEqual(curses.pair_content(maxpair), (0, 0))
 
         for pair in self.bad_pairs():
             self.assertRaises(ValueError, curses.init_pair, pair, 0, 0)
@@ -582,6 +609,8 @@ def test_update_lines_cols(self):
     @requires_curses_func('ncurses_version')
     def test_ncurses_version(self):
         v = curses.ncurses_version
+        if verbose:
+            print(f'ncurses_version = {curses.ncurses_version}', flush=True)
         self.assertIsInstance(v[:], tuple)
         self.assertEqual(len(v), 3)
         self.assertIsInstance(v[0], int)
diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c
index 23f6d96f5144e..7175c72296579 100644
--- a/Modules/_cursesmodule.c
+++ b/Modules/_cursesmodule.c
@@ -135,29 +135,28 @@ typedef chtype attr_t;           /* No attr_t type is available */
 #define STRICT_SYSV_CURSES
 #endif
 
-#if defined(NCURSES_EXT_COLORS) && defined(NCURSES_EXT_FUNCS)
+#if NCURSES_EXT_COLORS+0 && NCURSES_EXT_FUNCS+0
 #define _NCURSES_EXTENDED_COLOR_FUNCS   1
 #else
 #define _NCURSES_EXTENDED_COLOR_FUNCS   0
 #endif  /* defined(NCURSES_EXT_COLORS) && defined(NCURSES_EXT_FUNCS)  */
 
 #if _NCURSES_EXTENDED_COLOR_FUNCS
-#define _NCURSES_COLOR_VAL_TYPE         int
+#define _CURSES_COLOR_VAL_TYPE          int
+#define _CURSES_COLOR_NUM_TYPE          int
 #define _CURSES_INIT_COLOR_FUNC         init_extended_color
 #define _CURSES_INIT_PAIR_FUNC          init_extended_pair
 #define _COLOR_CONTENT_FUNC             extended_color_content
-#define _CURSES_PAIR_NUMBER_FUNC        extended_pair_content
+#define _CURSES_PAIR_CONTENT_FUNC       extended_pair_content
 #else
-#define _NCURSES_COLOR_VAL_TYPE         short
+#define _CURSES_COLOR_VAL_TYPE          short
+#define _CURSES_COLOR_NUM_TYPE          short
 #define _CURSES_INIT_COLOR_FUNC         init_color
 #define _CURSES_INIT_PAIR_FUNC          init_pair
 #define _COLOR_CONTENT_FUNC             color_content
-#define _CURSES_PAIR_NUMBER_FUNC        pair_content
+#define _CURSES_PAIR_CONTENT_FUNC       pair_content
 #endif  /* _NCURSES_EXTENDED_COLOR_FUNCS */
 
-#define _CURSES_INIT_COLOR_FUNC_NAME    Py_STRINGIFY(_CURSES_INIT_COLOR_FUNC)
-#define _CURSES_INIT_PAIR_FUNC_NAME     Py_STRINGIFY(_CURSES_INIT_PAIR_FUNC)
-
 /*[clinic input]
 module _curses
 class _curses.window "PyCursesWindowObject *" "&PyCursesWindow_Type"
@@ -2737,18 +2736,18 @@ static PyObject *
 _curses_color_content_impl(PyObject *module, int color_number)
 /*[clinic end generated code: output=17b466df7054e0de input=03b5ed0472662aea]*/
 {
-    _NCURSES_COLOR_VAL_TYPE r,g,b;
+    _CURSES_COLOR_VAL_TYPE r,g,b;
 
     PyCursesInitialised;
     PyCursesInitialisedColor;
 
-    if (_COLOR_CONTENT_FUNC(color_number, &r, &g, &b) != ERR)
-        return Py_BuildValue("(iii)", r, g, b);
-    else {
-        PyErr_SetString(PyCursesError,
-                        "Argument 1 was out of range. Check value of COLORS.");
+    if (_COLOR_CONTENT_FUNC(color_number, &r, &g, &b) == ERR) {
+        PyErr_Format(PyCursesError, "%s() returned ERR",
+                        Py_STRINGIFY(_COLOR_CONTENT_FUNC));
         return NULL;
     }
+
+    return Py_BuildValue("(iii)", r, g, b);
 }
 
 /*[clinic input]
@@ -3190,7 +3189,8 @@ _curses_init_color_impl(PyObject *module, int color_number, short r, short g,
     PyCursesInitialised;
     PyCursesInitialisedColor;
 
-    return PyCursesCheckERR(_CURSES_INIT_COLOR_FUNC(color_number, r, g, b), _CURSES_INIT_COLOR_FUNC_NAME);
+    return PyCursesCheckERR(_CURSES_INIT_COLOR_FUNC(color_number, r, g, b),
+                            Py_STRINGIFY(_CURSES_INIT_COLOR_FUNC));
 }
 
 /*[clinic input]
@@ -3217,7 +3217,20 @@ _curses_init_pair_impl(PyObject *module, int pair_number, int fg, int bg)
     PyCursesInitialised;
     PyCursesInitialisedColor;
 
-    return PyCursesCheckERR(_CURSES_INIT_PAIR_FUNC(pair_number, fg, bg), _CURSES_INIT_PAIR_FUNC_NAME);
+    if (_CURSES_INIT_PAIR_FUNC(pair_number, fg, bg) == ERR) {
+        if (pair_number >= COLOR_PAIRS) {
+            PyErr_Format(PyExc_ValueError,
+                         "Color pair is greater than COLOR_PAIRS-1 (%d).",
+                         COLOR_PAIRS - 1);
+        }
+        else {
+            PyErr_Format(PyCursesError, "%s() returned ERR",
+                         Py_STRINGIFY(_CURSES_INIT_PAIR_FUNC));
+        }
+        return NULL;
+    }
+
+    Py_RETURN_NONE;
 }
 
 static PyObject *ModDict;
@@ -3845,14 +3858,21 @@ static PyObject *
 _curses_pair_content_impl(PyObject *module, int pair_number)
 /*[clinic end generated code: output=4a726dd0e6885f3f input=03970f840fc7b739]*/
 {
-    _NCURSES_COLOR_VAL_TYPE f, b;
+    _CURSES_COLOR_NUM_TYPE f, b;
 
     PyCursesInitialised;
     PyCursesInitialisedColor;
 
-    if (_CURSES_PAIR_NUMBER_FUNC(pair_number, &f, &b)==ERR) {
-        PyErr_SetString(PyCursesError,
-                        "Argument 1 was out of range. (0..COLOR_PAIRS-1)");
+    if (_CURSES_PAIR_CONTENT_FUNC(pair_number, &f, &b) == ERR) {
+        if (pair_number >= COLOR_PAIRS) {
+            PyErr_Format(PyExc_ValueError,
+                         "Color pair is greater than COLOR_PAIRS-1 (%d).",
+                         COLOR_PAIRS - 1);
+        }
+        else {
+            PyErr_Format(PyCursesError, "%s() returned ERR",
+                         Py_STRINGIFY(_CURSES_PAIR_CONTENT_FUNC));
+        }
         return NULL;
     }
 



More information about the Python-checkins mailing list