Files
bass-foundry/scripts/tests/test_release.py
T
nvrl 15853e61ac
Check and Windows preview / check (patina) (push) Successful in 2m20s
Check and Windows preview / check (rivet) (push) Successful in 1m38s
Check and Windows preview / previews (push) Successful in 1m6s
Check and Windows preview / windows (push) Successful in 1m50s
Independent plugin release / publish (push) Successful in 8s
Independent plugin release / plan (push) Successful in 7s
Independent plugin release / build (push) Successful in 2m21s
restructure
2026-11-05 23:58:00 +01:00

188 lines
7.9 KiB
Python

import contextlib
import hashlib
import importlib.util
import io
import json
from pathlib import Path
import struct
import sys
import tempfile
import unittest
import zipfile
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from plugin_catalog import archive_name, tag_version
import release
spec = importlib.util.spec_from_file_location("package_windows", Path(__file__).resolve().parents[1] / "package-windows.py")
packaging = importlib.util.module_from_spec(spec)
spec.loader.exec_module(packaging)
class FakeGitea:
def __init__(self):
self.release = None
self.bytes = {}
self.calls = []
self.tag_exists = True
self.fail_upload = False
def request(self, method, path, data=None, missing=False):
self.calls.append((method, path, data))
if path.startswith("/tags/"):
return {"name": path.split("/")[-1]} if self.tag_exists else None
if method == "GET":
return self.release
if method == "POST" and path == "/releases":
self.release = dict(data, id=7, assets=[])
return self.release
if method == "PATCH":
self.release.update(data)
return self.release
raise AssertionError((method, path))
def upload(self, release_id, path):
if self.fail_upload and path.name.endswith(".sha256"):
raise RuntimeError("simulated interruption")
self.calls.append(("UPLOAD", path.name, None))
url = "https://gitea.invalid/download/" + path.name
self.bytes[url] = path.read_bytes()
asset = {"name": path.name, "browser_download_url": url}
self.release["assets"].append(asset)
return asset
def download(self, url):
return self.bytes[url]
class ReleaseTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name)
self.folder = self.root / "dist/x86_64-pc-windows-gnu"
self.folder.mkdir(parents=True)
catalog = {}
for plugin, name, version in [("rivet", "Rivet", "0.3.0"), ("patina", "Patina", "0.1.0")]:
manifest = f"plugins/{plugin}/plugin/Cargo.toml"
catalog[plugin] = {"name": name, "description": name + " processor", "manifest": manifest,
"documentation": f"docs/{plugin}"}
self.write(manifest, f'[package]\nname="{plugin}"\nversion="{version}"\n')
self.write(f"docs/{plugin}/README.md", name + " guide")
self.write(f"docs/{plugin}/CHANGELOG.md", f"# Changelog\n\n## {version}\n\nFirst {name} release.\n")
self.write(f"dist/x86_64-pc-windows-gnu/docs/{plugin}/README.md", name + " guide")
self.write(f"dist/x86_64-pc-windows-gnu/docs/{plugin}/CHANGELOG.md", f"# Changelog\n\n## {version}\n\nFirst {name} release.\n")
binary = bytearray(160)
binary[:2] = b"MZ"
struct.pack_into("<I", binary, 0x3C, 64)
binary[64:68] = b"PE\0\0"
struct.pack_into("<H", binary, 68, 0x8664)
binary[120:131] = b"clap_entry\0"
(self.folder / f"{name}.clap").write_bytes(binary)
self.write("plugins/catalog.json", json.dumps(catalog))
for name in ["README.md", "LICENSE", "docs/development.md", "docs/releases.md"]:
self.write("dist/x86_64-pc-windows-gnu/" + name, name)
(self.folder / "Legacy.clap").write_bytes(b"unused")
def write(self, path, value):
p = self.root / path
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(value)
def package(self, plugin):
with contextlib.redirect_stdout(io.StringIO()):
return packaging.package(plugin=plugin, root=self.root)
def publish(self, client, plugin="patina"):
tag = "patina-v0.1.0" if plugin == "patina" else "rivet-v0.3.0"
with contextlib.redirect_stdout(io.StringIO()):
release.publish(plugin, tag, client, self.root)
def test_tag_and_version_are_independent(self):
self.assertEqual(tag_version("rivet", "rivet-v0.3.0", self.root), "0.3.0")
self.assertEqual(release.plan("refs/tags/patina-v0.1.0", self.root)["plugin"], "patina")
for plugin, tag in [("rivet", "patina-v0.1.0"), ("patina", "patina-v0.3.0"), ("patina", "v0.1.0")]:
with self.assertRaises(ValueError):
tag_version(plugin, tag, self.root)
def test_individual_zip_excludes_other_plugin_and_is_reproducible(self):
path = self.package("patina")
first = path.read_bytes()
self.package("patina")
self.assertEqual(first, path.read_bytes())
with zipfile.ZipFile(path) as z:
self.assertIn("Patina.clap", z.namelist())
self.assertNotIn("Rivet.clap", z.namelist())
self.assertNotIn("Legacy.clap", z.namelist())
self.assertFalse(any(n.startswith("docs/rivet/") for n in z.namelist()))
for line in z.read("SHA256SUMS").decode().splitlines():
digest, name = line.split(" ", 1)
self.assertEqual(hashlib.sha256(z.read(name)).hexdigest(), digest)
def test_draft_publish_and_retry_preserve_notes(self):
self.package("patina")
client = FakeGitea()
self.publish(client)
self.assertFalse(client.release["draft"])
self.assertEqual(len(client.release["assets"]), 2)
client.release["body"] = "User edited notes"
client.calls.clear()
self.publish(client)
self.assertEqual(client.release["body"], "User edited notes")
self.assertTrue(all(call[0] == "GET" for call in client.calls))
def test_partial_upload_is_repaired_before_draft_is_published(self):
self.package("patina")
client = FakeGitea()
client.fail_upload = True
with self.assertRaises(RuntimeError):
self.publish(client)
self.assertTrue(client.release["draft"])
self.assertEqual(len(client.release["assets"]), 1)
client.fail_upload = False
self.publish(client)
self.assertFalse(client.release["draft"])
self.assertEqual(len(client.release["assets"]), 2)
def test_different_existing_asset_is_never_replaced(self):
self.package("patina")
client = FakeGitea()
self.publish(client)
client.bytes[client.release["assets"][0]["browser_download_url"]] = b"different"
client.calls.clear()
with self.assertRaises(ValueError):
self.publish(client)
self.assertTrue(all(call[0] == "GET" for call in client.calls))
def test_bad_checksum_or_missing_tag_cannot_publish(self):
path = self.package("rivet")
path.with_suffix(".zip.sha256").write_text("invalid")
client = FakeGitea()
with self.assertRaises(ValueError):
self.publish(client, "rivet")
self.assertEqual(client.calls, [])
self.package("rivet")
client.tag_exists = False
with self.assertRaises(ValueError):
self.publish(client, "rivet")
self.assertFalse(any(call[0] != "GET" for call in client.calls))
def test_no_release_notes_fails_before_network(self):
self.package("patina")
self.write("docs/patina/CHANGELOG.md", "# No matching release")
client = FakeGitea()
with self.assertRaises(ValueError):
self.publish(client)
self.assertEqual(client.calls, [])
def test_foreign_plugin_in_archive_cannot_publish(self):
path = self.package("patina")
with zipfile.ZipFile(path, "a") as z:
z.writestr("Rivet.clap", b"wrong plugin")
path.with_suffix(".zip.sha256").write_text(f"{hashlib.sha256(path.read_bytes()).hexdigest()} {path.name}\n")
client = FakeGitea()
with self.assertRaises(ValueError):
self.publish(client)
self.assertEqual(client.calls, [])
if __name__ == "__main__":
unittest.main()