From 1bc7e904b8fcefa45e255b671f0304d211314a05 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Fri, 16 May 2025 11:11:16 +0100 Subject: [PATCH] Improve integration test options (#2385) * Improve integration test options - Keep the platform and enable options in os.environ, don't duplicate state in utils - Provide more user-friendly options for setting enable and platform in integration tests * Ensure that CIBW_ENABLE is set in the test process * Use get_enable_groups() where necessary --- .github/workflows/test.yml | 2 +- docs/contributing.md | 4 +-- test/conftest.py | 56 +++++++++++++++++++++++++++----- test/test_0_basic.py | 3 +- test/test_abi_variants.py | 4 +-- test/test_before_build.py | 4 +-- test/test_before_test.py | 2 +- test/test_build_frontend_args.py | 4 +-- test/test_container_engine.py | 4 +-- test/test_container_images.py | 2 +- test/test_cpp_standards.py | 8 ++--- test/test_dependency_versions.py | 16 +++++---- test/test_emulation.py | 2 +- test/test_environment.py | 4 +-- test/test_ios.py | 10 +++--- test/test_linux_python.py | 2 +- test/test_macos_archs.py | 12 +++---- test/test_manylinuxXXXX_only.py | 2 +- test/test_musllinux_X_Y_only.py | 2 +- test/test_pep518.py | 4 +-- test/test_troubleshooting.py | 4 +-- test/test_wheel_tag.py | 2 +- test/test_windows.py | 2 +- test/utils.py | 50 ++++++++++++++++++---------- 24 files changed, 130 insertions(+), 75 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 379edcdc..92d2d659 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -83,7 +83,7 @@ jobs: CIBW_ENABLE=all else # get the default CIBW_ENABLE value from the test module - CIBW_ENABLE=$(uv run --no-sync python -c 'import sys, test.conftest as c; sys.stdout.write(c.DEFAULT_CIBW_ENABLE)') + CIBW_ENABLE=$(uv run --no-sync python -c 'import sys, test.utils as c; sys.stdout.write(c.DEFAULT_CIBW_ENABLE)') # if this is a PR, check for labels if [[ -n "$GITHUB_PR_LABEL_CI_PYPY" ]]; then diff --git a/docs/contributing.md b/docs/contributing.md index 86cd49bd..40ebaa8b 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -78,11 +78,11 @@ nox -s tests -- test -k before_build A few notes- -- Because they run inside a container, Linux tests can run on all platforms where Docker is installed, so they're convenient for running integration tests locally. Set CIBW_PLATFORM to do this: `CIBW_PLATFORM=linux nox -s tests -- test`. +- Because they run inside a container, Linux tests can run on all platforms where Docker is installed, so they're convenient for running integration tests locally. Set the `--platform` flag on pytest to do this: `nox -s tests -- test --platform linux`. - Running the macOS integration tests requires _system installs_ of Python from python.org for all the versions that are tested. We won't attempt to install these when running locally, but you can do so manually using the URL in the error message that is printed when the install is not found. -- The 'enable groups' run by default are just 'cpython-prerelease' and 'cpython-freethreading'. You can add other groups like pypy or graalpy by setting the [CIBW_ENABLE](options.md#enable) environment variable. On GitHub PRs, you can add a label to the PR to enable these groups. +- The ['enable groups'](options.md#enable) run by default are just 'cpython-prerelease' and 'cpython-freethreading'. You can add other groups like pypy or graalpy by passing the `--enable` argument to pytest, i.e. `nox -s tests -- test --enable pypy`. On GitHub PRs, you can add a label to the PR to enable these groups. #### Running pytest directly diff --git a/test/conftest.py b/test/conftest.py index 8c3e1522..88d6175b 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -9,12 +9,11 @@ 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 .utils import EMULATED_ARCHS, platform - -# default to just cpython -DEFAULT_CIBW_ENABLE = "cpython-freethreading cpython-prerelease cpython-experimental-riscv64" +from .utils import DEFAULT_CIBW_ENABLE, EMULATED_ARCHS, get_platform def pytest_addoption(parser: pytest.Parser) -> None: @@ -32,8 +31,47 @@ def pytest_addoption(parser: pytest.Parser) -> None: 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.", + ) - os.environ.setdefault("CIBW_ENABLE", DEFAULT_CIBW_ENABLE) + +def pytest_configure(config): + 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: @@ -91,7 +129,7 @@ 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": + 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 @@ -116,7 +154,7 @@ def docker_warmup_fixture( @pytest.fixture(params=["pip", "build"]) def build_frontend_env_nouv(request: pytest.FixtureRequest) -> dict[str, str]: frontend = request.param - if platform == "pyodide" and frontend == "pip": + if get_platform() == "pyodide" and frontend == "pip": pytest.skip("Can't use pip as build frontend for pyodide platform") return {"CIBW_BUILD_FRONTEND": frontend} @@ -125,7 +163,7 @@ def build_frontend_env_nouv(request: pytest.FixtureRequest) -> dict[str, str]: @pytest.fixture def build_frontend_env(build_frontend_env_nouv: dict[str, str]) -> dict[str, str]: frontend = build_frontend_env_nouv["CIBW_BUILD_FRONTEND"] - if frontend != "build" or platform == "pyodide" or find_uv() is None: + if frontend != "build" or get_platform() == "pyodide" or find_uv() is None: return build_frontend_env_nouv return {"CIBW_BUILD_FRONTEND": "build[uv]"} @@ -134,7 +172,7 @@ def build_frontend_env(build_frontend_env_nouv: dict[str, str]) -> dict[str, str @pytest.fixture def docker_cleanup() -> Generator[None, None, None]: def get_images() -> set[str]: - if detect_ci_provider() is None or platform != "linux": + if detect_ci_provider() is None or get_platform() != "linux": return set() images = subprocess.run( ["docker", "image", "ls", "--format", "{{json .ID}}"], diff --git a/test/test_0_basic.py b/test/test_0_basic.py index d0503c10..c1a8dfcb 100644 --- a/test/test_0_basic.py +++ b/test/test_0_basic.py @@ -1,4 +1,3 @@ -import os import textwrap import pytest @@ -40,7 +39,7 @@ def test(tmp_path, build_frontend_env, capfd): expected_wheels = utils.expected_wheels("spam", "0.1.0") assert set(actual_wheels) == set(expected_wheels) - enable_groups = EnableGroup.parse_option_value(os.environ.get("CIBW_ENABLE", "")) + enable_groups = utils.get_enable_groups() if EnableGroup.GraalPy not in enable_groups: # Verify pip warning not shown captured = capfd.readouterr() diff --git a/test/test_abi_variants.py b/test/test_abi_variants.py index 62f03850..780f6d90 100644 --- a/test/test_abi_variants.py +++ b/test/test_abi_variants.py @@ -47,7 +47,7 @@ def test_abi3(tmp_path): ) # check that the expected wheels are produced - if utils.platform == "pyodide": + if utils.get_platform() == "pyodide": # there's only 1 possible configuration for pyodide, cp312 expected_wheels = utils.expected_wheels("spam", "0.1.0", python_abi_tags=["cp310-abi3"]) else: @@ -196,7 +196,7 @@ def test_abi_none(tmp_path, capfd): # check that each wheel was built once, and reused captured = capfd.readouterr() assert "Building wheel..." in captured.out - if utils.platform == "pyodide": + if utils.get_platform() == "pyodide": # there's only 1 possible configuration for pyodide, we won't see the message expected on following builds assert "Found previously built wheel" not in captured.out else: diff --git a/test/test_before_build.py b/test/test_before_build.py index 9494de7b..767bb06c 100644 --- a/test/test_before_build.py +++ b/test/test_before_build.py @@ -6,7 +6,7 @@ import pytest from . import test_projects, utils # pyodide does not support building without isolation, need to check the base_prefix -SYS_PREFIX = f"sys.{'base_' if utils.platform == 'pyodide' else ''}prefix" +SYS_PREFIX = f"sys.{'base_' if utils.get_platform() == 'pyodide' else ''}prefix" project_with_before_build_asserts = test_projects.new_c_project( @@ -45,7 +45,7 @@ def test(tmp_path): f'''python -c "import pathlib, sys; pathlib.Path('{{project}}/pythonprefix_bb.txt').write_text({SYS_PREFIX})"''' ) frontend = "build" - if utils.platform != "pyodide": + if utils.get_platform() != "pyodide": before_build = f"python -m pip install setuptools && {before_build}" frontend = f"{frontend};args: --no-isolation" diff --git a/test/test_before_test.py b/test/test_before_test.py index a3700f84..c6ed5a7b 100644 --- a/test/test_before_test.py +++ b/test/test_before_test.py @@ -43,7 +43,7 @@ def test(tmp_path, build_frontend_env): '''python -c "import pathlib, sys; pathlib.Path('{project}/pythonprefix_bt.txt').write_text(sys.prefix)"''', ] - if utils.platform == "pyodide": + if utils.get_platform() == "pyodide": before_test_steps.extend( ["pyodide build {project}/dependency", "pip install --find-links dist/ spam"] ) diff --git a/test/test_build_frontend_args.py b/test/test_build_frontend_args.py index bffdd14f..83c0a177 100644 --- a/test/test_build_frontend_args.py +++ b/test/test_build_frontend_args.py @@ -20,7 +20,7 @@ def test_build_frontend_args(tmp_path, capfd, frontend_name): # the build will fail because the frontend is called with '-h' - it prints the help message add_env = {"CIBW_BUILD_FRONTEND": f"{frontend_name}; args: -h"} - if utils.platform == "pyodide": + if utils.get_platform() == "pyodide": add_env["TERM"] = "dumb" # disable color / style add_env["NO_COLOR"] = "1" with pytest.raises(subprocess.CalledProcessError): @@ -33,7 +33,7 @@ def test_build_frontend_args(tmp_path, capfd, frontend_name): if frontend_name == "pip": assert "Usage:" in captured.out assert "Wheel Options:" in captured.out - elif utils.platform == "pyodide": + elif utils.get_platform() == "pyodide": assert "Usage: pyodide build" in captured.out else: assert "usage:" in captured.out diff --git a/test/test_container_engine.py b/test/test_container_engine.py index 4503b3b4..510916f0 100644 --- a/test/test_container_engine.py +++ b/test/test_container_engine.py @@ -6,7 +6,7 @@ basic_project = test_projects.new_c_project() def test_podman(tmp_path, capfd, request): - if utils.platform != "linux": + if utils.get_platform() != "linux": pytest.skip("the test is only relevant to the linux build") if not request.config.getoption("--run-podman"): @@ -36,7 +36,7 @@ def test_podman(tmp_path, capfd, request): def test_create_args(tmp_path, capfd): - if utils.platform != "linux": + if utils.get_platform() != "linux": pytest.skip("the test is only relevant to the linux build") project_dir = tmp_path / "project" diff --git a/test/test_container_images.py b/test/test_container_images.py index f1d4fda8..02263c64 100644 --- a/test/test_container_images.py +++ b/test/test_container_images.py @@ -23,7 +23,7 @@ dockcross_only_project = test_projects.new_c_project( @pytest.mark.usefixtures("docker_cleanup") def test(tmp_path): - if utils.platform != "linux": + if utils.get_platform() != "linux": pytest.skip("the test is only relevant to the linux build") if platform.machine() not in ["x86_64", "i686"]: pytest.skip( diff --git a/test/test_cpp_standards.py b/test/test_cpp_standards.py index 85fcb6d9..fb7a0a84 100644 --- a/test/test_cpp_standards.py +++ b/test/test_cpp_standards.py @@ -57,7 +57,7 @@ def test_cpp11(tmp_path): project_dir = tmp_path / "project" cpp11_project = cpp_test_project.copy() cpp11_project.template_context["extra_compile_args"] = ( - ["/std:c++11"] if utils.platform == "windows" else ["-std=c++11"] + ["/std:c++11"] if utils.get_platform() == "windows" else ["-std=c++11"] ) cpp11_project.template_context["spam_cpp_top_level_add"] = "#include " cpp11_project.generate(project_dir) @@ -73,7 +73,7 @@ def test_cpp14(tmp_path): project_dir = tmp_path / "project" cpp14_project = cpp_test_project.copy() cpp14_project.template_context["extra_compile_args"] = ( - ["/std:c++14"] if utils.platform == "windows" else ["-std=c++14"] + ["/std:c++14"] if utils.get_platform() == "windows" else ["-std=c++14"] ) cpp14_project.template_context["spam_cpp_top_level_add"] = "int a = 100'000;" cpp14_project.generate(project_dir) @@ -89,7 +89,7 @@ def test_cpp17(tmp_path): project_dir = tmp_path / "project" cpp17_project = cpp_test_project.copy() cpp17_project.template_context["extra_compile_args"] = [ - "/std:c++17" if utils.platform == "windows" else "-std=c++17" + "/std:c++17" if utils.get_platform() == "windows" else "-std=c++17" ] cpp17_project.template_context["spam_cpp_top_level_add"] = r""" #include @@ -98,7 +98,7 @@ def test_cpp17(tmp_path): cpp17_project.generate(project_dir) add_env = {} - if utils.platform == "macos": + if utils.get_platform() == "macos": add_env["MACOSX_DEPLOYMENT_TARGET"] = "10.13" actual_wheels = utils.cibuildwheel_run(project_dir, add_env=add_env, single_python=True) diff --git a/test/test_dependency_versions.py b/test/test_dependency_versions.py index d4b9416f..6e29adef 100644 --- a/test/test_dependency_versions.py +++ b/test/test_dependency_versions.py @@ -53,11 +53,15 @@ def get_versions_from_constraint_file(constraint_file: Path) -> dict[str, str]: @pytest.mark.parametrize("python_version", ["3.8", "3.12"]) def test_pinned_versions(tmp_path, python_version, build_frontend_env_nouv): - if utils.platform == "linux": + if utils.get_platform() == "linux": pytest.skip("linux doesn't pin individual tool versions, it pins manylinux images instead") - if python_version != "3.12" and utils.platform == "pyodide": + if python_version != "3.12" and utils.get_platform() == "pyodide": pytest.skip(f"pyodide does not support Python {python_version}") - if python_version == "3.8" and utils.platform == "windows" and platform.machine() == "ARM64": + if ( + python_version == "3.8" + and utils.get_platform() == "windows" + and platform.machine() == "ARM64" + ): pytest.skip(f"Windows ARM64 does not support Python {python_version}") project_dir = tmp_path / "project" @@ -96,7 +100,7 @@ def test_pinned_versions(tmp_path, python_version, build_frontend_env_nouv): @pytest.mark.parametrize("method", ["inline", "file"]) def test_dependency_constraints(method, tmp_path, build_frontend_env_nouv): - if utils.platform == "linux": + if utils.get_platform() == "linux": pytest.skip("linux doesn't pin individual tool versions, it pins manylinux images instead") project_dir = tmp_path / "project" @@ -129,7 +133,7 @@ def test_dependency_constraints(method, tmp_path, build_frontend_env_nouv): build_environment = {} if ( - utils.platform == "windows" + utils.get_platform() == "windows" and method == "file" and build_frontend_env_nouv["CIBW_BUILD_FRONTEND"] == "build" ): @@ -159,7 +163,7 @@ def test_dependency_constraints(method, tmp_path, build_frontend_env_nouv): expected_wheels = utils.expected_wheels("spam", "0.1.0") if ( - utils.platform == "windows" + utils.get_platform() == "windows" and method == "file" and build_frontend_env_nouv["CIBW_BUILD_FRONTEND"] == "build" ): diff --git a/test/test_emulation.py b/test/test_emulation.py index 9ffdc6de..9749d9da 100644 --- a/test/test_emulation.py +++ b/test/test_emulation.py @@ -53,7 +53,7 @@ def test(tmp_path, request): def test_setting_arch_on_other_platforms(tmp_path, capfd): - if utils.platform == "linux": + if utils.get_platform() == "linux": pytest.skip("this test checks the behaviour on platforms other than linux") project_dir = tmp_path / "project" diff --git a/test/test_environment.py b/test/test_environment.py index 6e7ed6fd..ee50155b 100644 --- a/test/test_environment.py +++ b/test/test_environment.py @@ -64,7 +64,7 @@ def test_overridden_path(tmp_path, capfd): output_dir.mkdir() # mess up PATH, somehow - if utils.platform == "linux": + if utils.get_platform() == "linux": with pytest.raises(subprocess.CalledProcessError): utils.cibuildwheel_run( project_dir, @@ -128,7 +128,7 @@ def test_overridden_pip_constraint(tmp_path, build_frontend): ) project.generate(project_dir) - if utils.platform == "linux": + if utils.get_platform() == "linux": # put the constraints file in the project directory, so it's available # in the docker container constraints_file = project_dir / "constraints.txt" diff --git a/test/test_ios.py b/test/test_ios.py index c3cfa61a..7c5080da 100644 --- a/test/test_ios.py +++ b/test/test_ios.py @@ -38,7 +38,7 @@ class TestPlatform(TestCase): ], ) def test_ios_platforms(tmp_path, build_config, monkeypatch, capfd): - if utils.platform != "macos": + if utils.get_platform() != "macos": pytest.skip("this test can only run on macOS") if utils.get_xcode_version() < (13, 0): pytest.skip("this test only works with Xcode 13.0 or greater") @@ -103,7 +103,7 @@ def test_ios_platforms(tmp_path, build_config, monkeypatch, capfd): def test_no_test_sources(tmp_path, capfd): """Build will fail if test-sources isn't defined.""" - if utils.platform != "macos": + if utils.get_platform() != "macos": pytest.skip("this test can only run on macOS") if utils.get_xcode_version() < (13, 0): pytest.skip("this test only works with Xcode 13.0 or greater") @@ -130,7 +130,7 @@ def test_no_test_sources(tmp_path, capfd): def test_missing_xbuild_tool(tmp_path, capfd): """Build will fail if xbuild-tools references a non-existent tool.""" - if utils.platform != "macos": + if utils.get_platform() != "macos": pytest.skip("this test can only run on macOS") if utils.get_xcode_version() < (13, 0): pytest.skip("this test only works with Xcode 13.0 or greater") @@ -158,7 +158,7 @@ def test_missing_xbuild_tool(tmp_path, capfd): def test_no_xbuild_tool_definition(tmp_path, capfd): """Build will succeed with a warning if there is no xbuild-tools definition.""" - if utils.platform != "macos": + if utils.get_platform() != "macos": pytest.skip("this test can only run on macOS") if utils.get_xcode_version() < (13, 0): pytest.skip("this test only works with Xcode 13.0 or greater") @@ -203,7 +203,7 @@ def test_no_xbuild_tool_definition(tmp_path, capfd): def test_empty_xbuild_tool_definition(tmp_path, capfd): """Build will succeed with no warning if there is an empty xbuild-tools definition.""" - if utils.platform != "macos": + if utils.get_platform() != "macos": pytest.skip("this test can only run on macOS") if utils.get_xcode_version() < (13, 0): pytest.skip("this test only works with Xcode 13.0 or greater") diff --git a/test/test_linux_python.py b/test/test_linux_python.py index 7c5f58a5..48a70c2e 100644 --- a/test/test_linux_python.py +++ b/test/test_linux_python.py @@ -7,7 +7,7 @@ from . import test_projects, utils def test_python_exist(tmp_path, capfd): - if utils.platform != "linux": + if utils.get_platform() != "linux": pytest.skip("the test is only relevant to the linux build") machine = platform.machine() if machine not in ["x86_64", "i686"]: diff --git a/test/test_macos_archs.py b/test/test_macos_archs.py index 848303ec..f38cff56 100644 --- a/test/test_macos_archs.py +++ b/test/test_macos_archs.py @@ -20,7 +20,7 @@ DEPLOYMENT_TARGET_TOO_LOW_WARNING = "Bumping MACOSX_DEPLOYMENT_TARGET" def test_cross_compiled_build(tmp_path): - if utils.platform != "macos": + if utils.get_platform() != "macos": pytest.skip("this test is only relevant to macos") if utils.get_xcode_version() < (12, 2): pytest.skip("this test only works with Xcode 12.2 or greater") @@ -54,7 +54,7 @@ def test_cross_compiled_build(tmp_path): ], ) def test_cross_compiled_test(tmp_path, capfd, build_universal2, test_config): - if utils.platform != "macos": + if utils.get_platform() != "macos": pytest.skip("this test is only relevant to macos") if utils.get_xcode_version() < (12, 2): pytest.skip("this test only works with Xcode 12.2 or greater") @@ -114,7 +114,7 @@ def test_cross_compiled_test(tmp_path, capfd, build_universal2, test_config): def test_deployment_target_warning_is_firing(tmp_path, capfd): # force the warning to check that we can detect it if it happens - if utils.platform != "macos": + if utils.get_platform() != "macos": pytest.skip("this test is only relevant to macos") project_dir = tmp_path / "project" @@ -136,7 +136,7 @@ def test_deployment_target_warning_is_firing(tmp_path, capfd): @pytest.mark.parametrize("skip_arm64_test", [False, True]) def test_universal2_testing_on_x86_64(tmp_path, capfd, skip_arm64_test): - if utils.platform != "macos": + if utils.get_platform() != "macos": pytest.skip("this test is only relevant to macos") if utils.get_xcode_version() < (12, 2): pytest.skip("this test only works with Xcode 12.2 or greater") @@ -176,7 +176,7 @@ def test_universal2_testing_on_x86_64(tmp_path, capfd, skip_arm64_test): def test_universal2_testing_on_arm64(build_frontend_env, tmp_path, capfd): # cibuildwheel should test the universal2 wheel on both x86_64 and arm64, when run on arm64 - if utils.platform != "macos": + if utils.get_platform() != "macos": pytest.skip("this test is only relevant to macos") if platform.machine() != "arm64": pytest.skip("this test only works on arm64") @@ -206,7 +206,7 @@ def test_universal2_testing_on_arm64(build_frontend_env, tmp_path, capfd): def test_cp38_arm64_testing(tmp_path, capfd, request): - if utils.platform != "macos": + if utils.get_platform() != "macos": pytest.skip("this test is only relevant to macos") if utils.get_xcode_version() < (12, 2): pytest.skip("this test only works with Xcode 12.2 or greater") diff --git a/test/test_manylinuxXXXX_only.py b/test/test_manylinuxXXXX_only.py index c8d2c6d9..5f083971 100644 --- a/test/test_manylinuxXXXX_only.py +++ b/test/test_manylinuxXXXX_only.py @@ -62,7 +62,7 @@ project_with_manylinux_symbols = test_projects.new_c_project( ) @pytest.mark.usefixtures("docker_cleanup") def test(manylinux_image, tmp_path): - if utils.platform != "linux": + if utils.get_platform() != "linux": pytest.skip("the container image test is only relevant to the linux build") elif manylinux_image in {"manylinux_2_28", "manylinux_2_34"} and platform.machine() == "i686": pytest.skip(f"{manylinux_image} doesn't exist for i686 architecture") diff --git a/test/test_musllinux_X_Y_only.py b/test/test_musllinux_X_Y_only.py index f8396aa6..5e2f2e24 100644 --- a/test/test_musllinux_X_Y_only.py +++ b/test/test_musllinux_X_Y_only.py @@ -28,7 +28,7 @@ project_with_manylinux_symbols = test_projects.new_c_project( ) @pytest.mark.usefixtures("docker_cleanup") def test(musllinux_image, tmp_path): - if utils.platform != "linux": + if utils.get_platform() != "linux": pytest.skip("the container image test is only relevant to the linux build") project_dir = tmp_path / "project" diff --git a/test/test_pep518.py b/test/test_pep518.py index c861f0da..d519cc29 100644 --- a/test/test_pep518.py +++ b/test/test_pep518.py @@ -37,7 +37,7 @@ def test_pep518(tmp_path, build_frontend_env): # from a virtualenv seeded executable. See # https://github.com/oracle/graalpython/issues/491 and remove this once # fixed upstream. - if build_frontend_env["CIBW_BUILD_FRONTEND"] == "build" and utils.platform == "windows": + if build_frontend_env["CIBW_BUILD_FRONTEND"] == "build" and utils.get_platform() == "windows": build_frontend_env["CIBW_SKIP"] = "gp*" # build the wheels @@ -50,7 +50,7 @@ def test_pep518(tmp_path, build_frontend_env): # from a virtualenv seeded executable. See # https://github.com/oracle/graalpython/issues/491 and remove this once # fixed upstream. - if build_frontend_env["CIBW_BUILD_FRONTEND"] == "build" and utils.platform == "windows": + if build_frontend_env["CIBW_BUILD_FRONTEND"] == "build" and utils.get_platform() == "windows": expected_wheels = [w for w in expected_wheels if "graalpy" not in w] assert set(actual_wheels) == set(expected_wheels) diff --git a/test/test_troubleshooting.py b/test/test_troubleshooting.py index 7039c268..1b8e8a37 100644 --- a/test/test_troubleshooting.py +++ b/test/test_troubleshooting.py @@ -15,7 +15,7 @@ def test_failed_build_with_so_files(tmp_path, capfd, build_frontend_env, project if project_contains_so_files: project.files["libnothing.so"] = "" - if utils.platform != "linux": + if utils.get_platform() != "linux": pytest.skip("this test is only relevant to the linux build") project_dir = tmp_path / "project" @@ -36,7 +36,7 @@ def test_failed_build_with_so_files(tmp_path, capfd, build_frontend_env, project @pytest.mark.parametrize("project_contains_so_files", [False, True]) def test_failed_repair_with_so_files(tmp_path, capfd, project_contains_so_files): - if utils.platform != "linux": + if utils.get_platform() != "linux": pytest.skip("this test is only relevant to the linux build") project = new_c_project() diff --git a/test/test_wheel_tag.py b/test/test_wheel_tag.py index 580481ff..09c99cca 100644 --- a/test/test_wheel_tag.py +++ b/test/test_wheel_tag.py @@ -6,7 +6,7 @@ basic_project = test_projects.new_c_project() def test_wheel_tag_is_correct_when_using_macosx_deployment_target(tmp_path): - if utils.platform != "macos": + if utils.get_platform() != "macos": pytest.skip("This test is only relevant to MACOSX_DEPLOYMENT_TARGET") project_dir = tmp_path / "project" diff --git a/test/test_windows.py b/test/test_windows.py index 142fccf2..073669ba 100644 --- a/test/test_windows.py +++ b/test/test_windows.py @@ -39,7 +39,7 @@ def skip_if_no_msvc(arm64: bool = False) -> None: @pytest.mark.parametrize("use_pyproject_toml", [True, False]) def test_wheel_tag_is_correct_when_using_windows_cross_compile(tmp_path, use_pyproject_toml): - if utils.platform != "windows": + if utils.get_platform() != "windows": pytest.skip("This test is only relevant to Windows") skip_if_no_msvc(arm64=True) diff --git a/test/utils.py b/test/utils.py index 1ed93642..dfc66aab 100644 --- a/test/utils.py +++ b/test/utils.py @@ -34,18 +34,29 @@ _AARCH64_CAN_RUN_ARMV7: Final[bool] = Architecture.aarch64.value not in EMULATED CIProvider.cirrus_ci: False, }.get(detect_ci_provider(), True) -platform = os.environ.get("CIBW_PLATFORM", "") -if platform: - pass -elif sys.platform.startswith("linux"): - platform = "linux" -elif sys.platform.startswith("darwin"): - platform = "macos" -elif sys.platform.startswith(("win32", "cygwin")): - platform = "windows" -else: - msg = f"Unsupported platform {sys.platform!r}" - raise Exception(msg) + +def get_platform() -> str: + """Return the current platform as determined by CIBW_PLATFORM or sys.platform.""" + platform = os.environ.get("CIBW_PLATFORM", "") + if platform: + return platform + elif sys.platform.startswith("linux"): + return "linux" + elif sys.platform.startswith("darwin"): + return "macos" + elif sys.platform.startswith(("win32", "cygwin")): + return "windows" + else: + msg = f"Unsupported platform {sys.platform!r}" + raise Exception(msg) + + +DEFAULT_CIBW_ENABLE = "cpython-freethreading cpython-prerelease cpython-experimental-riscv64" + + +def get_enable_groups() -> frozenset[EnableGroup]: + value = os.environ.get("CIBW_ENABLE", DEFAULT_CIBW_ENABLE) + return EnableGroup.parse_option_value(value) def cibuildwheel_get_build_identifiers( @@ -74,7 +85,7 @@ def cibuildwheel_get_build_identifiers( def _update_pip_cache_dir(env: dict[str, str]) -> None: # Fix for pip concurrency bug https://github.com/pypa/pip/issues/11340 # See https://github.com/pypa/cibuildwheel/issues/1254 for discussion. - if platform == "linux": + if get_platform() == "linux": return if "PIP_CACHE_DIR" in env: return @@ -168,12 +179,12 @@ def expected_wheels( """ if machine_arch is None: machine_arch = pm.machine() - if platform == "linux": + if get_platform() == "linux": machine_arch = arch_name_for_linux(machine_arch) architectures = [machine_arch] if not single_arch: - if platform == "linux": + if get_platform() == "linux": if machine_arch == "x86_64": architectures.append("i686") elif ( @@ -182,7 +193,7 @@ def expected_wheels( and _AARCH64_CAN_RUN_ARMV7 ): architectures.append("armv7l") - elif platform == "windows" and machine_arch == "AMD64": + elif get_platform() == "windows" and machine_arch == "AMD64": architectures.append("x86") return [ @@ -216,6 +227,8 @@ def _expected_wheels( """ Returns a list of expected wheels from a run of cibuildwheel. """ + platform = get_platform() + # per PEP 425 (https://www.python.org/dev/peps/pep-0425/), wheel files shall have name of the form # {distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl # {python tag} and {abi tag} are closely related to the python interpreter used to build the wheel @@ -246,6 +259,7 @@ def _expected_wheels( "cp313-cp313", ] + enable_groups = get_enable_groups() if EnableGroup.CPythonFreeThreading in enable_groups: python_abi_tags.append("cp313-cp313t") @@ -378,12 +392,12 @@ def get_xcode_version() -> tuple[int, int]: def skip_if_pyodide(reason: str) -> Any: - return pytest.mark.skipif(platform == "pyodide", reason=reason) + return pytest.mark.skipif(get_platform() == "pyodide", reason=reason) def invoke_pytest() -> str: # see https://github.com/pyodide/pyodide/issues/4802 - if platform == "pyodide" and sys.platform.startswith("darwin"): + if get_platform() == "pyodide" and sys.platform.startswith("darwin"): return "python -m pytest" return "pytest"