Compare commits

..

No commits in common. "b0ee2b492dbf550fbd2a63b82de0a4dc9d67f32e" and "bfc413ce4fa37ccb889757388102c7755e057bf5" have entirely different histories.

5 changed files with 98 additions and 218 deletions

View File

@ -112,15 +112,14 @@ The module :mod:`curses` defines the following functions:
.. function:: color_content(color_number)
Return the intensity of the red, green, and blue (RGB) components in the color
*color_number*, which must be between ``0`` and ``COLORS - 1``. Return a 3-tuple,
*color_number*, which must be between ``0`` and :const:`COLORS`. Return a 3-tuple,
containing the R,G,B values for the given color, which will be between
``0`` (no component) and ``1000`` (maximum amount of component).
.. function:: color_pair(pair_number)
.. function:: color_pair(color_number)
Return the attribute value for displaying text in the specified color pair.
Only the first 256 color pairs are supported. This
Return the attribute value for displaying text in the specified color. This
attribute value can be combined with :const:`A_STANDOUT`, :const:`A_REVERSE`,
and the other :const:`A_\*` attributes. :func:`pair_number` is the counterpart
to this function.
@ -279,7 +278,7 @@ The module :mod:`curses` defines the following functions:
Change the definition of a color, taking the number of the color to be changed
followed by three RGB values (for the amounts of red, green, and blue
components). The value of *color_number* must be between ``0`` and
`COLORS - 1`. Each of *r*, *g*, *b*, must be a value between ``0`` and
:const:`COLORS`. Each of *r*, *g*, *b*, must be a value between ``0`` and
``1000``. When :func:`init_color` is used, all occurrences of that color on the
screen immediately change to the new definition. This function is a no-op on
most terminals; it is active only if :func:`can_change_color` returns ``True``.
@ -292,8 +291,7 @@ The module :mod:`curses` defines the following functions:
color number. The value of *pair_number* must be between ``1`` and
``COLOR_PAIRS - 1`` (the ``0`` color pair is wired to white on black and cannot
be changed). The value of *fg* and *bg* arguments must be between ``0`` and
``COLORS - 1``, or, after calling :func:`use_default_colors`, ``-1``.
If the color-pair was previously initialized, the screen is
:const:`COLORS`. If the color-pair was previously initialized, the screen is
refreshed and all occurrences of that color-pair are changed to the new
definition.
@ -443,7 +441,7 @@ The module :mod:`curses` defines the following functions:
.. function:: pair_content(pair_number)
Return a tuple ``(fg, bg)`` containing the colors for the requested color pair.
The value of *pair_number* must be between ``0`` and ``COLOR_PAIRS - 1``.
The value of *pair_number* must be between ``1`` and ``COLOR_PAIRS - 1``.
.. function:: pair_number(attr)

View File

@ -4,7 +4,8 @@
# This script doesn't actually display anything very coherent. but it
# does call (nearly) every method and function.
#
# Functions not tested: {def,reset}_{shell,prog}_mode, getch(), getstr()
# Functions not tested: {def,reset}_{shell,prog}_mode, getch(), getstr(),
# init_color()
# Only called, not tested: getmouse(), ungetmouse()
#
@ -12,7 +13,6 @@ import os
import string
import sys
import tempfile
import functools
import unittest
from test.support import requires, import_module, verbose, SaveSignals
@ -36,17 +36,7 @@ def requires_curses_func(name):
return unittest.skipUnless(hasattr(curses, name),
'requires curses.%s' % name)
def requires_colors(test):
@functools.wraps(test)
def wrapped(self, *args, **kwargs):
if not curses.has_colors():
self.skipTest('requires colors support')
curses.start_color()
test(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',
@ -57,59 +47,37 @@ class TestCurses(unittest.TestCase):
@classmethod
def setUpClass(cls):
if verbose:
print(f'TERM={term}', file=sys.stderr, flush=True)
if not sys.__stdout__.isatty():
# Temporary skip tests on non-tty
raise unittest.SkipTest('sys.__stdout__ is not a tty')
cls.tmp = tempfile.TemporaryFile()
fd = cls.tmp.fileno()
else:
cls.tmp = None
fd = sys.__stdout__.fileno()
# testing setupterm() inside initscr/endwin
# causes terminal breakage
stdout_fd = sys.__stdout__.fileno()
curses.setupterm(fd=stdout_fd)
curses.setupterm(fd=fd)
@classmethod
def tearDownClass(cls):
if cls.tmp:
cls.tmp.close()
del cls.tmp
def setUp(self):
self.isatty = True
self.output = sys.__stdout__
stdout_fd = sys.__stdout__.fileno()
if not sys.__stdout__.isatty():
# initstr() unconditionally uses C stdout.
# If it is redirected to file or pipe, try to attach it
# to terminal.
# First, save a copy of the file descriptor of stdout, so it
# can be restored after finishing the test.
dup_fd = os.dup(stdout_fd)
self.addCleanup(os.close, dup_fd)
self.addCleanup(os.dup2, dup_fd, stdout_fd)
if sys.__stderr__.isatty():
# If stderr is connected to terminal, use it.
tmp = sys.__stderr__
self.output = sys.__stderr__
else:
try:
# Try to open the terminal device.
tmp = open('/dev/tty', 'wb', buffering=0)
except OSError:
# As a fallback, use regular file to write control codes.
# Some functions (like savetty) will not work, but at
# least the garbage control sequences will not be mixed
# with the testing report.
tmp = tempfile.TemporaryFile(mode='wb', buffering=0)
self.isatty = False
self.addCleanup(tmp.close)
self.output = None
os.dup2(tmp.fileno(), stdout_fd)
self.save_signals = SaveSignals()
self.save_signals.save()
self.addCleanup(self.save_signals.restore)
if verbose and self.output is not None:
if verbose:
# just to make the test output a little more readable
sys.stderr.flush()
sys.stdout.flush()
print(file=self.output, flush=True)
print()
self.stdscr = curses.initscr()
if self.isatty:
curses.savetty()
self.addCleanup(curses.endwin)
self.addCleanup(curses.resetty)
curses.savetty()
def tearDown(self):
curses.resetty()
curses.endwin()
self.save_signals.restore()
def test_window_funcs(self):
"Test the methods of windows"
@ -127,7 +95,7 @@ class TestCurses(unittest.TestCase):
for meth in [stdscr.clear, stdscr.clrtobot,
stdscr.clrtoeol, stdscr.cursyncup, stdscr.delch,
stdscr.deleteln, stdscr.erase, stdscr.getbegyx,
stdscr.getbkgd, stdscr.getmaxyx,
stdscr.getbkgd, stdscr.getkey, stdscr.getmaxyx,
stdscr.getparyx, stdscr.getyx, stdscr.inch,
stdscr.insertln, stdscr.instr, stdscr.is_wintouched,
win.noutrefresh, stdscr.redrawwin, stdscr.refresh,
@ -238,11 +206,6 @@ class TestCurses(unittest.TestCase):
if hasattr(stdscr, 'enclose'):
stdscr.enclose(10, 10)
with tempfile.TemporaryFile() as f:
self.stdscr.putwin(f)
f.seek(0)
curses.getwin(f)
self.assertRaises(ValueError, stdscr.getstr, -400)
self.assertRaises(ValueError, stdscr.getstr, 2, 3, -400)
self.assertRaises(ValueError, stdscr.instr, -2)
@ -261,19 +224,16 @@ class TestCurses(unittest.TestCase):
def test_module_funcs(self):
"Test module-level functions"
for func in [curses.baudrate, curses.beep, curses.can_change_color,
curses.doupdate, curses.flash, curses.flushinp,
curses.cbreak, curses.def_prog_mode, curses.doupdate,
curses.flash, curses.flushinp,
curses.has_colors, curses.has_ic, curses.has_il,
curses.isendwin, curses.killchar, curses.longname,
curses.noecho, curses.nonl, curses.noqiflush,
curses.termattrs, curses.termname, curses.erasechar]:
curses.nocbreak, curses.noecho, curses.nonl,
curses.noqiflush, curses.noraw,
curses.reset_prog_mode, curses.termattrs,
curses.termname, curses.erasechar]:
with self.subTest(func=func.__qualname__):
func()
if self.isatty:
for func in [curses.cbreak, curses.def_prog_mode,
curses.nocbreak, curses.noraw,
curses.reset_prog_mode]:
with self.subTest(func=func.__qualname__):
func()
if hasattr(curses, 'filter'):
curses.filter()
if hasattr(curses, 'getsyx'):
@ -285,9 +245,13 @@ class TestCurses(unittest.TestCase):
curses.delay_output(1)
curses.echo() ; curses.echo(1)
with tempfile.TemporaryFile() as f:
self.stdscr.putwin(f)
f.seek(0)
curses.getwin(f)
curses.halfdelay(1)
if self.isatty:
curses.intrflush(1)
curses.intrflush(1)
curses.meta(1)
curses.napms(100)
curses.newpad(50,50)
@ -296,8 +260,7 @@ class TestCurses(unittest.TestCase):
curses.nl() ; curses.nl(1)
curses.putp(b'abc')
curses.qiflush()
if self.isatty:
curses.raw() ; curses.raw(1)
curses.raw() ; curses.raw(1)
curses.set_escdelay(25)
self.assertEqual(curses.get_escdelay(), 25)
curses.set_tabsize(4)
@ -316,111 +279,18 @@ class TestCurses(unittest.TestCase):
curses.use_env(1)
# Functions only available on a few platforms
def test_colors_funcs(self):
if not curses.has_colors():
self.skipTest('requires colors support')
curses.start_color()
curses.init_pair(2, 1,1)
curses.color_content(1)
curses.color_pair(2)
curses.pair_content(curses.COLOR_PAIRS - 1)
curses.pair_number(0)
def bad_colors(self):
return (-2**31 - 1, 2**31, -2**63 - 1, 2**63, 2**64)
def bad_pairs(self):
return (-2**31 - 1, 2**31, -2**63 - 1, 2**63, 2**64)
@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(min(curses.COLORS - 1, SHORT_MAX))
for color in self.bad_colors():
self.assertRaises(OverflowError, curses.color_content, color)
if curses.COLORS <= SHORT_MAX:
self.assertRaises(curses.error, curses.color_content, curses.COLORS)
self.assertRaises(curses.error, curses.color_content, -1)
@requires_colors
def test_init_color(self):
if not curses.can_change_color:
self.skipTest('cannot change color')
old = curses.color_content(0)
try:
curses.init_color(0, *old)
except curses.error:
self.skipTest('cannot change color (init_color() failed)')
self.addCleanup(curses.init_color, 0, *old)
curses.init_color(0, 0, 0, 0)
self.assertEqual(curses.color_content(0), (0, 0, 0))
curses.init_color(0, 1000, 1000, 1000)
self.assertEqual(curses.color_content(0), (1000, 1000, 1000))
maxcolor = min(curses.COLORS - 1, SHORT_MAX)
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(OverflowError, curses.init_color, color, 0, 0, 0)
if curses.COLORS <= SHORT_MAX:
self.assertRaises(curses.error, curses.init_color, curses.COLORS, 0, 0, 0)
self.assertRaises(curses.error, curses.init_color, -1, 0, 0, 0)
for comp in (-1, 1001):
self.assertRaises(curses.error, curses.init_color, 0, comp, 0, 0)
self.assertRaises(curses.error, curses.init_color, 0, 0, comp, 0)
self.assertRaises(curses.error, curses.init_color, 0, 0, 0, comp)
@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(min(curses.COLOR_PAIRS - 1, SHORT_MAX))
for pair in self.bad_pairs():
self.assertRaises(OverflowError, curses.pair_content, pair)
self.assertRaises(curses.error, curses.pair_content, -1)
@requires_colors
def test_init_pair(self):
old = curses.pair_content(1)
curses.init_pair(1, *old)
self.addCleanup(curses.init_pair, 1, *old)
curses.init_pair(1, 0, 0)
self.assertEqual(curses.pair_content(1), (0, 0))
maxcolor = min(curses.COLORS - 1, SHORT_MAX)
curses.init_pair(1, maxcolor, maxcolor)
self.assertEqual(curses.pair_content(1), (maxcolor, maxcolor))
maxpair = min(curses.COLOR_PAIRS - 1, SHORT_MAX)
curses.init_pair(maxpair, 2, 3)
self.assertEqual(curses.pair_content(maxpair), (2, 3))
for pair in self.bad_pairs():
self.assertRaises(OverflowError, curses.init_pair, pair, 0, 0)
self.assertRaises(curses.error, curses.init_pair, -1, 0, 0)
for color in self.bad_colors():
self.assertRaises(OverflowError, curses.init_pair, 1, color, 0)
self.assertRaises(OverflowError, curses.init_pair, 1, 0, color)
if curses.COLORS <= SHORT_MAX:
self.assertRaises(curses.error, curses.init_pair, 1, curses.COLORS, 0)
self.assertRaises(curses.error, curses.init_pair, 1, 0, curses.COLORS)
@requires_colors
def test_color_attrs(self):
for pair in 0, 1, 255:
attr = curses.color_pair(pair)
self.assertEqual(curses.pair_number(attr), pair, attr)
self.assertEqual(curses.pair_number(attr | curses.A_BOLD), pair)
self.assertEqual(curses.color_pair(0), 0)
self.assertEqual(curses.pair_number(0), 0)
@requires_curses_func('use_default_colors')
@requires_colors
def test_use_default_colors(self):
self.assertIn(curses.pair_content(0),
((curses.COLOR_WHITE, curses.COLOR_BLACK), (-1, -1)))
curses.use_default_colors()
self.assertEqual(curses.pair_content(0), (-1, -1))
if hasattr(curses, 'use_default_colors'):
curses.use_default_colors()
@requires_curses_func('keyname')
def test_keyname(self):
@ -488,6 +358,7 @@ class TestCurses(unittest.TestCase):
@requires_curses_func('resizeterm')
def test_resizeterm(self):
stdscr = self.stdscr
lines, cols = curses.LINES, curses.COLS
new_lines = lines - 1
new_cols = cols + 1

View File

@ -1 +0,0 @@
Fixed range checks for color and pair numbers in :mod:`curses`.

View File

@ -176,6 +176,18 @@ static char *screen_encoding = NULL;
/* Utility Functions */
static inline int
color_pair_to_attr(short color_number)
{
return ((int)color_number << 8);
}
static inline short
attr_to_color_pair(int attr)
{
return (short)((attr & A_COLOR) >> 8);
}
/*
* Check the return code from a curses function and return None
* or raise an exception as appropriate. These are exported using the
@ -606,7 +618,7 @@ _curses_window_addch_impl(PyCursesWindowObject *self, int group_left_1,
if (type == 2) {
funcname = "add_wch";
wstr[1] = L'\0';
setcchar(&wcval, wstr, attr, PAIR_NUMBER(attr), NULL);
setcchar(&wcval, wstr, attr, attr_to_color_pair(attr), NULL);
if (coordinates_group)
rtn = mvwadd_wch(self->win,y,x, &wcval);
else {
@ -2574,7 +2586,7 @@ NoArgOrFlagNoReturnFunctionBody(cbreak, flag)
_curses.color_content
color_number: short
The number of the color (0 - (COLORS-1)).
The number of the color (0 - COLORS).
/
Return the red, green, and blue (RGB) components of the specified color.
@ -2585,7 +2597,7 @@ which will be between 0 (no component) and 1000 (maximum amount of component).
static PyObject *
_curses_color_content_impl(PyObject *module, short color_number)
/*[clinic end generated code: output=cb15cf3120d4bfc1 input=630f6737514db6ad]*/
/*[clinic end generated code: output=cb15cf3120d4bfc1 input=5555abb1c11e11b7]*/
{
short r,g,b;
@ -2604,8 +2616,8 @@ _curses_color_content_impl(PyObject *module, short color_number)
/*[clinic input]
_curses.color_pair
pair_number: short
The number of the color pair.
color_number: short
The number of the color (0 - COLORS).
/
Return the attribute value for displaying text in the specified color.
@ -2615,13 +2627,13 @@ other A_* attributes. pair_number() is the counterpart to this function.
[clinic start generated code]*/
static PyObject *
_curses_color_pair_impl(PyObject *module, short pair_number)
/*[clinic end generated code: output=ce609d238b70dc11 input=8dd0d5da94cb15b5]*/
_curses_color_pair_impl(PyObject *module, short color_number)
/*[clinic end generated code: output=6a84cb6b29ecaf9a input=a9d3eb6f50e4dc12]*/
{
PyCursesInitialised;
PyCursesInitialisedColor;
return PyLong_FromLong(COLOR_PAIR(pair_number));
return PyLong_FromLong(color_pair_to_attr(color_number));
}
/*[clinic input]
@ -3016,7 +3028,7 @@ _curses_has_key_impl(PyObject *module, int key)
_curses.init_color
color_number: short
The number of the color to be changed (0 - (COLORS-1)).
The number of the color to be changed (0 - COLORS).
r: short
Red component (0 - 1000).
g: short
@ -3029,13 +3041,13 @@ Change the definition of a color.
When init_color() is used, all occurrences of that color on the screen
immediately change to the new definition. This function is a no-op on
most terminals; it is active only if can_change_color() returns true.
most terminals; it is active only if can_change_color() returns 1.
[clinic start generated code]*/
static PyObject *
_curses_init_color_impl(PyObject *module, short color_number, short r,
short g, short b)
/*[clinic end generated code: output=280236f5efe9776a input=128601b5dc76d548]*/
/*[clinic end generated code: output=280236f5efe9776a input=f3a05bd38f619175]*/
{
PyCursesInitialised;
PyCursesInitialisedColor;
@ -3049,9 +3061,9 @@ _curses.init_pair
pair_number: short
The number of the color-pair to be changed (1 - (COLOR_PAIRS-1)).
fg: short
Foreground color number (-1 - (COLORS-1)).
Foreground color number (0 - COLORS).
bg: short
Background color number (-1 - (COLORS-1)).
Background color number (0 - COLORS).
/
Change the definition of a color-pair.
@ -3063,7 +3075,7 @@ all occurrences of that color-pair are changed to the new definition.
static PyObject *
_curses_init_pair_impl(PyObject *module, short pair_number, short fg,
short bg)
/*[clinic end generated code: output=9c2ce39c22f376b6 input=12c320ec14396ea2]*/
/*[clinic end generated code: output=9c2ce39c22f376b6 input=c9f0b11b17a2ac6d]*/
{
PyCursesInitialised;
PyCursesInitialisedColor;
@ -3703,7 +3715,7 @@ _curses_pair_content_impl(PyObject *module, short pair_number)
if (pair_content(pair_number, &f, &b)==ERR) {
PyErr_SetString(PyCursesError,
"Argument 1 was out of range. (0..COLOR_PAIRS-1)");
"Argument 1 was out of range. (1..COLOR_PAIRS-1)");
return NULL;
}
@ -3728,7 +3740,7 @@ _curses_pair_number_impl(PyObject *module, int attr)
PyCursesInitialised;
PyCursesInitialisedColor;
return PyLong_FromLong(PAIR_NUMBER(attr));
return PyLong_FromLong(attr_to_color_pair(attr));
}
/*[clinic input]

View File

@ -2028,7 +2028,7 @@ PyDoc_STRVAR(_curses_color_content__doc__,
"Return the red, green, and blue (RGB) components of the specified color.\n"
"\n"
" color_number\n"
" The number of the color (0 - (COLORS-1)).\n"
" The number of the color (0 - COLORS).\n"
"\n"
"A 3-tuple is returned, containing the R, G, B values for the given color,\n"
"which will be between 0 (no component) and 1000 (maximum amount of component).");
@ -2076,13 +2076,13 @@ exit:
}
PyDoc_STRVAR(_curses_color_pair__doc__,
"color_pair($module, pair_number, /)\n"
"color_pair($module, color_number, /)\n"
"--\n"
"\n"
"Return the attribute value for displaying text in the specified color.\n"
"\n"
" pair_number\n"
" The number of the color pair.\n"
" color_number\n"
" The number of the color (0 - COLORS).\n"
"\n"
"This attribute value can be combined with A_STANDOUT, A_REVERSE, and the\n"
"other A_* attributes. pair_number() is the counterpart to this function.");
@ -2091,13 +2091,13 @@ PyDoc_STRVAR(_curses_color_pair__doc__,
{"color_pair", (PyCFunction)_curses_color_pair, METH_O, _curses_color_pair__doc__},
static PyObject *
_curses_color_pair_impl(PyObject *module, short pair_number);
_curses_color_pair_impl(PyObject *module, short color_number);
static PyObject *
_curses_color_pair(PyObject *module, PyObject *arg)
{
PyObject *return_value = NULL;
short pair_number;
short color_number;
if (PyFloat_Check(arg)) {
PyErr_SetString(PyExc_TypeError,
@ -2120,10 +2120,10 @@ _curses_color_pair(PyObject *module, PyObject *arg)
goto exit;
}
else {
pair_number = (short) ival;
color_number = (short) ival;
}
}
return_value = _curses_color_pair_impl(module, pair_number);
return_value = _curses_color_pair_impl(module, color_number);
exit:
return return_value;
@ -2699,7 +2699,7 @@ PyDoc_STRVAR(_curses_init_color__doc__,
"Change the definition of a color.\n"
"\n"
" color_number\n"
" The number of the color to be changed (0 - (COLORS-1)).\n"
" The number of the color to be changed (0 - COLORS).\n"
" r\n"
" Red component (0 - 1000).\n"
" g\n"
@ -2709,7 +2709,7 @@ PyDoc_STRVAR(_curses_init_color__doc__,
"\n"
"When init_color() is used, all occurrences of that color on the screen\n"
"immediately change to the new definition. This function is a no-op on\n"
"most terminals; it is active only if can_change_color() returns true.");
"most terminals; it is active only if can_change_color() returns 1.");
#define _CURSES_INIT_COLOR_METHODDEF \
{"init_color", (PyCFunction)(void(*)(void))_curses_init_color, METH_FASTCALL, _curses_init_color__doc__},
@ -2841,9 +2841,9 @@ PyDoc_STRVAR(_curses_init_pair__doc__,
" pair_number\n"
" The number of the color-pair to be changed (1 - (COLOR_PAIRS-1)).\n"
" fg\n"
" Foreground color number (-1 - (COLORS-1)).\n"
" Foreground color number (0 - COLORS).\n"
" bg\n"
" Background color number (-1 - (COLORS-1)).\n"
" Background color number (0 - COLORS).\n"
"\n"
"If the color-pair was previously initialized, the screen is refreshed and\n"
"all occurrences of that color-pair are changed to the new definition.");
@ -4713,4 +4713,4 @@ _curses_use_default_colors(PyObject *module, PyObject *Py_UNUSED(ignored))
#ifndef _CURSES_USE_DEFAULT_COLORS_METHODDEF
#define _CURSES_USE_DEFAULT_COLORS_METHODDEF
#endif /* !defined(_CURSES_USE_DEFAULT_COLORS_METHODDEF) */
/*[clinic end generated code: output=5e739120041df368 input=a9049054013a1b77]*/
/*[clinic end generated code: output=b53652f8acafd817 input=a9049054013a1b77]*/