Skip to content

Commit

Permalink
pythongh-93649: Add Modules/_testcapi/function.c file
Browse files Browse the repository at this point in the history
* Move PyFunction C API tests to a new file.
* Add Lib/test/test_capi/test_function.py.
* Move tests from test_capi.test_misc to test_capi.test_function.
  • Loading branch information
vstinner committed Jan 31, 2025
1 parent 79f85a0 commit 0a5c535
Show file tree
Hide file tree
Showing 8 changed files with 465 additions and 413 deletions.
312 changes: 312 additions & 0 deletions Lib/test/test_capi/test_function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
import unittest
from test.support import import_helper


_testcapi = import_helper.import_module('_testcapi')


class FunctionTest(unittest.TestCase):
def test_function_get_code(self):
# Test PyFunction_GetCode()
import types

def some():
pass

code = _testcapi.function_get_code(some)
self.assertIsInstance(code, types.CodeType)
self.assertEqual(code, some.__code__)

with self.assertRaises(SystemError):
_testcapi.function_get_code(None) # not a function

def test_function_get_globals(self):
# Test PyFunction_GetGlobals()
def some():
pass

globals_ = _testcapi.function_get_globals(some)
self.assertIsInstance(globals_, dict)
self.assertEqual(globals_, some.__globals__)

with self.assertRaises(SystemError):
_testcapi.function_get_globals(None) # not a function

def test_function_get_module(self):
# Test PyFunction_GetModule()
def some():
pass

module = _testcapi.function_get_module(some)
self.assertIsInstance(module, str)
self.assertEqual(module, some.__module__)

with self.assertRaises(SystemError):
_testcapi.function_get_module(None) # not a function

def test_function_get_defaults(self):
# Test PyFunction_GetDefaults()
def some(
pos_only1, pos_only2='p',
/,
zero=0, optional=None,
*,
kw1,
kw2=True,
):
pass

defaults = _testcapi.function_get_defaults(some)
self.assertEqual(defaults, ('p', 0, None))
self.assertEqual(defaults, some.__defaults__)

with self.assertRaises(SystemError):
_testcapi.function_get_defaults(None) # not a function

def test_function_set_defaults(self):
# Test PyFunction_SetDefaults()
def some(
pos_only1, pos_only2='p',
/,
zero=0, optional=None,
*,
kw1,
kw2=True,
):
pass

old_defaults = ('p', 0, None)
self.assertEqual(_testcapi.function_get_defaults(some), old_defaults)
self.assertEqual(some.__defaults__, old_defaults)

with self.assertRaises(SystemError):
_testcapi.function_set_defaults(some, 1) # not tuple or None
self.assertEqual(_testcapi.function_get_defaults(some), old_defaults)
self.assertEqual(some.__defaults__, old_defaults)

with self.assertRaises(SystemError):
_testcapi.function_set_defaults(1, ()) # not a function
self.assertEqual(_testcapi.function_get_defaults(some), old_defaults)
self.assertEqual(some.__defaults__, old_defaults)

new_defaults = ('q', 1, None)
_testcapi.function_set_defaults(some, new_defaults)
self.assertEqual(_testcapi.function_get_defaults(some), new_defaults)
self.assertEqual(some.__defaults__, new_defaults)

# Empty tuple is fine:
new_defaults = ()
_testcapi.function_set_defaults(some, new_defaults)
self.assertEqual(_testcapi.function_get_defaults(some), new_defaults)
self.assertEqual(some.__defaults__, new_defaults)

class tuplesub(tuple): ... # tuple subclasses must work

new_defaults = tuplesub(((1, 2), ['a', 'b'], None))
_testcapi.function_set_defaults(some, new_defaults)
self.assertEqual(_testcapi.function_get_defaults(some), new_defaults)
self.assertEqual(some.__defaults__, new_defaults)

# `None` is special, it sets `defaults` to `NULL`,
# it needs special handling in `_testcapi`:
_testcapi.function_set_defaults(some, None)
self.assertEqual(_testcapi.function_get_defaults(some), None)
self.assertEqual(some.__defaults__, None)

def test_function_get_kw_defaults(self):
# Test PyFunction_GetKwDefaults()
def some(
pos_only1, pos_only2='p',
/,
zero=0, optional=None,
*,
kw1,
kw2=True,
):
pass

defaults = _testcapi.function_get_kw_defaults(some)
self.assertEqual(defaults, {'kw2': True})
self.assertEqual(defaults, some.__kwdefaults__)

with self.assertRaises(SystemError):
_testcapi.function_get_kw_defaults(None) # not a function

def test_function_set_kw_defaults(self):
# Test PyFunction_SetKwDefaults()
def some(
pos_only1, pos_only2='p',
/,
zero=0, optional=None,
*,
kw1,
kw2=True,
):
pass

old_defaults = {'kw2': True}
self.assertEqual(_testcapi.function_get_kw_defaults(some), old_defaults)
self.assertEqual(some.__kwdefaults__, old_defaults)

with self.assertRaises(SystemError):
_testcapi.function_set_kw_defaults(some, 1) # not dict or None
self.assertEqual(_testcapi.function_get_kw_defaults(some), old_defaults)
self.assertEqual(some.__kwdefaults__, old_defaults)

with self.assertRaises(SystemError):
_testcapi.function_set_kw_defaults(1, {}) # not a function
self.assertEqual(_testcapi.function_get_kw_defaults(some), old_defaults)
self.assertEqual(some.__kwdefaults__, old_defaults)

new_defaults = {'kw2': (1, 2, 3)}
_testcapi.function_set_kw_defaults(some, new_defaults)
self.assertEqual(_testcapi.function_get_kw_defaults(some), new_defaults)
self.assertEqual(some.__kwdefaults__, new_defaults)

# Empty dict is fine:
new_defaults = {}
_testcapi.function_set_kw_defaults(some, new_defaults)
self.assertEqual(_testcapi.function_get_kw_defaults(some), new_defaults)
self.assertEqual(some.__kwdefaults__, new_defaults)

class dictsub(dict): ... # dict subclasses must work

new_defaults = dictsub({'kw2': None})
_testcapi.function_set_kw_defaults(some, new_defaults)
self.assertEqual(_testcapi.function_get_kw_defaults(some), new_defaults)
self.assertEqual(some.__kwdefaults__, new_defaults)

# `None` is special, it sets `kwdefaults` to `NULL`,
# it needs special handling in `_testcapi`:
_testcapi.function_set_kw_defaults(some, None)
self.assertEqual(_testcapi.function_get_kw_defaults(some), None)
self.assertEqual(some.__kwdefaults__, None)

def test_function_get_closure(self):
# Test PyFunction_GetClosure()
from types import CellType

def regular_function(): ...
def unused_one_level(arg1):
def inner(arg2, arg3): ...
return inner
def unused_two_levels(arg1, arg2):
def decorator(arg3, arg4):
def inner(arg5, arg6): ...
return inner
return decorator
def with_one_level(arg1):
def inner(arg2, arg3):
return arg1 + arg2 + arg3
return inner
def with_two_levels(arg1, arg2):
def decorator(arg3, arg4):
def inner(arg5, arg6):
return arg1 + arg2 + arg3 + arg4 + arg5 + arg6
return inner
return decorator

# Functions without closures:
self.assertIsNone(_testcapi.function_get_closure(regular_function))
self.assertIsNone(regular_function.__closure__)

func = unused_one_level(1)
closure = _testcapi.function_get_closure(func)
self.assertIsNone(closure)
self.assertIsNone(func.__closure__)

func = unused_two_levels(1, 2)(3, 4)
closure = _testcapi.function_get_closure(func)
self.assertIsNone(closure)
self.assertIsNone(func.__closure__)

# Functions with closures:
func = with_one_level(5)
closure = _testcapi.function_get_closure(func)
self.assertEqual(closure, func.__closure__)
self.assertIsInstance(closure, tuple)
self.assertEqual(len(closure), 1)
self.assertEqual(len(closure), len(func.__code__.co_freevars))
for cell in closure:
self.assertIsInstance(cell, CellType)
self.assertTrue(closure[0].cell_contents, 5)

func = with_two_levels(1, 2)(3, 4)
closure = _testcapi.function_get_closure(func)
self.assertEqual(closure, func.__closure__)
self.assertIsInstance(closure, tuple)
self.assertEqual(len(closure), 4)
self.assertEqual(len(closure), len(func.__code__.co_freevars))
for cell in closure:
self.assertIsInstance(cell, CellType)
self.assertEqual([cell.cell_contents for cell in closure],
[1, 2, 3, 4])

def test_function_get_closure_error(self):
# Test PyFunction_GetClosure()
with self.assertRaises(SystemError):
_testcapi.function_get_closure(1)
with self.assertRaises(SystemError):
_testcapi.function_get_closure(None)

def test_function_set_closure(self):
# Test PyFunction_SetClosure()
from types import CellType

def function_without_closure(): ...
def function_with_closure(arg):
def inner():
return arg
return inner

func = function_without_closure
_testcapi.function_set_closure(func, (CellType(1), CellType(1)))
closure = _testcapi.function_get_closure(func)
self.assertEqual([c.cell_contents for c in closure], [1, 1])
self.assertEqual([c.cell_contents for c in func.__closure__], [1, 1])

func = function_with_closure(1)
_testcapi.function_set_closure(func,
(CellType(1), CellType(2), CellType(3)))
closure = _testcapi.function_get_closure(func)
self.assertEqual([c.cell_contents for c in closure], [1, 2, 3])
self.assertEqual([c.cell_contents for c in func.__closure__], [1, 2, 3])

def test_function_set_closure_none(self):
# Test PyFunction_SetClosure()
def function_without_closure(): ...
def function_with_closure(arg):
def inner():
return arg
return inner

_testcapi.function_set_closure(function_without_closure, None)
self.assertIsNone(
_testcapi.function_get_closure(function_without_closure))
self.assertIsNone(function_without_closure.__closure__)

_testcapi.function_set_closure(function_with_closure, None)
self.assertIsNone(
_testcapi.function_get_closure(function_with_closure))
self.assertIsNone(function_with_closure.__closure__)

def test_function_set_closure_errors(self):
# Test PyFunction_SetClosure()
def function_without_closure(): ...

with self.assertRaises(SystemError):
_testcapi.function_set_closure(None, ()) # not a function

with self.assertRaises(SystemError):
_testcapi.function_set_closure(function_without_closure, 1)
self.assertIsNone(function_without_closure.__closure__) # no change

# NOTE: this works, but goes against the docs:
_testcapi.function_set_closure(function_without_closure, (1, 2))
self.assertEqual(
_testcapi.function_get_closure(function_without_closure), (1, 2))
self.assertEqual(function_without_closure.__closure__, (1, 2))


if __name__ == "__main__":
unittest.main()
Loading

0 comments on commit 0a5c535

Please sign in to comment.