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 @@ + 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 @@ +