Files
cibuildwheel/unit_test/oci_container_test.py
T

677 lines
24 KiB
Python
Raw Normal View History

2023-09-07 20:30:22 +02:00
import json
2022-06-16 18:11:59 -04:00
import os
import platform
2020-07-10 12:46:32 +01:00
import random
import shutil
2020-06-26 21:37:02 +01:00
import subprocess
import sys
2020-06-26 21:37:02 +01:00
import textwrap
2025-01-27 14:35:31 -05:00
import time
from contextlib import nullcontext
2022-05-24 17:35:46 -06:00
from pathlib import Path, PurePath, PurePosixPath
2020-06-26 21:37:02 +01:00
2020-06-26 21:41:00 +01:00
import pytest
2022-06-27 16:23:02 -04:00
import tomli_w
2020-06-26 21:41:00 +01:00
import cibuildwheel.oci_container
2025-01-27 20:35:56 +01:00
from cibuildwheel.ci import CIProvider, detect_ci_provider
2021-11-21 15:19:45 -05:00
from cibuildwheel.environment import EnvironmentAssignmentBash
from cibuildwheel.errors import OCIEngineTooOldError
from cibuildwheel.oci_container import (
OCIContainer,
OCIContainerEngineConfig,
OCIPlatform,
_check_engine_version,
)
2020-06-26 21:41:00 +01:00
# Test utilities
2020-07-08 22:56:33 +01:00
# for these tests we use manylinux2014 images, because they're available on
# multi architectures and include python3.8
2023-09-07 20:30:22 +02:00
DEFAULT_IMAGE_TEMPLATE = "quay.io/pypa/manylinux2014_{machine}:2023-09-04-0828984"
pm = platform.machine()
2023-09-07 20:30:22 +02:00
if pm in {"x86_64", "ppc64le", "s390x"}:
DEFAULT_IMAGE = DEFAULT_IMAGE_TEMPLATE.format(machine=pm)
2023-05-26 15:58:18 +01:00
elif pm in {"aarch64", "arm64"}:
2023-09-07 20:30:22 +02:00
DEFAULT_IMAGE = DEFAULT_IMAGE_TEMPLATE.format(machine="aarch64")
2022-06-16 18:11:59 -04:00
else:
DEFAULT_IMAGE = ""
DEFAULT_OCI_PLATFORM = {
"AMD64": OCIPlatform.AMD64,
"x86_64": OCIPlatform.AMD64,
"ppc64le": OCIPlatform.PPC64LE,
"s390x": OCIPlatform.S390X,
"aarch64": OCIPlatform.ARM64,
"arm64": OCIPlatform.ARM64,
}[pm]
2022-06-16 18:11:59 -04:00
PODMAN = OCIContainerEngineConfig(name="podman")
@pytest.fixture(params=["docker", "podman"], scope="module")
def container_engine(request):
if request.param == "docker" and not request.config.getoption("--run-docker"):
pytest.skip("need --run-docker option to run")
if request.param == "podman" and not request.config.getoption("--run-podman"):
pytest.skip("need --run-podman option to run")
def get_images() -> set[str]:
if detect_ci_provider() is None:
return set()
images = subprocess.run(
[request.param, "image", "ls", "--format", "{{json .ID}}"],
text=True,
check=True,
stdout=subprocess.PIPE,
).stdout
return {json.loads(image.strip()) for image in images.splitlines() if image.strip()}
images_before = get_images()
try:
yield OCIContainerEngineConfig(name=request.param)
finally:
images_after = get_images()
for image in images_after - images_before:
subprocess.run([request.param, "rmi", image], check=False)
2022-06-16 18:11:59 -04:00
# Tests
2022-06-16 18:11:59 -04:00
def test_simple(container_engine):
with OCIContainer(
engine=container_engine, image=DEFAULT_IMAGE, oci_platform=DEFAULT_OCI_PLATFORM
) as container:
2021-05-03 11:45:43 -04:00
assert container.call(["echo", "hello"], capture_output=True) == "hello\n"
2020-06-26 21:37:02 +01:00
2020-06-26 21:41:00 +01:00
def test_no_lf(container_engine):
with OCIContainer(
engine=container_engine, image=DEFAULT_IMAGE, oci_platform=DEFAULT_OCI_PLATFORM
) as container:
2021-05-03 11:45:43 -04:00
assert container.call(["printf", "hello"], capture_output=True) == "hello"
2020-06-26 21:37:02 +01:00
2020-06-26 21:41:00 +01:00
def test_debug_info(container_engine):
container = OCIContainer(
engine=container_engine, image=DEFAULT_IMAGE, oci_platform=DEFAULT_OCI_PLATFORM
)
2022-06-16 18:11:59 -04:00
print(container.debug_info())
with container:
pass
def test_environment(container_engine):
with OCIContainer(
engine=container_engine, image=DEFAULT_IMAGE, oci_platform=DEFAULT_OCI_PLATFORM
) as container:
2021-04-30 17:56:34 -04:00
assert (
container.call(
2021-05-03 11:45:43 -04:00
["sh", "-c", "echo $TEST_VAR"], env={"TEST_VAR": "1"}, capture_output=True
2021-04-30 17:56:34 -04:00
)
2021-05-03 11:45:43 -04:00
== "1\n"
2021-04-30 17:56:34 -04:00
)
2020-06-26 21:37:02 +01:00
2020-06-26 21:41:00 +01:00
def test_environment_pass(container_engine, monkeypatch):
monkeypatch.setenv("CIBUILDWHEEL", "1")
monkeypatch.setenv("SOURCE_DATE_EPOCH", "1489957071")
with OCIContainer(
engine=container_engine, image=DEFAULT_IMAGE, oci_platform=DEFAULT_OCI_PLATFORM
) as container:
assert container.call(["sh", "-c", "echo $CIBUILDWHEEL"], capture_output=True) == "1\n"
assert (
container.call(["sh", "-c", "echo $SOURCE_DATE_EPOCH"], capture_output=True)
== "1489957071\n"
)
def test_cwd(container_engine):
with OCIContainer(
engine=container_engine,
image=DEFAULT_IMAGE,
oci_platform=DEFAULT_OCI_PLATFORM,
cwd="/cibuildwheel/working_directory",
) as container:
2021-05-03 11:45:43 -04:00
assert container.call(["pwd"], capture_output=True) == "/cibuildwheel/working_directory\n"
assert container.call(["pwd"], capture_output=True, cwd="/opt") == "/opt\n"
2020-07-20 01:04:26 +02:00
def test_container_removed(container_engine):
2025-01-27 14:35:31 -05:00
# test is flaky on some platforms, implement retry for 5 second
timeout = 50 # * 100 ms = 5s
with OCIContainer(
engine=container_engine, image=DEFAULT_IMAGE, oci_platform=DEFAULT_OCI_PLATFORM
) as container:
2025-01-27 14:35:31 -05:00
assert container.name is not None
container_name = container.name
for _ in range(timeout):
docker_containers_listing = subprocess.run(
f"{container.engine.name} container ls",
shell=True,
check=True,
stdout=subprocess.PIPE,
text=True,
).stdout
if container_name in docker_containers_listing:
break
time.sleep(0.1)
assert container_name in docker_containers_listing
for _ in range(timeout):
2021-04-30 17:56:34 -04:00
docker_containers_listing = subprocess.run(
2023-05-15 09:09:13 +01:00
f"{container.engine.name} container ls",
2021-04-30 17:56:34 -04:00
shell=True,
check=True,
stdout=subprocess.PIPE,
text=True,
2021-04-30 17:56:34 -04:00
).stdout
2025-01-27 14:35:31 -05:00
if container_name not in docker_containers_listing:
break
time.sleep(0.1)
assert container_name not in docker_containers_listing
2020-06-26 21:37:02 +01:00
2020-06-26 21:41:00 +01:00
def test_large_environment(container_engine):
2020-06-26 21:37:02 +01:00
# max environment variable size is 128kB
2021-04-30 17:56:34 -04:00
long_env_var_length = 127 * 1024
2020-06-26 21:37:02 +01:00
large_environment = {
2021-05-03 11:45:43 -04:00
"a": "0" * long_env_var_length,
"b": "0" * long_env_var_length,
"c": "0" * long_env_var_length,
"d": "0" * long_env_var_length,
2020-06-26 21:37:02 +01:00
}
with OCIContainer(
engine=container_engine, image=DEFAULT_IMAGE, oci_platform=DEFAULT_OCI_PLATFORM
) as container:
2020-06-26 21:37:02 +01:00
# check the length of d
2021-04-30 17:56:34 -04:00
assert (
2021-05-03 11:45:43 -04:00
container.call(["sh", "-c", "echo ${#d}"], env=large_environment, capture_output=True)
== f"{long_env_var_length}\n"
2021-04-30 17:56:34 -04:00
)
2020-06-26 21:37:02 +01:00
2020-06-26 21:41:00 +01:00
def test_binary_output(container_engine):
with OCIContainer(
engine=container_engine, image=DEFAULT_IMAGE, oci_platform=DEFAULT_OCI_PLATFORM
) as container:
2020-07-08 22:56:33 +01:00
# note: the below embedded snippets are in python2
2020-06-26 21:37:02 +01:00
# check that we can pass though arbitrary binary data without erroring
2021-04-30 17:56:34 -04:00
container.call(
[
2021-05-03 11:45:43 -04:00
"/usr/bin/python2",
"-c",
2021-04-30 17:56:34 -04:00
textwrap.dedent(
2021-05-03 11:45:43 -04:00
"""
2021-05-02 16:37:38 +02:00
import sys
sys.stdout.write(''.join(chr(n) for n in range(0, 256)))
2021-05-03 11:45:43 -04:00
"""
2021-04-30 17:56:34 -04:00
),
]
)
2020-06-26 21:37:02 +01:00
# check that we can capture arbitrary binary data
2021-04-30 17:56:34 -04:00
output = container.call(
[
2021-05-03 11:45:43 -04:00
"/usr/bin/python2",
"-c",
2021-04-30 17:56:34 -04:00
textwrap.dedent(
2021-05-03 11:45:43 -04:00
"""
2021-05-02 16:37:38 +02:00
import sys
sys.stdout.write(''.join(chr(n % 256) for n in range(0, 512)))
2021-05-03 11:45:43 -04:00
"""
2021-04-30 17:56:34 -04:00
),
],
capture_output=True,
)
2020-06-26 21:37:02 +01:00
2021-05-03 11:45:43 -04:00
data = bytes(output, encoding="utf8", errors="surrogateescape")
2020-06-26 21:37:02 +01:00
for i in range(512):
2020-06-26 21:37:02 +01:00
assert data[i] == i % 256
# check that environment variables can carry binary data, except null characters
# (https://www.gnu.org/software/libc/manual/html_node/Environment-Variables.html)
binary_data = bytes(n for n in range(1, 256))
2021-05-03 11:45:43 -04:00
binary_data_string = str(binary_data, encoding="utf8", errors="surrogateescape")
2020-06-26 21:37:02 +01:00
output = container.call(
2021-05-03 11:45:43 -04:00
["python2", "-c", 'import os, sys; sys.stdout.write(os.environ["TEST_VAR"])'],
env={"TEST_VAR": binary_data_string},
2020-06-26 21:37:02 +01:00
capture_output=True,
)
assert output == binary_data_string
2020-07-08 22:56:33 +01:00
2024-10-22 10:16:59 -04:00
def test_file_operation(tmp_path: Path, container_engine: OCIContainerEngineConfig) -> None:
with OCIContainer(
engine=container_engine, image=DEFAULT_IMAGE, oci_platform=DEFAULT_OCI_PLATFORM
) as container:
2020-07-08 22:56:33 +01:00
# test copying a file in
2020-07-10 12:46:32 +01:00
test_binary_data = bytes(random.randrange(256) for _ in range(1000))
2021-05-03 11:45:43 -04:00
original_test_file = tmp_path / "test.dat"
2020-07-08 22:56:33 +01:00
original_test_file.write_bytes(test_binary_data)
2021-05-03 11:45:43 -04:00
dst_file = PurePath("/tmp/test.dat")
2020-07-08 22:56:33 +01:00
container.copy_into(original_test_file, dst_file)
owner = container.call(["stat", "-c", "%u:%g", dst_file], capture_output=True).strip()
assert owner == "0:0"
2021-05-03 11:45:43 -04:00
output = container.call(["cat", dst_file], capture_output=True)
assert test_binary_data == bytes(output, encoding="utf8", errors="surrogateescape")
2020-07-08 22:56:33 +01:00
2020-07-10 13:04:50 +01:00
2024-10-22 10:16:59 -04:00
def test_dir_operations(tmp_path: Path, container_engine: OCIContainerEngineConfig) -> None:
with OCIContainer(
engine=container_engine, image=DEFAULT_IMAGE, oci_platform=DEFAULT_OCI_PLATFORM
) as container:
2020-07-10 12:46:32 +01:00
test_binary_data = bytes(random.randrange(256) for _ in range(1000))
2021-05-03 11:45:43 -04:00
original_test_file = tmp_path / "test.dat"
2020-07-10 12:46:32 +01:00
original_test_file.write_bytes(test_binary_data)
2020-07-08 22:56:33 +01:00
# test copying a dir in
2021-05-03 11:45:43 -04:00
test_dir = tmp_path / "test_dir"
2020-07-10 12:46:32 +01:00
test_dir.mkdir()
2021-05-03 11:45:43 -04:00
test_file = test_dir / "test.dat"
2020-07-10 12:46:32 +01:00
shutil.copyfile(original_test_file, test_file)
2020-07-08 22:56:33 +01:00
2022-05-24 17:35:46 -06:00
dst_dir = PurePosixPath("/tmp/test_dir")
2021-05-03 11:45:43 -04:00
dst_file = dst_dir / "test.dat"
2020-07-10 12:46:32 +01:00
container.copy_into(test_dir, dst_dir)
owner = container.call(["stat", "-c", "%u:%g", dst_dir], capture_output=True).strip()
assert owner == "0:0"
owner = container.call(["stat", "-c", "%u:%g", dst_file], capture_output=True).strip()
assert owner == "0:0"
2021-05-03 11:45:43 -04:00
output = container.call(["cat", dst_file], capture_output=True)
assert test_binary_data == bytes(output, encoding="utf8", errors="surrogateescape")
2020-07-10 12:46:32 +01:00
# test glob
2021-05-03 11:45:43 -04:00
assert container.glob(dst_dir, "*.dat") == [dst_file]
2020-07-10 12:46:32 +01:00
# test copy dir out
2021-05-03 11:45:43 -04:00
new_test_dir = tmp_path / "test_dir_new"
2020-07-10 12:46:32 +01:00
container.copy_out(dst_dir, new_test_dir)
assert os.getuid() == new_test_dir.stat().st_uid
assert os.getgid() == new_test_dir.stat().st_gid
assert os.getuid() == (new_test_dir / "test.dat").stat().st_uid
assert os.getgid() == (new_test_dir / "test.dat").stat().st_gid
2021-05-03 11:45:43 -04:00
assert test_binary_data == (new_test_dir / "test.dat").read_bytes()
2024-10-22 10:16:59 -04:00
def test_environment_executor(container_engine: OCIContainerEngineConfig) -> None:
with OCIContainer(
engine=container_engine, image=DEFAULT_IMAGE, oci_platform=DEFAULT_OCI_PLATFORM
) as container:
2021-11-21 15:19:45 -05:00
assignment = EnvironmentAssignmentBash("TEST=$(echo 42)")
assert assignment.evaluated_value({}, container.environment_executor) == "42"
2024-10-22 10:16:59 -04:00
def test_podman_vfs(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, container_engine: OCIContainerEngineConfig
) -> None:
if container_engine.name != "podman":
pytest.skip("only runs with podman")
if sys.platform.startswith("darwin"):
pytest.skip("Skipping test because podman on this platform does not support vfs")
# create the VFS configuration
vfs_path = tmp_path / "podman_vfs"
vfs_path.mkdir()
# This requires that we write configuration files and point to them
# with environment variables before we run podman
# https://github.com/containers/common/blob/main/docs/containers.conf.5.md
vfs_containers_conf_data = {
"containers": {
"default_capabilities": [
"CHOWN",
"DAC_OVERRIDE",
"FOWNER",
"FSETID",
"KILL",
"NET_BIND_SERVICE",
"SETFCAP",
"SETGID",
"SETPCAP",
"SETUID",
"SYS_CHROOT",
]
},
"engine": {"cgroup_manager": "cgroupfs", "events_logger": "file"},
}
# https://github.com/containers/storage/blob/main/docs/containers-storage.conf.5.md
storage_root = vfs_path / ".local/share/containers/vfs-storage"
run_root = vfs_path / ".local/share/containers/vfs-runroot"
storage_root.mkdir(parents=True, exist_ok=True)
run_root.mkdir(parents=True, exist_ok=True)
vfs_containers_storage_conf_data = {
"storage": {
"driver": "vfs",
"graphroot": os.fspath(storage_root),
"runroot": os.fspath(run_root),
"rootless_storage_path": os.fspath(storage_root),
"options": {
# "remap-user": "containers",
"aufs": {"mountopt": "rw"},
"overlay": {"mountopt": "rw", "force_mask": "shared"},
# "vfs": {"ignore_chown_errors": "true"},
},
}
}
vfs_containers_conf_fpath = vfs_path / "temp_vfs_containers.conf"
vfs_containers_storage_conf_fpath = vfs_path / "temp_vfs_containers_storage.conf"
2022-06-27 16:23:02 -04:00
with open(vfs_containers_conf_fpath, "wb") as file:
tomli_w.dump(vfs_containers_conf_data, file)
2022-06-27 16:23:02 -04:00
with open(vfs_containers_storage_conf_fpath, "wb") as file:
tomli_w.dump(vfs_containers_storage_conf_data, file)
monkeypatch.setenv("CONTAINERS_CONF", str(vfs_containers_conf_fpath))
monkeypatch.setenv("CONTAINERS_STORAGE_CONF", str(vfs_containers_storage_conf_fpath))
with OCIContainer(
engine=PODMAN, image=DEFAULT_IMAGE, oci_platform=DEFAULT_OCI_PLATFORM
) as container:
# test running a command
assert container.call(["echo", "hello"], capture_output=True) == "hello\n"
# test copying a file into the container
(tmp_path / "some_file.txt").write_text("1234")
container.copy_into(tmp_path / "some_file.txt", PurePosixPath("some_file.txt"))
assert container.call(["cat", "some_file.txt"], capture_output=True) == "1234"
# Clean up
# When using the VFS, user is not given write permissions by default in
# new directories. As a workaround we use 'podman unshare' to delete them
# as UID 0. The reason why permission errors occur on podman is documented
# in https://podman.io/blogs/2018/10/03/podman-remove-content-homedir.html
subprocess.run(["podman", "unshare", "rm", "-rf", vfs_path], check=True)
2024-10-22 10:16:59 -04:00
def test_create_args_volume(tmp_path: Path, container_engine: OCIContainerEngineConfig) -> None:
if container_engine.name != "docker":
pytest.skip("only runs with docker")
2023-05-15 09:13:28 +01:00
2023-05-16 08:53:51 +01:00
if "CIRCLECI" in os.environ or "GITLAB_CI" in os.environ:
pytest.skip(
"Skipping test on CircleCI/GitLab because docker there does not support --volume"
)
2023-05-15 22:07:09 +01:00
test_mount_dir = tmp_path / "test_mount"
test_mount_dir.mkdir()
(test_mount_dir / "test_file.txt").write_text("1234")
container_engine = OCIContainerEngineConfig(
name="docker", create_args=(f"--volume={test_mount_dir}:/test_mount",)
)
with OCIContainer(
engine=container_engine,
image=DEFAULT_IMAGE,
oci_platform=DEFAULT_OCI_PLATFORM,
) as container:
assert container.call(["cat", "/test_mount/test_file.txt"], capture_output=True) == "1234"
@pytest.mark.parametrize(
("config", "name", "create_args"),
[
(
"docker",
"docker",
(),
),
(
"docker;create_args:",
"docker",
(),
),
(
"docker;create_args:--abc --def",
"docker",
("--abc", "--def"),
),
(
"docker; create_args: --abc --def",
"docker",
("--abc", "--def"),
),
(
"name:docker; create_args: --abc --def",
"docker",
("--abc", "--def"),
),
(
'docker; create_args: --some-option="value with spaces"',
"docker",
("--some-option=value with spaces",),
),
(
'docker; create_args: --some-option="value; with; semicolons" --another-option',
"docker",
("--some-option=value; with; semicolons", "--another-option"),
),
(
"docker; create_args: --platform=linux/amd64",
"docker",
(),
),
(
"podman; create_args: --platform=linux/amd64",
"podman",
(),
),
(
"docker; create_args: --platform linux/amd64",
"docker",
(),
),
(
"podman; create_args: --platform linux/amd64",
"podman",
(),
),
],
)
def test_parse_engine_config(config, name, create_args, capsys):
engine_config = OCIContainerEngineConfig.from_config_string(config)
assert engine_config.name == name
assert engine_config.create_args == create_args
if "--platform" in config:
captured = capsys.readouterr()
assert (
"Using '--platform' in 'container-engine::create_args' is deprecated. It will be ignored."
in captured.err
)
2023-09-07 20:30:22 +02:00
@pytest.mark.skipif(pm != "x86_64", reason="Only runs on x86_64")
def test_enforce_32_bit(container_engine):
with OCIContainer(
engine=container_engine,
image=DEFAULT_IMAGE_TEMPLATE.format(machine="i686"),
oci_platform=OCIPlatform.i386,
) as container:
2023-09-07 20:30:22 +02:00
assert container.call(["uname", "-m"], capture_output=True).strip() == "i686"
container_args = subprocess.run(
f"{container.engine.name} inspect -f '{{{{json .Args }}}}' {container.name}",
shell=True,
check=True,
stdout=subprocess.PIPE,
text=True,
).stdout
assert json.loads(container_args) == ["/bin/bash"]
@pytest.mark.parametrize(
("config", "should_have_host_mount"),
[
("{name}", True),
("{name}; disable_host_mount: false", True),
("{name}; disable_host_mount: true", False),
],
)
2024-10-22 10:16:59 -04:00
def test_disable_host_mount(
tmp_path: Path,
container_engine: OCIContainerEngineConfig,
config: str,
should_have_host_mount: bool,
) -> None:
2023-11-24 10:31:59 +00:00
if detect_ci_provider() in {CIProvider.circle_ci, CIProvider.gitlab}:
pytest.skip("Skipping test because docker on this platform does not support host mounts")
if sys.platform.startswith("darwin"):
pytest.skip("Skipping test because docker on this platform does not support host mounts")
engine = OCIContainerEngineConfig.from_config_string(config.format(name=container_engine.name))
sentinel_file = tmp_path / "sentinel"
sentinel_file.write_text("12345")
with OCIContainer(
engine=engine, image=DEFAULT_IMAGE, oci_platform=DEFAULT_OCI_PLATFORM
) as container:
host_mount_path = "/host" + str(sentinel_file)
if should_have_host_mount:
assert container.call(["cat", host_mount_path], capture_output=True) == "12345"
else:
with pytest.raises(subprocess.CalledProcessError):
container.call(["cat", host_mount_path], capture_output=True)
2024-10-01 16:33:57 +02:00
@pytest.mark.parametrize("platform", list(OCIPlatform))
2024-10-22 10:16:59 -04:00
def test_local_image(
container_engine: OCIContainerEngineConfig, platform: OCIPlatform, tmp_path: Path
) -> None:
2024-10-01 16:33:57 +02:00
if (
detect_ci_provider() == CIProvider.travis_ci
and pm != "x86_64"
2024-10-01 16:33:57 +02:00
and platform != DEFAULT_OCI_PLATFORM
):
pytest.skip("Skipping test because docker on this platform does not support QEMU")
if container_engine.name == "podman" and platform == OCIPlatform.ARMV7:
# both GHA & local macOS arm64 podman desktop are failing
pytest.xfail("podman fails with armv7l images")
remote_image = "debian:12-slim"
platform_name = platform.value.replace("/", "_")
local_image = f"cibw_{container_engine.name}_{platform_name}_local:latest"
dockerfile = tmp_path / "Dockerfile"
dockerfile.write_text(f"FROM {remote_image}")
subprocess.run(
2024-10-01 16:33:57 +02:00
[container_engine.name, "pull", f"--platform={platform.value}", remote_image],
check=True,
)
2024-10-01 16:33:57 +02:00
subprocess.run(
[container_engine.name, "build", f"--platform={platform.value}", "-t", local_image, "."],
check=True,
cwd=tmp_path,
)
with OCIContainer(engine=container_engine, image=local_image, oci_platform=platform):
pass
@pytest.mark.parametrize("platform", list(OCIPlatform))
def test_multiarch_image(container_engine, platform):
if (
detect_ci_provider() == CIProvider.travis_ci
and pm != "x86_64"
and platform != DEFAULT_OCI_PLATFORM
):
pytest.skip("Skipping test because docker on this platform does not support QEMU")
2024-10-01 16:33:57 +02:00
if container_engine.name == "podman" and platform == OCIPlatform.ARMV7:
# both GHA & local macOS arm64 podman desktop are failing
pytest.xfail("podman fails with armv7l images")
with OCIContainer(
engine=container_engine, image="debian:12-slim", oci_platform=platform
) as container:
output = container.call(["uname", "-m"], capture_output=True)
2024-10-01 16:33:57 +02:00
output_map_kernel = {
OCIPlatform.i386: ("i686",),
OCIPlatform.AMD64: ("x86_64",),
OCIPlatform.ARMV7: ("armv7l", "armv8l"),
OCIPlatform.ARM64: ("aarch64",),
OCIPlatform.PPC64LE: ("ppc64le",),
OCIPlatform.S390X: ("s390x",),
}
2024-10-01 16:33:57 +02:00
assert output.strip() in output_map_kernel[platform]
output = container.call(["dpkg", "--print-architecture"], capture_output=True)
2024-10-01 16:33:57 +02:00
output_map_dpkg = {
OCIPlatform.i386: "i386",
OCIPlatform.AMD64: "amd64",
2024-10-01 16:33:57 +02:00
OCIPlatform.ARMV7: "armhf",
OCIPlatform.ARM64: "arm64",
OCIPlatform.PPC64LE: "ppc64el",
OCIPlatform.S390X: "s390x",
}
2024-10-01 16:33:57 +02:00
assert output_map_dpkg[platform] == output.strip()
@pytest.mark.parametrize(
("engine_name", "version", "context"),
[
(
"docker",
None, # 17.12.1-ce does supports "docker version --format '{{json . }}'" so a version before that
pytest.raises(OCIEngineTooOldError),
),
(
"docker",
'{"Client":{"Version":"19.03.15","ApiVersion": "1.40"},"Server":{"ApiVersion": "1.40"}}',
pytest.raises(OCIEngineTooOldError),
),
(
"docker",
'{"Client":{"Version":"20.10.0","ApiVersion":"1.41"},"Server":{"ApiVersion":"1.41"}}',
nullcontext(),
),
(
"docker",
'{"Client":{"Version":"24.0.0","ApiVersion":"1.43"},"Server":{"ApiVersion":"1.43"}}',
nullcontext(),
),
(
"docker",
'{"Client":{"ApiVersion":"1.43"},"Server":{"ApiVersion":"1.30"}}',
pytest.raises(OCIEngineTooOldError),
),
(
"docker",
'{"Client":{"ApiVersion":"1.30"},"Server":{"ApiVersion":"1.43"}}',
pytest.raises(OCIEngineTooOldError),
),
("podman", '{"Client":{"Version":"5.2.0"},"Server":{"Version":"5.1.2"}}', nullcontext()),
("podman", '{"Client":{"Version":"4.9.4-rhel"}}', nullcontext()),
(
"podman",
'{"Client":{"Version":"5.2.0"},"Server":{"Version":"2.1.2"}}',
pytest.raises(OCIEngineTooOldError),
),
(
"podman",
'{"Client":{"Version":"2.2.0"},"Server":{"Version":"5.1.2"}}',
pytest.raises(OCIEngineTooOldError),
),
("podman", '{"Client":{"Version":"3.0~rc1-rhel"}}', nullcontext()),
("podman", '{"Client":{"Version":"2.1.0~rc1"}}', pytest.raises(OCIEngineTooOldError)),
],
)
def test_engine_version(engine_name, version, context, monkeypatch):
def mockcall(*args, **kwargs):
if version is None:
raise subprocess.CalledProcessError(1, " ".join(str(arg) for arg in args))
return version
monkeypatch.setattr(cibuildwheel.oci_container, "call", mockcall)
engine = OCIContainerEngineConfig.from_config_string(engine_name)
with context:
_check_engine_version(engine)