feat(catalog): add cheap source fingerprint cache
Complete production catalog generation hashes every package twice, which is necessary for publication but wasteful when the same package tree has already produced the current catalog. Add a metadata-only cache that records package paths, sizes, nanosecond mtimes, catalog and unrar metadata, and output identity. The cache is advisory: incomplete outputs never hit, and production build validation remains the authority. Record files atomically under the ignored local cache directory, with focused tests for hits, source changes, and incomplete output. Test Plan: - `python3 -m unittest discover -s tools -p 'test_*.py'` -- passed - `git diff --cached --check` -- passed
This commit is contained in:
3 files changed
+310
No files matched your search
@@ -0,0 +1,107 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import catalog_source_cache
|
||||
|
||||
|
||||
class CatalogSourceCacheTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.temporary = tempfile.TemporaryDirectory()
|
||||
self.root = Path(self.temporary.name)
|
||||
self.packages = self.root / "packages"
|
||||
self.packages.mkdir()
|
||||
self.package = self.packages / "game"
|
||||
self.package.mkdir()
|
||||
(self.package / "version.ini").write_text("20260820\n", encoding="utf-8")
|
||||
(self.package / "game.eti").write_bytes(b"eti")
|
||||
self.catalog_db = self.root / "game.db"
|
||||
self.catalog_db.write_bytes(b"catalog")
|
||||
self.unrar = self.root / "unrar"
|
||||
self.unrar.write_bytes(b"unrar")
|
||||
self.manifests = self.root / "manifests"
|
||||
self.manifests.mkdir()
|
||||
(self.manifests / "catalog-content-index-v1.jsonl").write_text(
|
||||
"{}\n", encoding="utf-8"
|
||||
)
|
||||
self.stamp = self.root / "cache" / "catalog.json"
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self.temporary.cleanup()
|
||||
|
||||
def test_record_then_check_and_source_change(self) -> None:
|
||||
self.assertFalse(
|
||||
catalog_source_cache.cache_is_current(
|
||||
self.stamp,
|
||||
self.packages,
|
||||
self.catalog_db,
|
||||
self.manifests,
|
||||
self.unrar,
|
||||
)
|
||||
)
|
||||
catalog_source_cache.record_cache(
|
||||
self.stamp,
|
||||
self.packages,
|
||||
self.catalog_db,
|
||||
self.manifests,
|
||||
self.unrar,
|
||||
)
|
||||
self.assertTrue(
|
||||
catalog_source_cache.cache_is_current(
|
||||
self.stamp,
|
||||
self.packages,
|
||||
self.catalog_db,
|
||||
self.manifests,
|
||||
self.unrar,
|
||||
)
|
||||
)
|
||||
(self.package / "payload.bin").write_bytes(b"payload")
|
||||
self.assertFalse(
|
||||
catalog_source_cache.cache_is_current(
|
||||
self.stamp,
|
||||
self.packages,
|
||||
self.catalog_db,
|
||||
self.manifests,
|
||||
self.unrar,
|
||||
)
|
||||
)
|
||||
|
||||
def test_missing_or_incomplete_output_never_hits(self) -> None:
|
||||
catalog_source_cache.record_cache(
|
||||
self.stamp,
|
||||
self.packages,
|
||||
self.catalog_db,
|
||||
self.manifests,
|
||||
self.unrar,
|
||||
)
|
||||
(self.manifests / "catalog-content-index-v1.jsonl").unlink()
|
||||
self.assertFalse(
|
||||
catalog_source_cache.cache_is_current(
|
||||
self.stamp,
|
||||
self.packages,
|
||||
self.catalog_db,
|
||||
self.manifests,
|
||||
self.unrar,
|
||||
)
|
||||
)
|
||||
|
||||
def test_stamp_is_structured_and_sorted(self) -> None:
|
||||
catalog_source_cache.record_cache(
|
||||
self.stamp,
|
||||
self.packages,
|
||||
self.catalog_db,
|
||||
self.manifests,
|
||||
self.unrar,
|
||||
)
|
||||
stamp = json.loads(self.stamp.read_text(encoding="utf-8"))
|
||||
self.assertEqual(stamp["schema"], 1)
|
||||
self.assertEqual(stamp["catalog_db"]["size"], len(b"catalog"))
|
||||
self.assertEqual(stamp["unrar"]["size"], len(b"unrar"))
|
||||
self.assertEqual(stamp["packages_dir"], str(self.packages.absolute()))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user