diff --git a/scripts/profile_sync.py b/scripts/profile_sync.py index ac517a05b..405228d35 100644 --- a/scripts/profile_sync.py +++ b/scripts/profile_sync.py @@ -4275,6 +4275,7 @@ def _apply_writes( file=sys.stderr, ) return + _WRITTEN.add(name) for message in messages: print(message) @@ -4663,16 +4664,20 @@ def main() -> None: if not profile_path.is_file(): continue try: + before = profile_path.read_bytes() for line in realign_prose( profile_path, args.cache_dir, args.offline, args.dry_run ): print(f"{name}: {line}") + if profile_path.read_bytes() != before: + _WRITTEN.add(name) except upstream.UpstreamError as exc: print(f"{name}: {exc}", file=sys.stderr) failed.add(name) except YamlWriteError as exc: print(f"{name}: write refused: {exc}", file=sys.stderr) failed.add(name) + _refresh_source_paths(args) if failed: raise SystemExit(1) return @@ -4710,12 +4715,46 @@ def main() -> None: if not args.changed_only or report.needs_review() or report.name in failed ] _emit(args, selected, reports, shown) + _refresh_source_paths(args) if failed or (args.strict and any( report.needs_review() or report.uncheckable() for report in reports )): raise SystemExit(1) +# Profiles this run wrote. Their new pins name trees the site's record of +# cited source paths does not hold yet. +_WRITTEN: set[str] = set() + + +def _refresh_source_paths(args: argparse.Namespace) -> None: + """Rebuild the site's record of cited source paths after a write. + + Each bump left website/src/data/source-paths.json behind its pins, and + the site's freshness test stayed red until someone ran sitesources.py + --fetch by hand. A run that writes nothing, or makes no request, leaves + it alone. + """ + if not _WRITTEN or args.dry_run or args.offline or os.environ.get("RETROBIOS_OFFLINE"): + return + import sitesources # noqa: PLC0415 + from common import write_if_changed # noqa: PLC0415 + + profiles = load_emulator_profiles(args.emulators_dir, skip_aliases=False) + held, unread = sitesources.build_source_paths( + profiles, [sitesources.TREE_CACHE], fetch=True + ) + sitesources.SOURCE_PATHS.parent.mkdir(parents=True, exist_ok=True) + write_if_changed( + str(sitesources.SOURCE_PATHS), json.dumps(held, indent=1, sort_keys=True) + "\n" + ) + print( + f"source paths: {sum(map(len, held.values()))} in {len(held)} trees, " + f"{len(unread)} trees not cached: {sitesources.SOURCE_PATHS}", + file=sys.stderr, + ) + + def _build_full_report( args: argparse.Namespace, name: str, diff --git a/tests/test_profile_sync.py b/tests/test_profile_sync.py index b348004ac..61f51538b 100644 --- a/tests/test_profile_sync.py +++ b/tests/test_profile_sync.py @@ -4599,6 +4599,35 @@ class TestRateLimitEndsOneProfile(unittest.TestCase): self.assertEqual(by_name["after"]["needs_review"], 1) +class AWriteRefreshesTheSourcePaths(unittest.TestCase): + """Each bump left website/src/data/source-paths.json behind its pins and + the site's freshness test red until someone ran sitesources by hand.""" + + def _args(self, **kw): + base = {"dry_run": False, "offline": False, "emulators_dir": "emulators"} + return argparse.Namespace(**{**base, **kw}) + + def test_only_a_real_online_write_refreshes(self): + from unittest import mock # noqa: PLC0415 + + import sitesources # noqa: PLC0415 + + with mock.patch.object(sitesources, "build_source_paths", return_value=({}, [])) as built, \ + mock.patch.dict(os.environ, {"RETROBIOS_OFFLINE": ""}), \ + mock.patch.object(profile_sync, "load_emulator_profiles", return_value={}), \ + mock.patch("common.write_if_changed") as written, \ + contextlib.redirect_stderr(io.StringIO()): + profile_sync._WRITTEN.clear() + profile_sync._refresh_source_paths(self._args()) + self.assertFalse(built.called) + profile_sync._WRITTEN.add("x") + profile_sync._refresh_source_paths(self._args(dry_run=True)) + self.assertFalse(built.called) + profile_sync._refresh_source_paths(self._args()) + self.assertTrue(built.called and written.called) + profile_sync._WRITTEN.clear() + + class FreshRefusesOffline(unittest.TestCase): def test_the_pair_is_refused(self): saved = sys.argv