Skip to content

bpo-42681: Fix test_curses failures related to COLOR_PAIRS #24089

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 41 additions & 12 deletions Lib/test/test_curses.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
60 changes: 40 additions & 20 deletions Modules/_cursesmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down