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

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

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
154156
#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