https://github.com/python/cpython/commit/b71989d65780683dbbcc3882afc0ef0d794a6ddb
commit: b71989d65780683dbbcc3882afc0ef0d794a6ddb
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-09-25T22:31:44Z
summary:
gh-157649: Merge test_cppext into test_cext (#158176)
* test_cext now uses a single virtual environment for all tests.
It makes the test way faster since creating a virtual environment
is quite slow.
* Add Py_TARGET_ABI3T test on C++.
* Move macro tests from module exec function to test_macros().
* Call Py_BEGIN_CRITICAL_SECTION() on a fresh dictionary object,
rather than on the module.
* Rename some environment variables to always use "CPYTHON_TEST_"
prefix.
* Replace support.MS_WINDOWS with MSVC to be more explicit.
* Move macro tests to test_macros().
* On macOS, use C++11 by default.
files:
A Lib/test/test_cext/extension.cpp
D Lib/test/test_cppext/__init__.py
D Lib/test/test_cppext/extension.cpp
D Lib/test/test_cppext/setup.py
M Lib/test/pythoninfo.py
M Lib/test/test_cext/__init__.py
M Lib/test/test_cext/extension.c
M Lib/test/test_cext/setup.py
M Makefile.pre.in
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'.
[email protected](support.MS_WINDOWS and support.Py_DEBUG,
[email protected](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<unsigned int>(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<VirtualPyObject*>(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: <missing>")
- 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'.
[email protected](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
[email protected]_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
[email protected]_venv_with_pip()
[email protected]_subprocess()
[email protected]_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<unsigned int>(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<VirtualPyObject*>(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: <missing>")
- 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 \
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]