221 lines
6.9 KiB
Python
221 lines
6.9 KiB
Python
from __future__ import annotations
|
|
|
|
import subprocess
|
|
import textwrap
|
|
|
|
import pytest
|
|
|
|
from . import test_projects, utils
|
|
|
|
TYPE_CHECKING = False
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
pyproject_toml = r"""
|
|
[build-system]
|
|
requires = ["setuptools", "wheel"]
|
|
build-backend = "setuptools.build_meta"
|
|
"""
|
|
|
|
limited_api_project = test_projects.new_c_project(
|
|
setup_py_add=textwrap.dedent(
|
|
r"""
|
|
import sys
|
|
import sysconfig
|
|
|
|
IS_CPYTHON = sys.implementation.name == "cpython"
|
|
Py_GIL_DISABLED = sysconfig.get_config_var("Py_GIL_DISABLED")
|
|
CAN_USE_ABI3 = IS_CPYTHON and not Py_GIL_DISABLED
|
|
setup_options = {}
|
|
extension_kwargs = {}
|
|
if CAN_USE_ABI3 and sys.version_info[:2] >= (3, 10):
|
|
extension_kwargs["define_macros"] = [("Py_LIMITED_API", "0x030A0000")]
|
|
extension_kwargs["py_limited_api"] = True
|
|
setup_options = {"bdist_wheel": {"py_limited_api": "cp310"}}
|
|
"""
|
|
),
|
|
setup_py_extension_args_add="**extension_kwargs",
|
|
setup_py_setup_args_add="options=setup_options",
|
|
)
|
|
|
|
limited_api_project.files["pyproject.toml"] = pyproject_toml
|
|
|
|
# Project that claims abi3 but violates the stable ABI by calling
|
|
# PyUnicode_AsUTF8 (not in stable ABI until 3.13) without defining
|
|
# Py_LIMITED_API in the C code.
|
|
violating_abi3_project = test_projects.new_c_project(
|
|
setup_py_add=textwrap.dedent(
|
|
r"""
|
|
import sys
|
|
import sysconfig
|
|
|
|
IS_CPYTHON = sys.implementation.name == "cpython"
|
|
Py_GIL_DISABLED = sysconfig.get_config_var("Py_GIL_DISABLED")
|
|
CAN_USE_ABI3 = IS_CPYTHON and not Py_GIL_DISABLED
|
|
setup_options = {}
|
|
extension_kwargs = {}
|
|
if CAN_USE_ABI3 and sys.version_info[:2] >= (3, 10):
|
|
# Intentionally NOT defining Py_LIMITED_API as a C macro,
|
|
# but still tagging the wheel as abi3.
|
|
extension_kwargs["py_limited_api"] = True
|
|
setup_options = {"bdist_wheel": {"py_limited_api": "cp310"}}
|
|
"""
|
|
),
|
|
spam_c_function_add=textwrap.dedent(
|
|
r"""
|
|
// Call a function not in the stable ABI until Python 3.13.
|
|
// Without Py_LIMITED_API defined, the compiler allows it.
|
|
PyObject *str_obj = PyUnicode_FromString(content);
|
|
const char *utf8 = PyUnicode_AsUTF8(str_obj);
|
|
(void)utf8;
|
|
Py_DECREF(str_obj);
|
|
"""
|
|
),
|
|
setup_py_extension_args_add="**extension_kwargs",
|
|
setup_py_setup_args_add="options=setup_options",
|
|
)
|
|
|
|
violating_abi3_project.files["pyproject.toml"] = pyproject_toml
|
|
|
|
|
|
@utils.skip_if_pyodide("abi3audit is disabled on Pyodide (wasm shared objects are not supported)")
|
|
def test_abi3audit_runs_on_abi3_wheel(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None:
|
|
"""Test that abi3audit runs automatically on abi3 wheels."""
|
|
project_dir = tmp_path / "project"
|
|
limited_api_project.generate(project_dir)
|
|
|
|
actual_wheels = utils.cibuildwheel_run(
|
|
project_dir,
|
|
add_env={
|
|
# Let's only build one cpython version to keep the test fast.
|
|
"CIBW_BUILD": "cp310-*",
|
|
"CIBW_ARCHS": "native",
|
|
},
|
|
)
|
|
|
|
assert len(actual_wheels) >= 1
|
|
|
|
captured = capfd.readouterr()
|
|
assert "Running audit command: abi3audit" in captured.out
|
|
|
|
|
|
def test_abi3audit_skipped_for_non_abi3_wheel(
|
|
tmp_path: Path, capfd: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
"""Test that abi3audit does not run for non-abi3 wheels."""
|
|
project_dir = tmp_path / "project"
|
|
basic_project = test_projects.new_c_project()
|
|
basic_project.generate(project_dir)
|
|
|
|
actual_wheels = utils.cibuildwheel_run(
|
|
project_dir,
|
|
add_env={
|
|
"CIBW_ARCHS": "native",
|
|
},
|
|
single_python=True,
|
|
)
|
|
|
|
assert len(actual_wheels) >= 1
|
|
|
|
captured = capfd.readouterr()
|
|
assert "Running audit command: abi3audit" not in captured.out
|
|
|
|
|
|
@utils.skip_if_pyodide("abi3audit is disabled on Pyodide (wasm shared objects are not supported)")
|
|
def test_abi3audit_detects_violation(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None:
|
|
"""Test that abi3audit catches stable ABI violations and fails the build.
|
|
|
|
This project tags the wheel as cp310-abi3 but uses PyUnicode_AsUTF8,
|
|
which was not part of the stable ABI until Python 3.13.
|
|
"""
|
|
project_dir = tmp_path / "project"
|
|
violating_abi3_project.generate(project_dir)
|
|
|
|
with pytest.raises(subprocess.CalledProcessError):
|
|
utils.cibuildwheel_run(
|
|
project_dir,
|
|
add_env={
|
|
"CIBW_BUILD": "cp310-*",
|
|
"CIBW_ARCHS": "native",
|
|
},
|
|
)
|
|
|
|
captured = capfd.readouterr()
|
|
assert "Running audit command: abi3audit" in captured.out
|
|
|
|
|
|
def test_custom_audit_command(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None:
|
|
project_dir = tmp_path / "project"
|
|
test_projects.new_c_project().generate(project_dir)
|
|
|
|
actual_wheels = utils.cibuildwheel_run(
|
|
project_dir,
|
|
add_env={
|
|
"CIBW_AUDIT_COMMAND": "echo custom-audit-ok {wheel}",
|
|
"CIBW_AUDIT_REQUIRES": "",
|
|
"CIBW_ARCHS": "native",
|
|
},
|
|
single_python=True,
|
|
)
|
|
|
|
assert len(actual_wheels) >= 1
|
|
captured = capfd.readouterr()
|
|
assert "Auditing wheel" in captured.out
|
|
assert "custom-audit-ok" in captured.out
|
|
|
|
|
|
def test_custom_audit_requires(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None:
|
|
project_dir = tmp_path / "project"
|
|
test_projects.new_c_project().generate(project_dir)
|
|
|
|
actual_wheels = utils.cibuildwheel_run(
|
|
project_dir,
|
|
add_env={
|
|
"CIBW_AUDIT_REQUIRES": "pycowsay",
|
|
"CIBW_AUDIT_COMMAND": "pycowsay moo {wheel}",
|
|
"CIBW_ARCHS": "native",
|
|
},
|
|
single_python=True,
|
|
)
|
|
|
|
assert len(actual_wheels) >= 1
|
|
captured = capfd.readouterr()
|
|
assert "Installing audit dependencies: pycowsay" in captured.out
|
|
assert "moo" in captured.out
|
|
|
|
|
|
def test_empty_audit_command_disables_audit(
|
|
tmp_path: Path, capfd: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
project_dir = tmp_path / "project"
|
|
test_projects.new_c_project().generate(project_dir)
|
|
|
|
actual_wheels = utils.cibuildwheel_run(
|
|
project_dir,
|
|
add_env={
|
|
"CIBW_AUDIT_COMMAND": "",
|
|
"CIBW_ARCHS": "native",
|
|
},
|
|
single_python=True,
|
|
)
|
|
|
|
assert len(actual_wheels) >= 1
|
|
captured = capfd.readouterr()
|
|
assert "Auditing wheel" not in captured.out
|
|
|
|
|
|
def test_custom_audit_command_failure(tmp_path: Path) -> None:
|
|
project_dir = tmp_path / "project"
|
|
test_projects.new_c_project().generate(project_dir)
|
|
|
|
with pytest.raises(subprocess.CalledProcessError):
|
|
utils.cibuildwheel_run(
|
|
project_dir,
|
|
add_env={
|
|
"CIBW_AUDIT_COMMAND": 'python -c "import sys; sys.exit(1)" {wheel}',
|
|
"CIBW_AUDIT_REQUIRES": "",
|
|
"CIBW_ARCHS": "native",
|
|
},
|
|
single_python=True,
|
|
)
|