# Copyright Kevin Deldycke <[email protected]> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
"""Tests for `repomatic.release.binary`: the build roster and its asset names."""
from __future__ import annotations
import json
from string import ascii_lowercase, digits
import pytest
from extra_platforms import (
AARCH64,
ALL_ARCHITECTURES,
ALL_IDS,
FREEBSD,
LINUX,
MACOS,
UBUNTU,
UNKNOWN_PLATFORM,
WINDOWS,
X86_64,
)
from packaging.version import Version
from repomatic.metadata.core import Metadata
from repomatic.release.binary import (
BINARY_ASSET_SUFFIXES,
NUITKA_BUILD_TARGETS,
NUITKA_DISABLE_CCACHE_FLAG,
BinaryFormat,
BuildTarget,
current_build_target,
pack_binary_assets,
)
from tests.conftest import (
EM_X86_64,
make_elf,
make_fat,
make_macho,
make_pe,
metadata_from_pyproject,
)
[docs]
@pytest.mark.parametrize(
"target",
[
"linux-arm64",
"linux-x64",
"macos-arm64",
"macos-x64",
"windows-arm64",
"windows-x64",
],
)
def test_all_targets_present(target):
"""All expected targets are in the mapping."""
assert target in NUITKA_BUILD_TARGETS
[docs]
def test_pack_binary_assets(tmp_path):
"""Aliases are byte-identical copies; Python distributions are skipped."""
(tmp_path / "repomatic-1.2.3-linux-arm64.bin").write_bytes(b"elf-bytes")
(tmp_path / "repomatic-1.2.3-windows-x64.exe").write_bytes(b"pe-bytes")
(tmp_path / "repomatic-manpages.attestation.json").write_bytes(b"{}")
(tmp_path / "repomatic-1.2.3.tar.gz").write_bytes(b"sdist")
(tmp_path / "repomatic-1.2.3-py3-none-any.whl").write_bytes(b"wheel")
uploads = pack_binary_assets(tmp_path, "1.2.3")
assert [path.name for path in uploads] == [
"repomatic-1.2.3-linux-arm64.bin",
"repomatic-1.2.3-windows-x64.exe",
"repomatic-linux-arm64.bin",
"repomatic-manpages.attestation.json",
"repomatic-windows-x64.exe",
]
assert (tmp_path / "repomatic-linux-arm64.bin").read_bytes() == b"elf-bytes"
assert (tmp_path / "repomatic-windows-x64.exe").read_bytes() == b"pe-bytes"
# Idempotent: a second run returns the same list with the same bytes.
assert pack_binary_assets(tmp_path, "1.2.3") == uploads
ASSET_ARCH_LABELS = {AARCH64: "arm64", X86_64: "x64"}
"""Short architecture spellings frozen into the published release-asset names.
`BuildTarget.arch` carries the canonical extra-platforms id (`aarch64`,
`x86_64`), while every published download URL says `arm64` or `x64`. This maps
one onto the other, so a target key and its fields cannot drift apart.
"""
[docs]
@pytest.mark.parametrize(
("target_id", "build_target"), sorted(NUITKA_BUILD_TARGETS.items())
)
def test_nuitka_targets(target_id: str, build_target: BuildTarget) -> None:
assert isinstance(target_id, str)
assert build_target.id == target_id
assert set(build_target.runner).issubset(ascii_lowercase + digits + "-.")
assert build_target.platform.id in ALL_IDS
assert build_target.arch in ALL_ARCHITECTURES
# Keyed on the platform, not on the format the extension is derived from,
# so a wrong `BinaryFormat.extension` cannot satisfy both sides.
assert build_target.extension == (
"exe" if build_target.platform is WINDOWS else "bin"
)
assert f".{build_target.extension}" in BINARY_ASSET_SUFFIXES
# Every target declares a floor; only Linux compiles in a container, and
# what the version counts differs by format (a glibc symbol version below
# 3, an OS release above 10).
assert Version(build_target.floor) >= Version(
"2" if build_target.binary_format is BinaryFormat.ELF else "10"
)
if build_target.platform is LINUX:
assert build_target.container is not None
assert build_target.container.startswith("quay.io/pypa/manylinux_2_28_")
assert "@sha256:" in build_target.container
else:
assert build_target.container is None
# The key is the published asset identifier, frozen on the short
# architecture spelling rather than the canonical id the field carries.
label = ASSET_ARCH_LABELS[build_target.arch]
assert target_id == f"{build_target.platform.id}-{label}"
assert set(target_id).issubset(ascii_lowercase + digits + "-")
[docs]
@pytest.mark.parametrize(
("target_id", "build_target"), sorted(NUITKA_BUILD_TARGETS.items())
)
def test_matrix_entry_is_json_safe(target_id: str, build_target: BuildTarget) -> None:
"""No extra-platforms trait may leak into the GitHub matrix.
`build_targets` and `nuitka_matrix` are serialized into the metadata JSON
the release workflow reads, so an `Architecture` object reaching one of
those dicts breaks the whole release lane at metadata time.
"""
entry = build_target.as_matrix_entry()
assert json.loads(json.dumps(entry)) == entry
for key, value in entry.items():
assert isinstance(key, str)
assert isinstance(value, str)
assert entry["target"] == target_id
assert entry["platform_id"] == build_target.platform.id
assert entry["arch"] == build_target.arch.id
assert entry["os"] == build_target.runner
assert entry["floor"] == build_target.floor
expected_keys = {"target", "os", "platform_id", "arch", "extension", "floor"}
if build_target.platform is LINUX:
expected_keys.add("container")
assert set(entry) == expected_keys, f"Unexpected matrix keys for {target_id}"
[docs]
@pytest.mark.parametrize(
("target_id", "build_target"), sorted(NUITKA_BUILD_TARGETS.items())
)
def test_macos_compiles_without_ccache_at_its_floor(
target_id: str, build_target: BuildTarget
) -> None:
"""macOS turns ccache off, keeps no cache, and pins its deployment target.
Every other target adds no option and no variable, and keeps its cache.
"""
if build_target.platform is MACOS:
assert build_target.nuitka_flags == (NUITKA_DISABLE_CCACHE_FLAG,)
assert build_target.compile_cache is False
assert build_target.build_env == {
"MACOSX_DEPLOYMENT_TARGET": build_target.floor
}
else:
assert build_target.nuitka_flags == ()
assert build_target.compile_cache is True
assert build_target.build_env == {}
[docs]
@pytest.mark.parametrize(
("platform", "architecture", "expected"),
(
pytest.param(MACOS, AARCH64, "macos-arm64", id="macos-arm"),
pytest.param(MACOS, X86_64, "macos-x64", id="macos-intel"),
pytest.param(WINDOWS, X86_64, "windows-x64", id="windows"),
pytest.param(UBUNTU, AARCH64, "linux-arm64", id="linux-distribution"),
pytest.param(UNKNOWN_PLATFORM, X86_64, "linux-x64", id="unnamed-linux"),
pytest.param(FREEBSD, X86_64, None, id="no-target"),
),
)
def test_current_build_target(monkeypatch, platform, architecture, expected):
"""The machine's platform and architecture pick its target."""
monkeypatch.setattr("repomatic.release.binary.current_platform", lambda: platform)
monkeypatch.setattr(
"repomatic.release.binary.current_architecture", lambda: architecture
)
target = current_build_target()
assert (target.id if target else None) == expected
[docs]
def test_nuitka_enabled_default():
"""Test that nuitka.enabled config defaults to True."""
metadata = Metadata()
assert metadata.config.nuitka_enabled is True
[docs]
def test_nuitka_disabled_skips_matrix(tmp_path, monkeypatch):
"""`nuitka_matrix` is `None` when nuitka is disabled in `pyproject.toml`."""
pyproject_content = """\
[project]
name = "test-project"
version = "1.0.0"
[project.scripts]
my-cli = "my_package.__main__:main"
[tool.repomatic]
nuitka.enabled = false
"""
metadata = metadata_from_pyproject(tmp_path, monkeypatch, pyproject_content)
assert metadata.config.nuitka_enabled is False
assert metadata.nuitka_matrix is None