* tests: fully type the test suite * chore: require more typing Signed-off-by: Henry Schreiner <henryfs@princeton.edu> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Signed-off-by: Henry Schreiner <henryfs@princeton.edu> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
223 lines
7.8 KiB
Python
223 lines
7.8 KiB
Python
import json
|
|
import os
|
|
import subprocess
|
|
from collections.abc import Generator
|
|
|
|
import pytest
|
|
from filelock import FileLock
|
|
|
|
from cibuildwheel.architecture import Architecture
|
|
from cibuildwheel.ci import detect_ci_provider
|
|
from cibuildwheel.options import CommandLineArguments, Options
|
|
from cibuildwheel.selector import EnableGroup
|
|
from cibuildwheel.typing import PLATFORMS
|
|
from cibuildwheel.venv import find_uv
|
|
|
|
from . import utils
|
|
from .utils import DEFAULT_CIBW_ENABLE, EMULATED_ARCHS, get_platform
|
|
|
|
|
|
def pytest_addoption(parser: pytest.Parser) -> None:
|
|
parser.addoption(
|
|
"--run-emulation",
|
|
action="store",
|
|
default=None,
|
|
help="run emulation tests",
|
|
choices=("all", *EMULATED_ARCHS),
|
|
)
|
|
parser.addoption("--run-podman", action="store_true", default=False, help="run podman tests")
|
|
parser.addoption(
|
|
"--run-cp38-universal2",
|
|
action="store_true",
|
|
default=False,
|
|
help="macOS cp38 uses the universal2 installer",
|
|
)
|
|
parser.addoption(
|
|
"--enable",
|
|
action="store",
|
|
default=None,
|
|
help="Set the CIBW_ENABLE environment variable for all tests.",
|
|
)
|
|
parser.addoption(
|
|
"--platform",
|
|
action="store",
|
|
default=None,
|
|
help="Set the CIBW_PLATFORM environment variable for all tests.",
|
|
)
|
|
|
|
|
|
def pytest_configure(config: pytest.Config) -> None:
|
|
flag_enable = config.getoption("--enable")
|
|
flag_platform = config.getoption("--platform")
|
|
|
|
if flag_enable is not None and "CIBW_ENABLE" in os.environ:
|
|
msg = (
|
|
"Both --enable pytest option and CIBW_ENABLE environment variable are set. "
|
|
"Please specify only one."
|
|
)
|
|
raise pytest.UsageError(msg)
|
|
if flag_platform is not None and "CIBW_PLATFORM" in os.environ:
|
|
msg = (
|
|
"Both --platform pytest option and CIBW_PLATFORM environment variable are set. "
|
|
"Please specify only one."
|
|
)
|
|
raise pytest.UsageError(msg)
|
|
|
|
if flag_enable is not None:
|
|
EnableGroup.parse_option_value(flag_enable)
|
|
os.environ["CIBW_ENABLE"] = flag_enable
|
|
if flag_enable is None and "CIBW_ENABLE" not in os.environ:
|
|
# Set default value for CIBW_ENABLE
|
|
os.environ["CIBW_ENABLE"] = DEFAULT_CIBW_ENABLE
|
|
|
|
if flag_platform is not None:
|
|
assert flag_platform in PLATFORMS, f"Invalid platform: {flag_platform}"
|
|
os.environ["CIBW_PLATFORM"] = flag_platform
|
|
|
|
|
|
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 = set(EMULATED_ARCHS)
|
|
else:
|
|
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(),
|
|
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
|
|
]
|
|
command = (
|
|
"manylinux-interpreters ensure-all &&"
|
|
"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:
|
|
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()
|
|
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)
|
|
|
|
|
|
@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 get_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
|
|
# it might be unsafe to write to tmp_path_factory.getbasetemp().parent
|
|
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
|
|
marks = {m.name for m in request.node.iter_markers()}
|
|
|
|
platform = "pyodide" if "pyodide" in marks else get_platform()
|
|
if platform == "pyodide" and frontend == "pip":
|
|
pytest.skip("Can't use pip as build frontend for pyodide platform")
|
|
|
|
return {"CIBW_BUILD_FRONTEND": frontend}
|
|
|
|
|
|
@pytest.fixture(params=["pip", "build", "build[uv]", "uv"])
|
|
def build_frontend_env(request: pytest.FixtureRequest) -> Generator[dict[str, str], None, None]:
|
|
frontend = request.param
|
|
marks = {m.name for m in request.node.iter_markers()}
|
|
if "android" in marks:
|
|
platform = "android"
|
|
elif "ios" in marks:
|
|
platform = "ios"
|
|
elif "pyodide" in marks:
|
|
platform = "pyodide"
|
|
else:
|
|
platform = get_platform()
|
|
|
|
if platform in {"pyodide", "ios", "android"} and frontend == "pip":
|
|
pytest.skip(f"Can't use pip as build frontend for {platform}")
|
|
if platform == "pyodide" and frontend in {"build[uv]", "uv"}:
|
|
pytest.skip("Can't use uv with pyodide yet")
|
|
uv_path = find_uv()
|
|
if uv_path is None and frontend in {"build[uv]", "uv"}:
|
|
pytest.skip("Can't find uv, so skipping uv tests")
|
|
if uv_path is not None and frontend == "build" and platform not in {"android", "ios"}:
|
|
pytest.skip("No need to check build when uv is present")
|
|
|
|
# temporary workaround: uv doesn't work with graalpy yet
|
|
uses_uv = "uv" in frontend
|
|
env: dict[str, str] = {"CIBW_BUILD_FRONTEND": frontend}
|
|
if uses_uv:
|
|
utils.include_graalpy_in_expected_wheels = False
|
|
env["CIBW_SKIP"] = "gp*" # skip graalpy when using uv, until uv supports it
|
|
try:
|
|
yield env
|
|
finally:
|
|
if uses_uv:
|
|
utils.include_graalpy_in_expected_wheels = True
|
|
|
|
|
|
@pytest.fixture
|
|
def docker_cleanup() -> Generator[None, None, None]:
|
|
def get_images() -> set[str]:
|
|
if detect_ci_provider() is None or get_platform() != "linux":
|
|
return set()
|
|
images = subprocess.run(
|
|
["docker", "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
|
|
finally:
|
|
images_after = get_images()
|
|
for image in images_after - images_before:
|
|
subprocess.run(["docker", "rmi", image], check=False)
|