From 3a29d5b56a0011031d173543a845f3d670f8edc6 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Sun, 18 Oct 2026 15:19:51 +0200 Subject: [PATCH] fix: refuse a profile path into .variants --- scripts/validate_schemas.py | 25 +++++++++++++++++++++++++ tests/test_audit_regressions.py | 23 +++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/scripts/validate_schemas.py b/scripts/validate_schemas.py index 85ecea5e..26e2ad8d 100644 --- a/scripts/validate_schemas.py +++ b/scripts/validate_schemas.py @@ -256,6 +256,30 @@ def _unknown_data_dir_refs() -> list[str]: return out +def _repository_layout_in_profiles() -> list[str]: + """Profile destinations that name this repository's storage. + + `path:` is where the emulator's code reads the file, never where we keep + it: a profile written from a clone filed its variants under + `.variants/...`, and the pack then wrote them where no emulator looks. + """ + out: list[str] = [] + for path in source_yaml_paths(ROOT / "emulators"): + with path.open(encoding="utf-8") as handle: + document = yaml_load(handle) or {} + for entry in document.get("files") or []: + if not isinstance(entry, dict): + continue + for key in ("path", "standalone_path"): + value = str(entry.get(key) or "") + if ".variants" in value.replace("\\", "/").split("/"): + out.append( + f"{path.relative_to(ROOT)}: {entry.get('name')} {key} " + f"names the repository's .variants/ storage: {value}" + ) + return out + + def _semantic_envelope_checks(path: Path, document: dict) -> list[str]: if document.get("count") != len(document.get("items", [])): return [f"{path.relative_to(ROOT)}: count does not equal len(items)"] @@ -354,6 +378,7 @@ def main() -> int: ) ) errors.extend(_unknown_data_dir_refs()) + errors.extend(_repository_layout_in_profiles()) if not args.source_only: database_path = ROOT / "database.json" diff --git a/tests/test_audit_regressions.py b/tests/test_audit_regressions.py index 6b05720c..2341e83a 100644 --- a/tests/test_audit_regressions.py +++ b/tests/test_audit_regressions.py @@ -4860,5 +4860,28 @@ class RommNeoGeoFoldersGetTheirBios(unittest.TestCase): self.assertNotIn("neogeoaes/pgm.zip", text) + +class AProfilePathIsTheEmulatorsNotOurs(unittest.TestCase): + """No guard stopped a profile `path:` naming .variants/, our storage: + the pack would write the file where no emulator reads.""" + + def test_a_variants_path_is_refused(self): + from unittest import mock + + from scripts import validate_schemas + + with tempfile.TemporaryDirectory(dir=TMP_ROOT) as directory: + emulators = Path(directory) / "emulators" + emulators.mkdir() + (emulators / "x.yml").write_text( + "emulator: X\nfiles:\n - name: scph1001.bin\n" + " path: .variants/scph1001.bin.abc12345\n" + ) + with mock.patch.object(validate_schemas, "ROOT", Path(directory)): + errors = validate_schemas._repository_layout_in_profiles() + self.assertEqual(len(errors), 1) + self.assertIn(".variants/", errors[0]) + + if __name__ == "__main__": unittest.main()