this repo has no description
1// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
2#include "Python.h"
3#include "gtest/gtest.h"
4
5#include "capi-fixture.h"
6#include "capi-testing.h"
7
8namespace py {
9namespace testing {
10
11using DescrExtensionApiTest = ExtensionApi;
12
13// Create a new type with PyType_FromSpec with no methods, members, or getters
14static void createEmptyBarType() {
15 static PyType_Slot slots[1];
16 slots[0] = {0, nullptr};
17 static PyType_Spec spec;
18 spec = {
19 "__main__.Bar", 0, 0, Py_TPFLAGS_DEFAULT, slots,
20 };
21 PyObjectPtr type(PyType_FromSpec(&spec));
22 ASSERT_NE(type, nullptr);
23 ASSERT_EQ(PyType_CheckExact(type), 1);
24 ASSERT_EQ(moduleSet("__main__", "Bar", type), 0);
25}
26
27TEST_F(DescrExtensionApiTest, ClassMethodAsDescriptorReturnsFunction) {
28 ASSERT_NO_FATAL_FAILURE(createEmptyBarType());
29 binaryfunc meth = [](PyObject* self, PyObject* args) {
30 return PyTuple_Pack(2, self, args);
31 };
32 static PyMethodDef method_def;
33 method_def = {"foo", meth, METH_VARARGS};
34 PyObjectPtr type(mainModuleGet("Bar"));
35 PyObjectPtr descriptor(PyDescr_NewClassMethod(
36 reinterpret_cast<PyTypeObject*>(type.get()), &method_def));
37 ASSERT_NE(descriptor, nullptr);
38 PyObject_SetAttrString(type, "foo", descriptor);
39 PyObjectPtr func(PyObject_GetAttrString(type, "foo"));
40 ASSERT_NE(func, nullptr);
41 ASSERT_EQ(PyErr_Occurred(), nullptr);
42
43 PyObjectPtr args(PyTuple_New(0));
44 PyObjectPtr result(PyObject_CallObject(func, args));
45 ASSERT_NE(result, nullptr);
46 ASSERT_EQ(PyTuple_Check(result), 1);
47 ASSERT_EQ(PyTuple_Size(result), 2);
48
49 // self
50 PyObject* arg0 = PyTuple_GetItem(result, 0);
51 ASSERT_NE(arg0, nullptr);
52 EXPECT_EQ(arg0, type);
53
54 // args
55 PyObject* arg1 = PyTuple_GetItem(result, 1);
56 ASSERT_NE(arg1, nullptr);
57 EXPECT_EQ(args, arg1);
58}
59
60TEST_F(DescrExtensionApiTest, ClassMethodAsCallableReturnsTypeAsFirstArg) {
61 ASSERT_NO_FATAL_FAILURE(createEmptyBarType());
62 binaryfunc meth = [](PyObject* self, PyObject* args) {
63 return PyTuple_Pack(2, self, args);
64 };
65 static PyMethodDef method_def;
66 method_def = {"foo", meth, METH_VARARGS};
67 PyObjectPtr type(mainModuleGet("Bar"));
68 PyObjectPtr callable(PyDescr_NewClassMethod(
69 reinterpret_cast<PyTypeObject*>(type.get()), &method_def));
70 ASSERT_NE(callable, nullptr);
71
72 PyObjectPtr args(PyTuple_New(1));
73 Py_INCREF(type); // SetItem steals a reference
74 PyTuple_SetItem(args, 0, type);
75 PyObjectPtr result(PyObject_CallObject(callable, args));
76 ASSERT_NE(result, nullptr);
77 ASSERT_EQ(PyTuple_Check(result), 1);
78 ASSERT_EQ(PyTuple_Size(result), 2);
79
80 // self
81 PyObject* arg0 = PyTuple_GetItem(result, 0);
82 ASSERT_NE(arg0, nullptr);
83 EXPECT_EQ(arg0, type);
84
85 // args
86 PyObject* arg1 = PyTuple_GetItem(result, 1);
87 ASSERT_NE(arg1, nullptr);
88 ASSERT_EQ(PyTuple_Check(arg1), 1);
89 EXPECT_EQ(PyTuple_Size(arg1), 0);
90}
91
92TEST_F(DescrExtensionApiTest, ClassMethodCallWithNoArgsRaisesTypeError) {
93 ASSERT_NO_FATAL_FAILURE(createEmptyBarType());
94 binaryfunc meth = [](PyObject*, PyObject*) {
95 ADD_FAILURE(); // unreachable
96 return Py_None;
97 };
98 static PyMethodDef method_def;
99 method_def = {"foo", meth, METH_VARARGS};
100 PyObjectPtr type(mainModuleGet("Bar"));
101 PyObjectPtr callable(PyDescr_NewClassMethod(
102 reinterpret_cast<PyTypeObject*>(type.get()), &method_def));
103 ASSERT_NE(callable, nullptr);
104
105 PyObjectPtr args(PyTuple_New(0));
106 PyObject* result = PyObject_CallObject(callable, args);
107 ASSERT_EQ(result, nullptr);
108 ASSERT_NE(PyErr_Occurred(), nullptr);
109 EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_TypeError));
110}
111
112TEST_F(DescrExtensionApiTest, ClassMethodCallWithNonBoundClassRaisesTypeError) {
113 ASSERT_NO_FATAL_FAILURE(createEmptyBarType());
114 binaryfunc meth = [](PyObject*, PyObject*) {
115 ADD_FAILURE(); // unreachable
116 return Py_None;
117 };
118 static PyMethodDef method_def;
119 method_def = {"foo", meth, METH_VARARGS};
120 PyObjectPtr type(mainModuleGet("Bar"));
121 PyObjectPtr callable(PyDescr_NewClassMethod(
122 reinterpret_cast<PyTypeObject*>(type.get()), &method_def));
123 ASSERT_NE(callable, nullptr);
124
125 PyObjectPtr args(PyTuple_New(1));
126 PyTuple_SetItem(args, 0, PyLong_FromLong(123));
127 PyObject* result = PyObject_CallObject(callable, args);
128 ASSERT_EQ(result, nullptr);
129 ASSERT_NE(PyErr_Occurred(), nullptr);
130 EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_TypeError));
131}
132
133TEST_F(DescrExtensionApiTest, DictProxyNewWithMappingReturnsMappingProxy) {
134 PyObjectPtr dict(PyDict_New());
135 PyObjectPtr key(PyLong_FromLong(10));
136 PyObjectPtr value(PyLong_FromLong(54321));
137 // Insert the value into the dictionary
138 ASSERT_EQ(PyDict_SetItem(dict, key, value), 0);
139
140 PyObjectPtr result(PyDictProxy_New(dict));
141 ASSERT_EQ(PyErr_Occurred(), nullptr);
142
143 // Verify that __getitem__ returns the result from the embedded mapping.
144 moduleSet("__main__", "foo", result);
145 PyRun_SimpleString("value_from_proxy = foo[10]");
146 ASSERT_EQ(PyErr_Occurred(), nullptr);
147 PyObjectPtr value_from_proxy(mainModuleGet("value_from_proxy"));
148 EXPECT_TRUE(PyLong_CheckExact(value_from_proxy));
149 EXPECT_EQ(PyLong_AsDouble(value_from_proxy), 54321.0);
150
151 // Verify that __setitem__ fails by raising TypeError.
152 PyRun_SimpleString(R"(
153type_error_caught = False
154try:
155 foo["random"] = 124134
156except TypeError:
157 type_error_caught = True
158)");
159 ASSERT_EQ(PyErr_Occurred(), nullptr);
160
161 PyObjectPtr type_error_caught(mainModuleGet("type_error_caught"));
162 EXPECT_EQ(type_error_caught, Py_True);
163}
164
165TEST_F(DescrExtensionApiTest, DictProxyNewWithNonMappingReturnsMappingProxy) {
166 PyObjectPtr non_mapping(PyTuple_New(1));
167 EXPECT_EQ(PyDictProxy_New(non_mapping), nullptr);
168 EXPECT_NE(PyErr_Occurred(), nullptr);
169}
170
171TEST_F(DescrExtensionApiTest, DescrGetSetCallsGetter) {
172 ASSERT_NO_FATAL_FAILURE(createEmptyBarType());
173 PyObject* self_arg = nullptr;
174 getter get = [](PyObject* self, void* closure) -> PyObject* {
175 PyObject** self_arg_ptr = reinterpret_cast<PyObject**>(closure);
176 Py_INCREF(self);
177 *self_arg_ptr = self;
178 Py_RETURN_NONE;
179 };
180 PyGetSetDef getset_def = {"foo", get, nullptr, nullptr, &self_arg};
181 PyObjectPtr type(mainModuleGet("Bar"));
182 PyObjectPtr descriptor(PyDescr_NewGetSet(type.asTypeObject(), &getset_def));
183 ASSERT_NE(descriptor, nullptr);
184 PyObject_SetAttrString(type, "foo", descriptor);
185 PyObjectPtr instance(PyObject_CallObject(type, nullptr));
186 ASSERT_NE(instance, nullptr);
187 EXPECT_EQ(self_arg, nullptr);
188 PyObjectPtr result(PyObject_GetAttrString(instance, "foo"));
189 EXPECT_EQ(result, Py_None);
190 EXPECT_EQ(self_arg, instance.get());
191 Py_XDECREF(self_arg);
192}
193
194TEST_F(DescrExtensionApiTest, DescrGetSetCallsGetterAndRaises) {
195 ASSERT_NO_FATAL_FAILURE(createEmptyBarType());
196 getter get = [](PyObject*, void*) -> PyObject* {
197 PyErr_SetString(PyExc_UserWarning, "test exception");
198 return nullptr;
199 };
200 PyGetSetDef getset_def = {"foo", get, nullptr, nullptr, nullptr};
201 PyObjectPtr type(mainModuleGet("Bar"));
202 PyObjectPtr descriptor(PyDescr_NewGetSet(type.asTypeObject(), &getset_def));
203 ASSERT_NE(descriptor, nullptr);
204 PyObject_SetAttrString(type, "foo", descriptor);
205 PyObjectPtr instance(PyObject_CallObject(type, nullptr));
206 ASSERT_NE(instance, nullptr);
207 PyObjectPtr result(PyObject_GetAttrString(instance, "foo"));
208 EXPECT_EQ(result.get(), nullptr);
209 ASSERT_NE(PyErr_Occurred(), nullptr);
210 EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_UserWarning));
211}
212
213TEST_F(DescrExtensionApiTest, DescrGetSetCallsSetter) {
214 ASSERT_NO_FATAL_FAILURE(createEmptyBarType());
215 struct Env {
216 PyObject* self_arg;
217 PyObject* value_arg;
218 };
219 setter set = [](PyObject* self, PyObject* value, void* closure) -> int {
220 Env* env = reinterpret_cast<Env*>(closure);
221 Py_INCREF(self);
222 env->self_arg = self;
223 Py_INCREF(value);
224 env->value_arg = value;
225 return 0;
226 };
227 Env env = {nullptr, nullptr};
228 PyGetSetDef getset_def = {"foo", nullptr, set, nullptr, &env};
229 PyObjectPtr type(mainModuleGet("Bar"));
230 PyObjectPtr descriptor(PyDescr_NewGetSet(type.asTypeObject(), &getset_def));
231 ASSERT_NE(descriptor, nullptr);
232 PyObject_SetAttrString(type, "foo", descriptor);
233 PyObjectPtr instance(PyObject_CallObject(type, nullptr));
234 ASSERT_NE(instance, nullptr);
235 EXPECT_EQ(env.self_arg, nullptr);
236 EXPECT_EQ(env.value_arg, nullptr);
237 PyObjectPtr value(PyLong_FromLong(42));
238 EXPECT_EQ(PyObject_SetAttrString(instance, "foo", value), 0);
239 EXPECT_EQ(env.self_arg, instance.get());
240 EXPECT_EQ(env.value_arg, value.get());
241 Py_XDECREF(env.self_arg);
242 Py_XDECREF(env.value_arg);
243}
244
245TEST_F(DescrExtensionApiTest, DescrGetSetCallsSetterAndRaises) {
246 ASSERT_NO_FATAL_FAILURE(createEmptyBarType());
247 setter set = [](PyObject*, PyObject*, void*) -> int {
248 PyErr_SetString(PyExc_UserWarning, "test exception");
249 return -1;
250 };
251 PyGetSetDef getset_def = {"foo", nullptr, set, nullptr, nullptr};
252 PyObjectPtr type(mainModuleGet("Bar"));
253 PyObjectPtr descriptor(PyDescr_NewGetSet(type.asTypeObject(), &getset_def));
254 ASSERT_NE(descriptor, nullptr);
255 PyObject_SetAttrString(type, "foo", descriptor);
256 PyObjectPtr instance(PyObject_CallObject(type, nullptr));
257 ASSERT_NE(instance, nullptr);
258 EXPECT_EQ(PyObject_SetAttrString(instance, "foo", Py_None), -1);
259 ASSERT_NE(PyErr_Occurred(), nullptr);
260 EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_UserWarning));
261}
262
263TEST_F(DescrExtensionApiTest, MethodAsDescriptorReturnsFunction) {
264 ASSERT_NO_FATAL_FAILURE(createEmptyBarType());
265 binaryfunc meth = [](PyObject* self, PyObject* args) {
266 return PyTuple_Pack(2, self, args);
267 };
268 static PyMethodDef method_def;
269 method_def = {"foo", meth, METH_VARARGS};
270 PyObjectPtr type(mainModuleGet("Bar"));
271 PyObjectPtr descriptor(PyDescr_NewMethod(
272 reinterpret_cast<PyTypeObject*>(type.get()), &method_def));
273 ASSERT_NE(descriptor, nullptr);
274 PyObject_SetAttrString(type, "foo", descriptor);
275
276 PyRun_SimpleString(R"(
277bar = Bar()
278r1 = bar.foo()
279)");
280 PyObjectPtr bar(mainModuleGet("bar"));
281 PyObjectPtr r1(mainModuleGet("r1"));
282 ASSERT_EQ(PyTuple_Check(r1), 1);
283 ASSERT_EQ(PyTuple_Size(r1), 2);
284
285 // self
286 PyObject* arg0 = PyTuple_GetItem(r1, 0);
287 ASSERT_NE(arg0, nullptr);
288 EXPECT_EQ(arg0, bar);
289
290 // args
291 PyObject* arg1 = PyTuple_GetItem(r1, 1);
292 ASSERT_NE(arg1, nullptr);
293 ASSERT_EQ(PyTuple_Check(arg1), 1);
294 EXPECT_EQ(PyTuple_Size(arg1), 0);
295}
296
297TEST_F(DescrExtensionApiTest, NameWithClassMethodReturnsName) {
298 ASSERT_NO_FATAL_FAILURE(createEmptyBarType());
299 binaryfunc meth = [](PyObject* self, PyObject* args) {
300 return PyTuple_Pack(2, self, args);
301 };
302 static PyMethodDef method_def;
303 method_def = {"foo", meth, METH_VARARGS};
304 PyObjectPtr type(mainModuleGet("Bar"));
305 PyObjectPtr descriptor(
306 PyDescr_NewClassMethod(type.asTypeObject(), &method_def));
307 PyObject* name = PyDescr_NAME(descriptor.get());
308 ASSERT_TRUE(isUnicodeEqualsCStr(name, "foo"));
309}
310
311TEST_F(DescrExtensionApiTest, NameWithGetSetReturnsName) {
312 ASSERT_NO_FATAL_FAILURE(createEmptyBarType());
313 getter get = [](PyObject*, void*) { return Py_None; };
314 setter set = [](PyObject*, PyObject*, void*) { return 0; };
315 static PyGetSetDef getset_def;
316 getset_def = {"foo", get, set, nullptr, nullptr};
317 PyObjectPtr type(mainModuleGet("Bar"));
318 PyObjectPtr descriptor(PyDescr_NewGetSet(type.asTypeObject(), &getset_def));
319 PyObject* name = PyDescr_NAME(descriptor.get());
320 ASSERT_TRUE(isUnicodeEqualsCStr(name, "foo"));
321}
322
323TEST_F(DescrExtensionApiTest, NameWithMethodReturnsName) {
324 ASSERT_NO_FATAL_FAILURE(createEmptyBarType());
325 binaryfunc meth = [](PyObject* self, PyObject* args) {
326 return PyTuple_Pack(2, self, args);
327 };
328 static PyMethodDef method_def;
329 method_def = {"foo", meth, METH_VARARGS};
330 PyObjectPtr type(mainModuleGet("Bar"));
331 PyObjectPtr descriptor(PyDescr_NewMethod(type.asTypeObject(), &method_def));
332 PyObject* name = PyDescr_NAME(descriptor.get());
333 ASSERT_TRUE(isUnicodeEqualsCStr(name, "foo"));
334}
335
336} // namespace testing
337} // namespace py