from __future__ import annotations 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 TYPE_CHECKING = False if TYPE_CHECKING: import contextlib 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)