107 lines
5.2 KiB
Python
107 lines
5.2 KiB
Python
import importlib.util
|
|
from pathlib import Path
|
|
import tempfile
|
|
import unittest
|
|
import zipfile
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
|
|
def load(name, filename):
|
|
spec = importlib.util.spec_from_file_location(name, ROOT / "scripts/release" / filename)
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
metadata = load("metadata", "metadata.py")
|
|
checksums = load("checksums", "checksums.py")
|
|
assets = load("assets", "verify-assets.py")
|
|
|
|
|
|
class ReleaseTests(unittest.TestCase):
|
|
def test_only_main_pushes_and_matching_version_tags_publish(self):
|
|
for event, kind, ref, publish, prerelease in [
|
|
("push", "branch", "main", "true", "true"),
|
|
("push", "branch", "feat/test", "false", "true"),
|
|
("pull_request", "branch", "main", "false", "true"),
|
|
("workflow_dispatch", "tag", f"v{metadata.version()}", "false", "true"),
|
|
("push", "tag", f"v{metadata.version()}", "true", "false"),
|
|
]:
|
|
with self.subTest(event=event, kind=kind, ref=ref):
|
|
result = metadata.resolve(event, kind, ref, "10", "2")
|
|
self.assertEqual(result["publish"], publish)
|
|
self.assertEqual(result["prerelease"], prerelease)
|
|
with self.assertRaises(ValueError):
|
|
metadata.resolve("push", "tag", "v0.0.0", "10", "1")
|
|
|
|
def test_reruns_have_unique_prerelease_tags(self):
|
|
self.assertNotEqual(metadata.resolve("push", "branch", "main", "10", "1")["release_tag"],
|
|
metadata.resolve("push", "branch", "main", "10", "2")["release_tag"])
|
|
|
|
def test_checksums_reject_tampering_and_path_escape(self):
|
|
with tempfile.TemporaryDirectory() as temp:
|
|
directory = Path(temp)
|
|
(directory / "app.zip").write_bytes(b"original")
|
|
checksums.write(directory, "SHA256SUMS")
|
|
checksums.check(directory, "SHA256SUMS")
|
|
(directory / "app.zip").write_bytes(b"tampered")
|
|
with self.assertRaises(ValueError):
|
|
checksums.check(directory, "SHA256SUMS")
|
|
(directory / "SHA256SUMS").write_text("bad ../outside\n")
|
|
with self.assertRaises(ValueError):
|
|
checksums.check(directory, "SHA256SUMS")
|
|
|
|
def test_release_requires_both_platforms_and_real_pe_executable(self):
|
|
with tempfile.TemporaryDirectory() as temp:
|
|
directory = Path(temp)
|
|
for name in ("TTREE_1.0.0_universal.dmg", "TTREE_universal.app.tar.gz",
|
|
"TTREE_universal.app.tar.gz.sig", "latest.json"):
|
|
(directory / name).write_bytes(b"fixture")
|
|
with self.assertRaises(ValueError):
|
|
assets.verify(directory, "1.0.0")
|
|
zip_path = directory / "TTREE_1.0.0_windows_x64.zip"
|
|
with zipfile.ZipFile(zip_path, "w") as archive:
|
|
archive.writestr("TTREE/TTREE.exe", b"MZfixture")
|
|
assets.verify(directory, "1.0.0")
|
|
with zipfile.ZipFile(zip_path, "w") as archive:
|
|
archive.writestr("TTREE/TTREE.exe", b"not an executable")
|
|
with self.assertRaises(ValueError):
|
|
assets.verify(directory, "1.0.0")
|
|
|
|
def test_update_signature_is_required_before_both_publication_checks(self):
|
|
workflow = (ROOT / ".github/workflows/build.yml").read_text()
|
|
self.assertLess(workflow.index("python3 scripts/release/checksums.py dist/release --write SHA256SUMS"),
|
|
workflow.index("run: scripts/release/sign-checksums.sh dist/release"))
|
|
publish = (ROOT / "scripts/release/publish.sh").read_text()
|
|
for directory in ("$RELEASE_DIR", "$DOWNLOADED"):
|
|
self.assertIn(f'scripts/release/verify-checksums-signature.sh "{directory}"', publish)
|
|
self.assertLess(publish.index('verify-checksums-signature.sh "$DOWNLOADED"'),
|
|
publish.index('--draft=false'))
|
|
with tempfile.TemporaryDirectory() as temp:
|
|
directory = Path(temp)
|
|
(directory / "app.zip").write_bytes(b"original")
|
|
(directory / "SHA256SUMS.sig").write_text("stale signature")
|
|
checksums.write(directory, "SHA256SUMS")
|
|
self.assertNotIn("SHA256SUMS.sig", (directory / "SHA256SUMS").read_text())
|
|
(directory / "SHA256SUMS.sig").write_text("new signature")
|
|
checksums.check(directory, "SHA256SUMS")
|
|
|
|
def test_workspace_and_workflow_use_current_source(self):
|
|
self.assertTrue((ROOT / "apps/macos/src/main.rs").exists())
|
|
self.assertTrue((ROOT / "apps/windows/src/main.rs").exists())
|
|
self.assertFalse((ROOT / "crates/ttree-app").exists())
|
|
workflow = (ROOT / ".github/workflows/build.yml").read_text()
|
|
self.assertIn("needs: [metadata, macos, windows]", workflow)
|
|
self.assertIn("scripts/release/publish.sh", workflow)
|
|
self.assertNotIn("npm run", workflow)
|
|
self.assertNotIn("src-tauri/", workflow)
|
|
for name in ("AGENTS.md", "README.md", "CLAUDE.md"):
|
|
text = (ROOT / name).read_text()
|
|
self.assertNotIn("npm run tauri", text)
|
|
self.assertNotIn("src-tauri/", text)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|