diff --git a/bin/run_tests.py b/bin/run_tests.py index 1c092394..5c0ad149 100755 --- a/bin/run_tests.py +++ b/bin/run_tests.py @@ -32,6 +32,23 @@ if __name__ == "__main__": # move cwd to the project root os.chdir(Path(__file__).resolve().parents[1]) + # doc tests + doc_test_args = [sys.executable, "-m", "pytest", "cibuildwheel"] + + print( + "\n\n================================== DOC TESTS ==================================", + flush=True, + ) + result = subprocess.run(doc_test_args, check=False) + if result.returncode not in (0, 5): + # Allow case where no doctests are collected (returncode 5) because + # circleci sets an explicit "-k" filter that disables doctests. There + # isn't a pattern that will only select doctests. This can be removed + # and have check=True if the circleci PYTEST_ADDOPTS is removed. + raise subprocess.CalledProcessError( + result.returncode, result.args, output=result.stdout, stderr=result.stderr + ) + # unit tests unit_test_args = [sys.executable, "-m", "pytest", "unit_test"] diff --git a/cibuildwheel/oci_container.py b/cibuildwheel/oci_container.py index 29ddb990..ca77c06d 100644 --- a/cibuildwheel/oci_container.py +++ b/cibuildwheel/oci_container.py @@ -36,6 +36,28 @@ class OCIPlatform(Enum): RISCV64 = "linux/riscv64" S390X = "linux/s390x" + @classmethod + def native(cls) -> "OCIPlatform": + """Return the current OCI platform, or raise ValueError if unknown.""" + arch = platform.machine().lower() + mapping = { + "i386": cls.i386, + "i686": cls.i386, + "x86_64": cls.AMD64, + "amd64": cls.AMD64, + "armv7l": cls.ARMV7, + "aarch64": cls.ARM64, + "arm64": cls.ARM64, + "ppc64le": cls.PPC64LE, + "riscv64": cls.RISCV64, + "s390x": cls.S390X, + } + try: + return mapping[arch] + except KeyError as ex: + msg = f"Unsupported platform architecture: {arch}" + raise OSError(msg) from ex + @dataclasses.dataclass(frozen=True) class OCIContainerEngineConfig: @@ -152,11 +174,19 @@ class OCIContainer: back to cibuildwheel. Example: + >>> # xdoctest: +REQUIRES(LINUX) >>> from cibuildwheel.oci_container import * # NOQA >>> from cibuildwheel.options import _get_pinned_container_images + >>> import pytest + >>> try: + ... oci_platform = OCIPlatform.native() + ... except OSError as ex: + ... pytest.skip(str(ex)) + >>> if oci_platform != OCIPlatform.AMD64: + ... pytest.skip('only runs on amd64') >>> image = _get_pinned_container_images()['x86_64']['manylinux2014'] >>> # Test the default container - >>> with OCIContainer(image=image) as self: + >>> with OCIContainer(image=image, oci_platform=oci_platform) as self: ... self.call(["echo", "hello world"]) ... self.call(["cat", "/proc/1/cgroup"]) ... print(self.get_environment()) diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index e3417adf..fa10ff6f 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -391,6 +391,7 @@ class OptionsReader: by the platform. Example: + >>> # xdoctest: +SKIP >>> options_reader = OptionsReader(config_file, platform='macos') >>> options_reader.get('cool-color') diff --git a/cibuildwheel/util/helpers.py b/cibuildwheel/util/helpers.py index f2e017ef..adefb495 100644 --- a/cibuildwheel/util/helpers.py +++ b/cibuildwheel/util/helpers.py @@ -20,8 +20,8 @@ def format_safe(template: str, **kwargs: str | os.PathLike[str]) -> str: >>> format_safe('{a} {b[4]:3f}', a='123') '123 {b[4]:3f}' - To avoid variable expansion, precede with a single backslash e.g. - >>> format_safe('\\{a} {b}', a='123') + To avoid variable expansion, precede with a single hash e.g. + >>> format_safe('#{a} {b}', a='123') '{a} {b}' """ diff --git a/noxfile.py b/noxfile.py index d475c60e..66f47f3c 100755 --- a/noxfile.py +++ b/noxfile.py @@ -58,6 +58,7 @@ def tests(session: nox.Session) -> None: session.run("pytest", *session.posargs) else: unit_test_args = ["--run-docker"] if sys.platform.startswith("linux") else [] + session.run("pytest", "cibuildwheel") # run doctests session.run("pytest", "unit_test", *unit_test_args) session.run("pytest", "test", "-x", "--durations", "0", "--timeout=2400", "test") diff --git a/pyproject.toml b/pyproject.toml index d1bfd6e8..6f6a17d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,6 +93,7 @@ test = [ "setuptools", "tomli_w", "validate-pyproject", + "xdoctest", ] dev = [ {include-group = "bin"}, @@ -103,7 +104,7 @@ dev = [ [tool.pytest.ini_options] minversion = "6.0" -addopts = ["-ra", "--showlocals", "--strict-markers", "--strict-config"] +addopts = ["-ra", "--showlocals", "--strict-markers", "--strict-config", "--xdoctest", "--ignore-glob=*venv*"] junit_family = "xunit2" xfail_strict = true filterwarnings = ["error"] diff --git a/unit_test/oci_container_test.py b/unit_test/oci_container_test.py index 373144a1..b172fe6e 100644 --- a/unit_test/oci_container_test.py +++ b/unit_test/oci_container_test.py @@ -1,6 +1,5 @@ import json import os -import platform import random import shutil import subprocess @@ -29,16 +28,7 @@ from cibuildwheel.oci_container import ( # for these tests we use manylinux2014 images, because they're available on # multi architectures and include python3.8 DEFAULT_IMAGE = "quay.io/pypa/manylinux2014:2025.03.08-1" -pm = platform.machine() -DEFAULT_OCI_PLATFORM = { - "AMD64": OCIPlatform.AMD64, - "x86_64": OCIPlatform.AMD64, - "ppc64le": OCIPlatform.PPC64LE, - "s390x": OCIPlatform.S390X, - "aarch64": OCIPlatform.ARM64, - "arm64": OCIPlatform.ARM64, - "ARM64": OCIPlatform.ARM64, -}[pm] +DEFAULT_OCI_PLATFORM = OCIPlatform.native() PODMAN = OCIContainerEngineConfig(name="podman") @@ -494,7 +484,7 @@ def test_parse_engine_config(config, name, create_args, capsys): ) -@pytest.mark.skipif(pm != "x86_64", reason="Only runs on x86_64") +@pytest.mark.skipif(DEFAULT_OCI_PLATFORM != OCIPlatform.AMD64, reason="Only runs on x86_64") def test_enforce_32_bit(container_engine): with OCIContainer( engine=container_engine, image=DEFAULT_IMAGE, oci_platform=OCIPlatform.i386 @@ -551,7 +541,7 @@ def test_local_image( ) -> None: if ( detect_ci_provider() == CIProvider.travis_ci - and pm != "x86_64" + and DEFAULT_OCI_PLATFORM != OCIPlatform.AMD64 and platform != DEFAULT_OCI_PLATFORM ): pytest.skip("Skipping test because docker on this platform does not support QEMU") @@ -581,7 +571,7 @@ def test_local_image( def test_multiarch_image(container_engine, platform): if ( detect_ci_provider() == CIProvider.travis_ci - and pm != "x86_64" + and DEFAULT_OCI_PLATFORM != OCIPlatform.AMD64 and platform != DEFAULT_OCI_PLATFORM ): pytest.skip("Skipping test because docker on this platform does not support QEMU")