--- /dev/null
+# gh-116869: Build a basic C test extension to check that the Python C API
+# does not emit C compiler warnings.
+
+import os.path
+import shutil
+import subprocess
+import sysconfig
+import unittest
+from test import support
+
+
+SOURCE = os.path.join(os.path.dirname(__file__), 'extension.c')
+SETUP = os.path.join(os.path.dirname(__file__), 'setup.py')
+
+
+# gh-110119: pip does not currently support 't' in the ABI flag use by
+# --disable-gil builds. Once it does, we can remove this skip.
+@unittest.skipIf(support.Py_GIL_DISABLED,
+ 'test does not work with --disable-gil')
+@support.requires_subprocess()
+@support.requires_resource('cpu')
+class TestExt(unittest.TestCase):
+ def test_build_c99(self):
+ self.check_build('c99', '_test_c99_ext')
+
+ def test_build_c11(self):
+ self.check_build('c11', '_test_c11_ext')
+
+ # With MSVC, the linker fails with: cannot open file 'python311.lib'
+ # https://github.com/python/cpython/pull/32175#issuecomment-1111175897
+ @unittest.skipIf(support.MS_WINDOWS, 'test fails on Windows')
+ # Building and running an extension in clang sanitizing mode is not
+ # straightforward
+ @unittest.skipIf(
+ '-fsanitize' in (sysconfig.get_config_var('PY_CFLAGS') or ''),
+ 'test does not work with analyzing builds')
+ # the test uses venv+pip: skip if it's not available
+ @support.requires_venv_with_pip()
+ def check_build(self, clang_std, extension_name):
+ venv_dir = 'env'
+ with support.setup_venv_with_pip_setuptools_wheel(venv_dir) as python_exe:
+ self._check_build(clang_std, extension_name, python_exe)
+
+ def _check_build(self, clang_std, extension_name, python_exe):
+ pkg_dir = 'pkg'
+ os.mkdir(pkg_dir)
+ shutil.copy(SETUP, os.path.join(pkg_dir, os.path.basename(SETUP)))
+ shutil.copy(SOURCE, os.path.join(pkg_dir, os.path.basename(SOURCE)))
+
+ def run_cmd(operation, cmd):
+ env = os.environ.copy()
+ env['CPYTHON_TEST_STD'] = clang_std
+ env['CPYTHON_TEST_EXT_NAME'] = extension_name
+ if support.verbose:
+ print('Run:', ' '.join(cmd))
+ subprocess.run(cmd, check=True, env=env)
+ else:
+ proc = subprocess.run(cmd,
+ env=env,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ text=True)
+ if proc.returncode:
+ print(proc.stdout, end='')
+ self.fail(
+ f"{operation} failed with exit code {proc.returncode}")
+
+ # Build and install the C extension
+ cmd = [python_exe, '-X', 'dev',
+ '-m', 'pip', 'install', '--no-build-isolation',
+ os.path.abspath(pkg_dir)]
+ run_cmd('Install', cmd)
+
+ # Do a reference run. Until we test that running python
+ # doesn't leak references (gh-94755), run it so one can manually check
+ # -X showrefcount results against this baseline.
+ cmd = [python_exe,
+ '-X', 'dev',
+ '-X', 'showrefcount',
+ '-c', 'pass']
+ run_cmd('Reference run', cmd)
+
+ # Import the C extension
+ cmd = [python_exe,
+ '-X', 'dev',
+ '-X', 'showrefcount',
+ '-c', f"import {extension_name}"]
+ run_cmd('Import', cmd)
+
+
+if __name__ == "__main__":
+ unittest.main()
--- /dev/null
+// gh-116869: Basic C test extension to check that the Python C API
+// does not emit C compiler warnings.
+
+// Always enable assertions
+#undef NDEBUG
+
+#include "Python.h"
+
+#if defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L
+# define NAME _test_c2x_ext
+#elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
+# define NAME _test_c11_ext
+#else
+# define NAME _test_c99_ext
+#endif
+
+#define _STR(NAME) #NAME
+#define STR(NAME) _STR(NAME)
+
+PyDoc_STRVAR(_testcext_add_doc,
+"add(x, y)\n"
+"\n"
+"Return the sum of two integers: x + y.");
+
+static PyObject *
+_testcext_add(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ long i, j;
+ if (!PyArg_ParseTuple(args, "ll:foo", &i, &j)) {
+ return NULL;
+ }
+ long res = i + j;
+ return PyLong_FromLong(res);
+}
+
+
+static PyMethodDef _testcext_methods[] = {
+ {"add", _testcext_add, METH_VARARGS, _testcext_add_doc},
+ {NULL, NULL, 0, NULL} // sentinel
+};
+
+
+static int
+_testcext_exec(PyObject *module)
+{
+ if (PyModule_AddIntMacro(module, __STDC_VERSION__) < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+static PyModuleDef_Slot _testcext_slots[] = {
+ {Py_mod_exec, _testcext_exec},
+ {0, NULL}
+};
+
+
+PyDoc_STRVAR(_testcext_doc, "C test extension.");
+
+static struct PyModuleDef _testcext_module = {
+ PyModuleDef_HEAD_INIT, // m_base
+ STR(NAME), // m_name
+ _testcext_doc, // m_doc
+ 0, // m_size
+ _testcext_methods, // m_methods
+ _testcext_slots, // m_slots
+ NULL, // m_traverse
+ NULL, // m_clear
+ 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(&_testcext_module);
+}
--- /dev/null
+# gh-91321: Build a basic C test extension to check that the Python C API is
+# compatible with C and does not emit C compiler warnings.
+import os
+import shlex
+import sys
+import sysconfig
+from test import support
+
+from setuptools import setup, Extension
+
+
+SOURCE = 'extension.c'
+if not support.MS_WINDOWS:
+ # C compiler flags for GCC and clang
+ CFLAGS = [
+ # The purpose of test_cext extension is to check that building a C
+ # extension using the Python C API does not emit C compiler warnings.
+ '-Werror',
+ ]
+else:
+ # Don't pass any compiler flag to MSVC
+ CFLAGS = []
+
+
+def main():
+ std = os.environ["CPYTHON_TEST_STD"]
+ name = os.environ["CPYTHON_TEST_EXT_NAME"]
+ cflags = [*CFLAGS, f'-std={std}']
+
+ # Remove existing -std options to only test ours
+ cmd = (sysconfig.get_config_var('CC') or '')
+ if cmd is not None:
+ cmd = shlex.split(cmd)
+ cmd = [arg for arg in cmd if not arg.startswith('-std=')]
+ cmd = shlex.join(cmd)
+ # CC env var overrides sysconfig CC variable in setuptools
+ os.environ['CC'] = cmd
+
+ ext = Extension(
+ name,
+ sources=[SOURCE],
+ extra_compile_args=cflags)
+ setup(name='internal' + name, version='0.0', ext_modules=[ext])
+
+
+if __name__ == "__main__":
+ main()