forked from pypa/packaging
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathtest_musllinux.py
146 lines (121 loc) · 4.29 KB
/
test_musllinux.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import collections
import io
import pathlib
import struct
import subprocess
import pretend
import pytest
from packvers import _musllinux
from packvers._musllinux import (
_get_musl_version,
_MuslVersion,
_parse_ld_musl_from_elf,
_parse_musl_version,
)
MUSL_AMD64 = "musl libc (x86_64)\nVersion 1.2.2\n"
MUSL_I386 = "musl libc (i386)\nVersion 1.2.1\n"
MUSL_AARCH64 = "musl libc (aarch64)\nVersion 1.1.24\n"
MUSL_INVALID = "musl libc (invalid)\n"
MUSL_UNKNOWN = "musl libc (unknown)\nVersion unknown\n"
MUSL_DIR = pathlib.Path(__file__).with_name("musllinux").resolve()
BIN_GLIBC_X86_64 = MUSL_DIR.joinpath("glibc-x86_64")
BIN_MUSL_X86_64 = MUSL_DIR.joinpath("musl-x86_64")
BIN_MUSL_I386 = MUSL_DIR.joinpath("musl-i386")
BIN_MUSL_AARCH64 = MUSL_DIR.joinpath("musl-aarch64")
LD_MUSL_X86_64 = "/lib/ld-musl-x86_64.so.1"
LD_MUSL_I386 = "/lib/ld-musl-i386.so.1"
LD_MUSL_AARCH64 = "/lib/ld-musl-aarch64.so.1"
@pytest.fixture(autouse=True)
def clear_lru_cache():
yield
_get_musl_version.cache_clear()
@pytest.mark.parametrize(
"output, version",
[
(MUSL_AMD64, _MuslVersion(1, 2)),
(MUSL_I386, _MuslVersion(1, 2)),
(MUSL_AARCH64, _MuslVersion(1, 1)),
(MUSL_INVALID, None),
(MUSL_UNKNOWN, None),
],
ids=["amd64-1.2.2", "i386-1.2.1", "aarch64-1.1.24", "invalid", "unknown"],
)
def test_parse_musl_version(output, version):
assert _parse_musl_version(output) == version
@pytest.mark.parametrize(
"executable, location",
[
(BIN_GLIBC_X86_64, None),
(BIN_MUSL_X86_64, LD_MUSL_X86_64),
(BIN_MUSL_I386, LD_MUSL_I386),
(BIN_MUSL_AARCH64, LD_MUSL_AARCH64),
],
ids=["glibc", "x86_64", "i386", "aarch64"],
)
def test_parse_ld_musl_from_elf(executable, location):
with executable.open("rb") as f:
assert _parse_ld_musl_from_elf(f) == location
@pytest.mark.parametrize(
"data",
[
# Too short for magic.
b"\0",
# Enough for magic, but not ELF.
b"#!/bin/bash" + b"\0" * 16,
# ELF, but unknown byte declaration.
b"\x7fELF\3" + b"\0" * 16,
],
ids=["no-magic", "wrong-magic", "unknown-format"],
)
def test_parse_ld_musl_from_elf_invalid(data):
assert _parse_ld_musl_from_elf(io.BytesIO(data)) is None
@pytest.mark.parametrize(
"head",
[
25, # Enough for magic, but not the section definitions.
58, # Enough for section definitions, but not the actual sections.
],
)
def test_parse_ld_musl_from_elf_invalid_section(head):
data = BIN_MUSL_X86_64.read_bytes()[:head]
assert _parse_ld_musl_from_elf(io.BytesIO(data)) is None
def test_parse_ld_musl_from_elf_no_interpreter_section():
with BIN_MUSL_X86_64.open("rb") as f:
data = f.read()
# Change all sections to *not* PT_INTERP.
unpacked = struct.unpack("16BHHIQQQIHHH", data[:58])
*_, e_phoff, _, _, _, e_phentsize, e_phnum = unpacked
for i in range(e_phnum + 1):
sb = e_phoff + e_phentsize * i
se = sb + 56
section = struct.unpack("IIQQQQQQ", data[sb:se])
data = data[:sb] + struct.pack("IIQQQQQQ", 0, *section[1:]) + data[se:]
assert _parse_ld_musl_from_elf(io.BytesIO(data)) is None
@pytest.mark.parametrize(
"executable, output, version, ld_musl",
[
(MUSL_DIR.joinpath("does-not-exist"), "error", None, None),
(BIN_GLIBC_X86_64, "error", None, None),
(BIN_MUSL_X86_64, MUSL_AMD64, _MuslVersion(1, 2), LD_MUSL_X86_64),
(BIN_MUSL_I386, MUSL_I386, _MuslVersion(1, 2), LD_MUSL_I386),
(BIN_MUSL_AARCH64, MUSL_AARCH64, _MuslVersion(1, 1), LD_MUSL_AARCH64),
],
ids=["does-not-exist", "glibc", "x86_64", "i386", "aarch64"],
)
def test_get_musl_version(monkeypatch, executable, output, version, ld_musl):
def mock_run(*args, **kwargs):
return collections.namedtuple("Proc", "stderr")(output)
run_recorder = pretend.call_recorder(mock_run)
monkeypatch.setattr(_musllinux.subprocess, "run", run_recorder)
assert _get_musl_version(str(executable)) == version
if ld_musl is not None:
expected_calls = [
pretend.call(
[ld_musl],
stderr=subprocess.PIPE,
universal_newlines=True,
)
]
else:
expected_calls = []
assert run_recorder.calls == expected_calls