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]

Reply via email to