ebox/scripts/test_ebox_release.py

101 lines
5.1 KiB
Python

"""Regression tests for deterministic packaging and dependency boundaries."""
import importlib.util
import json
from pathlib import Path
import subprocess
import tarfile
import tempfile
import unittest
from unittest import mock
SPEC = importlib.util.spec_from_file_location("ebox_release", Path(__file__).with_name("ebox-release.py"))
RELEASE = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(RELEASE)
class ReleaseTests(unittest.TestCase):
def test_lock_requires_exact_revisions_and_optional_provider_is_explicit(self):
lock = json.loads((RELEASE.ROOT / "release-dependencies.json").read_text())
self.assertEqual(set(RELEASE.lock_entries(lock)), {"tp", "ecss"})
self.assertEqual(set(RELEASE.lock_entries(lock, True)), {"tp", "ecss", "ekp"})
lock["dependencies"]["tp"]["revision"] = "main"
with self.assertRaises(ValueError):
RELEASE.lock_entries(lock)
def test_archives_are_reproducible_source_complete_and_exclude_local_outputs(self):
with tempfile.TemporaryDirectory() as work:
root = Path(work) / "source"
root.mkdir()
subprocess.run(["git", "init", "-q", str(root)], check=True)
(root / "ebox.el").write_text(';;; ebox.el --- Test -*- lexical-binding: t -*-\n;; Version: 3.0.0\n;; Package-Requires: ((emacs "29.1"))\n')
(root / "LICENSE").write_text("license")
(root / "native/src").mkdir(parents=True)
(root / "native/src/lib.rs").write_text("native source")
(root / "native/target/release").mkdir(parents=True)
(root / "native/target/release/private.log").write_text("exclude")
(root / "ebox.elc").write_text("stale bytecode")
first, second = Path(work) / "first", Path(work) / "second"
first.mkdir()
second.mkdir()
one = RELEASE.write_package(first, root, "ebox")
(root / "ebox.el").touch()
two = RELEASE.write_package(second, root, "ebox")
self.assertEqual(one["sha256"], two["sha256"])
with tarfile.open(first / one["file"]) as archive:
names = archive.getnames()
self.assertIn("ebox-3.0.0/LICENSE", names)
self.assertIn("ebox-3.0.0/native/src/lib.rs", names)
self.assertIn("ebox-3.0.0/ebox-pkg.el", names)
self.assertFalse(any("target/" in name or name.endswith(".elc") for name in names))
self.assertTrue(all(member.mtime == 0 for member in archive.getmembers()))
def test_existing_output_is_preserved(self):
with tempfile.TemporaryDirectory() as work:
directory = Path(work)
sentinel = directory / "keep"
sentinel.write_text("owned")
with self.assertRaises(ValueError):
RELEASE.checkout_dependencies(directory, {})
with self.assertRaises(ValueError):
RELEASE.build(directory, directory, directory, {})
self.assertEqual(sentinel.read_text(), "owned")
def test_modified_archive_fails_before_emacs_runs(self):
with tempfile.TemporaryDirectory() as work:
directory = Path(work)
(directory / "bad.tar").write_bytes(b"changed")
(directory / "manifest.json").write_text(json.dumps({"packages": [{"file": "bad.tar", "sha256": "wrong"}]}))
with self.assertRaisesRegex(ValueError, "checksum mismatch"):
RELEASE.verify(directory, "must-not-run")
def test_failed_fetch_removes_temporary_checkout_and_never_creates_output(self):
with tempfile.TemporaryDirectory() as work:
parent = Path(work)
output = parent / "deps"
with mock.patch.object(RELEASE, "run", side_effect=RuntimeError("fetch failed")):
with self.assertRaisesRegex(RuntimeError, "fetch failed"):
RELEASE.checkout_dependencies(output, {"tp": {"url": "https://invalid", "revision": "0" * 40}})
self.assertEqual(list(parent.iterdir()), [])
def test_dependency_validation_rejects_modified_checkout(self):
with tempfile.TemporaryDirectory() as work:
root = Path(work)
dependency = root / "tp"
dependency.mkdir()
subprocess.run(["git", "init", "-q", str(dependency)], check=True)
(dependency / "tp.el").write_text("original")
subprocess.run(["git", "add", "tp.el"], cwd=dependency, check=True)
subprocess.run(["git", "-c", "user.name=Release Test", "-c", "user.email=release@example.invalid",
"commit", "-qm", "fixture"], cwd=dependency, check=True)
revision = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=dependency).decode().strip()
entries = {"tp": {"revision": revision}}
RELEASE.validate_dependencies(root, entries)
(dependency / "tp.el").write_text("modified")
with self.assertRaisesRegex(ValueError, "dependency must be clean"):
RELEASE.validate_dependencies(root, entries)
if __name__ == "__main__":
unittest.main()