Skip to content

bpo-37191: Move PEP 590 vectorcall tests #13892

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions Lib/test/test_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,5 +475,128 @@ def __index__(self):
# called, which changes the keywords dict.
compile("pass", "", "exec", x, **x.kwargs)


Py_TPFLAGS_HAVE_VECTORCALL = 1 << 11
Py_TPFLAGS_METHOD_DESCRIPTOR = 1 << 17


def testfunction(self):
"""some doc"""
return self


def testfunction_kw(self, *, kw):
"""some doc"""
return self


class TestPEP590(unittest.TestCase):

def test_method_descriptor_flag(self):
import functools
cached = functools.lru_cache(1)(testfunction)

self.assertFalse(type(repr).__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)
self.assertTrue(type(list.append).__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)
self.assertTrue(type(list.__add__).__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)
self.assertTrue(type(testfunction).__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)
self.assertTrue(type(cached).__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)

self.assertTrue(_testcapi.MethodDescriptorBase.__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)
self.assertTrue(_testcapi.MethodDescriptorDerived.__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)
self.assertFalse(_testcapi.MethodDescriptorNopGet.__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)

# Heap type should not inherit Py_TPFLAGS_METHOD_DESCRIPTOR
class MethodDescriptorHeap(_testcapi.MethodDescriptorBase):
pass
self.assertFalse(MethodDescriptorHeap.__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)

def test_vectorcall_flag(self):
self.assertTrue(_testcapi.MethodDescriptorBase.__flags__ & Py_TPFLAGS_HAVE_VECTORCALL)
self.assertTrue(_testcapi.MethodDescriptorDerived.__flags__ & Py_TPFLAGS_HAVE_VECTORCALL)
self.assertFalse(_testcapi.MethodDescriptorNopGet.__flags__ & Py_TPFLAGS_HAVE_VECTORCALL)
self.assertTrue(_testcapi.MethodDescriptor2.__flags__ & Py_TPFLAGS_HAVE_VECTORCALL)

# Heap type should not inherit Py_TPFLAGS_HAVE_VECTORCALL
class MethodDescriptorHeap(_testcapi.MethodDescriptorBase):
pass
self.assertFalse(MethodDescriptorHeap.__flags__ & Py_TPFLAGS_HAVE_VECTORCALL)

def test_vectorcall_override(self):
# Check that tp_call can correctly override vectorcall.
# MethodDescriptorNopGet implements tp_call but it inherits from
# MethodDescriptorBase, which implements vectorcall. Since
# MethodDescriptorNopGet returns the args tuple when called, we check
# additionally that no new tuple is created for this call.
args = tuple(range(5))
f = _testcapi.MethodDescriptorNopGet()
self.assertIs(f(*args), args)

def test_vectorcall(self):
# Test a bunch of different ways to call objects:
# 1. vectorcall using PyVectorcall_Call()
# (only for objects that support vectorcall directly)
# 2. normal call
# 3. vectorcall using _PyObject_Vectorcall()
# 4. call as bound method
# 5. call using functools.partial

# A list of (function, args, kwargs, result) calls to test
calls = [(len, (range(42),), {}, 42),
(list.append, ([], 0), {}, None),
([].append, (0,), {}, None),
(sum, ([36],), {"start":6}, 42),
(testfunction, (42,), {}, 42),
(testfunction_kw, (42,), {"kw":None}, 42),
(_testcapi.MethodDescriptorBase(), (0,), {}, True),
(_testcapi.MethodDescriptorDerived(), (0,), {}, True),
(_testcapi.MethodDescriptor2(), (0,), {}, False)]

from _testcapi import pyobject_vectorcall, pyvectorcall_call
from types import MethodType
from functools import partial

def vectorcall(func, args, kwargs):
args = *args, *kwargs.values()
kwnames = tuple(kwargs)
return pyobject_vectorcall(func, args, kwnames)

for (func, args, kwargs, expected) in calls:
with self.subTest(str(func)):
if not kwargs:
self.assertEqual(expected, pyvectorcall_call(func, args))
self.assertEqual(expected, pyvectorcall_call(func, args, kwargs))

# Add derived classes (which do not support vectorcall directly,
# but do support all other ways of calling).

class MethodDescriptorHeap(_testcapi.MethodDescriptorBase):
pass

class MethodDescriptorOverridden(_testcapi.MethodDescriptorBase):
def __call__(self, n):
return 'new'

calls += [
(MethodDescriptorHeap(), (0,), {}, True),
(MethodDescriptorOverridden(), (0,), {}, 'new'),
]

for (func, args, kwargs, expected) in calls:
with self.subTest(str(func)):
args1 = args[1:]
meth = MethodType(func, args[0])
wrapped = partial(func)
if not kwargs:
self.assertEqual(expected, func(*args))
self.assertEqual(expected, pyobject_vectorcall(func, args, None))
self.assertEqual(expected, meth(*args1))
self.assertEqual(expected, wrapped(*args))
self.assertEqual(expected, func(*args, **kwargs))
self.assertEqual(expected, vectorcall(func, args, kwargs))
self.assertEqual(expected, meth(*args1, **kwargs))
self.assertEqual(expected, wrapped(*args, **kwargs))


if __name__ == "__main__":
unittest.main()
115 changes: 0 additions & 115 deletions Lib/test/test_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,11 @@
# Were we compiled --with-pydebug or with #define Py_DEBUG?
Py_DEBUG = hasattr(sys, 'gettotalrefcount')

Py_TPFLAGS_HAVE_VECTORCALL = 1 << 11
Py_TPFLAGS_METHOD_DESCRIPTOR = 1 << 17


def testfunction(self):
"""some doc"""
return self

def testfunction_kw(self, *, kw):
"""some doc"""
return self


class InstanceMethod:
id = _testcapi.instancemethod(id)
Expand Down Expand Up @@ -471,114 +464,6 @@ def test_pendingcalls_non_threaded(self):
self.pendingcalls_wait(l, n)


class TestPEP590(unittest.TestCase):

def test_method_descriptor_flag(self):
import functools
cached = functools.lru_cache(1)(testfunction)

self.assertFalse(type(repr).__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)
self.assertTrue(type(list.append).__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)
self.assertTrue(type(list.__add__).__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)
self.assertTrue(type(testfunction).__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)
self.assertTrue(type(cached).__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)

self.assertTrue(_testcapi.MethodDescriptorBase.__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)
self.assertTrue(_testcapi.MethodDescriptorDerived.__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)
self.assertFalse(_testcapi.MethodDescriptorNopGet.__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)

# Heap type should not inherit Py_TPFLAGS_METHOD_DESCRIPTOR
class MethodDescriptorHeap(_testcapi.MethodDescriptorBase):
pass
self.assertFalse(MethodDescriptorHeap.__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)

def test_vectorcall_flag(self):
self.assertTrue(_testcapi.MethodDescriptorBase.__flags__ & Py_TPFLAGS_HAVE_VECTORCALL)
self.assertTrue(_testcapi.MethodDescriptorDerived.__flags__ & Py_TPFLAGS_HAVE_VECTORCALL)
self.assertFalse(_testcapi.MethodDescriptorNopGet.__flags__ & Py_TPFLAGS_HAVE_VECTORCALL)
self.assertTrue(_testcapi.MethodDescriptor2.__flags__ & Py_TPFLAGS_HAVE_VECTORCALL)

# Heap type should not inherit Py_TPFLAGS_HAVE_VECTORCALL
class MethodDescriptorHeap(_testcapi.MethodDescriptorBase):
pass
self.assertFalse(MethodDescriptorHeap.__flags__ & Py_TPFLAGS_HAVE_VECTORCALL)

def test_vectorcall_override(self):
# Check that tp_call can correctly override vectorcall.
# MethodDescriptorNopGet implements tp_call but it inherits from
# MethodDescriptorBase, which implements vectorcall. Since
# MethodDescriptorNopGet returns the args tuple when called, we check
# additionally that no new tuple is created for this call.
args = tuple(range(5))
f = _testcapi.MethodDescriptorNopGet()
self.assertIs(f(*args), args)

def test_vectorcall(self):
# Test a bunch of different ways to call objects:
# 1. vectorcall using PyVectorcall_Call()
# (only for objects that support vectorcall directly)
# 2. normal call
# 3. vectorcall using _PyObject_Vectorcall()
# 4. call as bound method
# 5. call using functools.partial

# A list of (function, args, kwargs, result) calls to test
calls = [(len, (range(42),), {}, 42),
(list.append, ([], 0), {}, None),
([].append, (0,), {}, None),
(sum, ([36],), {"start":6}, 42),
(testfunction, (42,), {}, 42),
(testfunction_kw, (42,), {"kw":None}, 42),
(_testcapi.MethodDescriptorBase(), (0,), {}, True),
(_testcapi.MethodDescriptorDerived(), (0,), {}, True),
(_testcapi.MethodDescriptor2(), (0,), {}, False)]

from _testcapi import pyobject_vectorcall, pyvectorcall_call
from types import MethodType
from functools import partial

def vectorcall(func, args, kwargs):
args = *args, *kwargs.values()
kwnames = tuple(kwargs)
return pyobject_vectorcall(func, args, kwnames)

for (func, args, kwargs, expected) in calls:
with self.subTest(str(func)):
if not kwargs:
self.assertEqual(expected, pyvectorcall_call(func, args))
self.assertEqual(expected, pyvectorcall_call(func, args, kwargs))

# Add derived classes (which do not support vectorcall directly,
# but do support all other ways of calling).

class MethodDescriptorHeap(_testcapi.MethodDescriptorBase):
pass

class MethodDescriptorOverridden(_testcapi.MethodDescriptorBase):
def __call__(self, n):
return 'new'

calls += [
(MethodDescriptorHeap(), (0,), {}, True),
(MethodDescriptorOverridden(), (0,), {}, 'new'),
]

for (func, args, kwargs, expected) in calls:
with self.subTest(str(func)):
args1 = args[1:]
meth = MethodType(func, args[0])
wrapped = partial(func)
if not kwargs:
self.assertEqual(expected, func(*args))
self.assertEqual(expected, pyobject_vectorcall(func, args, None))
self.assertEqual(expected, meth(*args1))
self.assertEqual(expected, wrapped(*args))
self.assertEqual(expected, func(*args, **kwargs))
self.assertEqual(expected, vectorcall(func, args, kwargs))
self.assertEqual(expected, meth(*args1, **kwargs))
self.assertEqual(expected, wrapped(*args, **kwargs))


class SubinterpreterTest(unittest.TestCase):

def test_subinterps(self):
Expand Down