test: trim musl coverage to two invariants
Keep one PM invariant (a musl ELF userland resolves to linux-<arch>-musl and the default closure is satisfiable with musl-native artifacts) and one installer invariant (uv_bootstrap_target picks musl uv from a musl ELF interpreter even when ldd says glibc). Drop the lock-content and URL-shape change-detectors.
This commit is contained in:
@@ -1,213 +1,40 @@
|
||||
"""Regression contracts for native musl Linux PM targets (#123682)."""
|
||||
"""A native musl userland resolves PM to musl-native runtime artifacts (#123682)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sysconfig
|
||||
from argparse import Namespace
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from pm.lock import Lockfile
|
||||
from pm.registry import get_package
|
||||
from pm.store import ALL_TARGETS
|
||||
from pm.registry import all_packages, source_install_packages, walk
|
||||
|
||||
pytestmark = pytest.mark.platforms("linux")
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
_MUSL_TARGETS = ("linux-x64-musl", "linux-arm64-musl")
|
||||
|
||||
|
||||
def test_all_targets_include_native_musl_lanes():
|
||||
for target in _MUSL_TARGETS:
|
||||
assert target in ALL_TARGETS
|
||||
assert ALL_TARGETS.count(target) == 1
|
||||
|
||||
|
||||
def test_current_target_detects_musl_from_python_build_metadata(monkeypatch):
|
||||
def test_musl_userland_gets_a_satisfiable_native_musl_closure(monkeypatch, tmp_path):
|
||||
from pm import store
|
||||
|
||||
# /bin/sh's ELF interpreter is musl; the bootstrap Python (this glibc test
|
||||
# host's) says otherwise and must not win.
|
||||
fake_sh = tmp_path / "sh"
|
||||
fake_sh.write_bytes(b"\x7fELF" + b"\0" * 60 + b"/lib/ld-musl-x86_64.so.1\0")
|
||||
monkeypatch.setattr(store, "_native_linux_uses_musl",
|
||||
lambda: store._elf_loader_is_musl(fake_sh), raising=False)
|
||||
monkeypatch.setattr(store, "_native_machine", lambda: "x86_64")
|
||||
monkeypatch.setattr(store, "_is_bionic_libc", lambda: False)
|
||||
monkeypatch.setattr(store, "_native_linux_uses_musl", lambda: None)
|
||||
monkeypatch.setattr(
|
||||
sysconfig,
|
||||
"get_config_var",
|
||||
lambda key: "x86_64-alpine-linux-musl" if key == "HOST_GNU_TYPE" else None,
|
||||
)
|
||||
|
||||
assert store.current_target() == "linux-x64-musl"
|
||||
target = store.current_target()
|
||||
assert target == "linux-x64-musl"
|
||||
|
||||
|
||||
def test_elf_loader_distinguishes_native_musl_from_glibc(tmp_path):
|
||||
from pm import store
|
||||
|
||||
musl = tmp_path / "musl-bin"
|
||||
musl.write_bytes(b"\x7fELF" + b"\0" * 64 + b"/lib/ld-musl-x86_64.so.1\0")
|
||||
glibc = tmp_path / "glibc-bin"
|
||||
glibc.write_bytes(b"\x7fELF" + b"\0" * 64 + b"/lib64/ld-linux-x86-64.so.2\0")
|
||||
|
||||
assert store._elf_loader_is_musl(musl) is True
|
||||
assert store._elf_loader_is_musl(glibc) is False
|
||||
|
||||
|
||||
def test_musl_detection_falls_back_to_native_userland(monkeypatch):
|
||||
from pm import store
|
||||
|
||||
monkeypatch.setattr(sysconfig, "get_config_var", lambda _key: "x86_64-unknown-linux-gnu")
|
||||
monkeypatch.setattr(store, "_native_linux_uses_musl", lambda: True)
|
||||
|
||||
assert store._is_musl_libc() is True
|
||||
|
||||
|
||||
def test_native_glibc_overrides_foreign_musl_bootstrap(monkeypatch):
|
||||
from pm import store
|
||||
|
||||
monkeypatch.setattr(store, "_native_linux_uses_musl", lambda: False)
|
||||
monkeypatch.setattr(sysconfig, "get_config_var", lambda _key: "x86_64-unknown-linux-musl")
|
||||
|
||||
assert store._is_musl_libc() is False
|
||||
|
||||
|
||||
def test_bionic_remains_more_specific_than_musl(monkeypatch):
|
||||
from pm import store
|
||||
|
||||
monkeypatch.setattr(store, "_native_machine", lambda: "aarch64")
|
||||
monkeypatch.setattr(store, "_is_bionic_libc", lambda: True)
|
||||
monkeypatch.setattr(store, "_is_musl_libc", lambda: True)
|
||||
|
||||
assert store.current_target() == "linux-arm64-bionic"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("target", _MUSL_TARGETS)
|
||||
def test_required_runtime_has_native_musl_pins(target):
|
||||
"""Every runtime tool PM selects by default has an artifact for musl."""
|
||||
lock = Lockfile(REPO_ROOT / "pm" / "lock.json")
|
||||
|
||||
for name in ("python", "uv", "node", "npm", "ripgrep"):
|
||||
assert get_package(name).missing_reason(target) is None, name
|
||||
assert lock.artifacts(name, target), f"{name} has no {target} artifact"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("target", _MUSL_TARGETS)
|
||||
def test_interpreter_and_js_runtime_never_reuse_glibc_rows(target):
|
||||
lock = Lockfile(REPO_ROOT / "pm" / "lock.json")
|
||||
|
||||
closure = walk(source_install_packages(all_packages()))
|
||||
assert {"python", "uv", "node"} <= {package.name for package in closure}
|
||||
for package in closure:
|
||||
assert package.missing_reason(target) is None, package.name
|
||||
if lock.version(package.name):
|
||||
assert lock.artifacts(package.name, target), f"{package.name} has no {target} artifact"
|
||||
for name in ("python", "uv", "node"):
|
||||
row = lock.artifacts(name, target)[0]
|
||||
assert "musl" in row["url"], f"{name} {target} fell back to {row['url']}"
|
||||
assert get_package(name).fetch_url(lock.version(name), target) == row["url"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"target,base",
|
||||
[("linux-x64-musl", "linux-x64"), ("linux-arm64-musl", "linux-arm64")],
|
||||
)
|
||||
def test_static_linux_tools_reuse_the_reviewed_bytes(target, base):
|
||||
lock = Lockfile(REPO_ROOT / "pm" / "lock.json")
|
||||
|
||||
for name in ("ripgrep", "gh", "bws", "iron-proxy"):
|
||||
assert lock.artifacts(name, target) == lock.artifacts(name, base)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("target", _MUSL_TARGETS)
|
||||
def test_musl_default_closure_excludes_incompatible_ffmpeg(monkeypatch, target):
|
||||
from pm import store
|
||||
from pm.package import InstallError
|
||||
from pm.registry import source_install_packages, tool_roots
|
||||
|
||||
lock = Lockfile(REPO_ROOT / "pm" / "lock.json")
|
||||
monkeypatch.setattr(store, "current_target", lambda: target)
|
||||
assert get_package("ffmpeg").missing_reason(target)
|
||||
assert lock.artifacts("ffmpeg", target) == []
|
||||
assert "ffmpeg" not in source_install_packages(lock.names())
|
||||
assert "ffmpeg" not in tool_roots(lock.names())
|
||||
with pytest.raises(InstallError, match="unavailable on"):
|
||||
get_package("ffmpeg").fetch_url(lock.version("ffmpeg"), target)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("target", _MUSL_TARGETS)
|
||||
def test_optional_incompatible_tools_are_explicit_gaps(target):
|
||||
for name in ("git", "cua-driver", "agent-browser", "chromium"):
|
||||
assert get_package(name).missing_reason(target), name
|
||||
|
||||
|
||||
def test_portable_go_tools_resolve_the_generic_linux_archives():
|
||||
lock = Lockfile(REPO_ROOT / "pm" / "lock.json")
|
||||
|
||||
for name in ("gh", "iron-proxy"):
|
||||
package = get_package(name)
|
||||
for target, base in (("linux-x64-musl", "linux-x64"), ("linux-arm64-musl", "linux-arm64")):
|
||||
assert package.missing_reason(target) is None
|
||||
assert package.fetch_url(lock.version(name), target) == lock.artifacts(name, base)[0]["url"]
|
||||
|
||||
|
||||
def test_security_tools_only_claim_musl_where_upstream_has_an_asset():
|
||||
lock = Lockfile(REPO_ROOT / "pm" / "lock.json")
|
||||
|
||||
for target, base in (("linux-x64-musl", "linux-x64"), ("linux-arm64-musl", "linux-arm64")):
|
||||
assert lock.artifacts("bws", target) == lock.artifacts("bws", base)
|
||||
|
||||
tirith = get_package("tirith")
|
||||
assert tirith.missing_reason("linux-x64-musl")
|
||||
assert tirith.missing_reason("linux-arm64-musl") is None
|
||||
arm = lock.artifacts("tirith", "linux-arm64-musl")
|
||||
assert arm and "aarch64-unknown-linux-musl" in arm[0]["url"]
|
||||
|
||||
|
||||
def test_node_musl_rows_use_the_dedicated_build_channel():
|
||||
lock = Lockfile(REPO_ROOT / "pm" / "lock.json")
|
||||
|
||||
for target in _MUSL_TARGETS:
|
||||
assert lock.artifacts("node", target)[0]["url"].startswith(
|
||||
"https://unofficial-builds.nodejs.org/download/release/"
|
||||
)
|
||||
|
||||
|
||||
def test_lock_hashes_a_shared_target_url_once(monkeypatch):
|
||||
from pm import cli
|
||||
|
||||
shared = "https://example.invalid/static-linux.tar.gz"
|
||||
other = "https://example.invalid/darwin.tar.gz"
|
||||
|
||||
class Package:
|
||||
name = "portable"
|
||||
|
||||
def missing_reason(self, _target):
|
||||
return None
|
||||
|
||||
def fetch_urls(self, _version, target):
|
||||
return [other if target == "darwin-arm64" else shared]
|
||||
|
||||
def known_sha256(self, _version, _url):
|
||||
return None
|
||||
|
||||
class FakeLock:
|
||||
def __init__(self):
|
||||
self.pin = None
|
||||
self.saved = False
|
||||
|
||||
def set_pin(self, name, version, artifacts):
|
||||
self.pin = (name, version, artifacts)
|
||||
|
||||
def save(self):
|
||||
self.saved = True
|
||||
|
||||
lock = FakeLock()
|
||||
hashed = []
|
||||
monkeypatch.setattr(
|
||||
cli, "ALL_TARGETS",
|
||||
("linux-x64", "linux-x64-musl", "darwin-arm64"),
|
||||
)
|
||||
monkeypatch.setattr(cli, "get_package", lambda _name: Package())
|
||||
monkeypatch.setattr(cli, "_lockfile", lambda: lock)
|
||||
monkeypatch.setattr(
|
||||
cli, "hash_url",
|
||||
lambda url: hashed.append(url) or ("a" * 64 if url == shared else "b" * 64),
|
||||
)
|
||||
|
||||
assert cli._pin_tool(Namespace(name="portable", version="1.0")) == 0
|
||||
assert hashed.count(shared) == 1
|
||||
assert hashed.count(other) == 1
|
||||
assert lock.saved is True
|
||||
assert lock.pin[2]["linux-x64"] == lock.pin[2]["linux-x64-musl"]
|
||||
assert "musl" in lock.artifacts(name, target)[0]["url"], name
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""The standalone shell installer selects the lockfile's native uv pin."""
|
||||
"""install.sh bootstraps musl uv when the native userland is musl (#123682)."""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
@@ -11,44 +11,21 @@ pytestmark = pytest.mark.platforms("linux")
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"libc,loader_present,suffix",
|
||||
[
|
||||
("musl libc", False, "-musl"),
|
||||
("GNU libc", False, ""),
|
||||
("absent", True, "-musl"),
|
||||
("BusyBox", True, "-musl"),
|
||||
("GNU libc", True, ""),
|
||||
("absent", False, ""),
|
||||
],
|
||||
)
|
||||
def test_bootstrap_selects_native_uv_pin(libc, loader_present, suffix):
|
||||
machine = subprocess.check_output(["uname", "-m"], text=True).strip()
|
||||
arch = {"x86_64": "x64", "aarch64": "arm64"}[machine]
|
||||
target = f"linux-{arch}{suffix}"
|
||||
script = """
|
||||
source "$1" --manifest
|
||||
libc_output="$2"
|
||||
loader_present="$3"
|
||||
ldd() {
|
||||
[ "$libc_output" != absent ] || return 127
|
||||
printf '%s\\n' "$libc_output"
|
||||
}
|
||||
compgen() {
|
||||
if [ "$1" = -G ] && [ "$2" = '/lib/ld-musl-*.so.1' ]; then
|
||||
[ "$loader_present" = yes ]
|
||||
else
|
||||
builtin compgen "$@"
|
||||
fi
|
||||
}
|
||||
def test_musl_elf_interpreter_selects_musl_uv_even_when_ldd_says_glibc():
|
||||
# The ELF interpreter of /bin/sh decides, as in pm/store.py; a glibc ldd
|
||||
# (musl installed as a secondary toolchain, or a gcompat shim) must not.
|
||||
script = r"""
|
||||
source "$1" --manifest >/dev/null
|
||||
uname() { case "$1" in -m) echo x86_64 ;; -s) echo Linux ;; esac; }
|
||||
head() { printf '\177ELF\0\0\0/lib/ld-musl-x86_64.so.1\0'; }
|
||||
ldd() { echo "ldd (GNU libc) 2.39"; }
|
||||
target="$(uv_bootstrap_target)"
|
||||
uv_bootstrap_pin "$target"
|
||||
printf '%s\\n%s\\n%s\\n' "$target" "$UV_PIN_URL" "$UV_PIN_SHA256"
|
||||
printf '%s\n%s\n%s\n' "$target" "$UV_PIN_URL" "$UV_PIN_SHA256"
|
||||
"""
|
||||
result = subprocess.run(
|
||||
["bash", "-c", script, "_", str(ROOT / "scripts" / "install.sh"), libc,
|
||||
"yes" if loader_present else "no"],
|
||||
["bash", "-c", script, "_", str(ROOT / "scripts" / "install.sh")],
|
||||
check=True, capture_output=True, text=True,
|
||||
)
|
||||
row = json.loads((ROOT / "pm" / "lock.json").read_text())["packages"]["uv"]["artifacts"][target]
|
||||
assert result.stdout.splitlines() == [target, row["url"], row["sha256"]]
|
||||
row = json.loads((ROOT / "pm" / "lock.json").read_text())["packages"]["uv"]["artifacts"]["linux-x64-musl"]
|
||||
assert result.stdout.splitlines() == ["linux-x64-musl", row["url"], row["sha256"]]
|
||||
|
||||
@@ -67,15 +67,6 @@ def build_cli(data, out, tmp_path):
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("target", ["linux-x64-musl", "linux-arm64-musl"])
|
||||
def test_agent_inputs_accept_pm_musl_targets(tmp_path, target):
|
||||
from scripts.build.inputs import AgentInputs
|
||||
|
||||
out, data = inputs_fixture(tmp_path)
|
||||
data["target"] = target
|
||||
AgentInputs.from_dict(data).validate(out)
|
||||
|
||||
|
||||
@pytest.mark.platforms("posix")
|
||||
def test_contained_cli_assembly_runs_after_move_and_preserves_prepared_state(tmp_path):
|
||||
out, data = inputs_fixture(tmp_path)
|
||||
|
||||
Reference in New Issue
Block a user