101 lines
5.1 KiB
Python
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()
|