Skip to content

Commit 79886e7

Browse files
authored
gh-91321: Add _testcppext C++ extension (#32175)
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 18b07d7 commit 79886e7

File tree

2 files changed

+141
-0
lines changed

2 files changed

+141
-0
lines changed

Lib/test/_testcppext.cpp

+62
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

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

0 commit comments

Comments
 (0)