Skip to content

Commit a9af13f

Browse files
committed
Add regression test for PySet_Contains
As discussed in pythongh-141183, the test suite did not test that PySet_Contains does not convert unhashable key into a frozenset. This commit adds a regression test for this behavior, to ensure that any behavior change is caught by the test suite.
1 parent d890aba commit a9af13f

File tree

1 file changed

+63
-0
lines changed
  • Modules/_testlimitedcapi

1 file changed

+63
-0
lines changed

Modules/_testlimitedcapi/set.c

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,67 @@ test_frozenset_add_in_capi(PyObject *self, PyObject *Py_UNUSED(obj))
155155
return NULL;
156156
}
157157

158+
static PyObject *
159+
test_set_contains_does_not_convert_unhashable_key(PyObject *self, PyObject *Py_UNUSED(obj))
160+
{
161+
// The documentation of PySet_Contains state:
162+
//
163+
// int PySet_Contains(PyObject *anyset, PyObject *key)
164+
//
165+
// Part of the Stable ABI.
166+
//
167+
// ... Unlike the Python __contains__() method, this function does not
168+
// automatically convert unhashable sets [key] into temporary frozensets.
169+
// Raise a TypeError if the key is unhashable.
170+
//
171+
// That is to say {2,3} in {1, 2, frozenset({2,3})}
172+
// ^_ will be converted in a frozenset in Python code.
173+
// But not if using PySet_Contains(..., key)
174+
//
175+
// We test that this behavior is unchanged as this is a stable API.
176+
177+
PyObject *outer_set = PySet_New(NULL);
178+
179+
PyObject *needle = PySet_New(NULL);
180+
if (needle == NULL) {
181+
Py_DECREF(outer_set);
182+
return NULL;
183+
}
184+
185+
PyObject *num = PyLong_FromLong(42);
186+
if (num == NULL) {
187+
Py_DECREF(outer_set);
188+
Py_DECREF(needle);
189+
return NULL;
190+
}
191+
192+
// Add an element to needle to make it {42}
193+
if (PySet_Add(needle, num) < 0) {
194+
Py_DECREF(outer_set);
195+
Py_DECREF(needle);
196+
Py_DECREF(num);
197+
return NULL;
198+
}
199+
200+
int result = PySet_Contains(outer_set, needle);
201+
202+
Py_DECREF(num);
203+
Py_DECREF(needle);
204+
Py_DECREF(outer_set);
205+
206+
if (result < 0) {
207+
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
208+
PyErr_Clear();
209+
Py_RETURN_TRUE;
210+
}
211+
return NULL;
212+
}
213+
214+
PyErr_SetString(PyExc_AssertionError,
215+
"PySet_Contains should have raised TypeError for unhashable key");
216+
return NULL;
217+
}
218+
158219
static PyMethodDef test_methods[] = {
159220
{"set_check", set_check, METH_O},
160221
{"set_checkexact", set_checkexact, METH_O},
@@ -174,6 +235,8 @@ static PyMethodDef test_methods[] = {
174235
{"set_clear", set_clear, METH_O},
175236

176237
{"test_frozenset_add_in_capi", test_frozenset_add_in_capi, METH_NOARGS},
238+
{"test_set_contains_does_not_convert_unhashable_key",
239+
test_set_contains_does_not_convert_unhashable_key, METH_NOARGS},
177240

178241
{NULL},
179242
};

0 commit comments

Comments
 (0)