8000 gh-116869: Add test_cext test: build a C extension (#116954) · diegorusso/cpython@c9450e1 · GitHub
[go: up one dir, main page]

Skip to content

Commit c9450e1

Browse files
vstinnerdiegorusso
authored andcommitted
pythongh-116869: Add test_cext test: build a C extension (python#116954)
1 parent de674c6 commit c9450e1

File tree

5 files changed

+222
-0
lines changed

5 files changed

+222
-0
lines changed

Lib/test/test_cext/__init__.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# gh-116869: Build a basic C test extension to check that the Python C API
2+
# does not emit C compiler warnings.
3+
4+
import os.path
5+
import shutil
6+
import subprocess
7+
import sysconfig
8+
import unittest
9+
from test import support
10+
11+
12+
SOURCE = os.path.join(os.path.dirname(__file__), 'extension.c')
13+
SETUP = os.path.join(os.path.dirname(__file__), 'setup.py')
14+
15+
16+
# gh-110119: pip does not 10000 currently support 't' in the ABI flag use by
17+
# --disable-gil builds. Once it does, we can remove this skip.
18+
@unittest.skipIf(support.Py_GIL_DISABLED,
19+
'test does not work with --disable-gil')
20+
@support.requires_subprocess()
21+
@support.requires_resource('cpu')
22+
class TestExt(unittest.TestCase):
23+
def test_build_c99(self):
24+
self.check_build('c99', '_test_c99_ext')
25+
26+
def test_build_c11(self):
27+
self.check_build('c11', '_test_c11_ext')
28+
29+
# With MSVC, the linker fails with: cannot open file 'python311.lib'
30+
# https://github.com/python/cpython/pull/32175#issuecomment-1111175897
31+
@unittest.skipIf(support.MS_WINDOWS, 'test fails on Windows')
32+
# Building and running an extension in clang sanitizing mode is not
33+
# straightforward
34+
@unittest.skipIf(
35+
'-fsanitize' in (sysconfig.get_config_var('PY_CFLAGS') or ''),
36+
'test does not work with analyzing builds')
37+
# the test uses venv+pip: skip if it's not available
38+
@support.requires_venv_with_pip()
39+
def check_build(self, clang_std, extension_name):
40+
venv_dir = 'env'
41+
with support.setup_venv_with_pip_setuptools_wheel(venv_dir) as python_exe:
42+
self._check_build(clang_std, extension_name, python_exe)
43+
44+
def _check_build(self, clang_std, extension_name, python_exe):
45+
pkg_dir = 'pkg'
46+
os.mkdir(pkg_dir)
47+
shutil.copy(SETUP, os.path.join(pkg_dir, os.path.basename(SETUP)))
48+
shutil.copy(SOURCE, os.path.join(pkg_dir, os.path.basename(SOURCE)))
49+
50+
def run_cmd(operation, cmd):
51+
env = os.environ.copy()
52+
env['CPYTHON_TEST_STD'] = clang_std
53+
env['CPYTHON_TEST_EXT_NAME'] = extension_name
54+
if support.verbose:
55+
print('Run:', ' '.join(cmd))
56+
subprocess.run(cmd, check=True, env=env)
57+
else:
58+
proc = subprocess.run(cmd,
59+
env=env,
60+
stdout=subprocess.PIPE,
61+
stderr=subprocess.STDOUT,
62+
text=True)
63+
if proc.returncode:
64+
print(proc.stdout, end='')
65+
self.fail(
66+
f"{operation} failed with exit code {proc.returncode}")
67+
68+
# Build and install the C extension
69+
cmd = [python_exe, '-X', 'dev',
70+
'-m', 'pip', 'install', '--no-build-isolation',
71+
os.path.abspath(pkg_dir)]
72+
run_cmd('Install', cmd)
73+
74+
# Do a reference run. Until we test that running python
75+
# doesn't leak references (gh-94755), run it so one can manually check
76+
# -X showrefcount results against this baseline.
77+
cmd = [python_exe,
78+
'-X', 'dev',
79+
'-X', 'showrefcount',
80+
'-c', 'pass']
81+
run_cmd('Reference run', cmd)
82+
83+
# Import the C extension
84+
cmd = [python_exe,
85+
'-X', 'dev',
86+
'-X', 'showrefcount',
87+
'-c', f"import {extension_name}"]
88+
run_cmd('Import', cmd)
89+
90+
91+
if __name__ == "__main__":
92+
unittest.main()

Lib/test/test_cext/extension.c

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// gh-116869: Basic C test extension to check that the Python C API
2+
// does not emit C compiler warnings.
3+
4+
// Always enable assertions
5+
#undef NDEBUG
6+
7+
#include "Python.h"
8+
9+
#if defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L
10+
# define NAME _test_c2x_ext
11+
#elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
12+
# define NAME _test_c11_ext
13+
#else
14+
# define NAME _test_c99_ext
15+
#endif
16+
17+
#define _STR(NAME) #NAME
18+
#define STR(NAME) _STR(NAME)
19+
20+
PyDoc_STRVAR(_testcext_add_doc,
21+
"add(x, y)\n"
22+
"\n"
23+
"Return the sum of two integers: x + y.");
24+
25+
static PyObject *
26+
_testcext_add(PyObject *Py_UNUSED(module), PyObject *args)
27+
{
28+
long i, j;
29+
if (!PyArg_ParseTuple(args, "ll:foo", &i, &j)) {
30+
return NULL;
31+
}
32+
long res = i + j;
33+
return PyLong_FromLong(res);
34+
}
35+
36+
37+
static PyMethodDef _testcext_methods[] = {
38+
{"add", _testcext_add, METH_VARARGS, _testcext_add_doc},
39+
{NULL, NULL, 0, NULL} // sentinel
40+
};
41+
42+
43+
static int
44+
_testcext_exec(PyObject *module)
45+
{
46+
if (PyModule_AddIntMacro(module, __STDC_VERSION__) < 0) {
47+
return -1;
48+
}
49+
return 0;
50+
}
51+
52+
static PyModuleDef_Slot _testcext_slots[] = {
53+
{Py_mod_exec, _testcext_exec},
54+
{0, NULL}
55+
};
56+
57+
58+
PyDoc_STRVAR(_testcext_doc, "C test extension.");
59+
60+
static struct PyModuleDef _testcext_module = {
61+
PyModuleDef_HEAD_INIT, // m_base
62+
STR(NAME), // m_name
63+
_testcext_doc, // m_doc
64+
0, // m_size
65+
_testcext_methods, // m_methods
66+
_testcext_slots, // m_slots
67+
NULL, // m_traverse
68+
NULL, // m_clear
69+
NULL, // m_free
70+
};
71+
72+
73+
#define _FUNC_NAME(NAME) PyInit_ ## NAME
74+
#define FUNC_NAME(NAME) _FUNC_NAME(NAME)
75+
76+
PyMODINIT_FUNC
77+
FUNC_NAME(NAME)(void)
78+
{
79+
return PyModuleDef_Init(&_testcext_module);
80+
}

Lib/test/test_cext/setup.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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 shlex
5+
import sys
6+
import sysconfig
7+
from test import support
8+
9+
from setuptools import setup, Extension
10+
11+
12+
SOURCE = 'extension.c'
13+
if not support.MS_WINDOWS:
14+
# C compiler flags for GCC and clang
15+
CFLAGS = [
16+
# The purpose of test_cext extension is to check that building a C
17+
# extension using the Python C API does not emit C compiler warnings.
18+
'-Werror',
19+
]
20+
else:
21+
# Don't pass any compiler flag to MSVC
22+
CFLAGS = []
23+
24+
25+
def main():
26+
std = os.environ["CPYTHON_TEST_STD"]
27+
name = os.environ["CPYTHON_TEST_EXT_NAME"]
28+
cflags = [*CFLAGS, f'-std={std}']
29+
30+
# Remove existing -std options to only test ours
31+
cmd = (sysconfig.get_config_var('CC') or '')
32+
if cmd is not None:
33+
cmd = shlex.split(cmd)
34+
cmd = [arg for arg in cmd if not arg.startswith('-std=')]
35+
cmd = shlex.join(cmd)
36+
# CC env var overrides sysconfig CC variable in setuptools
37+
os.environ['CC'] = cmd
38+
39+
ext = Extension(
40+
name,
41+
sources=[SOURCE],
42+
extra_compile_args=cflags)
43+
setup(name='internal' + name, version='0.0', ext_modules=[ext])
44+
45+
46+
if __name__ == "__main__":
47+
main()

Makefile.pre.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2330,6 +2330,7 @@ TESTSUBDIRS= idlelib/idle_test \
23302330
test/support/interpreters \
23312331
test/test_asyncio \
23322332
test/test_capi \
2333+
test/test_cext \
23332334
test/test_concurrent_futures \
23342335
test/test_cppext \
23352336
test/test_ctypes \
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add ``test_cext`` test: build a C extension to check if the Python C API
2+
emits C compiler warnings. Patch by Victor Stinner.

0 commit comments

Comments
 (0)
0