_ _
| |_ ___| |_ _
| | . | | | |
|_|_|___|_|_ |
|___|
git mirror - github.com/owenewans/holy - branch master
file tests/scoop.py
#!/usr/bin/env python3
"""converts Scoop manifests into native packages and installs what they carry."""
import hashlib
import json
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
import zipfile
binary = str(Path(sys.argv[1]).resolve())
def call(*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 manifest(directory, name, entries, overrides=None):
"""a manifest beside the artifact it names, with the digest the manifest pins"""
archive = directory / f"{name}-fixture.zip"
with zipfile.ZipFile(archive, "w") as opened:
for entry, body in entries.items():
opened.writestr(entry, body)
body = {
"version": "2.4.1",
"description": "A Scoop conversion fixture",
"homepage": "https://example.org/fixture",
"license": "MIT",
"url": f"https://example.org/{archive.name}",
"hash": hashlib.sha256(archive.read_bytes()).hexdigest(),
"depends": "holyruntime/1.0 other/2.3",
"notes": ["reads its configuration from the working directory"],
"checkver": "https://example.org/fixture/version",
"autoupdate": {"url": "https://example.org/fixture-{{version}}.zip"},
"env_add_path": ["."],
"env_set": {"FIXTURE_HOME": "$dir"},
"persist": ["config"],
"installer": {"script": ["$dir\\fixture.exe", "Write-Host done"]},
}
if overrides:
body.update(overrides)
(directory / f"{name}.json").write_text(json.dumps(body, indent=2))
return archive
def main():
with tempfile.TemporaryDirectory() as scratch:
root = Path(scratch)
bucket = root / "bucket"
bucket.mkdir()
manifest(bucket, "holyscoop", {
"holyscoop-2.4.1/holyscoop.exe": "MZ a program for another operating system\n",
"holyscoop-2.4.1/README.txt": "holyscoop reads its configuration\n",
}, {"extract_dir": "holyscoop", "bin": "holyscoop.exe"})
# the artifact is verified against the digest the manifest pins, and carried
out = root / "converted"
report = call("import", bucket / "holyscoop.json", "--source", "bucket", "--format",
"scoop", "--output", out, status=3)
artifact = out / "holyscoop--noarch--nolibc.holy"
assert "imported holyscoop--noarch--nolibc.holy artifact" in report, report
assert artifact.is_file(), sorted(p.name for p in out.iterdir())
call("verify", "local:" + str(artifact))
call("info", "local:" + str(artifact))
identity = call("info", "local:" + str(artifact))
assert "name holyscoop\n" in identity and "version 2.4.1\n" in identity, identity
assert "arch noarch\n" in identity and "libc nolibc\n" in identity, identity
manifest_text = call("manifest", "local:" + str(artifact))
for path in ("usr/lib/holy/private/holyscoop/holyscoop/holyscoop.exe",
"usr/lib/holy/private/holyscoop/holyscoop/README.txt"):
assert path in manifest_text, path
# the manifest travels whole, so an installed copy can be compared with it
assert (out / "original").read_bytes() == (bucket / "holyscoop.json").read_bytes()
package = (out / "package").read_text()
assert "format holy-scoop-package-1\n" in package
assert "status review-required\n" in package
assert "artifact https://example.org/holyscoop-fixture.zip" in package
assert "verified against the digest the manifest pins" in package
assert "artifact-placed the payload carries 2 files" in package
assert "extract-dir holyscoop is the directory the manifest places" in package
assert "program holyscoop.exe is in the payload" in package
# nothing runs, and no runtime this manager does not provide is promised
assert "nothing runs the artifact" in package
assert "a package cannot promise a Wine" in package
assert "so no such requirement is recorded" in package
# the installer and the Windows integration are dropped with a count
assert "installer 1 PowerShell installer keys are dropped" in package
assert "integration 3 Windows integration keys are dropped" in package
assert "update 2 keys that check or rewrite the upstream version" in package
# a bucket dependency is a requirement, since a target has to find it
requirements = call("requirements", "local:" + str(artifact))
assert 'require "scoop-depend-0" "holyscoop" "package" "holyruntime"' in requirements
assert 'require "scoop-depend-1" "holyscoop" "package" "other"' in requirements
assert "depends 2 apps of the same bucket" in package
# the payload installs as ordinary files and nothing executes
target = root / "target"
(target / "usr/bin").mkdir(parents=True)
call("db", "init", "--root", target)
call("cache", "stage", "local:" + str(artifact), "--root", target)
# the two bucket apps have no provider here, so the set names what is missing
digest = hashlib.sha256(artifact.read_bytes()).hexdigest()
refused = subprocess.run([binary, "db", "plan-set", digest, "--root", str(target)],
capture_output=True, text=True)
assert refused.returncode == 4, refused
assert "unresolved requirement scoop-depend-0" in refused.stderr, refused.stderr
assert not (target / "usr/lib").exists()
# a manifest whose apps are absent is refused, and a fake provider is not
# invented to satisfy them
runtimes = root / "runtimes"
for name in ("holyruntime", "other"):
tree = runtimes / name
(tree / "HOLY").mkdir(parents=True)
(tree / "DATA/usr/lib" / name).mkdir(parents=True)
(tree / "DATA/usr/lib" / name / "note").write_text(f"{name} fixture\n")
(tree / "HOLY/meta").write_text(
"format holy-package-1\nname %s\nversion 1\nrelease 1\nos linux\n"
"arch noarch\nlibc nolibc\n" % name)
for field in ("deps", "provides", "hooks", "origin", "transform"):
(tree / "HOLY" / field).write_text("")
# the generator reads the tree it walks, so its output lands beside it
call("manifest", "generate", tree, "--output", root / (name + "-files"))
(tree / "HOLY/files").write_text((root / (name + "-files")).read_text())
call("pack", tree, "--output", root / (name + ".holy"))
call("cache", "stage", "local:" + str(root / (name + ".holy")), "--root", target)
digests = [digest]
for name in ("holyruntime", "other"):
digests.append(hashlib.sha256((root / (name + ".holy")).read_bytes()).hexdigest())
plan = call("db", "plan-set", *digests, "--root", target)
plan_sha = [line.split()[10] for line in plan.splitlines()
if line.startswith("plan-set ")][0]
applied = call("db", "apply-set", plan_sha, *digests, "--root", target)
assert "artifacts 3" in applied, applied
program = (target / "usr/lib/holy/private/holyscoop/holyscoop/holyscoop.exe")
assert program.read_text() == "MZ a program for another operating system\n"
assert (target / "usr/lib/holyruntime/note").read_text() == "holyruntime fixture\n"
# nothing in the payload is executable on this system, and no launcher exists
assert not (target / "usr/bin/holyscoop").exists()
assert not program.stat().st_mode & 0o111
# an artifact whose digest the manifest does not pin is refused
wrong = root / "wrong"
wrong.mkdir()
manifest(wrong, "holyscoopbad", {"bad/bad.exe": "MZ\n"})
body = json.loads((wrong / "holyscoopbad.json").read_text())
body["hash"] = "0" * 64
(wrong / "holyscoopbad.json").write_text(json.dumps(body))
result = subprocess.run([binary, "import", wrong / "holyscoopbad.json", "--source",
"bucket", "--format", "scoop", "--output", root / "wrong-out"],
capture_output=True, text=True)
assert result.returncode == 6, result
assert "the manifest pins" in result.stderr, result.stderr
assert not list((root / "wrong-out").glob("*.holy"))
# an artifact that is not beside the manifest is a missing capability
absent = root / "absent"
absent.mkdir()
manifest(absent, "holyscoopsparse", {"x/y.exe": "MZ\n"})
(absent / "holyscoopsparse-fixture.zip").unlink()
result = subprocess.run([binary, "import", absent / "holyscoopsparse.json", "--source",
"bucket", "--format", "scoop", "--output", root / "absent-out"],
capture_output=True, text=True)
assert result.returncode == 6, result
assert "is not beside the manifest" in result.stderr, result.stderr
# a manifest with no digest, no url or no version is refused
for field, value in (("hash", None), ("url", None), ("version", None)):
case = root / ("case-" + field)
case.mkdir()
manifest(case, "holyscoopcase", {"c/c.exe": "MZ\n"})
body = json.loads((case / "holyscoopcase.json").read_text())
if value is None:
del body[field]
(case / "holyscoopcase.json").write_text(json.dumps(body))
result = subprocess.run([binary, "import", case / "holyscoopcase.json", "--source",
"bucket", "--format", "scoop", "--output",
root / (case.name + "-out")],
capture_output=True, text=True)
assert result.returncode == 2, (field, result)
assert "holyscoopcase" not in result.stderr
# an artifact with an escaping path is refused rather than placed
escape = root / "escape"
escape.mkdir()
manifest(escape, "holyscoopescape", {"../outside.exe": "MZ\n"})
result = subprocess.run([binary, "import", escape / "holyscoopescape.json", "--source",
"bucket", "--format", "scoop", "--output",
root / "escape-out"], capture_output=True, text=True)
assert result.returncode == 3, result
assert "cannot place" in result.stderr, result.stderr
# an artifact that is a plain file travels as one payload entry
plain = root / "plain"
plain.mkdir()
archive = plain / "holyscoopplain-fixture.bin"
archive.write_bytes(b"MZ a single file artifact\n")
(plain / "holyscoopplain.json").write_text(json.dumps({
"version": "1.0",
"url": f"https://example.org/{archive.name}",
"hash": hashlib.sha256(archive.read_bytes()).hexdigest(),
}, indent=2))
call("import", plain / "holyscoopplain.json", "--source", "bucket", "--format", "scoop",
"--output", root / "plain-out", status=3)
plain_manifest = call("manifest", "local:" + str(root /
"plain-out/holyscoopplain--noarch--nolibc.holy"))
assert "usr/lib/holy/private/holyscoopplain/holyscoopplain-fixture.bin" in plain_manifest
# a manifest that is not JSON, or not an object, is refused
broken = root / "broken"
broken.mkdir()
(broken / "holyscoopbroken.json").write_text("id: holyscoopbroken\n")
result = subprocess.run([binary, "import", broken / "holyscoopbroken.json", "--source",
"bucket", "--format", "scoop", "--output", root / "broken-out"],
capture_output=True, text=True)
assert result.returncode == 2, result
assert "reads no other form" in result.stderr, result.stderr
result = subprocess.run([binary, "import", root / "absent.json", "--source", "bucket",
"--format", "scoop", "--output", root / "absent2-out"],
capture_output=True, text=True)
assert result.returncode == 6, result
assert "manifest unavailable" in result.stderr, result.stderr
print("scoop manifest fixtures passed")
return 0
if __name__ == "__main__":
sys.exit(main())