git mirror - github.com/owenewans/holy - branch master
clone: https://src.holypkg.eu/holy/

file tests/versions.py

#!/usr/bin/env python3
import hashlib
import pathlib
import subprocess
import sys
import tempfile

binary = str(pathlib.Path(sys.argv[1]).resolve())
with tempfile.TemporaryDirectory(prefix="holy-versions-") as scratch:
    tmp = pathlib.Path(scratch)

    def run(*args, status=0):
        result = subprocess.run([binary, *map(str, args)], capture_output=True, text=True)
        assert result.returncode == status, (args, result.returncode, result.stdout, result.stderr)
        return result.stdout

    def package(label, name, version, deps="", family="pacman", arch="noarch", libc="nolibc", provides=""):
        tree = tmp / label
        (tree / "HOLY").mkdir(parents=True)
        (tree / "DATA").mkdir()
        (tree / "DATA" / name).write_text(version)
        metadata = "format holy-package-1\nname " + name + "\nversion " + version
        metadata += "\nrelease 1\nos linux\narch " + arch + "\nlibc " + libc + "\n"
        if family:
            metadata += "x-version-family " + family + "\n"
        (tree / "HOLY/meta").write_text(metadata)
        for field in ("deps", "provides", "hooks", "origin", "transform"):
            (tree / "HOLY" / field).write_text(deps if field == "deps" else provides if field == "provides" else "")
        run("manifest", "generate", tree, "--output", tmp / (label + ".files"))
        (tmp / (label + ".files")).rename(tree / "HOLY/files")
        artifact = tmp / (label + ".holy")
        run("pack", tree, "--output", artifact)
        return artifact, hashlib.sha256(artifact.read_bytes()).hexdigest()

    def dependency(relation, version, arch="any", libc="any"):
        return "require dep app package library " + arch + " " + libc + " " + relation + " " + version + " original fixture\n"

    old, old_hash = package("old", "library", "1:2.0-1")
    new, new_hash = package("new", "library", "1:2.0-3")
    foreign, _ = package("foreign", "library", "999:99", family="rpm")
    unknown, _ = package("unknown", "library", "999:99-99", family=None)
    wrong_arch, _ = package("wrong-arch", "library", "1:2.0-3", arch="x86")
    for relation, version, expected in (
        ("ge", "1:2.0-2", new_hash), ("gt", "1:2.0-1", new_hash),
        ("le", "1:2.0-2", old_hash), ("lt", "1:2.0-3", old_hash),
        ("eq", "1:2.0-3", new_hash),
    ):
        app, _ = package("app-" + relation, "app", "1-1", dependency(relation, version))
        text = run("solve", "local:" + str(app), "local:" + str(old), "local:" + str(new),
                   "local:" + str(foreign), "local:" + str(unknown))
        assert "selected " + expected in text
        assert "selected " + (old_hash if expected == new_hash else new_hash) not in text
        rejected = old_hash if expected == new_hash else new_hash
        run("solve", "local:" + str(app), "local:" + str(old), "local:" + str(new),
            "--choose", "dep=" + rejected, status=3)

    app, app_hash = package("app", "app", "1-1", dependency("ge", "1:2.0-2", "noarch", "nolibc"))
    run("solve", "local:" + str(app), "local:" + str(old), status=4)
    run("solve", "local:" + str(app), "local:" + str(foreign), status=4)
    run("solve", "local:" + str(app), "local:" + str(unknown), status=4)
    run("solve", "local:" + str(app), "local:" + str(wrong_arch), status=4)
    wrong_libc, _ = package("wrong-libc", "library", "1:2.0-3", arch="x86_64", libc="glibc")
    scope, _ = package("scope", "app", "1-1", dependency("any", "-", "any", "nolibc"))
    run("solve", "local:" + str(scope), "local:" + str(wrong_libc), status=4)
    run("solve", "local:" + str(scope), "local:" + str(new))
    for family in (None, "future"):
        unsupported, _ = package("unsupported-" + str(family), "app", "1-1", dependency("ge", "1"), family)
        run("solve", "local:" + str(unsupported), "local:" + str(new), status=6)
    release, _ = package("no-release", "app", "1-1", dependency("eq", "1:2.0"))
    run("solve", "local:" + str(release), "local:" + str(old), "local:" + str(new), status=3)
    run("solve", "local:" + str(release), "local:" + str(old), "local:" + str(new),
        "--choose", "dep=" + new_hash)
    root = tmp / "root"
    root.mkdir()
    run("db", "init", "--root", root)
    for artifact in (old, new, app):
        run("cache", "stage", "local:" + str(artifact), "--root", root)
    plan = run("db", "plan-set", app_hash, old_hash, new_hash, "--root", root).split(" sha256 ")[1].split()[0]
    run("db", "apply-set", plan, app_hash, old_hash, new_hash, "--root", root)
    run("db", "check", "--all", "--root", root)
    assert (root / "library").read_text() == "1:2.0-3"
    second, second_hash = package("second", "second", "1-1", dependency("eq", "1:2.0").replace(" app package", " second package"))
    run("cache", "stage", "local:" + str(second), "--root", root)
    reuse = run("db", "plan-set", second_hash, "--root", root).split(" sha256 ")[1].split()[0]
    run("db", "apply-set", reuse, second_hash, "--root", root)
    run("db", "check", "--all", "--root", root)
    run("db", "plan-update", new_hash, old_hash, "--root", root, status=4)
    assert (root / "library").read_text() == "1:2.0-3"
    run("db", "rm", new_hash, "--root", root, status=3)
    run("db", "rm", second_hash, "--root", root)
    run("db", "rm", app_hash, "--root", root)
    run("db", "rm", new_hash, "--root", root)
    claim = "provide package library any any 2:1.0-3 fixture\n"
    alias, alias_hash = package("alias", "implementation", "0.1-1", provides=claim)
    lower, lower_hash = package("lower-alias", "other-implementation", "999-1",
        provides="provide package library any any 2:1.0-1 fixture\n")
    unversioned, _ = package("unversioned-alias", "unversioned", "999-1",
        provides="provide package library any any - fixture\n")
    alias_app, alias_app_hash = package("alias-app", "app", "1-1", dependency("ge", "2:1.0-2"))
    selected = run("solve", "local:" + str(alias_app), "local:" + str(alias), "local:" + str(lower), "local:" + str(unversioned))
    assert "selected " + alias_hash in selected and "selected " + lower_hash not in selected
    run("solve", "local:" + str(alias_app), "local:" + str(unversioned), status=4)
    mismatch, _ = package("scope-lie", "scope-lie", "1-1",
        provides="provide package library x86 musl 2:1.0-3 fixture\n")
    run("solve", "local:" + str(alias_app), "local:" + str(mismatch), status=6)
    assert "name implementation\n" in run("info", "local:" + str(alias))
    alternate, alternate_hash = package("alternate", "alternate", "5-1", provides=claim)
    run("solve", "local:" + str(alias_app), "local:" + str(alias), "local:" + str(alternate), status=3)
    chosen = run("solve", "local:" + str(alias_app), "local:" + str(alias), "local:" + str(alternate),
        "--choose", "dep=" + alternate_hash)
    assert "selected " + alternate_hash in chosen and "selected " + alias_hash not in chosen
    scoped_alias, _ = package("scoped-alias", "implementation", "1-1", arch="x86", provides=claim)
    scoped_app, _ = package("scoped-alias-app", "app", "1-1", dependency("ge", "2:1.0-2", "noarch", "nolibc"))
    run("solve", "local:" + str(scoped_app), "local:" + str(scoped_alias), status=4)
    alien_alias, _ = package("alien-alias", "implementation", "1", family="rpm", provides=claim)
    run("solve", "local:" + str(alias_app), "local:" + str(alien_alias), status=4)
    alias_root = tmp / "alias-root"
    alias_root.mkdir()
    run("db", "init", "--root", alias_root)
    for artifact in (alias, alias_app):
        run("cache", "stage", "local:" + str(artifact), "--root", alias_root)
    plan = run("db", "plan-set", alias_hash, "--root", alias_root).split(" sha256 ")[1].split()[0]
    run("db", "apply-set", plan, alias_hash, "--root", alias_root)
    record = alias_root / "var/lib/holypkg/installed" / alias_hash
    assert (record / "provides").read_text() == claim
    assert "provides " + hashlib.sha256(claim.encode()).hexdigest() in (record / "state").read_text()
    plan = run("db", "plan-set", alias_app_hash, "--root", alias_root).split(" sha256 ")[1].split()[0]
    run("db", "apply-set", plan, alias_app_hash, "--root", alias_root)
    graph = (alias_root / "var/lib/holypkg/installed" / alias_app_hash / "graph").read_text()
    assert '"package" "library"' in graph and alias_hash in graph
    cache = alias_root / "var/cache/holypkg/objects/sha256" / (alias_hash + ".holy")
    cache.unlink()
    run("db", "check", "--all", "--root", alias_root)
    for corrupted in (claim + "bad\n", ""):
        (record / "provides").write_text(corrupted)
        run("db", "status", "--root", alias_root, status=1)
    (record / "provides").write_text(claim)
    (record / "provides").chmod(0o666)
    run("db", "status", "--root", alias_root, status=1)
    (record / "provides").chmod(0o600)
    (record / "provides").unlink()
    run("db", "status", "--root", alias_root, status=1)
    (record / "provides").symlink_to(alias)
    run("db", "status", "--root", alias_root, status=1)
    (record / "provides").unlink()
    (record / "provides").write_text(claim)
    run("cache", "stage", "local:" + str(alias), "--root", alias_root)
    replacement, replacement_hash = package("drop-claim", "implementation", "0.2-1")
    run("cache", "stage", "local:" + str(replacement), "--root", alias_root)
    run("db", "plan-update", alias_hash, replacement_hash, "--root", alias_root, status=4)
    run("db", "rm", alias_app_hash, "--root", alias_root)
    state = (record / "state").read_text()
    legacy = state.replace("holy-instance-4", "holy-instance-2")
    legacy = "\n".join(line for line in legacy.splitlines() if not line.startswith(("provides ", "source-record "))) + "\n"
    (record / "state").write_text(legacy)
    (record / "provides").unlink()
    run("db", "status", "--root", alias_root)
    cache.unlink()
    run("db", "plan-set", alias_app_hash, "--root", alias_root, status=6)
    run("cache", "stage", "local:" + str(alias), "--root", alias_root)
    legacy_plan = run("db", "plan-set", alias_app_hash, "--root", alias_root).split(" sha256 ")[1].split()[0]
    run("db", "apply-set", legacy_plan, alias_app_hash, "--root", alias_root)
    run("db", "check", "--all", "--root", alias_root)
    upgraded, upgraded_hash = package("keep-claim", "implementation", "0.3-1", provides=claim)
    run("cache", "stage", "local:" + str(upgraded), "--root", alias_root)
    update = run("db", "plan-update", alias_hash, upgraded_hash, "--root", alias_root).split(" sha256 ")[1].split()[0]
    run("db", "apply-update", update, alias_hash, upgraded_hash, "--root", alias_root)
    run("db", "check", "--all", "--root", alias_root)
    updated_record = record.parent / upgraded_hash
    assert (updated_record / "provides").read_text() == claim
    assert "format holy-instance-4\n" in (updated_record / "state").read_text()
    graph = (record.parent / alias_app_hash / "graph").read_text()
    assert upgraded_hash in graph and alias_hash not in graph
    run("db", "rm", alias_app_hash, "--root", alias_root)
    run("db", "rm", upgraded_hash, "--root", alias_root)
    deb_old, _ = package("deb-old", "deblib", "1.0~rc1", family="deb")
    deb_new, deb_new_hash = package("deb-new", "deblib", "1.0", family="deb")
    deb_wrong, _ = package("deb-wrong-family", "deblib", "99.0", family="pacman")
    deb_app, _ = package("deb-app", "app", "1", dependency("ge", "1.0").replace("library", "deblib"), family="deb")
    run("solve", "local:" + str(deb_app), "local:" + str(deb_old), status=4)
    run("solve", "local:" + str(deb_app), "local:" + str(deb_wrong), status=4)
    assert "selected " + deb_new_hash in run("solve", "local:" + str(deb_app),
        "local:" + str(deb_old), "local:" + str(deb_new), "local:" + str(deb_wrong))
    native_old, _ = package("native-old", "nativelib", "1.9", family="holy")
    native_pre, native_pre_hash = package("native-pre", "nativelib", "1.10-rc2", family="holy")
    native_new, native_new_hash = package("native-new", "nativelib", "1.10", family="holy")
    native_wrong, _ = package("native-wrong", "nativelib", "99", family="pacman")
    native_app, _ = package("native-app", "app", "1", dependency("ge", "1.10").replace("library", "nativelib"), family="holy")
    run("solve", "local:" + str(native_app), "local:" + str(native_old), "local:" + str(native_pre), status=4)
    run("solve", "local:" + str(native_app), "local:" + str(native_wrong), status=4)
    assert "selected " + native_new_hash in run("solve", "local:" + str(native_app),
        "local:" + str(native_old), "local:" + str(native_pre), "local:" + str(native_new),
        "local:" + str(native_wrong))
    native_pre_app, _ = package("native-pre-app", "app", "1", dependency("lt", "1.10").replace("library", "nativelib"), family="holy")
    assert "selected " + native_pre_hash in run("solve", "local:" + str(native_pre_app),
        "local:" + str(native_pre))
    invalid_native = tmp / "invalid-native"
    (invalid_native / "HOLY").mkdir(parents=True)
    (invalid_native / "DATA").mkdir()
    (invalid_native / "HOLY/meta").write_text(
        "format holy-package-1\nname invalid-native\nversion 1+build\nrelease 1\n"
        "os linux\narch noarch\nlibc nolibc\nx-version-family holy\n")
    for field in ("files", "deps", "provides", "hooks", "origin", "transform"):
        (invalid_native / "HOLY" / field).write_text("")
    run("pack", invalid_native, "--output", tmp / "invalid-native.holy", status=1)
    print("version constraints, virtual capabilities and transaction fixtures passed")