Skip to content

gh-116869: Add test_cext test: build a C extension #116954

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions Lib/test/test_cext/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# 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')
Comment on lines +16 to +19
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should work now that #116878 landed!

(The comment was a bit inaccurate: it turns out the issue was in wheel, not pip)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's great! I will write a follow-up to patch also test_cppext

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #116973

@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()
80 changes: 80 additions & 0 deletions Lib/test/test_cext/extension.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// 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);
}
47 changes: 47 additions & 0 deletions Lib/test/test_cext/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# 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()
1 change: 1 addition & 0 deletions Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -2367,6 +2367,7 @@ TESTSUBDIRS= idlelib/idle_test \
test/support/interpreters \
test/test_asyncio \
test/test_capi \
test/test_cext \
test/test_concurrent_futures \
test/test_cppext \
test/test_ctypes \
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add ``test_cext`` test: build a C extension to check if the Python C API
emits C compiler warnings. Patch by Victor Stinner.