5
5
from typing import Generator
6
6
7
7
import pytest
8
+ from filelock import FileLock
8
9
10
+ from cibuildwheel .architecture import Architecture
11
+ from cibuildwheel .options import CommandLineArguments , Options
9
12
from cibuildwheel .util import detect_ci_provider , find_uv
10
13
11
14
from .utils import EMULATED_ARCHS , platform
@@ -28,6 +31,84 @@ def pytest_addoption(parser: pytest.Parser) -> None:
28
31
)
29
32
30
33
34
+ def docker_warmup (request : pytest .FixtureRequest ) -> None :
35
+ machine = request .config .getoption ("--run-emulation" , default = None )
36
+ if machine is None :
37
+ archs = {arch .value for arch in Architecture .auto_archs ("linux" )}
38
+ elif machine == "all" :
39
+ archs = set (EMULATED_ARCHS )
40
+ else :
41
+ archs = {machine }
42
+
43
+ # Only include architectures where there are missing pre-installed interpreters
44
+ archs &= {"x86_64" , "i686" , "aarch64" }
45
+ if not archs :
46
+ return
47
+
48
+ options = Options (
49
+ platform = "linux" ,
50
+ command_line_arguments = CommandLineArguments .defaults (),
51
+ env = {},
52
+ defaults = True ,
53
+ )
54
+ build_options = options .build_options (None )
55
+ assert build_options .manylinux_images is not None
56
+ assert build_options .musllinux_images is not None
57
+ images = [build_options .manylinux_images [arch ] for arch in archs ] + [
58
+ build_options .musllinux_images [arch ] for arch in archs
59
+ ]
60
+ # exclude GraalPy as it's not a target for cibuildwheel
61
+ command = (
62
+ "manylinux-interpreters ensure $(manylinux-interpreters list 2>/dev/null | grep -v graalpy) &&"
63
+ "cpython3.13 -m pip download -d /tmp setuptools wheel pytest"
64
+ )
65
+ for image in images :
66
+ container_id = subprocess .run (
67
+ ["docker" , "create" , image , "bash" , "-c" , command ],
68
+ text = True ,
69
+ check = True ,
70
+ stdout = subprocess .PIPE ,
71
+ ).stdout .strip ()
72
+ try :
73
+ subprocess .run (["docker" , "start" , container_id ], check = True , stdout = subprocess .DEVNULL )
74
+ exit_code = subprocess .run (
75
+ ["docker" , "wait" , container_id ], text = True , check = True , stdout = subprocess .PIPE
76
+ ).stdout .strip ()
77
+ assert exit_code == "0"
78
+ subprocess .run (
79
+ ["docker" , "commit" , container_id , image ], check = True , stdout = subprocess .DEVNULL
80
+ )
81
+ finally :
82
+ subprocess .run (["docker" , "rm" , container_id ], check = True , stdout = subprocess .DEVNULL )
83
+
84
+
85
+ @pytest .fixture (scope = "session" , autouse = True )
86
+ def docker_warmup_fixture (
87
+ request : pytest .FixtureRequest , tmp_path_factory : pytest .TempPathFactory , worker_id : str
88
+ ) -> None :
89
+ # if we're in CI testing linux, let's warm-up docker images
90
+ if detect_ci_provider () is None or platform != "linux" :
91
+ return None
92
+ if request .config .getoption ("--run-emulation" , default = None ) is not None :
93
+ # emulation tests only run one test in CI, caching the image only slows down the test
94
+ return None
95
+
96
+ if worker_id == "master" :
97
+ # not executing with multiple workers
98
+ # it might be unsafe to write to tmp_path_factory.getbasetemp().parent
99
+ return docker_warmup (request )
100
+
101
+ # get the temp directory shared by all workers
102
+ root_tmp_dir = tmp_path_factory .getbasetemp ().parent
103
+
104
+ fn = root_tmp_dir / "warmup.done"
105
+ with FileLock (str (fn ) + ".lock" ):
106
+ if not fn .is_file ():
107
+ docker_warmup (request )
108
+ fn .write_text ("done" )
109
+ return None
110
+
111
+
31
112
@pytest .fixture (params = ["pip" , "build" ])
32
113
def build_frontend_env_nouv (request : pytest .FixtureRequest ) -> dict [str , str ]:
33
114
frontend = request .param
0 commit comments