summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_cppext/extension.cpp
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@python.org>2023-08-22 18:30:18 (GMT)
committerGitHub <noreply@github.com>2023-08-22 18:30:18 (GMT)
commit21dda09600848ac280481f7c64f8d9516dc69bb2 (patch)
tree7f07e637dfad29e825fc4f10c1aa313b39ba3d0f /Lib/test/test_cppext/extension.cpp
parentc1c9cd61da1c5b6318da9f7055d45ec82a9949ba (diff)
downloadcpython-21dda09600848ac280481f7c64f8d9516dc69bb2.zip
cpython-21dda09600848ac280481f7c64f8d9516dc69bb2.tar.gz
cpython-21dda09600848ac280481f7c64f8d9516dc69bb2.tar.bz2
gh-108303: Add Lib/test/test_cppext/ sub-directory (#108325)
* Move test_cppext to its own directory * Rename setup_testcppext.py to setup.py * Rename _testcppext.cpp to extension.cpp * The source (extension.cpp) is now also copied by the test.
Diffstat (limited to 'Lib/test/test_cppext/extension.cpp')
-rw-r--r--Lib/test/test_cppext/extension.cpp260
1 files changed, 260 insertions, 0 deletions
diff --git a/Lib/test/test_cppext/extension.cpp b/Lib/test/test_cppext/extension.cpp
new file mode 100644
index 0000000..90669b1
--- /dev/null
+++ b/Lib/test/test_cppext/extension.cpp
@@ -0,0 +1,260 @@
+// gh-91321: Very basic C++ test extension to check that the Python C API is
+// compatible with C++ and does not emit C++ compiler warnings.
+//
+// The code is only built, not executed.
+
+// Always enable assertions
+#undef NDEBUG
+
+#include "Python.h"
+
+#if __cplusplus >= 201103
+# define NAME _testcpp11ext
+#else
+# define NAME _testcpp03ext
+#endif
+
+#define _STR(NAME) #NAME
+#define STR(NAME) _STR(NAME)
+
+PyDoc_STRVAR(_testcppext_add_doc,
+"add(x, y)\n"
+"\n"
+"Return the sum of two integers: x + y.");
+
+static PyObject *
+_testcppext_add(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ long i, j;
+ if (!PyArg_ParseTuple(args, "ll:foo", &i, &j)) {
+ return _Py_NULL;
+ }
+ long res = i + j;
+ return PyLong_FromLong(res);
+}
+
+
+// Class to test operator casting an object to PyObject*
+class StrongRef
+{
+public:
+ StrongRef(PyObject *obj) : m_obj(obj) {
+ Py_INCREF(this->m_obj);
+ }
+
+ ~StrongRef() {
+ Py_DECREF(this->m_obj);
+ }
+
+ // Cast to PyObject*: get a borrowed reference
+ inline operator PyObject*() const { return this->m_obj; }
+
+private:
+ PyObject *m_obj; // Strong reference
+};
+
+
+static PyObject *
+test_api_casts(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+ PyObject *obj = Py_BuildValue("(ii)", 1, 2);
+ if (obj == _Py_NULL) {
+ return _Py_NULL;
+ }
+ Py_ssize_t refcnt = Py_REFCNT(obj);
+ assert(refcnt >= 1);
+
+ // gh-92138: For backward compatibility, functions of Python C API accepts
+ // "const PyObject*". Check that using it does not emit C++ compiler
+ // warnings.
+ const PyObject *const_obj = obj;
+ Py_INCREF(const_obj);
+ Py_DECREF(const_obj);
+ PyTypeObject *type = Py_TYPE(const_obj);
+ assert(Py_REFCNT(const_obj) == refcnt);
+ assert(type == &PyTuple_Type);
+ assert(PyTuple_GET_SIZE(const_obj) == 2);
+ PyObject *one = PyTuple_GET_ITEM(const_obj, 0);
+ assert(PyLong_AsLong(one) == 1);
+
+ // gh-92898: StrongRef doesn't inherit from PyObject but has an operator to
+ // cast to PyObject*.
+ StrongRef strong_ref(obj);
+ assert(Py_TYPE(strong_ref) == &PyTuple_Type);
+ assert(Py_REFCNT(strong_ref) == (refcnt + 1));
+ Py_INCREF(strong_ref);
+ Py_DECREF(strong_ref);
+
+ // gh-93442: Pass 0 as NULL for PyObject*
+ Py_XINCREF(0);
+ Py_XDECREF(0);
+#if __cplusplus >= 201103
+ // Test nullptr passed as PyObject*
+ Py_XINCREF(nullptr);
+ Py_XDECREF(nullptr);
+#endif
+
+ Py_DECREF(obj);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *
+test_unicode(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+ PyObject *str = PyUnicode_FromString("abc");
+ if (str == _Py_NULL) {
+ return _Py_NULL;
+ }
+
+ assert(PyUnicode_Check(str));
+ assert(PyUnicode_GET_LENGTH(str) == 3);
+
+ // gh-92800: test PyUnicode_READ()
+ const void* data = PyUnicode_DATA(str);
+ assert(data != _Py_NULL);
+ int kind = PyUnicode_KIND(str);
+ assert(kind == PyUnicode_1BYTE_KIND);
+ assert(PyUnicode_READ(kind, data, 0) == 'a');
+
+ // gh-92800: test PyUnicode_READ() casts
+ const void* const_data = PyUnicode_DATA(str);
+ unsigned int ukind = static_cast<unsigned int>(kind);
+ assert(PyUnicode_READ(ukind, const_data, 2) == 'c');
+
+ assert(PyUnicode_READ_CHAR(str, 1) == 'b');
+
+ Py_DECREF(str);
+ Py_RETURN_NONE;
+}
+
+/* Test a `new`-allocated object with a virtual method.
+ * (https://github.com/python/cpython/issues/94731) */
+
+class VirtualPyObject : public PyObject {
+public:
+ VirtualPyObject();
+ virtual ~VirtualPyObject() {
+ delete [] internal_data;
+ --instance_count;
+ }
+ virtual void set_internal_data() {
+ internal_data[0] = 1;
+ }
+ static void dealloc(PyObject* o) {
+ delete static_cast<VirtualPyObject*>(o);
+ }
+
+ // Number of "living" instances
+ static int instance_count;
+private:
+ // buffer that can get corrupted
+ int* internal_data;
+};
+
+int VirtualPyObject::instance_count = 0;
+
+PyType_Slot VirtualPyObject_Slots[] = {
+ {Py_tp_free, (void*)VirtualPyObject::dealloc},
+ {0, _Py_NULL},
+};
+
+PyType_Spec VirtualPyObject_Spec = {
+ /* .name */ STR(NAME) ".VirtualPyObject",
+ /* .basicsize */ sizeof(VirtualPyObject),
+ /* .itemsize */ 0,
+ /* .flags */ Py_TPFLAGS_DEFAULT,
+ /* .slots */ VirtualPyObject_Slots,
+};
+
+VirtualPyObject::VirtualPyObject() {
+ // Create a temporary type (just so we don't need to store it)
+ PyObject *type = PyType_FromSpec(&VirtualPyObject_Spec);
+ // no good way to signal failure from a C++ constructor, so use assert
+ // for error handling
+ assert(type);
+ assert(PyObject_Init(this, (PyTypeObject *)type));
+ Py_DECREF(type);
+ internal_data = new int[50];
+ ++instance_count;
+}
+
+static PyObject *
+test_virtual_object(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+ VirtualPyObject* obj = new VirtualPyObject();
+ obj->set_internal_data();
+ Py_DECREF(obj);
+ if (VirtualPyObject::instance_count != 0) {
+ return PyErr_Format(
+ PyExc_AssertionError,
+ "instance_count should be 0, got %d",
+ VirtualPyObject::instance_count);
+ }
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef _testcppext_methods[] = {
+ {"add", _testcppext_add, METH_VARARGS, _testcppext_add_doc},
+ {"test_api_casts", test_api_casts, METH_NOARGS, _Py_NULL},
+ {"test_unicode", test_unicode, METH_NOARGS, _Py_NULL},
+ {"test_virtual_object", test_virtual_object, METH_NOARGS, _Py_NULL},
+ // Note: _testcppext_exec currently runs all test functions directly.
+ // When adding a new one, add a call there.
+
+ {_Py_NULL, _Py_NULL, 0, _Py_NULL} /* sentinel */
+};
+
+
+static int
+_testcppext_exec(PyObject *module)
+{
+ if (PyModule_AddIntMacro(module, __cplusplus) < 0) {
+ return -1;
+ }
+
+ PyObject *result;
+
+ result = PyObject_CallMethod(module, "test_api_casts", "");
+ if (!result) return -1;
+ Py_DECREF(result);
+
+ result = PyObject_CallMethod(module, "test_unicode", "");
+ if (!result) return -1;
+ Py_DECREF(result);
+
+ result = PyObject_CallMethod(module, "test_virtual_object", "");
+ if (!result) return -1;
+ Py_DECREF(result);
+
+ return 0;
+}
+
+static PyModuleDef_Slot _testcppext_slots[] = {
+ {Py_mod_exec, reinterpret_cast<void*>(_testcppext_exec)},
+ {0, _Py_NULL}
+};
+
+
+PyDoc_STRVAR(_testcppext_doc, "C++ test extension.");
+
+static struct PyModuleDef _testcppext_module = {
+ PyModuleDef_HEAD_INIT, // m_base
+ STR(NAME), // m_name
+ _testcppext_doc, // m_doc
+ 0, // m_size
+ _testcppext_methods, // m_methods
+ _testcppext_slots, // m_slots
+ _Py_NULL, // m_traverse
+ _Py_NULL, // m_clear
+ _Py_NULL, // m_free
+};
+
+#define _FUNC_NAME(NAME) PyInit_ ## NAME
+#define FUNC_NAME(NAME) _FUNC_NAME(NAME)
+
+PyMODINIT_FUNC
+FUNC_NAME(NAME)(void)
+{
+ return PyModuleDef_Init(&_testcppext_module);
+}