this repo has no description
at trunk 337 lines 12 kB view raw
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