Issue #14769: test_capi now has SkipitemTest, which cleverly checks

for "parity" between PyArg_ParseTuple() and the Python/getargs.c static
function skipitem() for all possible "format units".
This commit is contained in:
Larry Hastings 2012-06-22 03:56:29 -07:00
parent 466bfff9fb
commit 8f904daee9
3 changed files with 134 additions and 39 deletions

View File

@ -214,9 +214,78 @@ class EmbeddingTest(unittest.TestCase):
finally:
os.chdir(oldcwd)
class SkipitemTest(unittest.TestCase):
def test_skipitem(self):
"""
If this test failed, you probably added a new "format unit"
in Python/getargs.c, but neglected to update our poor friend
skipitem() in the same file. (If so, shame on you!)
This function brute-force tests all** ASCII characters (1 to 127
inclusive) as format units, checking to see that
PyArg_ParseTupleAndKeywords() return consistent errors both when
the unit is attempted to be used and when it is skipped. If the
format unit doesn't exist, we'll get one of two specific error
messages (one for used, one for skipped); if it does exist we
*won't* get that error--we'll get either no error or some other
error. If we get the "does not exist" error for one test and
not for the other, there's a mismatch, and the test fails.
** Okay, it actually skips some ASCII characters. Some characters
have special funny semantics, and it would be difficult to
accomodate them here.
"""
empty_tuple = ()
tuple_1 = (0,)
dict_b = {'b':1}
keywords = ["a", "b"]
# Python C source files must be ASCII,
# therefore we'll never have a format unit > 127
for i in range(1, 128):
c = chr(i)
# skip non-printable characters, no one is insane enough to define
# one as a format unit
# skip parentheses, the error reporting is inconsistent about them
# skip 'e', it's always a two-character code
# skip '|' and '$', they don't represent arguments anyway
if (not c.isprintable()) or (c in '()e|$'):
continue
# test the format unit when not skipped
format = c + "i"
try:
# (note: the format string must be bytes!)
_testcapi.parse_tuple_and_keywords(tuple_1, dict_b,
format.encode("ascii"), keywords)
when_not_skipped = False
except TypeError as e:
s = "argument 1 must be impossible<bad format char>, not int"
when_not_skipped = (str(e) == s)
except RuntimeError as e:
when_not_skipped = False
# test the format unit when skipped
optional_format = "|" + format
try:
_testcapi.parse_tuple_and_keywords(empty_tuple, dict_b,
optional_format.encode("ascii"), keywords)
when_skipped = False
except RuntimeError as e:
s = "impossible<bad format char>: '{}'".format(format)
when_skipped = (str(e) == s)
message = ("test_skipitem_parity: "
"detected mismatch between convertsimple and skipitem "
"for format unit '{}' ({}), not skipped {}, skipped {}".format(
c, i, when_skipped, when_not_skipped))
self.assertIs(when_skipped, when_not_skipped, message)
def test_main():
support.run_unittest(CAPITest, TestPendingCalls, Test6012, EmbeddingTest)
support.run_unittest(CAPITest, TestPendingCalls,
Test6012, EmbeddingTest, SkipitemTest)
for name in dir(_testcapi):
if name.startswith('test_'):

View File

@ -165,6 +165,10 @@ Documentation
Tests
-----
- Issue #14769: test_capi now has SkipitemTest, which cleverly checks
for "parity" between PyArg_ParseTuple() and the Python/getargs.c static
function skipitem() for all possible "format units".
- test_nntplib now tolerates being run from behind NNTP gateways that add
"X-Antivirus" headers to articles

View File

@ -1195,51 +1195,73 @@ test_s_code(PyObject *self)
}
static PyObject *
test_bug_7414(PyObject *self)
parse_tuple_and_keywords(PyObject *self, PyObject *args)
{
/* Issue #7414: for PyArg_ParseTupleAndKeywords, 'C' code wasn't being
skipped properly in skipitem() */
int a = 0, b = 0, result;
char *kwlist[] = {"a", "b", NULL};
PyObject *tuple = NULL, *dict = NULL, *b_str;
PyObject *sub_args;
PyObject *sub_kwargs;
char *sub_format;
PyObject *sub_keywords;
tuple = PyTuple_New(0);
if (tuple == NULL)
goto failure;
dict = PyDict_New();
if (dict == NULL)
goto failure;
b_str = PyUnicode_FromString("b");
if (b_str == NULL)
goto failure;
result = PyDict_SetItemString(dict, "b", b_str);
Py_DECREF(b_str);
if (result < 0)
goto failure;
Py_ssize_t i, size;
char *keywords[8 + 1]; /* space for NULL at end */
PyObject *o;
PyObject *converted[8];
result = PyArg_ParseTupleAndKeywords(tuple, dict, "|CC",
kwlist, &a, &b);
if (!result)
goto failure;
int result;
PyObject *return_value = NULL;
if (a != 0)
return raiseTestError("test_bug_7414",
"C format code not skipped properly");
if (b != 'b')
return raiseTestError("test_bug_7414",
"C format code returned wrong value");
char buffers[32][8];
Py_DECREF(dict);
Py_DECREF(tuple);
Py_RETURN_NONE;
if (!PyArg_ParseTuple(args, "OOyO:parse_tuple_and_keywords",
&sub_args, &sub_kwargs,
&sub_format, &sub_keywords))
return NULL;
failure:
Py_XDECREF(dict);
Py_XDECREF(tuple);
return NULL;
if (!(PyList_CheckExact(sub_keywords) || PyTuple_CheckExact(sub_keywords))) {
PyErr_SetString(PyExc_ValueError,
"parse_tuple_and_keywords: sub_keywords must be either list or tuple");
return NULL;
}
memset(buffers, 0, sizeof(buffers));
memset(converted, 0, sizeof(converted));
memset(keywords, 0, sizeof(keywords));
size = PySequence_Fast_GET_SIZE(sub_keywords);
if (size > 8) {
PyErr_SetString(PyExc_ValueError,
"parse_tuple_and_keywords: too many keywords in sub_keywords");
goto exit;
}
for (i = 0; i < size; i++) {
o = PySequence_Fast_GET_ITEM(sub_keywords, i);
if (!PyUnicode_FSConverter(o, (void *)(converted + i))) {
PyErr_Format(PyExc_ValueError,
"parse_tuple_and_keywords: could not convert keywords[%s] to narrow string", i);
goto exit;
}
keywords[i] = PyBytes_AS_STRING(converted[i]);
}
result = PyArg_ParseTupleAndKeywords(sub_args, sub_kwargs,
sub_format, keywords,
buffers + 0, buffers + 1, buffers + 2, buffers + 3,
buffers + 4, buffers + 5, buffers + 6, buffers + 7);
if (result) {
return_value = Py_None;
Py_INCREF(Py_None);
}
exit:
size = sizeof(converted) / sizeof(converted[0]);
for (i = 0; i < size; i++) {
Py_XDECREF(converted[i]);
}
return return_value;
}
static volatile int x;
/* Test the u and u# codes for PyArg_ParseTuple. May leak memory in case
@ -2426,7 +2448,7 @@ static PyMethodDef TestMethods[] = {
{"test_long_numbits", (PyCFunction)test_long_numbits, METH_NOARGS},
{"test_k_code", (PyCFunction)test_k_code, METH_NOARGS},
{"test_empty_argparse", (PyCFunction)test_empty_argparse,METH_NOARGS},
{"test_bug_7414", (PyCFunction)test_bug_7414, METH_NOARGS},
{"parse_tuple_and_keywords", parse_tuple_and_keywords, METH_VARARGS},
{"test_null_strings", (PyCFunction)test_null_strings, METH_NOARGS},
{"test_string_from_format", (PyCFunction)test_string_from_format, METH_NOARGS},
{"test_with_docstring", (PyCFunction)test_with_docstring, METH_NOARGS,