ext/Objects/typeobject-test.cpp (2,979 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
#include "Python.h"
#include "gtest/gtest.h"
#include "structmember.h"
#include "capi-fixture.h"
#include "capi-testing.h"
namespace py {
namespace testing {
using TypeExtensionApiTest = ExtensionApi;
using TypeExtensionApiDeathTest = ExtensionApi;
// Common deallocation function for types with only primitive members
static void deallocLeafObject(PyObject* self) {
PyTypeObject* type = Py_TYPE(self);
PyObject_Del(self);
Py_DECREF(type);
}
TEST_F(TypeExtensionApiTest, PyTypeCheckOnLong) {
PyObjectPtr pylong(PyLong_FromLong(10));
EXPECT_FALSE(PyType_Check(pylong));
EXPECT_FALSE(PyType_CheckExact(pylong));
}
TEST_F(TypeExtensionApiTest, PyTypeCheckOnType) {
PyObjectPtr pylong(PyLong_FromLong(10));
PyObjectPtr pylong_type(PyObject_Type(pylong));
EXPECT_TRUE(PyType_Check(pylong_type));
EXPECT_TRUE(PyType_CheckExact(pylong_type));
}
TEST_F(TypeExtensionApiTest,
PyTypeGenericNewWithTypeWithoutNativeDataReturnsPyObject) {
PyType_Slot slots[] = {
{Py_tp_new, reinterpret_cast<void*>(PyType_GenericNew)},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"foo.Bar", 0, 0, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, slots,
};
PyObjectPtr ext_type(PyType_FromSpec(&spec));
ASSERT_NE(ext_type, nullptr);
ASSERT_TRUE(PyType_CheckExact(ext_type));
EXPECT_EQ(PyType_GetSlot(ext_type.asTypeObject(), Py_tp_new),
reinterpret_cast<void*>(PyType_GenericNew));
auto new_slot = reinterpret_cast<newfunc>(
PyType_GetSlot(ext_type.asTypeObject(), Py_tp_new));
PyObjectPtr args(PyTuple_New(0));
PyObjectPtr kwargs(PyDict_New());
PyObjectPtr result(new_slot(ext_type.asTypeObject(), args, kwargs));
ASSERT_NE(result, nullptr);
EXPECT_EQ(PyObject_IsInstance(result, ext_type), 1);
// Ensure that a managed subtype of the native type can be created
// via its __new__ function.
testing::moduleSet("__main__", "Bar", ext_type);
EXPECT_EQ(PyRun_SimpleString(R"(
class SubBar(Bar):
pass
s = SubBar()
)"),
0);
}
TEST_F(TypeExtensionApiDeathTest, GetFlagsFromManagedTypePyro) {
PyRun_SimpleString(R"(class Foo: pass)");
PyObjectPtr foo_type(testing::mainModuleGet("Foo"));
ASSERT_TRUE(PyType_CheckExact(foo_type));
EXPECT_DEATH(
PyType_GetFlags(reinterpret_cast<PyTypeObject*>(foo_type.get())),
"unimplemented: GetFlags from types initialized through Python code");
}
TEST_F(TypeExtensionApiTest, GetFlagsFromExtensionTypeReturnsSetFlags) {
PyType_Slot slots[] = {
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"foo.Bar", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
ASSERT_TRUE(PyType_CheckExact(type));
EXPECT_TRUE(PyType_GetFlags(reinterpret_cast<PyTypeObject*>(type.get())) &
Py_TPFLAGS_DEFAULT);
EXPECT_TRUE(PyType_GetFlags(reinterpret_cast<PyTypeObject*>(type.get())) &
Py_TPFLAGS_READY);
EXPECT_TRUE(PyType_GetFlags(reinterpret_cast<PyTypeObject*>(type.get())) &
Py_TPFLAGS_HEAPTYPE);
}
TEST_F(TypeExtensionApiTest, FromSpecCreatesRuntimeType) {
PyType_Slot slots[] = {
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"foo.Bar", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
ASSERT_TRUE(PyType_CheckExact(type));
testing::moduleSet("__main__", "Empty", type);
PyRun_SimpleString("x = Empty");
PyObjectPtr result(testing::mainModuleGet("x"));
EXPECT_TRUE(PyType_CheckExact(result));
PyObjectPtr module(PyObject_GetAttrString(result, "__module__"));
EXPECT_TRUE(isUnicodeEqualsCStr(module, "foo"));
PyObjectPtr name(PyObject_GetAttrString(result, "__name__"));
EXPECT_TRUE(isUnicodeEqualsCStr(name, "Bar"));
PyObjectPtr qualname(PyObject_GetAttrString(result, "__qualname__"));
EXPECT_TRUE(isUnicodeEqualsCStr(qualname, "Bar"));
}
TEST_F(TypeExtensionApiTest, FromSpecWithInvalidSlotRaisesError) {
PyType_Slot slots[] = {
{-1, nullptr},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"foo.Bar", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
ASSERT_EQ(PyType_FromSpec(&spec), nullptr);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_RuntimeError));
// TODO(eelizondo): Check that error matches with "invalid slot offset"
}
TEST_F(TypeExtensionApiTest,
FromSpecWithZeroBasicSizeAndItemSetsTpNewOfManagedTypePyro) {
PyType_Slot slots[] = {
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"foo.Bar", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr ext_type(PyType_FromSpec(&spec));
ASSERT_NE(ext_type, nullptr);
ASSERT_TRUE(PyType_CheckExact(ext_type));
PyRun_SimpleString(R"(class D: pass)");
PyObjectPtr managed_type(mainModuleGet("D"));
ASSERT_NE(PyType_GetSlot(managed_type.asTypeObject(), Py_tp_new), nullptr);
// Instances of `ext_type` does not cause any memory leak.
auto new_slot = reinterpret_cast<newfunc>(
PyType_GetSlot(ext_type.asTypeObject(), Py_tp_new));
PyObjectPtr args(PyTuple_New(0));
PyObjectPtr kwargs(PyDict_New());
PyObjectPtr result(new_slot(ext_type.asTypeObject(), args, kwargs));
ASSERT_NE(result, nullptr);
EXPECT_EQ(PyObject_IsInstance(result, ext_type), 1);
}
TEST_F(TypeExtensionApiTest,
FromSpecWithNonZeroBasicSizeAndItemSetsCustomTpNewPyro) {
PyType_Slot slots[] = {
{0, nullptr},
};
struct BarState {
PyObject_HEAD
int foo;
};
static PyType_Spec spec;
spec = {
"foo.Bar", sizeof(BarState), 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr ext_type(PyType_FromSpec(&spec));
ASSERT_NE(ext_type, nullptr);
ASSERT_TRUE(PyType_CheckExact(ext_type));
PyRun_SimpleString(R"(class D: pass)");
PyObjectPtr managed_type(mainModuleGet("D"));
ASSERT_NE(PyType_GetSlot(managed_type.asTypeObject(), Py_tp_new), nullptr);
EXPECT_NE(PyType_GetSlot(managed_type.asTypeObject(), Py_tp_new),
PyType_GetSlot(ext_type.asTypeObject(), Py_tp_new));
// Instances of `ext_type` does not cause any memory leak.
auto new_slot = reinterpret_cast<newfunc>(
PyType_GetSlot(ext_type.asTypeObject(), Py_tp_new));
PyObjectPtr args(PyTuple_New(0));
PyObjectPtr kwargs(PyDict_New());
PyObjectPtr result(new_slot(ext_type.asTypeObject(), args, kwargs));
ASSERT_NE(result, nullptr);
EXPECT_EQ(PyObject_IsInstance(result, ext_type), 1);
}
TEST_F(TypeExtensionApiTest, CallExtensionTypeReturnsExtensionInstancePyro) {
struct BarObject {
PyObject_HEAD
int value;
};
newfunc new_func = [](PyTypeObject* type, PyObject*, PyObject*) {
void* slot = PyType_GetSlot(type, Py_tp_alloc);
return reinterpret_cast<allocfunc>(slot)(type, 0);
};
initproc init_func = [](PyObject* self, PyObject*, PyObject*) {
reinterpret_cast<BarObject*>(self)->value = 30;
return 0;
};
PyType_Slot slots[] = {
{Py_tp_alloc, reinterpret_cast<void*>(PyType_GenericAlloc)},
{Py_tp_new, reinterpret_cast<void*>(new_func)},
{Py_tp_init, reinterpret_cast<void*>(init_func)},
{Py_tp_dealloc, reinterpret_cast<void*>(deallocLeafObject)},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"foo.Bar", sizeof(BarObject), 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
ASSERT_TRUE(PyType_CheckExact(type));
testing::moduleSet("__main__", "Bar", type);
PyRun_SimpleString(R"(
bar = Bar()
)");
PyObjectPtr bar(testing::mainModuleGet("bar"));
ASSERT_NE(bar, nullptr);
BarObject* barobj = reinterpret_cast<BarObject*>(bar.get());
EXPECT_EQ(barobj->value, 30);
}
TEST_F(TypeExtensionApiTest, GenericAllocationReturnsMallocMemory) {
// These numbers determine the allocated size of the PyObject
// The values in this test are abitrary and are usally set with `sizeof(Foo)`
int basic_size = sizeof(PyObject) + 10;
int item_size = 5;
PyType_Slot slots[] = {
{Py_tp_dealloc, reinterpret_cast<void*>(deallocLeafObject)},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"foo.Bar", basic_size, item_size, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
ASSERT_TRUE(PyType_CheckExact(type));
PyObjectPtr result(PyType_GenericAlloc(
reinterpret_cast<PyTypeObject*>(type.get()), item_size));
ASSERT_NE(result, nullptr);
ASSERT_GE(Py_REFCNT(result), 1); // CPython
ASSERT_LE(Py_REFCNT(result), 2); // Pyro
EXPECT_EQ(Py_SIZE(result.get()), item_size);
}
TEST_F(TypeExtensionApiTest, GetSlotTpNewOnManagedTypeReturnsSlot) {
ASSERT_EQ(PyRun_SimpleString(R"(
class Foo:
def __new__(ty, a, b, c, d):
obj = super().__new__(ty)
obj.args = (a, b, c, d)
return obj
)"),
0);
PyObjectPtr foo(mainModuleGet("Foo"));
auto new_slot =
reinterpret_cast<newfunc>(PyType_GetSlot(foo.asTypeObject(), Py_tp_new));
ASSERT_NE(new_slot, nullptr);
PyObjectPtr one(PyLong_FromLong(1));
PyObjectPtr two(PyLong_FromLong(2));
PyObjectPtr cee(PyUnicode_FromString("cee"));
PyObjectPtr dee(PyUnicode_FromString("dee"));
PyObjectPtr args(PyTuple_Pack(2, one.get(), two.get()));
PyObjectPtr kwargs(PyDict_New());
PyDict_SetItemString(kwargs, "d", dee);
PyDict_SetItemString(kwargs, "c", cee);
PyObjectPtr result(new_slot(foo.asTypeObject(), args, kwargs));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyObject_IsInstance(result, foo), 1);
PyObjectPtr obj_args(PyObject_GetAttrString(result, "args"));
ASSERT_NE(obj_args, nullptr);
ASSERT_EQ(PyTuple_CheckExact(obj_args), 1);
ASSERT_EQ(PyTuple_Size(obj_args), 4);
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(obj_args, 0), 1));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(obj_args, 1), 2));
EXPECT_TRUE(isUnicodeEqualsCStr(PyTuple_GetItem(obj_args, 2), "cee"));
EXPECT_TRUE(isUnicodeEqualsCStr(PyTuple_GetItem(obj_args, 3), "dee"));
}
TEST_F(TypeExtensionApiTest, IsSubtypeWithSameTypeReturnsTrue) {
PyObjectPtr pylong(PyLong_FromLong(10));
PyObjectPtr pylong_type(PyObject_Type(pylong));
EXPECT_TRUE(
PyType_IsSubtype(reinterpret_cast<PyTypeObject*>(pylong_type.get()),
reinterpret_cast<PyTypeObject*>(pylong_type.get())));
}
TEST_F(TypeExtensionApiTest, IsSubtypeWithSubtypeReturnsTrue) {
EXPECT_EQ(PyRun_SimpleString("class MyFloat(float): pass"), 0);
PyObjectPtr pyfloat(PyFloat_FromDouble(1.23));
PyObjectPtr pyfloat_type(PyObject_Type(pyfloat));
PyObjectPtr myfloat_type(mainModuleGet("MyFloat"));
EXPECT_TRUE(
PyType_IsSubtype(reinterpret_cast<PyTypeObject*>(myfloat_type.get()),
reinterpret_cast<PyTypeObject*>(pyfloat_type.get())));
}
TEST_F(TypeExtensionApiTest, IsSubtypeWithDifferentTypesReturnsFalse) {
PyObjectPtr pylong(PyLong_FromLong(10));
PyObjectPtr pylong_type(PyObject_Type(pylong));
PyObjectPtr pyuni(PyUnicode_FromString("string"));
PyObjectPtr pyuni_type(PyObject_Type(pyuni));
EXPECT_FALSE(
PyType_IsSubtype(reinterpret_cast<PyTypeObject*>(pylong_type.get()),
reinterpret_cast<PyTypeObject*>(pyuni_type.get())));
}
TEST_F(TypeExtensionApiTest, PyTypeModifiedWithHeapTypeDoesNothing) {
PyRun_SimpleString(R"(
class C:
pass
)");
PyObjectPtr c(mainModuleGet("C"));
PyType_Modified(c.asTypeObject());
}
TEST_F(TypeExtensionApiTest, GetSlotFromBuiltinTypeRaisesSystemError) {
PyObjectPtr pylong(PyLong_FromLong(5));
PyObjectPtr pylong_type(PyObject_Type(pylong));
ASSERT_TRUE(
PyType_CheckExact(reinterpret_cast<PyTypeObject*>(pylong_type.get())));
EXPECT_EQ(PyType_GetSlot(reinterpret_cast<PyTypeObject*>(pylong_type.get()),
Py_tp_init),
nullptr);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_SystemError));
}
TEST_F(TypeExtensionApiDeathTest,
GetSlotFromManagedTypeReturnsFunctionPointerPyro) {
PyRun_SimpleString(R"(
class Foo:
def __init__(self):
pass
)");
PyObjectPtr foo_type(testing::mainModuleGet("Foo"));
ASSERT_TRUE(PyType_CheckExact(foo_type));
EXPECT_DEATH(PyType_GetSlot(reinterpret_cast<PyTypeObject*>(foo_type.get()),
Py_tp_init),
"Unsupported default slot");
}
TEST_F(TypeExtensionApiTest, GetUnsupportedSlotFromManagedTypeAbortsPyro) {
PyRun_SimpleString(R"(
class Foo: pass
)");
PyObjectPtr foo_type(testing::mainModuleGet("Foo"));
ASSERT_TRUE(PyType_CheckExact(foo_type));
EXPECT_DEATH(
PyType_GetSlot(reinterpret_cast<PyTypeObject*>(foo_type.get()), Py_nb_or),
"Unsupported default slot");
}
TEST_F(TypeExtensionApiTest, GetSetDescriptorTypeMatchesPyTpGetSet) {
struct BarObject {
PyObject_HEAD
long attribute;
};
getter attribute_getter = [](PyObject* self, void*) {
return PyLong_FromLong(reinterpret_cast<BarObject*>(self)->attribute);
};
setter attribute_setter = [](PyObject* self, PyObject* value, void*) {
reinterpret_cast<BarObject*>(self)->attribute = PyLong_AsLong(value);
return 0;
};
static PyGetSetDef getsets[2];
getsets[0] = {"attribute", attribute_getter, attribute_setter};
getsets[1] = {nullptr};
static PyType_Slot slots[2];
slots[0] = {Py_tp_getset, reinterpret_cast<void*>(getsets)};
slots[1] = {0, nullptr};
static PyType_Spec spec;
spec = {
"__main__.Bar", sizeof(BarObject), 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_EQ(PyType_CheckExact(type), 1);
ASSERT_EQ(moduleSet("__main__", "Bar", type), 0);
PyRun_SimpleString(R"(
import types
descrType = types.GetSetDescriptorType
tpType = type(Bar.__dict__['attribute'])
)");
PyObjectPtr descr_type(testing::mainModuleGet("descrType"));
PyObjectPtr tp_type(testing::mainModuleGet("tpType"));
ASSERT_EQ(descr_type, tp_type);
}
TEST_F(TypeExtensionApiTest, GetSlotFromNegativeSlotRaisesSystemError) {
PyRun_SimpleString(R"(
class Foo: pass
)");
PyObjectPtr foo_type(testing::mainModuleGet("Foo"));
ASSERT_TRUE(PyType_CheckExact(foo_type));
EXPECT_EQ(PyType_GetSlot(reinterpret_cast<PyTypeObject*>(foo_type.get()), -1),
nullptr);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_SystemError));
}
TEST_F(TypeExtensionApiTest, GetSlotFromLargerThanMaxSlotReturnsNull) {
PyRun_SimpleString(R"(
class Foo: pass
)");
PyObjectPtr foo_type(testing::mainModuleGet("Foo"));
ASSERT_TRUE(PyType_CheckExact(foo_type));
EXPECT_EQ(
PyType_GetSlot(reinterpret_cast<PyTypeObject*>(foo_type.get()), 1000),
nullptr);
EXPECT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(TypeExtensionApiTest, GetSlotFromExtensionType) {
newfunc new_func = [](PyTypeObject* type, PyObject*, PyObject*) {
void* slot = PyType_GetSlot(type, Py_tp_alloc);
return reinterpret_cast<allocfunc>(slot)(type, 0);
};
initproc init_func = [](PyObject*, PyObject*, PyObject*) { return 0; };
binaryfunc add_func = [](PyObject*, PyObject*) { return PyLong_FromLong(7); };
PyType_Slot slots[] = {
{Py_tp_alloc, reinterpret_cast<void*>(PyType_GenericAlloc)},
{Py_tp_new, reinterpret_cast<void*>(new_func)},
{Py_tp_init, reinterpret_cast<void*>(init_func)},
{Py_nb_add, reinterpret_cast<void*>(add_func)},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"foo.Bar", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
ASSERT_TRUE(PyType_CheckExact(type));
PyTypeObject* typeobj = reinterpret_cast<PyTypeObject*>(type.get());
EXPECT_EQ(PyType_GetSlot(typeobj, Py_tp_alloc),
reinterpret_cast<void*>(PyType_GenericAlloc));
EXPECT_EQ(PyType_GetSlot(typeobj, Py_tp_new),
reinterpret_cast<void*>(new_func));
EXPECT_EQ(PyType_GetSlot(typeobj, Py_tp_init),
reinterpret_cast<void*>(init_func));
EXPECT_EQ(PyType_GetSlot(typeobj, Py_nb_add),
reinterpret_cast<void*>(add_func));
EXPECT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(TypeExtensionApiTest, DunderBasicsizeWithExtensionTypeReturnsBasicsize) {
PyType_Slot slots[] = {
{0, nullptr},
};
int size = sizeof(PyObject) + 13;
static PyType_Spec spec;
spec = {
"foo.Bar", size, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
PyObjectPtr basicsize(PyObject_GetAttrString(type, "__basicsize__"));
ASSERT_NE(basicsize, nullptr);
EXPECT_TRUE(isLongEqualsLong(basicsize, size));
}
TEST_F(TypeExtensionApiTest,
DunderBasicsizeExtensionTypeWithZeroSizeReturnsBasicsize) {
PyType_Slot slots[] = {
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"foo.Bar", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
PyObjectPtr basicsize(PyObject_GetAttrString(type, "__basicsize__"));
ASSERT_NE(basicsize, nullptr);
EXPECT_TRUE(isLongEqualsLong(basicsize, sizeof(PyObject)));
}
TEST_F(TypeExtensionApiTest,
DunderBasicsizeExtensionTypeWithHeadSizeReturnsBasicsize) {
PyType_Slot slots[] = {
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"foo.Bar", sizeof(PyObject), 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
PyObjectPtr basicsize(PyObject_GetAttrString(type, "__basicsize__"));
ASSERT_NE(basicsize, nullptr);
EXPECT_TRUE(isLongEqualsLong(basicsize, sizeof(PyObject)));
}
TEST_F(TypeExtensionApiTest,
MembersWithoutDunderDictoffsetReturnsTypeWithoutDunderDict) {
PyType_Slot slots[] = {
{0, nullptr},
};
static PyType_Spec spec;
spec = {"foo.Bar", 0, 0, Py_TPFLAGS_DEFAULT, slots};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
// Cannot set arbitrary attributes on instances.
PyObjectPtr instance(PyObject_CallObject(type, nullptr));
ASSERT_NE(instance, nullptr);
PyObjectPtr value(PyUnicode_FromString("world"));
EXPECT_EQ(PyObject_SetAttrString(instance, "hello", value), -1);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_AttributeError));
PyErr_Clear();
// Has no `__dict__`.
EXPECT_EQ(PyObject_GetAttrString(instance, "__dict__"), nullptr);
PyErr_Clear();
}
TEST_F(TypeExtensionApiTest,
MembersWithDunderDictoffsetReturnsTypeWithDunderDict) {
struct BarObject {
PyObject_HEAD
PyObject* dict;
};
static PyMemberDef members[2];
members[0] = {"__dictoffset__", T_PYSSIZET, offsetof(BarObject, dict),
READONLY};
members[1] = {nullptr};
PyType_Slot slots[] = {
{Py_tp_members, members},
{0, nullptr},
};
static PyType_Spec spec;
spec = {"foo.Bar", 0, sizeof(BarObject), Py_TPFLAGS_DEFAULT, slots};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
// Can set arbitrary attributes on instances.
PyObjectPtr instance(PyObject_CallObject(type, nullptr));
ASSERT_NE(instance, nullptr);
PyObjectPtr value(PyUnicode_FromString("world"));
EXPECT_EQ(PyObject_SetAttrString(instance, "hello", value), 0);
PyObjectPtr item(PyObject_GetAttrString(instance, "hello"));
EXPECT_TRUE(isUnicodeEqualsCStr(item, "world"));
}
TEST_F(TypeExtensionApiTest, MemberDescriptorTypeMatchesPyTpMembers) {
struct BarObject {
PyObject_HEAD
int value;
};
static PyMemberDef members[2];
members[0] = {"value", T_INT, offsetof(BarObject, value)};
members[1] = {nullptr};
static const PyType_Slot slots[] = {
{Py_tp_members, const_cast<PyMemberDef*>(members)},
{0, nullptr},
};
static PyType_Spec spec = {
"__main__.Bar",
sizeof(BarObject),
0,
Py_TPFLAGS_DEFAULT,
const_cast<PyType_Slot*>(slots),
};
PyObjectPtr type(PyType_FromSpec(&spec));
testing::moduleSet("__main__", "Bar", type);
PyRun_SimpleString(R"(
import types
descrType = types.MemberDescriptorType
tpType = type(Bar.__dict__['value'])
)");
PyObjectPtr descr_type(testing::mainModuleGet("descrType"));
PyObjectPtr tp_type(testing::mainModuleGet("tpType"));
ASSERT_EQ(descr_type, tp_type);
}
// METH_NOARGS and CALL_FUNCTION
TEST_F(TypeExtensionApiTest, MethodsMethNoargsPosCall) {
binaryfunc meth = [](PyObject*, PyObject*) { return PyLong_FromLong(1234); };
static PyMethodDef methods[] = {{"noargs", meth, METH_NOARGS}, {nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
result = C().noargs()
)");
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
EXPECT_EQ(PyLong_AsLong(result), 1234);
}
// METH_NOARGS | METH_CLASS | METH_STATIC and CALL_FUNCTION
TEST_F(TypeExtensionApiTest, MethodsClassAndStaticRaisesValueError) {
binaryfunc meth = [](PyObject*, PyObject*) { return PyLong_FromLong(1234); };
static PyMethodDef methods[] = {
{"noargs", meth, METH_NOARGS | METH_CLASS | METH_STATIC}, {nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
EXPECT_EQ(PyType_FromSpec(&spec), nullptr);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_ValueError));
}
TEST_F(TypeExtensionApiTest,
MethodsWithTypeSlotNameCoExistGetsResolvedForFunctionCall) {
newfunc new_func = [](PyTypeObject*, PyObject*, PyObject*) {
return PyLong_FromLong(100);
};
binaryfunc meth = [](PyObject*, PyObject*) { return PyLong_FromLong(200); };
static PyMethodDef methods[] = {
{"__new__", meth, METH_NOARGS | METH_STATIC | METH_COEXIST}, {nullptr}};
PyType_Slot slots[] = {
{Py_tp_new, reinterpret_cast<void*>(new_func)},
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_EQ(PyErr_Occurred(), nullptr);
EXPECT_EQ(
reinterpret_cast<newfunc>(PyType_GetSlot(type.asTypeObject(), Py_tp_new)),
new_func);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
result = C.__new__()
)");
PyObjectPtr result(testing::moduleGet("__main__", "result"));
ASSERT_NE(result, nullptr);
EXPECT_EQ(PyLong_AsLong(result), 200);
}
TEST_F(TypeExtensionApiTest, MethodsWithTypeSlotNameClassAndStaticGetsIgnored) {
newfunc new_func = [](PyTypeObject*, PyObject*, PyObject*) {
return PyLong_FromLong(100);
};
binaryfunc meth = [](PyObject*, PyObject*) { return PyLong_FromLong(200); };
static PyMethodDef methods[] = {{"__new__", meth, METH_NOARGS | METH_STATIC},
{nullptr}};
PyType_Slot slots[] = {
{Py_tp_new, reinterpret_cast<void*>(new_func)},
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_EQ(PyErr_Occurred(), nullptr);
EXPECT_EQ(
reinterpret_cast<newfunc>(PyType_GetSlot(type.asTypeObject(), Py_tp_new)),
new_func);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
result = C.__new__(C)
)");
PyObjectPtr result(testing::moduleGet("__main__", "result"));
ASSERT_NE(result, nullptr);
EXPECT_EQ(PyLong_AsLong(result), 100);
}
// METH_NOARGS and CALL_FUNCTION_EX
TEST_F(TypeExtensionApiTest, MethodsMethNoargsExCall) {
binaryfunc meth = [](PyObject*, PyObject*) { return PyLong_FromLong(1234); };
static PyMethodDef methods[] = {{"noargs", meth, METH_NOARGS}, {nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
result = C().noargs(*[])
)");
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
EXPECT_EQ(PyLong_AsLong(result), 1234);
}
// METH_NOARGS and CALL_FUNCTION_EX with VARKEYWORDS
TEST_F(TypeExtensionApiTest, MethodsMethNoargsExNoKwargsCall) {
binaryfunc meth = [](PyObject*, PyObject*) { return PyLong_FromLong(1234); };
static PyMethodDef methods[] = {{"noargs", meth, METH_NOARGS}, {nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
result = C().noargs(*[],**{})
)");
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
EXPECT_EQ(PyLong_AsLong(result), 1234);
}
TEST_F(TypeExtensionApiTest, MethodsMethNoargsExHasKwargsRaisesTypeError) {
binaryfunc meth = [](PyObject*, PyObject*) { return PyLong_FromLong(1234); };
static PyMethodDef methods[] = {{"noargs", meth, METH_NOARGS}, {nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
self = C()
result = False
try:
self.noargs(*[],**{'foo': 'bar'})
except:
result = True
)");
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_EQ(result, Py_True);
}
// METH_O and CALL_FUNCTION
TEST_F(TypeExtensionApiTest, MethodsMethOneArgPosCall) {
binaryfunc meth = [](PyObject* self, PyObject* arg) {
return PyTuple_Pack(2, self, arg);
};
static PyMethodDef methods[] = {{"onearg", meth, METH_O}, {nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
self = C()
result = self.onearg(1234)
)");
PyObjectPtr self(testing::mainModuleGet("self"));
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyTuple_CheckExact(result), 1);
ASSERT_EQ(PyTuple_Size(result), 2);
EXPECT_EQ(PyTuple_GetItem(result, 0), self);
EXPECT_EQ(PyLong_AsLong(PyTuple_GetItem(result, 1)), 1234);
}
TEST_F(TypeExtensionApiTest, MethodsMethOneArgNoArgsRaisesTypeError) {
binaryfunc meth = [](PyObject*, PyObject*) {
ADD_FAILURE(); // unreachable
return Py_None;
};
static PyMethodDef methods[] = {{"onearg", meth, METH_O}, {nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
result = False
self = C()
try:
self.onearg()
except TypeError:
result = True
)");
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
EXPECT_EQ(result, Py_True);
}
// METH_O | METH_CLASS and CALL_FUNCTION
TEST_F(TypeExtensionApiTest, MethodsMethOneArgClassPosCallOnClass) {
binaryfunc meth = [](PyObject* cls, PyObject* arg) {
return PyTuple_Pack(2, cls, arg);
};
static PyMethodDef methods[] = {{"onearg", meth, METH_O | METH_CLASS},
{nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString("result = C.onearg(1234)");
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyTuple_CheckExact(result), 1);
ASSERT_EQ(PyTuple_Size(result), 2);
EXPECT_EQ(PyTuple_GetItem(result, 0), type);
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 1), 1234));
}
TEST_F(TypeExtensionApiTest, MethodsMethOneArgClassPosCallOnInstance) {
binaryfunc meth = [](PyObject* cls, PyObject* arg) {
return PyTuple_Pack(2, cls, arg);
};
static PyMethodDef methods[] = {{"onearg", meth, METH_O | METH_CLASS},
{nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString("result = C().onearg(1234)");
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyTuple_CheckExact(result), 1);
ASSERT_EQ(PyTuple_Size(result), 2);
EXPECT_EQ(PyTuple_GetItem(result, 0), type);
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 1), 1234));
}
TEST_F(TypeExtensionApiTest, MethodsMethOneArgClassPosCallOnSubclass) {
binaryfunc meth = [](PyObject* cls, PyObject* arg) {
return PyTuple_Pack(2, cls, arg);
};
static PyMethodDef methods[] = {{"onearg", meth, METH_O | METH_CLASS},
{nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
class D(C):
pass
result0 = D.onearg(1234)
result1 = D().onearg(5678)
)");
PyObjectPtr d(testing::mainModuleGet("D"));
PyObjectPtr result0(testing::mainModuleGet("result0"));
ASSERT_NE(result0, nullptr);
ASSERT_EQ(PyTuple_CheckExact(result0), 1);
ASSERT_EQ(PyTuple_Size(result0), 2);
EXPECT_EQ(PyTuple_GetItem(result0, 0), d);
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result0, 1), 1234));
PyObjectPtr result1(testing::mainModuleGet("result1"));
ASSERT_NE(result1, nullptr);
ASSERT_EQ(PyTuple_CheckExact(result1), 1);
ASSERT_EQ(PyTuple_Size(result1), 2);
EXPECT_EQ(PyTuple_GetItem(result1, 0), d);
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result1, 1), 5678));
}
// METH_O | METH_STATIC
TEST_F(TypeExtensionApiTest, MethodsMethOneArgStaticCalledOnClass) {
binaryfunc meth = [](PyObject* self, PyObject* arg) {
EXPECT_EQ(self, nullptr);
Py_INCREF(arg);
return arg;
};
static PyMethodDef methods[] = {{"onearg", meth, METH_O | METH_STATIC},
{nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString("result = C.onearg(1234)");
PyObjectPtr result(testing::mainModuleGet("result"));
EXPECT_TRUE(isLongEqualsLong(result, 1234));
}
TEST_F(TypeExtensionApiTest, MethodsMethOneArgStaticCalledOnInstance) {
binaryfunc meth = [](PyObject* self, PyObject* arg) {
EXPECT_EQ(self, nullptr);
Py_INCREF(arg);
return arg;
};
static PyMethodDef methods[] = {{"onearg", meth, METH_O | METH_STATIC},
{nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString("result = C().onearg(1234)");
PyObjectPtr result(testing::mainModuleGet("result"));
EXPECT_TRUE(isLongEqualsLong(result, 1234));
}
TEST_F(TypeExtensionApiTest, MethodsMethOneArgStaticCalledOnSubclass) {
binaryfunc meth = [](PyObject* self, PyObject* arg) {
EXPECT_EQ(self, nullptr);
Py_INCREF(arg);
return arg;
};
static PyMethodDef methods[] = {{"onearg", meth, METH_O | METH_STATIC},
{nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
class D(C):
pass
result0 = D.onearg(1234)
result1 = D().onearg(5678)
)");
PyObjectPtr result0(testing::mainModuleGet("result0"));
EXPECT_TRUE(isLongEqualsLong(result0, 1234));
PyObjectPtr result1(testing::mainModuleGet("result1"));
EXPECT_TRUE(isLongEqualsLong(result1, 5678));
}
// METH_O and CALL_FUNCTION_KW
TEST_F(TypeExtensionApiTest, MethodsMethOneArgKwCall) {
binaryfunc meth = [](PyObject*, PyObject*) {
ADD_FAILURE(); // unreachable
return Py_None;
};
static PyMethodDef methods[] = {{"onearg", meth, METH_O}, {nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
ASSERT_NO_FATAL_FAILURE(PyRun_SimpleString(R"(
try:
obj = C().onearg(foo=1234)
result = False
except TypeError:
result = True
)"));
PyObjectPtr result(testing::mainModuleGet("result"));
EXPECT_EQ(result, Py_True);
}
// METH_O and CALL_FUNCTION_EX
TEST_F(TypeExtensionApiTest, MethodsMethOneArgExCall) {
binaryfunc meth = [](PyObject* self, PyObject* arg) {
return PyTuple_Pack(2, self, arg);
};
static PyMethodDef methods[] = {{"onearg", meth, METH_O}, {nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
obj = C()
result = obj.onearg(*[1234])
)");
PyObjectPtr obj(testing::mainModuleGet("obj"));
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyTuple_CheckExact(result), 1);
ASSERT_EQ(PyTuple_Size(result), 2);
EXPECT_EQ(PyTuple_GetItem(result, 0), obj);
EXPECT_EQ(PyLong_AsLong(PyTuple_GetItem(result, 1)), 1234);
}
// METH_VARARGS and CALL_FUNCTION
TEST_F(TypeExtensionApiTest, MethodsVarargsArgPosCall) {
binaryfunc meth = [](PyObject* self, PyObject* arg) {
return PyTuple_Pack(2, self, arg);
};
static PyMethodDef methods[] = {{"varargs", meth, METH_VARARGS}, {nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
self = C()
result = self.varargs(1234)
)");
PyObjectPtr self(testing::mainModuleGet("self"));
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyTuple_CheckExact(result), 1);
ASSERT_EQ(PyTuple_Size(result), 2);
EXPECT_EQ(PyTuple_GetItem(result, 0), self);
PyObject* args = PyTuple_GetItem(result, 1);
EXPECT_TRUE(PyTuple_CheckExact(args));
EXPECT_EQ(PyLong_AsLong(PyTuple_GetItem(args, 0)), 1234);
}
TEST_F(TypeExtensionApiTest, MethodsVarargsArgPosNoArgsCall) {
binaryfunc meth = [](PyObject* self, PyObject* arg) {
return PyTuple_Pack(2, self, arg);
};
static PyMethodDef methods[] = {{"varargs", meth, METH_VARARGS}, {nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
self = C()
result = self.varargs()
)");
PyObjectPtr self(testing::mainModuleGet("self"));
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyTuple_CheckExact(result), 1);
ASSERT_EQ(PyTuple_Size(result), 2);
EXPECT_EQ(PyTuple_GetItem(result, 0), self);
PyObject* args = PyTuple_GetItem(result, 1);
EXPECT_TRUE(PyTuple_CheckExact(args));
EXPECT_EQ(PyTuple_Size(args), 0);
}
// METH_VARARGS and CALL_FUNCTION_KW
TEST_F(TypeExtensionApiTest, MethodsVarargsArgKwCall) {
binaryfunc meth = [](PyObject* self, PyObject* arg) {
return PyTuple_Pack(2, self, arg);
};
static PyMethodDef methods[] = {{"varargs", meth, METH_VARARGS}, {nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
try:
obj = C().varargs(foo=1234)
result = False
except TypeError:
result = True
)");
PyObjectPtr result(testing::mainModuleGet("result"));
EXPECT_EQ(result, Py_True);
}
// METH_VARARGS and CALL_FUNCTION_EX
TEST_F(TypeExtensionApiTest, MethodsVarargsArgExCall) {
binaryfunc meth = [](PyObject* self, PyObject* arg) {
return PyTuple_Pack(2, self, arg);
};
static PyMethodDef methods[] = {{"varargs", meth, METH_VARARGS}, {nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
self = C()
result = self.varargs(*[1234])
)");
PyObjectPtr self(testing::mainModuleGet("self"));
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyTuple_CheckExact(result), 1);
ASSERT_EQ(PyTuple_Size(result), 2);
EXPECT_EQ(PyTuple_GetItem(result, 0), self);
PyObject* args = PyTuple_GetItem(result, 1);
EXPECT_TRUE(PyTuple_CheckExact(args));
EXPECT_EQ(PyLong_AsLong(PyTuple_GetItem(args, 0)), 1234);
}
TEST_F(TypeExtensionApiTest, MethodsVarargsArgExHasEmptyKwargsCall) {
binaryfunc meth = [](PyObject* self, PyObject* arg) {
return PyTuple_Pack(2, self, arg);
};
static PyMethodDef methods[] = {{"varargs", meth, METH_VARARGS}, {nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
self = C()
result = self.varargs(*[1234], **{})
)");
PyObjectPtr self(testing::mainModuleGet("self"));
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyTuple_CheckExact(result), 1);
ASSERT_EQ(PyTuple_Size(result), 2);
EXPECT_EQ(PyTuple_GetItem(result, 0), self);
PyObject* args = PyTuple_GetItem(result, 1);
EXPECT_TRUE(PyTuple_CheckExact(args));
EXPECT_EQ(PyLong_AsLong(PyTuple_GetItem(args, 0)), 1234);
}
// METH_KEYWORDS and CALL_FUNCTION
TEST_F(TypeExtensionApiTest, MethodsMethKeywordsPosCall) {
ternaryfunc meth = [](PyObject* self, PyObject* args, PyObject* kwargs) {
if (kwargs == nullptr) return PyTuple_Pack(2, self, args);
return PyTuple_Pack(3, self, args, kwargs);
};
static PyMethodDef methods[] = {
{"keywords", reinterpret_cast<PyCFunction>(reinterpret_cast<void*>(meth)),
METH_VARARGS | METH_KEYWORDS},
{nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
self = C()
result = self.keywords(1234)
)");
PyObjectPtr self(testing::mainModuleGet("self"));
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyTuple_CheckExact(result), 1);
ASSERT_EQ(PyTuple_Size(result), 2);
EXPECT_EQ(PyTuple_GetItem(result, 0), self);
PyObject* args = PyTuple_GetItem(result, 1);
EXPECT_TRUE(PyTuple_CheckExact(args));
EXPECT_EQ(PyTuple_Size(args), 1);
EXPECT_EQ(PyLong_AsLong(PyTuple_GetItem(args, 0)), 1234);
}
// METH_KEYWORDS and CALL_FUNCTION_KW
TEST_F(TypeExtensionApiTest, MethodsMethKeywordsKwCall) {
ternaryfunc meth = [](PyObject* self, PyObject* args, PyObject* kwargs) {
if (kwargs == nullptr) return PyTuple_Pack(2, self, args);
return PyTuple_Pack(3, self, args, kwargs);
};
static PyMethodDef methods[] = {
{"keywords", reinterpret_cast<PyCFunction>(reinterpret_cast<void*>(meth)),
METH_VARARGS | METH_KEYWORDS},
{nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
self = C()
result = self.keywords(1234, kwarg=5678)
)");
PyObjectPtr self(testing::mainModuleGet("self"));
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyTuple_CheckExact(result), 1);
ASSERT_EQ(PyTuple_Size(result), 3);
EXPECT_EQ(PyTuple_GetItem(result, 0), self);
PyObject* args = PyTuple_GetItem(result, 1);
EXPECT_TRUE(PyTuple_CheckExact(args));
EXPECT_EQ(PyTuple_Size(args), 1);
EXPECT_EQ(PyLong_AsLong(PyTuple_GetItem(args, 0)), 1234);
PyObject* kwargs = PyTuple_GetItem(result, 2);
EXPECT_TRUE(PyDict_CheckExact(kwargs));
ASSERT_EQ(PyDict_Size(kwargs), 1);
PyObject* item = PyDict_GetItemString(kwargs, "kwarg");
EXPECT_TRUE(isLongEqualsLong(item, 5678));
}
// METH_KEYWORDS and CALL_FUNCTION_EX
TEST_F(TypeExtensionApiTest, MethodsMethKeywordsExCall) {
ternaryfunc meth = [](PyObject* self, PyObject* args, PyObject* kwargs) {
return PyTuple_Pack(3, self, args, kwargs);
};
static PyMethodDef methods[] = {
{"keywords", reinterpret_cast<PyCFunction>(reinterpret_cast<void*>(meth)),
METH_VARARGS | METH_KEYWORDS},
{nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
self = C()
result = self.keywords(*[1234], kwarg=5678)
)");
PyObjectPtr self(testing::mainModuleGet("self"));
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyTuple_CheckExact(result), 1);
ASSERT_EQ(PyTuple_Size(result), 3);
EXPECT_EQ(PyTuple_GetItem(result, 0), self);
PyObject* args = PyTuple_GetItem(result, 1);
EXPECT_TRUE(PyTuple_CheckExact(args));
EXPECT_EQ(PyTuple_Size(args), 1);
EXPECT_EQ(PyLong_AsLong(PyTuple_GetItem(args, 0)), 1234);
PyObject* kwargs = PyTuple_GetItem(result, 2);
EXPECT_TRUE(PyDict_CheckExact(kwargs));
ASSERT_EQ(PyDict_Size(kwargs), 1);
PyObject* item = PyDict_GetItemString(kwargs, "kwarg");
EXPECT_TRUE(isLongEqualsLong(item, 5678));
}
TEST_F(TypeExtensionApiTest, MethodsMethKeywordsExEmptyKwargsCall) {
ternaryfunc meth = [](PyObject* self, PyObject* args, PyObject* kwargs) {
if (kwargs == nullptr) return PyTuple_Pack(2, self, args);
return PyTuple_Pack(3, self, args, kwargs);
};
static PyMethodDef methods[] = {
{"keywords", reinterpret_cast<PyCFunction>(reinterpret_cast<void*>(meth)),
METH_VARARGS | METH_KEYWORDS},
{nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
self = C()
result = self.keywords(*[1234], *{})
)");
PyObjectPtr self(testing::mainModuleGet("self"));
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyTuple_CheckExact(result), 1);
ASSERT_EQ(PyTuple_Size(result), 2);
EXPECT_EQ(PyTuple_GetItem(result, 0), self);
PyObject* args = PyTuple_GetItem(result, 1);
EXPECT_TRUE(PyTuple_CheckExact(args));
EXPECT_EQ(PyTuple_Size(args), 1);
EXPECT_EQ(PyLong_AsLong(PyTuple_GetItem(args, 0)), 1234);
}
TEST_F(TypeExtensionApiTest, GetObjectCreatedInManagedCode) {
static PyType_Slot slots[1];
slots[0] = {0, nullptr};
static PyType_Spec spec;
spec = {
"__main__.Foo", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
ASSERT_EQ(PyType_CheckExact(type), 1);
ASSERT_EQ(moduleSet("__main__", "Foo", type), 0);
// This is similar to CallExtensionTypeReturnsExtensionInstancePyro, but it
// tests the RawObject -> PyObject* path for objects that were created on the
// managed heap and had no corresponding PyObject* before the call to
// moduleGet().
ASSERT_EQ(PyRun_SimpleString("f = Foo()"), 0);
PyObjectPtr foo(mainModuleGet("f"));
EXPECT_NE(foo, nullptr);
}
TEST_F(TypeExtensionApiTest, GenericNewReturnsExtensionInstance) {
struct BarObject {
PyObject_HEAD
};
PyType_Slot slots[] = {
{Py_tp_alloc, reinterpret_cast<void*>(PyType_GenericAlloc)},
{Py_tp_new, reinterpret_cast<void*>(PyType_GenericNew)},
{Py_tp_dealloc, reinterpret_cast<void*>(deallocLeafObject)},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"foo.Bar", sizeof(BarObject), 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
ASSERT_TRUE(PyType_CheckExact(type));
auto new_func = reinterpret_cast<newfunc>(
PyType_GetSlot(reinterpret_cast<PyTypeObject*>(type.get()), Py_tp_new));
PyObjectPtr bar(
new_func(reinterpret_cast<PyTypeObject*>(type.get()), nullptr, nullptr));
EXPECT_NE(bar, nullptr);
}
// Given one slot id and a function pointer to go with it, create a Bar type
// containing that slot.
template <typename T>
static void createTypeWithSlotAndBase(const char* type_name, int slot, T pfunc,
PyObject* base) {
static PyType_Slot slots[2];
slots[0] = {slot, reinterpret_cast<void*>(pfunc)};
slots[1] = {0, nullptr};
static PyType_Spec spec;
static char qualname[100];
std::sprintf(qualname, "__main__.%s", type_name);
unsigned int flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
spec = {
qualname, 0, 0, flags, slots,
};
PyObject* tp;
if (base == nullptr) {
tp = PyType_FromSpec(&spec);
} else {
PyObjectPtr bases(PyTuple_Pack(1, base));
tp = PyType_FromSpecWithBases(&spec, bases);
}
PyObjectPtr type(tp);
ASSERT_NE(type, nullptr);
ASSERT_EQ(PyType_CheckExact(type), 1);
ASSERT_EQ(moduleSet("__main__", type_name, type), 0);
}
template <typename T>
static void createTypeWithSlot(const char* type_name, int slot, T pfunc) {
createTypeWithSlotAndBase(type_name, slot, pfunc, nullptr);
}
TEST_F(TypeExtensionApiTest, CallReverseBinarySlotSwapsArguments) {
binaryfunc add_func = [](PyObject* a, PyObject* b) {
return PyTuple_Pack(2, a, b);
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_nb_add, add_func));
ASSERT_EQ(PyRun_SimpleString(R"(
instance = Bar()
left, right = instance.__radd__(12)
)"),
0);
PyObjectPtr instance(mainModuleGet("instance"));
PyObjectPtr left(mainModuleGet("left"));
PyObjectPtr right(mainModuleGet("right"));
EXPECT_TRUE(isLongEqualsLong(left, 12));
EXPECT_EQ(right, instance);
}
TEST_F(TypeExtensionApiTest, CallBinarySlotFromManagedCode) {
binaryfunc add_func = [](PyObject* a, PyObject* b) {
PyObjectPtr num(PyLong_FromLong(24));
return PyLong_Check(a) ? PyNumber_Add(a, num) : PyNumber_Add(num, b);
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_nb_add, add_func));
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.__add__(12)
r2 = Bar.__add__(b, 24)
r3 = 1000 + b
args = (b, 42)
r4 = Bar.__add__(*args)
kwargs = {}
r5 = b.__add__(100, **kwargs)
b += -12
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
EXPECT_TRUE(isLongEqualsLong(r1, 36));
PyObjectPtr r2(mainModuleGet("r2"));
EXPECT_TRUE(isLongEqualsLong(r2, 48));
PyObjectPtr r3(mainModuleGet("r3"));
EXPECT_TRUE(isLongEqualsLong(r3, 1024));
PyObjectPtr r4(mainModuleGet("r4"));
EXPECT_TRUE(isLongEqualsLong(r4, 66));
PyObjectPtr r5(mainModuleGet("r5"));
EXPECT_TRUE(isLongEqualsLong(r5, 124));
PyObjectPtr b(mainModuleGet("b"));
EXPECT_TRUE(isLongEqualsLong(b, 12));
}
TEST_F(TypeExtensionApiTest, CallBinarySlotWithKwargsRaisesTypeError) {
binaryfunc dummy_add = [](PyObject*, PyObject*) -> PyObject* {
EXPECT_TRUE(false) << "Shouldn't be called";
Py_RETURN_NONE;
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_nb_add, dummy_add));
// TODO(T40700664): Use PyRun_String() so we can directly inspect the thrown
// exception(s).
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
try:
b.__add__(a=2)
raise RuntimeError("call didn't throw")
except TypeError:
pass
try:
kwargs = {'a': 2}
b.__add__(**kwargs)
raise RuntimeError("call didn't throw")
except TypeError:
pass
)"),
0);
}
TEST_F(TypeExtensionApiTest, CallHashSlotFromManagedCode) {
hashfunc hash_func = [](PyObject*) -> Py_hash_t { return 0xba5eba11; };
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_tp_hash, hash_func));
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
h1 = b.__hash__()
h2 = Bar.__hash__(b)
)"),
0);
PyObjectPtr h1(mainModuleGet("h1"));
EXPECT_TRUE(isLongEqualsLong(h1, 0xba5eba11));
PyObjectPtr h2(mainModuleGet("h2"));
EXPECT_TRUE(isLongEqualsLong(h2, 0xba5eba11));
}
static PyObject* abortingTernaryFunc(PyObject*, PyObject*, PyObject*) {
std::fprintf(stderr, "%s should not have been called!\n", __func__);
std::abort();
}
TEST_F(TypeExtensionApiTest, CallCallSlotWithMismatchedSelfRaisesTypeError) {
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("MyType", Py_tp_call, &abortingTernaryFunc));
PyObjectPtr my_type(mainModuleGet("MyType"));
PyObjectPtr dunder_call(PyObject_GetAttrString(my_type, "__call__"));
PyObjectPtr arg(PyLong_FromLong(5));
PyObjectPtr call_result(
PyObject_CallFunctionObjArgs(dunder_call, arg.get(), nullptr));
EXPECT_EQ(call_result, nullptr);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_TypeError));
}
TEST_F(TypeExtensionApiTest, CallNewSlotWithNonTypeClsRaisesTypeError) {
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("MyType", Py_tp_new, &abortingTernaryFunc));
PyObjectPtr my_type(mainModuleGet("MyType"));
PyObjectPtr dunder_call(PyObject_GetAttrString(my_type, "__new__"));
PyObjectPtr arg(PyLong_FromLong(5));
PyObjectPtr call_result(
PyObject_CallFunctionObjArgs(dunder_call, arg.get(), nullptr));
EXPECT_EQ(call_result, nullptr);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_TypeError));
}
TEST_F(TypeExtensionApiTest, CallNewSlotWithNonSubclassClsRaisesTypeError) {
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("MyType", Py_tp_new, &abortingTernaryFunc));
PyObjectPtr my_type(mainModuleGet("MyType"));
PyObjectPtr dunder_call(PyObject_GetAttrString(my_type, "__new__"));
PyObjectPtr arg(borrow(reinterpret_cast<PyObject*>(&PyType_Type)));
PyObjectPtr call_result(
PyObject_CallFunctionObjArgs(dunder_call, arg.get(), nullptr));
EXPECT_EQ(call_result, nullptr);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_TypeError));
}
TEST_F(TypeExtensionApiTest, CallCallSlotFromManagedCode) {
ternaryfunc call_func = [](PyObject* self, PyObject* args, PyObject* kwargs) {
return PyTuple_Pack(3, self, args, kwargs ? kwargs : Py_None);
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_tp_call, call_func));
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.__call__()
r2 = b.__call__('a', 'b', c='see')
r3 = b('hello!')
args=(b,"an argument")
r4 = Bar.__call__(*args)
)"),
0);
PyObjectPtr b(mainModuleGet("b"));
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyTuple_Check(r1), 1);
ASSERT_EQ(PyTuple_Size(r1), 3);
EXPECT_EQ(PyTuple_GetItem(r1, 0), b);
PyObject* tmp = PyTuple_GetItem(r1, 1);
ASSERT_EQ(PyTuple_Check(tmp), 1);
EXPECT_EQ(PyTuple_Size(tmp), 0);
EXPECT_EQ(PyTuple_GetItem(r1, 2), Py_None);
PyObjectPtr r2(mainModuleGet("r2"));
ASSERT_EQ(PyTuple_Check(r2), 1);
ASSERT_EQ(PyTuple_Size(r2), 3);
EXPECT_EQ(PyTuple_GetItem(r2, 0), b);
tmp = PyTuple_GetItem(r2, 1);
ASSERT_EQ(PyTuple_Check(tmp), 1);
ASSERT_EQ(PyTuple_Size(tmp), 2);
EXPECT_TRUE(isUnicodeEqualsCStr(PyTuple_GetItem(tmp, 0), "a"));
EXPECT_TRUE(isUnicodeEqualsCStr(PyTuple_GetItem(tmp, 1), "b"));
tmp = PyTuple_GetItem(r2, 2);
ASSERT_EQ(PyDict_Check(tmp), 1);
PyObjectPtr key(PyUnicode_FromString("c"));
EXPECT_TRUE(isUnicodeEqualsCStr(PyDict_GetItem(tmp, key), "see"));
PyObjectPtr r3(mainModuleGet("r3"));
ASSERT_EQ(PyTuple_Check(r3), 1);
ASSERT_EQ(PyTuple_Size(r3), 3);
EXPECT_EQ(PyTuple_GetItem(r3, 0), b);
tmp = PyTuple_GetItem(r3, 1);
ASSERT_EQ(PyTuple_Check(tmp), 1);
ASSERT_EQ(PyTuple_Size(tmp), 1);
EXPECT_TRUE(isUnicodeEqualsCStr(PyTuple_GetItem(tmp, 0), "hello!"));
EXPECT_EQ(PyTuple_GetItem(r3, 2), Py_None);
PyObjectPtr r4(mainModuleGet("r4"));
ASSERT_EQ(PyTuple_Check(r4), 1);
ASSERT_EQ(PyTuple_Size(r4), 3);
EXPECT_EQ(PyTuple_GetItem(r4, 0), b);
tmp = PyTuple_GetItem(r4, 1);
ASSERT_EQ(PyTuple_Check(tmp), 1);
ASSERT_EQ(PyTuple_Size(tmp), 1);
EXPECT_TRUE(isUnicodeEqualsCStr(PyTuple_GetItem(tmp, 0), "an argument"));
EXPECT_EQ(PyTuple_GetItem(r4, 2), Py_None);
}
TEST_F(TypeExtensionApiTest, CallGetattroSlotFromManagedCode) {
getattrofunc getattr_func = [](PyObject* self, PyObject* name) {
return PyTuple_Pack(2, name, self);
};
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("Bar", Py_tp_getattro, getattr_func));
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r0 = b.foo_bar
def foo(b):
return b.bar_baz
r1 = foo(b)
)"),
0);
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NE(b, nullptr);
PyObjectPtr r0(mainModuleGet("r0"));
ASSERT_NE(r0, nullptr);
ASSERT_EQ(PyTuple_Check(r0), 1);
EXPECT_TRUE(isUnicodeEqualsCStr(PyTuple_GetItem(r0, 0), "foo_bar"));
EXPECT_EQ(PyTuple_GetItem(r0, 1), b);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_NE(r1, nullptr);
ASSERT_EQ(PyTuple_Check(r1), 1);
EXPECT_TRUE(isUnicodeEqualsCStr(PyTuple_GetItem(r1, 0), "bar_baz"));
EXPECT_EQ(PyTuple_GetItem(r0, 1), b);
}
// Pyro-only due to
// https://github.com/python/cpython/commit/4dcdb78c6ffd203c9d72ef41638cc4a0e3857adf
TEST_F(TypeExtensionApiTest, CallSetattroSlotFromManagedCodePyro) {
setattrofunc setattr_func = [](PyObject* self, PyObject* name,
PyObject* value) {
PyObjectPtr tuple(value ? PyTuple_Pack(3, self, name, value)
: PyTuple_Pack(2, self, name));
const char* var = value ? "set_attr" : "del_attr";
moduleSet("__main__", var, tuple);
return 0;
};
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("Bar", Py_tp_setattro, setattr_func));
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.__setattr__("attr", 1234)
)"),
0);
PyObjectPtr b(mainModuleGet("b"));
PyObjectPtr r1(mainModuleGet("r1"));
EXPECT_EQ(r1, Py_None);
PyObjectPtr set_attr(mainModuleGet("set_attr"));
ASSERT_EQ(PyTuple_Check(set_attr), 1);
ASSERT_EQ(PyTuple_Size(set_attr), 3);
EXPECT_EQ(PyTuple_GetItem(set_attr, 0), b);
EXPECT_TRUE(isUnicodeEqualsCStr(PyTuple_GetItem(set_attr, 1), "attr"));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(set_attr, 2), 1234));
ASSERT_EQ(PyRun_SimpleString(R"(r2 = b.__delattr__("other attr"))"), 0);
PyObjectPtr r2(mainModuleGet("r2"));
EXPECT_EQ(r2, Py_None);
PyObjectPtr del_attr(mainModuleGet("del_attr"));
ASSERT_EQ(PyTuple_Check(del_attr), 1);
ASSERT_EQ(PyTuple_Size(del_attr), 2);
EXPECT_EQ(PyTuple_GetItem(del_attr, 0), b);
EXPECT_TRUE(isUnicodeEqualsCStr(PyTuple_GetItem(del_attr, 1), "other attr"));
}
TEST_F(TypeExtensionApiTest, CallRichcompareSlotFromManagedCode) {
richcmpfunc cmp_func = [](PyObject* self, PyObject* other, int op) {
PyObjectPtr op_obj(PyLong_FromLong(op));
return PyTuple_Pack(3, self, other, op_obj.get());
};
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("Bar", Py_tp_richcompare, cmp_func));
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.__eq__("equal")
r2 = b.__gt__(0xcafe)
)"),
0);
PyObjectPtr b(mainModuleGet("b"));
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyTuple_Check(r1), 1);
ASSERT_EQ(PyTuple_Size(r1), 3);
EXPECT_EQ(PyTuple_GetItem(r1, 0), b);
EXPECT_TRUE(isUnicodeEqualsCStr(PyTuple_GetItem(r1, 1), "equal"));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(r1, 2), Py_EQ));
PyObjectPtr r2(mainModuleGet("r2"));
ASSERT_EQ(PyTuple_Check(r2), 1);
ASSERT_EQ(PyTuple_Size(r2), 3);
EXPECT_EQ(PyTuple_GetItem(r2, 0), b);
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(r2, 1), 0xcafe));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(r2, 2), Py_GT));
}
TEST_F(TypeExtensionApiTest, CallNextSlotFromManagedCode) {
unaryfunc next_func = [](PyObject* self) {
Py_INCREF(self);
return self;
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_tp_iternext, next_func));
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r = b.__next__()
)"),
0);
PyObjectPtr b(mainModuleGet("b"));
PyObjectPtr r(mainModuleGet("r"));
EXPECT_EQ(r, b);
}
TEST_F(TypeExtensionApiTest, NextSlotReturningNullRaisesStopIteration) {
unaryfunc next_func = [](PyObject*) -> PyObject* { return nullptr; };
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_tp_iternext, next_func));
ASSERT_EQ(PyRun_SimpleString(R"(
caught = False
try:
Bar().__next__()
except StopIteration:
caught = True
)"),
0);
PyObjectPtr caught(mainModuleGet("caught"));
EXPECT_EQ(caught, Py_True);
}
TEST_F(TypeExtensionApiTest, CallDescrGetSlotFromManagedCode) {
descrgetfunc get_func = [](PyObject* self, PyObject* instance,
PyObject* owner) {
return PyTuple_Pack(3, self, instance, owner);
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_tp_descr_get, get_func));
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
b2 = Bar()
r = b.__get__(b2, Bar)
)"),
0);
PyObjectPtr bar(mainModuleGet("Bar"));
PyObjectPtr b(mainModuleGet("b"));
PyObjectPtr b2(mainModuleGet("b2"));
PyObjectPtr r(mainModuleGet("r"));
ASSERT_EQ(PyTuple_Check(r), 1);
ASSERT_EQ(PyTuple_Size(r), 3);
EXPECT_EQ(PyTuple_GetItem(r, 0), b);
EXPECT_EQ(PyTuple_GetItem(r, 1), b2);
EXPECT_EQ(PyTuple_GetItem(r, 2), bar);
}
TEST_F(TypeExtensionApiTest, DescrGetSlotWithNonesRaisesTypeError) {
descrgetfunc get_func = [](PyObject*, PyObject*, PyObject*) -> PyObject* {
EXPECT_TRUE(false) << "Shouldn't be called";
return nullptr;
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_tp_descr_get, get_func));
// TODO(T40700664): Use PyRun_String() so we can inspect the exception more
// directly.
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
exc = None
try:
b.__get__(None, None)
except TypeError as e:
exc = e
)"),
0);
PyObjectPtr exc(mainModuleGet("exc"));
EXPECT_EQ(PyErr_GivenExceptionMatches(exc, PyExc_TypeError), 1);
}
TEST_F(TypeExtensionApiTest, CallDescrSetSlotFromManagedCode) {
descrsetfunc set_func = [](PyObject* /* self */, PyObject* obj,
PyObject* value) -> int {
EXPECT_TRUE(isLongEqualsLong(obj, 123));
EXPECT_TRUE(isLongEqualsLong(value, 456));
return 0;
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_tp_descr_set, set_func));
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
b.__set__(123, 456)
)"),
0);
}
TEST_F(TypeExtensionApiTest, CallDescrDeleteSlotFromManagedCode) {
descrsetfunc set_func = [](PyObject* /* self */, PyObject* obj,
PyObject* value) -> int {
EXPECT_TRUE(isLongEqualsLong(obj, 24));
EXPECT_EQ(value, nullptr);
return 0;
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_tp_descr_set, set_func));
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
b.__delete__(24)
)"),
0);
}
TEST_F(TypeExtensionApiTest, CallInitSlotFromManagedCode) {
initproc init_func = [](PyObject* /* self */, PyObject* args,
PyObject* kwargs) -> int {
moduleSet("__main__", "args", args);
moduleSet("__main__", "kwargs", kwargs);
return 0;
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_tp_init, init_func));
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar.__new__(Bar)
b.__init__(123, four=4)
)"),
0);
PyObjectPtr args(mainModuleGet("args"));
ASSERT_NE(args, nullptr);
ASSERT_EQ(PyTuple_Check(args), 1);
ASSERT_EQ(PyTuple_Size(args), 1);
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(args, 0), 123));
PyObjectPtr kwargs(mainModuleGet("kwargs"));
ASSERT_NE(kwargs, nullptr);
ASSERT_EQ(PyDict_Check(kwargs), 1);
ASSERT_EQ(PyDict_Size(kwargs), 1);
EXPECT_TRUE(isLongEqualsLong(PyDict_GetItemString(kwargs, "four"), 4));
}
TEST_F(TypeExtensionApiTest, CallDelSlotFromManagedCode) {
destructor del_func = [](PyObject* /* self */) {
moduleSet("__main__", "called", Py_True);
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_tp_del, del_func));
ASSERT_EQ(PyRun_SimpleString(R"(
bar = Bar()
)"),
0);
PyObjectPtr bar_type(mainModuleGet("Bar"));
PyObject* bar = mainModuleGet("bar");
auto func = reinterpret_cast<destructor>(PyType_GetSlot(
reinterpret_cast<PyTypeObject*>(bar_type.get()), Py_tp_dealloc));
(*func)(bar);
PyObjectPtr called(mainModuleGet("called"));
EXPECT_EQ(called, Py_True);
}
TEST_F(TypeExtensionApiTest, CallTernarySlotFromManagedCode) {
ternaryfunc pow_func = [](PyObject* self, PyObject* value, PyObject* mod) {
return PyTuple_Pack(3, self, value, mod);
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_nb_power, pow_func));
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.__pow__(123, 456)
r2 = b.__pow__(789)
)"),
0);
PyObjectPtr b(mainModuleGet("b"));
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyTuple_Check(r1), 1);
ASSERT_EQ(PyTuple_Size(r1), 3);
EXPECT_EQ(PyTuple_GetItem(r1, 0), b);
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(r1, 1), 123));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(r1, 2), 456));
PyObjectPtr r2(mainModuleGet("r2"));
ASSERT_EQ(PyTuple_Check(r2), 1);
ASSERT_EQ(PyTuple_Size(r1), 3);
EXPECT_EQ(PyTuple_GetItem(r2, 0), b);
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(r2, 1), 789));
EXPECT_EQ(PyTuple_GetItem(r2, 2), Py_None);
}
TEST_F(TypeExtensionApiTest, CallInquirySlotFromManagedCode) {
inquiry bool_func = [](PyObject* self) {
PyObjectPtr b(mainModuleGet("b"));
EXPECT_EQ(self, b);
return 1;
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_nb_bool, bool_func));
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r = b.__bool__()
)"),
0);
PyObjectPtr r(mainModuleGet("r"));
EXPECT_EQ(r, Py_True);
}
TEST_F(TypeExtensionApiTest, CallObjobjargSlotFromManagedCode) {
objobjargproc set_func = [](PyObject* self, PyObject* key, PyObject* value) {
PyObjectPtr b(mainModuleGet("b"));
EXPECT_EQ(self, b);
moduleSet("__main__", "key", key);
moduleSet("__main__", "value", value);
return 0;
};
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("Bar", Py_mp_ass_subscript, set_func));
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r = b.__setitem__("some key", "a value")
)"),
0);
PyObjectPtr r(mainModuleGet("r"));
EXPECT_EQ(r, Py_None);
PyObjectPtr key(mainModuleGet("key"));
EXPECT_TRUE(isUnicodeEqualsCStr(key, "some key"));
PyObjectPtr value(mainModuleGet("value"));
EXPECT_TRUE(isUnicodeEqualsCStr(value, "a value"));
}
TEST_F(TypeExtensionApiTest, CallObjobjSlotFromManagedCode) {
objobjproc contains_func = [](PyObject* self, PyObject* value) {
PyObjectPtr b(mainModuleGet("b"));
EXPECT_EQ(self, b);
moduleSet("__main__", "value", value);
return 123456;
};
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("Bar", Py_sq_contains, contains_func));
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r = b.__contains__("a key")
)"),
0);
PyObjectPtr r(mainModuleGet("r"));
EXPECT_EQ(r, Py_True);
PyObjectPtr value(mainModuleGet("value"));
EXPECT_TRUE(isUnicodeEqualsCStr(value, "a key"));
}
TEST_F(TypeExtensionApiTest, CallDelitemSlotFromManagedCode) {
objobjargproc del_func = [](PyObject* self, PyObject* key, PyObject* value) {
PyObjectPtr b(mainModuleGet("b"));
EXPECT_EQ(self, b);
EXPECT_EQ(value, nullptr);
moduleSet("__main__", "key", key);
return 0;
};
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("Bar", Py_mp_ass_subscript, del_func));
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r = b.__delitem__("another key")
)"),
0);
PyObjectPtr r(mainModuleGet("r"));
EXPECT_EQ(r, Py_None);
PyObjectPtr key(mainModuleGet("key"));
EXPECT_TRUE(isUnicodeEqualsCStr(key, "another key"));
}
TEST_F(TypeExtensionApiTest, CallLenSlotFromManagedCode) {
lenfunc len_func = [](PyObject* self) -> Py_ssize_t {
PyObjectPtr b(mainModuleGet("b"));
EXPECT_EQ(self, b);
return 0xdeadbeef;
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_sq_length, len_func));
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r = b.__len__()
)"),
0);
PyObjectPtr r(mainModuleGet("r"));
EXPECT_TRUE(isLongEqualsLong(r, 0xdeadbeef));
}
TEST_F(TypeExtensionApiTest, CallIndexargSlotFromManagedCode) {
ssizeargfunc mul_func = [](PyObject* self, Py_ssize_t i) {
PyObjectPtr b(mainModuleGet("b"));
EXPECT_EQ(self, b);
return PyLong_FromLong(i * 456);
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_sq_repeat, mul_func));
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r = b.__mul__(123)
)"),
0);
PyObjectPtr r(mainModuleGet("r"));
EXPECT_TRUE(isLongEqualsLong(r, 123 * 456));
}
TEST_F(TypeExtensionApiTest, CallSqItemSlotFromManagedCode) {
ssizeargfunc item_func = [](PyObject* self, Py_ssize_t i) {
PyObjectPtr b(mainModuleGet("b"));
EXPECT_EQ(self, b);
return PyLong_FromLong(i + 100);
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_sq_item, item_func));
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r = b.__getitem__(1337)
)"),
0);
PyObjectPtr r(mainModuleGet("r"));
EXPECT_TRUE(isLongEqualsLong(r, 1337 + 100));
}
TEST_F(TypeExtensionApiTest, CallSqSetitemSlotFromManagedCode) {
ssizeobjargproc set_func = [](PyObject* self, Py_ssize_t i, PyObject* value) {
PyObjectPtr b(mainModuleGet("b"));
EXPECT_EQ(self, b);
PyObjectPtr key(PyLong_FromLong(i));
moduleSet("__main__", "key", key);
moduleSet("__main__", "value", value);
return 0;
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_sq_ass_item, set_func));
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r = b.__setitem__(123, 456)
)"),
0);
PyObjectPtr r(mainModuleGet("r"));
EXPECT_EQ(r, Py_None);
PyObjectPtr key(mainModuleGet("key"));
EXPECT_TRUE(isLongEqualsLong(key, 123));
PyObjectPtr value(mainModuleGet("value"));
EXPECT_TRUE(isLongEqualsLong(value, 456));
}
TEST_F(TypeExtensionApiTest, CallSqDelitemSlotFromManagedCode) {
ssizeobjargproc del_func = [](PyObject* self, Py_ssize_t i, PyObject* value) {
PyObjectPtr b(mainModuleGet("b"));
EXPECT_EQ(self, b);
PyObjectPtr key(PyLong_FromLong(i));
moduleSet("__main__", "key", key);
EXPECT_EQ(value, nullptr);
return 0;
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_sq_ass_item, del_func));
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r = b.__delitem__(7890)
)"),
0);
PyObjectPtr r(mainModuleGet("r"));
EXPECT_EQ(r, Py_None);
PyObjectPtr key(mainModuleGet("key"));
EXPECT_TRUE(isLongEqualsLong(key, 7890));
}
TEST_F(TypeExtensionApiTest, HashNotImplementedSlotSetsNoneDunderHash) {
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("Bar", Py_tp_hash, PyObject_HashNotImplemented));
PyObjectPtr bar(mainModuleGet("Bar"));
PyObjectPtr hash(PyObject_GetAttrString(bar, "__hash__"));
EXPECT_EQ(hash, Py_None);
}
TEST_F(TypeExtensionApiTest, CallNewSlotFromManagedCode) {
ternaryfunc new_func = [](PyObject* type, PyObject* args, PyObject* kwargs) {
PyObjectPtr name(PyObject_GetAttrString(type, "__name__"));
EXPECT_TRUE(isUnicodeEqualsCStr(name, "Bar"));
EXPECT_EQ(PyTuple_Check(args), 1);
EXPECT_EQ(kwargs, nullptr);
Py_INCREF(args);
return args;
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_tp_new, new_func));
ASSERT_EQ(PyRun_SimpleString(R"(
r0 = Bar.__new__(Bar, 1, 2, 3)
r1 = Bar(1, 2, 3)
)"),
0);
PyObjectPtr r0(mainModuleGet("r0"));
ASSERT_EQ(PyTuple_Check(r0), 1);
ASSERT_EQ(PyTuple_Size(r0), 3);
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(r0, 0), 1));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(r0, 1), 2));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(r0, 2), 3));
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyTuple_Check(r1), 1);
ASSERT_EQ(PyTuple_Size(r1), 3);
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(r1, 0), 1));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(r1, 1), 2));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(r1, 2), 3));
}
TEST_F(TypeExtensionApiTest, NbAddSlotTakesPrecedenceOverSqConcatSlot) {
binaryfunc add_func = [](PyObject* /* self */, PyObject* obj) {
EXPECT_TRUE(isUnicodeEqualsCStr(obj, "foo"));
return PyLong_FromLong(0xf00);
};
binaryfunc concat_func = [](PyObject*, PyObject*) -> PyObject* {
std::abort();
};
static PyType_Slot slots[3];
// Both of these slots map to __add__. nb_add appears in slotdefs first, so it
// wins.
slots[0] = {Py_nb_add, reinterpret_cast<void*>(add_func)};
slots[1] = {Py_sq_concat, reinterpret_cast<void*>(concat_func)};
slots[2] = {0, nullptr};
static PyType_Spec spec;
spec = {
"__main__.Bar", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
ASSERT_EQ(PyType_CheckExact(type), 1);
ASSERT_EQ(moduleSet("__main__", "Bar", type), 0);
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r = b.__add__("foo")
)"),
0);
PyObjectPtr r(mainModuleGet("r"));
EXPECT_TRUE(isLongEqualsLong(r, 0xf00));
}
TEST_F(TypeExtensionApiTest, TypeSlotPropagatesException) {
binaryfunc add_func = [](PyObject*, PyObject*) -> PyObject* {
PyErr_SetString(PyExc_RuntimeError, "hello, there!");
return nullptr;
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_nb_add, add_func));
// TODO(T40700664): Use PyRun_String() so we can inspect the exception more
// directly.
EXPECT_EQ(PyRun_SimpleString(R"(
exc = None
try:
Bar().__add__(1)
except RuntimeError as e:
exc = e
)"),
0);
PyObjectPtr exc(mainModuleGet("exc"));
EXPECT_EQ(PyErr_GivenExceptionMatches(exc, PyExc_RuntimeError), 1);
}
typedef void (*verifyfunc)(PyObject*);
static verifyfunc createBarTypeWithMembers() {
struct BarObject {
PyObject_HEAD
char t_bool;
char t_byte;
unsigned char t_ubyte;
short t_short;
unsigned short t_ushort;
int t_int;
unsigned int t_uint;
long t_long;
unsigned long t_ulong;
Py_ssize_t t_pyssizet;
float t_float;
double t_double;
const char* t_string;
char t_char;
PyObject* t_object;
PyObject* t_object_null;
long long t_longlong;
unsigned long long t_ulonglong;
};
static const PyMemberDef members[] = {
{"t_bool", T_BOOL, offsetof(BarObject, t_bool)},
{"t_byte", T_BYTE, offsetof(BarObject, t_byte)},
{"t_ubyte", T_UBYTE, offsetof(BarObject, t_ubyte)},
{"t_short", T_SHORT, offsetof(BarObject, t_short)},
{"t_ushort", T_USHORT, offsetof(BarObject, t_ushort)},
{"t_int", T_INT, offsetof(BarObject, t_int)},
{"t_uint", T_UINT, offsetof(BarObject, t_uint)},
{"t_long", T_LONG, offsetof(BarObject, t_long)},
{"t_ulong", T_ULONG, offsetof(BarObject, t_ulong)},
{"t_pyssize", T_PYSSIZET, offsetof(BarObject, t_pyssizet)},
{"t_float", T_FLOAT, offsetof(BarObject, t_float)},
{"t_double", T_DOUBLE, offsetof(BarObject, t_double)},
{"t_string", T_STRING, offsetof(BarObject, t_string)},
{"t_char", T_CHAR, offsetof(BarObject, t_char)},
{"t_object", T_OBJECT, offsetof(BarObject, t_object)},
{"t_object_null", T_OBJECT, offsetof(BarObject, t_object_null)},
{"t_objectex", T_OBJECT_EX, offsetof(BarObject, t_object)},
{"t_objectex_null", T_OBJECT_EX, offsetof(BarObject, t_object_null)},
{"t_longlong", T_LONGLONG, offsetof(BarObject, t_longlong)},
{"t_ulonglong", T_ULONGLONG, offsetof(BarObject, t_ulonglong)},
{"t_int_readonly", T_INT, offsetof(BarObject, t_int), READONLY},
{nullptr},
};
newfunc new_func = [](PyTypeObject* type, PyObject*, PyObject*) {
void* slot = PyType_GetSlot(type, Py_tp_alloc);
return reinterpret_cast<allocfunc>(slot)(type, 0);
};
freefunc dealloc_func = [](void* self_ptr) {
PyObject* self = static_cast<PyObject*>(self_ptr);
BarObject* self_bar = static_cast<BarObject*>(self_ptr);
// Guaranteed to be null or initialized by something
Py_XDECREF(self_bar->t_object);
PyTypeObject* type = Py_TYPE(self);
// Since this object is subtypable (has Py_TPFLAGS_BASETYPE), we must pull
// out tp_free slot instead of calling PyObject_Del.
void* slot = PyType_GetSlot(type, Py_tp_free);
assert(slot != nullptr);
reinterpret_cast<freefunc>(slot)(self);
Py_DECREF(type);
};
initproc init_func = [](PyObject* self, PyObject*, PyObject*) {
BarObject* bar_obj = reinterpret_cast<BarObject*>(self);
bar_obj->t_bool = 1;
bar_obj->t_byte = -12;
bar_obj->t_ubyte = std::numeric_limits<unsigned char>::max();
bar_obj->t_short = -12;
bar_obj->t_ushort = std::numeric_limits<unsigned short>::max();
bar_obj->t_int = -1234;
bar_obj->t_uint = std::numeric_limits<unsigned int>::max();
bar_obj->t_long = -1234;
bar_obj->t_ulong = std::numeric_limits<unsigned long>::max();
bar_obj->t_pyssizet = 1234;
bar_obj->t_float = 1.0;
bar_obj->t_double = 1.0;
bar_obj->t_string = "foo";
bar_obj->t_char = 'a';
bar_obj->t_object = PyList_New(0);
bar_obj->t_object_null = nullptr;
bar_obj->t_longlong = std::numeric_limits<long long>::max();
bar_obj->t_ulonglong = std::numeric_limits<unsigned long long>::max();
return 0;
};
static const PyType_Slot slots[] = {
{Py_tp_new, reinterpret_cast<void*>(new_func)},
{Py_tp_init, reinterpret_cast<void*>(init_func)},
{Py_tp_dealloc, reinterpret_cast<void*>(dealloc_func)},
{Py_tp_members, const_cast<PyMemberDef*>(members)},
{0, nullptr},
};
static PyType_Spec spec = {
"__main__.Bar",
sizeof(BarObject),
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
const_cast<PyType_Slot*>(slots),
};
PyObjectPtr type(PyType_FromSpec(&spec));
moduleSet("__main__", "Bar", type);
// Verifies the original values
return [](PyObject* self) {
BarObject* bar_obj = reinterpret_cast<BarObject*>(self);
ASSERT_EQ(bar_obj->t_bool, 1);
ASSERT_EQ(bar_obj->t_byte, -12);
ASSERT_EQ(bar_obj->t_ubyte, std::numeric_limits<unsigned char>::max());
ASSERT_EQ(bar_obj->t_short, -12);
ASSERT_EQ(bar_obj->t_ushort, std::numeric_limits<unsigned short>::max());
ASSERT_EQ(bar_obj->t_int, -1234);
ASSERT_EQ(bar_obj->t_uint, std::numeric_limits<unsigned int>::max());
ASSERT_EQ(bar_obj->t_long, -1234);
ASSERT_EQ(bar_obj->t_ulong, std::numeric_limits<unsigned long>::max());
ASSERT_EQ(bar_obj->t_pyssizet, 1234);
ASSERT_EQ(bar_obj->t_float, 1.0);
ASSERT_EQ(bar_obj->t_double, 1.0);
ASSERT_EQ(bar_obj->t_string, "foo");
ASSERT_EQ(bar_obj->t_char, 'a');
ASSERT_TRUE(PyList_CheckExact(bar_obj->t_object));
ASSERT_EQ(PyList_Size(bar_obj->t_object), 0);
ASSERT_EQ(bar_obj->t_object_null, nullptr);
ASSERT_EQ(bar_obj->t_longlong, std::numeric_limits<long long>::max());
ASSERT_EQ(bar_obj->t_ulonglong,
std::numeric_limits<unsigned long long>::max());
};
}
TEST_F(TypeExtensionApiTest, MemberBool) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.t_bool
b.t_bool = False
r2 = b.t_bool
b.t_bool = r1
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyBool_Check(r1), 1);
EXPECT_EQ(r1, Py_True);
PyObjectPtr r2(mainModuleGet("r2"));
ASSERT_EQ(PyBool_Check(r2), 1);
EXPECT_EQ(r2, Py_False);
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
TEST_F(TypeExtensionApiTest, MemberByte) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.t_byte
b.t_byte = 21
r2 = b.t_byte
b.t_byte = r1
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyLong_Check(r1), 1);
EXPECT_TRUE(isLongEqualsLong(r1, -12));
PyObjectPtr r2(mainModuleGet("r2"));
ASSERT_EQ(PyLong_Check(r2), 1);
EXPECT_TRUE(isLongEqualsLong(r2, 21));
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
TEST_F(TypeExtensionApiTest, MemberUByte) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.t_ubyte
b.t_ubyte = 21
r2 = b.t_ubyte
b.t_ubyte = r1
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyLong_Check(r1), 1);
EXPECT_TRUE(isLongEqualsLong(r1, std::numeric_limits<unsigned char>::max()));
PyObjectPtr r2(mainModuleGet("r2"));
ASSERT_EQ(PyLong_Check(r2), 1);
EXPECT_TRUE(isLongEqualsLong(r2, 21));
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
TEST_F(TypeExtensionApiTest, MemberShort) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.t_short
b.t_short = 21
r2 = b.t_short
b.t_short = r1
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyLong_Check(r1), 1);
EXPECT_TRUE(isLongEqualsLong(r1, -12));
PyObjectPtr r2(mainModuleGet("r2"));
ASSERT_EQ(PyLong_Check(r2), 1);
EXPECT_TRUE(isLongEqualsLong(r2, 21));
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
TEST_F(TypeExtensionApiTest, MemberUShort) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.t_ushort
b.t_ushort = 21
r2 = b.t_ushort
b.t_ushort = r1
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyLong_Check(r1), 1);
EXPECT_TRUE(isLongEqualsLong(r1, std::numeric_limits<unsigned short>::max()));
PyObjectPtr r2(mainModuleGet("r2"));
ASSERT_EQ(PyLong_Check(r2), 1);
EXPECT_TRUE(isLongEqualsLong(r2, 21));
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
TEST_F(TypeExtensionApiTest, MemberInt) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.t_int
b.t_int = 4321
r2 = b.t_int
b.t_int = r1
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyLong_Check(r1), 1);
EXPECT_TRUE(isLongEqualsLong(r1, -1234));
PyObjectPtr r2(mainModuleGet("r2"));
ASSERT_EQ(PyLong_Check(r2), 1);
EXPECT_TRUE(isLongEqualsLong(r2, 4321));
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
TEST_F(TypeExtensionApiTest, MemberUInt) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.t_uint
b.t_uint = 4321
r2 = b.t_uint
b.t_uint = r1
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyLong_Check(r1), 1);
EXPECT_EQ(PyLong_AsUnsignedLong(r1),
std::numeric_limits<unsigned int>::max());
PyObjectPtr r2(mainModuleGet("r2"));
ASSERT_EQ(PyLong_Check(r2), 1);
EXPECT_EQ(PyLong_AsUnsignedLong(r2), 4321UL);
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
TEST_F(TypeExtensionApiTest, MemberLong) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.t_long
b.t_long = 4321
r2 = b.t_long
b.t_long = r1
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyLong_Check(r1), 1);
EXPECT_TRUE(isLongEqualsLong(r1, -1234));
PyObjectPtr r2(mainModuleGet("r2"));
ASSERT_EQ(PyLong_Check(r2), 1);
EXPECT_TRUE(isLongEqualsLong(r2, 4321));
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
TEST_F(TypeExtensionApiTest, MemberULong) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.t_ulong
b.t_ulong = 4321
r2 = b.t_ulong
b.t_ulong = r1
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyLong_Check(r1), 1);
EXPECT_EQ(PyLong_AsUnsignedLong(r1),
std::numeric_limits<unsigned long>::max());
PyObjectPtr r2(mainModuleGet("r2"));
ASSERT_EQ(PyLong_Check(r2), 1);
EXPECT_EQ(PyLong_AsUnsignedLong(r2), 4321UL);
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
TEST_F(TypeExtensionApiTest, MemberLongLong) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.t_longlong
b.t_longlong = -4321
r2 = b.t_longlong
b.t_longlong = r1
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyLong_Check(r1), 1);
EXPECT_EQ(PyLong_AsLongLong(r1), std::numeric_limits<long long>::max());
PyObjectPtr r2(mainModuleGet("r2"));
ASSERT_EQ(PyLong_Check(r2), 1);
EXPECT_TRUE(isLongEqualsLong(r2, -4321));
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
TEST_F(TypeExtensionApiTest, MemberULongLong) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.t_ulonglong
b.t_ulonglong = 4321
r2 = b.t_ulonglong
b.t_ulonglong = r1
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyLong_Check(r1), 1);
EXPECT_EQ(PyLong_AsUnsignedLongLong(r1),
std::numeric_limits<unsigned long long>::max());
PyObjectPtr r2(mainModuleGet("r2"));
ASSERT_EQ(PyLong_Check(r2), 1);
EXPECT_EQ(PyLong_AsUnsignedLongLong(r2), 4321UL);
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
TEST_F(TypeExtensionApiTest, MemberFloat) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.t_float
b.t_float = 1.5
r2 = b.t_float
b.t_float = r1
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyFloat_Check(r1), 1);
EXPECT_EQ(PyFloat_AsDouble(r1), 1.0);
PyObjectPtr r2(mainModuleGet("r2"));
ASSERT_EQ(PyFloat_Check(r2), 1);
EXPECT_EQ(PyFloat_AsDouble(r2), 1.5);
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
TEST_F(TypeExtensionApiTest, MemberDouble) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.t_double
b.t_double = 1.5
r2 = b.t_double
b.t_double = r1
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyFloat_Check(r1), 1);
EXPECT_EQ(PyFloat_AsDouble(r1), 1.0);
PyObjectPtr r2(mainModuleGet("r2"));
ASSERT_EQ(PyFloat_Check(r2), 1);
EXPECT_EQ(PyFloat_AsDouble(r2), 1.5);
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
TEST_F(TypeExtensionApiTest, MemberChar) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.t_char
b.t_char = 'b'
r2 = b.t_char
b.t_char = r1
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyUnicode_Check(r1), 1);
EXPECT_TRUE(isUnicodeEqualsCStr(r1, "a"));
PyObjectPtr r2(mainModuleGet("r2"));
ASSERT_EQ(PyUnicode_Check(r2), 1);
EXPECT_TRUE(isUnicodeEqualsCStr(r2, "b"));
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
TEST_F(TypeExtensionApiTest, MemberString) {
// T_STRING is read-only
ASSERT_NO_FATAL_FAILURE(createBarTypeWithMembers());
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.t_string
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyUnicode_Check(r1), 1);
EXPECT_TRUE(isUnicodeEqualsCStr(r1, "foo"));
}
TEST_F(TypeExtensionApiTest, MemberStringWithNullReturnsNone) {
struct BarObject {
PyObject_HEAD
char* name;
};
static const PyMemberDef members[] = {
{"name", T_STRING, offsetof(BarObject, name), 0, nullptr},
{nullptr},
};
static const PyType_Slot slots[] = {
{Py_tp_members, const_cast<PyMemberDef*>(members)},
{0, nullptr},
};
static PyType_Spec spec = {
"__main__.Bar",
sizeof(BarObject),
0,
Py_TPFLAGS_DEFAULT,
const_cast<PyType_Slot*>(slots),
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_TRUE(PyType_Check(type));
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_EQ(moduleSet("__main__", "Bar", type), 0);
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
none = b.name
)"),
0);
PyObjectPtr none(mainModuleGet("none"));
ASSERT_EQ(none, Py_None);
} // namespace testing
TEST_F(TypeExtensionApiTest, MemberStringRaisesTypeError) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
raised = False
try:
b.t_string = "bar"
raise RuntimeError("call didn't throw")
except TypeError:
raised = True
r1 = b.t_string
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
PyObjectPtr raised(mainModuleGet("raised"));
EXPECT_EQ(raised, Py_True);
ASSERT_EQ(PyUnicode_Check(r1), 1);
EXPECT_TRUE(isUnicodeEqualsCStr(r1, "foo"));
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
TEST_F(TypeExtensionApiTest, MemberObject) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.t_object
b.t_object = (1, "a", 2, "b", 3, "c")
r2 = b.t_object
b.t_object = r1
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyList_Check(r1), 1);
EXPECT_EQ(PyList_Size(r1), 0);
PyObjectPtr r2(mainModuleGet("r2"));
ASSERT_EQ(PyTuple_Check(r2), 1);
EXPECT_EQ(PyTuple_Size(r2), 6);
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
TEST_F(TypeExtensionApiTest, MemberObjectWithNull) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.t_object_null
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
EXPECT_EQ(r1, Py_None);
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
TEST_F(TypeExtensionApiTest, MemberObjectEx) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.t_objectex
b.t_objectex = tuple()
r2 = b.t_objectex
b.t_objectex = r1
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyList_Check(r1), 1);
EXPECT_EQ(PyList_Size(r1), 0);
PyObjectPtr r2(mainModuleGet("r2"));
ASSERT_EQ(PyTuple_Check(r2), 1);
EXPECT_EQ(PyTuple_Size(r2), 0);
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
TEST_F(TypeExtensionApiTest, MemberObjectExWithNullRaisesAttributeError) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
raised = False
try:
b.t_objectex_null
raise RuntimeError("call didn't throw")
except AttributeError:
raised = True
)"),
0);
PyObjectPtr raised(mainModuleGet("raised"));
EXPECT_EQ(raised, Py_True);
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
TEST_F(TypeExtensionApiTest, MemberPySsizeT) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.t_pyssize
b.t_pyssize = 4321
r2 = b.t_pyssize
b.t_pyssize = r1
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyLong_Check(r1), 1);
EXPECT_EQ(PyLong_AsSsize_t(r1), 1234);
PyObjectPtr r2(mainModuleGet("r2"));
ASSERT_EQ(PyLong_Check(r2), 1);
EXPECT_EQ(PyLong_AsSsize_t(r2), 4321);
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
TEST_F(TypeExtensionApiTest, MemberReadOnlyRaisesAttributeError) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.t_int_readonly
raised = False
try:
b.t_int_readonly = 4321
raise RuntimeError("call didn't throw")
except AttributeError:
raised = True
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyLong_Check(r1), 1);
EXPECT_TRUE(isLongEqualsLong(r1, -1234));
PyObjectPtr raised(mainModuleGet("raised"));
EXPECT_EQ(raised, Py_True);
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
TEST_F(TypeExtensionApiTest, MemberIntSetIncorrectTypeRaisesTypeError) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
raised = False
try:
b.t_int = "foo"
raise RuntimeError("call didn't throw")
except TypeError:
raised = True
r1 = b.t_int
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
PyObjectPtr raised(mainModuleGet("raised"));
EXPECT_EQ(raised, Py_True);
ASSERT_EQ(PyLong_Check(r1), 1);
EXPECT_TRUE(isLongEqualsLong(r1, -1234));
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
TEST_F(TypeExtensionApiTest, MemberCharIncorrectSizeRaisesTypeError) {
auto verify_func = createBarTypeWithMembers();
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
raised = False
try:
b.t_char = "foo"
raise RuntimeError("call didn't throw")
except TypeError:
raised = True
r1 = b.t_char
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
PyObjectPtr raised(mainModuleGet("raised"));
EXPECT_EQ(raised, Py_True);
ASSERT_EQ(PyUnicode_Check(r1), 1);
EXPECT_TRUE(isUnicodeEqualsCStr(r1, "a"));
PyObjectPtr b(mainModuleGet("b"));
ASSERT_NO_FATAL_FAILURE(verify_func(b));
}
// Pyro raises a SystemError but CPython returns a new type.
// TODO(T56634824): Figure out why Pyro differs from CPython.
TEST_F(TypeExtensionApiTest, MemberUnknownRaisesSystemErrorPyro) {
int unknown_type = -1;
struct BarObject {
PyObject_HEAD
int value;
};
static const PyMemberDef members[] = {
{"value", unknown_type, offsetof(BarObject, value), 0, nullptr},
{nullptr},
};
static const PyType_Slot slots[] = {
{Py_tp_members, const_cast<PyMemberDef*>(members)},
{0, nullptr},
};
static PyType_Spec spec = {
"__main__.Bar",
sizeof(BarObject),
0,
Py_TPFLAGS_DEFAULT,
const_cast<PyType_Slot*>(slots),
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_EQ(type, nullptr);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_SystemError));
}
static void createBarTypeWithGetSetObject() {
struct BarObject {
PyObject_HEAD
long attribute;
long readonly_attribute;
};
getter attribute_getter = [](PyObject* self, void*) {
return PyLong_FromLong(reinterpret_cast<BarObject*>(self)->attribute);
};
setter attribute_setter = [](PyObject* self, PyObject* value, void*) {
reinterpret_cast<BarObject*>(self)->attribute = PyLong_AsLong(value);
return 0;
};
getter readonly_attribute_getter = [](PyObject* self, void*) {
return PyLong_FromLong(
reinterpret_cast<BarObject*>(self)->readonly_attribute);
};
setter raise_attribute_setter = [](PyObject*, PyObject*, void*) {
PyErr_BadArgument();
return -1;
};
static PyGetSetDef getsets[4];
getsets[0] = {"attribute", attribute_getter, attribute_setter};
getsets[1] = {"readonly_attribute", readonly_attribute_getter, nullptr};
getsets[2] = {"raise_attribute", attribute_getter, raise_attribute_setter};
getsets[3] = {nullptr};
newfunc new_func = [](PyTypeObject* type, PyObject*, PyObject*) {
void* slot = PyType_GetSlot(type, Py_tp_alloc);
return reinterpret_cast<allocfunc>(slot)(type, 0);
};
initproc init_func = [](PyObject* self, PyObject*, PyObject*) {
reinterpret_cast<BarObject*>(self)->attribute = 123;
reinterpret_cast<BarObject*>(self)->readonly_attribute = 456;
return 0;
};
static PyType_Slot slots[6];
// TODO(T40540469): Most of functions should be inherited from object.
// However, inheritance is not supported yet. For now, just set them manually.
slots[0] = {Py_tp_new, reinterpret_cast<void*>(new_func)};
slots[1] = {Py_tp_init, reinterpret_cast<void*>(init_func)};
slots[2] = {Py_tp_alloc, reinterpret_cast<void*>(PyType_GenericAlloc)},
slots[3] = {Py_tp_dealloc, reinterpret_cast<void*>(deallocLeafObject)};
slots[4] = {Py_tp_getset, reinterpret_cast<void*>(getsets)};
slots[5] = {0, nullptr};
static PyType_Spec spec;
spec = {
"__main__.Bar", sizeof(BarObject), 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
ASSERT_EQ(PyType_CheckExact(type), 1);
ASSERT_EQ(moduleSet("__main__", "Bar", type), 0);
}
TEST_F(TypeExtensionApiTest, GetSetAttributePyro) {
ASSERT_NO_FATAL_FAILURE(createBarTypeWithGetSetObject());
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
r1 = b.attribute
b.attribute = 321
r2 = b.attribute
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyLong_Check(r1), 1);
EXPECT_EQ(PyLong_AsLong(r1), 123);
PyObjectPtr r2(mainModuleGet("r2"));
ASSERT_EQ(PyLong_Check(r2), 1);
EXPECT_EQ(PyLong_AsLong(r2), 321);
}
TEST_F(TypeExtensionApiTest, GetSetReadonlyAttributePyro) {
ASSERT_NO_FATAL_FAILURE(createBarTypeWithGetSetObject());
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
raised = False
try:
b.readonly_attribute = 321
raise RuntimeError("call didn't throw")
except AttributeError:
raised = True
r1 = b.readonly_attribute
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
PyObjectPtr raised(mainModuleGet("raised"));
EXPECT_EQ(raised, Py_True);
ASSERT_EQ(PyLong_Check(r1), 1);
EXPECT_EQ(PyLong_AsLong(r1), 456);
}
TEST_F(TypeExtensionApiTest, GetSetRaiseAttributePyro) {
ASSERT_NO_FATAL_FAILURE(createBarTypeWithGetSetObject());
ASSERT_EQ(PyRun_SimpleString(R"(
b = Bar()
raised = False
try:
b.raise_attribute = 321
raise SystemError("call didn't throw")
except TypeError:
raised = True
r1 = b.raise_attribute
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
PyObjectPtr raised(mainModuleGet("raised"));
EXPECT_EQ(raised, Py_True);
ASSERT_EQ(PyLong_Check(r1), 1);
EXPECT_EQ(PyLong_AsLong(r1), 123);
}
TEST_F(TypeExtensionApiTest, PyTypeNameWithNullTypeRaisesSystemErrorPyro) {
EXPECT_EQ(_PyType_Name(nullptr), nullptr);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_SystemError));
}
TEST_F(TypeExtensionApiTest, PyTypeNameWithNonTypeRaisesSystemErrorPyro) {
PyObjectPtr long_obj(PyLong_FromLong(5));
EXPECT_EQ(_PyType_Name(reinterpret_cast<PyTypeObject*>(long_obj.get())),
nullptr);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_SystemError));
}
TEST_F(TypeExtensionApiTest, PyTypeNameWithBuiltinTypeReturnsName) {
PyObjectPtr long_obj(PyLong_FromLong(5));
const char* name = _PyType_Name(Py_TYPE(long_obj));
EXPECT_STREQ(name, "int");
}
TEST_F(TypeExtensionApiTest, PyTypeNameReturnsSamePointerEachCall) {
PyObjectPtr long_obj(PyLong_FromLong(5));
const char* name = _PyType_Name(Py_TYPE(long_obj));
EXPECT_STREQ(name, "int");
const char* name2 = _PyType_Name(Py_TYPE(long_obj));
EXPECT_EQ(name, name2);
}
TEST_F(TypeExtensionApiTest, PyTypeNameWithUserDefinedTypeReturnsName) {
PyRun_SimpleString(R"(
class FooBarTheBaz:
pass
)");
PyObjectPtr c(mainModuleGet("FooBarTheBaz"));
const char* name = _PyType_Name(reinterpret_cast<PyTypeObject*>(c.get()));
EXPECT_STREQ(name, "FooBarTheBaz");
}
static PyObject* emptyBinaryFunc(PyObject*, PyObject*) { return Py_None; }
static void emptyDestructorFunc(PyObject*) { return; }
static Py_ssize_t emptyLenFunc(PyObject*) { return Py_ssize_t{0}; }
static PyObject* emptyCompareFunc(PyObject*, PyObject*, int) { return Py_None; }
static int emptySetattroFunc(PyObject*, PyObject*, PyObject*) { return 0; }
static PyObject* emptyTernaryFunc(PyObject*, PyObject*, PyObject*) {
return Py_None;
}
static PyObject* emptyUnaryFunc(PyObject*) { return Py_None; }
TEST_F(TypeExtensionApiTest,
GetSlotFromExceptionWithTpNewReturnsConstructorPyro) {
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"Subclass", Py_tp_init, &emptyUnaryFunc, PyExc_Exception));
PyObjectPtr subclass(mainModuleGet("Subclass"));
ASSERT_NE(subclass, nullptr);
newfunc dunder_new = reinterpret_cast<newfunc>(PyType_GetSlot(
reinterpret_cast<PyTypeObject*>(PyExc_Exception), Py_tp_new));
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_NE(dunder_new, nullptr);
PyObjectPtr instance(dunder_new(
reinterpret_cast<PyTypeObject*>(subclass.get()), nullptr, nullptr));
ASSERT_NE(instance, nullptr);
EXPECT_TRUE(PyErr_GivenExceptionMatches(instance, subclass));
}
TEST_F(TypeExtensionApiTest,
GetSlotFromExceptionWithNonZeroSizeWithTpNewReturnsConstructorPyro) {
PyType_Slot slots[] = {
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"foo.Foo", sizeof(PyObject), 0, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
slots,
};
PyObjectPtr bases(PyTuple_Pack(1, PyExc_Exception));
PyObjectPtr subclass(PyType_FromSpecWithBases(&spec, bases));
ASSERT_NE(subclass, nullptr);
newfunc dunder_new = reinterpret_cast<newfunc>(PyType_GetSlot(
reinterpret_cast<PyTypeObject*>(PyExc_Exception), Py_tp_new));
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_NE(dunder_new, nullptr);
PyObjectPtr instance(dunder_new(
reinterpret_cast<PyTypeObject*>(subclass.get()), nullptr, nullptr));
ASSERT_NE(instance, nullptr);
EXPECT_TRUE(PyErr_GivenExceptionMatches(instance, subclass));
}
TEST_F(TypeExtensionApiTest,
ExceptionSubclassWithNonZeroConstructorCreatesExceptionSubclass) {
PyObjectPtr basicsize(
PyObject_GetAttrString(PyExc_Exception, "__basicsize__"));
ASSERT_TRUE(PyLong_Check(basicsize));
int size = _PyLong_AsInt(basicsize);
PyType_Slot slots[] = {
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"foo.Foo", size, 0, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, slots,
};
PyObjectPtr bases(PyTuple_Pack(1, PyExc_Exception));
PyObjectPtr subclass(PyType_FromSpecWithBases(&spec, bases));
ASSERT_NE(subclass, nullptr);
newfunc dunder_new = reinterpret_cast<newfunc>(PyType_GetSlot(
reinterpret_cast<PyTypeObject*>(subclass.get()), Py_tp_new));
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_NE(dunder_new, nullptr);
PyObjectPtr empty_tuple(PyTuple_New(0));
PyObjectPtr instance(dunder_new(
reinterpret_cast<PyTypeObject*>(subclass.get()), empty_tuple, nullptr));
ASSERT_NE(instance, nullptr);
EXPECT_TRUE(PyErr_GivenExceptionMatches(instance, subclass));
}
TEST_F(TypeExtensionApiTest, GetSlotFromTypeWithTpNewReturnsConstructorPyro) {
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlotAndBase("Subclass", Py_tp_init, &emptyUnaryFunc,
reinterpret_cast<PyObject*>(&PyType_Type)));
PyObjectPtr subclass(mainModuleGet("Subclass"));
ASSERT_NE(subclass, nullptr);
newfunc dunder_new =
reinterpret_cast<newfunc>(PyType_GetSlot(&PyType_Type, Py_tp_new));
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_NE(dunder_new, nullptr);
PyObjectPtr args(PyTuple_New(3));
ASSERT_EQ(PyTuple_SetItem(args, 0, PyUnicode_FromString("Subclass")), 0);
ASSERT_EQ(PyTuple_SetItem(args, 1, PyTuple_Pack(1, &PyType_Type)), 0);
ASSERT_EQ(PyTuple_SetItem(args, 2, PyDict_New()), 0);
PyObjectPtr instance(dunder_new(
reinterpret_cast<PyTypeObject*>(subclass.get()), args, nullptr));
ASSERT_NE(instance, nullptr);
EXPECT_TRUE(PyType_IsSubtype(reinterpret_cast<PyTypeObject*>(instance.get()),
&PyType_Type));
}
TEST_F(TypeExtensionApiTest, GetDestructorSlotsFromExceptionReturnsNoOpsPyro) {
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"Subclass", Py_tp_new, &emptyBinaryFunc, PyExc_Exception));
PyObject* subclass = mainModuleGet("Subclass");
PyTypeObject* exception = reinterpret_cast<PyTypeObject*>(PyExc_Exception);
inquiry tp_clear =
reinterpret_cast<inquiry>(PyType_GetSlot(exception, Py_tp_clear));
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_NE(tp_clear, nullptr);
EXPECT_EQ(tp_clear(subclass), 0);
destructor tp_dealloc =
reinterpret_cast<destructor>(PyType_GetSlot(exception, Py_tp_dealloc));
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_NE(tp_dealloc, nullptr);
tp_dealloc(subclass);
ASSERT_EQ(PyErr_Occurred(), nullptr);
traverseproc tp_traverse =
reinterpret_cast<traverseproc>(PyType_GetSlot(exception, Py_tp_traverse));
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_NE(tp_traverse, nullptr);
EXPECT_EQ(tp_traverse(
subclass, [](PyObject*, void*) { return 0; }, nullptr),
0);
}
TEST_F(TypeExtensionApiTest, FromSpecWithBasesSetsBaseSlots) {
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("BaseType", Py_nb_add, &emptyBinaryFunc));
PyObjectPtr base_type(mainModuleGet("BaseType"));
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"SubclassedType", Py_nb_add, &emptyBinaryFunc, base_type));
PyObjectPtr subclassed_type(mainModuleGet("SubclassedType"));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(subclassed_type.get());
PyObject* bases = static_cast<PyObject*>(PyType_GetSlot(tp, Py_tp_bases));
ASSERT_NE(bases, nullptr);
EXPECT_EQ(PyTuple_Check(bases), 1);
EXPECT_EQ(PyTuple_Size(bases), 1);
EXPECT_EQ(static_cast<PyObject*>(PyType_GetSlot(tp, Py_tp_base)),
base_type.get());
}
TEST_F(TypeExtensionApiTest, FromSpecWithBasesWithBuiltinBase) {
static PyType_Slot slots[] = {
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"foo.Bar", 0, 0, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, slots,
};
PyObjectPtr bases(PyTuple_Pack(1, &PyType_Type));
PyObjectPtr type(PyType_FromSpecWithBases(&spec, bases));
ASSERT_NE(type.get(), nullptr);
ASSERT_TRUE(PyType_CheckExact(type.get()));
EXPECT_TRUE(PyObject_IsInstance(reinterpret_cast<PyObject*>(type.get()),
reinterpret_cast<PyObject*>(&PyType_Type)));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(type.get());
PyObject* tp_bases = static_cast<PyObject*>(PyType_GetSlot(tp, Py_tp_bases));
ASSERT_NE(tp_bases, nullptr);
EXPECT_EQ(PyTuple_Check(tp_bases), 1);
EXPECT_EQ(PyTuple_Size(tp_bases), 1);
EXPECT_EQ(static_cast<PyTypeObject*>(PyType_GetSlot(tp, Py_tp_base)),
&PyType_Type);
}
TEST_F(TypeExtensionApiTest,
FromSpecWithBasesWithTypeObjectAsBaseInheritsTpSetAttro) {
static PyType_Slot slots[] = {
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"foo.Bar", 0, 0, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, slots,
};
PyObjectPtr bases(PyTuple_Pack(1, &PyType_Type));
PyObjectPtr type(PyType_FromSpecWithBases(&spec, bases));
ASSERT_NE(type.get(), nullptr);
ASSERT_TRUE(PyType_CheckExact(type.get()));
EXPECT_TRUE(PyObject_IsInstance(reinterpret_cast<PyObject*>(type.get()),
reinterpret_cast<PyObject*>(&PyType_Type)));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(type.get());
ASSERT_NE(PyType_GetSlot(tp, Py_tp_setattro), nullptr);
setattrofunc set_attrofunc =
reinterpret_cast<setattrofunc>(PyType_GetSlot(tp, Py_tp_setattro));
PyObjectPtr name(PyUnicode_FromString("foo"));
PyObjectPtr value(PyUnicode_FromString("foo_value"));
EXPECT_EQ(set_attrofunc(type.get(), name.get(), value.get()), 0);
PyObjectPtr foo_value(PyObject_GetAttrString(type.get(), "foo"));
EXPECT_EQ(value.get(), foo_value.get());
PyObjectPtr non_str_name(PyLong_FromLong(10));
EXPECT_NE(set_attrofunc(type.get(), non_str_name.get(), value.get()), 0);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_TypeError));
PyErr_Clear();
}
TEST_F(TypeExtensionApiTest, FromSpecWithBasesWithoutBasetypeIsRejectedAsBase) {
static PyType_Slot slots[] = {
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"Bar", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr bases(PyTuple_Pack(1, &PyType_Type));
PyObjectPtr type(PyType_FromSpecWithBases(&spec, bases));
ASSERT_NE(type.get(), nullptr);
PyObjectPtr main(Borrowed(PyImport_AddModule("__main__")));
Py_INCREF(type.get());
PyModule_AddObject(main, "Bar", type);
PyObjectPtr main_dict(Borrowed(PyModule_GetDict(main)));
EXPECT_EQ(
PyRun_String("class C(Bar): pass", Py_file_input, main_dict, main_dict),
nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_TypeError));
PyObject* exc_type;
PyObject* exc_value;
PyObject* exc_traceback;
PyErr_Fetch(&exc_type, &exc_value, &exc_traceback);
EXPECT_TRUE(isUnicodeEqualsCStr(exc_value,
"type 'Bar' is not an acceptable base type"));
}
TEST_F(
TypeExtensionApiTest,
FromSpecWithBasesWithNonZeroSizeBaseAndZeroBasicSizeAndItemSetsCustomTpNewPyro) {
PyType_Slot base_slots[] = {
{0, nullptr},
};
static PyType_Spec base_spec;
base_spec = {
"foo.Foo", 16, 0, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, base_slots,
};
PyObjectPtr base_type(PyType_FromSpec(&base_spec));
PyType_Slot slots[] = {
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"foo.Bar", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr bases(PyTuple_Pack(1, base_type.get()));
PyObjectPtr ext_type(PyType_FromSpecWithBases(&spec, bases));
ASSERT_NE(ext_type, nullptr);
ASSERT_TRUE(PyType_CheckExact(ext_type));
PyRun_SimpleString(R"(class D: pass)");
PyObjectPtr managed_type(mainModuleGet("D"));
ASSERT_NE(PyType_GetSlot(managed_type.asTypeObject(), Py_tp_new), nullptr);
EXPECT_NE(PyType_GetSlot(managed_type.asTypeObject(), Py_tp_new),
PyType_GetSlot(ext_type.asTypeObject(), Py_tp_new));
// Instances of `ext_type` does not cause any memory leak.
auto new_slot = reinterpret_cast<newfunc>(
PyType_GetSlot(ext_type.asTypeObject(), Py_tp_new));
PyObjectPtr args(PyTuple_New(0));
PyObjectPtr kwargs(PyDict_New());
PyObjectPtr result(new_slot(ext_type.asTypeObject(), args, kwargs));
ASSERT_NE(result, nullptr);
EXPECT_EQ(PyObject_IsInstance(result, ext_type), 1);
}
TEST_F(TypeExtensionApiTest,
FromSpecWithBasesWithoutBaseTypeFlagsRaisesTypeError) {
static PyType_Slot base_slots[1];
base_slots[0] = {0, nullptr};
static PyType_Spec base_spec;
base_spec = {
"__main__.BaseType", 0, 0, Py_TPFLAGS_DEFAULT, base_slots,
};
PyObjectPtr base_type(PyType_FromSpec(&base_spec));
ASSERT_NE(base_type, nullptr);
ASSERT_EQ(PyType_CheckExact(base_type), 1);
static PyType_Slot slots[1];
slots[0] = {0, nullptr};
static PyType_Spec spec;
spec = {
"__main__.SubclassedType", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr bases(PyTuple_Pack(1, base_type.get()));
ASSERT_EQ(PyType_FromSpecWithBases(&spec, bases), nullptr);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_TypeError));
}
TEST_F(TypeExtensionApiTest, FromSpecWithBasesInheritsNumberSlots) {
binaryfunc empty_binary_func2 = [](PyObject*, PyObject*) { return Py_None; };
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("BaseType", Py_nb_add, &emptyBinaryFunc));
PyObjectPtr base_type(mainModuleGet("BaseType"));
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"SubclassedType", Py_nb_subtract, empty_binary_func2, base_type));
PyObjectPtr subclassed_type(mainModuleGet("SubclassedType"));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(subclassed_type.get());
EXPECT_EQ(reinterpret_cast<binaryfunc>(PyType_GetSlot(tp, Py_nb_add)),
&emptyBinaryFunc);
EXPECT_EQ(reinterpret_cast<binaryfunc>(PyType_GetSlot(tp, Py_nb_subtract)),
empty_binary_func2);
}
TEST_F(TypeExtensionApiTest, FromSpecWithBasesInheritsAsyncSlots) {
unaryfunc empty_unary_func2 = [](PyObject*) { return Py_None; };
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("BaseType", Py_am_await, &emptyUnaryFunc));
PyObjectPtr base_type(mainModuleGet("BaseType"));
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"SubclassedType", Py_am_aiter, empty_unary_func2, base_type));
PyObjectPtr subclassed_type(mainModuleGet("SubclassedType"));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(subclassed_type.get());
EXPECT_EQ(reinterpret_cast<unaryfunc>(PyType_GetSlot(tp, Py_am_await)),
&emptyUnaryFunc);
EXPECT_EQ(reinterpret_cast<unaryfunc>(PyType_GetSlot(tp, Py_am_aiter)),
empty_unary_func2);
}
TEST_F(TypeExtensionApiTest, FromSpecWithBasesInheritsSequenceSlots) {
ssizeargfunc empty_sizearg_func = [](PyObject*, Py_ssize_t) {
return Py_None;
};
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("BaseType", Py_sq_concat, &emptyBinaryFunc));
PyObjectPtr base_type(mainModuleGet("BaseType"));
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"SubclassedType", Py_sq_repeat, empty_sizearg_func, base_type));
PyObjectPtr subclassed_type(mainModuleGet("SubclassedType"));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(subclassed_type.get());
EXPECT_EQ(reinterpret_cast<binaryfunc>(PyType_GetSlot(tp, Py_sq_concat)),
&emptyBinaryFunc);
EXPECT_EQ(reinterpret_cast<ssizeargfunc>(PyType_GetSlot(tp, Py_sq_repeat)),
empty_sizearg_func);
}
TEST_F(TypeExtensionApiTest, FromSpecWithBasesInheritsMappingSlots) {
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("BaseType", Py_mp_subscript, &emptyBinaryFunc));
PyObjectPtr base_type(mainModuleGet("BaseType"));
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"SubclassedType", Py_mp_length, &emptyLenFunc, base_type));
PyObjectPtr subclassed_type(mainModuleGet("SubclassedType"));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(subclassed_type.get());
EXPECT_EQ(reinterpret_cast<binaryfunc>(PyType_GetSlot(tp, Py_mp_subscript)),
&emptyBinaryFunc);
EXPECT_EQ(reinterpret_cast<lenfunc>(PyType_GetSlot(tp, Py_mp_length)),
&emptyLenFunc);
}
TEST_F(TypeExtensionApiTest, FromSpecWithBasesInheritsTypeSlots) {
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("BaseType", Py_tp_call, &emptyTernaryFunc));
PyObjectPtr base_type(mainModuleGet("BaseType"));
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"SubclassedType", Py_nb_add, &emptyBinaryFunc, base_type));
PyObjectPtr subclassed_type(mainModuleGet("SubclassedType"));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(subclassed_type.get());
EXPECT_EQ(reinterpret_cast<ternaryfunc>(PyType_GetSlot(tp, Py_tp_call)),
&emptyTernaryFunc);
}
TEST_F(TypeExtensionApiTest, FromSpecWithBasesInheritsMixedSlots) {
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("BaseType", Py_nb_add, &emptyBinaryFunc));
PyObjectPtr base_type(mainModuleGet("BaseType"));
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"SubclassedType", Py_mp_length, &emptyLenFunc, base_type));
PyObjectPtr subclassed_type(mainModuleGet("SubclassedType"));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(subclassed_type.get());
EXPECT_EQ(reinterpret_cast<binaryfunc>(PyType_GetSlot(tp, Py_nb_add)),
&emptyBinaryFunc);
EXPECT_EQ(reinterpret_cast<lenfunc>(PyType_GetSlot(tp, Py_mp_length)),
&emptyLenFunc);
}
TEST_F(TypeExtensionApiTest, FromSpecWithBasesDoesNotInheritGetAttrIfDefined) {
getattrfunc empty_getattr_func = [](PyObject*, char*) { return Py_None; };
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("BaseType", Py_tp_getattro, &emptyBinaryFunc));
PyObjectPtr base_type(mainModuleGet("BaseType"));
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"SubclassedType", Py_tp_getattr, empty_getattr_func, base_type));
PyObjectPtr subclassed_type(mainModuleGet("SubclassedType"));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(subclassed_type.get());
EXPECT_EQ(PyType_GetSlot(tp, Py_tp_getattro), nullptr);
EXPECT_EQ(reinterpret_cast<getattrfunc>(PyType_GetSlot(tp, Py_tp_getattr)),
empty_getattr_func);
}
TEST_F(TypeExtensionApiTest, FromSpecWithBasesInheritsGetAttrIfNotDefined) {
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("BaseType", Py_tp_getattro, &emptyBinaryFunc));
PyObjectPtr base_type(mainModuleGet("BaseType"));
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"SubclassedType", Py_nb_add, &emptyBinaryFunc, base_type));
PyObjectPtr subclassed_type(mainModuleGet("SubclassedType"));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(subclassed_type.get());
EXPECT_EQ(reinterpret_cast<getattrofunc>(PyType_GetSlot(tp, Py_tp_getattro)),
&emptyBinaryFunc);
EXPECT_EQ(PyType_GetSlot(tp, Py_tp_getattr), nullptr);
}
TEST_F(TypeExtensionApiTest, FromSpecWithBasesInheritsSetAttrIfNotDefined) {
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("BaseType", Py_tp_setattro, &emptySetattroFunc));
PyObjectPtr base_type(mainModuleGet("BaseType"));
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"SubclassedType", Py_nb_add, &emptyBinaryFunc, base_type));
PyObjectPtr subclassed_type(mainModuleGet("SubclassedType"));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(subclassed_type.get());
EXPECT_EQ(reinterpret_cast<setattrofunc>(PyType_GetSlot(tp, Py_tp_setattro)),
&emptySetattroFunc);
EXPECT_EQ(PyType_GetSlot(tp, Py_tp_setattr), nullptr);
}
TEST_F(TypeExtensionApiTest,
FromSpecWithBasesDoesNotInheritCompareAndHashIfDefined) {
hashfunc empty_hash_func = [](PyObject*) { return Py_hash_t{0}; };
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("BaseType", Py_tp_richcompare, &emptyCompareFunc));
PyObjectPtr base_type(mainModuleGet("BaseType"));
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"SubclassedType", Py_tp_hash, empty_hash_func, base_type));
PyObjectPtr subclassed_type(mainModuleGet("SubclassedType"));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(subclassed_type.get());
EXPECT_EQ(PyType_GetSlot(tp, Py_tp_richcompare), nullptr);
EXPECT_EQ(reinterpret_cast<hashfunc>(PyType_GetSlot(tp, Py_tp_hash)),
empty_hash_func);
}
TEST_F(TypeExtensionApiTest,
FromSpecWithBasesInheritsCompareAndHashIfNotDefined) {
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("BaseType", Py_tp_richcompare, &emptyCompareFunc));
PyObjectPtr base_type(mainModuleGet("BaseType"));
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"SubclassedType", Py_nb_add, &emptyBinaryFunc, base_type));
PyObjectPtr subclassed_type(mainModuleGet("SubclassedType"));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(subclassed_type.get());
EXPECT_EQ(
reinterpret_cast<richcmpfunc>(PyType_GetSlot(tp, Py_tp_richcompare)),
&emptyCompareFunc);
}
TEST_F(TypeExtensionApiTest,
FromSpecWithBasesInheritsFinalizeRegardlessOfFlag) {
static PyType_Slot base_slots[2];
base_slots[0] = {Py_tp_finalize,
reinterpret_cast<void*>(&emptyDestructorFunc)};
base_slots[1] = {0, nullptr};
static PyType_Spec base_spec;
base_spec = {
"__main__.BaseType",
0,
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_FINALIZE,
base_slots,
};
PyObjectPtr base_type(PyType_FromSpec(&base_spec));
ASSERT_NE(base_type, nullptr);
ASSERT_EQ(PyType_CheckExact(base_type), 1);
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"SubclassedType", Py_nb_add, &emptyBinaryFunc, base_type));
PyObjectPtr subclassed_type(mainModuleGet("SubclassedType"));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(subclassed_type.get());
EXPECT_EQ(PyType_GetSlot(tp, Py_tp_finalize),
reinterpret_cast<void*>(&emptyDestructorFunc));
}
TEST_F(TypeExtensionApiTest,
FromSpecWithBasesInheritsFinalizeWhenWhateverFlagSet) {
static PyType_Slot base_slots[2];
base_slots[0] = {Py_tp_finalize,
reinterpret_cast<void*>(&emptyDestructorFunc)};
base_slots[1] = {0, nullptr};
static PyType_Spec base_spec;
base_spec = {
"__main__.BaseType",
0,
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_FINALIZE,
base_slots,
};
PyObjectPtr base_type(PyType_FromSpec(&base_spec));
ASSERT_NE(base_type, nullptr);
ASSERT_EQ(PyType_CheckExact(base_type), 1);
static PyType_Slot slots[1];
slots[0] = {0, nullptr};
static PyType_Spec spec;
spec = {
"__main__.SubclassedType",
0,
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_FINALIZE,
slots,
};
PyObjectPtr bases(PyTuple_Pack(1, base_type.get()));
PyObjectPtr type(PyType_FromSpecWithBases(&spec, bases));
ASSERT_NE(type, nullptr);
ASSERT_EQ(PyType_CheckExact(type), 1);
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(type.get());
EXPECT_EQ(reinterpret_cast<destructor>(PyType_GetSlot(tp, Py_tp_finalize)),
&emptyDestructorFunc);
}
TEST_F(TypeExtensionApiTest,
FromSpecWithBasesDoesNotInheritFreeIfHaveGCUnsetInBase) {
freefunc empty_free_func = [](void*) { return; };
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("BaseType", Py_tp_free, empty_free_func));
PyObjectPtr base_type(mainModuleGet("BaseType"));
static PyType_Slot slots[1];
slots[0] = {0, nullptr};
static PyType_Spec spec;
spec = {
"__main__.SubclassedType",
0,
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
slots,
};
PyObjectPtr bases(PyTuple_Pack(1, base_type.get()));
PyObjectPtr type(PyType_FromSpecWithBases(&spec, bases));
ASSERT_NE(type, nullptr);
ASSERT_EQ(PyType_CheckExact(type), 1);
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(type.get());
EXPECT_NE(reinterpret_cast<freefunc>(PyType_GetSlot(tp, Py_tp_free)),
empty_free_func);
}
TEST_F(TypeExtensionApiTest, FromSpecWithBasesInheritsFreeIfBothHaveGCFlagSet) {
freefunc empty_free_func = PyObject_Free;
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("BaseType", Py_tp_free, empty_free_func));
PyObjectPtr base_type(mainModuleGet("BaseType"));
static PyType_Slot slots[1];
slots[0] = {0, nullptr};
static PyType_Spec spec;
spec = {
"__main__.SubclassedType",
0,
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
slots,
};
PyObjectPtr bases(PyTuple_Pack(1, base_type.get()));
PyObjectPtr type(PyType_FromSpecWithBases(&spec, bases));
ASSERT_NE(type, nullptr);
ASSERT_EQ(PyType_CheckExact(type), 1);
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(type.get());
EXPECT_EQ(PyType_GetSlot(tp, Py_tp_free),
reinterpret_cast<void*>(PyObject_GC_Del));
}
TEST_F(TypeExtensionApiTest, FromSpecWithBasesInheritsIfGcFlagIsPresentOnBoth) {
freefunc empty_free_func = [](void*) { return; };
static PyType_Slot base_slots[2];
base_slots[0] = {Py_tp_free, reinterpret_cast<void*>(empty_free_func)};
base_slots[1] = {0, nullptr};
static PyType_Spec base_spec;
base_spec = {
"__main__.BaseType",
0,
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
base_slots,
};
PyObjectPtr base_type(PyType_FromSpec(&base_spec));
ASSERT_NE(base_type, nullptr);
ASSERT_EQ(PyType_CheckExact(base_type), 1);
static PyType_Slot slots[1];
slots[0] = {0, nullptr};
static PyType_Spec spec;
spec = {
"__main__.SubclassedType",
0,
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
slots,
};
PyObjectPtr bases(PyTuple_Pack(1, base_type.get()));
PyObjectPtr type(PyType_FromSpecWithBases(&spec, bases));
ASSERT_NE(type, nullptr);
ASSERT_EQ(PyType_CheckExact(type), 1);
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(type.get());
EXPECT_EQ(reinterpret_cast<freefunc>(PyType_GetSlot(tp, Py_tp_free)),
empty_free_func);
}
TEST_F(TypeExtensionApiTest, FromSpecWithBasesPopulatesTpDeallocIfNotDefined) {
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"SubclassedType", Py_nb_add, &emptyBinaryFunc, /*base=*/nullptr));
PyObjectPtr subclassed_type(mainModuleGet("SubclassedType"));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(subclassed_type.get());
auto dealloc_func =
reinterpret_cast<destructor>(PyType_GetSlot(tp, Py_tp_dealloc));
EXPECT_NE(dealloc_func, nullptr);
}
TEST_F(TypeExtensionApiTest, FromSpecWithBasesInheritsObjectReprIfNotDefined) {
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"SubclassedType", Py_nb_add, &emptyBinaryFunc, /*base=*/nullptr));
PyObjectPtr subclassed_type(mainModuleGet("SubclassedType"));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(subclassed_type.get());
auto repr_func = reinterpret_cast<reprfunc>(PyType_GetSlot(tp, Py_tp_repr));
ASSERT_NE(repr_func, nullptr);
PyObjectPtr instance(_PyObject_CallNoArg(subclassed_type));
PyObjectPtr slot_result(repr_func(instance));
ASSERT_EQ(PyErr_Occurred(), nullptr);
PyObjectPtr repr_result(PyObject_Repr(instance));
ASSERT_EQ(PyErr_Occurred(), nullptr);
EXPECT_EQ(PyUnicode_Compare(slot_result, repr_result), 0);
}
TEST_F(TypeExtensionApiTest, FromSpecWithBasesInheritsObjectStrIfNotDefined) {
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"SubclassedType", Py_nb_add, &emptyBinaryFunc, /*base=*/nullptr));
PyObjectPtr subclassed_type(mainModuleGet("SubclassedType"));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(subclassed_type.get());
auto str_func = reinterpret_cast<reprfunc>(PyType_GetSlot(tp, Py_tp_str));
ASSERT_NE(str_func, nullptr);
PyObjectPtr instance(_PyObject_CallNoArg(subclassed_type));
PyObjectPtr slot_result(str_func(instance));
ASSERT_EQ(PyErr_Occurred(), nullptr);
PyObjectPtr str_result(PyObject_Str(instance));
ASSERT_EQ(PyErr_Occurred(), nullptr);
EXPECT_EQ(PyUnicode_Compare(slot_result, str_result), 0);
}
TEST_F(TypeExtensionApiTest, FromSpecWithBasesPopulatesTpInitIfNotDefined) {
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"SubclassedType", Py_nb_add, &emptyBinaryFunc, /*base=*/nullptr));
PyObjectPtr subclassed_type(mainModuleGet("SubclassedType"));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(subclassed_type.get());
auto init_proc = reinterpret_cast<initproc>(PyType_GetSlot(tp, Py_tp_init));
ASSERT_NE(init_proc, nullptr);
}
TEST_F(TypeExtensionApiTest,
FromSpecWithBasesInheritsPyTypeGenericAllocIfNotDefined) {
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"SubclassedType", Py_nb_add, &emptyBinaryFunc, /*base=*/nullptr));
PyObjectPtr subclassed_type(mainModuleGet("SubclassedType"));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(subclassed_type.get());
auto alloc_func =
reinterpret_cast<allocfunc>(PyType_GetSlot(tp, Py_tp_alloc));
EXPECT_EQ(alloc_func, &PyType_GenericAlloc);
}
TEST_F(TypeExtensionApiTest, FromSpecWithBasesPopulatesTpNewIfNotDefined) {
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"SubclassedType", Py_nb_add, &emptyBinaryFunc, /*base=*/nullptr));
PyObjectPtr subclassed_type(mainModuleGet("SubclassedType"));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(subclassed_type.get());
auto new_func = reinterpret_cast<newfunc>(PyType_GetSlot(tp, Py_tp_init));
EXPECT_NE(new_func, nullptr);
}
TEST_F(TypeExtensionApiTest,
FromSpecWithBasesWithoutGcFlagInheritsObjectDelIfNotDefined) {
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"SubclassedType", Py_nb_add, &emptyBinaryFunc, /*base=*/nullptr));
PyObjectPtr subclassed_type(mainModuleGet("SubclassedType"));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(subclassed_type.get());
auto free_func = reinterpret_cast<freefunc>(PyType_GetSlot(tp, Py_tp_free));
EXPECT_EQ(free_func, &PyObject_Del);
}
TEST_F(TypeExtensionApiTest,
FromSpecWithBasesWithGcFlagInheritsObjectGcDelIfNotDefined) {
static PyType_Slot slots[1];
slots[0] = {0, nullptr};
static PyType_Spec spec;
spec = {
"__main__.SubclassedType",
0,
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
slots,
};
PyObjectPtr type(PyType_FromSpecWithBases(&spec, /*bases=*/nullptr));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(type.get());
auto free_func = reinterpret_cast<freefunc>(PyType_GetSlot(tp, Py_tp_free));
EXPECT_EQ(free_func, &PyObject_GC_Del);
}
TEST_F(TypeExtensionApiTest, MethodIsInheirtedFromClassFromWinningParent) {
// class C:
// def __int__(self):
// return 11
unaryfunc c_int_func = [](PyObject*) { return PyLong_FromLong(11); };
static PyType_Slot c_slots[2];
c_slots[0] = {Py_nb_int, reinterpret_cast<void*>(c_int_func)};
c_slots[1] = {0, nullptr};
static PyType_Spec c_spec;
c_spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, c_slots,
};
PyObjectPtr c_type(PyType_FromSpec(&c_spec));
ASSERT_NE(c_type, nullptr);
ASSERT_EQ(PyType_CheckExact(c_type), 1);
// class D(C):
// def __int__(self):
// return 22
unaryfunc d_int_func = [](PyObject*) { return PyLong_FromLong(22); };
static PyType_Slot d_slots[2];
d_slots[0] = {Py_nb_int, reinterpret_cast<void*>(d_int_func)};
d_slots[1] = {0, nullptr};
static PyType_Spec d_spec;
d_spec = {
"__main__.D", 0, 0, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, d_slots,
};
PyObjectPtr d_bases(PyTuple_Pack(1, c_type.get()));
PyObjectPtr d_type(PyType_FromSpecWithBases(&d_spec, d_bases));
ASSERT_NE(d_type, nullptr);
ASSERT_EQ(PyType_CheckExact(d_type), 1);
// class B(C): pass
static PyType_Slot b_slots[1];
b_slots[0] = {0, nullptr};
static PyType_Spec b_spec;
b_spec = {
"__main__.B", 0, 0, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, d_slots,
};
PyObjectPtr b_bases(PyTuple_Pack(1, c_type.get()));
PyObjectPtr b_type(PyType_FromSpecWithBases(&b_spec, b_bases));
ASSERT_NE(b_type, nullptr);
ASSERT_EQ(PyType_CheckExact(b_type), 1);
// class A(B, C): pass
static PyType_Slot a_slots[1];
a_slots[0] = {0, nullptr};
static PyType_Spec a_spec;
a_spec = {
"__main__.A", 0, 0, Py_TPFLAGS_DEFAULT, a_slots,
};
PyObjectPtr a_bases(PyTuple_Pack(2, b_type.get(), d_type.get()));
PyObjectPtr a_type(PyType_FromSpecWithBases(&a_spec, a_bases));
ASSERT_NE(a_type, nullptr);
ASSERT_EQ(PyType_CheckExact(a_type), 1);
// MRO is (A, B, D, C, object)
ASSERT_EQ(moduleSet("__main__", "A", a_type), 0);
PyRun_SimpleString(R"(
a_mro = A.__mro__
)");
PyObjectPtr a_mro(mainModuleGet("a_mro"));
ASSERT_EQ(PyTuple_Check(a_mro), 1);
ASSERT_EQ(PyTuple_GetItem(a_mro, 0), a_type);
ASSERT_EQ(PyTuple_GetItem(a_mro, 1), b_type);
ASSERT_EQ(PyTuple_GetItem(a_mro, 2), d_type);
ASSERT_EQ(PyTuple_GetItem(a_mro, 3), c_type);
// Even though B inherited Py_tp_int from C, A should inherit it until
// the first concrete definition, which is in D.
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(a_type.get());
void* int_slot = PyType_GetSlot(tp, Py_nb_int);
ASSERT_NE(int_slot, nullptr);
EXPECT_NE(reinterpret_cast<unaryfunc>(int_slot), c_int_func);
EXPECT_EQ(reinterpret_cast<unaryfunc>(int_slot), d_int_func);
}
TEST_F(TypeExtensionApiTest,
FromSpecWithBasesInheritsGcFlagAndTraverseClearSlots) {
traverseproc empty_traverse_func = [](PyObject*, visitproc, void*) {
return 0;
};
inquiry empty_clear_func = [](PyObject*) { return 0; };
static PyType_Slot base_slots[3];
base_slots[0] = {Py_tp_traverse,
reinterpret_cast<void*>(empty_traverse_func)};
base_slots[1] = {Py_tp_clear, reinterpret_cast<void*>(empty_clear_func)};
base_slots[2] = {0, nullptr};
static PyType_Spec base_spec;
base_spec = {
"__main__.BaseType",
0,
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
base_slots,
};
PyObjectPtr base_type(PyType_FromSpec(&base_spec));
ASSERT_NE(base_type, nullptr);
ASSERT_EQ(PyType_CheckExact(base_type), 1);
static PyType_Slot slots[1];
slots[0] = {0, nullptr};
static PyType_Spec spec;
spec = {
"__main__.SubclassedType", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr bases(PyTuple_Pack(1, base_type.get()));
PyObjectPtr type(PyType_FromSpecWithBases(&spec, bases));
ASSERT_NE(type, nullptr);
ASSERT_EQ(PyType_CheckExact(type), 1);
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(base_type.get());
EXPECT_NE(PyType_GetFlags(tp) & Py_TPFLAGS_HAVE_GC, 0UL);
EXPECT_EQ(reinterpret_cast<traverseproc>(PyType_GetSlot(tp, Py_tp_traverse)),
empty_traverse_func);
EXPECT_EQ(reinterpret_cast<inquiry>(PyType_GetSlot(tp, Py_tp_clear)),
empty_clear_func);
}
TEST_F(TypeExtensionApiTest, FromSpecWithBasesInheritsNew) {
newfunc empty_new_func = [](PyTypeObject*, PyObject*, PyObject*) {
return Py_None;
};
ASSERT_NO_FATAL_FAILURE(
createTypeWithSlot("BaseType", Py_tp_new, empty_new_func));
PyObjectPtr base_type(mainModuleGet("BaseType"));
ASSERT_NO_FATAL_FAILURE(createTypeWithSlotAndBase(
"SubclassedType", Py_nb_add, &emptyBinaryFunc, base_type));
PyObjectPtr subclassed_type(mainModuleGet("SubclassedType"));
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(subclassed_type.get());
EXPECT_EQ(reinterpret_cast<newfunc>(PyType_GetSlot(tp, Py_tp_new)),
empty_new_func);
}
TEST_F(TypeExtensionApiTest,
FromSpecWithMixedBasesSetsExtensionAsDominantBase) {
struct ExtensionObject {
PyObject_HEAD
int native_data;
};
static PyType_Slot extension_slots[2];
extension_slots[0] = {0, nullptr};
unsigned int flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
static PyType_Spec extension_spec = {
"__main__.ExtensionBaseClass",
sizeof(ExtensionObject),
0,
flags,
extension_slots,
};
PyObjectPtr extension_basetype(PyType_FromSpec(&extension_spec));
ASSERT_EQ(PyType_CheckExact(extension_basetype), 1);
ASSERT_EQ(moduleSet("__main__", "ExtensionBaseClass", extension_basetype), 0);
PyRun_SimpleString(R"(
class SimpleManagedBaseClass: pass
class Base(ExtensionBaseClass): pass
class SubClass(SimpleManagedBaseClass, Base): pass
)");
PyObjectPtr base(mainModuleGet("Base"));
ASSERT_NE(base, nullptr);
PyObjectPtr subclass(mainModuleGet("SubClass"));
ASSERT_NE(subclass, nullptr);
PyObjectPtr subclass_base(PyObject_GetAttrString(subclass, "__base__"));
EXPECT_EQ(subclass_base.get(), base.get());
}
TEST_F(TypeExtensionApiTest, FromSpecWithoutBasicSizeInheritsDefaultBasicSize) {
static PyType_Slot slots[1];
slots[0] = {0, nullptr};
static PyType_Spec spec;
spec = {
"__main__.Foo", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
ASSERT_EQ(PyType_CheckExact(type), 1);
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(type.get());
EXPECT_EQ(_PyObject_SIZE(tp), static_cast<Py_ssize_t>(sizeof(PyObject)));
}
TEST_F(TypeExtensionApiTest, FromSpecWithoutAllocInheritsDefaultAlloc) {
static PyType_Slot slots[1];
slots[0] = {0, nullptr};
static PyType_Spec spec;
spec = {
"__main__.Foo", sizeof(PyObject), 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
ASSERT_EQ(PyType_CheckExact(type), 1);
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(type.get());
ASSERT_EQ(reinterpret_cast<allocfunc>(PyType_GetSlot(tp, Py_tp_alloc)),
&PyType_GenericAlloc);
}
TEST_F(TypeExtensionApiTest, FromSpecWithoutNewInheritsDefaultNew) {
static PyType_Slot slots[1];
slots[0] = {0, nullptr};
static PyType_Spec spec;
spec = {
"__main__.Foo", sizeof(PyObject), 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
ASSERT_EQ(PyType_CheckExact(type), 1);
ASSERT_EQ(moduleSet("__main__", "Foo", type), 0);
// In Pyro tp_new = PyType_GenericNew, in CPython tp_new = object_new
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(type.get());
ASSERT_NE(PyType_GetSlot(tp, Py_tp_new), nullptr);
}
TEST_F(TypeExtensionApiTest, FromSpecWithoutDeallocInheritsDefaultDealloc) {
struct FooObject {
PyObject_HEAD
int native_data;
};
static PyType_Slot slots[1];
slots[0] = {0, nullptr};
static PyType_Spec spec;
spec = {
"__main__.Foo", sizeof(FooObject), 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
ASSERT_EQ(PyType_CheckExact(type), 1);
// type inherited subclassDealloc
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(type.get());
ASSERT_NE(PyType_GetSlot(tp, Py_tp_dealloc), nullptr);
Py_ssize_t type_refcnt = Py_REFCNT(tp);
// Create an instance
FooObject* instance = PyObject_New(FooObject, tp);
ASSERT_GE(Py_REFCNT(instance), 1); // CPython
ASSERT_LE(Py_REFCNT(instance), 2); // Pyro
ASSERT_EQ(Py_REFCNT(tp), type_refcnt + 1);
// Trigger a tp_dealloc
Py_DECREF(instance);
collectGarbage();
ASSERT_EQ(Py_REFCNT(tp), type_refcnt);
}
TEST_F(TypeExtensionApiTest, DefaultDeallocCallsDelAndFinalize) {
struct FooObject {
PyObject_HEAD
};
destructor del_func = [](PyObject*) {
moduleSet("__main__", "called_del", Py_True);
};
static PyType_Slot slots[2];
slots[0] = {Py_tp_del, reinterpret_cast<void*>(del_func)};
slots[1] = {0, nullptr};
static PyType_Spec spec;
spec = {
"__main__.Foo", sizeof(FooObject), 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
ASSERT_EQ(PyType_CheckExact(type), 1);
// type inherited subclassDealloc
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(type.get());
ASSERT_NE(PyType_GetSlot(tp, Py_tp_dealloc), nullptr);
Py_ssize_t type_refcnt = Py_REFCNT(tp);
// Create an instance
FooObject* instance = PyObject_New(FooObject, tp);
ASSERT_GE(Py_REFCNT(instance), 1); // CPython
ASSERT_LE(Py_REFCNT(instance), 2); // Pyro
ASSERT_EQ(Py_REFCNT(tp), type_refcnt + 1);
// Trigger a tp_dealloc
Py_DECREF(instance);
collectGarbage();
ASSERT_EQ(Py_REFCNT(tp), type_refcnt);
PyObjectPtr called_del(testing::mainModuleGet("called_del"));
EXPECT_EQ(called_del, Py_True);
}
TEST_F(TypeExtensionApiTest, FromSpecWithBasesSubclassInheritsParentDealloc) {
struct FooObject {
PyObject_HEAD
};
struct FooSubclassObject {
FooObject base;
};
destructor dealloc_func = [](PyObject* self) {
PyTypeObject* tp = Py_TYPE(self);
PyObject_Del(self);
Py_DECREF(tp);
};
static PyType_Slot base_slots[2];
base_slots[0] = {Py_tp_dealloc, reinterpret_cast<void*>(dealloc_func)};
base_slots[1] = {0, nullptr};
static PyType_Spec base_spec;
base_spec = {
"__main__.BaseType",
sizeof(FooObject),
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
base_slots,
};
PyObjectPtr base_type(PyType_FromSpec(&base_spec));
ASSERT_NE(base_type, nullptr);
ASSERT_EQ(PyType_CheckExact(base_type), 1);
static PyType_Slot slots[1];
slots[0] = {0, nullptr};
static PyType_Spec spec;
spec = {
"__main__.SubclassedType",
sizeof(FooSubclassObject),
0,
Py_TPFLAGS_DEFAULT,
slots,
};
PyObjectPtr bases(PyTuple_Pack(1, base_type.get()));
PyObjectPtr type(PyType_FromSpecWithBases(&spec, bases));
ASSERT_NE(type, nullptr);
ASSERT_EQ(PyType_CheckExact(type), 1);
// type inherited subclassDealloc
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(type.get());
ASSERT_NE(PyType_GetSlot(tp, Py_tp_dealloc), nullptr);
Py_ssize_t type_refcnt = Py_REFCNT(tp);
// Create an instance
FooObject* instance = PyObject_New(FooObject, tp);
ASSERT_GE(Py_REFCNT(instance), 1); // CPython
ASSERT_LE(Py_REFCNT(instance), 2); // Pyro
ASSERT_EQ(Py_REFCNT(tp), type_refcnt + 1);
// Trigger a tp_dealloc
Py_DECREF(instance);
collectGarbage();
ASSERT_EQ(Py_REFCNT(tp), type_refcnt);
}
TEST_F(TypeExtensionApiTest, FromSpecWithBasesSubclassInheritsDefaultDealloc) {
struct FooObject {
PyObject_HEAD
int native_data;
};
struct FooSubclassObject {
FooObject base;
};
static PyType_Slot base_slots[1];
base_slots[0] = {0, nullptr};
static PyType_Spec base_spec;
base_spec = {
"__main__.BaseType",
sizeof(FooObject),
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
base_slots,
};
PyObjectPtr base_type(PyType_FromSpec(&base_spec));
ASSERT_NE(base_type, nullptr);
ASSERT_EQ(PyType_CheckExact(base_type), 1);
static PyType_Slot slots[1];
slots[0] = {0, nullptr};
static PyType_Spec spec;
spec = {
"__main__.SubclassedType",
sizeof(FooSubclassObject),
0,
Py_TPFLAGS_DEFAULT,
slots,
};
PyObjectPtr bases(PyTuple_Pack(1, base_type.get()));
PyObjectPtr type(PyType_FromSpecWithBases(&spec, bases));
ASSERT_NE(type, nullptr);
ASSERT_EQ(PyType_CheckExact(type), 1);
// type inherited subclassDealloc
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(type.get());
ASSERT_NE(PyType_GetSlot(tp, Py_tp_dealloc), nullptr);
Py_ssize_t type_refcnt = Py_REFCNT(tp);
// Create an instance
FooObject* instance = PyObject_New(FooObject, tp);
ASSERT_GE(Py_REFCNT(instance), 1); // CPython
ASSERT_LE(Py_REFCNT(instance), 2); // Pyro
ASSERT_EQ(Py_REFCNT(tp), type_refcnt + 1);
// Trigger a tp_dealloc
Py_DECREF(instance);
collectGarbage();
ASSERT_EQ(Py_REFCNT(tp), type_refcnt);
}
TEST_F(TypeExtensionApiTest, TypeLookupSkipsInstanceDictionary) {
PyRun_SimpleString(R"(
class Foo:
bar = 2
foo = Foo()
foo.bar = 1
)");
PyObjectPtr foo(mainModuleGet("foo"));
PyObjectPtr foo_type(PyObject_Type(foo));
PyObjectPtr bar_str(PyUnicode_FromString("bar"));
PyObject* res =
_PyType_Lookup(reinterpret_cast<PyTypeObject*>(foo_type.get()), bar_str);
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_NE(res, nullptr);
EXPECT_TRUE(isLongEqualsLong(res, 2));
}
TEST_F(TypeExtensionApiTest, TypeLookupWithoutMatchDoesNotRaise) {
PyRun_SimpleString(R"(
class Foo: pass
)");
PyObjectPtr foo_type(mainModuleGet("Foo"));
PyObjectPtr bar_str(PyUnicode_FromString("bar"));
PyObjectPtr res(
_PyType_Lookup(reinterpret_cast<PyTypeObject*>(foo_type.get()), bar_str));
ASSERT_EQ(PyErr_Occurred(), nullptr);
EXPECT_EQ(res, nullptr);
}
TEST_F(TypeExtensionApiTest, TypeLookupWithNonStrDoesNotRaise) {
PyRun_SimpleString(R"(
class Foo: pass
)");
PyObjectPtr foo_type(mainModuleGet("Foo"));
PyObjectPtr res(
_PyType_Lookup(reinterpret_cast<PyTypeObject*>(foo_type.get()), Py_None));
ASSERT_EQ(PyErr_Occurred(), nullptr);
EXPECT_EQ(res, nullptr);
}
TEST_F(TypeExtensionApiTest, FromSpecWithGCFlagCallsDealloc) {
destructor dealloc_func = [](PyObject* self) {
moduleSet("__main__", "called_del", Py_True);
PyTypeObject* type = Py_TYPE(self);
PyObject_GC_UnTrack(self);
PyObject_GC_Del(self);
Py_DECREF(type);
};
static PyType_Slot slots[2];
slots[0] = {Py_tp_dealloc, reinterpret_cast<void*>(dealloc_func)};
slots[1] = {0, nullptr};
static PyType_Spec spec;
spec = {
"__main__.Foo",
sizeof(PyObject),
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
ASSERT_EQ(PyType_CheckExact(type), 1);
PyTypeObject* tp = reinterpret_cast<PyTypeObject*>(type.get());
ASSERT_NE(PyType_GetSlot(tp, Py_tp_dealloc), nullptr);
Py_ssize_t type_refcnt = Py_REFCNT(tp);
// Create an instance
PyObject* instance = PyObject_GC_New(PyObject, tp);
PyObject_GC_Track(instance);
ASSERT_GE(Py_REFCNT(instance), 1); // CPython
ASSERT_LE(Py_REFCNT(instance), 2); // Pyro
ASSERT_EQ(Py_REFCNT(tp), type_refcnt + 1);
// Trigger a tp_dealloc
Py_DECREF(instance);
collectGarbage();
ASSERT_EQ(Py_REFCNT(tp), type_refcnt);
PyObjectPtr called_del(testing::mainModuleGet("called_del"));
EXPECT_EQ(called_del, Py_True);
}
TEST_F(TypeExtensionApiTest, ManagedTypeInheritsTpFlagsFromCType) {
ASSERT_NO_FATAL_FAILURE(createBarTypeWithMembers());
ASSERT_EQ(PyRun_SimpleString(R"(
class Baz(Bar): pass
)"),
0);
PyObjectPtr baz_type(mainModuleGet("Baz"));
EXPECT_TRUE(PyType_GetFlags(reinterpret_cast<PyTypeObject*>(baz_type.get())) &
Py_TPFLAGS_HEAPTYPE);
}
TEST_F(TypeExtensionApiTest, ManagedTypeInheritsFromCType) {
ASSERT_NO_FATAL_FAILURE(createBarTypeWithMembers());
ASSERT_EQ(PyRun_SimpleString(R"(
r1 = Bar().t_bool
class Baz(Bar): pass
r2 = Baz().t_bool
r3 = Baz().t_object
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyBool_Check(r1), 1);
EXPECT_EQ(r1, Py_True);
PyObjectPtr r2(mainModuleGet("r2"));
ASSERT_EQ(PyBool_Check(r2), 1);
EXPECT_EQ(r2, Py_True);
PyObjectPtr r3(mainModuleGet("r3"));
ASSERT_EQ(PyList_Check(r3), 1);
EXPECT_EQ(PyList_Size(r3), 0);
}
TEST_F(TypeExtensionApiTest, ManagedTypeWithLayoutInheritsFromCType) {
ASSERT_NO_FATAL_FAILURE(createBarTypeWithMembers());
ASSERT_EQ(PyRun_SimpleString(R"(
class Baz(Bar):
def __init__(self):
self.value = 123
baz = Baz()
r1 = baz.t_bool
r2 = baz.value
r3 = baz.t_object
)"),
0);
PyObjectPtr baz(mainModuleGet("baz"));
ASSERT_NE(baz, nullptr);
PyObjectPtr r1(mainModuleGet("r1"));
ASSERT_EQ(PyBool_Check(r1), 1);
EXPECT_EQ(r1, Py_False);
PyObjectPtr r2(mainModuleGet("r2"));
EXPECT_TRUE(isLongEqualsLong(r2, 123));
PyObjectPtr r3(mainModuleGet("r3"));
EXPECT_FALSE(PyList_Check(r3));
}
TEST_F(TypeExtensionApiTest, CTypeWithSlotsBuiltinBaseTpNewCreatesNewInstance) {
static PyType_Slot base_slots[1];
base_slots[0] = {0, nullptr};
static PyType_Spec base_spec;
base_spec = {
"__main__.BaseType", 0, 0, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
base_slots,
};
PyObjectPtr base_bases(PyTuple_Pack(1, PyExc_Exception));
PyObjectPtr base_type(PyType_FromSpecWithBases(&base_spec, base_bases));
ASSERT_NE(base_type, nullptr);
ASSERT_EQ(PyType_CheckExact(base_type), 1);
ASSERT_EQ(moduleSet("__main__", "BaseType", base_type), 0);
newfunc new_func = [](PyTypeObject* t, PyObject* a, PyObject* k) {
PyObjectPtr base(mainModuleGet("BaseType"));
newfunc base_tp_new = reinterpret_cast<newfunc>(
PyType_GetSlot(base.asTypeObject(), Py_tp_new));
return base_tp_new(t, a, k);
};
static PyType_Slot slots[2];
slots[0] = {Py_tp_new, reinterpret_cast<void*>(new_func)};
slots[1] = {0, nullptr};
static PyType_Spec spec;
spec = {
"__main__.SubclassedType", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr bases(PyTuple_Pack(1, base_type.get()));
PyObjectPtr type(PyType_FromSpecWithBases(&spec, bases));
ASSERT_NE(type, nullptr);
ASSERT_EQ(PyType_CheckExact(type), 1);
PyObjectPtr instance(_PyObject_CallNoArg(type));
ASSERT_NE(instance, nullptr);
ASSERT_EQ(Py_TYPE(instance.get()), type.asTypeObject());
}
TEST_F(TypeExtensionApiTest,
CTypeWithSlotsBuiltinBaseTpDeallocFreesInstancePyro) {
destructor dealloc_func = [](PyObject* o) {
PyTypeObject* tp = Py_TYPE(o);
// Call the base type Py_tp_dealloc slot
destructor base_dealloc = reinterpret_cast<destructor>(PyType_GetSlot(
reinterpret_cast<PyTypeObject*>(PyExc_Exception), Py_tp_dealloc));
(*base_dealloc)(o);
Py_DECREF(tp);
};
static PyType_Slot slots[2];
slots[0] = {Py_tp_dealloc, reinterpret_cast<void*>(dealloc_func)};
slots[1] = {0, nullptr};
static PyType_Spec spec;
spec = {
"__main__.SubclassedType", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr bases(PyTuple_Pack(1, PyExc_Exception));
PyObjectPtr type(PyType_FromSpecWithBases(&spec, bases));
ASSERT_NE(type, nullptr);
ASSERT_EQ(PyType_CheckExact(type), 1);
Py_ssize_t type_refcnt = Py_REFCNT(type);
PyObject* instance = _PyObject_CallNoArg(type);
ASSERT_NE(instance, nullptr);
ASSERT_EQ(Py_TYPE(instance), type.asTypeObject());
ASSERT_EQ(Py_REFCNT(type), type_refcnt + 1);
Py_DECREF(instance);
collectGarbage();
ASSERT_EQ(Py_REFCNT(type), type_refcnt);
}
TEST_F(TypeExtensionApiTest, CTypeInheritsFromManagedType) {
ASSERT_EQ(PyRun_SimpleString(R"(
class Foo:
def foo(self):
return 123
)"),
0);
PyObjectPtr foo_type(mainModuleGet("Foo"));
struct FooObject {
PyObject_HEAD
PyObject* dict;
int t_int;
};
static PyMemberDef members[2];
members[0] = {"t_int", T_INT, offsetof(FooObject, t_int)};
members[1] = {nullptr};
initproc init_func = [](PyObject* self, PyObject*, PyObject*) {
reinterpret_cast<FooObject*>(self)->t_int = 321;
return 0;
};
destructor dealloc_func = [](PyObject* self) {
PyTypeObject* type = Py_TYPE(self);
PyObject_GC_UnTrack(self);
PyObject_GC_Del(self);
Py_DECREF(type);
};
static PyType_Slot slots[4];
slots[0] = {Py_tp_init, reinterpret_cast<void*>(init_func)};
slots[1] = {Py_tp_members, reinterpret_cast<void*>(members)};
slots[2] = {Py_tp_dealloc, reinterpret_cast<void*>(dealloc_func)},
slots[3] = {0, nullptr};
static PyType_Spec spec;
spec = {
"__main__.FooSubclass",
sizeof(FooObject),
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
slots,
};
PyObjectPtr bases(PyTuple_Pack(1, foo_type.get()));
PyObjectPtr type(PyType_FromSpecWithBases(&spec, bases));
ASSERT_NE(type, nullptr);
ASSERT_EQ(PyType_CheckExact(type), 1);
ASSERT_EQ(moduleSet("__main__", "FooSubclass", type), 0);
ASSERT_EQ(PyRun_SimpleString(R"(
r1 = FooSubclass().foo()
r2 = FooSubclass().t_int
)"),
0);
PyObjectPtr r1(mainModuleGet("r1"));
EXPECT_TRUE(isLongEqualsLong(r1, 123));
PyObjectPtr r2(mainModuleGet("r2"));
EXPECT_TRUE(isLongEqualsLong(r2, 321));
}
TEST_F(TypeExtensionApiTest, MethodsMethFastWithKeywordsCallNoArg) {
_PyCFunctionFastWithKeywords meth = [](PyObject* self, PyObject* const*,
Py_ssize_t nargs, PyObject* kwnames) {
EXPECT_EQ(kwnames, nullptr);
PyObjectPtr nargs_obj(PyLong_FromSsize_t(nargs));
return PyTuple_Pack(2, self, nargs_obj.get());
};
static PyMethodDef methods[] = {
{"fastcall", reinterpret_cast<PyCFunction>(reinterpret_cast<void*>(meth)),
METH_FASTCALL | METH_KEYWORDS},
{nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
self = C()
result = self.fastcall()
)");
PyObjectPtr self(testing::mainModuleGet("self"));
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyTuple_CheckExact(result), 1);
ASSERT_EQ(PyTuple_Size(result), 2);
EXPECT_EQ(PyTuple_GetItem(result, 0), self);
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 1), 0));
}
TEST_F(TypeExtensionApiTest, MethodsMethFastWithKeywordsCallPosCall) {
_PyCFunctionFastWithKeywords meth = [](PyObject* self, PyObject* const* args,
Py_ssize_t nargs, PyObject* kwnames) {
EXPECT_EQ(kwnames, nullptr);
PyObjectPtr nargs_obj(PyLong_FromSsize_t(nargs));
return PyTuple_Pack(3, self, args[0], nargs_obj.get());
};
static PyMethodDef methods[] = {
{"fastcall", reinterpret_cast<PyCFunction>(reinterpret_cast<void*>(meth)),
METH_FASTCALL | METH_KEYWORDS},
{nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
self = C()
result = self.fastcall(1234)
)");
PyObjectPtr self(testing::mainModuleGet("self"));
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyTuple_CheckExact(result), 1);
ASSERT_EQ(PyTuple_Size(result), 3);
EXPECT_EQ(PyTuple_GetItem(result, 0), self);
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 1), 1234));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 2), 1));
}
TEST_F(TypeExtensionApiTest, MethodsMethFastCallWithKeywordsPosCallMultiArgs) {
_PyCFunctionFastWithKeywords meth = [](PyObject* self, PyObject* const* args,
Py_ssize_t nargs, PyObject* kwnames) {
EXPECT_EQ(kwnames, nullptr);
PyObjectPtr nargs_obj(PyLong_FromSsize_t(nargs));
return PyTuple_Pack(4, self, args[0], args[1], nargs_obj.get());
};
static PyMethodDef methods[] = {
{"fastcall", reinterpret_cast<PyCFunction>(reinterpret_cast<void*>(meth)),
METH_FASTCALL | METH_KEYWORDS},
{nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
self = C()
result = self.fastcall(1234, 5678)
)");
PyObjectPtr self(testing::mainModuleGet("self"));
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyTuple_CheckExact(result), 1);
ASSERT_EQ(PyTuple_Size(result), 4);
EXPECT_EQ(PyTuple_GetItem(result, 0), self);
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 1), 1234));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 2), 5678));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 3), 2));
}
TEST_F(TypeExtensionApiTest, MethodsMethFastCallWithKeywordsKwCall) {
_PyCFunctionFastWithKeywords meth = [](PyObject* self, PyObject* const* args,
Py_ssize_t nargs, PyObject* kwnames) {
PyObjectPtr nargs_obj(PyLong_FromSsize_t(nargs));
return PyTuple_Pack(5, self, args[0], args[1], nargs_obj.get(), kwnames);
};
static PyMethodDef methods[] = {
{"fastcall", reinterpret_cast<PyCFunction>(reinterpret_cast<void*>(meth)),
METH_FASTCALL | METH_KEYWORDS},
{nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
self = C()
result = self.fastcall(1234, kwarg=5678)
)");
PyObjectPtr self(testing::mainModuleGet("self"));
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyTuple_CheckExact(result), 1);
ASSERT_EQ(PyTuple_Size(result), 5);
EXPECT_EQ(PyTuple_GetItem(result, 0), self);
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 1), 1234));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 2), 5678));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 3), 1));
PyObject* kwnames = PyTuple_GetItem(result, 4);
ASSERT_EQ(PyTuple_CheckExact(kwnames), 1);
ASSERT_EQ(PyTuple_Size(kwnames), 1);
EXPECT_TRUE(isUnicodeEqualsCStr(PyTuple_GetItem(kwnames, 0), "kwarg"));
}
TEST_F(TypeExtensionApiTest, MethodsMethFastCallWithKeywordsKwCallMultiArg) {
_PyCFunctionFastWithKeywords meth = [](PyObject* self, PyObject* const* args,
Py_ssize_t nargs, PyObject* kwnames) {
PyObjectPtr nargs_obj(PyLong_FromSsize_t(nargs));
return PyTuple_Pack(7, self, args[0], args[1], args[2], args[3],
nargs_obj.get(), kwnames);
};
static PyMethodDef methods[] = {
{"fastcall", reinterpret_cast<PyCFunction>(reinterpret_cast<void*>(meth)),
METH_FASTCALL | METH_KEYWORDS},
{nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
self = C()
result = self.fastcall(1234, 99, kwarg=5678, kwdos=22)
)");
PyObjectPtr self(testing::mainModuleGet("self"));
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyTuple_CheckExact(result), 1);
ASSERT_EQ(PyTuple_Size(result), 7);
EXPECT_EQ(PyTuple_GetItem(result, 0), self);
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 1), 1234));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 2), 99));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 3), 5678));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 4), 22));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 5), 2));
PyObject* kwnames = PyTuple_GetItem(result, 6);
ASSERT_EQ(PyTuple_CheckExact(kwnames), 1);
ASSERT_EQ(PyTuple_Size(kwnames), 2);
EXPECT_TRUE(isUnicodeEqualsCStr(PyTuple_GetItem(kwnames, 0), "kwarg"));
EXPECT_TRUE(isUnicodeEqualsCStr(PyTuple_GetItem(kwnames, 1), "kwdos"));
}
TEST_F(TypeExtensionApiTest, MethodsMethFastCallWithKeywordsExCall) {
_PyCFunctionFastWithKeywords meth = [](PyObject* self, PyObject* const* args,
Py_ssize_t nargs, PyObject* kwnames) {
PyObjectPtr nargs_obj(PyLong_FromLong(nargs));
return PyTuple_Pack(5, self, args[0], args[1], nargs_obj.get(), kwnames);
};
static PyMethodDef methods[] = {
{"fastcall", reinterpret_cast<PyCFunction>(reinterpret_cast<void*>(meth)),
METH_FASTCALL | METH_KEYWORDS},
{nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
self = C()
result = self.fastcall(*[1234], kwarg=5678)
)");
PyObjectPtr self(testing::mainModuleGet("self"));
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyTuple_CheckExact(result), 1);
ASSERT_EQ(PyTuple_Size(result), 5);
EXPECT_EQ(PyTuple_GetItem(result, 0), self);
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 1), 1234));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 2), 5678));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 3), 1));
PyObject* kwnames = PyTuple_GetItem(result, 4);
ASSERT_EQ(PyTuple_CheckExact(kwnames), 1);
ASSERT_EQ(PyTuple_Size(kwnames), 1);
EXPECT_TRUE(isUnicodeEqualsCStr(PyTuple_GetItem(kwnames, 0), "kwarg"));
}
TEST_F(TypeExtensionApiTest, MethodsMethFastCallWithKeywordsExCallMultiArg) {
_PyCFunctionFastWithKeywords meth = [](PyObject* self, PyObject* const* args,
Py_ssize_t nargs, PyObject* kwnames) {
PyObjectPtr nargs_obj(PyLong_FromSsize_t(nargs));
return PyTuple_Pack(7, self, args[0], args[1], args[2], args[3],
nargs_obj.get(), kwnames);
};
static PyMethodDef methods[] = {
{"fastcall", reinterpret_cast<PyCFunction>(reinterpret_cast<void*>(meth)),
METH_FASTCALL | METH_KEYWORDS},
{nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
self = C()
result = self.fastcall(*[1234, 99], kwarg=5678, kwdos=22)
)");
PyObjectPtr self(testing::mainModuleGet("self"));
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyTuple_CheckExact(result), 1);
ASSERT_EQ(PyTuple_Size(result), 7);
EXPECT_EQ(PyTuple_GetItem(result, 0), self);
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 1), 1234));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 2), 99));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 3), 5678));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 4), 22));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 5), 2));
PyObject* kwnames = PyTuple_GetItem(result, 6);
ASSERT_EQ(PyTuple_CheckExact(kwnames), 1);
ASSERT_EQ(PyTuple_Size(kwnames), 2);
EXPECT_TRUE(isUnicodeEqualsCStr(PyTuple_GetItem(kwnames, 0), "kwarg"));
EXPECT_TRUE(isUnicodeEqualsCStr(PyTuple_GetItem(kwnames, 1), "kwdos"));
}
TEST_F(TypeExtensionApiTest, MethodsMethFastCallWithKeywordsExEmptyKwargsCall) {
_PyCFunctionFastWithKeywords meth = [](PyObject* self, PyObject* const* args,
Py_ssize_t nargs, PyObject* kwnames) {
EXPECT_EQ(kwnames, nullptr);
PyObjectPtr nargs_obj(PyLong_FromSsize_t(nargs));
return PyTuple_Pack(3, self, args[0], nargs_obj.get());
};
static PyMethodDef methods[] = {
{"fastcall", reinterpret_cast<PyCFunction>(reinterpret_cast<void*>(meth)),
METH_FASTCALL | METH_KEYWORDS},
{nullptr}};
PyType_Slot slots[] = {
{Py_tp_methods, methods},
{0, nullptr},
};
static PyType_Spec spec;
spec = {
"__main__.C", 0, 0, Py_TPFLAGS_DEFAULT, slots,
};
PyObjectPtr type(PyType_FromSpec(&spec));
ASSERT_NE(type, nullptr);
testing::moduleSet("__main__", "C", type);
PyRun_SimpleString(R"(
self = C()
result = self.fastcall(*[1234], *{})
)");
PyObjectPtr self(testing::mainModuleGet("self"));
PyObjectPtr result(testing::mainModuleGet("result"));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyTuple_CheckExact(result), 1);
ASSERT_EQ(PyTuple_Size(result), 3);
EXPECT_EQ(PyTuple_GetItem(result, 0), self);
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 1), 1234));
EXPECT_TRUE(isLongEqualsLong(PyTuple_GetItem(result, 2), 1));
}
TEST(TypeExtensionApiTestNoFixture, DeallocSlotCalledDuringFinalize) {
resetPythonEnv();
Py_Initialize();
static bool destroyed;
destroyed = false;
destructor dealloc = [](PyObject* self) {
PyTypeObject* type = Py_TYPE(self);
destroyed = true;
PyObject_Del(self);
Py_DECREF(type);
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_tp_dealloc, dealloc));
PyTypeObject* type = reinterpret_cast<PyTypeObject*>(mainModuleGet("Bar"));
PyObject* obj = PyObject_New(PyObject, type);
Py_DECREF(type);
ASSERT_EQ(moduleSet("__main__", "bar_obj", obj), 0);
Py_DECREF(obj);
ASSERT_FALSE(destroyed);
Py_FinalizeEx();
ASSERT_TRUE(destroyed);
}
TEST_F(TypeExtensionApiTest, CallIterSlotFromManagedCode) {
unaryfunc iter_func = [](PyObject* self) {
Py_INCREF(self);
return self;
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Foo", Py_tp_iter, iter_func));
ASSERT_EQ(PyRun_SimpleString(R"(
f = Foo()
itr = f.__iter__()
)"),
0);
PyObjectPtr f(mainModuleGet("f"));
PyObjectPtr itr(mainModuleGet("itr"));
EXPECT_EQ(f, itr);
}
TEST_F(TypeExtensionApiTest, TypeCheckWithSameTypeReturnsTrue) {
PyObjectPtr pylong(PyLong_FromLong(10));
PyObjectPtr pylong_type(PyObject_Type(pylong));
EXPECT_EQ(PyObject_TypeCheck(
pylong, reinterpret_cast<PyTypeObject*>(pylong_type.get())),
1);
}
TEST_F(TypeExtensionApiTest, TypeCheckWithSubtypeReturnsTrue) {
ASSERT_EQ(PyRun_SimpleString(R"(
class MyFloat(float): pass
myflt = MyFloat(1.23)
)"),
0);
PyObjectPtr myfloat(mainModuleGet("myflt"));
PyObjectPtr pyfloat(PyFloat_FromDouble(3.21));
PyObjectPtr pyfloat_type(PyObject_Type(pyfloat));
EXPECT_EQ(PyObject_TypeCheck(
myfloat, reinterpret_cast<PyTypeObject*>(pyfloat_type.get())),
1);
}
TEST_F(TypeExtensionApiTest, TypeCheckWithDifferentTypesReturnsFalse) {
PyObjectPtr pylong(PyLong_FromLong(10));
PyObjectPtr pyuni(PyUnicode_FromString("string"));
PyObjectPtr pyuni_type(PyObject_Type(pyuni));
EXPECT_EQ(PyObject_TypeCheck(
pylong, reinterpret_cast<PyTypeObject*>(pyuni_type.get())),
0);
}
TEST_F(TypeExtensionApiTest, SetDunderClassWithExtensionTypeRaisesTypeError) {
destructor dealloc = [](PyObject* self) {
PyTypeObject* type = Py_TYPE(self);
PyObject_Del(self);
Py_DECREF(type);
};
ASSERT_NO_FATAL_FAILURE(createTypeWithSlot("Bar", Py_tp_dealloc, dealloc));
CaptureStdStreams streams;
PyRun_SimpleString(R"(
bar = Bar()
class C: pass
bar.__class__ = C
)");
std::string err = streams.err();
EXPECT_NE(err.find("TypeError:"), std::string::npos);
EXPECT_NE(err.find("__class__"), std::string::npos);
EXPECT_NE(err.find("differs"), std::string::npos);
}
TEST_F(TypeExtensionApiTest, TpDeallocWithoutFreeingMemoryUntracksNativeProxy) {
// We test this indirectly with a typical freelist pattern; `PyObject_Init`
// only works on memory that has been untracked before.
static const int max_free = 4;
static int numfree = 0;
static PyObject* freelist[max_free];
newfunc new_func = [](PyTypeObject* type, PyObject*, PyObject*) {
PyObject* object;
if (numfree > 0) {
object = freelist[--numfree];
PyObject_Init(object, type);
} else {
object = PyObject_New(PyObject, type);
}
return object;
};
destructor dealloc = [](PyObject* self) {
PyTypeObject* type = Py_TYPE(self);
Py_DECREF(type);
if (numfree + 1 < max_free) {
freelist[numfree++] = self;
} else {
freefunc tp_free =
reinterpret_cast<freefunc>(PyType_GetSlot(type, Py_tp_free));
tp_free(self);
}
};
static const PyType_Slot slots[] = {
{Py_tp_new, reinterpret_cast<void*>(new_func)},
{Py_tp_dealloc, reinterpret_cast<void*>(dealloc)},
{0, nullptr},
};
static const PyType_Spec spec = {
"foo", 0, 0, Py_TPFLAGS_DEFAULT, const_cast<PyType_Slot*>(slots),
};
PyObjectPtr type(PyType_FromSpec(const_cast<PyType_Spec*>(&spec)));
ASSERT_NE(type, nullptr);
PyObject* o0 = _PyObject_CallNoArg(type);
PyObject* o1 = _PyObject_CallNoArg(type);
Py_DECREF(o0);
collectGarbage();
PyObject* o2 = _PyObject_CallNoArg(type);
EXPECT_EQ(o0, o2);
Py_DECREF(o1);
collectGarbage();
PyObject* o3 = _PyObject_CallNoArg(type);
EXPECT_EQ(o1, o3);
PyObject* o4 = _PyObject_CallNoArg(type);
Py_DECREF(o3);
Py_DECREF(o2);
Py_DECREF(o4);
}
struct TpSlotTestObject {
PyObject_HEAD
int val0;
int val1;
};
static PyObject* makeTestInstanceWithSlots(const PyType_Slot* slots) {
const PyType_Spec spec = {
"foo",
sizeof(TpSlotTestObject),
0,
Py_TPFLAGS_DEFAULT,
const_cast<PyType_Slot*>(slots),
};
PyObjectPtr type(PyType_FromSpec(const_cast<PyType_Spec*>(&spec)));
if (type == nullptr) return nullptr;
PyObject* instance(PyObject_CallFunction(type, nullptr));
if (instance == nullptr) return nullptr;
TpSlotTestObject* data = reinterpret_cast<TpSlotTestObject*>(instance);
data->val0 = 42;
data->val1 = 128077;
return instance;
}
TEST_F(TypeExtensionApiTest, CallDunderStrReturnsStr) {
reprfunc func = [](PyObject* obj) -> PyObject* {
TpSlotTestObject* data = reinterpret_cast<TpSlotTestObject*>(obj);
return PyUnicode_FromFormat("<str %d %c>", data->val0, data->val1);
};
static const PyType_Slot slots[] = {
{Py_tp_str, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr result(PyObject_CallMethod(instance, "__str__", nullptr));
EXPECT_TRUE(isUnicodeEqualsCStr(result, "<str 42 \xf0\x9f\x91\x8d>"));
}
TEST_F(TypeExtensionApiTest, CallDunderReprReturnsStr) {
reprfunc func = [](PyObject* obj) -> PyObject* {
TpSlotTestObject* data = reinterpret_cast<TpSlotTestObject*>(obj);
return PyUnicode_FromFormat("<repr %d %c>", data->val0, data->val1);
};
static const PyType_Slot slots[] = {
{Py_tp_repr, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr result(PyObject_CallMethod(instance, "__repr__", nullptr));
EXPECT_TRUE(isUnicodeEqualsCStr(result, "<repr 42 \xf0\x9f\x91\x8d>"));
}
TEST_F(TypeExtensionApiTest, CallDunderIterReturnsStr) {
getiterfunc func = [](PyObject* obj) -> PyObject* {
TpSlotTestObject* data = reinterpret_cast<TpSlotTestObject*>(obj);
return PyUnicode_FromFormat("<iter %d %c>", data->val0, data->val1);
};
static const PyType_Slot slots[] = {
{Py_tp_iter, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr result(PyObject_CallMethod(instance, "__iter__", nullptr));
EXPECT_TRUE(isUnicodeEqualsCStr(result, "<iter 42 \xf0\x9f\x91\x8d>"));
}
TEST_F(TypeExtensionApiTest, CallDunderAwaitReturnsStr) {
reprfunc func = [](PyObject* obj) -> PyObject* {
TpSlotTestObject* data = reinterpret_cast<TpSlotTestObject*>(obj);
return PyUnicode_FromFormat("<await %d %c>", data->val0, data->val1);
};
static const PyType_Slot slots[] = {
{Py_am_await, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr result(PyObject_CallMethod(instance, "__await__", nullptr));
EXPECT_TRUE(isUnicodeEqualsCStr(result, "<await 42 \xf0\x9f\x91\x8d>"));
}
TEST_F(TypeExtensionApiTest, CallDunderAiterReturnsStr) {
reprfunc func = [](PyObject* obj) -> PyObject* {
TpSlotTestObject* data = reinterpret_cast<TpSlotTestObject*>(obj);
return PyUnicode_FromFormat("<aiter %d %c>", data->val0, data->val1);
};
static const PyType_Slot slots[] = {
{Py_am_aiter, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr result(PyObject_CallMethod(instance, "__aiter__", nullptr));
EXPECT_TRUE(isUnicodeEqualsCStr(result, "<aiter 42 \xf0\x9f\x91\x8d>"));
}
TEST_F(TypeExtensionApiTest, CallDunderAnextReturnsStr) {
reprfunc func = [](PyObject* obj) -> PyObject* {
TpSlotTestObject* data = reinterpret_cast<TpSlotTestObject*>(obj);
return PyUnicode_FromFormat("<aiter %d %c>", data->val0, data->val1);
};
static const PyType_Slot slots[] = {
{Py_am_anext, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr result(PyObject_CallMethod(instance, "__anext__", nullptr));
EXPECT_TRUE(isUnicodeEqualsCStr(result, "<aiter 42 \xf0\x9f\x91\x8d>"));
}
TEST_F(TypeExtensionApiTest, CallDunderNegReturnsStr) {
reprfunc func = [](PyObject* obj) -> PyObject* {
TpSlotTestObject* data = reinterpret_cast<TpSlotTestObject*>(obj);
return PyUnicode_FromFormat("<neg %d %c>", data->val0, data->val1);
};
static const PyType_Slot slots[] = {
{Py_nb_negative, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr result(PyObject_CallMethod(instance, "__neg__", nullptr));
EXPECT_TRUE(isUnicodeEqualsCStr(result, "<neg 42 \xf0\x9f\x91\x8d>"));
}
TEST_F(TypeExtensionApiTest, CallDunderPosReturnsStr) {
reprfunc func = [](PyObject* obj) -> PyObject* {
TpSlotTestObject* data = reinterpret_cast<TpSlotTestObject*>(obj);
return PyUnicode_FromFormat("<pos %d %c>", data->val0, data->val1);
};
static const PyType_Slot slots[] = {
{Py_nb_positive, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr result(PyObject_CallMethod(instance, "__pos__", nullptr));
EXPECT_TRUE(isUnicodeEqualsCStr(result, "<pos 42 \xf0\x9f\x91\x8d>"));
}
TEST_F(TypeExtensionApiTest, CallDunderAbsReturnsStr) {
reprfunc func = [](PyObject* obj) -> PyObject* {
TpSlotTestObject* data = reinterpret_cast<TpSlotTestObject*>(obj);
return PyUnicode_FromFormat("<abs %d %c>", data->val0, data->val1);
};
static const PyType_Slot slots[] = {
{Py_nb_absolute, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr result(PyObject_CallMethod(instance, "__abs__", nullptr));
EXPECT_TRUE(isUnicodeEqualsCStr(result, "<abs 42 \xf0\x9f\x91\x8d>"));
}
TEST_F(TypeExtensionApiTest, CallDunderInvertReturnsStr) {
reprfunc func = [](PyObject* obj) -> PyObject* {
TpSlotTestObject* data = reinterpret_cast<TpSlotTestObject*>(obj);
return PyUnicode_FromFormat("<invert %d %c>", data->val0, data->val1);
};
static const PyType_Slot slots[] = {
{Py_nb_invert, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr result(PyObject_CallMethod(instance, "__invert__", nullptr));
EXPECT_TRUE(isUnicodeEqualsCStr(result, "<invert 42 \xf0\x9f\x91\x8d>"));
}
TEST_F(TypeExtensionApiTest, CallDunderIntReturnsStr) {
reprfunc func = [](PyObject* obj) -> PyObject* {
TpSlotTestObject* data = reinterpret_cast<TpSlotTestObject*>(obj);
return PyUnicode_FromFormat("<int %d %c>", data->val0, data->val1);
};
static const PyType_Slot slots[] = {
{Py_nb_int, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr result(PyObject_CallMethod(instance, "__int__", nullptr));
EXPECT_TRUE(isUnicodeEqualsCStr(result, "<int 42 \xf0\x9f\x91\x8d>"));
}
TEST_F(TypeExtensionApiTest, CallDunderFloatReturnsStr) {
reprfunc func = [](PyObject* obj) -> PyObject* {
TpSlotTestObject* data = reinterpret_cast<TpSlotTestObject*>(obj);
return PyUnicode_FromFormat("<float %d %c>", data->val0, data->val1);
};
static const PyType_Slot slots[] = {
{Py_nb_float, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr result(PyObject_CallMethod(instance, "__float__", nullptr));
EXPECT_TRUE(isUnicodeEqualsCStr(result, "<float 42 \xf0\x9f\x91\x8d>"));
}
TEST_F(TypeExtensionApiTest, CallDunderIndexReturnsStr) {
reprfunc func = [](PyObject* obj) -> PyObject* {
TpSlotTestObject* data = reinterpret_cast<TpSlotTestObject*>(obj);
return PyUnicode_FromFormat("<index %d %c>", data->val0, data->val1);
};
static const PyType_Slot slots[] = {
{Py_nb_index, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr result(PyObject_CallMethod(instance, "__index__", nullptr));
EXPECT_TRUE(isUnicodeEqualsCStr(result, "<index 42 \xf0\x9f\x91\x8d>"));
}
TEST_F(TypeExtensionApiTest, MultipleInheritanceWithBaseWithoutSlotsWorks) {
PyType_Slot slots[] = {
{0, nullptr},
};
static PyType_Spec spec;
int size = sizeof(PyObject) + 17;
spec = {
"foo.N", size, 0, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, slots,
};
PyObjectPtr ext_type(PyType_FromSpec(&spec));
moduleSet("__main__", "N", ext_type);
EXPECT_EQ(PyRun_SimpleString(R"(
class A:
pass
class C(A, N):
pass
class D(C):
pass
c = C()
d = D()
)"),
0);
PyObjectPtr a(mainModuleGet("A"));
PyObjectPtr c(mainModuleGet("C"));
EXPECT_TRUE(PyType_GetFlags(reinterpret_cast<PyTypeObject*>(c.get())) &
Py_TPFLAGS_DEFAULT);
EXPECT_TRUE(PyType_GetFlags(reinterpret_cast<PyTypeObject*>(c.get())) &
Py_TPFLAGS_BASETYPE);
PyObjectPtr c_size(PyObject_GetAttrString(c.get(), "__basicsize__"));
EXPECT_TRUE(PyLong_AsLong(c_size) >= size);
PyObjectPtr mro(PyObject_GetAttrString(c.get(), "__mro__"));
EXPECT_TRUE(PyTuple_Check(mro));
EXPECT_EQ(PyTuple_GetItem(mro, 0), c.get());
EXPECT_EQ(PyTuple_GetItem(mro, 1), a.get());
EXPECT_EQ(PyTuple_GetItem(mro, 2), ext_type.get());
PyObjectPtr base(PyObject_GetAttrString(c.get(), "__base__"));
EXPECT_EQ(base.get(), ext_type.get());
}
struct TpSlotRefcntTestObject {
PyObject_HEAD
Py_ssize_t initial_refcnt;
};
static PyObject* makeTestRefcntInstanceWithSlots(const PyType_Slot* slots) {
const PyType_Spec spec = {
"foo",
sizeof(TpSlotRefcntTestObject),
0,
Py_TPFLAGS_DEFAULT,
const_cast<PyType_Slot*>(slots),
};
PyObjectPtr type(PyType_FromSpec(const_cast<PyType_Spec*>(&spec)));
if (type == nullptr) return nullptr;
PyObject* instance = PyObject_CallFunction(type, nullptr);
if (instance == nullptr) return nullptr;
reinterpret_cast<TpSlotRefcntTestObject*>(instance)->initial_refcnt =
Py_REFCNT(instance);
return instance;
}
TEST_F(TypeExtensionApiTest, IntegralSlotOwnsReference) {
lenfunc len_func = [](PyObject* self) -> Py_ssize_t {
// We expect a refcount of 1 greater than the inital refcount since the
// method is called with a new reference.
EXPECT_EQ(
Py_REFCNT(self),
reinterpret_cast<TpSlotRefcntTestObject*>(self)->initial_refcnt + 1);
return 0xdeadbeef;
};
// __len__ is created as an integral slot
static const PyType_Slot slots[] = {
{Py_sq_length, reinterpret_cast<void*>(len_func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr result(PyObject_CallMethod(instance, "__len__", nullptr));
// Once the call is complete the wrapper should decref the arg so we expect to
// see the original refcount.
EXPECT_EQ(Py_REFCNT(instance),
reinterpret_cast<TpSlotRefcntTestObject*>(instance.get())
->initial_refcnt);
EXPECT_TRUE(isLongEqualsLong(result, 0xdeadbeef));
}
TEST_F(TypeExtensionApiTest, UnarySlotOwnsReference) {
reprfunc func = [](PyObject* obj) -> PyObject* {
// We expect a refcount of 1 greater than the inital refcount since the
// method is called with a new reference.
EXPECT_EQ(
Py_REFCNT(obj),
reinterpret_cast<TpSlotRefcntTestObject*>(obj)->initial_refcnt + 1);
Py_RETURN_NONE;
};
// __repr__ is created as a unary slot
static const PyType_Slot slots[] = {
{Py_tp_repr, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr result(PyObject_CallMethod(instance, "__repr__", nullptr));
// Once the call is complete the wrapper should decref the arg so we expect to
// see the original refcount.
EXPECT_EQ(Py_REFCNT(instance),
reinterpret_cast<TpSlotRefcntTestObject*>(instance.get())
->initial_refcnt);
EXPECT_EQ(result, Py_None);
}
TEST_F(TypeExtensionApiTest, BinarySlotOwnsReference) {
binaryfunc func = [](PyObject* self, PyObject* other) -> PyObject* {
// We expect a refcount of 1 greater than the inital refcount since the
// method is called with a new reference.
EXPECT_EQ(
Py_REFCNT(self),
reinterpret_cast<TpSlotRefcntTestObject*>(self)->initial_refcnt + 1);
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_GE(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other)->initial_refcnt + 1);
Py_RETURN_NONE;
};
// __add__ is created as a binary slot
static const PyType_Slot slots[] = {
{Py_nb_add, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr other(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(other, nullptr);
PyObjectPtr result(
PyObject_CallMethod(instance, "__add__", "O", other.get()));
// Once the call is complete the wrapper should decref the arg so we expect to
// see the original refcount.
EXPECT_EQ(Py_REFCNT(instance),
reinterpret_cast<TpSlotRefcntTestObject*>(instance.get())
->initial_refcnt);
EXPECT_EQ(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other.get())->initial_refcnt);
EXPECT_EQ(result, Py_None);
}
TEST_F(TypeExtensionApiTest, BinarySwappedSlotOwnsReference) {
binaryfunc func = [](PyObject* self, PyObject* other) -> PyObject* {
// We expect a refcount of 1 greater than the inital refcount since the
// method is called with a new reference.
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_GE(
Py_REFCNT(self),
reinterpret_cast<TpSlotRefcntTestObject*>(self)->initial_refcnt + 1);
EXPECT_EQ(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other)->initial_refcnt + 1);
Py_RETURN_NONE;
};
// __add__ is created as a binary slot
static const PyType_Slot slots[] = {
{Py_nb_add, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr other(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(other, nullptr);
PyObjectPtr result(
PyObject_CallMethod(instance, "__radd__", "O", other.get()));
// Once the call is complete the wrapper should decref the arg so we expect to
// see the original refcount.
EXPECT_EQ(Py_REFCNT(instance),
reinterpret_cast<TpSlotRefcntTestObject*>(instance.get())
->initial_refcnt);
EXPECT_EQ(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other.get())->initial_refcnt);
EXPECT_EQ(result, Py_None);
}
TEST_F(TypeExtensionApiTest, TernarySlotOwnsReference) {
ternaryfunc func = [](PyObject* self, PyObject* other,
PyObject* third) -> PyObject* {
// We expect a refcount of 1 greater than the inital refcount since the
// method is called with a new reference.
EXPECT_EQ(
Py_REFCNT(self),
reinterpret_cast<TpSlotRefcntTestObject*>(self)->initial_refcnt + 1);
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_GE(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other)->initial_refcnt + 1);
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_GE(
Py_REFCNT(third),
reinterpret_cast<TpSlotRefcntTestObject*>(third)->initial_refcnt + 1);
Py_RETURN_NONE;
};
// __pow__ is created as a ternary slot
static const PyType_Slot slots[] = {
{Py_nb_power, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr other(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(other, nullptr);
PyObjectPtr third(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(third, nullptr);
PyObjectPtr result(
PyObject_CallMethod(instance, "__pow__", "OO", other.get(), third.get()));
// Once the call is complete the wrapper should decref the arg so we expect to
// see the original refcount.
EXPECT_EQ(Py_REFCNT(instance),
reinterpret_cast<TpSlotRefcntTestObject*>(instance.get())
->initial_refcnt);
EXPECT_EQ(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other.get())->initial_refcnt);
EXPECT_EQ(
Py_REFCNT(third),
reinterpret_cast<TpSlotRefcntTestObject*>(third.get())->initial_refcnt);
EXPECT_EQ(result, Py_None);
}
TEST_F(TypeExtensionApiTest, TernarySwappedSlotOwnsReference) {
ternaryfunc func = [](PyObject* self, PyObject* other,
PyObject* third) -> PyObject* {
// We expect a refcount of 1 greater than the inital refcount since the
// method is called with a new reference.
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_GE(
Py_REFCNT(self),
reinterpret_cast<TpSlotRefcntTestObject*>(self)->initial_refcnt + 1);
EXPECT_EQ(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other)->initial_refcnt + 1);
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_GE(
Py_REFCNT(third),
reinterpret_cast<TpSlotRefcntTestObject*>(third)->initial_refcnt + 1);
Py_RETURN_NONE;
};
// __pow__ is created as a ternary slot
static const PyType_Slot slots[] = {
{Py_nb_power, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr other(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(other, nullptr);
PyObjectPtr third(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(third, nullptr);
PyObjectPtr result(PyObject_CallMethod(instance, "__rpow__", "OO",
other.get(), third.get()));
// Once the call is complete the wrapper should decref the arg so we expect to
// see the original refcount.
EXPECT_EQ(Py_REFCNT(instance),
reinterpret_cast<TpSlotRefcntTestObject*>(instance.get())
->initial_refcnt);
EXPECT_EQ(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other.get())->initial_refcnt);
EXPECT_EQ(
Py_REFCNT(third),
reinterpret_cast<TpSlotRefcntTestObject*>(third.get())->initial_refcnt);
EXPECT_EQ(result, Py_None);
}
TEST_F(TypeExtensionApiTest, TernaryVarKwSlotOwnsReference) {
ternaryfunc func = [](PyObject* self, PyObject* other,
PyObject* third) -> PyObject* {
// We expect a refcount of 1 greater than the inital refcount since the
// method is called with a new reference.
EXPECT_EQ(
Py_REFCNT(self),
reinterpret_cast<TpSlotRefcntTestObject*>(self)->initial_refcnt + 1);
EXPECT_EQ(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other)->initial_refcnt + 1);
EXPECT_EQ(
Py_REFCNT(third),
reinterpret_cast<TpSlotRefcntTestObject*>(third)->initial_refcnt + 1);
Py_RETURN_NONE;
};
// __pow__ is created as a ternary slot
static const PyType_Slot slots[] = {
{Py_nb_power, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr other(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(other, nullptr);
PyObjectPtr third(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(third, nullptr);
PyObjectPtr args(PyTuple_Pack(2, other.get(), third.get()));
PyObjectPtr pow(PyObject_GetAttrString(instance, "__pow__"));
PyObjectPtr result(PyObject_Call(pow, args, nullptr));
// Once the call is complete the wrapper should decref the arg so we expect to
// see the original refcount.
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_GE(Py_REFCNT(instance),
reinterpret_cast<TpSlotRefcntTestObject*>(instance.get())
->initial_refcnt);
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_GE(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other.get())->initial_refcnt);
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_GE(
Py_REFCNT(third),
reinterpret_cast<TpSlotRefcntTestObject*>(third.get())->initial_refcnt);
EXPECT_EQ(result, Py_None);
}
TEST_F(TypeExtensionApiTest, SetattrWrapperOwnsReference) {
setattrofunc func = [](PyObject* self, PyObject*, PyObject* third) -> int {
// We expect a refcount of 1 greater than the inital refcount since the
// method is called with a new reference.
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_LE(
Py_REFCNT(self),
reinterpret_cast<TpSlotRefcntTestObject*>(self)->initial_refcnt + 1);
// The middle arg is not checked because it needs to be a string, not
// whatever object we use to hold a refcount.
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_LE(
Py_REFCNT(third),
reinterpret_cast<TpSlotRefcntTestObject*>(third)->initial_refcnt + 1);
return 0;
};
static const PyType_Slot slots[] = {
{Py_tp_setattro, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr other(PyUnicode_FromString("name"));
ASSERT_NE(other, nullptr);
PyObjectPtr third(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(third, nullptr);
int result = PyObject_SetAttr(instance, other, third);
EXPECT_EQ(result, 0);
// Once the call is complete the wrapper should decref the arg so we expect to
// see the original refcount.
EXPECT_EQ(Py_REFCNT(instance),
reinterpret_cast<TpSlotRefcntTestObject*>(instance.get())
->initial_refcnt);
// The middle arg is not checked because it needs to be a string, not
// whatever object we use to hold a refcount.
EXPECT_EQ(
Py_REFCNT(third),
reinterpret_cast<TpSlotRefcntTestObject*>(third.get())->initial_refcnt);
}
TEST_F(TypeExtensionApiTest, DelattrWrapperOwnsReference) {
setattrofunc func = [](PyObject* self, PyObject*, PyObject*) -> int {
// We expect a refcount of 1 greater than the inital refcount since the
// method is called with a new reference.
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_LE(
Py_REFCNT(self),
reinterpret_cast<TpSlotRefcntTestObject*>(self)->initial_refcnt + 1);
// The second arg is not checked because it needs to be a string, not
// whatever object we use to hold a refcount.
return 0;
};
static const PyType_Slot slots[] = {
{Py_tp_setattro, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr other(PyUnicode_FromString("name"));
ASSERT_NE(other, nullptr);
// Need to set attribute before we can delete it.
PyObjectPtr value(PyLong_FromLong(5));
int result = PyObject_SetAttr(instance, other, value);
EXPECT_EQ(result, 0);
result = PyObject_DelAttr(instance, other);
EXPECT_EQ(result, 0);
// Once the call is complete the wrapper should decref the arg so we expect to
// see the original refcount.
EXPECT_EQ(Py_REFCNT(instance),
reinterpret_cast<TpSlotRefcntTestObject*>(instance.get())
->initial_refcnt);
// The second arg is not checked because it needs to be a string, not
// whatever object we use to hold a refcount.
}
TEST_F(TypeExtensionApiTest, RichCmpSlotOwnsReference) {
richcmpfunc func = [](PyObject* self, PyObject* other, int) -> PyObject* {
// We expect a refcount of 1 greater than the inital refcount since the
// method is called with a new reference.
EXPECT_EQ(
Py_REFCNT(self),
reinterpret_cast<TpSlotRefcntTestObject*>(self)->initial_refcnt + 1);
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_GE(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other)->initial_refcnt + 1);
Py_RETURN_TRUE;
};
static const PyType_Slot slots[] = {
{Py_tp_richcompare, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr other(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(other, nullptr);
PyObjectPtr result(PyObject_CallMethod(instance, "__eq__", "O", other.get()));
// Once the call is complete the wrapper should decref the arg so we expect to
// see the original refcount.
EXPECT_EQ(Py_REFCNT(instance),
reinterpret_cast<TpSlotRefcntTestObject*>(instance.get())
->initial_refcnt);
EXPECT_EQ(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other.get())->initial_refcnt);
EXPECT_EQ(result, Py_True);
}
TEST_F(TypeExtensionApiTest, NextSlotOwnsReference) {
unaryfunc func = [](PyObject* obj) -> PyObject* {
// We expect a refcount of 1 greater than the inital refcount since the
// method is called with a new reference.
EXPECT_EQ(
Py_REFCNT(obj),
reinterpret_cast<TpSlotRefcntTestObject*>(obj)->initial_refcnt + 1);
Py_RETURN_NONE;
};
static const PyType_Slot slots[] = {
{Py_tp_iternext, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr result(PyObject_CallMethod(instance, "__next__", nullptr));
// Once the call is complete the wrapper should decref the arg so we expect to
// see the original refcount.
EXPECT_EQ(Py_REFCNT(instance),
reinterpret_cast<TpSlotRefcntTestObject*>(instance.get())
->initial_refcnt);
EXPECT_EQ(result, Py_None);
}
TEST_F(TypeExtensionApiTest, DescrGetSlotOwnsReference) {
descrgetfunc func = [](PyObject* self, PyObject* other,
PyObject* third) -> PyObject* {
// We expect a refcount of 1 greater than the inital refcount since the
// method is called with a new reference.
EXPECT_EQ(
Py_REFCNT(self),
reinterpret_cast<TpSlotRefcntTestObject*>(self)->initial_refcnt + 1);
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_GE(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other)->initial_refcnt + 1);
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_GE(
Py_REFCNT(third),
reinterpret_cast<TpSlotRefcntTestObject*>(third)->initial_refcnt + 1);
Py_RETURN_NONE;
};
static const PyType_Slot slots[] = {
{Py_tp_descr_get, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr other(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(other, nullptr);
PyObjectPtr third(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(third, nullptr);
PyObjectPtr result(
PyObject_CallMethod(instance, "__get__", "OO", other.get(), third.get()));
// Once the call is complete the wrapper should decref the arg so we expect to
// see the original refcount.
EXPECT_EQ(Py_REFCNT(instance),
reinterpret_cast<TpSlotRefcntTestObject*>(instance.get())
->initial_refcnt);
EXPECT_EQ(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other.get())->initial_refcnt);
EXPECT_EQ(
Py_REFCNT(third),
reinterpret_cast<TpSlotRefcntTestObject*>(third.get())->initial_refcnt);
EXPECT_EQ(result, Py_None);
}
TEST_F(TypeExtensionApiTest, DescrSetSlotOwnsReference) {
descrsetfunc func = [](PyObject* self, PyObject* other,
PyObject* third) -> int {
// We expect a refcount of 1 greater than the inital refcount since the
// method is called with a new reference.
EXPECT_EQ(
Py_REFCNT(self),
reinterpret_cast<TpSlotRefcntTestObject*>(self)->initial_refcnt + 1);
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_GE(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other)->initial_refcnt + 1);
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_GE(
Py_REFCNT(third),
reinterpret_cast<TpSlotRefcntTestObject*>(third)->initial_refcnt + 1);
return 0;
};
static const PyType_Slot slots[] = {
{Py_tp_descr_set, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr other(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(other, nullptr);
PyObjectPtr third(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(third, nullptr);
PyObjectPtr result(
PyObject_CallMethod(instance, "__set__", "OO", other.get(), third.get()));
// Once the call is complete the wrapper should decref the arg so we expect to
// see the original refcount.
EXPECT_EQ(Py_REFCNT(instance),
reinterpret_cast<TpSlotRefcntTestObject*>(instance.get())
->initial_refcnt);
EXPECT_EQ(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other.get())->initial_refcnt);
EXPECT_EQ(
Py_REFCNT(third),
reinterpret_cast<TpSlotRefcntTestObject*>(third.get())->initial_refcnt);
EXPECT_EQ(result, Py_None);
}
TEST_F(TypeExtensionApiTest, DescrDeleteSlotOwnsReference) {
descrsetfunc func = [](PyObject* self, PyObject* other,
PyObject* third) -> int {
// We expect a refcount of 1 greater than the inital refcount since the
// method is called with a new reference.
EXPECT_EQ(
Py_REFCNT(self),
reinterpret_cast<TpSlotRefcntTestObject*>(self)->initial_refcnt + 1);
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_GE(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other)->initial_refcnt + 1);
EXPECT_EQ(third, nullptr);
return 0;
};
static const PyType_Slot slots[] = {
{Py_tp_descr_set, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr other(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(other, nullptr);
PyObjectPtr result(
PyObject_CallMethod(instance, "__delete__", "O", other.get()));
EXPECT_EQ(result, Py_None);
// Once the call is complete the wrapper should decref the arg so we expect to
// see the original refcount.
EXPECT_EQ(Py_REFCNT(instance),
reinterpret_cast<TpSlotRefcntTestObject*>(instance.get())
->initial_refcnt);
EXPECT_EQ(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other.get())->initial_refcnt);
}
TEST_F(TypeExtensionApiTest, SetitemSlotOwnsReference) {
objobjargproc func = [](PyObject* self, PyObject* other,
PyObject* third) -> int {
// We expect a refcount of 1 greater than the inital refcount since the
// method is called with a new reference.
EXPECT_EQ(
Py_REFCNT(self),
reinterpret_cast<TpSlotRefcntTestObject*>(self)->initial_refcnt + 1);
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_GE(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other)->initial_refcnt + 1);
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_GE(
Py_REFCNT(third),
reinterpret_cast<TpSlotRefcntTestObject*>(third)->initial_refcnt + 1);
return 0;
};
static const PyType_Slot slots[] = {
{Py_mp_ass_subscript, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr other(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(other, nullptr);
PyObjectPtr third(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(third, nullptr);
PyObjectPtr result(PyObject_CallMethod(instance, "__setitem__", "OO",
other.get(), third.get()));
EXPECT_EQ(result, Py_None);
// Once the call is complete the wrapper should decref the arg so we expect to
// see the original refcount.
EXPECT_EQ(Py_REFCNT(instance),
reinterpret_cast<TpSlotRefcntTestObject*>(instance.get())
->initial_refcnt);
EXPECT_EQ(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other.get())->initial_refcnt);
EXPECT_EQ(
Py_REFCNT(third),
reinterpret_cast<TpSlotRefcntTestObject*>(third.get())->initial_refcnt);
}
TEST_F(TypeExtensionApiTest, DelitemSlotOwnsReference) {
objobjargproc func = [](PyObject* self, PyObject* other,
PyObject* third) -> int {
// We expect a refcount of 1 greater than the inital refcount since the
// method is called with a new reference.
EXPECT_EQ(
Py_REFCNT(self),
reinterpret_cast<TpSlotRefcntTestObject*>(self)->initial_refcnt + 1);
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_GE(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other)->initial_refcnt + 1);
EXPECT_EQ(third, nullptr);
return 0;
};
static const PyType_Slot slots[] = {
{Py_mp_ass_subscript, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr other(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(other, nullptr);
PyObjectPtr result(
PyObject_CallMethod(instance, "__delitem__", "O", other.get()));
EXPECT_EQ(result, Py_None);
// Once the call is complete the wrapper should decref the arg so we expect to
// see the original refcount.
EXPECT_EQ(Py_REFCNT(instance),
reinterpret_cast<TpSlotRefcntTestObject*>(instance.get())
->initial_refcnt);
EXPECT_EQ(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other.get())->initial_refcnt);
}
TEST_F(TypeExtensionApiTest, ContainsSlotOwnsReference) {
objobjproc func = [](PyObject* self, PyObject* other) -> int {
// We expect a refcount of 1 greater than the inital refcount since the
// method is called with a new reference.
EXPECT_EQ(
Py_REFCNT(self),
reinterpret_cast<TpSlotRefcntTestObject*>(self)->initial_refcnt + 1);
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_GE(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other)->initial_refcnt + 1);
return 1;
};
static const PyType_Slot slots[] = {
{Py_sq_contains, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr other(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(other, nullptr);
PyObjectPtr result(
PyObject_CallMethod(instance, "__contains__", "O", other.get()));
EXPECT_EQ(result, Py_True);
// Once the call is complete the wrapper should decref the arg so we expect to
// see the original refcount.
EXPECT_EQ(Py_REFCNT(instance),
reinterpret_cast<TpSlotRefcntTestObject*>(instance.get())
->initial_refcnt);
EXPECT_EQ(
Py_REFCNT(other),
reinterpret_cast<TpSlotRefcntTestObject*>(other.get())->initial_refcnt);
}
TEST_F(TypeExtensionApiTest, MulSlotOwnsReference) {
ssizeargfunc func = [](PyObject* self, Py_ssize_t) -> PyObject* {
// We expect a refcount of 1 greater than the inital refcount since the
// method is called with a new reference.
EXPECT_EQ(
Py_REFCNT(self),
reinterpret_cast<TpSlotRefcntTestObject*>(self)->initial_refcnt + 1);
Py_RETURN_NONE;
};
static const PyType_Slot slots[] = {
{Py_sq_repeat, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr other(PyLong_FromLong(5));
ASSERT_NE(other, nullptr);
PyObjectPtr result(
PyObject_CallMethod(instance, "__mul__", "O", other.get()));
EXPECT_EQ(result, Py_None);
// Once the call is complete the wrapper should decref the arg so we expect to
// see the original refcount.
EXPECT_EQ(Py_REFCNT(instance),
reinterpret_cast<TpSlotRefcntTestObject*>(instance.get())
->initial_refcnt);
}
TEST_F(TypeExtensionApiTest, SequenceItemSlotOwnsReference) {
ssizeargfunc func = [](PyObject* self, Py_ssize_t) -> PyObject* {
// We expect a refcount of 1 greater than the inital refcount since the
// method is called with a new reference.
EXPECT_EQ(
Py_REFCNT(self),
reinterpret_cast<TpSlotRefcntTestObject*>(self)->initial_refcnt + 1);
Py_RETURN_NONE;
};
static const PyType_Slot slots[] = {
{Py_sq_item, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr other(PyLong_FromLong(5));
ASSERT_NE(other, nullptr);
PyObjectPtr result(
PyObject_CallMethod(instance, "__getitem__", "O", other.get()));
EXPECT_EQ(result, Py_None);
// Once the call is complete the wrapper should decref the arg so we expect to
// see the original refcount.
EXPECT_EQ(Py_REFCNT(instance),
reinterpret_cast<TpSlotRefcntTestObject*>(instance.get())
->initial_refcnt);
}
TEST_F(TypeExtensionApiTest, SequenceSetItemSlotOwnsReference) {
ssizeobjargproc func = [](PyObject* self, Py_ssize_t,
PyObject* third) -> int {
// We expect a refcount of 1 greater than the inital refcount since the
// method is called with a new reference.
EXPECT_EQ(
Py_REFCNT(self),
reinterpret_cast<TpSlotRefcntTestObject*>(self)->initial_refcnt + 1);
// TODO(T89073278): Revert to EQ(other, initial+1).
EXPECT_GE(
Py_REFCNT(third),
reinterpret_cast<TpSlotRefcntTestObject*>(third)->initial_refcnt + 1);
return 0;
};
static const PyType_Slot slots[] = {
{Py_sq_ass_item, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr other(PyLong_FromLong(5));
ASSERT_NE(other, nullptr);
PyObjectPtr third(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(third, nullptr);
PyObjectPtr result(PyObject_CallMethod(instance, "__setitem__", "OO",
other.get(), third.get()));
EXPECT_EQ(result, Py_None);
// Once the call is complete the wrapper should decref the arg so we expect to
// see the original refcount.
EXPECT_EQ(Py_REFCNT(instance),
reinterpret_cast<TpSlotRefcntTestObject*>(instance.get())
->initial_refcnt);
EXPECT_EQ(
Py_REFCNT(third),
reinterpret_cast<TpSlotRefcntTestObject*>(third.get())->initial_refcnt);
}
TEST_F(TypeExtensionApiTest, SequenceDelItemSlotOwnsReference) {
ssizeobjargproc func = [](PyObject* self, Py_ssize_t,
PyObject* third) -> int {
// We expect a refcount of 1 greater than the inital refcount since the
// method is called with a new reference.
EXPECT_EQ(
Py_REFCNT(self),
reinterpret_cast<TpSlotRefcntTestObject*>(self)->initial_refcnt + 1);
EXPECT_EQ(third, nullptr);
return 0;
};
static const PyType_Slot slots[] = {
{Py_sq_ass_item, reinterpret_cast<void*>(func)},
{0, nullptr},
};
PyObjectPtr instance(makeTestRefcntInstanceWithSlots(slots));
ASSERT_NE(instance, nullptr);
PyObjectPtr other(PyLong_FromLong(5));
ASSERT_NE(other, nullptr);
PyObjectPtr result(
PyObject_CallMethod(instance, "__delitem__", "O", other.get()));
EXPECT_EQ(result, Py_None);
// Once the call is complete the wrapper should decref the arg so we expect to
// see the original refcount.
EXPECT_EQ(Py_REFCNT(instance),
reinterpret_cast<TpSlotRefcntTestObject*>(instance.get())
->initial_refcnt);
}
} // namespace testing
} // namespace py