Skip to content

Commit 8182319

Browse files
authored
gh-94808: add tests covering PyFunction_{Get,Set}Closure (GH-99429)
1 parent 1530932 commit 8182319

File tree

2 files changed

+148
-1
lines changed

2 files changed

+148
-1
lines changed

Lib/test/test_capi/test_misc.py

+119-1
Original file line numberDiff line numberDiff line change
@@ -1170,7 +1170,6 @@ class MyType:
11701170
self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')
11711171

11721172

1173-
11741173
@requires_limited_api
11751174
class TestHeapTypeRelative(unittest.TestCase):
11761175
"""Test API for extending opaque types (PEP 697)"""
@@ -1326,6 +1325,125 @@ def test_pyobject_getitemdata_error(self):
13261325
_testcapi.pyobject_getitemdata(0)
13271326

13281327

1328+
def test_function_get_closure(self):
1329+
from types import CellType
1330+
1331+
def regular_function(): ...
1332+
def unused_one_level(arg1):
1333+
def inner(arg2, arg3): ...
1334+
return inner
1335+
def unused_two_levels(arg1, arg2):
1336+
def decorator(arg3, arg4):
1337+
def inner(arg5, arg6): ...
1338+
return inner
1339+
return decorator
1340+
def with_one_level(arg1):
1341+
def inner(arg2, arg3):
1342+
return arg1 + arg2 + arg3
1343+
return inner
1344+
def with_two_levels(arg1, arg2):
1345+
def decorator(arg3, arg4):
1346+
def inner(arg5, arg6):
1347+
return arg1 + arg2 + arg3 + arg4 + arg5 + arg6
1348+
return inner
1349+
return decorator
1350+
1351+
# Functions without closures:
1352+
self.assertIsNone(_testcapi.function_get_closure(regular_function))
1353+
self.assertIsNone(regular_function.__closure__)
1354+
1355+
func = unused_one_level(1)
1356+
closure = _testcapi.function_get_closure(func)
1357+
self.assertIsNone(closure)
1358+
self.assertIsNone(func.__closure__)
1359+
1360+
func = unused_two_levels(1, 2)(3, 4)
1361+
closure = _testcapi.function_get_closure(func)
1362+
self.assertIsNone(closure)
1363+
self.assertIsNone(func.__closure__)
1364+
1365+
# Functions with closures:
1366+
func = with_one_level(5)
1367+
closure = _testcapi.function_get_closure(func)
1368+
self.assertEqual(closure, func.__closure__)
1369+
self.assertIsInstance(closure, tuple)
1370+
self.assertEqual(len(closure), 1)
1371+
self.assertEqual(len(closure), len(func.__code__.co_freevars))
1372+
self.assertTrue(all(isinstance(cell, CellType) for cell in closure))
1373+
self.assertTrue(closure[0].cell_contents, 5)
1374+
1375+
func = with_two_levels(1, 2)(3, 4)
1376+
closure = _testcapi.function_get_closure(func)
1377+
self.assertEqual(closure, func.__closure__)
1378+
self.assertIsInstance(closure, tuple)
1379+
self.assertEqual(len(closure), 4)
1380+
self.assertEqual(len(closure), len(func.__code__.co_freevars))
1381+
self.assertTrue(all(isinstance(cell, CellType) for cell in closure))
1382+
self.assertEqual([cell.cell_contents for cell in closure],
1383+
[1, 2, 3, 4])
1384+
1385+
def test_function_get_closure_error(self):
1386+
with self.assertRaises(SystemError):
1387+
_testcapi.function_get_closure(1)
1388+
with self.assertRaises(SystemError):
1389+
_testcapi.function_get_closure(None)
1390+
1391+
def test_function_set_closure(self):
1392+
from types import CellType
1393+
1394+
def function_without_closure(): ...
1395+
def function_with_closure(arg):
1396+
def inner():
1397+
return arg
1398+
return inner
1399+
1400+
func = function_without_closure
1401+
_testcapi.function_set_closure(func, (CellType(1), CellType(1)))
1402+
closure = _testcapi.function_get_closure(func)
1403+
self.assertEqual([c.cell_contents for c in closure], [1, 1])
1404+
self.assertEqual([c.cell_contents for c in func.__closure__], [1, 1])
1405+
1406+
func = function_with_closure(1)
1407+
_testcapi.function_set_closure(func,
1408+
(CellType(1), CellType(2), CellType(3)))
1409+
closure = _testcapi.function_get_closure(func)
1410+
self.assertEqual([c.cell_contents for c in closure], [1, 2, 3])
1411+
self.assertEqual([c.cell_contents for c in func.__closure__], [1, 2, 3])
1412+
1413+
def test_function_set_closure_none(self):
1414+
def function_without_closure(): ...
1415+
def function_with_closure(arg):
1416+
def inner():
1417+
return arg
1418+
return inner
1419+
1420+
_testcapi.function_set_closure(function_without_closure, None)
1421+
self.assertIsNone(
1422+
_testcapi.function_get_closure(function_without_closure))
1423+
self.assertIsNone(function_without_closure.__closure__)
1424+
1425+
_testcapi.function_set_closure(function_with_closure, None)
1426+
self.assertIsNone(
1427+
_testcapi.function_get_closure(function_with_closure))
1428+
self.assertIsNone(function_with_closure.__closure__)
1429+
1430+
def test_function_set_closure_errors(self):
1431+
def function_without_closure(): ...
1432+
1433+
with self.assertRaises(SystemError):
1434+
_testcapi.function_set_closure(None, ()) # not a function
1435+
1436+
with self.assertRaises(SystemError):
1437+
_testcapi.function_set_closure(function_without_closure, 1)
1438+
self.assertIsNone(function_without_closure.__closure__) # no change
1439+
1440+
# NOTE: this works, but goes against the docs:
1441+
_testcapi.function_set_closure(function_without_closure, (1, 2))
1442+
self.assertEqual(
1443+
_testcapi.function_get_closure(function_without_closure), (1, 2))
1444+
self.assertEqual(function_without_closure.__closure__, (1, 2))
1445+
1446+
13291447
class TestPendingCalls(unittest.TestCase):
13301448

13311449
# See the comment in ceval.c (at the "handle_eval_breaker" label)

Modules/_testcapimodule.c

+29
Original file line numberDiff line numberDiff line change
@@ -3094,6 +3094,33 @@ function_set_kw_defaults(PyObject *self, PyObject *args)
30943094
Py_RETURN_NONE;
30953095
}
30963096

3097+
static PyObject *
3098+
function_get_closure(PyObject *self, PyObject *func)
3099+
{
3100+
PyObject *closure = PyFunction_GetClosure(func);
3101+
if (closure != NULL) {
3102+
return Py_NewRef(closure);
3103+
} else if (PyErr_Occurred()) {
3104+
return NULL;
3105+
} else {
3106+
Py_RETURN_NONE; // This can happen when `closure` is set to `None`
3107+
}
3108+
}
3109+
3110+
static PyObject *
3111+
function_set_closure(PyObject *self, PyObject *args)
3112+
{
3113+
PyObject *func = NULL, *closure = NULL;
3114+
if (!PyArg_ParseTuple(args, "OO", &func, &closure)) {
3115+
return NULL;
3116+
}
3117+
int result = PyFunction_SetClosure(func, closure);
3118+
if (result == -1) {
3119+
return NULL;
3120+
}
3121+
Py_RETURN_NONE;
3122+
}
3123+
30973124
static PyObject *
30983125
check_pyimport_addmodule(PyObject *self, PyObject *args)
30993126
{
@@ -3379,6 +3406,8 @@ static PyMethodDef TestMethods[] = {
33793406
{"function_set_defaults", function_set_defaults, METH_VARARGS, NULL},
33803407
{"function_get_kw_defaults", function_get_kw_defaults, METH_O, NULL},
33813408
{"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL},
3409+
{"function_get_closure", function_get_closure, METH_O, NULL},
3410+
{"function_set_closure", function_set_closure, METH_VARARGS, NULL},
33823411
{"check_pyimport_addmodule", check_pyimport_addmodule, METH_VARARGS},
33833412
{"test_weakref_capi", test_weakref_capi, METH_NOARGS},
33843413
{NULL, NULL} /* sentinel */

0 commit comments

Comments
 (0)