diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index a6099cda28f07c..1f0753b9839cdd 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -917,18 +917,17 @@ def collect_support_threading_helper(info_add): copy_attributes(info_add, threading_helper, 'support_threading_helper.%s', attributes) -def collect_cc(info_add): +def get_compiler_version(sysconfig_var): import sysconfig - - CC = sysconfig.get_config_var('CC') - if not CC: + program = sysconfig.get_config_var(sysconfig_var) + if not program: return try: import shlex - args = shlex.split(CC) + args = shlex.split(program) except ImportError: - args = CC.split() + args = program.split() args.append('--version') stdout = run_command(args) @@ -942,7 +941,21 @@ def collect_cc(info_add): text = first_line(stdout) text = normalize_text(text) - info_add('CC.version', text) + if text: + text = f'[{program}] {text}' + return text + + +def collect_cc(info_add): + # C compiler + version = get_compiler_version('CC') + if version: + info_add('CC.version', version) + + # C++ compiler + version = get_compiler_version('CXX') + if version: + info_add('CXX.version', version) def collect_gdbm(info_add): diff --git a/Lib/test/test_cext/__init__.py b/Lib/test/test_cext/__init__.py index dfc7d230426cf8..457925413c94ec 100644 --- a/Lib/test/test_cext/__init__.py +++ b/Lib/test/test_cext/__init__.py @@ -1,28 +1,37 @@ -# gh-116869: Build a basic C test extension to check that the Python C API -# does not emit C compiler warnings. +# gh-116869: Build a C/C++ test extension to check that the Python C API does +# not emit compiler warnings. # # The Python C API must be compatible with building # with the -Werror=declaration-after-statement compiler flag. import os.path +import platform import shlex import shutil import subprocess -import sysconfig import sys +import sysconfig import unittest from test import support +from test.support import os_helper + + +if not support.has_subprocess_support: + raise unittest.SkipTest("requires subprocess support") +SOURCE_DIR = os.path.dirname(__file__) SOURCES = [ - os.path.join(os.path.dirname(__file__), 'extension.c'), + os.path.join(SOURCE_DIR, 'extension.c'), + os.path.join(SOURCE_DIR, 'extension.cpp'), + os.path.join(SOURCE_DIR, 'setup.py'), ] -SETUP = os.path.join(os.path.dirname(__file__), 'setup.py') +MSVC = support.MS_WINDOWS # With MSVC on a debug build, the linker fails with: cannot open file # 'python311.lib', it should look 'python311_d.lib'. -@unittest.skipIf(support.MS_WINDOWS and support.Py_DEBUG, +@unittest.skipIf(MSVC and support.Py_DEBUG, 'test fails on Windows debug build') # Building and running an extension in clang sanitizing mode is not # straightforward @@ -34,41 +43,38 @@ @support.requires_resource('cpu') class BaseTests: TEST_INTERNAL_C_API = False - - # Default build with no options - def test_build(self): - self.check_build('_test_cext') + LANGUAGE = None def check_build(self, extension_name, std=None, limited=False, - abi3t=False): - venv_dir = 'env' - with support.setup_venv_with_pip_setuptools(venv_dir) as python_exe: - self._check_build(extension_name, python_exe, - std=std, limited=limited, - abi3t=abi3t) - - def _check_build(self, extension_name, python_exe, std, limited, - abi3t): + abi3t=False, extra_cflags=None): + if self.LANGUAGE == 'C++' and not std and sys.platform == 'darwin': + # Old Apple clang++ default C++ std is gnu++98, use C++11 instead + std = 'c++11' + pkg_dir = 'pkg' os.mkdir(pkg_dir) - shutil.copy(SETUP, os.path.join(pkg_dir, os.path.basename(SETUP))) + self.addCleanup(os_helper.rmtree, pkg_dir) + for source in SOURCES: dest = os.path.join(pkg_dir, os.path.basename(source)) shutil.copy(source, dest) def run_cmd(operation, cmd): env = os.environ.copy() + env['CPYTHON_TEST_EXT_NAME'] = extension_name + env['CPYTHON_TEST_LANG'] = self.LANGUAGE if std: env['CPYTHON_TEST_STD'] = std if limited: env['CPYTHON_TEST_LIMITED'] = '1' if abi3t: env['CPYTHON_TEST_ABI3T'] = '1' - if support.MS_WINDOWS and sysconfig.is_python_build(): - env['CPYTHON_EXTRA_INCDIRS'] = os.path.split(sysconfig.get_config_h_filename())[0] - env['CPYTHON_EXTRA_LIBDIRS'] = os.path.split(sys.executable)[0] - env['CPYTHON_TEST_EXT_NAME'] = extension_name - env['TEST_INTERNAL_C_API'] = str(int(self.TEST_INTERNAL_C_API)) + if MSVC and sysconfig.is_python_build(): + env['CPYTHON_TEST_EXTRA_INCDIRS'] = os.path.split(sysconfig.get_config_h_filename())[0] + env['CPYTHON_TEST_EXTRA_LIBDIRS'] = os.path.split(sys.executable)[0] + env['CPYTHON_TEST_INTERNAL_C_API'] = str(int(self.TEST_INTERNAL_C_API)) + if extra_cflags: + env['CPYTHON_TEST_EXTRA_CFLAGS'] = extra_cflags if support.verbose: print('Run:', ' '.join(map(shlex.quote, cmd))) subprocess.run(cmd, check=True, env=env) @@ -85,6 +91,7 @@ def run_cmd(operation, cmd): f"{operation} failed with exit code {proc.returncode}") # Build and install the C extension + python_exe = PYTHON_EXE cmd = [python_exe, '-X', 'dev', '-m', 'pip', 'install', '--no-build-isolation', os.path.abspath(pkg_dir)] @@ -101,7 +108,7 @@ def run_cmd(operation, cmd): '-c', 'pass'] run_cmd('Reference run', cmd) - # Import the C extension + # Import the C/C++ extension cmd = [python_exe, '-X', 'dev', '-X', 'showrefcount', @@ -109,31 +116,101 @@ def run_cmd(operation, cmd): run_cmd('Import', cmd) -class TestPublicCAPI(BaseTests, unittest.TestCase): +class TestPublicC(BaseTests, unittest.TestCase): + LANGUAGE = 'C' + + # Default build with no options + def test_build(self): + self.check_build('_test_cext') + + @unittest.skipIf(MSVC, "MSVC doesn't support /std:c99") + def test_build_c99(self): + # In public docs, we say C API is compatible with C11. However, + # in practice we do maintain C99 compatibility in public headers. + # Please ask the C API WG before adding a new C11-only feature. + self.check_build('_test_cext_c99', std='c99') + + def test_build_c11(self): + self.check_build('_test_cext_c11', std='c11') + def test_build_limited(self): - self.check_build('_test_limited_cext', limited=True) + self.check_build('_test_cext_limited', limited=True) def test_build_limited_c11(self): - self.check_build('_test_limited_c11_cext', limited=True, std='c11') + self.check_build('_test_cext_limited_c11', limited=True, std='c11') - def test_build_c11(self): - self.check_build('_test_c11_cext', std='c11') + def test_build_abi3t(self): + # Test with Py_TARGET_ABI3T + self.check_build('_test_cext_abi3t', abi3t=True) + + +class TestPublicCpp(BaseTests, unittest.TestCase): + LANGUAGE = 'C++' + + def test_build(self): + self.check_build('_test_cppext') + + def test_build_cpp03(self): + # In public docs, we say C API is compatible with C++11. However, + # in practice we do maintain C++03 compatibility in public headers. + # Please ask the C API WG before adding a new C++11-only feature. + self.check_build('_test_cppext_cpp03', std='c++03') + + @unittest.skipIf(MSVC, "MSVC doesn't support /std:c++11") + def test_build_cpp11(self): + self.check_build('_test_cppext_cpp11', std='c++11') + + # Only test C++14 on MSVC. + # On s390x RHEL7, GCC 4.8.5 doesn't support C++14. + @unittest.skipIf(not MSVC, "need MSVC") + def test_build_cpp14(self): + self.check_build('_test_cppext_cpp14', std='c++14') + + # Test that headers compile with Intel asm syntax, which may conflict + # with inline assembly in free-threading headers that use AT&T syntax. + @unittest.skipIf(MSVC, "MSVC doesn't support -masm=intel") + @unittest.skipUnless(platform.machine() in ('x86_64', 'i686', 'AMD64'), + "x86-specific flag") + def test_build_intel_asm(self): + self.check_build('_test_cppext_intel_asm', extra_cflags='-masm=intel') + + def test_build_limited(self): + self.check_build('_test_cppext_limited', limited=True) + + def test_build_limited_cpp03(self): + self.check_build('_test_cppext_limited_cpp03', std='c++03', limited=True) def test_build_abi3t(self): # Test with Py_TARGET_ABI3T - self.check_build('_test_abi3t', abi3t=True) + self.check_build('_test_cppext_abi3t', abi3t=True) - @unittest.skipIf(support.MS_WINDOWS, "MSVC doesn't support /std:c99") - def test_build_c99(self): - # In public docs, we say C API is compatible with C11. However, - # in practice we do maintain C99 compatibility in public headers. - # Please ask the C API WG before adding a new C11-only feature. - self.check_build('_test_c99_cext', std='c99') + +class TestInteralC(BaseTests, unittest.TestCase): + LANGUAGE = 'C' + TEST_INTERNAL_C_API = True + + # Default build with no options + def test_build(self): + self.check_build('_test_cext_internal') -class TestInteralCAPI(BaseTests, unittest.TestCase): +class TestInteralCpp(BaseTests, unittest.TestCase): + LANGUAGE = 'C++' TEST_INTERNAL_C_API = True + def test_build(self): + self.check_build('_test_cppext_internal') + + +def setUpModule(): + global VENV_CONTEXT, PYTHON_EXE + VENV_CONTEXT = support.setup_venv_with_pip_setuptools('env') + PYTHON_EXE = VENV_CONTEXT.__enter__() + + +def tearDownModule(): + VENV_CONTEXT.__exit__(None, None, None) + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_cext/extension.c b/Lib/test/test_cext/extension.c index 0a26a0d8753711..038f1a2af46c67 100644 --- a/Lib/test/test_cext/extension.c +++ b/Lib/test/test_cext/extension.c @@ -1,7 +1,10 @@ -// gh-116869: Basic C test extension to check that the Python C API -// does not emit C compiler warnings. +// gh-116869: C/C++ extension module used to check that the Python C API does +// not emit compiler warnings. // // Test also the internal C API if the TEST_INTERNAL_C_API macro is defined. +// +// Declare variables at the top of each function to support building with +// -Werror=declaration-after-statement. // Always enable assertions #undef NDEBUG @@ -13,21 +16,42 @@ #include "Python.h" #include "datetime.h" -#ifdef TEST_INTERNAL_C_API - // gh-135906: Check for compiler warnings in the internal C API. - // - Cython uses pycore_critical_section.h, pycore_frame.h and - // pycore_template.h. - // - greenlet uses pycore_frame.h, pycore_interpframe_structs.h and - // pycore_interpframe.h. -# include "internal/pycore_critical_section.h" -# include "internal/pycore_frame.h" -# include "internal/pycore_gc.h" -# include "internal/pycore_interp.h" -# include "internal/pycore_interpframe.h" -# include "internal/pycore_interpframe_structs.h" -# include "internal/pycore_object.h" -# include "internal/pycore_pystate.h" -# include "internal/pycore_template.h" +#ifdef __cplusplus +# ifdef TEST_INTERNAL_C_API + // gh-135906: Check for compiler warnings in the internal C API + // - Cython uses pycore_critical_section.h, pycore_frame.h and + // pycore_template.h. + // - greenlet uses pycore_frame.h, pycore_interpframe_structs.h and + // pycore_interpframe.h. +# include "internal/pycore_frame.h" +# include "internal/pycore_interpframe_structs.h" +# include "internal/pycore_template.h" + + // mimalloc emits compiler warnings on Windows. +# if !defined(MS_WINDOWS) +# include "internal/pycore_backoff.h" +# include "internal/pycore_cell.h" +# include "internal/pycore_critical_section.h" +# include "internal/pycore_interpframe.h" +# endif +# endif +#else +# ifdef TEST_INTERNAL_C_API + // gh-135906: Check for compiler warnings in the internal C API. + // - Cython uses pycore_critical_section.h, pycore_frame.h and + // pycore_template.h. + // - greenlet uses pycore_frame.h, pycore_interpframe_structs.h and + // pycore_interpframe.h. +# include "internal/pycore_critical_section.h" +# include "internal/pycore_frame.h" +# include "internal/pycore_gc.h" +# include "internal/pycore_interp.h" +# include "internal/pycore_interpframe.h" +# include "internal/pycore_interpframe_structs.h" +# include "internal/pycore_object.h" +# include "internal/pycore_pystate.h" +# include "internal/pycore_template.h" +# endif #endif #ifndef MODULE_NAME @@ -37,13 +61,13 @@ #define _STR(NAME) #NAME #define STR(NAME) _STR(NAME) -PyDoc_STRVAR(_testcext_add_doc, +PyDoc_STRVAR(test_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) +test_add(PyObject *Py_UNUSED(module), PyObject *args) { long i, j, res; if (!PyArg_ParseTuple(args, "ll:foo", &i, &j)) { @@ -54,6 +78,50 @@ _testcext_add(PyObject *Py_UNUSED(module), PyObject *args) } +static PyObject * +test_macros(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + PyObject *obj, *dict; + + // test Py_BUILD_ASSERT() and Py_BUILD_ASSERT_EXPR() + Py_BUILD_ASSERT(sizeof(int) == sizeof(unsigned int)); + assert(Py_BUILD_ASSERT_EXPR(sizeof(int) == sizeof(unsigned int)) == 0); + + // Test Py_MIN(), Py_MAX(), Py_ABS() + assert(Py_MIN(5, 11) == 5); + assert(Py_MAX(5, 11) == 11); + assert(Py_ABS(-5) == 5); + + // Test Py_CLEAR(): use typeof()/__typeof__() if available, or memcpy() + obj = Py_None; + Py_CLEAR(obj); + assert(obj == _Py_NULL); + +#ifndef Py_LIMITED_API + // Test Py_SETREF(): use typeof()/__typeof__() if available, or memcpy() + obj = Py_None; + Py_SETREF(obj, _Py_NULL); + assert(obj == _Py_NULL); + + // Test Py_XSETREF(): use typeof()/__typeof__() if available, or memcpy() + obj = Py_None; + Py_XSETREF(obj, _Py_NULL); + assert(obj == _Py_NULL); +#endif + + // Test that Py_BEGIN_CRITICAL_SECTION is available + dict = PyDict_New(); + if (dict == NULL) { + return NULL; + } + Py_BEGIN_CRITICAL_SECTION(dict); + Py_END_CRITICAL_SECTION(); + Py_DECREF(dict); + + Py_RETURN_NONE; +} + + static PyObject * test_datetime(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) { @@ -69,52 +137,261 @@ test_datetime(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) } -static PyMethodDef _testcext_methods[] = { - {"add", _testcext_add, METH_VARARGS, _testcext_add_doc}, - {"test_datetime", test_datetime, METH_NOARGS, NULL}, - {NULL, NULL, 0, NULL} // sentinel +static PyObject * +test_unicode(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + PyObject *str; +#ifndef Py_LIMITED_API + const void* data; + int kind; + const void* const_data; + unsigned int ukind; +#endif + + str = PyUnicode_FromString("abc"); + if (str == _Py_NULL) { + return _Py_NULL; + } + + assert(PyUnicode_Check(str)); + + assert(PyUnicode_GetLength(str) == 3); + assert(PyUnicode_ReadChar(str, 0) == 'a'); + assert(PyUnicode_ReadChar(str, 1) == 'b'); + +#ifndef Py_LIMITED_API + assert(PyUnicode_GET_LENGTH(str) == 3); + + // gh-92800: test PyUnicode_READ() + data = PyUnicode_DATA(str); + assert(data != _Py_NULL); + kind = PyUnicode_KIND(str); + assert(kind == PyUnicode_1BYTE_KIND); + assert(PyUnicode_READ(kind, data, 0) == 'a'); + + // gh-92800: test PyUnicode_READ() casts + const_data = PyUnicode_DATA(str); +#ifdef __cplusplus + ukind = static_cast(kind); +#else + ukind = (unsigned int)kind; +#endif + assert(PyUnicode_READ(ukind, const_data, 2) == 'c'); + + assert(PyUnicode_READ_CHAR(str, 1) == 'b'); +#endif + + Py_DECREF(str); + Py_RETURN_NONE; +} + + +#ifdef __cplusplus +// Class to test operator casting an object to PyObject* +class StrongRef +{ +public: + StrongRef(PyObject *obj) : m_obj(obj) { + Py_INCREF(this->m_obj); + } + + ~StrongRef() { + Py_DECREF(this->m_obj); + } + + // Cast to PyObject*: get a borrowed reference + inline operator PyObject*() const { return this->m_obj; } + +private: + PyObject *m_obj; // Strong reference +}; + + +static PyObject * +test_api_casts(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + PyObject *obj = Py_BuildValue("(ii)", 1, 2); + if (obj == _Py_NULL) { + return _Py_NULL; + } + Py_ssize_t refcnt = Py_REFCNT(obj); + assert(refcnt >= 1); + +#ifndef Py_LIMITED_API + // gh-92138: For backward compatibility, functions of Python C API accepts + // "const PyObject*". Check that using it does not emit C++ compiler + // warnings. + const PyObject *const_obj = obj; + Py_INCREF(const_obj); + Py_DECREF(const_obj); + PyTypeObject *type = Py_TYPE(const_obj); + assert(Py_REFCNT(const_obj) == refcnt); + assert(type == &PyTuple_Type); + assert(PyTuple_GET_SIZE(const_obj) == 2); + PyObject *one = PyTuple_GET_ITEM(const_obj, 0); + assert(PyLong_AsLong(one) == 1); +#endif + + // gh-92898: StrongRef doesn't inherit from PyObject but has an operator to + // cast to PyObject*. + StrongRef strong_ref(obj); + assert(Py_TYPE(strong_ref) == &PyTuple_Type); + assert(Py_REFCNT(strong_ref) == (refcnt + 1)); + Py_INCREF(strong_ref); + Py_DECREF(strong_ref); + + // gh-93442: Pass 0 as NULL for PyObject* + Py_XINCREF(0); + Py_XDECREF(0); +#if __cplusplus >= 201103 + // Test nullptr passed as PyObject* + Py_XINCREF(nullptr); + Py_XDECREF(nullptr); +#endif + + Py_DECREF(obj); + Py_RETURN_NONE; +} +#endif // __cplusplus + + +// VirtualPyObject is incompatible with opaque PyObject +#if defined(__cplusplus) && !defined(Py_TARGET_ABI3T) +/* Test a `new`-allocated object with a virtual method. + * (https://github.com/python/cpython/issues/94731) */ + +class VirtualPyObject : public PyObject { +public: + VirtualPyObject(); + virtual ~VirtualPyObject() { + delete [] internal_data; + --instance_count; + } + virtual void set_internal_data() { + internal_data[0] = 1; + } + static void dealloc(PyObject* o) { + delete static_cast(o); + } + + // Number of "living" instances + static int instance_count; +private: + // buffer that can get corrupted + int* internal_data; +}; + +int VirtualPyObject::instance_count = 0; + +// Converting from function pointer to void* has undefined behavior, but +// works on all known platforms, and CPython's module and type slots currently +// need it. +// (GCC doesn't have a narrower category for this than -Wpedantic.) +_Py_COMP_DIAG_PUSH +#if defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wpedantic" +#elif defined(__clang__) +#pragma clang diagnostic ignored "-Wpedantic" +#endif + +PyType_Slot VirtualPyObject_Slots[] = { + {Py_tp_free, (void*)VirtualPyObject::dealloc}, + {0, _Py_NULL}, +}; + +_Py_COMP_DIAG_POP + +PyType_Spec VirtualPyObject_Spec = { + /* .name */ STR(MODULE_NAME) ".VirtualPyObject", + /* .basicsize */ sizeof(VirtualPyObject), + /* .itemsize */ 0, + /* .flags */ Py_TPFLAGS_DEFAULT, + /* .slots */ VirtualPyObject_Slots, +}; + +VirtualPyObject::VirtualPyObject() { + // Create a temporary type (just so we don't need to store it) + PyObject *type = PyType_FromSpec(&VirtualPyObject_Spec); + // no good way to signal failure from a C++ constructor, so use assert + // for error handling + assert(type); + assert(PyObject_Init(this, (PyTypeObject *)type)); + Py_DECREF(type); + internal_data = new int[50]; + ++instance_count; +} + +static PyObject * +test_virtual_object(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + VirtualPyObject* obj = new VirtualPyObject(); + obj->set_internal_data(); + Py_DECREF(obj); + if (VirtualPyObject::instance_count != 0) { + return PyErr_Format( + PyExc_AssertionError, + "instance_count should be 0, got %d", + VirtualPyObject::instance_count); + } + Py_RETURN_NONE; +} +#endif // __cplusplus && !Py_TARGET_ABI3T + + +static PyMethodDef module_methods[] = { + {"add", test_add, METH_VARARGS, test_add_doc}, + {"test_macros", test_macros, METH_NOARGS, _Py_NULL}, + {"test_datetime", test_datetime, METH_NOARGS, _Py_NULL}, + {"test_unicode", test_unicode, METH_NOARGS, _Py_NULL}, +#ifdef __cplusplus + {"test_api_casts", test_api_casts, METH_NOARGS, _Py_NULL}, +#endif +#if defined(__cplusplus) && !defined(Py_TARGET_ABI3T) + {"test_virtual_object", test_virtual_object, METH_NOARGS, _Py_NULL}, +#endif + {_Py_NULL, _Py_NULL, 0, _Py_NULL}, // sentinel }; static int -_testcext_exec(PyObject *module) +module_exec(PyObject *module) { - PyObject *result, *obj; + PyObject *result; #ifdef __STDC_VERSION__ if (PyModule_AddIntMacro(module, __STDC_VERSION__) < 0) { return -1; } #endif +#ifdef __cplusplus + if (PyModule_AddIntMacro(module, __cplusplus) < 0) { + return -1; + } +#endif - result = PyObject_CallMethod(module, "test_datetime", ""); + result = PyObject_CallMethod(module, "test_macros", ""); if (!result) return -1; Py_DECREF(result); - // Test Py_BUILD_ASSERT() and Py_BUILD_ASSERT_EXPR() - Py_BUILD_ASSERT(sizeof(int) == sizeof(unsigned int)); - assert(Py_BUILD_ASSERT_EXPR(sizeof(int) == sizeof(unsigned int)) == 0); - - // Test Py_MIN(), Py_MAX(), Py_ABS() - assert(Py_MIN(5, 11) == 5); - assert(Py_MAX(5, 11) == 11); - assert(Py_ABS(-5) == 5); + result = PyObject_CallMethod(module, "test_datetime", ""); + if (!result) return -1; + Py_DECREF(result); - // Test Py_CLEAR(): use typeof()/__typeof__() if available, or memcpy() - obj = Py_None; - Py_CLEAR(obj); - assert(obj == NULL); + result = PyObject_CallMethod(module, "test_unicode", ""); + if (!result) return -1; + Py_DECREF(result); -#ifndef Py_LIMITED_API - // Test Py_SETREF(): use typeof()/__typeof__() if available, or memcpy() - obj = Py_None; - Py_SETREF(obj, NULL); - assert(obj == NULL); +#ifdef __cplusplus + result = PyObject_CallMethod(module, "test_api_casts", ""); + if (!result) return -1; + Py_DECREF(result); #endif - // Test that Py_BEGIN_CRITICAL_SECTION is available - Py_BEGIN_CRITICAL_SECTION(module); - Py_END_CRITICAL_SECTION(); +#if defined(__cplusplus) && !defined(Py_TARGET_ABI3T) + result = PyObject_CallMethod(module, "test_virtual_object", ""); + if (!result) return -1; + Py_DECREF(result); +#endif return 0; } @@ -122,38 +399,68 @@ _testcext_exec(PyObject *module) #define _FUNC_NAME(NAME) PyModExport_ ## NAME #define FUNC_NAME(NAME) _FUNC_NAME(NAME) +#ifdef __cplusplus +PyDoc_STRVAR(module_doc, "C++ test extension."); +#else +PyDoc_STRVAR(module_doc, "C test extension."); +#endif +PyABIInfo_VAR(abi_info); + +#ifdef __cplusplus + +// Need to ignore "-Wpedantic" warnings; see VirtualPyObject_Slots above +_Py_COMP_DIAG_PUSH +#if defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wpedantic" +#elif defined(__clang__) +# pragma clang diagnostic ignored "-Wpedantic" +#endif + +static PySlot module_slots[] = { + PySlot_PTR_STATIC(Py_mod_abi, &abi_info), + PySlot_PTR_STATIC(Py_mod_name, (void*)STR(MODULE_NAME)), + PySlot_PTR_STATIC(Py_mod_doc, (void*)(char*)module_doc), + PySlot_PTR_STATIC(Py_mod_exec, (void*)module_exec), + PySlot_PTR_STATIC(Py_mod_methods, module_methods), + PySlot_PTR_STATIC(Py_mod_gil, Py_MOD_GIL_NOT_USED), + PySlot_END, +}; + +_Py_COMP_DIAG_POP + +#else + // Converting from function pointer to void* has undefined behavior, but // works on all known platforms, and CPython's module and type slots currently // need it. // (GCC doesn't have a narrower category for this than -Wpedantic.) _Py_COMP_DIAG_PUSH #if defined(__GNUC__) -#pragma GCC diagnostic ignored "-Wpedantic" -#pragma GCC diagnostic ignored "-Wcast-qual" +# pragma GCC diagnostic ignored "-Wpedantic" +# pragma GCC diagnostic ignored "-Wcast-qual" #elif defined(__clang__) -#pragma clang diagnostic ignored "-Wpedantic" -#pragma clang diagnostic ignored "-Wcast-qual" +# pragma clang diagnostic ignored "-Wpedantic" +# pragma clang diagnostic ignored "-Wcast-qual" #endif -PyDoc_STRVAR(_testcext_doc, "C test extension."); -PyABIInfo_VAR(abi_info); - -static PySlot _testcext_slots[] = { +static PySlot module_slots[] = { PySlot_STATIC_DATA(Py_mod_abi, &abi_info), PySlot_STATIC_DATA(Py_mod_name, STR(MODULE_NAME)), - PySlot_STATIC_DATA(Py_mod_doc, (void*)(char*)_testcext_doc), - PySlot_FUNC(Py_mod_exec, (void*)_testcext_exec), - PySlot_STATIC_DATA(Py_mod_methods, _testcext_methods), + PySlot_STATIC_DATA(Py_mod_doc, (void*)(char*)module_doc), + PySlot_FUNC(Py_mod_exec, (void*)module_exec), + PySlot_STATIC_DATA(Py_mod_methods, module_methods), PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), PySlot_END, }; _Py_COMP_DIAG_POP +#endif // !__cplusplus + PyMODEXPORT_FUNC FUNC_NAME(MODULE_NAME)(void) { - return _testcext_slots; + return module_slots; } // Also define the soft-deprecated entrypoint to ensure it isn't called diff --git a/Lib/test/test_cext/extension.cpp b/Lib/test/test_cext/extension.cpp new file mode 100644 index 00000000000000..8889e1ca7b3489 --- /dev/null +++ b/Lib/test/test_cext/extension.cpp @@ -0,0 +1,3 @@ +// extension.cpp just copy/paste extension.c. This file is used to pass +// a filename with ".cpp" extension to select a C++ compiler. +#include "extension.c" diff --git a/Lib/test/test_cext/setup.py b/Lib/test/test_cext/setup.py index 1eca44bdf823dc..58bee3e255a4b3 100644 --- a/Lib/test/test_cext/setup.py +++ b/Lib/test/test_cext/setup.py @@ -1,5 +1,5 @@ -# 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. +# gh-91321: Build a basic C or C++ test extension module to check that the +# Python C API does not emit compiler warnings. import os import shlex import sys @@ -8,10 +8,17 @@ from setuptools import setup, Extension +SOURCE = { + 'C': 'extension.c', + 'C++': 'extension.cpp', +} -SOURCE = 'extension.c' +MSVC = support.MS_WINDOWS -if not support.MS_WINDOWS: + +### C flags ################################################################# + +if not MSVC: # C compiler flags for GCC and clang BASE_CFLAGS = [ # The purpose of test_cext extension is to check that building a C @@ -59,32 +66,74 @@ ] +### C++ flags ############################################################### + +if not MSVC: + # C++ compiler flags for GCC and clang + CPPFLAGS = [ + # gh-91321: The purpose of _testcppext extension is to check that building + # a C++ extension using the Python C API does not emit C++ compiler + # warnings + '-Werror', + ] + + CPPFLAGS_PEDANTIC = [ + # Ask for strict(er) compliance with the standard. + # We cannot do this for c++03 unlimited API, since several headers in + # Include/cpython/ use commas at end of `enum` declarations, a C++11 + # feature for which GCC has no narrower option than -Wpedantic itself. + '-pedantic-errors', + + # We also use `long long`, a C++11 feature we can enable individually. + '-Wno-long-long', + ] +else: + # MSVC compiler flags + CPPFLAGS = [ + # Display warnings level 1 to 4 + '/W4', + # Treat all compiler warnings as compiler errors + '/WX', + ] + CPPFLAGS_PEDANTIC = [] + + def main(): - std = os.environ.get("CPYTHON_TEST_STD", "") module_name = os.environ["CPYTHON_TEST_EXT_NAME"] + language = os.environ.get("CPYTHON_TEST_LANG", "C") + std = os.environ.get("CPYTHON_TEST_STD", "") limited = bool(os.environ.get("CPYTHON_TEST_LIMITED", "")) abi3t = bool(os.environ.get("CPYTHON_TEST_ABI3T", "")) - internal = bool(int(os.environ.get("TEST_INTERNAL_C_API", "0"))) - incdirs = os.environ.get("CPYTHON_EXTRA_INCDIRS", "") - libdirs = os.environ.get("CPYTHON_EXTRA_LIBDIRS", "") + internal = bool(int(os.environ.get("CPYTHON_TEST_INTERNAL_C_API", "0"))) + incdirs = os.environ.get("CPYTHON_TEST_EXTRA_INCDIRS", "") + libdirs = os.environ.get("CPYTHON_TEST_EXTRA_LIBDIRS", "") + extra_cflags = os.environ.get("CPYTHON_TEST_EXTRA_CFLAGS", "") - sources = [SOURCE] + source = SOURCE[language] - if not internal: - cflags = list(PUBLIC_CFLAGS) + if language == 'C++': + flags = list(CPPFLAGS) else: - cflags = list(INTERNAL_CFLAGS) - cflags.append(f'-DMODULE_NAME={module_name}') + if not internal: + flags = list(PUBLIC_CFLAGS) + else: + flags = list(INTERNAL_CFLAGS) + flags.append(f'-DMODULE_NAME={module_name}') # Add -std=STD or /std:STD (MSVC) compiler flag if std: - if support.MS_WINDOWS: - cflags.append(f'/std:{std}') + if MSVC: + flags.append(f'/std:{std}') else: - cflags.append(f'-std={std}') + flags.append(f'-std={std}') - # Remove existing -std or /std options from CC command line. - # Python adds -std=c11 option. + if language == 'C++' and (limited or (std != 'c++03') and not internal): + # See CPPFLAGS_PEDANTIC docstring + flags.extend(CPPFLAGS_PEDANTIC) + + # gh-105776: When "gcc -std=11" is used as the C++ compiler, -std=c11 + # option emits a C++ compiler warning. Remove "-std11" option from the + # CC command. cmd = (sysconfig.get_config_var('CC') or '') if cmd is not None: if support.MS_WINDOWS: @@ -99,13 +148,13 @@ def main(): # Define opt-in macros if limited: - cflags.append(f'-DPy_LIMITED_API={sys.hexversion:#x}') - + flags.append(f'-DPy_LIMITED_API={sys.hexversion:#x}') if abi3t: - cflags.append(f'-DPy_TARGET_ABI3T={sys.hexversion:#x}') - + flags.append(f'-DPy_TARGET_ABI3T={sys.hexversion:#x}') if internal: - cflags.append('-DTEST_INTERNAL_C_API=1') + flags.append('-DTEST_INTERNAL_C_API=1') + if extra_cflags: + flags.extend(shlex.split(extra_cflags)) # Add additional include and library directories, typically for in-tree # testing where not all directories are inferred @@ -119,17 +168,19 @@ def main(): library_dirs.extend(libdirs.split(os.pathsep)) # Display information to help debugging - for env_name in ('CC', 'CFLAGS', 'CPPFLAGS'): + print(f"Language: {language}") + print(f"Source: {source}") + for env_name in ('CC', 'CXX', 'CFLAGS', 'CPPFLAGS', 'CXXFLAGS'): if env_name in os.environ: print(f"{env_name} env var: {os.environ[env_name]!r}") else: print(f"{env_name} env var: ") - print(f"extra_compile_args: {cflags!r}") + print(f"extra_compile_args: {flags!r}") ext = Extension( module_name, - sources=sources, - extra_compile_args=cflags, + sources=[source], + extra_compile_args=flags, include_dirs=include_dirs, library_dirs=library_dirs) setup(name=f'internal_{module_name}', diff --git a/Lib/test/test_cppext/__init__.py b/Lib/test/test_cppext/__init__.py deleted file mode 100644 index db7f41d9ef7a11..00000000000000 --- a/Lib/test/test_cppext/__init__.py +++ /dev/null @@ -1,148 +0,0 @@ -# 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.path -import platform -import shlex -import shutil -import subprocess -import sys -import sysconfig -import unittest -from test import support - - -SOURCE = os.path.join(os.path.dirname(__file__), 'extension.cpp') -SETUP = os.path.join(os.path.dirname(__file__), 'setup.py') - - -# With MSVC on a debug build, the linker fails with: cannot open file -# 'python311.lib', it should look 'python311_d.lib'. -@unittest.skipIf(support.MS_WINDOWS and support.Py_DEBUG, - 'test fails on Windows debug build') -# Building and running an extension in clang sanitizing mode is not -# straightforward -@support.skip_if_sanitizer('test does not work with analyzing builds', - address=True, memory=True, ub=True, thread=True) -# the test uses venv+pip: skip if it's not available -@support.requires_venv_with_pip() -@support.requires_subprocess() -@support.requires_resource('cpu') -class BaseTests: - TEST_INTERNAL_C_API = False - - def check_build(self, extension_name, std=None, limited=False, - extra_cflags=None): - venv_dir = 'env' - with support.setup_venv_with_pip_setuptools(venv_dir) as python_exe: - self._check_build(extension_name, python_exe, - std=std, limited=limited, - extra_cflags=extra_cflags) - - def _check_build(self, extension_name, python_exe, std, limited, - extra_cflags=None): - 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() - if std: - env['CPYTHON_TEST_CPP_STD'] = std - if limited: - env['CPYTHON_TEST_LIMITED'] = '1' - if support.MS_WINDOWS and sysconfig.is_python_build(): - env['CPYTHON_EXTRA_INCDIRS'] = os.path.split(sysconfig.get_config_h_filename())[0] - env['CPYTHON_EXTRA_LIBDIRS'] = os.path.split(sys.executable)[0] - env['CPYTHON_TEST_EXT_NAME'] = extension_name - env['TEST_INTERNAL_C_API'] = str(int(self.TEST_INTERNAL_C_API)) - if extra_cflags: - env['CPYTHON_TEST_EXTRA_CFLAGS'] = extra_cflags - if support.verbose: - print('Run:', ' '.join(map(shlex.quote, 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('Run:', ' '.join(map(shlex.quote, cmd))) - 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)] - if support.verbose: - cmd.append('-v') - 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) - - -class TestPublicCAPI(BaseTests, unittest.TestCase): - def test_build(self): - self.check_build('_testcppext') - - def test_build_limited_cpp03(self): - self.check_build('_test_limited_cpp03ext', std='c++03', limited=True) - - def test_build_limited(self): - self.check_build('_testcppext_limited', limited=True) - - def test_build_cpp03(self): - # In public docs, we say C API is compatible with C++11. However, - # in practice we do maintain C++03 compatibility in public headers. - # Please ask the C API WG before adding a new C++11-only feature. - self.check_build('_testcpp03ext', std='c++03') - - @unittest.skipIf(support.MS_WINDOWS, "MSVC doesn't support /std:c++11") - def test_build_cpp11(self): - self.check_build('_testcpp11ext', std='c++11') - - # Only test C++14 on MSVC. - # On s390x RHEL7, GCC 4.8.5 doesn't support C++14. - @unittest.skipIf(not support.MS_WINDOWS, "need Windows") - def test_build_cpp14(self): - self.check_build('_testcpp14ext', std='c++14') - - # Test that headers compile with Intel asm syntax, which may conflict - # with inline assembly in free-threading headers that use AT&T syntax. - @unittest.skipIf(support.MS_WINDOWS, "MSVC doesn't support -masm=intel") - @unittest.skipUnless(platform.machine() in ('x86_64', 'i686', 'AMD64'), - "x86-specific flag") - def test_build_intel_asm(self): - self.check_build('_testcppext_asm', extra_cflags='-masm=intel') - - -class TestInteralCAPI(BaseTests, unittest.TestCase): - TEST_INTERNAL_C_API = True - - def test_build(self): - kwargs = {} - if sys.platform == 'darwin': - # Old Apple clang++ default C++ std is gnu++98 - kwargs['std'] = 'c++11' - self.check_build('_testcppext_internal', **kwargs) - - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/test/test_cppext/extension.cpp b/Lib/test/test_cppext/extension.cpp deleted file mode 100644 index 1ff56d0e7fd25a..00000000000000 --- a/Lib/test/test_cppext/extension.cpp +++ /dev/null @@ -1,375 +0,0 @@ -// gh-91321: Basic C++ test extension to check that the Python C API is -// compatible with C++ and does not emit C++ compiler warnings. -// -// The code is only built, not executed. - -// Always enable assertions -#undef NDEBUG - -#ifdef TEST_INTERNAL_C_API -# define Py_BUILD_CORE_MODULE 1 -#endif - -#include "Python.h" -#include "datetime.h" - -#ifdef TEST_INTERNAL_C_API - // gh-135906: Check for compiler warnings in the internal C API - // - Cython uses pycore_critical_section.h, pycore_frame.h and - // pycore_template.h. - // - greenlet uses pycore_frame.h, pycore_interpframe_structs.h and - // pycore_interpframe.h. -# include "internal/pycore_frame.h" -# include "internal/pycore_interpframe_structs.h" -# include "internal/pycore_template.h" - - // mimalloc emits compiler warnings on Windows. -# if !defined(MS_WINDOWS) -# include "internal/pycore_backoff.h" -# include "internal/pycore_cell.h" -# include "internal/pycore_critical_section.h" -# include "internal/pycore_interpframe.h" -# endif -#endif - -#ifndef MODULE_NAME -# error "MODULE_NAME macro must be defined" -#endif - -#define _STR(NAME) #NAME -#define STR(NAME) _STR(NAME) - -PyDoc_STRVAR(_testcppext_add_doc, -"add(x, y)\n" -"\n" -"Return the sum of two integers: x + y."); - -static PyObject * -_testcppext_add(PyObject *Py_UNUSED(module), PyObject *args) -{ - long i, j; - if (!PyArg_ParseTuple(args, "ll:foo", &i, &j)) { - return _Py_NULL; - } - long res = i + j; - return PyLong_FromLong(res); -} - - -// Class to test operator casting an object to PyObject* -class StrongRef -{ -public: - StrongRef(PyObject *obj) : m_obj(obj) { - Py_INCREF(this->m_obj); - } - - ~StrongRef() { - Py_DECREF(this->m_obj); - } - - // Cast to PyObject*: get a borrowed reference - inline operator PyObject*() const { return this->m_obj; } - -private: - PyObject *m_obj; // Strong reference -}; - - -static PyObject * -test_api_casts(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) -{ - PyObject *obj = Py_BuildValue("(ii)", 1, 2); - if (obj == _Py_NULL) { - return _Py_NULL; - } - Py_ssize_t refcnt = Py_REFCNT(obj); - assert(refcnt >= 1); - -#ifndef Py_LIMITED_API - // gh-92138: For backward compatibility, functions of Python C API accepts - // "const PyObject*". Check that using it does not emit C++ compiler - // warnings. - const PyObject *const_obj = obj; - Py_INCREF(const_obj); - Py_DECREF(const_obj); - PyTypeObject *type = Py_TYPE(const_obj); - assert(Py_REFCNT(const_obj) == refcnt); - assert(type == &PyTuple_Type); - assert(PyTuple_GET_SIZE(const_obj) == 2); - PyObject *one = PyTuple_GET_ITEM(const_obj, 0); - assert(PyLong_AsLong(one) == 1); -#endif - - // gh-92898: StrongRef doesn't inherit from PyObject but has an operator to - // cast to PyObject*. - StrongRef strong_ref(obj); - assert(Py_TYPE(strong_ref) == &PyTuple_Type); - assert(Py_REFCNT(strong_ref) == (refcnt + 1)); - Py_INCREF(strong_ref); - Py_DECREF(strong_ref); - - // gh-93442: Pass 0 as NULL for PyObject* - Py_XINCREF(0); - Py_XDECREF(0); -#if __cplusplus >= 201103 - // Test nullptr passed as PyObject* - Py_XINCREF(nullptr); - Py_XDECREF(nullptr); -#endif - - Py_DECREF(obj); - Py_RETURN_NONE; -} - - -static PyObject * -test_unicode(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) -{ - PyObject *str = PyUnicode_FromString("abc"); - if (str == _Py_NULL) { - return _Py_NULL; - } - - assert(PyUnicode_Check(str)); - - assert(PyUnicode_GetLength(str) == 3); - assert(PyUnicode_ReadChar(str, 0) == 'a'); - assert(PyUnicode_ReadChar(str, 1) == 'b'); - -#ifndef Py_LIMITED_API - assert(PyUnicode_GET_LENGTH(str) == 3); - - // gh-92800: test PyUnicode_READ() - const void* data = PyUnicode_DATA(str); - assert(data != _Py_NULL); - int kind = PyUnicode_KIND(str); - assert(kind == PyUnicode_1BYTE_KIND); - assert(PyUnicode_READ(kind, data, 0) == 'a'); - - // gh-92800: test PyUnicode_READ() casts - const void* const_data = PyUnicode_DATA(str); - unsigned int ukind = static_cast(kind); - assert(PyUnicode_READ(ukind, const_data, 2) == 'c'); - - assert(PyUnicode_READ_CHAR(str, 1) == 'b'); -#endif - - Py_DECREF(str); - Py_RETURN_NONE; -} - -// VirtualPyObject is incompatible with opaque PyObject -#ifndef Py_TARGET_ABI3T -/* Test a `new`-allocated object with a virtual method. - * (https://github.com/python/cpython/issues/94731) */ - -class VirtualPyObject : public PyObject { -public: - VirtualPyObject(); - virtual ~VirtualPyObject() { - delete [] internal_data; - --instance_count; - } - virtual void set_internal_data() { - internal_data[0] = 1; - } - static void dealloc(PyObject* o) { - delete static_cast(o); - } - - // Number of "living" instances - static int instance_count; -private: - // buffer that can get corrupted - int* internal_data; -}; - -int VirtualPyObject::instance_count = 0; - -// Converting from function pointer to void* has undefined behavior, but -// works on all known platforms, and CPython's module and type slots currently -// need it. -// (GCC doesn't have a narrower category for this than -Wpedantic.) -_Py_COMP_DIAG_PUSH -#if defined(__GNUC__) -#pragma GCC diagnostic ignored "-Wpedantic" -#elif defined(__clang__) -#pragma clang diagnostic ignored "-Wpedantic" -#endif - -PyType_Slot VirtualPyObject_Slots[] = { - {Py_tp_free, (void*)VirtualPyObject::dealloc}, - {0, _Py_NULL}, -}; - -_Py_COMP_DIAG_POP - -PyType_Spec VirtualPyObject_Spec = { - /* .name */ STR(MODULE_NAME) ".VirtualPyObject", - /* .basicsize */ sizeof(VirtualPyObject), - /* .itemsize */ 0, - /* .flags */ Py_TPFLAGS_DEFAULT, - /* .slots */ VirtualPyObject_Slots, -}; - -VirtualPyObject::VirtualPyObject() { - // Create a temporary type (just so we don't need to store it) - PyObject *type = PyType_FromSpec(&VirtualPyObject_Spec); - // no good way to signal failure from a C++ constructor, so use assert - // for error handling - assert(type); - assert(PyObject_Init(this, (PyTypeObject *)type)); - Py_DECREF(type); - internal_data = new int[50]; - ++instance_count; -} - -static PyObject * -test_virtual_object(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) -{ - VirtualPyObject* obj = new VirtualPyObject(); - obj->set_internal_data(); - Py_DECREF(obj); - if (VirtualPyObject::instance_count != 0) { - return PyErr_Format( - PyExc_AssertionError, - "instance_count should be 0, got %d", - VirtualPyObject::instance_count); - } - Py_RETURN_NONE; -} -#endif // Py_TARGET_ABI3T - - -static PyObject * -test_datetime(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) -{ - // datetime.h is excluded from the limited C API -#ifndef Py_LIMITED_API - PyDateTime_IMPORT; - if (PyErr_Occurred()) { - return NULL; - } -#endif - - Py_RETURN_NONE; -} - -static PyMethodDef _testcppext_methods[] = { - {"add", _testcppext_add, METH_VARARGS, _testcppext_add_doc}, - {"test_api_casts", test_api_casts, METH_NOARGS, _Py_NULL}, - {"test_unicode", test_unicode, METH_NOARGS, _Py_NULL}, -#ifndef Py_TARGET_ABI3T - {"test_virtual_object", test_virtual_object, METH_NOARGS, _Py_NULL}, -#endif - {"test_datetime", test_datetime, METH_NOARGS, _Py_NULL}, - // Note: _testcppext_exec currently runs all test functions directly. - // When adding a new one, add a call there. - - {_Py_NULL, _Py_NULL, 0, _Py_NULL} /* sentinel */ -}; - - -static int -_testcppext_exec(PyObject *module) -{ - if (PyModule_AddIntMacro(module, __cplusplus) < 0) { - return -1; - } - - PyObject *result; - - result = PyObject_CallMethod(module, "test_api_casts", ""); - if (!result) return -1; - Py_DECREF(result); - - result = PyObject_CallMethod(module, "test_unicode", ""); - if (!result) return -1; - Py_DECREF(result); - -#ifndef Py_TARGET_ABI3T - result = PyObject_CallMethod(module, "test_virtual_object", ""); - if (!result) return -1; - Py_DECREF(result); -#endif - - result = PyObject_CallMethod(module, "test_datetime", ""); - if (!result) return -1; - Py_DECREF(result); - - // test Py_BUILD_ASSERT() and Py_BUILD_ASSERT_EXPR() - Py_BUILD_ASSERT(sizeof(int) == sizeof(unsigned int)); - assert(Py_BUILD_ASSERT_EXPR(sizeof(int) == sizeof(unsigned int)) == 0); - - // Test Py_MIN(), Py_MAX(), Py_ABS() - assert(Py_MIN(5, 11) == 5); - assert(Py_MAX(5, 11) == 11); - assert(Py_ABS(-5) == 5); - - // Test Py_CLEAR(): use typeof()/__typeof__() if available, or memcpy() - PyObject *obj = Py_None; - Py_CLEAR(obj); - assert(obj == _Py_NULL); - -#ifndef Py_LIMITED_API - // Test Py_SETREF(): use typeof()/__typeof__() if available, or memcpy() - obj = Py_None; - Py_SETREF(obj, _Py_NULL); - assert(obj == _Py_NULL); -#endif - - // Test that Py_BEGIN_CRITICAL_SECTION is available - Py_BEGIN_CRITICAL_SECTION(module); - Py_END_CRITICAL_SECTION(); - - return 0; -} - - -PyDoc_STRVAR(_testcppext_doc, "C++ test extension."); -PyABIInfo_VAR(abi_info); - -// Need to ignore "-Wpedantic" warnings; see VirtualPyObject_Slots above -_Py_COMP_DIAG_PUSH -#if defined(__GNUC__) -#pragma GCC diagnostic ignored "-Wpedantic" -#elif defined(__clang__) -#pragma clang diagnostic ignored "-Wpedantic" -#endif - -static PySlot _testcppext_slots[] = { - PySlot_PTR_STATIC(Py_mod_abi, &abi_info), - PySlot_PTR_STATIC(Py_mod_name, (void*)STR(MODULE_NAME)), - PySlot_PTR_STATIC(Py_mod_doc, (void*)(char*)_testcppext_doc), - PySlot_PTR_STATIC(Py_mod_exec, (void*)_testcppext_exec), - PySlot_PTR_STATIC(Py_mod_methods, _testcppext_methods), - PySlot_PTR_STATIC(Py_mod_gil, Py_MOD_GIL_NOT_USED), - PySlot_END, -}; - -_Py_COMP_DIAG_POP - - -#define _FUNC_NAME(NAME) PyModExport_ ## NAME -#define FUNC_NAME(NAME) _FUNC_NAME(NAME) - -PyMODEXPORT_FUNC -FUNC_NAME(MODULE_NAME)(void) -{ - return _testcppext_slots; -} - -// Also define the soft-deprecated entrypoint to ensure it isn't called - -#define _INITFUNC_NAME(NAME) PyInit_ ## NAME -#define INITFUNC_NAME(NAME) _INITFUNC_NAME(NAME) - -PyMODINIT_FUNC -INITFUNC_NAME(MODULE_NAME)(void) -{ - PyErr_SetString( - PyExc_AssertionError, - "PyInit_* function called while a PyModExport_* one is available"); - return NULL; -} diff --git a/Lib/test/test_cppext/setup.py b/Lib/test/test_cppext/setup.py deleted file mode 100644 index 5d004ca6e3ad78..00000000000000 --- a/Lib/test/test_cppext/setup.py +++ /dev/null @@ -1,126 +0,0 @@ -# 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.cpp' - -if not support.MS_WINDOWS: - # C++ compiler flags for GCC and clang - CPPFLAGS = [ - # gh-91321: The purpose of _testcppext extension is to check that building - # a C++ extension using the Python C API does not emit C++ compiler - # warnings - '-Werror', - ] - - CPPFLAGS_PEDANTIC = [ - # Ask for strict(er) compliance with the standard. - # We cannot do this for c++03 unlimited API, since several headers in - # Include/cpython/ use commas at end of `enum` declarations, a C++11 - # feature for which GCC has no narrower option than -Wpedantic itself. - '-pedantic-errors', - - # We also use `long long`, a C++11 feature we can enable individually. - '-Wno-long-long', - ] -else: - # MSVC compiler flags - CPPFLAGS = [ - # Display warnings level 1 to 4 - '/W4', - # Treat all compiler warnings as compiler errors - '/WX', - ] - CPPFLAGS_PEDANTIC = [] - - -def main(): - cppflags = list(CPPFLAGS) - std = os.environ.get("CPYTHON_TEST_CPP_STD", "") - module_name = os.environ["CPYTHON_TEST_EXT_NAME"] - limited = bool(os.environ.get("CPYTHON_TEST_LIMITED", "")) - internal = bool(int(os.environ.get("TEST_INTERNAL_C_API", "0"))) - incdirs = os.environ.get("CPYTHON_EXTRA_INCDIRS", "") - libdirs = os.environ.get("CPYTHON_EXTRA_LIBDIRS", "") - - cppflags = list(CPPFLAGS) - cppflags.append(f'-DMODULE_NAME={module_name}') - - # Add -std=STD or /std:STD (MSVC) compiler flag - if std: - if support.MS_WINDOWS: - cppflags.append(f'/std:{std}') - else: - cppflags.append(f'-std={std}') - - if limited or (std != 'c++03') and not internal: - # See CPPFLAGS_PEDANTIC docstring - cppflags.extend(CPPFLAGS_PEDANTIC) - - # gh-105776: When "gcc -std=11" is used as the C++ compiler, -std=c11 - # option emits a C++ compiler warning. Remove "-std11" option from the - # CC command. - cmd = (sysconfig.get_config_var('CC') or '') - if cmd is not None: - if support.MS_WINDOWS: - std_prefix = '/std' - else: - std_prefix = '-std' - cmd = shlex.split(cmd) - cmd = [arg for arg in cmd if not arg.startswith(std_prefix)] - cmd = shlex.join(cmd) - # CC env var overrides sysconfig CC variable in setuptools - os.environ['CC'] = cmd - - # Define Py_LIMITED_API macro - if limited: - version = sys.hexversion - cppflags.append(f'-DPy_LIMITED_API={version:#x}') - - if internal: - cppflags.append('-DTEST_INTERNAL_C_API=1') - - extra_cflags = os.environ.get("CPYTHON_TEST_EXTRA_CFLAGS", "") - if extra_cflags: - cppflags.extend(shlex.split(extra_cflags)) - - # Add additional include and library directories, typically for in-tree - # testing where not all directories are inferred - include_dirs = [] - library_dirs = [] - if incdirs: - print("Add incdirs:", incdirs) - include_dirs.extend(incdirs.split(os.pathsep)) - if libdirs: - print("Add libdirs:", libdirs) - library_dirs.extend(libdirs.split(os.pathsep)) - - # Display information to help debugging - for env_name in ('CC', 'CXX', 'CFLAGS', 'CPPFLAGS', 'CXXFLAGS'): - if env_name in os.environ: - print(f"{env_name} env var: {os.environ[env_name]!r}") - else: - print(f"{env_name} env var: ") - print(f"extra_compile_args: {cppflags!r}") - - ext = Extension( - module_name, - sources=[SOURCE], - language='c++', - extra_compile_args=cppflags, - include_dirs=include_dirs, - library_dirs=library_dirs) - setup(name=f'internal_{module_name}', - version='0.0', - ext_modules=[ext]) - - -if __name__ == "__main__": - main() diff --git a/Makefile.pre.in b/Makefile.pre.in index b29976ee041099..42480f28ad7f31 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2611,7 +2611,6 @@ TESTSUBDIRS= idlelib/idle_test \ test/test_capi \ test/test_cext \ test/test_concurrent_futures \ - test/test_cppext \ test/test_ctypes \ test/test_dataclasses \ test/test_doctest \