https://github.com/python/cpython/commit/cb7a629f4df7c3ad9535e5ac4ace3769589ec452
commit: cb7a629f4df7c3ad9535e5ac4ace3769589ec452
branch: 3.15
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-10-16T11:15:23Z
summary:

[3.15] gh-158445: Add C API tests on build information functions (#159515)

* gh-158445: Add C API tests on build information functions (#158603)

Add tests on functions:

* Py_GetBuildInfo()
* Py_GetCompiler()
* Py_GetCopyright()
* Py_GetPlatform()
* Py_GetVersion()

Changes:

* Add Modules/_testlimitedcapi.c and
  Lib/test/test_capi/test_build.py files.
* Add internal _Py_GetBuiltWithAssert() function.
* Reimplemenet support.built_with_c_assertions() using
  _Py_GetBuiltWithAssert() to cover more cases like buildbots using
  CFLAGS=-UNDEBUG in release mode.

(cherry picked from commit a831abba141ceeb0ef8acfada37602aa578cc670)

* gh-155742: Add support.built_with_c_assertions() (#156776)

Co-authored-by: Stan Ulbrych <[email protected]>
(cherry picked from commit c700121b15c9a055ec4488d22726b09f3f184d34)

---------

Co-authored-by: Stan Ulbrych <[email protected]>

files:
A Lib/test/test_capi/test_build.py
A Modules/_testlimitedcapi/build.c
M Lib/test/libregrtest/utils.py
M Lib/test/pythoninfo.py
M Lib/test/support/__init__.py
M Lib/test/test_gc.py
M Lib/test/test_support.py
M Modules/Setup.stdlib.in
M Modules/_testlimitedcapi.c
M Modules/_testlimitedcapi/parts.h
M Modules/getbuildinfo.c
M PCbuild/_testlimitedcapi.vcxproj
M PCbuild/_testlimitedcapi.vcxproj.filters

diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py
index 4581ab328e738cb..830f5c744b9b7c5 100644
--- a/Lib/test/libregrtest/utils.py
+++ b/Lib/test/libregrtest/utils.py
@@ -321,9 +321,6 @@ def get_build_info():
     # Get most important configure and build options as a list of strings.
     # Example: ['debug', 'ASAN+MSAN'] or ['release', 'LTO+PGO'].
 
-    config_args = sysconfig.get_config_var('CONFIG_ARGS') or ''
-    cflags = sysconfig.get_config_var('PY_CFLAGS') or ''
-    cflags += ' ' + (sysconfig.get_config_var('PY_CFLAGS_NODIST') or '')
     ldflags_nodist = sysconfig.get_config_var('PY_LDFLAGS_NODIST') or ''
 
     build = []
@@ -342,18 +339,16 @@ def get_build_info():
             free_threading = f"{free_threading} GIL={int(PYTHON_GIL)}"
         build.append(free_threading)
 
-    if hasattr(sys, 'gettotalrefcount'):
+    if support.Py_DEBUG:
         # --with-pydebug
         build.append('debug')
 
-        if '-DNDEBUG' in cflags:
+        if not support.built_with_c_assertions():
             build.append('without_assert')
     else:
         build.append('release')
 
-        if '--with-assertions' in config_args:
-            build.append('with_assert')
-        elif '-DNDEBUG' not in cflags:
+        if support.built_with_c_assertions():
             build.append('with_assert')
 
     # --enable-experimental-jit
diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py
index 02c7f555db7e75a..f3bfa6c0982742c 100644
--- a/Lib/test/pythoninfo.py
+++ b/Lib/test/pythoninfo.py
@@ -613,14 +613,6 @@ def collect_sysconfig(info_add):
         value = normalize_text(value)
         info_add('sysconfig[%s]' % name, value)
 
-    PY_CFLAGS = sysconfig.get_config_var('PY_CFLAGS')
-    NDEBUG = (PY_CFLAGS and '-DNDEBUG' in PY_CFLAGS)
-    if NDEBUG:
-        text = 'ignore assertions (macro defined)'
-    else:
-        text= 'build assertions (macro not defined)'
-    info_add('build.NDEBUG',text)
-
     for name in (
         'WITH_DOC_STRINGS',
         'WITH_DTRACE',
@@ -846,6 +838,8 @@ def collect_support(info_add):
              support.check_sanitizer(memory=True))
     info_add('support.check_sanitizer(ub=True)',
              support.check_sanitizer(ub=True))
+    info_add('support.built_with_c_assertions',
+             support.built_with_c_assertions())
 
 
 def collect_support_os_helper(info_add):
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index 7bb873b41356306..cbfbac23aafdbbe 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -73,7 +73,7 @@
     "in_systemd_nspawn_sync_suppressed",
     "run_no_yield_async_fn", "run_yielding_async_fn", "async_yield",
     "reset_code", "on_github_actions",
-    "requires_root_user", "requires_non_root_user",
+    "requires_root_user", "requires_non_root_user", "built_with_c_assertions",
     ]
 
 
@@ -3443,3 +3443,13 @@ def 
skip_on_low_desktop_heap_memory_subprocess(returncode):
     if returncode == STATUS_DLL_INIT_FAILED:
         raise unittest.SkipTest('gh-150436: DLL init failed, likely because '
                                 'of low desktop heap memory')
+
+
+def built_with_c_assertions():
+    """Check if Python was built with C assertions (assert())."""
+    try:
+        import _testlimitedcapi
+    except ImportError:
+        return Py_DEBUG
+    else:
+        return bool(_testlimitedcapi._py_getbuiltwithassert())
diff --git a/Lib/test/test_capi/test_build.py b/Lib/test/test_capi/test_build.py
new file mode 100644
index 000000000000000..8319ad1ec77da81
--- /dev/null
+++ b/Lib/test/test_capi/test_build.py
@@ -0,0 +1,39 @@
+import sys
+import unittest
+from test.support import import_helper
+
+_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
+
+
+class CAPITest(unittest.TestCase):
+    # Test Py_GetVersion()
+    def test_getversion(self):
+        self.assertEqual(_testlimitedcapi.py_getversion(), sys.version)
+
+    # Test Py_GetPlatform()
+    def test_getplatform(self):
+        self.assertEqual(_testlimitedcapi.py_getplatform(), sys.platform)
+
+    # Test Py_GetCopyright()
+    def test_getcopyright(self):
+        self.assertEqual(_testlimitedcapi.py_getcopyright(), sys.copyright)
+
+    # Test Py_GetCompiler()
+    def test_getcompiler(self):
+        compiler = _testlimitedcapi.py_getcompiler()
+        self.assertIsInstance(compiler, str)
+        self.assertNotEqual(compiler, '')
+
+    # Test Py_GetBuildInfo()
+    def test_getbuildinfo(self):
+        buildinfo = _testlimitedcapi.py_getbuildinfo()
+        self.assertIsInstance(buildinfo, str)
+        self.assertNotEqual(buildinfo, '')
+
+    # Test internal _Py_GetBuiltWithAssert()
+    def test__getbuiltwithassert(self):
+        self.assertIn(_testlimitedcapi._py_getbuiltwithassert(), (0, 1))
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py
index 7e86f6123b970e6..42f422baf539016 100644
--- a/Lib/test/test_gc.py
+++ b/Lib/test/test_gc.py
@@ -12,7 +12,6 @@
 
 import gc
 import sys
-import sysconfig
 import textwrap
 import threading
 import time
@@ -79,13 +78,6 @@ def __init__(self, partner=None):
     def __tp_del__(self):
         pass
 
-if sysconfig.get_config_vars().get('PY_CFLAGS', ''):
-    BUILD_WITH_NDEBUG = ('-DNDEBUG' in 
sysconfig.get_config_vars()['PY_CFLAGS'])
-else:
-    # Usually, sys.gettotalrefcount() is only present if Python has been
-    # compiled in debug mode. If it's missing, expect that Python has
-    # been released in release mode: with NDEBUG defined.
-    BUILD_WITH_NDEBUG = (not hasattr(sys, 'gettotalrefcount'))
 
 ### Tests
 ###############################################################################
@@ -1422,8 +1414,8 @@ def test_collect_garbage(self):
 
 
     @requires_subprocess()
-    @unittest.skipIf(BUILD_WITH_NDEBUG,
-                     'built with -NDEBUG')
+    @unittest.skipIf(not support.built_with_c_assertions(),
+                     'built without C assertions')
     def test_refcount_errors(self):
         self.preclean()
         # Verify the "handling" of objects with broken refcounts
diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py
index ae868bf141eb588..ef9e89fa1578a09 100644
--- a/Lib/test/test_support.py
+++ b/Lib/test/test_support.py
@@ -820,6 +820,10 @@ def test_linked_to_musl(self):
             for v in linked:
                 self.assertIsInstance(v, int)
 
+    def test_built_with_c_assertions(self):
+        # Check that calling the function doesn't raise an exception
+        result = support.built_with_c_assertions()
+        self.assertIsInstance(result, bool)
 
     # XXX -follows a list of untested API
     # make_legacy_pyc
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index 8086a3151564c72..e1bfc8f4e9b9f05 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -176,7 +176,7 @@
 @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
 @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c 
_testinternalcapi/test_lock.c _testinternalcapi/pytime.c 
_testinternalcapi/set.c _testinternalcapi/test_critical_sections.c 
_testinternalcapi/complex.c _testinternalcapi/interpreter.c 
_testinternalcapi/tuple.c
 @MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c 
_testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c 
_testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c 
_testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c 
_testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c 
_testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c 
_testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c 
_testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c 
_testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c 
_testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c 
_testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c
-@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c 
_testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c 
_testlimitedcapi/bytes.c _testlimitedcapi/capsule.c _testlimitedcapi/codec.c 
_testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c 
_testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c 
_testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c 
_testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c 
_testlimitedcapi/slice.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c 
_testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c 
_testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c 
_testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c 
_testlimitedcapi/run.c _testlimitedcapi/type.c
+@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c 
_testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c 
_testlimitedcapi/bytes.c _testlimitedcapi/capsule.c _testlimitedcapi/codec.c 
_testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c 
_testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c 
_testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c 
_testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c 
_testlimitedcapi/slice.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c 
_testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c 
_testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c 
_testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c 
_testlimitedcapi/run.c _testlimitedcapi/type.c _testlimitedcapi/build.c
 @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
 @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
 
diff --git a/Modules/_testlimitedcapi.c b/Modules/_testlimitedcapi.c
index 1165f5317e7e279..7cea4fcf1dcd16f 100644
--- a/Modules/_testlimitedcapi.c
+++ b/Modules/_testlimitedcapi.c
@@ -94,6 +94,9 @@ module_exec(PyObject *mod)
     if (_PyTestLimitedCAPI_Init_Type(mod) < 0) {
         return -1;
     }
+    if (_PyTestLimitedCAPI_Init_Build(mod) < 0) {
+        return -1;
+    }
     return 0;
 }
 
diff --git a/Modules/_testlimitedcapi/build.c b/Modules/_testlimitedcapi/build.c
new file mode 100644
index 000000000000000..ae26fdc07fe9089
--- /dev/null
+++ b/Modules/_testlimitedcapi/build.c
@@ -0,0 +1,78 @@
+#include "parts.h"
+#include "util.h"
+
+
+static PyObject*
+return_string(const char *str)
+{
+    assert(str != NULL);
+    return PyUnicode_FromString(str);
+}
+
+
+/* Test Py_GetVersion() */
+static PyObject*
+py_getversion(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+    return return_string(Py_GetVersion());
+}
+
+
+/* Test Py_GetPlatform() */
+static PyObject*
+py_getplatform(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+    return return_string(Py_GetPlatform());
+}
+
+
+/* Test Py_GetCopyright() */
+static PyObject*
+py_getcopyright(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+    return return_string(Py_GetCopyright());
+}
+
+
+/* Test Py_GetCompiler() */
+static PyObject*
+py_getcompiler(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+    return return_string(Py_GetCompiler());
+}
+
+
+/* Test Py_GetBuildInfo() */
+static PyObject*
+py_getbuildinfo(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+    return return_string(Py_GetBuildInfo());
+}
+
+
+/* Test _Py_GetBuiltWithAssert() */
+static PyObject*
+_py_getbuiltwithassert(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+    // Function only exported for _testlimitedcapi
+    PyAPI_FUNC(int) _Py_GetBuiltWithAssert(void);
+
+    return PyLong_FromLong(_Py_GetBuiltWithAssert());
+}
+
+
+static PyMethodDef test_methods[] = {
+    {"py_getversion", py_getversion, METH_NOARGS},
+    {"py_getplatform", py_getplatform, METH_NOARGS},
+    {"py_getcopyright", py_getcopyright, METH_NOARGS},
+    {"py_getcompiler", py_getcompiler, METH_NOARGS},
+    {"py_getbuildinfo", py_getbuildinfo, METH_NOARGS},
+    {"_py_getbuiltwithassert", _py_getbuiltwithassert, METH_NOARGS},
+    {NULL},
+};
+
+int
+_PyTestLimitedCAPI_Init_Build(PyObject *m)
+{
+    return PyModule_AddFunctions(m, test_methods);
+}
diff --git a/Modules/_testlimitedcapi/parts.h b/Modules/_testlimitedcapi/parts.h
index 95bfbc7e1ff6cbd..477ed02d5cb6b99 100644
--- a/Modules/_testlimitedcapi/parts.h
+++ b/Modules/_testlimitedcapi/parts.h
@@ -52,5 +52,6 @@ int _PyTestLimitedCAPI_Init_File(PyObject *module);
 int _PyTestLimitedCAPI_Init_Weakref(PyObject *module);
 int _PyTestLimitedCAPI_Init_Run(PyObject *module);
 int _PyTestLimitedCAPI_Init_Type(PyObject *module);
+int _PyTestLimitedCAPI_Init_Build(PyObject *module);
 
 #endif // Py_TESTLIMITEDCAPI_PARTS_H
diff --git a/Modules/getbuildinfo.c b/Modules/getbuildinfo.c
index b68f0f5cc56a44c..006043d7dc3ba61 100644
--- a/Modules/getbuildinfo.c
+++ b/Modules/getbuildinfo.c
@@ -77,3 +77,19 @@ _Py_gitidentifier(void)
         gitid = GITBRANCH;
     return gitid;
 }
+
+
+// Check if Python was built with NDEBUG macro defined or not. Implement the
+// function in Modules/getbuildinfo.c so it's built with the same compiler
+// flags than the Python core C code.
+//
+// Export the function for '_testlimitedcapi' shared extension.
+PyAPI_FUNC(int)
+_Py_GetBuiltWithAssert(void)
+{
+#ifdef NDEBUG
+    return 0;
+#else
+    return 1;
+#endif
+}
diff --git a/PCbuild/_testlimitedcapi.vcxproj b/PCbuild/_testlimitedcapi.vcxproj
index 7ccfbc2a0f04b0c..09c7babb7f5382f 100644
--- a/PCbuild/_testlimitedcapi.vcxproj
+++ b/PCbuild/_testlimitedcapi.vcxproj
@@ -122,6 +122,7 @@
     <ClCompile Include="..\Modules\_testlimitedcapi\weakref.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi\run.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi\type.c" />
+    <ClCompile Include="..\Modules\_testlimitedcapi\build.c" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\PC\python_nt.rc" />
diff --git a/PCbuild/_testlimitedcapi.vcxproj.filters 
b/PCbuild/_testlimitedcapi.vcxproj.filters
index e95d9e7862ff13b..cec617d5d14128b 100644
--- a/PCbuild/_testlimitedcapi.vcxproj.filters
+++ b/PCbuild/_testlimitedcapi.vcxproj.filters
@@ -38,6 +38,7 @@
     <ClCompile Include="..\Modules\_testlimitedcapi\weakref.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi\run.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi\type.c" />
+    <ClCompile Include="..\Modules\_testlimitedcapi\build.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi.c" />
   </ItemGroup>
   <ItemGroup>

_______________________________________________
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