Skip to content

Commit 9fa7d94

Browse files
authored
ci: run more integration tests on GitHub Actions (#551)
* ci: run more integration tests on GitHub Actions Add some `armv7l` & `i686` tests on GHA. QEMU `ppc64le` & `s390x` have been tested but are disabled here as Travis CI can do the job natively for now.
1 parent 401d690 commit 9fa7d94

File tree

4 files changed

+103
-58
lines changed

4 files changed

+103
-58
lines changed

.github/workflows/test.yml

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,30 +37,50 @@ jobs:
3737
run: pipx run nox -s test-dist -- 3.9
3838

3939
test:
40-
name: CPython ${{ matrix.python }} on ${{ matrix.runner }}
40+
name: CPython ${{ matrix.python }} ${{ matrix.platform[0] }} on ${{ matrix.platform[1] }}
4141
needs: test-dist
42-
runs-on: ${{ matrix.runner }}
42+
runs-on: ${{ matrix.platform[1] }}
4343
strategy:
4444
fail-fast: false
4545
matrix:
46-
runner: [ ubuntu-24.04, ubuntu-24.04-arm ]
46+
platform:
47+
- [ 'x86_64', 'ubuntu-24.04' ]
4748
python: [ '3.9', '3.10', '3.11', '3.12', '3.13' ]
49+
include:
50+
- platform: [ 'aarch64', 'ubuntu-24.04-arm' ]
51+
python: '3.12'
52+
- platform: [ 'i686', 'ubuntu-24.04' ]
53+
python: '3.12'
54+
- platform: [ 'armv7l', 'ubuntu-24.04-arm' ]
55+
python: '3.12'
56+
#- platform: [ 'ppc64le', 'ubuntu-24.04' ]
57+
# python: '3.12'
58+
# qemu: true
59+
#- platform: [ 's390x', 'ubuntu-24.04' ]
60+
# python: '3.12'
61+
# qemu: true
4862
steps:
4963
- name: Checkout
5064
uses: actions/checkout@v4
5165
- name: Setup cache
5266
uses: actions/cache@v4
5367
with:
5468
path: ~/.cache/auditwheel_tests
55-
key: python${{ matrix.python }}-${{ runner.arch }}-${{ hashFiles('**/test_manylinux.py') }}
56-
restore-keys: python${{ matrix.python }}-${{ runner.arch }}-
69+
key: python${{ matrix.python }}-${{ matrix.platform[0] }}-${{ hashFiles('**/test_manylinux.py') }}
70+
restore-keys: python${{ matrix.python }}-${{ matrix.platform[0] }}-
5771
- name: Install CPython ${{ matrix.python }}
5872
uses: actions/setup-python@v5
5973
with:
6074
python-version: "${{ matrix.python }}"
6175
allow-prereleases: true
76+
- name: Set up QEMU
77+
if: matrix.qemu
78+
uses: docker/setup-qemu-action@v3
6279
- name: Run tests
6380
run: pipx run nox -s tests-${{ matrix.python }}
81+
env:
82+
AUDITWHEEL_ARCH: ${{ matrix.platform[0] }}
83+
AUDITWHEEL_QEMU: ${{ matrix.qemu }}
6484
- name: Upload coverage to codecov
6585
uses: codecov/codecov-action@v5
6686
with:

tests/integration/test_manylinux.py

Lines changed: 58 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,36 +19,47 @@
1919
from elftools.elf.elffile import ELFFile
2020

2121
from auditwheel.architecture import Architecture
22+
from auditwheel.libc import Libc
2223
from auditwheel.policy import WheelPolicies
2324

2425
logger = logging.getLogger(__name__)
2526

26-
ENCODING = "utf-8"
27-
PLATFORM = Architecture.get_native_architecture().value
27+
NATIVE_PLATFORM = Architecture.get_native_architecture().value
28+
PLATFORM = os.environ.get("AUDITWHEEL_ARCH", NATIVE_PLATFORM)
2829
MANYLINUX1_IMAGE_ID = f"quay.io/pypa/manylinux1_{PLATFORM}:latest"
2930
MANYLINUX2010_IMAGE_ID = f"quay.io/pypa/manylinux2010_{PLATFORM}:latest"
3031
MANYLINUX2014_IMAGE_ID = f"quay.io/pypa/manylinux2014_{PLATFORM}:latest"
3132
MANYLINUX_2_28_IMAGE_ID = f"quay.io/pypa/manylinux_2_28_{PLATFORM}:latest"
33+
MANYLINUX_2_31_IMAGE_ID = f"quay.io/pypa/manylinux_2_31_{PLATFORM}:latest"
3234
MANYLINUX_2_34_IMAGE_ID = f"quay.io/pypa/manylinux_2_34_{PLATFORM}:latest"
3335
if PLATFORM in {"i686", "x86_64"}:
3436
MANYLINUX_IMAGES = {
3537
"manylinux_2_5": MANYLINUX1_IMAGE_ID,
3638
"manylinux_2_12": MANYLINUX2010_IMAGE_ID,
3739
"manylinux_2_17": MANYLINUX2014_IMAGE_ID,
38-
"manylinux_2_28": MANYLINUX_2_28_IMAGE_ID,
39-
"manylinux_2_34": MANYLINUX_2_34_IMAGE_ID,
4040
}
41+
if PLATFORM == "x86_64":
42+
MANYLINUX_IMAGES.update(
43+
{
44+
"manylinux_2_28": MANYLINUX_2_28_IMAGE_ID,
45+
"manylinux_2_34": MANYLINUX_2_34_IMAGE_ID,
46+
}
47+
)
4148
POLICY_ALIASES = {
4249
"manylinux_2_5": ["manylinux1"],
4350
"manylinux_2_12": ["manylinux2010"],
4451
"manylinux_2_17": ["manylinux2014"],
4552
}
53+
elif PLATFORM == "armv7l":
54+
MANYLINUX_IMAGES = {"manylinux_2_31": MANYLINUX_2_31_IMAGE_ID}
55+
POLICY_ALIASES = {}
4656
else:
4757
MANYLINUX_IMAGES = {
4858
"manylinux_2_17": MANYLINUX2014_IMAGE_ID,
4959
"manylinux_2_28": MANYLINUX_2_28_IMAGE_ID,
50-
"manylinux_2_34": MANYLINUX_2_34_IMAGE_ID,
5160
}
61+
if os.environ.get("AUDITWHEEL_QEMU", "") != "true":
62+
MANYLINUX_IMAGES.update({"manylinux_2_34": MANYLINUX_2_34_IMAGE_ID})
5263
POLICY_ALIASES = {
5364
"manylinux_2_17": ["manylinux2014"],
5465
}
@@ -67,6 +78,7 @@
6778
"manylinux_2_12": "devtoolset-8",
6879
"manylinux_2_17": "devtoolset-10",
6980
"manylinux_2_28": "gcc-toolset-14",
81+
"manylinux_2_31": "devtoolset-not-present",
7082
"manylinux_2_34": "gcc-toolset-14",
7183
"musllinux_1_2": "devtoolset-not-present",
7284
}
@@ -265,6 +277,12 @@ def docker_start(
265277
client = docker.from_env()
266278

267279
dvolumes = {host: {"bind": ctr, "mode": "rw"} for (ctr, host) in volumes.items()}
280+
goarch = {
281+
"x86_64": "amd64",
282+
"i686": "386",
283+
"aarch64": "arm64",
284+
"armv7l": "arm/v7",
285+
}.get(PLATFORM, PLATFORM)
268286

269287
logger.info("Starting container with image %r", image)
270288
con = client.containers.run(
@@ -273,6 +291,7 @@ def docker_start(
273291
detach=True,
274292
volumes=dvolumes,
275293
environment=env_variables,
294+
platform=f"linux/{goarch}",
276295
)
277296
logger.info("Started container %s", con.id[:12])
278297
return con
@@ -316,7 +335,7 @@ def docker_exec(
316335
) -> str:
317336
logger.info("docker exec %s: %r", container.id[:12], cmd)
318337
ec, output = container.exec_run(cmd, workdir=cwd, environment=env)
319-
output = output.decode(ENCODING)
338+
output = output.decode("utf-8")
320339
if ec != expected_retcode:
321340
print(output)
322341
raise CalledProcessError(ec, cmd, output=output)
@@ -386,12 +405,16 @@ def build_numpy(container: AnyLinuxContainer, output_dir: Path) -> str:
386405
# https://github.com/numpy/numpy/issues/27932
387406
fix_hwcap = "echo '#define HWCAP_S390_VX 2048' >> /usr/include/bits/hwcap.h"
388407
container.exec(f'sh -c "{fix_hwcap}"')
389-
elif container.policy.startswith(("manylinux_2_28_", "manylinux_2_34_")):
390-
container.exec("dnf install -y openblas-devel")
391-
else:
408+
elif container.policy.startswith(
409+
("manylinux_2_5_", "manylinux_2_12_", "manylinux_2_17_")
410+
):
392411
if tuple(int(part) for part in NUMPY_VERSION.split(".")[:2]) >= (1, 26):
393412
pytest.skip("numpy>=1.26 requires openblas")
394413
container.exec("yum install -y atlas atlas-devel")
414+
elif container.policy.startswith("manylinux_2_31_"):
415+
container.exec("apt-get install -y libopenblas-dev")
416+
else:
417+
container.exec("dnf install -y openblas-devel")
395418

396419
cached_wheel = container.cache_dir / ORIGINAL_NUMPY_WHEEL
397420
orig_wheel = output_dir / ORIGINAL_NUMPY_WHEEL
@@ -466,7 +489,6 @@ def test_numpy(self, anylinux: AnyLinuxContainer, python: PythonContainer) -> No
466489
if policy.startswith("musllinux_"):
467490
python.exec("apk add musl-dev gfortran")
468491
else:
469-
python.exec("apt-get update -yqq")
470492
python.exec("apt-get install -y gfortran")
471493
if tuple(int(part) for part in NUMPY_VERSION.split(".")[:2]) >= (1, 26):
472494
python.pip_install("meson ninja")
@@ -498,6 +520,8 @@ def test_with_binary_executable(
498520
policy = anylinux.policy
499521
if policy.startswith("musllinux_"):
500522
anylinux.exec("apk add gsl-dev")
523+
elif policy.startswith("manylinux_2_31_"):
524+
anylinux.exec("apt-get install -y libgsl-dev")
501525
else:
502526
anylinux.exec("yum install -y gsl-devel")
503527

@@ -768,9 +792,8 @@ class TestManylinux(Anylinux):
768792
@pytest.fixture(scope="session")
769793
def docker_python_img(self):
770794
"""The glibc Python base image with up-to-date pip"""
771-
with tmp_docker_image(
772-
MANYLINUX_PYTHON_IMAGE_ID, ["pip install -U pip"]
773-
) as img_id:
795+
commnds = ["pip install -U pip", "apt-get update -yqq"]
796+
with tmp_docker_image(MANYLINUX_PYTHON_IMAGE_ID, commnds) as img_id:
774797
yield img_id
775798

776799
@pytest.fixture(scope="session", params=MANYLINUX_IMAGES.keys())
@@ -780,25 +803,23 @@ def any_manylinux_img(self, request):
780803
Plus up-to-date pip, setuptools and pytest-cov
781804
"""
782805
policy = request.param
783-
support_check_map = {
806+
check_set = {
784807
"manylinux_2_5": {"38", "39"},
785808
"manylinux_2_12": {"38", "39", "310"},
786-
}
787-
check_set = support_check_map.get(policy)
809+
}.get(policy)
788810
if check_set and PYTHON_ABI_MAJ_MIN not in check_set:
789811
pytest.skip(f"{policy} images do not support cp{PYTHON_ABI_MAJ_MIN}")
790812

791813
base = MANYLINUX_IMAGES[policy]
792814
env = {"PATH": PATH[policy]}
793-
with tmp_docker_image(
794-
base,
795-
[
796-
'git config --global --add safe.directory "/auditwheel_src"',
797-
"pip install -U pip setuptools pytest-cov",
798-
"pip install -U -e /auditwheel_src",
799-
],
800-
env,
801-
) as img_id:
815+
commands = [
816+
'git config --global --add safe.directory "/auditwheel_src"',
817+
"pip install -U pip setuptools pytest-cov",
818+
"pip install -U -e /auditwheel_src",
819+
]
820+
if policy == "manylinux_2_31":
821+
commands.append("apt-get update -yqq")
822+
with tmp_docker_image(base, commands, env) as img_id:
802823
yield policy, img_id
803824

804825
@pytest.mark.parametrize("with_dependency", ["0", "1"])
@@ -824,7 +845,7 @@ def test_image_dependencies(
824845
test_path, env={"WITH_DEPENDENCY": with_dependency}
825846
)
826847

827-
wheel_policy = WheelPolicies()
848+
wheel_policy = WheelPolicies(libc=Libc.GLIBC, arch=Architecture(PLATFORM))
828849
policy = wheel_policy.get_policy_by_name(policy_name)
829850
older_policies = [
830851
f"{p}_{PLATFORM}"
@@ -905,7 +926,9 @@ def test_compat(
905926

906927
def test_zlib_blacklist(self, anylinux: AnyLinuxContainer) -> None:
907928
policy = anylinux.policy
908-
if policy.startswith(("manylinux_2_17_", "manylinux_2_28_", "manylinux_2_34_")):
929+
if policy.startswith(
930+
("manylinux_2_17_", "manylinux_2_28_", "manylinux_2_31_", "manylinux_2_34_")
931+
):
909932
pytest.skip(f"{policy} image has no blacklist symbols in libz.so.1")
910933

911934
test_path = "/auditwheel_src/tests/integration/testzlib"
@@ -925,9 +948,8 @@ class TestMusllinux(Anylinux):
925948
@pytest.fixture(scope="session")
926949
def docker_python_img(self):
927950
"""The alpine Python base image with up-to-date pip"""
928-
with tmp_docker_image(
929-
MUSLLINUX_PYTHON_IMAGE_ID, ["pip install -U pip"]
930-
) as img_id:
951+
commands = ["pip install -U pip"]
952+
with tmp_docker_image(MUSLLINUX_PYTHON_IMAGE_ID, commands) as img_id:
931953
yield img_id
932954

933955
@pytest.fixture(scope="session", params=MUSLLINUX_IMAGES.keys())
@@ -939,13 +961,10 @@ def any_manylinux_img(self, request):
939961
policy = request.param
940962
base = MUSLLINUX_IMAGES[policy]
941963
env = {"PATH": PATH[policy]}
942-
with tmp_docker_image(
943-
base,
944-
[
945-
'git config --global --add safe.directory "/auditwheel_src"',
946-
"pip install -U pip setuptools pytest-cov",
947-
"pip install -U -e /auditwheel_src",
948-
],
949-
env,
950-
) as img_id:
964+
commands = [
965+
'git config --global --add safe.directory "/auditwheel_src"',
966+
"pip install -U pip setuptools pytest-cov",
967+
"pip install -U -e /auditwheel_src",
968+
]
969+
with tmp_docker_image(base, commands, env) as img_id:
951970
yield policy, img_id
Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
#include "dependency.h"
2+
#include <errno.h>
23
#include <malloc.h>
3-
#include <stdlib.h>
4-
#include <stdint.h>
54
#include <math.h>
65
#include <pthread.h>
6+
#include <stdint.h>
7+
#include <stdlib.h>
8+
#include <time.h>
9+
#include <unistd.h>
710
#if defined(__GLIBC_PREREQ)
811
#if __GLIBC_PREREQ(2, 28)
912
#include <threads.h>
@@ -13,7 +16,6 @@
1316
int dep_run()
1417
{
1518
#if defined(__GLIBC_PREREQ)
16-
1719
#if __GLIBC_PREREQ(2, 34)
1820
// pthread_mutexattr_init was moved to libc.so.6 in manylinux_2_34+
1921
pthread_mutexattr_t attr;
@@ -22,19 +24,20 @@ int dep_run()
2224
pthread_mutexattr_destroy(&attr);
2325
}
2426
return sts;
25-
#elif defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 28)
27+
#elif __GLIBC_PREREQ(2, 30)
28+
return gettid() == getpid() ? 0 : 1;
29+
#elif __GLIBC_PREREQ(2, 28)
2630
return thrd_equal(thrd_current(), thrd_current()) ? 0 : 1;
27-
#elif defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 24)
31+
#elif __GLIBC_PREREQ(2, 24)
2832
return (int)nextupf(0.0F);
29-
#elif defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 17)
33+
#elif __GLIBC_PREREQ(2, 17)
3034
return (int)(intptr_t)secure_getenv("NON_EXISTING_ENV_VARIABLE");
31-
#elif defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 10)
35+
#elif __GLIBC_PREREQ(2, 10)
3236
return malloc_info(0, stdout);
3337
#else
3438
return 0;
3539
#endif
36-
37-
#else
40+
#else // !defined(__GLIBC_PREREQ)
3841
return 0;
3942
#endif
4043
}

tests/integration/testdependencies/testdependencies.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
#ifdef WITH_DEPENDENCY
22
#include "dependency.h"
33
#else
4+
#include <errno.h>
45
#include <malloc.h>
5-
#include <stdlib.h>
6-
#include <stdint.h>
76
#include <math.h>
87
#include <pthread.h>
8+
#include <stdint.h>
9+
#include <stdlib.h>
10+
#include <time.h>
11+
#include <unistd.h>
912
#if defined(__GLIBC_PREREQ)
1013
#if __GLIBC_PREREQ(2, 28)
1114
#include <threads.h>
@@ -27,14 +30,15 @@ run(PyObject *self, PyObject *args)
2730
#ifdef WITH_DEPENDENCY
2831
res = dep_run();
2932
#elif defined(__GLIBC_PREREQ)
30-
3133
#if __GLIBC_PREREQ(2, 34)
3234
// pthread_mutexattr_init was moved to libc.so.6 in manylinux_2_34+
3335
pthread_mutexattr_t attr;
3436
res = pthread_mutexattr_init(&attr);
3537
if (res == 0) {
3638
pthread_mutexattr_destroy(&attr);
3739
}
40+
#elif __GLIBC_PREREQ(2, 30)
41+
res = gettid() == getpid() ? 0 : 1;
3842
#elif __GLIBC_PREREQ(2, 28)
3943
res = thrd_equal(thrd_current(), thrd_current()) ? 0 : 1;
4044
#elif __GLIBC_PREREQ(2, 24)
@@ -46,8 +50,7 @@ run(PyObject *self, PyObject *args)
4650
#else
4751
res = 0;
4852
#endif
49-
50-
#else
53+
#else // !defined(__GLIBC_PREREQ)
5154
res = 0;
5255
#endif
5356
return PyLong_FromLong(res + tres);

0 commit comments

Comments
 (0)