* WIP - initial punt at audit command * Add `abi3audit` as a dependency * Add helper functions to check stable ABI wheels * Run `abi3audit` for macOS and Windows wheels * Copy out of container for repairing? * Add some notes that `cibuildwheel` runs `abi3audit` * Add basic unit tests * Add a basic C extension with `Py_LIMITED_API` * Add a test project that violates Stable ABI * Fix linux test * Skip abi3 wheel tests for Pyodide * Patch the correct subprocess module * wrap cleanup of abi3audit dir * Write the docs for the new options * Move to above testing in docs * Implement audit-requires and audit-command * Some cleanups after self-review * Add default value * fix type errors * the key is `audit-command`, not `audit` * Add a variety of tests for audit requires options * Add `test_audit_requires` similar to `test_test_requires` * Add some configurability-related audit tests * Fix parsing error with options docs leaving out commands * Better way to extract version (maybe helps Pyodide?) * Fix a case of unbound `use_uv` * Standardise: rename to `abi3_wheel` * Fix audit command run message * Simplify custom audit command a bit * Remove unnecessary skip for Pyodide * Pyodide should have no default audit command * More accurate skip messages for Pyodide skips * Wheels are audited after they are repaired * Regenerate constraints to include `abi3audit` * Fix typos * Some attempts for Windows fixes * Check `pyvenv.cfg` instead of directory existence * Add validation for lack of wheel placeholders * Try yet another Windows `uv` fix * Regenerate diagram and re-trigger Azure CI * Add missing `import sys` for abi3 C extension tests * Remove audit-command at the global level * Clarify `abi3audit` pinning a little bit * Regen constraints * Discard changes to cibuildwheel/resources/constraints-pyodide312.txt * Discard changes to cibuildwheel/resources/constraints-pyodide313.txt * try opt-in uv again * fix issue on windows on Python 3.13 related to nested venvs On win / python 3.13, virtualenv creates a venv where the 'home' points back to the venv that sys.executable was running in, rather than the root install. that seemingly leads to problems with package resolution, where pip.exe couldn't find the pip python package. this appears to fix it! * Update constraints * chore: revert python-discovery bump Assisted-by: OpenCode:glm-5.1 Signed-off-by: Henry Schreiner <henryfs@princeton.edu> * fix: restore workaround for graalpy Assisted-by: OpenCode:glm-5.1 Signed-off-by: Henry Schreiner <henryfs@princeton.edu> --------- Signed-off-by: Henry Schreiner <henryfs@princeton.edu> Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Co-authored-by: Henry Schreiner <henryfs@princeton.edu>
140 lines
5.6 KiB
Python
140 lines
5.6 KiB
Python
import contextlib
|
|
import subprocess
|
|
from pathlib import Path
|
|
from unittest.mock import Mock, patch
|
|
|
|
import pytest
|
|
|
|
from cibuildwheel import errors
|
|
from cibuildwheel.audit import needs_audit, run_audit
|
|
|
|
|
|
def mock_virtualenv() -> contextlib.AbstractContextManager[Mock]:
|
|
return patch(
|
|
"cibuildwheel.audit.virtualenv",
|
|
return_value={
|
|
"PATH": "/bin",
|
|
"VIRTUAL_ENV": "/tmp/v",
|
|
},
|
|
)
|
|
|
|
|
|
class TestNeedsAudit:
|
|
def test_empty_commands(self) -> None:
|
|
assert needs_audit([], "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl") is False
|
|
|
|
def test_wheel_placeholder_matches_any_wheel(self) -> None:
|
|
assert needs_audit(
|
|
["my-tool {wheel}"], "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl"
|
|
)
|
|
|
|
def test_abi3_placeholder_skips_non_abi3(self) -> None:
|
|
assert (
|
|
needs_audit(
|
|
["abi3audit {abi3_wheel}"], "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl"
|
|
)
|
|
is False
|
|
)
|
|
|
|
def test_abi3_placeholder_matches_abi3(self) -> None:
|
|
assert needs_audit(
|
|
["abi3audit {abi3_wheel}"], "example-1.0.0-cp38-abi3-manylinux_2_17_x86_64.whl"
|
|
)
|
|
|
|
def test_mixed_commands_matches_if_any_applies(self) -> None:
|
|
commands = ["abi3audit {abi3_wheel}", "twine check {wheel}"]
|
|
# non-abi3 wheel still needs audit because of the {wheel} command
|
|
assert needs_audit(commands, "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl")
|
|
|
|
def test_command_without_placeholder_raises(self) -> None:
|
|
with pytest.raises(errors.ConfigurationError, match="must contain either"):
|
|
needs_audit(["my-tool"], "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl")
|
|
|
|
|
|
class TestRunAudit:
|
|
@pytest.fixture
|
|
def mock_build_options(self) -> Mock:
|
|
opts = Mock()
|
|
opts.audit_command = []
|
|
opts.audit_requires = []
|
|
opts.package_dir = Path("/fake/package")
|
|
opts.build_frontend.name = "build"
|
|
opts.dependency_constraints.get_for_python_version.return_value = None
|
|
return opts
|
|
|
|
def test_no_commands_does_nothing(self, tmp_path: Path, mock_build_options: Mock) -> None:
|
|
wheel = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl"
|
|
mock_build_options.audit_command = []
|
|
|
|
with patch("cibuildwheel.audit.shell") as mock_shell:
|
|
run_audit(tmp_dir=tmp_path, build_options=mock_build_options, wheel=wheel)
|
|
mock_shell.assert_not_called()
|
|
|
|
def test_runs_wheel_command(self, tmp_path: Path, mock_build_options: Mock) -> None:
|
|
wheel = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl"
|
|
mock_build_options.audit_command = ["my-tool {wheel}"]
|
|
|
|
with mock_virtualenv(), patch("cibuildwheel.audit.shell") as mock_shell:
|
|
run_audit(tmp_dir=tmp_path, build_options=mock_build_options, wheel=wheel)
|
|
mock_shell.assert_called_once()
|
|
cmd = mock_shell.call_args[0][0]
|
|
assert str(wheel) in cmd
|
|
|
|
def test_abi3_command_skipped_for_non_abi3(
|
|
self, tmp_path: Path, mock_build_options: Mock
|
|
) -> None:
|
|
wheel = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl"
|
|
mock_build_options.audit_command = ["abi3audit {abi3_wheel}"]
|
|
|
|
with patch("cibuildwheel.audit.shell") as mock_shell:
|
|
run_audit(tmp_dir=tmp_path, build_options=mock_build_options, wheel=wheel)
|
|
mock_shell.assert_not_called()
|
|
|
|
def test_abi3_command_runs_for_abi3(self, tmp_path: Path, mock_build_options: Mock) -> None:
|
|
wheel = tmp_path / "example-1.0.0-cp38-abi3-manylinux_2_17_x86_64.whl"
|
|
mock_build_options.audit_command = ["abi3audit {abi3_wheel}"]
|
|
|
|
with (
|
|
mock_virtualenv(),
|
|
patch("cibuildwheel.audit.shell") as mock_shell,
|
|
):
|
|
run_audit(tmp_dir=tmp_path, build_options=mock_build_options, wheel=wheel)
|
|
mock_shell.assert_called_once()
|
|
cmd = mock_shell.call_args[0][0]
|
|
assert str(wheel) in cmd
|
|
|
|
def test_raises_on_command_failure(self, tmp_path: Path, mock_build_options: Mock) -> None:
|
|
wheel = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl"
|
|
mock_build_options.audit_command = ["failing-tool {wheel}"]
|
|
|
|
with (
|
|
mock_virtualenv(),
|
|
patch(
|
|
"cibuildwheel.audit.shell",
|
|
side_effect=subprocess.CalledProcessError(1, "failing-tool"),
|
|
),
|
|
pytest.raises(errors.AuditCommandFailedError),
|
|
):
|
|
run_audit(tmp_dir=tmp_path, build_options=mock_build_options, wheel=wheel)
|
|
|
|
def test_multiple_commands_all_run(self, tmp_path: Path, mock_build_options: Mock) -> None:
|
|
wheel = tmp_path / "example-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl"
|
|
mock_build_options.audit_command = ["tool-a {wheel}", "tool-b {wheel}"]
|
|
|
|
with (
|
|
mock_virtualenv(),
|
|
patch("cibuildwheel.audit.shell") as mock_shell,
|
|
):
|
|
run_audit(tmp_dir=tmp_path, build_options=mock_build_options, wheel=wheel)
|
|
assert mock_shell.call_count == 2
|
|
|
|
def test_both_placeholders_raises(self, tmp_path: Path, mock_build_options: Mock) -> None:
|
|
wheel = tmp_path / "example-1.0.0-cp38-abi3-manylinux_2_17_x86_64.whl"
|
|
mock_build_options.audit_command = ["my-tool {wheel} {abi3_wheel}"]
|
|
|
|
with (
|
|
mock_virtualenv(),
|
|
pytest.raises(errors.ConfigurationError, match="cannot contain both"),
|
|
):
|
|
run_audit(tmp_dir=tmp_path, build_options=mock_build_options, wheel=wheel)
|