From c49edd7d9c5395a6a6696a4846f56bc8b2b22792 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Mon, 9 Oct 2023 11:57:48 +0300 Subject: [PATCH] gh-110525: Add CAPI tests for set and frozenset objects (GH-110526) --- Lib/test/test_capi/test_set.py | 215 +++++++++++++++++++++++++++++++++ Modules/Setup.stdlib.in | 2 +- Modules/_testcapi/parts.h | 1 + Modules/_testcapi/set.c | 162 +++++++++++++++++++++++++ Modules/_testcapimodule.c | 3 + PCbuild/_testcapi.vcxproj | 1 + 6 files changed, 383 insertions(+), 1 deletion(-) create mode 100644 Lib/test/test_capi/test_set.py create mode 100644 Modules/_testcapi/set.c diff --git a/Lib/test/test_capi/test_set.py b/Lib/test/test_capi/test_set.py new file mode 100644 index 00000000000..e9165e7e680 --- /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 7b3216a50bb..8428142a852 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -159,7 +159,7 @@ @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.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/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/pyatomic.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/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/pyatomic.c _testcapi/pyos.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h index 24abe54814e..acdba86504f 100644 --- a/Modules/_testcapi/parts.h +++ b/Modules/_testcapi/parts.h @@ -34,6 +34,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 00000000000..f68a1859698 --- /dev/null +++ b/Modules/_testcapi/set.c @@ -0,0 +1,162 @@ +#include // 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 a46d986c18e..ce3d0b1b1b0 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3981,6 +3981,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 0a02929db43..0f33c5a76ad 100644 --- a/PCbuild/_testcapi.vcxproj +++ b/PCbuild/_testcapi.vcxproj @@ -102,6 +102,7 @@ +