Skip to content

Commit cb7a629

Browse files
[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 a831abb) * gh-155742: Add support.built_with_c_assertions() (#156776) Co-authored-by: Stan Ulbrych <stan@python.org> (cherry picked from commit c700121) --------- Co-authored-by: Stan Ulbrych <stan@python.org>
1 parent 173dabe commit cb7a629

13 files changed

Lines changed: 162 additions & 28 deletions

File tree

‎Lib/test/libregrtest/utils.py‎

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -321,9 +321,6 @@ def get_build_info():
321321
# Get most important configure and build options as a list of strings.
322322
# Example: ['debug', 'ASAN+MSAN'] or ['release', 'LTO+PGO'].
323323

324-
config_args = sysconfig.get_config_var('CONFIG_ARGS') or ''
325-
cflags = sysconfig.get_config_var('PY_CFLAGS') or ''
326-
cflags += ' ' + (sysconfig.get_config_var('PY_CFLAGS_NODIST') or '')
327324
ldflags_nodist = sysconfig.get_config_var('PY_LDFLAGS_NODIST') or ''
328325

329326
build = []
@@ -342,18 +339,16 @@ def get_build_info():
342339
free_threading = f"{free_threading} GIL={int(PYTHON_GIL)}"
343340
build.append(free_threading)
344341

345-
if hasattr(sys, 'gettotalrefcount'):
342+
if support.Py_DEBUG:
346343
# --with-pydebug
347344
build.append('debug')
348345

349-
if '-DNDEBUG' in cflags:
346+
if not support.built_with_c_assertions():
350347
build.append('without_assert')
351348
else:
352349
build.append('release')
353350

354-
if '--with-assertions' in config_args:
355-
build.append('with_assert')
356-
elif '-DNDEBUG' not in cflags:
351+
if support.built_with_c_assertions():
357352
build.append('with_assert')
358353

359354
# --enable-experimental-jit

‎Lib/test/pythoninfo.py‎

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -613,14 +613,6 @@ def collect_sysconfig(info_add):
613613
value = normalize_text(value)
614614
info_add('sysconfig[%s]' % name, value)
615615

616-
PY_CFLAGS = sysconfig.get_config_var('PY_CFLAGS')
617-
NDEBUG = (PY_CFLAGS and '-DNDEBUG' in PY_CFLAGS)
618-
if NDEBUG:
619-
text = 'ignore assertions (macro defined)'
620-
else:
621-
text= 'build assertions (macro not defined)'
622-
info_add('build.NDEBUG',text)
623-
624616
for name in (
625617
'WITH_DOC_STRINGS',
626618
'WITH_DTRACE',
@@ -846,6 +838,8 @@ def collect_support(info_add):
846838
support.check_sanitizer(memory=True))
847839
info_add('support.check_sanitizer(ub=True)',
848840
support.check_sanitizer(ub=True))
841+
info_add('support.built_with_c_assertions',
842+
support.built_with_c_assertions())
849843

850844

851845
def collect_support_os_helper(info_add):

‎Lib/test/support/__init__.py‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
"in_systemd_nspawn_sync_suppressed",
7474
"run_no_yield_async_fn", "run_yielding_async_fn", "async_yield",
7575
"reset_code", "on_github_actions",
76-
"requires_root_user", "requires_non_root_user",
76+
"requires_root_user", "requires_non_root_user", "built_with_c_assertions",
7777
]
7878

7979

@@ -3443,3 +3443,13 @@ def skip_on_low_desktop_heap_memory_subprocess(returncode):
34433443
if returncode == STATUS_DLL_INIT_FAILED:
34443444
raise unittest.SkipTest('gh-150436: DLL init failed, likely because '
34453445
'of low desktop heap memory')
3446+
3447+
3448+
def built_with_c_assertions():
3449+
"""Check if Python was built with C assertions (assert())."""
3450+
try:
3451+
import _testlimitedcapi
3452+
except ImportError:
3453+
return Py_DEBUG
3454+
else:
3455+
return bool(_testlimitedcapi._py_getbuiltwithassert())

‎Lib/test/test_capi/test_build.py‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import sys
2+
import unittest
3+
from test.support import import_helper
4+
5+
_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
6+
7+
8+
class CAPITest(unittest.TestCase):
9+
# Test Py_GetVersion()
10+
def test_getversion(self):
11+
self.assertEqual(_testlimitedcapi.py_getversion(), sys.version)
12+
13+
# Test Py_GetPlatform()
14+
def test_getplatform(self):
15+
self.assertEqual(_testlimitedcapi.py_getplatform(), sys.platform)
16+
17+
# Test Py_GetCopyright()
18+
def test_getcopyright(self):
19+
self.assertEqual(_testlimitedcapi.py_getcopyright(), sys.copyright)
20+
21+
# Test Py_GetCompiler()
22+
def test_getcompiler(self):
23+
compiler = _testlimitedcapi.py_getcompiler()
24+
self.assertIsInstance(compiler, str)
25+
self.assertNotEqual(compiler, '')
26+
27+
# Test Py_GetBuildInfo()
28+
def test_getbuildinfo(self):
29+
buildinfo = _testlimitedcapi.py_getbuildinfo()
30+
self.assertIsInstance(buildinfo, str)
31+
self.assertNotEqual(buildinfo, '')
32+
33+
# Test internal _Py_GetBuiltWithAssert()
34+
def test__getbuiltwithassert(self):
35+
self.assertIn(_testlimitedcapi._py_getbuiltwithassert(), (0, 1))
36+
37+
38+
if __name__ == "__main__":
39+
unittest.main()

‎Lib/test/test_gc.py‎

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
import gc
1414
import sys
15-
import sysconfig
1615
import textwrap
1716
import threading
1817
import time
@@ -79,13 +78,6 @@ def __init__(self, partner=None):
7978
def __tp_del__(self):
8079
pass
8180

82-
if sysconfig.get_config_vars().get('PY_CFLAGS', ''):
83-
BUILD_WITH_NDEBUG = ('-DNDEBUG' in sysconfig.get_config_vars()['PY_CFLAGS'])
84-
else:
85-
# Usually, sys.gettotalrefcount() is only present if Python has been
86-
# compiled in debug mode. If it's missing, expect that Python has
87-
# been released in release mode: with NDEBUG defined.
88-
BUILD_WITH_NDEBUG = (not hasattr(sys, 'gettotalrefcount'))
8981

9082
### Tests
9183
###############################################################################
@@ -1422,8 +1414,8 @@ def test_collect_garbage(self):
14221414

14231415

14241416
@requires_subprocess()
1425-
@unittest.skipIf(BUILD_WITH_NDEBUG,
1426-
'built with -NDEBUG')
1417+
@unittest.skipIf(not support.built_with_c_assertions(),
1418+
'built without C assertions')
14271419
def test_refcount_errors(self):
14281420
self.preclean()
14291421
# Verify the "handling" of objects with broken refcounts

‎Lib/test/test_support.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,10 @@ def test_linked_to_musl(self):
820820
for v in linked:
821821
self.assertIsInstance(v, int)
822822

823+
def test_built_with_c_assertions(self):
824+
# Check that calling the function doesn't raise an exception
825+
result = support.built_with_c_assertions()
826+
self.assertIsInstance(result, bool)
823827

824828
# XXX -follows a list of untested API
825829
# make_legacy_pyc

‎Modules/Setup.stdlib.in‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@
176176
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
177177
@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
178178
@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
179-
@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
179+
@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
180180
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
181181
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
182182

‎Modules/_testlimitedcapi.c‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ module_exec(PyObject *mod)
9494
if (_PyTestLimitedCAPI_Init_Type(mod) < 0) {
9595
return -1;
9696
}
97+
if (_PyTestLimitedCAPI_Init_Build(mod) < 0) {
98+
return -1;
99+
}
97100
return 0;
98101
}
99102

‎Modules/_testlimitedcapi/build.c‎

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#include "parts.h"
2+
#include "util.h"
3+
4+
5+
static PyObject*
6+
return_string(const char *str)
7+
{
8+
assert(str != NULL);
9+
return PyUnicode_FromString(str);
10+
}
11+
12+
13+
/* Test Py_GetVersion() */
14+
static PyObject*
15+
py_getversion(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
16+
{
17+
return return_string(Py_GetVersion());
18+
}
19+
20+
21+
/* Test Py_GetPlatform() */
22+
static PyObject*
23+
py_getplatform(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
24+
{
25+
return return_string(Py_GetPlatform());
26+
}
27+
28+
29+
/* Test Py_GetCopyright() */
30+
static PyObject*
31+
py_getcopyright(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
32+
{
33+
return return_string(Py_GetCopyright());
34+
}
35+
36+
37+
/* Test Py_GetCompiler() */
38+
static PyObject*
39+
py_getcompiler(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
40+
{
41+
return return_string(Py_GetCompiler());
42+
}
43+
44+
45+
/* Test Py_GetBuildInfo() */
46+
static PyObject*
47+
py_getbuildinfo(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
48+
{
49+
return return_string(Py_GetBuildInfo());
50+
}
51+
52+
53+
/* Test _Py_GetBuiltWithAssert() */
54+
static PyObject*
55+
_py_getbuiltwithassert(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
56+
{
57+
// Function only exported for _testlimitedcapi
58+
PyAPI_FUNC(int) _Py_GetBuiltWithAssert(void);
59+
60+
return PyLong_FromLong(_Py_GetBuiltWithAssert());
61+
}
62+
63+
64+
static PyMethodDef test_methods[] = {
65+
{"py_getversion", py_getversion, METH_NOARGS},
66+
{"py_getplatform", py_getplatform, METH_NOARGS},
67+
{"py_getcopyright", py_getcopyright, METH_NOARGS},
68+
{"py_getcompiler", py_getcompiler, METH_NOARGS},
69+
{"py_getbuildinfo", py_getbuildinfo, METH_NOARGS},
70+
{"_py_getbuiltwithassert", _py_getbuiltwithassert, METH_NOARGS},
71+
{NULL},
72+
};
73+
74+
int
75+
_PyTestLimitedCAPI_Init_Build(PyObject *m)
76+
{
77+
return PyModule_AddFunctions(m, test_methods);
78+
}

‎Modules/_testlimitedcapi/parts.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,6 @@ int _PyTestLimitedCAPI_Init_File(PyObject *module);
5252
int _PyTestLimitedCAPI_Init_Weakref(PyObject *module);
5353
int _PyTestLimitedCAPI_Init_Run(PyObject *module);
5454
int _PyTestLimitedCAPI_Init_Type(PyObject *module);
55+
int _PyTestLimitedCAPI_Init_Build(PyObject *module);
5556

5657
#endif // Py_TESTLIMITEDCAPI_PARTS_H

0 commit comments

Comments
 (0)