]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.12] gh-110525: Add CAPI tests for set and frozenset objects (GH-110526). (GH-110547)
authorNikita Sobolev <mail@sobolevn.me>
Mon, 9 Oct 2023 10:04:09 +0000 (13:04 +0300)
committerGitHub <noreply@github.com>
Mon, 9 Oct 2023 10:04:09 +0000 (10:04 +0000)
(cherry picked from commit c49edd7d9c5395a6a6696a4846f56bc8b2b22792)

Lib/test/test_capi/test_set.py [new file with mode: 0644]
Modules/Setup.stdlib.in
Modules/_testcapi/parts.h
Modules/_testcapi/set.c [new file with mode: 0644]
Modules/_testcapimodule.c
PCbuild/_testcapi.vcxproj

diff --git a/Lib/test/test_capi/test_set.py b/Lib/test/test_capi/test_set.py
new file mode 100644 (file)
index 0000000..e9165e7
--- /dev/null
@@ -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)
index 3e01e25056dfa886ff615017414cb02b70e87a62..140245d474cd4175c9dd3a34f5caa917b65fafe1 100644 (file)
 @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.
index f37be9b67a3d786abfc35180763c3c18e385e35b..9bbb54171b0a939676309ca2e3431a2be3abe904 100644 (file)
@@ -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 (file)
index 0000000..f68a185
--- /dev/null
@@ -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;
+}
index c73b297adaa76dc4946406e7400be892a923b02a..805a20733cdbfbf84fe4625f380eeaf4819c6509 100644 (file)
@@ -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;
     }
index 1843b58de9d151843c7e8002453e358901907ba5..8c0d7a502dd468b58158c9a0b06e680755f14236 100644 (file)
     <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" />