From e9924e32dcebaa9bb69df68bab3632b830a615c1 Mon Sep 17 00:00:00 2001 From: mayeut Date: Mon, 25 Nov 2024 08:52:09 +0100 Subject: [PATCH 1/6] ci: warm-up docker images --- pyproject.toml | 1 + test/conftest.py | 74 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 3b0d72d1f..59ae18c79 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,6 +84,7 @@ docs = [ ] test = [ "build", + "filelock", "jinja2", "pytest-timeout", "pytest-xdist", diff --git a/test/conftest.py b/test/conftest.py index a42a3ce9a..c887e2eaa 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -5,7 +5,10 @@ from typing import Generator import pytest +from filelock import FileLock +from cibuildwheel.architecture import Architecture +from cibuildwheel.options import CommandLineArguments, Options from cibuildwheel.util import detect_ci_provider, find_uv from .utils import EMULATED_ARCHS, platform @@ -28,6 +31,77 @@ def pytest_addoption(parser: pytest.Parser) -> None: ) +def docker_warmup(request: pytest.FixtureRequest) -> None: + machine = request.config.getoption("--run-emulation", default=None) + if machine is None: + archs = [arch.value for arch in Architecture.auto_archs("linux")] + elif machine == "all": + archs = EMULATED_ARCHS + else: + archs = [machine] + options = Options( + platform="linux", + command_line_arguments=CommandLineArguments.defaults(), + env={}, + defaults=True, + ) + build_options = options.build_options(None) + assert build_options.manylinux_images is not None + assert build_options.musllinux_images is not None + images = [build_options.manylinux_images[arch] for arch in archs] + [ + build_options.musllinux_images[arch] for arch in archs + ] + for image in images: + try: + container_id = subprocess.run( + [ + "docker", + "create", + image, + "bash", + "-c", + "manylinux-interpreters ensure-all && cpython3.13 -m pip download -d /tmp setuptools wheel pytest", + ], + text=True, + check=True, + stdout=subprocess.PIPE, + ).stdout.strip() + subprocess.run(["docker", "start", container_id], check=True, stdout=subprocess.DEVNULL) + exit_code = subprocess.run( + ["docker", "wait", container_id], text=True, check=True, stdout=subprocess.PIPE + ).stdout.strip() + if exit_code == "0": + subprocess.run( + ["docker", "commit", container_id, image], check=True, stdout=subprocess.DEVNULL + ) + subprocess.run(["docker", "rm", container_id], check=True, stdout=subprocess.DEVNULL) + except subprocess.CalledProcessError: + print(f"failed to warm-up {image} image") + + +@pytest.fixture(scope="session", autouse=True) +def docker_warmup_fixture( + request: pytest.FixtureRequest, tmp_path_factory: pytest.TempPathFactory, worker_id: str +) -> None: + # if we're in CI testing linux, let's warm-up docker images + if detect_ci_provider() is None or platform != "linux": + return None + + if worker_id == "master": + # not executing with multiple workers + return docker_warmup(request) + + # get the temp directory shared by all workers + root_tmp_dir = tmp_path_factory.getbasetemp().parent + + fn = root_tmp_dir / "warmup.done" + with FileLock(str(fn) + ".lock"): + if not fn.is_file(): + docker_warmup(request) + fn.write_text("done") + return None + + @pytest.fixture(params=["pip", "build"]) def build_frontend_env_nouv(request: pytest.FixtureRequest) -> dict[str, str]: frontend = request.param From e099e8f33df343a8e2f8553bda42319d5cc18233 Mon Sep 17 00:00:00 2001 From: mayeut Date: Thu, 28 Nov 2024 21:50:48 +0100 Subject: [PATCH 2/6] don't cache when running emulation tests --- test/conftest.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/conftest.py b/test/conftest.py index c887e2eaa..d7f55991a 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -86,6 +86,9 @@ def docker_warmup_fixture( # if we're in CI testing linux, let's warm-up docker images if detect_ci_provider() is None or platform != "linux": return None + if request.config.getoption("--run-emulation", default=None) is not None: + # emulation tests only run one test in CI, caching the image only slows down the test + return None if worker_id == "master": # not executing with multiple workers From 0552ca1bc26780f80682474ce7339b4ebf91da58 Mon Sep 17 00:00:00 2001 From: mayeut Date: Thu, 28 Nov 2024 22:28:25 +0100 Subject: [PATCH 3/6] skip graalpy cache --- test/conftest.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index d7f55991a..eca316746 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -53,15 +53,12 @@ def docker_warmup(request: pytest.FixtureRequest) -> None: ] for image in images: try: + command = ( + "manylinux-interpreters ensure $(manylinux-interpreters list 2>/dev/null | grep -v graalpy) &&" + "cpython3.13 -m pip download -d /tmp setuptools wheel pytest" + ) container_id = subprocess.run( - [ - "docker", - "create", - image, - "bash", - "-c", - "manylinux-interpreters ensure-all && cpython3.13 -m pip download -d /tmp setuptools wheel pytest", - ], + ["docker", "create", image, "bash", "-c", command], text=True, check=True, stdout=subprocess.PIPE, From 55a221dace6d11bf1de60b23f34e6a405a2f964c Mon Sep 17 00:00:00 2001 From: mayeut Date: Sat, 30 Nov 2024 12:50:34 +0100 Subject: [PATCH 4/6] add comment about the single worker case --- test/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/conftest.py b/test/conftest.py index eca316746..55c0a9631 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -89,6 +89,7 @@ def docker_warmup_fixture( if worker_id == "master": # not executing with multiple workers + # it might be unsafe to write to tmp_path_factory.getbasetemp().parent return docker_warmup(request) # get the temp directory shared by all workers From 60d632b7cd4303588dddc2f36d1b470fb25e12ac Mon Sep 17 00:00:00 2001 From: mayeut Date: Sat, 30 Nov 2024 13:06:10 +0100 Subject: [PATCH 5/6] enforce docker warm-up succeeds in CI --- test/conftest.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 55c0a9631..be8840616 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -51,29 +51,29 @@ def docker_warmup(request: pytest.FixtureRequest) -> None: images = [build_options.manylinux_images[arch] for arch in archs] + [ build_options.musllinux_images[arch] for arch in archs ] + # exclude GraalPy as it's not a target for cibuildwheel + command = ( + "manylinux-interpreters ensure $(manylinux-interpreters list 2>/dev/null | grep -v graalpy) &&" + "cpython3.13 -m pip download -d /tmp setuptools wheel pytest" + ) for image in images: + container_id = subprocess.run( + ["docker", "create", image, "bash", "-c", command], + text=True, + check=True, + stdout=subprocess.PIPE, + ).stdout.strip() try: - command = ( - "manylinux-interpreters ensure $(manylinux-interpreters list 2>/dev/null | grep -v graalpy) &&" - "cpython3.13 -m pip download -d /tmp setuptools wheel pytest" - ) - container_id = subprocess.run( - ["docker", "create", image, "bash", "-c", command], - text=True, - check=True, - stdout=subprocess.PIPE, - ).stdout.strip() subprocess.run(["docker", "start", container_id], check=True, stdout=subprocess.DEVNULL) exit_code = subprocess.run( ["docker", "wait", container_id], text=True, check=True, stdout=subprocess.PIPE ).stdout.strip() - if exit_code == "0": - subprocess.run( - ["docker", "commit", container_id, image], check=True, stdout=subprocess.DEVNULL - ) + assert exit_code == "0" + subprocess.run( + ["docker", "commit", container_id, image], check=True, stdout=subprocess.DEVNULL + ) + finally: subprocess.run(["docker", "rm", container_id], check=True, stdout=subprocess.DEVNULL) - except subprocess.CalledProcessError: - print(f"failed to warm-up {image} image") @pytest.fixture(scope="session", autouse=True) From c9e50e8eb4e56d35475cc8067eb283671caecace Mon Sep 17 00:00:00 2001 From: mayeut Date: Sat, 30 Nov 2024 13:12:45 +0100 Subject: [PATCH 6/6] skip warm-up on architectures where it does not do any good --- test/conftest.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index be8840616..dd884f464 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -34,11 +34,17 @@ def pytest_addoption(parser: pytest.Parser) -> None: def docker_warmup(request: pytest.FixtureRequest) -> None: machine = request.config.getoption("--run-emulation", default=None) if machine is None: - archs = [arch.value for arch in Architecture.auto_archs("linux")] + archs = {arch.value for arch in Architecture.auto_archs("linux")} elif machine == "all": - archs = EMULATED_ARCHS + archs = set(EMULATED_ARCHS) else: - archs = [machine] + archs = {machine} + + # Only include architectures where there are missing pre-installed interpreters + archs &= {"x86_64", "i686", "aarch64"} + if not archs: + return + options = Options( platform="linux", command_line_arguments=CommandLineArguments.defaults(),