"""The site and the README share one coverage computation. generate_site.py and generate_readme.py each verified every platform, a minute of the site build spent twice on the same answer. The first to run stores the coverage, keyed by its inputs, and the second reads it. A cache that outlived a change to its inputs would publish stale counts, so these tests change an input and expect a fresh computation. """ from __future__ import annotations import sys import tempfile import unittest from pathlib import Path from unittest import mock REPO_ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(REPO_ROOT / "scripts")) import generate_readme # noqa: E402 class CoverageCache(unittest.TestCase): def setUp(self) -> None: self.work = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp" / "tests")) self.platforms = self.work / "platforms" self.platforms.mkdir() (self.platforms / "alpha.yml").write_text("platform: Alpha\n") self.emulators = self.work / "emulators" self.emulators.mkdir() self.db = self.work / "database.json" self.db.write_text("{}") self.calls: list[str] = [] patches = [ mock.patch.object(generate_readme, "COVERAGE_CACHE_DIR", self.work / "cache"), mock.patch.object(generate_readme, "compute_coverage", self._compute), ] for patch in patches: patch.start() self.addCleanup(patch.stop) def tearDown(self) -> None: import shutil shutil.rmtree(self.work, ignore_errors=True) def _compute(self, name, platforms_dir, db, data_registry=None, emulators_dir=""): self.calls.append(name) if name == "ghost": raise FileNotFoundError(name) return {"platform": name.title(), "missing_names": {"a.bin"}} def _run(self, names=("beta", "alpha")) -> dict: return generate_readme.platform_coverages( list(names), str(self.platforms), {}, {}, emulators_dir=str(self.emulators), db_path=str(self.db), ) def test_second_run_reads_the_first(self): first = self._run() self.assertEqual(self.calls, ["alpha", "beta"]) second = self._run() self.assertEqual(self.calls, ["alpha", "beta"]) self.assertEqual(first, second) # The caller's order, not the computation's. self.assertEqual(list(second), ["beta", "alpha"]) self.assertEqual(second["alpha"]["missing_names"], {"a.bin"}) def test_a_changed_input_computes_again(self): self._run() for change in ( lambda: (self.platforms / "alpha.yml").write_text("platform: Alpha 2\n"), lambda: self.db.write_text('{"files": {}}'), lambda: (self.emulators / "core.yml").write_text("emulator: core\n"), ): self.calls.clear() change() self._run() self.assertEqual(self.calls, ["alpha", "beta"]) def test_another_platform_list_computes_again(self): self._run() self.calls.clear() self._run(("alpha",)) self.assertEqual(self.calls, ["alpha"]) def test_a_platform_without_a_file_is_left_out(self): self.assertEqual(list(self._run(("ghost", "alpha"))), ["alpha"]) self.assertEqual(list(self._run(("ghost", "alpha"))), ["alpha"]) def test_one_cache_file_is_kept(self): self._run() (self.platforms / "alpha.yml").write_text("platform: Alpha 3\n") self._run() self.assertEqual(len(list((self.work / "cache").glob("*.pickle"))), 1) if __name__ == "__main__": unittest.main()