Skip to content

Commit 1ef44f4

Browse files
committed
gh-91321: Add basic _testcppext C++ extension
Build a basic C++ test extension to check that the Python C API is compatible with C++ and does not emit C++ compiler warnings. * Add Modules/_testcppext.cpp: C++ extension * Add Lib/test/test_cppext.py: test building the C++ extension.
1 parent 64113a4 commit 1ef44f4

File tree

2 files changed

+136
-0
lines changed

2 files changed

+136
-0
lines changed

Lib/test/_testcppext.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// gh-91321: Very basic C++ test extension to check that the Python C API is
2+
// compatible with C++ and does not emit C++ compiler warnings.
3+
4+
#include "Python.h"
5+
6+
PyDoc_STRVAR(_testcppext_add_doc,
7+
"add(x, y)\n"
8+
"\n"
9+
"Return the sum of two integers: x + y.");
10+
11+
static PyObject *
12+
_testcppext_add(PyObject *Py_UNUSED(module), PyObject *args)
13+
{
14+
long i, j;
15+
if (!PyArg_ParseTuple(args, "ll:foo", &i, &j)) {
16+
return nullptr;
17+
}
18+
long res = i + j;
19+
return PyLong_FromLong(res);
20+
}
21+
22+
23+
static PyMethodDef _testcppext_methods[] = {
24+
{"add", _testcppext_add, METH_VARARGS, _testcppext_add_doc},
25+
{nullptr, nullptr, 0, nullptr} /* sentinel */
26+
};
27+
28+
29+
static int
30+
_testcppext_exec(PyObject *module)
31+
{
32+
if (PyModule_AddIntMacro(module, __cplusplus) < 0) {
33+
return -1;
34+
}
35+
return 0;
36+
}
37+
38+
static PyModuleDef_Slot _testcppext_slots[] = {
39+
{Py_mod_exec, reinterpret_cast<void*>(_testcppext_exec)},
40+
{0, NULL}
41+
};
42+
43+
44+
PyDoc_STRVAR(_testcppext_doc, "C++ test extension.");
45+
46+
static struct PyModuleDef _testcppext_module = {
47+
PyModuleDef_HEAD_INIT, // m_base
48+
"_testcppext", // m_name
49+
_testcppext_doc, // m_doc
50+
0, // m_size
51+
_testcppext_methods, // m_methods
52+
_testcppext_slots, // m_slots
53+
NULL, // m_traverse
54+
NULL, // m_clear
55+
nullptr, // m_free
56+
};
57+
58+
PyMODINIT_FUNC
59+
PyInit__testcppext(void)
60+
{
61+
return PyModuleDef_Init(&_testcppext_module);
62+
}

Lib/test/test_cppext.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# gh-91321: Build a basic C++ test extension to check that the Python C API is
2+
# compatible with C++ and does not emit C++ compiler warnings.
3+
import os
4+
import sys
5+
import unittest
6+
import warnings
7+
from test import support
8+
from test.support import os_helper
9+
10+
with warnings.catch_warnings():
11+
warnings.simplefilter('ignore', DeprecationWarning)
12+
from distutils.core import setup, Extension
13+
# Import sysconfig here to make the DeprecationWarning quiet
14+
import distutils.sysconfig
15+
16+
17+
MS_WINDOWS = (sys.platform == 'win32')
18+
19+
20+
SOURCE = support.findfile('_testcppext.cpp')
21+
if not MS_WINDOWS:
22+
# C++ compiler flags for GCC and clang
23+
CPPFLAGS = [
24+
# Python currently targets C++11
25+
'-std=c++11',
26+
# gh-91321: The purpose of _testcppext extension is to check that building
27+
# a C++ extension using the Python C API does not emit C++ compiler
28+
# warnings
29+
'-Werror',
30+
]
31+
else:
32+
# Don't pass any compiler flag to MSVC
33+
CPPFLAGS = []
34+
35+
36+
class TestCPPExt(unittest.TestCase):
37+
def build(self):
38+
cpp_ext = Extension(
39+
'_testcppext',
40+
sources=[SOURCE],
41+
language='c++',
42+
extra_compile_args=CPPFLAGS)
43+
44+
try:
45+
try:
46+
with (support.captured_stdout() as stdout,
47+
support.swap_attr(sys, 'argv', ['setup.py', 'build_ext'])):
48+
setup(name="_testcppext", ext_modules=[cpp_ext])
49+
return
50+
except:
51+
# Show output on error
52+
print()
53+
print(stdout.getvalue())
54+
raise
55+
except SystemExit:
56+
self.fail("Build failed")
57+
58+
# With MSVC, the linker fails with: cannot open file 'python311.lib'
59+
# https://github.com/python/cpython/pull/32175#issuecomment-1111175897
60+
@unittest.skipIf(MS_WINDOWS, 'test fails on Windows')
61+
def test_build(self):
62+
# save/restore os.environ
63+
def restore_env(old_env):
64+
os.environ.clear()
65+
os.environ.update(old_env)
66+
self.addCleanup(restore_env, dict(os.environ))
67+
68+
# Build in a temporary directory
69+
with os_helper.temp_cwd():
70+
self.build()
71+
72+
73+
if __name__ == "__main__":
74+
unittest.main()

0 commit comments

Comments
 (0)