open-standards/wai/wai-quantum-desktop/release/manifests_test.py
Transaction Science d41ee743d6 wai-quantum-desktop release: the cask it writes passes brew style
The 0.1.1 cask had three findings in the tap: no blank line between each disk
image's sha256 and its url, and no depends_on :macos. The writer now puts both
in, and manifests_test.py checks them. Written from the 0.1.1 release's own
assets, the cask has no finding, and the Scoop manifest and latest.json are
byte for byte the ones published. (A minimum version is not given: the Intel
build states macOS 10.13, below what Homebrew supports, so brew style calls
it redundant.)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 22:19:02 -05:00

130 lines
6.5 KiB
Python

"""release/manifests.py against releases made up here, without a network:
python3 release/manifests_test.py
- every platform: the cask names each disk image with its SHA-256 and says the app
updates itself; the Scoop manifest names the archive, its SHA-256 and its folder; the
update manifest carries each update's own signature under the names the updater asks
for, and an update with no signature is left out;
- no update key: no update manifest, and a cask that does not say the app updates itself;
- a notarised build: no caveat;
- nothing attached: a failure.
Exit 0 iff every check passes.
"""
import json
import os
import pathlib
import shutil
import subprocess
import sys
import tempfile
here = pathlib.Path(__file__).resolve().parent
fails = 0
def check(ok, what):
global fails
print(f"{'PASS' if ok else 'FAIL'} {what}")
if not ok:
fails += 1
V = "0.3.0"
TAG = f"wai-quantum-desktop-v{V}"
PUBLIC = "https://forge.example/Org/repo"
def release(files):
"""A folder of assets: each name with its bytes, its .sha256 and, when asked, its .sig."""
d = pathlib.Path(tempfile.mkdtemp(prefix="wqd-assets-"))
for name, signed in files:
(d / name).write_text(f"bytes of {name}")
(d / f"{name}.sha256").write_text(f"{('%064x' % abs(hash(name)))[:64]} {name}\n")
if signed:
(d / f"{name}.sig").write_text(f"c2lnIG9m{name}\n")
return d
def run(assets, env=None):
out = pathlib.Path(tempfile.mkdtemp(prefix="wqd-out-"))
e = {k: v for k, v in os.environ.items() if k not in ("FORGE_TOKEN", "MACOS_NOTARISED")}
e.update({"ASSETS_DIR": str(assets)}, **(env or {}))
r = subprocess.run([sys.executable, "-I", str(here / "manifests.py"), V, TAG, "http://forge.invalid/api/v1", "Org/repo", PUBLIC, str(out)],
env=e, capture_output=True, text=True)
return r, out
def sha(assets, name):
return (assets / f"{name}.sha256").read_text().split()[0]
P = f"wai-quantum-desktop-{V}-"
every = [
(P + "aarch64-apple-darwin.dmg", False), (P + "aarch64-apple-darwin.app.tar.gz", True),
(P + "x86_64-apple-darwin.dmg", False), (P + "x86_64-apple-darwin.app.tar.gz", False),
(P + "x86_64-unknown-linux-gnu.AppImage", True), (P + "x86_64-unknown-linux-gnu.deb", True), (P + "x86_64-unknown-linux-gnu.rpm", True),
(P + "x86_64-pc-windows-msvc-setup.exe", True), (P + "x86_64-pc-windows-msvc.zip", False),
]
# Every platform.
a = release(every)
r, out = run(a)
check(r.returncode == 0, f"every platform: it runs ({r.stdout.strip()} {r.stderr.strip()})")
cask = (out / "wai-quantum-desktop.rb").read_text()
for cpu, triple in (("arm", "aarch64-apple-darwin"), ("intel", "x86_64-apple-darwin")):
name = P + triple + ".dmg"
check(f'on_{cpu} do\n sha256 "{sha(a, name)}"\n\n url "{PUBLIC}/releases/download/{TAG}/{name}",' in cask, f"the cask names the {triple} disk image with its SHA-256")
check(f'version "{V}"' in cask and 'app "Quantum Studio.app"' in cask and "auto_updates true\n depends_on :macos\n" in cask and "xattr -dr com.apple.quarantine" in cask,
"the cask: its version, the app, that it updates itself, that it is for macOS, and the caveat for an app signed ad hoc")
check('verified: "forge.example/Org/repo/"' in cask, "the cask says where its downloads are verified from")
check("\n\n\n" not in cask and cask.endswith(" EOS\nend\n"), "the cask has no run of blank lines, and ends with its caveat")
if shutil.which("ruby"):
p = out / "wai-quantum-desktop.rb"
rc = subprocess.run(["ruby", "-c", str(p)], capture_output=True, text=True)
check(rc.returncode == 0, f"the cask is Ruby ({rc.stdout.strip()}{rc.stderr.strip()})")
else:
print("note: no ruby here to parse the cask")
scoop = json.loads((out / "wai-quantum-desktop.json").read_text())
win = P + "x86_64-pc-windows-msvc"
check(scoop["version"] == V and scoop["architecture"]["64bit"] == {"url": f"{PUBLIC}/releases/download/{TAG}/{win}.zip", "hash": sha(a, win + ".zip"), "extract_dir": win}
and scoop["shortcuts"] == [["wai-quantum-desktop.exe", "Quantum Studio"]], f"the Scoop manifest names the archive, its SHA-256 and its folder ({scoop['architecture']})")
latest = json.loads((out / "latest.json").read_text())
want = {"darwin-aarch64": P + "aarch64-apple-darwin.app.tar.gz",
"linux-x86_64": P + "x86_64-unknown-linux-gnu.AppImage", "linux-x86_64-appimage": P + "x86_64-unknown-linux-gnu.AppImage",
"linux-x86_64-deb": P + "x86_64-unknown-linux-gnu.deb", "linux-x86_64-rpm": P + "x86_64-unknown-linux-gnu.rpm",
"windows-x86_64": P + "x86_64-pc-windows-msvc-setup.exe", "windows-x86_64-nsis": P + "x86_64-pc-windows-msvc-setup.exe"}
check(sorted(latest["platforms"]) == sorted(want), f"the update manifest has an update for each signed platform and no other ({sorted(latest['platforms'])})")
check(all(latest["platforms"][k] == {"signature": (a / f"{n}.sig").read_text().strip(), "url": f"{PUBLIC}/releases/download/{TAG}/{n}"} for k, n in want.items()),
"each update carries its own signature and its public address")
check(latest["version"] == V and "darwin-x86_64" not in latest["platforms"] and "has no update signature" in r.stderr,
"an update with no signature is left out, and the log says so")
# No update key: nothing signed.
b = release([(n, False) for n, _ in every])
r, out = run(b)
cask = (out / "wai-quantum-desktop.rb").read_text()
check(r.returncode == 0 and not (out / "latest.json").exists() and "auto_updates" not in cask and "\n\n\n" not in cask
and "no update is signed" in r.stderr, "with no update key: no update manifest, and the cask does not say the app updates itself")
# A notarised build: no caveat.
r, out = run(a, {"MACOS_NOTARISED": "true"})
cask = (out / "wai-quantum-desktop.rb").read_text()
check("caveats" not in cask and cask.endswith(" ]\nend\n"), "a notarised build's cask has no caveat")
# Only Windows attached: the Scoop manifest alone, and the missing ones named.
c = release([(P + "x86_64-pc-windows-msvc.zip", False)])
r, out = run(c)
check(r.returncode == 0 and (out / "wai-quantum-desktop.json").exists() and not (out / "wai-quantum-desktop.rb").exists() and "aarch64-apple-darwin.dmg is not attached" in r.stderr,
"a platform that is not attached is left out, and the log names it")
# Nothing attached.
d = release([])
r, out = run(d)
check(r.returncode != 0 and "no installer is attached" in r.stderr, "with nothing attached, it fails")
for x in (a, b, c, d):
shutil.rmtree(x, ignore_errors=True)
print(f"{fails} check(s) failed" if fails else "every check passed")
sys.exit(1 if fails else 0)