8000 bpo-42681: Fix test_curses failures related to color pairs (GH-24089) · python/cpython@59f9b4e · GitHub
[go: up one dir, main page]

Skip to content
8000

Commit 59f9b4e

Browse files
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.
1 parent 27f9daf commit 59f9b4e

File tree

2 files changed

+81
-32
lines changed

2 files changed

+81
-32
lines changed

Lib/test/test_curses.py

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def wrapped(self, *args, **kwargs):
4747
return wrapped
4848

4949
term = os.environ.get('TERM')
50+
SHORT_MAX = 0x7fff
5051

5152
# If newterm was supported we could use it instead of initscr and not exit
5253
@unittest.skipIf(not term or term == 'unknown',
@@ -327,11 +328,20 @@ def bad_colors2(self):
327328
def bad_pairs(self):
328329
return (-1, -2**31 - 1, 2**31, -2**63 - 1, 2**63, 2**64)
329330

331+
def test_start_color(self):
332+
if not curses.has_colors():
333+
self.skipTest('requires colors support')
334+
curses.start_color()
335+
if verbose:
336+
print(f'COLORS = {curses.COLORS}', file=sys.stderr)
337+
print(f'COLOR_PAIRS = {curses.COLOR_PAIRS}', file=sys.stderr)
338+
330339
@requires_colors
331340
def test_color_content(self):
332341
self.assertEqual(curses.color_content(curses.COLOR_BLACK), (0, 0, 0))
333342
curses.color_content(0)
334-
curses.color_content(curses.COLORS - 1)
343+
maxcolor = curses.COLORS - 1
344+
curses.color_content(maxcolor)
335345

336346
for color in self.bad_colors():
337347
self.assertRaises(ValueError, curses.color_content, color)
@@ -352,11 +362,12 @@ def test_init_color(self):
352362
curses.init_color(0, 1000, 1000, 1000)
353363
self.assertEqual(curses.color_content(0), (1000, 1000, 1000))
354364

355-
old = curses.color_content(curses.COLORS - 1)
356-
curses.init_color(curses.COLORS - 1, *old)
357-
self.addCleanup(curses.init_color, curses.COLORS - 1, *old)
358-
curses.init_color(curses.COLORS - 1, 0, 500, 1000)
359-
self.assertEqual(curses.color_content(curses.COLORS - 1), (0, 500, 1000))
365+
maxcolor = curses.COLORS - 1
366+
old = curses.color_content(maxcolor)
367+
curses.init_color(maxcolor, *old)
368+
self.addCleanup(curses.init_color, maxcolor, *old)
369+
curses.init_color(maxcolor, 0, 500, 1000)
370+
self.assertEqual(curses.color_content(maxcolor), (0, 500, 1000))
360371

361372
for color in self.bad_colors():
362373
self.assertRaises(ValueError, curses.init_color, color, 0, 0, 0)
@@ -365,13 +376,25 @@ def test_init_color(self):
365376
self.assertRaises(ValueError, curses.init_color, 0, 0, comp, 0)
366377
self.assertRaises(ValueError, curses.init_color, 0, 0, 0, comp)
367378

379+
def get_pair_limit(self):
380+
pair_limit = curses.COLOR_PAIRS
381+
if hasattr(curses, 'ncurses_version'):
382+
if curses.has_extended_color_support():
383+
pair_limit += 2*curses.COLORS + 1
384+
if (not curses.has_extended_color_support()
385+
or (6, 1) <= curses.ncurses_version < (6, 2)):
386+
pair_limit = min(pair_limit, SHORT_MAX)
387+
return pair_limit
388+
368389
@requires_colors
369390
def test_pair_content(self):
370391
if not hasattr(curses, 'use_default_colors'):
371392
self.assertEqual(curses.pair_content(0),
372393
(curses.COLOR_WHITE, curses.COLOR_BLACK))
373394
curses.pair_content(0)
374-
curses.pair_content(curses.COLOR_PAIRS - 1)
395+
maxpair = self.get_pair_limit() - 1
396+
if maxpair > 0:
397+
curses.pair_content(maxpair)
375398

376399
for pair in self.bad_pairs():
377400
self.assertRaises(ValueError, curses.pair_content, pair)
@@ -384,11 +407,15 @@ def test_init_pair(self):
384407

385408
curses.init_pair(1, 0, 0)
386409
self.assertEqual(curses.pair_content(1), (0, 0))
387-
curses.init_pair(1, curses.COLORS - 1, curses.COLORS - 1)
388-
self.assertEqual(curses.pair_content(1),
389-
(curses.COLORS - 1, curses.COLORS - 1))
390-
curses.init_pair(curses.COLOR_PAIRS - 1, 2, 3)
391-
self.assertEqual(curses.pair_content(curses.COLOR_PAIRS - 1), (2, 3))
410+
maxcolor = curses.COLORS - 1
411+
curses.init_pair(1, maxcolor, 0)
412+
self.assertEqual(curses.pair_content(1), (maxcolor, 0))
413+
curses.init_pair(1, 0, maxcolor)
414+
self.assertEqual(curses.pair_content(1), (0, maxcolor))
415+
maxpair = self.get_pair_limit() - 1
416+
if maxpair > 1:
417+
curses.init_pair(maxpair, 0, 0)
418+
self.assertEqual(curses.pair_content(maxpair), (0, 0))
392419

393420
for pair in self.bad_pairs():
394421
self.assertRaises(ValueError, curses.init_pair, pair, 0, 0)
@@ -582,6 +609,8 @@ def test_update_lines_cols(self):
582609
@requires_curses_func('ncurses_version')
583610
def test_ncurses_version(self):
584611
v = curses.ncurses_version
612+
if verbose:
613+
print(f'ncurses_version = {curses.ncurses_version}', flush=True)
585614
self.assertIsInstance(v[:], tuple)
586615
self.assertEqual(len(v), 3)
587616
self.assertIsInstance(v[0], int)

Modules/_cursesmodule.c

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -135,29 +135,28 @@ typedef chtype attr_t; /* No attr_t type is available */
135135
#define STRICT_SYSV_CURSES
136136
#endif
137137

138-
#if defined(NCURSES_EXT_COLORS) && defined(NCURSES_EXT_FUNCS)
138+
#if NCURSES_EXT_COLORS+0 && NCURSES_EXT_FUNCS+0
139139
#define _NCURSES_EXTENDED_COLOR_FUNCS 1
140140
#else
141141
#define _NCURSES_EXTENDED_COLOR_FUNCS 0
142142
#endif /* defined(NCURSES_EXT_COLORS) && defined(NCURSES_EXT_FUNCS) */
143143

144144
#if _NCURSES_EXTENDED_COLOR_FUNCS
145-
#define _NCURSES_COLOR_VAL_TYPE int
145+
#define _CURSES_COLOR_VAL_TYPE int
146+
#define _CURSES_COLOR_NUM_TYPE int
146147
#define _CURSES_INIT_COLOR_FUNC init_extended_color
147148
#define _CURSES_INIT_PAIR_FUNC init_extended_pair
148149
#define _COLOR_CONTENT_FUNC extended_color_content
149-
#define _CURSES_PAIR_NUMBER_FUNC extended_pair_content
150+
#define _CURSES_PAIR_CONTENT_FUNC extended_pair_content
150151
#else
151-
#define _NCURSES_COLOR_VAL_TYPE short
152+
#define _CURSES_COLOR_VAL_TYPE short
153+
#define _CURSES_COLOR_NUM_TYPE short
152154
#define _CURSES_INIT_COLOR_FUNC init_color
153155
#define _CURSES_INIT_PAIR_FUNC init_pair
1541 10000 56
#define _COLOR_CONTENT_FUNC color_content
155-
#define _CURSES_PAIR_NUMBER_FUNC pair_content
157+
#define _CURSES_PAIR_CONTENT_FUNC pair_content
156158
#endif /* _NCURSES_EXTENDED_COLOR_FUNCS */
157159

158-
#define _CURSES_INIT_COLOR_FUNC_NAME Py_STRINGIFY(_CURSES_INIT_COLOR_FUNC)
159-
#define _CURSES_INIT_PAIR_FUNC_NAME Py_STRINGIFY(_CURSES_INIT_PAIR_FUNC)
160-
161160
/*[clinic input]
162161
module _curses
163162
class _curses.window "PyCursesWindowObject *" "&PyCursesWindow_Type"
@@ -2737,18 +2736,18 @@ static PyObject *
27372736
_curses_color_content_impl(PyObject *module, int color_number)
27382737
/*[clinic end generated code: output=17b466df7054e0de input=03b5ed0472662aea]*/
27392738
{
2740-
_NCURSES_COLOR_VAL_TYPE r,g,b;
2739+
_CURSES_COLOR_VAL_TYPE r,g,b;
27412740

27422741
PyCursesInitialised;
27432742
PyCursesInitialisedColor;
27442743

2745-
if (_COLOR_CONTENT_FUNC(color_number, &r, &g, &b) != ERR)
2746-
return Py_BuildValue("(iii)", r, g, b);
2747-
else {
2748-
PyErr_SetString(PyCursesError,
2749-
"Argument 1 was out of range. Check value of COLORS.");
2744+
if (_COLOR_CONTENT_FUNC(color_number, &r, &g, &b) == ERR) {
2745+
PyErr_Format(PyCursesError, "%s() returned ERR",
2746+
Py_STRINGIFY(_COLOR_CONTENT_FUNC));
27502747
return NULL;
27512748
}
2749+
2750+
return Py_BuildValue("(iii)", r, g, b);
27522751
}
27532752

27542753
/*[clinic input]
@@ -3190,7 +3189,8 @@ _curses_init_color_impl(PyObject *module, int color_number, short r, short g,
31903189
PyCursesInitialised;
31913190
PyCursesInitialisedColor;
31923191

3193-
return PyCursesCheckERR(_CURSES_INIT_COLOR_FUNC(color_number, r, g, b), _CURSES_INIT_COLOR_FUNC_NAME);
3192+
return PyCursesCheckERR(_CURSES_INIT_COLOR_FUNC(color_number, r, g, b),
3193+
Py_STRINGIFY(_CURSES_INIT_COLOR_FUNC));
31943194
}
31953195

31963196
/*[clinic input]
@@ -3217,7 +3217,20 @@ _curses_init_pair_impl(PyObject *module, int pair_number, int fg, int bg)
32173217
PyCursesInitialised;
32183218
PyCursesInitialisedColor;
32193219

3220-
return PyCursesCheckERR(_CURSES_INIT_PAIR_FUNC(pair_number, fg, bg), _CURSES_INIT_PAIR_FUNC_NAME);
3220+
if (_CURSES_INIT_PAIR_FUNC(pair_number, fg, bg) == ERR) {
3221+
if (pair_number >= COLOR_PAIRS) {
3222+
PyErr_Format(PyExc_ValueError,
3223+
"Color pair is greater than COLOR_PAIRS-1 (%d).",
3224+
COLOR_PAIRS - 1);
3225+
}
3226+
else {
3227+
PyErr_Format(PyCursesError, "%s() returned ERR",
3228+
Py_STRINGIFY(_CURSES_INIT_PAIR_FUNC));
3229+
}
3230+
return NULL;
3231+
}
3232+
3233+
Py_RETURN_NONE;
32213234
}
32223235

32233236
static PyObject *ModDict;
@@ -3845,14 +3858,21 @@ static PyObject *
38453858
_curses_pair_content_impl(PyObject *module, int pair_number)
38463859
/*[clinic end generated code: output=4a726dd0e6885f3f input=03970f840fc7b739]*/
38473860
{
3848-
_NCURSES_COLOR_VAL_TYPE f, b;
3861+
_CURSES_COLOR_NUM_TYPE f, b;
38493862

38503863
PyCursesInitialised;
38513864
PyCursesInitialisedColor;
38523865

3853-
if (_CURSES_PAIR_NUMBER_FUNC(pair_number, &f, &b)==ERR) {
3854-
PyErr_SetString(PyCursesError,
3855-
"Argument 1 was out of range. (0..COLOR_PAIRS-1)");
3866+
if (_CURSES_PAIR_CONTENT_FUNC(pair_number, &f, &b) == ERR) {
3867+
if (pair_number >= COLOR_PAIRS) {
3868+
PyErr_Format(PyExc_ValueError,
3869+
"Color pair is greater than COLOR_PAIRS-1 (%d).",
3870+
COLOR_PAIRS - 1);
3871+
}
3872+
else {
3873+
PyErr_Format(PyCursesError, "%s() returned ERR",
3874+
Py_STRINGIFY(_CURSES_PAIR_CONTENT_FUNC));
3875+
}
38563876
return NULL;
38573877
}
38583878

0 commit comments

Comments
 (0)
0