-
-
Notifications
You must be signed in to change notification settings - Fork 32.1k
gh-90548: Make musl test skips smarter (fixes Alpine errors) #131313
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
2348711
35960c1
417143e
ef36eb2
909ea3b
4e76d78
83add5d
4d6aa92
111e2ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -189,22 +189,28 @@ def libc_ver(executable=None, lib='', version='', chunksize=16384): | |||||||||||||||||||||||||||||||||||
# sys.executable is not set. | ||||||||||||||||||||||||||||||||||||
return lib, version | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
libc_search = re.compile(b'(__libc_init)' | ||||||||||||||||||||||||||||||||||||
b'|' | ||||||||||||||||||||||||||||||||||||
b'(GLIBC_([0-9.]+))' | ||||||||||||||||||||||||||||||||||||
b'|' | ||||||||||||||||||||||||||||||||||||
br'(libc(_\w+)?\.so(?:\.(\d[0-9.]*))?)', re.ASCII) | ||||||||||||||||||||||||||||||||||||
libc_search = re.compile( | ||||||||||||||||||||||||||||||||||||
br'(__libc_init)' | ||||||||||||||||||||||||||||||||||||
br'|' | ||||||||||||||||||||||||||||||||||||
br'(GLIBC_([0-9.]+))' | ||||||||||||||||||||||||||||||||||||
br'|' | ||||||||||||||||||||||||||||||||||||
br'(libc(_\w+)?\.so(?:\.(\d[0-9.]*))?)' | ||||||||||||||||||||||||||||||||||||
br'|' | ||||||||||||||||||||||||||||||||||||
br'(musl-([0-9.]+))' | ||||||||||||||||||||||||||||||||||||
br'', | ||||||||||||||||||||||||||||||||||||
picnixz marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||
re.ASCII) | ||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BTW, we can switch to the VERBOSE mode, which would be slightly more clear.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like that. |
||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
V = _comparable_version | ||||||||||||||||||||||||||||||||||||
# We use os.path.realpath() | ||||||||||||||||||||||||||||||||||||
# here to work around problems with Cygwin not being | ||||||||||||||||||||||||||||||||||||
# able to open symlinks for reading | ||||||||||||||||||||||||||||||||||||
executable = os.path.realpath(executable) | ||||||||||||||||||||||||||||||||||||
ver = None | ||||||||||||||||||||||||||||||||||||
with open(executable, 'rb') as f: | ||||||||||||||||||||||||||||||||||||
binary = f.read(chunksize) | ||||||||||||||||||||||||||||||||||||
pos = 0 | ||||||||||||||||||||||||||||||||||||
while pos < len(binary): | ||||||||||||||||||||||||||||||||||||
if b'libc' in binary or b'GLIBC' in binary: | ||||||||||||||||||||||||||||||||||||
if b'libc' in binary or b'GLIBC' or 'musl' in binary: | ||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, that's a nasty one. The actual fix is |
||||||||||||||||||||||||||||||||||||
m = libc_search.search(binary, pos) | ||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||
m = None | ||||||||||||||||||||||||||||||||||||
|
@@ -216,26 +222,30 @@ def libc_ver(executable=None, lib='', version='', chunksize=16384): | |||||||||||||||||||||||||||||||||||
continue | ||||||||||||||||||||||||||||||||||||
if not m: | ||||||||||||||||||||||||||||||||||||
break | ||||||||||||||||||||||||||||||||||||
libcinit, glibc, glibcversion, so, threads, soversion = [ | ||||||||||||||||||||||||||||||||||||
libcinit, glibc, glibcversion, so, threads, soversion, musl, muslversion = [ | ||||||||||||||||||||||||||||||||||||
s.decode('latin1') if s is not None else s | ||||||||||||||||||||||||||||||||||||
for s in m.groups()] | ||||||||||||||||||||||||||||||||||||
if libcinit and not lib: | ||||||||||||||||||||||||||||||||||||
lib = 'libc' | ||||||||||||||||||||||||||||||||||||
elif glibc: | ||||||||||||||||||||||||||||||||||||
if lib != 'glibc': | ||||||||||||||||||||||||||||||||||||
lib = 'glibc' | ||||||||||||||||||||||||||||||||||||
version = glibcversion | ||||||||||||||||||||||||||||||||||||
elif V(glibcversion) > V(version): | ||||||||||||||||||||||||||||||||||||
version = glibcversion | ||||||||||||||||||||||||||||||||||||
ver = glibcversion | ||||||||||||||||||||||||||||||||||||
elif V(glibcversion) > V(ver): | ||||||||||||||||||||||||||||||||||||
ver = glibcversion | ||||||||||||||||||||||||||||||||||||
elif so: | ||||||||||||||||||||||||||||||||||||
if lib != 'glibc': | ||||||||||||||||||||||||||||||||||||
lib = 'libc' | ||||||||||||||||||||||||||||||||||||
if soversion and (not version or V(soversion) > V(version)): | ||||||||||||||||||||||||||||||||||||
version = soversion | ||||||||||||||||||||||||||||||||||||
if threads and version[-len(threads):] != threads: | ||||||||||||||||||||||||||||||||||||
version = version + threads | ||||||||||||||||||||||||||||||||||||
if soversion and (not ver or V(soversion) > V(ver)): | ||||||||||||||||||||||||||||||||||||
ver = soversion | ||||||||||||||||||||||||||||||||||||
if threads and ver[-len(threads):] != threads: | ||||||||||||||||||||||||||||||||||||
ver = ver + threads | ||||||||||||||||||||||||||||||||||||
elif musl: | ||||||||||||||||||||||||||||||||||||
lib = 'musl' | ||||||||||||||||||||||||||||||||||||
if not ver or V(muslversion) > V(ver): | ||||||||||||||||||||||||||||||||||||
ver = muslversion | ||||||||||||||||||||||||||||||||||||
pos = m.end() | ||||||||||||||||||||||||||||||||||||
return lib, version | ||||||||||||||||||||||||||||||||||||
return lib, version if ver is None else ver | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
def _norm_version(version, build=''): | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3017,20 +3017,31 @@ def is_libssl_fips_mode(): | |
return get_fips_mode() != 0 | ||
|
||
|
||
_linked_to_musl = None | ||
def linked_to_musl(): | ||
""" | ||
Test if the Python executable is linked to the musl C library. | ||
Report if the Python executable is linked to the musl C library. | ||
|
||
Return False if we don't think it is, or a version triple otherwise. | ||
""" | ||
# This is can be a relatively expensive check, so we use a cache. | ||
global _linked_to_musl | ||
if _linked_to_musl is not None: | ||
return _linked_to_musl | ||
|
||
# emscripten (at least as far as we're concerned) and wasi use musl, | ||
# but platform doesn't know how to get the version, so set it to zero. | ||
if is_emscripten or is_wasi: | ||
return (_linked_to_musl := (0, 0, 0)) | ||
picnixz marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not think that it is necessary to use the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is never necessary to use the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer the more verbose style, which is also more sequential and clear. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
||
# On all other non-linux platforms assume no musl. | ||
if sys.platform != 'linux': | ||
return False | ||
return (_linked_to_musl := False) | ||
|
||
import subprocess | ||
exe = getattr(sys, '_base_executable', sys.executable) | ||
cmd = ['ldd', exe] | ||
try: | ||
stdout = subprocess.check_output(cmd, | ||
text=True, | ||
stderr=subprocess.STDOUT) | ||
except (OSError, subprocess.CalledProcessError): | ||
return False | ||
return ('musl' in stdout) | ||
# On linux, we'll depend on the platform module to do the check, so new | ||
# musl platforms should add support in that module if possible. | ||
import platform | ||
lib, version = platform.libc_ver() | ||
if lib != 'musl': | ||
return (_linked_to_musl := False) | ||
return (_linked_to_musl := tuple(map(int, version.split('.')))) |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -2555,15 +2555,18 @@ def test_fchown(self): | |||||
@unittest.skipUnless(hasattr(os, 'fpathconf'), 'test needs os.fpathconf()') | ||||||
def test_fpathconf(self): | ||||||
self.assertIn("PC_NAME_MAX", os.pathconf_names) | ||||||
if not (support.is_emscripten or support.is_wasi): | ||||||
# musl libc pathconf ignores the file descriptor and always returns | ||||||
# a constant, so the assertion that it should notice a bad file | ||||||
# descriptor and return EBADF fails. | ||||||
self.check(os.pathconf, "PC_NAME_MAX") | ||||||
self.check(os.fpathconf, "PC_NAME_MAX") | ||||||
self.check_bool(os.pathconf, "PC_NAME_MAX") | ||||||
self.check_bool(os.fpathconf, "PC_NAME_MAX") | ||||||
|
||||||
@unittest.skipUnless(hasattr(os, 'fpathconf'), 'test needs os.fpathconf()') | ||||||
@unittest.skipIf( | ||||||
support.linked_to_musl(), | ||||||
'musl pathconf ignores the file descriptor and returns a constant', | ||||||
) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer the style I used because it lines up better with how python's statements work (first line only is outdented, lack of outdent signals return to outer logic block). This is accepted by PEP 8, are there additional style constrains on the cpython code base these days? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't seen lots of use of hanging indents in the lib. There is no additional constraints but usually we try to be consistent with the surrounding code when possible. |
||||||
def test_fpathconf_with_bad_fd(self): | ||||||
vstinner marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
self.check(os.pathconf, "PC_NAME_MAX") | ||||||
self.check(os.fpathconf, "PC_NAME_MAX") | ||||||
|
||||||
@unittest.skipUnless(hasattr(os, 'ftruncate'), 'test needs os.ftruncate()') | ||||||
def test_ftruncate(self): | ||||||
self.check(os.truncate, 0) | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -552,6 +552,7 @@ def test_libc_ver(self): | |
(b'GLIBC_2.9', ('glibc', '2.9')), | ||
(b'libc.so.1.2.5', ('libc', '1.2.5')), | ||
(b'libc_pthread.so.1.2.5', ('libc', '1.2.5_pthread')), | ||
(b'/aports/main/musl/src/musl-1.2.5', ('musl', '1.2.5')), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add tests on version with 2 numbers and 4 numbers? Like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I could, but I don't believe musl will ever have such release numbers, so is it worth doing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Parsing a binary file using a regular expression is a strange thing for me. So I prefer to have tests to make sure that the implementation is reliable. The question is more if the regex will match versions with 2 members (x.y) or 4 members (x.y.z.a), or if it only matchs version with 3 members (x.y.z). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair enough, though the existing tests don't do that, nor do they test the no-match cases. I could add such tests, but at that point I'd want to rewrite this test method to split it up into multiple tests, and I don't think that's in scope for this PR :) I'll add the couple of extra checks, though. |
||
(b'', ('', '')), | ||
): | ||
with open(filename, 'wb') as fp: | ||
|
@@ -563,14 +564,29 @@ def test_libc_ver(self): | |
expected) | ||
|
||
# binary containing multiple versions: get the most recent, | ||
# make sure that 1.9 is seen as older than 1.23.4 | ||
chunksize = 16384 | ||
with open(filename, 'wb') as f: | ||
# test match at chunk boundary | ||
f.write(b'x'*(chunksize - 10)) | ||
f.write(b'GLIBC_1.23.4\0GLIBC_1.9\0GLIBC_1.21\0') | ||
self.assertEqual(platform.libc_ver(filename, chunksize=chunksize), | ||
('glibc', '1.23.4')) | ||
# make sure that eg 1.9 is seen as older than 1.23.4, and that | ||
# the arguments don't count even if they are set. | ||
chunksize = 200 | ||
for data, expected in ( | ||
(b'GLIBC_1.23.4\0GLIBC_1.9\0GLIBC_1.21\0', ('glibc', '1.23.4')), | ||
(b'libc.so.2.4\0libc.so.9\0libc.so.23.1\0', ('libc', '23.1')), | ||
(b'musl-1.4.1\0musl-2.1.1\0musl-2.0.1\0', ('musl', '2.1.1')), | ||
(b'no match here, so defaults are used', ('test', '100.1.0')), | ||
): | ||
picnixz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
with open(filename, 'wb') as f: | ||
# test match at chunk boundary | ||
f.write(b'x'*(chunksize - 10)) | ||
f.write(data) | ||
self.assertEqual( | ||
expected, | ||
platform.libc_ver( | ||
filename, | ||
lib='test', | ||
version='100.1.0', | ||
chunksize=chunksize, | ||
), | ||
) | ||
picnixz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
def test_android_ver(self): | ||
res = platform.android_ver() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -544,10 +544,7 @@ def test_date_locale(self): | |
self.roundtrip('%x', slice(0, 3), time.localtime(now - 366*24*3600)) | ||
|
||
# NB: Dates before 1969 do not roundtrip on many locales, including C. | ||
@unittest.skipIf( | ||
support.is_emscripten or support.is_wasi, | ||
"musl libc issue on Emscripten, bpo-46390" | ||
) | ||
@unittest.skipIf(support.linked_to_musl(), "musl libc issue, bpo-46390") | ||
@run_with_locales('LC_TIME', 'en_US', 'fr_FR', 'de_DE', 'ja_JP', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can add the |
||
'eu_ES', 'ar_AE', 'my_MM', 'shn_MM') | ||
def test_date_locale2(self): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -746,7 +746,18 @@ def test_get_signal_name(self): | |
|
||
def test_linked_to_musl(self): | ||
linked = support.linked_to_musl() | ||
self.assertIsInstance(linked, bool) | ||
self.assertIsNotNone(linked) | ||
if support.is_wasi: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about is_emscripten? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I used is_wasi as an example, but it doesn't hurt to add emscripten as well. |
||
self.assertTrue(linked) | ||
vstinner marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# The value is cached, so make sure it returns the same value again. | ||
self.assertEqual(linked, support.linked_to_musl()) | ||
vstinner marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# The unlike libc, the musl version is a triple. | ||
if linked: | ||
self.assertIsInstance(linked, tuple) | ||
self.assertEqual(3, len(linked)) | ||
picnixz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for v in linked: | ||
self.assertIsInstance(v, int) | ||
|
||
|
||
# XXX -follows a list of untested API | ||
# make_legacy_pyc | ||
|
Uh oh!
There was an error while loading. Please reload this page.