Skip to content

Commit 2348711

Browse files
committedMar 15, 2025
Make musl test skips smarter (fixes Alpine errors)
A relatively small number of tests fail when the underlying c library is provided by musl. This was originally reported in bpo-46390 by Christian Heimes. Among other changes, these tests were marked for skipping in pythongh-31947/ef1327e3 as part of bpo-40280 (emscripten support), but the skips were conditioned on the *platform* being emscripten (or wasi, skips for which ere added in 9b50585). In pythongh-131071 Victor Stinner added a linked_to_musl function to enable skipping a test in test_math that fails under musl, like it does on a number of other platforms. This check can successfully detect that python is running under musl on Alpine, which was the original problem report in bpo-46390. This PR replaces Victor's solution with an enhancement to platform.libc_ver that does the check more cheaply, and also gets the version number. The latter is important because the math test being skipped is due to a bug in musl that has been fixed, but as of this checkin date has not yet been released. When it is, the test skip can be fixed to check for the minimum needed version. The enhanced version of linked_to_musl is also used to do the skips of the other tests that generically fail under musl, as opposed to emscripten or wasi only failures. This will allow these tests to be skipped automatically on Alpine. This PR does *not* enhance libc_ver to support emscripten and wasi, as I'm not familiar with those platforms; instead it returns a version triple of (0, 0, 0) for those platforms. This means the musl tests will be skipped regardless of musl version, so ideally someone will add support to libc_ver for these platforms.
1 parent 55815a6 commit 2348711

9 files changed

+72
-63
lines changed
 

‎Lib/platform.py

+14-6
Original file line numberDiff line numberDiff line change
@@ -189,11 +189,16 @@ def libc_ver(executable=None, lib='', version='', chunksize=16384):
189189
# sys.executable is not set.
190190
return lib, version
191191

192-
libc_search = re.compile(b'(__libc_init)'
193-
b'|'
194-
b'(GLIBC_([0-9.]+))'
195-
b'|'
196-
br'(libc(_\w+)?\.so(?:\.(\d[0-9.]*))?)', re.ASCII)
192+
libc_search = re.compile(
193+
b'(__libc_init)'
194+
b'|'
195+
b'(GLIBC_([0-9.]+))'
196+
b'|'
197+
br'(libc(_\w+)?\.so(?:\.(\d[0-9.]*))?)'
198+
b'|'
199+
b'(musl-([0-9.]+))'
200+
b'',
201+
re.ASCII)
197202

198203
V = _comparable_version
199204
# We use os.path.realpath()
@@ -216,7 +221,7 @@ def libc_ver(executable=None, lib='', version='', chunksize=16384):
216221
continue
217222
if not m:
218223
break
219-
libcinit, glibc, glibcversion, so, threads, soversion = [
224+
libcinit, glibc, glibcversion, so, threads, soversion, musl, muslversion = [
220225
s.decode('latin1') if s is not None else s
221226
for s in m.groups()]
222227
if libcinit and not lib:
@@ -234,6 +239,9 @@ def libc_ver(executable=None, lib='', version='', chunksize=16384):
234239
version = soversion
235240
if threads and version[-len(threads):] != threads:
236241
version = version + threads
242+
elif musl:
243+
lib = 'musl'
244+
version = muslversion
237245
pos = m.end()
238246
return lib, version
239247

‎Lib/test/support/__init__.py

+23-12
Original file line numberDiff line numberDiff line change
@@ -3017,20 +3017,31 @@ def is_libssl_fips_mode():
30173017
return get_fips_mode() != 0
30183018

30193019

3020+
_linked_to_musl = None
30203021
def linked_to_musl():
30213022
"""
3022-
Test if the Python executable is linked to the musl C library.
3023+
Report if the Python executable is linked to the musl C library.
3024+
3025+
Return False if we don't think it is, or a version triple otherwise.
30233026
"""
3027+
# This is can be a relatively expensive check, so we use a cache.
3028+
global _linked_to_musl
3029+
if _linked_to_musl is not None:
3030+
return _linked_to_musl
3031+
3032+
# emscripten (at least as far as we're concerned) and wasi use musl,
3033+
# but platform doesn't know how to get the version, so set it to zero.
3034+
if is_emscripten or is_wasi:
3035+
return (_linked_to_musl := (0, 0, 0))
3036+
3037+
# On all other non-linux platforms assume no musl.
30243038
if sys.platform != 'linux':
3025-
return False
3039+
return (_linked_to_musl := False)
30263040

3027-
import subprocess
3028-
exe = getattr(sys, '_base_executable', sys.executable)
3029-
cmd = ['ldd', exe]
3030-
try:
3031-
stdout = subprocess.check_output(cmd,
3032-
text=True,
3033-
stderr=subprocess.STDOUT)
3034-
except (OSError, subprocess.CalledProcessError):
3035-
return False
3036-
return ('musl' in stdout)
3041+
# On linux, we'll depend on the platform module to do the check, so new
3042+
# musl platforms should add support in that module if possible.
3043+
import platform
3044+
lib, version = platform.libc_ver()
3045+
if lib != 'musl':
3046+
return (_linked_to_musl := False)
3047+
return (_linked_to_musl := tuple(map(int, version.split('.'))))

‎Lib/test/test__locale.py

+4-16
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,7 @@ def numeric_tester(self, calc_type, calc_value, data_type, used_locale):
137137
return True
138138

139139
@unittest.skipUnless(nl_langinfo, "nl_langinfo is not available")
140-
@unittest.skipIf(
141-
support.is_emscripten or support.is_wasi,
142-
"musl libc issue on Emscripten, bpo-46390"
143-
)
140+
@unittest.skipIf(support.linked_to_musl(), "musl libc issue, bpo-46390")
144141
def test_lc_numeric_nl_langinfo(self):
145142
# Test nl_langinfo against known values
146143
tested = False
@@ -158,10 +155,7 @@ def test_lc_numeric_nl_langinfo(self):
158155
if not tested:
159156
self.skipTest('no suitable locales')
160157

161-
@unittest.skipIf(
162-
support.is_emscripten or support.is_wasi,
163-
"musl libc issue on Emscripten, bpo-46390"
164-
)
158+
@unittest.skipIf(support.linked_to_musl(), "musl libc issue, bpo-46390")
165159
def test_lc_numeric_localeconv(self):
166160
# Test localeconv against known values
167161
tested = False
@@ -210,10 +204,7 @@ def test_lc_numeric_basic(self):
210204

211205
@unittest.skipUnless(nl_langinfo, "nl_langinfo is not available")
212206
@unittest.skipUnless(hasattr(locale, 'ALT_DIGITS'), "requires locale.ALT_DIGITS")
213-
@unittest.skipIf(
214-
support.is_emscripten or support.is_wasi,
215-
"musl libc issue on Emscripten, bpo-46390"
216-
)
207+
@unittest.skipIf(support.linked_to_musl(), "musl libc issue, bpo-46390")
217208
def test_alt_digits_nl_langinfo(self):
218209
# Test nl_langinfo(ALT_DIGITS)
219210
tested = False
@@ -245,10 +236,7 @@ def test_alt_digits_nl_langinfo(self):
245236

246237
@unittest.skipUnless(nl_langinfo, "nl_langinfo is not available")
247238
@unittest.skipUnless(hasattr(locale, 'ERA'), "requires locale.ERA")
248-
@unittest.skipIf(
249-
support.is_emscripten or support.is_wasi,
250-
"musl libc issue on Emscripten, bpo-46390"
251-
)
239+
@unittest.skipIf(support.linked_to_musl(), "musl libc issue, bpo-46390")
252240
def test_era_nl_langinfo(self):
253241
# Test nl_langinfo(ERA)
254242
tested = False

‎Lib/test/test_locale.py

+3-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from decimal import Decimal
2-
from test.support import verbose, is_android, is_emscripten, is_wasi, os_helper
2+
from test.support import verbose, is_android, linked_to_musl, os_helper
33
from test.support.warnings_helper import check_warnings
44
from test.support.import_helper import import_fresh_module
55
from unittest import mock
@@ -351,21 +351,15 @@ def setUp(self):
351351

352352
@unittest.skipIf(sys.platform.startswith('aix'),
353353
'bpo-29972: broken test on AIX')
354-
@unittest.skipIf(
355-
is_emscripten or is_wasi,
356-
"musl libc issue on Emscripten/WASI, bpo-46390"
357-
)
354+
@unittest.skipIf(linked_to_musl(), "musl libc issue, bpo-46390")
358355
@unittest.skipIf(sys.platform.startswith("netbsd"),
359356
"gh-124108: NetBSD doesn't support UTF-8 for LC_COLLATE")
360357
def test_strcoll_with_diacritic(self):
361358
self.assertLess(locale.strcoll('à', 'b'), 0)
362359

363360
@unittest.skipIf(sys.platform.startswith('aix'),
364361
'bpo-29972: broken test on AIX')
365-
@unittest.skipIf(
366-
is_emscripten or is_wasi,
367-
"musl libc issue on Emscripten/WASI, bpo-46390"
368-
)
362+
@unittest.skipIf(linked_to_musl(), "musl libc issue, bpo-46390")
369363
@unittest.skipIf(sys.platform.startswith("netbsd"),
370364
"gh-124108: NetBSD doesn't support UTF-8 for LC_COLLATE")
371365
def test_strxfrm_with_diacritic(self):

‎Lib/test/test_math.py

+3
Original file line numberDiff line numberDiff line change
@@ -2772,6 +2772,9 @@ def test_fma_infinities(self):
27722772
or (sys.platform == "android" and platform.machine() == "x86_64")
27732773
or support.linked_to_musl(), # gh-131032
27742774
f"this platform doesn't implement IEE 754-2008 properly")
2775+
# XXX musl is fixed but the fix is not yet released; when the fixed version
2776+
# is known change this to:
2777+
# or support.linked_to_musl() < (1, <m>, <p>)
27752778
def test_fma_zero_result(self):
27762779
nonnegative_finites = [0.0, 1e-300, 2.3, 1e300]
27772780

‎Lib/test/test_os.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -2555,15 +2555,18 @@ def test_fchown(self):
25552555
@unittest.skipUnless(hasattr(os, 'fpathconf'), 'test needs os.fpathconf()')
25562556
def test_fpathconf(self):
25572557
self.assertIn("PC_NAME_MAX", os.pathconf_names)
2558-
if not (support.is_emscripten or support.is_wasi):
2559-
# musl libc pathconf ignores the file descriptor and always returns
2560-
# a constant, so the assertion that it should notice a bad file
2561-
# descriptor and return EBADF fails.
2562-
self.check(os.pathconf, "PC_NAME_MAX")
2563-
self.check(os.fpathconf, "PC_NAME_MAX")
25642558
self.check_bool(os.pathconf, "PC_NAME_MAX")
25652559
self.check_bool(os.fpathconf, "PC_NAME_MAX")
25662560

2561+
@unittest.skipUnless(hasattr(os, 'fpathconf'), 'test needs os.fpathconf()')
2562+
@unittest.skipIf(
2563+
support.linked_to_musl(),
2564+
'musl pathconf ignores the file descriptor and returns a constant',
2565+
)
2566+
def test_fpathconf_with_bad_fd(self):
2567+
self.check(os.pathconf, "PC_NAME_MAX")
2568+
self.check(os.fpathconf, "PC_NAME_MAX")
2569+
25672570
@unittest.skipUnless(hasattr(os, 'ftruncate'), 'test needs os.ftruncate()')
25682571
def test_ftruncate(self):
25692572
self.check(os.truncate, 0)

‎Lib/test/test_re.py

+3-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from test.support import (gc_collect, bigmemtest, _2G,
22
cpython_only, captured_stdout,
3-
check_disallow_instantiation, is_emscripten, is_wasi,
3+
check_disallow_instantiation, linked_to_musl,
44
warnings_helper, SHORT_TIMEOUT, CPUStopwatch, requires_resource)
55
import locale
66
import re
@@ -2172,10 +2172,7 @@ def test_bug_20998(self):
21722172
# with ignore case.
21732173
self.assertEqual(re.fullmatch('[a-c]+', 'ABC', re.I).span(), (0, 3))
21742174

2175-
@unittest.skipIf(
2176-
is_emscripten or is_wasi,
2177-
"musl libc issue on Emscripten/WASI, bpo-46390"
2178-
)
2175+
@unittest.skipIf(linked_to_musl(), "musl libc issue, bpo-46390")
21792176
def test_locale_caching(self):
21802177
# Issue #22410
21812178
oldlocale = locale.setlocale(locale.LC_CTYPE)
@@ -2212,10 +2209,7 @@ def check_en_US_utf8(self):
22122209
self.assertIsNone(re.match(b'(?Li)\xc5', b'\xe5'))
22132210
self.assertIsNone(re.match(b'(?Li)\xe5', b'\xc5'))
22142211

2215-
@unittest.skipIf(
2216-
is_emscripten or is_wasi,
2217-
"musl libc issue on Emscripten/WASI, bpo-46390"
2218-
)
2212+
@unittest.skipIf(linked_to_musl(), "musl libc issue, bpo-46390")
22192213
def test_locale_compiled(self):
22202214
oldlocale = locale.setlocale(locale.LC_CTYPE)
22212215
self.addCleanup(locale.setlocale, locale.LC_CTYPE, oldlocale)

‎Lib/test/test_strptime.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -544,10 +544,7 @@ def test_date_locale(self):
544544
self.roundtrip('%x', slice(0, 3), time.localtime(now - 366*24*3600))
545545

546546
# NB: Dates before 1969 do not roundtrip on many locales, including C.
547-
@unittest.skipIf(
548-
support.is_emscripten or support.is_wasi,
549-
"musl libc issue on Emscripten, bpo-46390"
550-
)
547+
@unittest.skipIf(support.linked_to_musl(), "musl libc issue, bpo-46390")
551548
@run_with_locales('LC_TIME', 'en_US', 'fr_FR', 'de_DE', 'ja_JP',
552549
'eu_ES', 'ar_AE', 'my_MM', 'shn_MM')
553550
def test_date_locale2(self):

‎Lib/test/test_support.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,18 @@ def test_get_signal_name(self):
746746

747747
def test_linked_to_musl(self):
748748
linked = support.linked_to_musl()
749-
self.assertIsInstance(linked, bool)
749+
self.assertIsNotNone(linked)
750+
if support.is_wasi:
751+
self.assertTrue(linked)
752+
# The value is cached, so make sure it returns the same value again.
753+
self.assertEqual(linked, support.linked_to_musl())
754+
# The unlike libc, the musl version is a triple.
755+
if linked:
756+
self.assertIsInstance(linked, tuple)
757+
self.assertEqual(3, len(linked))
758+
for v in linked:
759+
self.assertIsInstance(v, int)
760+
750761

751762
# XXX -follows a list of untested API
752763
# make_legacy_pyc

0 commit comments

Comments
 (0)
Please sign in to comment.