112 lines
5.5 KiB
Python
112 lines
5.5 KiB
Python
"""Negative build-input tests; no package installation or target imports."""
|
|
import base64
|
|
import hashlib
|
|
from pathlib import Path
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "packaging"))
|
|
from build_profiles import target_profile, validate_vendor
|
|
|
|
|
|
class VendorValidationTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.tmp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.tmp.cleanup)
|
|
self.root = Path(self.tmp.name)
|
|
self.profile = target_profile("mint22-amd64-py312")
|
|
self.make_package("cffi", "_cffi_backend.cpython-312-x86_64-linux-gnu.so")
|
|
self.make_package("pydantic_core", "pydantic_core/_pydantic_core.cpython-312-x86_64-linux-gnu.so")
|
|
self.make_package("rpds_py", "rpds/rpds.cpython-312-x86_64-linux-gnu.so")
|
|
|
|
def make_package(self, name, relative, tag="cp312-cp312-manylinux_2_28_x86_64", abi_extra=b""):
|
|
native = self.root / relative
|
|
native.parent.mkdir(parents=True, exist_ok=True)
|
|
native.write_bytes(b"\x7fELF\x02\x01" + b"\0" * 12 + (62).to_bytes(2, "little") + abi_extra)
|
|
info = self.root / (name + "-1.0.dist-info")
|
|
info.mkdir(exist_ok=True)
|
|
(info / "METADATA").write_text("Name: " + name + "\nVersion: 1.0\nRequires-Python: >=3.10\n")
|
|
(info / "WHEEL").write_text("Wheel-Version: 1.0\nRoot-Is-Purelib: false\nTag: " + tag + "\n")
|
|
self.write_record(info, native)
|
|
return info, native
|
|
|
|
def write_record(self, info, native):
|
|
rows = []
|
|
for path in (native, info / "METADATA", info / "WHEEL"):
|
|
digest = base64.urlsafe_b64encode(hashlib.sha256(path.read_bytes()).digest()).decode().rstrip("=")
|
|
rows.append(path.relative_to(self.root).as_posix() + ",sha256=" + digest + "," + str(path.stat().st_size))
|
|
rows.append((info / "RECORD").relative_to(self.root).as_posix() + ",,")
|
|
(info / "RECORD").write_text("\n".join(rows) + "\n")
|
|
|
|
def test_valid_target_and_abi3(self):
|
|
self.make_package("cryptography", "cryptography/_rust.abi3.so", "cp311-abi3-manylinux_2_34_x86_64")
|
|
result = validate_vendor(self.root, self.profile)
|
|
self.assertEqual(len(result["native_extensions"]), 4)
|
|
self.assertEqual(self.profile["validation_status"], "candidate")
|
|
|
|
def test_wrong_wheel_abi_rejected(self):
|
|
self.make_package("cffi", "_cffi_backend.cpython-312-x86_64-linux-gnu.so", "cp313-cp313-manylinux_2_28_x86_64")
|
|
with self.assertRaisesRegex(RuntimeError, "wheel ABI"):
|
|
validate_vendor(self.root, self.profile)
|
|
|
|
def test_wrong_extension_abi_rejected(self):
|
|
old = self.root / "_cffi_backend.cpython-312-x86_64-linux-gnu.so"
|
|
old.unlink()
|
|
self.make_package("cffi", "_cffi_backend.cpython-313-x86_64-linux-gnu.so")
|
|
with self.assertRaisesRegex(RuntimeError, "extension ABI"):
|
|
validate_vendor(self.root, self.profile)
|
|
|
|
def test_wrong_elf_cpu_rejected(self):
|
|
info, path = self.make_package("cffi", "_cffi_backend.cpython-312-x86_64-linux-gnu.so")
|
|
data = bytearray(path.read_bytes()); data[18:20] = (183).to_bytes(2, "little")
|
|
path.write_bytes(data); self.write_record(info, path)
|
|
with self.assertRaisesRegex(RuntimeError, "ELF amd64"):
|
|
validate_vendor(self.root, self.profile)
|
|
|
|
def test_new_glibc_rejected(self):
|
|
self.make_package("cffi", "_cffi_backend.cpython-312-x86_64-linux-gnu.so", abi_extra=b"GLIBC_2.35")
|
|
with self.assertRaisesRegex(RuntimeError, "glibc 2.34"):
|
|
validate_vendor(self.root, self.profile)
|
|
|
|
def test_requires_python_rejected(self):
|
|
info, path = self.make_package("cffi", "_cffi_backend.cpython-312-x86_64-linux-gnu.so")
|
|
with (info / "METADATA").open("a") as stream: stream.write("Requires-Python: >=3.13\n")
|
|
# Replace the original field to avoid duplicate-header ambiguity.
|
|
(info / "METADATA").write_text("Name: cffi\nVersion: 1.0\nRequires-Python: >=3.13\n")
|
|
self.write_record(info, path)
|
|
with self.assertRaisesRegex(RuntimeError, "Requires-Python"):
|
|
validate_vendor(self.root, self.profile)
|
|
|
|
def test_version_set_change_rejected(self):
|
|
with self.assertRaisesRegex(RuntimeError, "names/versions"):
|
|
validate_vendor(self.root, self.profile, {"cffi": "9.9"})
|
|
|
|
def test_modified_native_record_rejected(self):
|
|
with (self.root / "_cffi_backend.cpython-312-x86_64-linux-gnu.so").open("ab") as stream:
|
|
stream.write(b"altered")
|
|
with self.assertRaisesRegex(RuntimeError, "RECORD digest"):
|
|
validate_vendor(self.root, self.profile)
|
|
|
|
def test_missing_wheel_rejected(self):
|
|
(self.root / "cffi-1.0.dist-info/WHEEL").unlink()
|
|
with self.assertRaisesRegex(RuntimeError, "WHEEL/RECORD"):
|
|
validate_vendor(self.root, self.profile)
|
|
|
|
def test_native_record_requires_digest(self):
|
|
record = self.root / "cffi-1.0.dist-info/RECORD"
|
|
rows = record.read_text().splitlines()
|
|
rows[0] = rows[0].split(",")[0] + ",,"
|
|
record.write_text("\n".join(rows) + "\n")
|
|
with self.assertRaisesRegex(RuntimeError, "RECORD digest missing"):
|
|
validate_vendor(self.root, self.profile)
|
|
|
|
def test_pure_metadata_on_native_rejected(self):
|
|
self.make_package("cffi", "_cffi_backend.cpython-312-x86_64-linux-gnu.so", "py3-none-any")
|
|
with self.assertRaisesRegex(RuntimeError, "native WHEEL"):
|
|
validate_vendor(self.root, self.profile)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|