summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNikita Sobolev <mail@sobolevn.me>2023-10-09 10:04:09 (GMT)
committerGitHub <noreply@github.com>2023-10-09 10:04:09 (GMT)
commit3ed5cb0de37a0bb9325265465a56427b90a29598 (patch)
treebe35995ab2d316e9bb0e43ddb78a9d0202894df5
parenta7fe709fef75e056698b80b9861c34fc362dc5aa (diff)
downloadcpython-3ed5cb0de37a0bb9325265465a56427b90a29598.zip
cpython-3ed5cb0de37a0bb9325265465a56427b90a29598.tar.gz
cpython-3ed5cb0de37a0bb9325265465a56427b90a29598.tar.bz2
[3.12] gh-110525: Add CAPI tests for set and frozenset objects (GH-110526). (GH-110547)
(cherry picked from commit c49edd7d9c5395a6a6696a4846f56bc8b2b22792)
-rw-r--r--Lib/test/test_capi/test_set.py215
-rw-r--r--Modules/Setup.stdlib.in2
-rw-r--r--Modules/_testcapi/parts.h1
-rw-r--r--Modules/_testcapi/set.c162
-rw-r--r--Modules/_testcapimodule.c3
-rw-r--r--PCbuild/_testcapi.vcxproj1
6 files changed, 383 insertions, 1 deletions
diff --git a/Lib/test/test_capi/test_set.py b/Lib/test/test_capi/test_set.py
new file mode 100644
index 0000000..e9165e7
--- /dev/null
+++ b/Lib/test/test_capi/test_set.py
@@ -0,0 +1,215 @@
+import unittest
+
+from test.support import import_helper
+
+# Skip this test if the _testcapi module isn't available.
+_testcapi = import_helper.import_module('_testcapi')
+
+class set_subclass(set):
+ pass
+
+class frozenset_subclass(frozenset):
+ pass
+
+
+class TestSetCAPI(unittest.TestCase):
+ def assertImmutable(self, action, *args):
+ self.assertRaises(SystemError, action, frozenset(), *args)
+ self.assertRaises(SystemError, action, frozenset({1}), *args)
+ self.assertRaises(SystemError, action, frozenset_subclass(), *args)
+ self.assertRaises(SystemError, action, frozenset_subclass({1}), *args)
+
+ def test_set_check(self):
+ check = _testcapi.set_check
+ self.assertTrue(check(set()))
+ self.assertTrue(check({1, 2}))
+ self.assertFalse(check(frozenset()))
+ self.assertTrue(check(set_subclass()))
+ self.assertFalse(check(frozenset_subclass()))
+ self.assertFalse(check(object()))
+ # CRASHES: check(NULL)
+
+ def test_set_check_exact(self):
+ check = _testcapi.set_checkexact
+ self.assertTrue(check(set()))
+ self.assertTrue(check({1, 2}))
+ self.assertFalse(check(frozenset()))
+ self.assertFalse(check(set_subclass()))
+ self.assertFalse(check(frozenset_subclass()))
+ self.assertFalse(check(object()))
+ # CRASHES: check(NULL)
+
+ def test_frozenset_check(self):
+ check = _testcapi.frozenset_check
+ self.assertFalse(check(set()))
+ self.assertTrue(check(frozenset()))
+ self.assertTrue(check(frozenset({1, 2})))
+ self.assertFalse(check(set_subclass()))
+ self.assertTrue(check(frozenset_subclass()))
+ self.assertFalse(check(object()))
+ # CRASHES: check(NULL)
+
+ def test_frozenset_check_exact(self):
+ check = _testcapi.frozenset_checkexact
+ self.assertFalse(check(set()))
+ self.assertTrue(check(frozenset()))
+ self.assertTrue(check(frozenset({1, 2})))
+ self.assertFalse(check(set_subclass()))
+ self.assertFalse(check(frozenset_subclass()))
+ self.assertFalse(check(object()))
+ # CRASHES: check(NULL)
+
+ def test_anyset_check(self):
+ check = _testcapi.anyset_check
+ self.assertTrue(check(set()))
+ self.assertTrue(check({1, 2}))
+ self.assertTrue(check(frozenset()))
+ self.assertTrue(check(frozenset({1, 2})))
+ self.assertTrue(check(set_subclass()))
+ self.assertTrue(check(frozenset_subclass()))
+ self.assertFalse(check(object()))
+ # CRASHES: check(NULL)
+
+ def test_anyset_check_exact(self):
+ check = _testcapi.anyset_checkexact
+ self.assertTrue(check(set()))
+ self.assertTrue(check({1, 2}))
+ self.assertTrue(check(frozenset()))
+ self.assertTrue(check(frozenset({1, 2})))
+ self.assertFalse(check(set_subclass()))
+ self.assertFalse(check(frozenset_subclass()))
+ self.assertFalse(check(object()))
+ # CRASHES: check(NULL)
+
+ def test_set_new(self):
+ set_new = _testcapi.set_new
+ self.assertEqual(set_new().__class__, set)
+ self.assertEqual(set_new(), set())
+ self.assertEqual(set_new((1, 1, 2)), {1, 2})
+ self.assertEqual(set_new([1, 1, 2]), {1, 2})
+ with self.assertRaisesRegex(TypeError, 'object is not iterable'):
+ set_new(object())
+ with self.assertRaisesRegex(TypeError, 'object is not iterable'):
+ set_new(1)
+ with self.assertRaisesRegex(TypeError, "unhashable type: 'dict'"):
+ set_new((1, {}))
+
+ def test_frozenset_new(self):
+ frozenset_new = _testcapi.frozenset_new
+ self.assertEqual(frozenset_new().__class__, frozenset)
+ self.assertEqual(frozenset_new(), frozenset())
+ self.assertEqual(frozenset_new((1, 1, 2)), frozenset({1, 2}))
+ self.assertEqual(frozenset_new([1, 1, 2]), frozenset({1, 2}))
+ with self.assertRaisesRegex(TypeError, 'object is not iterable'):
+ frozenset_new(object())
+ with self.assertRaisesRegex(TypeError, 'object is not iterable'):
+ frozenset_new(1)
+ with self.assertRaisesRegex(TypeError, "unhashable type: 'dict'"):
+ frozenset_new((1, {}))
+
+ def test_set_size(self):
+ get_size = _testcapi.set_size
+ self.assertEqual(get_size(set()), 0)
+ self.assertEqual(get_size(frozenset()), 0)
+ self.assertEqual(get_size({1, 1, 2}), 2)
+ self.assertEqual(get_size(frozenset({1, 1, 2})), 2)
+ self.assertEqual(get_size(set_subclass((1, 2, 3))), 3)
+ self.assertEqual(get_size(frozenset_subclass((1, 2, 3))), 3)
+ with self.assertRaises(SystemError):
+ get_size(object())
+ # CRASHES: get_size(NULL)
+
+ def test_set_get_size(self):
+ get_size = _testcapi.set_get_size
+ self.assertEqual(get_size(set()), 0)
+ self.assertEqual(get_size(frozenset()), 0)
+ self.assertEqual(get_size({1, 1, 2}), 2)
+ self.assertEqual(get_size(frozenset({1, 1, 2})), 2)
+ self.assertEqual(get_size(set_subclass((1, 2, 3))), 3)
+ self.assertEqual(get_size(frozenset_subclass((1, 2, 3))), 3)
+ # CRASHES: get_size(NULL)
+ # CRASHES: get_size(object())
+
+ def test_set_contains(self):
+ contains = _testcapi.set_contains
+ for cls in (set, frozenset, set_subclass, frozenset_subclass):
+ with self.subTest(cls=cls):
+ instance = cls((1, 2))
+ self.assertTrue(contains(instance, 1))
+ self.assertFalse(contains(instance, 'missing'))
+ with self.assertRaisesRegex(TypeError, "unhashable type: 'list'"):
+ contains(instance, [])
+ # CRASHES: contains(instance, NULL)
+ # CRASHES: contains(NULL, object())
+ # CRASHES: contains(NULL, NULL)
+
+ def test_add(self):
+ add = _testcapi.set_add
+ for cls in (set, set_subclass):
+ with self.subTest(cls=cls):
+ instance = cls((1, 2))
+ self.assertEqual(add(instance, 1), 0)
+ self.assertEqual(instance, {1, 2})
+ self.assertEqual(add(instance, 3), 0)
+ self.assertEqual(instance, {1, 2, 3})
+ with self.assertRaisesRegex(TypeError, "unhashable type: 'list'"):
+ add(instance, [])
+ with self.assertRaises(SystemError):
+ add(object(), 1)
+ self.assertImmutable(add, 1)
+ # CRASHES: add(NULL, object())
+ # CRASHES: add(instance, NULL)
+ # CRASHES: add(NULL, NULL)
+
+ def test_discard(self):
+ discard = _testcapi.set_discard
+ for cls in (set, set_subclass):
+ with self.subTest(cls=cls):
+ instance = cls((1, 2))
+ self.assertEqual(discard(instance, 3), 0)
+ self.assertEqual(instance, {1, 2})
+ self.assertEqual(discard(instance, 1), 1)
+ self.assertEqual(instance, {2})
+ self.assertEqual(discard(instance, 2), 1)
+ self.assertEqual(instance, set())
+ self.assertEqual(discard(instance, 2), 0)
+ self.assertEqual(instance, set())
+ with self.assertRaisesRegex(TypeError, "unhashable type: 'list'"):
+ discard(instance, [])
+ with self.assertRaises(SystemError):
+ discard(object(), 1)
+ self.assertImmutable(discard, 1)
+ # CRASHES: discard(NULL, object())
+ # CRASHES: discard(instance, NULL)
+ # CRASHES: discard(NULL, NULL)
+
+ def test_pop(self):
+ pop = _testcapi.set_pop
+ orig = (1, 2)
+ for cls in (set, set_subclass):
+ with self.subTest(cls=cls):
+ instance = cls(orig)
+ self.assertIn(pop(instance), orig)
+ self.assertEqual(len(instance), 1)
+ self.assertIn(pop(instance), orig)
+ self.assertEqual(len(instance), 0)
+ with self.assertRaises(KeyError):
+ pop(instance)
+ with self.assertRaises(SystemError):
+ pop(object())
+ self.assertImmutable(pop)
+ # CRASHES: pop(NULL)
+
+ def test_clear(self):
+ clear = _testcapi.set_clear
+ for cls in (set, set_subclass):
+ with self.subTest(cls=cls):
+ instance = cls((1, 2))
+ self.assertEqual(clear(instance), 0)
+ self.assertEqual(instance, set())
+ self.assertEqual(clear(instance), 0)
+ self.assertEqual(instance, set())
+ with self.assertRaises(SystemError):
+ clear(object())
+ self.assertImmutable(clear)
+ # CRASHES: clear(NULL)
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index 3e01e25..140245d 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -168,7 +168,7 @@
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c
-@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyos.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c
+@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyos.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
# Some testing modules MUST be built as shared libraries.
diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h
index f37be9b..9bbb541 100644
--- a/Modules/_testcapi/parts.h
+++ b/Modules/_testcapi/parts.h
@@ -37,6 +37,7 @@ int _PyTestCapi_Init_Watchers(PyObject *module);
int _PyTestCapi_Init_Long(PyObject *module);
int _PyTestCapi_Init_Float(PyObject *module);
int _PyTestCapi_Init_Dict(PyObject *module);
+int _PyTestCapi_Init_Set(PyObject *module);
int _PyTestCapi_Init_Structmember(PyObject *module);
int _PyTestCapi_Init_Exceptions(PyObject *module);
int _PyTestCapi_Init_Code(PyObject *module);
diff --git a/Modules/_testcapi/set.c b/Modules/_testcapi/set.c
new file mode 100644
index 0000000..f68a185
--- /dev/null
+++ b/Modules/_testcapi/set.c
@@ -0,0 +1,162 @@
+#include <stddef.h> // ptrdiff_t
+
+#include "parts.h"
+#include "util.h"
+
+static PyObject *
+set_check(PyObject *self, PyObject *obj)
+{
+ NULLABLE(obj);
+ RETURN_INT(PySet_Check(obj));
+}
+
+static PyObject *
+set_checkexact(PyObject *self, PyObject *obj)
+{
+ NULLABLE(obj);
+ RETURN_INT(PySet_CheckExact(obj));
+}
+
+static PyObject *
+frozenset_check(PyObject *self, PyObject *obj)
+{
+ NULLABLE(obj);
+ RETURN_INT(PyFrozenSet_Check(obj));
+}
+
+static PyObject *
+frozenset_checkexact(PyObject *self, PyObject *obj)
+{
+ NULLABLE(obj);
+ RETURN_INT(PyFrozenSet_CheckExact(obj));
+}
+
+static PyObject *
+anyset_check(PyObject *self, PyObject *obj)
+{
+ NULLABLE(obj);
+ RETURN_INT(PyAnySet_Check(obj));
+}
+
+static PyObject *
+anyset_checkexact(PyObject *self, PyObject *obj)
+{
+ NULLABLE(obj);
+ RETURN_INT(PyAnySet_CheckExact(obj));
+}
+
+static PyObject *
+set_new(PyObject *self, PyObject *args)
+{
+ PyObject *iterable = NULL;
+ if (!PyArg_ParseTuple(args, "|O", &iterable)) {
+ return NULL;
+ }
+ return PySet_New(iterable);
+}
+
+static PyObject *
+frozenset_new(PyObject *self, PyObject *args)
+{
+ PyObject *iterable = NULL;
+ if (!PyArg_ParseTuple(args, "|O", &iterable)) {
+ return NULL;
+ }
+ return PyFrozenSet_New(iterable);
+}
+
+static PyObject *
+set_size(PyObject *self, PyObject *obj)
+{
+ NULLABLE(obj);
+ RETURN_SIZE(PySet_Size(obj));
+}
+
+static PyObject *
+set_get_size(PyObject *self, PyObject *obj)
+{
+ NULLABLE(obj);
+ RETURN_SIZE(PySet_GET_SIZE(obj));
+}
+
+static PyObject *
+set_contains(PyObject *self, PyObject *args)
+{
+ PyObject *obj, *item;
+ if (!PyArg_ParseTuple(args, "OO", &obj, &item)) {
+ return NULL;
+ }
+ NULLABLE(obj);
+ NULLABLE(item);
+ RETURN_INT(PySet_Contains(obj, item));
+}
+
+static PyObject *
+set_add(PyObject *self, PyObject *args)
+{
+ PyObject *obj, *item;
+ if (!PyArg_ParseTuple(args, "OO", &obj, &item)) {
+ return NULL;
+ }
+ NULLABLE(obj);
+ NULLABLE(item);
+ RETURN_INT(PySet_Add(obj, item));
+}
+
+static PyObject *
+set_discard(PyObject *self, PyObject *args)
+{
+ PyObject *obj, *item;
+ if (!PyArg_ParseTuple(args, "OO", &obj, &item)) {
+ return NULL;
+ }
+ NULLABLE(obj);
+ NULLABLE(item);
+ RETURN_INT(PySet_Discard(obj, item));
+}
+
+static PyObject *
+set_pop(PyObject *self, PyObject *obj)
+{
+ NULLABLE(obj);
+ return PySet_Pop(obj);
+}
+
+static PyObject *
+set_clear(PyObject *self, PyObject *obj)
+{
+ NULLABLE(obj);
+ RETURN_INT(PySet_Clear(obj));
+}
+
+static PyMethodDef test_methods[] = {
+ {"set_check", set_check, METH_O},
+ {"set_checkexact", set_checkexact, METH_O},
+ {"frozenset_check", frozenset_check, METH_O},
+ {"frozenset_checkexact", frozenset_checkexact, METH_O},
+ {"anyset_check", anyset_check, METH_O},
+ {"anyset_checkexact", anyset_checkexact, METH_O},
+
+ {"set_new", set_new, METH_VARARGS},
+ {"frozenset_new", frozenset_new, METH_VARARGS},
+
+ {"set_size", set_size, METH_O},
+ {"set_get_size", set_get_size, METH_O},
+ {"set_contains", set_contains, METH_VARARGS},
+ {"set_add", set_add, METH_VARARGS},
+ {"set_discard", set_discard, METH_VARARGS},
+ {"set_pop", set_pop, METH_O},
+ {"set_clear", set_clear, METH_O},
+
+ {NULL},
+};
+
+int
+_PyTestCapi_Init_Set(PyObject *m)
+{
+ if (PyModule_AddFunctions(m, test_methods) < 0) {
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index c73b297..805a207 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -4001,6 +4001,9 @@ PyInit__testcapi(void)
if (_PyTestCapi_Init_Dict(m) < 0) {
return NULL;
}
+ if (_PyTestCapi_Init_Set(m) < 0) {
+ return NULL;
+ }
if (_PyTestCapi_Init_Structmember(m) < 0) {
return NULL;
}
diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj
index 1843b58..8c0d7a5 100644
--- a/PCbuild/_testcapi.vcxproj
+++ b/PCbuild/_testcapi.vcxproj
@@ -102,6 +102,7 @@
<ClCompile Include="..\Modules\_testcapi\abstract.c" />
<ClCompile Include="..\Modules\_testcapi\unicode.c" />
<ClCompile Include="..\Modules\_testcapi\dict.c" />
+ <ClCompile Include="..\Modules\_testcapi\set.c" />
<ClCompile Include="..\Modules\_testcapi\pytime.c" />
<ClCompile Include="..\Modules\_testcapi\datetime.c" />
<ClCompile Include="..\Modules\_testcapi\docstring.c" />