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.
This commit is contained in:
Serhiy Storchaka 2021-01-05 09:13:15 +02:00 committed by GitHub
parent 27f9dafc2b
commit 59f9b4e450
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 81 additions and 32 deletions

View File

@ -47,6 +47,7 @@ def requires_colors(test):
return wrapped return wrapped
term = os.environ.get('TERM') term = os.environ.get('TERM')
SHORT_MAX = 0x7fff
# If newterm was supported we could use it instead of initscr and not exit # If newterm was supported we could use it instead of initscr and not exit
@unittest.skipIf(not term or term == 'unknown', @unittest.skipIf(not term or term == 'unknown',
@ -327,11 +328,20 @@ class TestCurses(unittest.TestCase):
def bad_pairs(self): def bad_pairs(self):
return (-1, -2**31 - 1, 2**31, -2**63 - 1, 2**63, 2**64) 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 @requires_colors
def test_color_content(self): def test_color_content(self):
self.assertEqual(curses.color_content(curses.COLOR_BLACK), (0, 0, 0)) self.assertEqual(curses.color_content(curses.COLOR_BLACK), (0, 0, 0))
curses.color_content(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(): for color in self.bad_colors():
self.assertRaises(ValueError, curses.color_content, color) self.assertRaises(ValueError, curses.color_content, color)
@ -352,11 +362,12 @@ class TestCurses(unittest.TestCase):
curses.init_color(0, 1000, 1000, 1000) curses.init_color(0, 1000, 1000, 1000)
self.assertEqual(curses.color_content(0), (1000, 1000, 1000)) self.assertEqual(curses.color_content(0), (1000, 1000, 1000))
old = curses.color_content(curses.COLORS - 1) maxcolor = curses.COLORS - 1
curses.init_color(curses.COLORS - 1, *old) old = curses.color_content(maxcolor)
self.addCleanup(curses.init_color, curses.COLORS - 1, *old) curses.init_color(maxcolor, *old)
curses.init_color(curses.COLORS - 1, 0, 500, 1000) self.addCleanup(curses.init_color, maxcolor, *old)
self.assertEqual(curses.color_content(curses.COLORS - 1), (0, 500, 1000)) curses.init_color(maxcolor, 0, 500, 1000)
self.assertEqual(curses.color_content(maxcolor), (0, 500, 1000))
for color in self.bad_colors(): for color in self.bad_colors():
self.assertRaises(ValueError, curses.init_color, color, 0, 0, 0) self.assertRaises(ValueError, curses.init_color, color, 0, 0, 0)
@ -365,13 +376,25 @@ class TestCurses(unittest.TestCase):
self.assertRaises(ValueError, curses.init_color, 0, 0, comp, 0) self.assertRaises(ValueError, curses.init_color, 0, 0, comp, 0)
self.assertRaises(ValueError, curses.init_color, 0, 0, 0, comp) 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 @requires_colors
def test_pair_content(self): def test_pair_content(self):
if not hasattr(curses, 'use_default_colors'): if not hasattr(curses, 'use_default_colors'):
self.assertEqual(curses.pair_content(0), self.assertEqual(curses.pair_content(0),
(curses.COLOR_WHITE, curses.COLOR_BLACK)) (curses.COLOR_WHITE, curses.COLOR_BLACK))
curses.pair_content(0) 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(): for pair in self.bad_pairs():
self.assertRaises(ValueError, curses.pair_content, pair) self.assertRaises(ValueError, curses.pair_content, pair)
@ -384,11 +407,15 @@ class TestCurses(unittest.TestCase):
curses.init_pair(1, 0, 0) curses.init_pair(1, 0, 0)
self.assertEqual(curses.pair_content(1), (0, 0)) self.assertEqual(curses.pair_content(1), (0, 0))
curses.init_pair(1, curses.COLORS - 1, curses.COLORS - 1) maxcolor = curses.COLORS - 1
self.assertEqual(curses.pair_content(1), curses.init_pair(1, maxcolor, 0)
(curses.COLORS - 1, curses.COLORS - 1)) self.assertEqual(curses.pair_content(1), (maxcolor, 0))
curses.init_pair(curses.COLOR_PAIRS - 1, 2, 3) curses.init_pair(1, 0, maxcolor)
self.assertEqual(curses.pair_content(curses.COLOR_PAIRS - 1), (2, 3)) 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(): for pair in self.bad_pairs():
self.assertRaises(ValueError, curses.init_pair, pair, 0, 0) self.assertRaises(ValueError, curses.init_pair, pair, 0, 0)
@ -582,6 +609,8 @@ class MiscTests(unittest.TestCase):
@requires_curses_func('ncurses_version') @requires_curses_func('ncurses_version')
def test_ncurses_version(self): def test_ncurses_version(self):
v = curses.ncurses_version v = curses.ncurses_version
if verbose:
print(f'ncurses_version = {curses.ncurses_version}', flush=True)
self.assertIsInstance(v[:], tuple) self.assertIsInstance(v[:], tuple)
self.assertEqual(len(v), 3) self.assertEqual(len(v), 3)
self.assertIsInstance(v[0], int) self.assertIsInstance(v[0], int)

View File

@ -135,29 +135,28 @@ typedef chtype attr_t; /* No attr_t type is available */
#define STRICT_SYSV_CURSES #define STRICT_SYSV_CURSES
#endif #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 #define _NCURSES_EXTENDED_COLOR_FUNCS 1
#else #else
#define _NCURSES_EXTENDED_COLOR_FUNCS 0 #define _NCURSES_EXTENDED_COLOR_FUNCS 0
#endif /* defined(NCURSES_EXT_COLORS) && defined(NCURSES_EXT_FUNCS) */ #endif /* defined(NCURSES_EXT_COLORS) && defined(NCURSES_EXT_FUNCS) */
#if _NCURSES_EXTENDED_COLOR_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_COLOR_FUNC init_extended_color
#define _CURSES_INIT_PAIR_FUNC init_extended_pair #define _CURSES_INIT_PAIR_FUNC init_extended_pair
#define _COLOR_CONTENT_FUNC extended_color_content #define _COLOR_CONTENT_FUNC extended_color_content
#define _CURSES_PAIR_NUMBER_FUNC extended_pair_content #define _CURSES_PAIR_CONTENT_FUNC extended_pair_content
#else #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_COLOR_FUNC init_color
#define _CURSES_INIT_PAIR_FUNC init_pair #define _CURSES_INIT_PAIR_FUNC init_pair
#define _COLOR_CONTENT_FUNC color_content #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 */ #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] /*[clinic input]
module _curses module _curses
class _curses.window "PyCursesWindowObject *" "&PyCursesWindow_Type" class _curses.window "PyCursesWindowObject *" "&PyCursesWindow_Type"
@ -2737,18 +2736,18 @@ static PyObject *
_curses_color_content_impl(PyObject *module, int color_number) _curses_color_content_impl(PyObject *module, int color_number)
/*[clinic end generated code: output=17b466df7054e0de input=03b5ed0472662aea]*/ /*[clinic end generated code: output=17b466df7054e0de input=03b5ed0472662aea]*/
{ {
_NCURSES_COLOR_VAL_TYPE r,g,b; _CURSES_COLOR_VAL_TYPE r,g,b;
PyCursesInitialised; PyCursesInitialised;
PyCursesInitialisedColor; PyCursesInitialisedColor;
if (_COLOR_CONTENT_FUNC(color_number, &r, &g, &b) != ERR) if (_COLOR_CONTENT_FUNC(color_number, &r, &g, &b) == ERR) {
return Py_BuildValue("(iii)", r, g, b); PyErr_Format(PyCursesError, "%s() returned ERR",
else { Py_STRINGIFY(_COLOR_CONTENT_FUNC));
PyErr_SetString(PyCursesError,
"Argument 1 was out of range. Check value of COLORS.");
return NULL; return NULL;
} }
return Py_BuildValue("(iii)", r, g, b);
} }
/*[clinic input] /*[clinic input]
@ -3190,7 +3189,8 @@ _curses_init_color_impl(PyObject *module, int color_number, short r, short g,
PyCursesInitialised; PyCursesInitialised;
PyCursesInitialisedColor; 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] /*[clinic input]
@ -3217,7 +3217,20 @@ _curses_init_pair_impl(PyObject *module, int pair_number, int fg, int bg)
PyCursesInitialised; PyCursesInitialised;
PyCursesInitialisedColor; 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; static PyObject *ModDict;
@ -3845,14 +3858,21 @@ static PyObject *
_curses_pair_content_impl(PyObject *module, int pair_number) _curses_pair_content_impl(PyObject *module, int pair_number)
/*[clinic end generated code: output=4a726dd0e6885f3f input=03970f840fc7b739]*/ /*[clinic end generated code: output=4a726dd0e6885f3f input=03970f840fc7b739]*/
{ {
_NCURSES_COLOR_VAL_TYPE f, b; _CURSES_COLOR_NUM_TYPE f, b;
PyCursesInitialised; PyCursesInitialised;
PyCursesInitialisedColor; PyCursesInitialisedColor;
if (_CURSES_PAIR_NUMBER_FUNC(pair_number, &f, &b)==ERR) { if (_CURSES_PAIR_CONTENT_FUNC(pair_number, &f, &b) == ERR) {
PyErr_SetString(PyCursesError, if (pair_number >= COLOR_PAIRS) {
"Argument 1 was out of range. (0..COLOR_PAIRS-1)"); 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; return NULL;
} }