mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-12 04:53:52 -06:00
feat: refuse source_refs that cite only data
validate_schemas now refuses a files[] source_ref whose parts are all data or build files (a table, a makefile, meson.build, the file itself). A ref passes when one part names a source file or a binary with an address. The closed-source profiles whose refs are still prose about the shipped binary are listed in DATA_ONLY_REF_EXCEPTIONS with a reason each.
This commit is contained in:
1 parent
0c2b718c5e
commit
611dd0fa55
2 files changed
+143
No files matched your search
@@ -87,6 +87,7 @@ def _validate_yaml_directory(
|
||||
out.extend(_systems_outside_profile(path, data))
|
||||
out.extend(_doubled_paths(path))
|
||||
out.extend(_group_requirement_split(path, data))
|
||||
out.extend(_data_only_source_refs(path, data))
|
||||
return out
|
||||
|
||||
|
||||
@@ -155,6 +156,65 @@ def _group_requirement_split(path: Path, document: object) -> list[str]:
|
||||
return out
|
||||
|
||||
|
||||
_CODE_SUFFIXES = frozenset(
|
||||
"c cc cpp cxx c++ h hh hpp hxx inc inl ipp m mm mjs cjs js jsx ts tsx py cs java kt "
|
||||
"go rs lua pas pp asm s sh bash zsh ps1 bat cmd vbs swift d zig nim qml qc rb pl php "
|
||||
"scala ml hs dart vue ino z80 smali".split()
|
||||
)
|
||||
_CODE_FILE = re.compile(r"[\w+.-]\.([A-Za-z0-9+]+)\b")
|
||||
_BINARY_EVIDENCE = re.compile(
|
||||
r"\b0x[0-9a-fA-F]+\b|\bFUN_[0-9a-fA-F]+\b|\.(?:so|dll|exe|dylib|dex|apk|jar|elf|nro|xex)\b",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
# Profiles whose files cite no published code. Each reason says why the
|
||||
# check cannot apply yet.
|
||||
DATA_ONLY_REF_EXCEPTIONS: dict[str, str] = {
|
||||
"ace-dl": "closed source: the refs describe what the shipped binary opens",
|
||||
"cpcemu": "closed source: the refs describe what the shipped binary opens",
|
||||
"drastic": "closed source: the shader format files are read by the shipped binary",
|
||||
"lexaloffle": "closed source: PICO-8 reads pico8_gpio, the refs are its readme and the KNULLI install script",
|
||||
"mfme": "closed source: the refs are the release notes of the shipped binary",
|
||||
}
|
||||
|
||||
|
||||
def _ref_cites_code(ref: str) -> bool:
|
||||
"""A source_ref part names a source file, or a binary with an address."""
|
||||
from sourceref import split_source_ref
|
||||
|
||||
if _BINARY_EVIDENCE.search(ref):
|
||||
return True
|
||||
for part in split_source_ref(ref):
|
||||
for suffix in _CODE_FILE.findall(part.raw):
|
||||
if suffix.lower() in _CODE_SUFFIXES:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _data_only_source_refs(path: Path, document: object) -> list[str]:
|
||||
"""A files[] source_ref that cites only data or build files.
|
||||
|
||||
A data file is evidence that the emulator reads a file only once the
|
||||
code that ships and parses it is cited too: iwadinfo.txt names the
|
||||
IWADs, d_iwad.cpp is what reads it. 655 entries in 27 profiles cited
|
||||
the table alone, and six of them named a file no code opens.
|
||||
"""
|
||||
if not isinstance(document, dict) or path.stem in DATA_ONLY_REF_EXCEPTIONS:
|
||||
return []
|
||||
out: list[str] = []
|
||||
for index, entry in enumerate(document.get("files") or []):
|
||||
if not isinstance(entry, dict) or not entry.get("source_ref"):
|
||||
continue
|
||||
refs = entry["source_ref"]
|
||||
for ref in refs.values() if isinstance(refs, dict) else [refs]:
|
||||
if ref and not _ref_cites_code(str(ref)):
|
||||
out.append(
|
||||
f"{path.relative_to(ROOT)}: files[{index}] ({entry.get('name')}) "
|
||||
f"source_ref cites no code that reads the file: {ref}"
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def _checks_without_values(path: Path, document: object) -> list[str]:
|
||||
"""A check an entry declares with nothing to compare against.
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
"""A files[] source_ref must cite the code that reads the file."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import tests # noqa: F401 offline, scratch under tmp/, the suite's own cache
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
||||
|
||||
try:
|
||||
import validate_schemas
|
||||
except ImportError as exc:
|
||||
validate_schemas = None
|
||||
IMPORT_ERROR = str(exc)
|
||||
else:
|
||||
IMPORT_ERROR = ""
|
||||
|
||||
|
||||
def _profile(ref: object) -> dict:
|
||||
return {"files": [{"name": "doom.wad", "source_ref": ref}]}
|
||||
|
||||
|
||||
@unittest.skipIf(validate_schemas is None, f"validate_schemas unavailable: {IMPORT_ERROR}")
|
||||
class DataOnlySourceRefs(unittest.TestCase):
|
||||
path = REPO_ROOT / "emulators" / "synthetic.yml"
|
||||
|
||||
def errors(self, ref: object) -> list[str]:
|
||||
return validate_schemas._data_only_source_refs(self.path, _profile(ref))
|
||||
|
||||
def test_a_data_table_alone_is_refused(self):
|
||||
self.assertEqual(len(self.errors("wadsrc_extra/static/iwadinfo.txt:420, 437")), 1)
|
||||
|
||||
def test_a_build_file_alone_is_refused(self):
|
||||
for ref in ("pc-bios/meson.build:30", "wadsrc/CMakeLists.txt:1",
|
||||
"dists/engine-data/engine_data.mk (ENABLE_KYRA)", "src/configure:1933"):
|
||||
with self.subTest(ref=ref):
|
||||
self.assertEqual(len(self.errors(ref)), 1)
|
||||
|
||||
def test_a_file_in_the_tree_alone_is_refused(self):
|
||||
self.assertEqual(len(self.errors("fMSX/ROMs/MSX.ROM")), 1)
|
||||
|
||||
def test_the_table_with_its_reader_passes(self):
|
||||
self.assertEqual(self.errors(
|
||||
"wadsrc_extra/static/iwadinfo.txt:420, wadsrc_extra/CMakeLists.txt:1, "
|
||||
"src/d_iwad.cpp:286-296, 502-531"), [])
|
||||
|
||||
def test_a_role_prefix_is_read_through(self):
|
||||
self.assertEqual(self.errors("source pc-bios/meson.build:30, source hw/i386/pc_piix.c:466"), [])
|
||||
|
||||
def test_a_binary_with_an_address_passes(self):
|
||||
for ref in ("libemucore.so 0x1a2b3c", "Fusion.exe 0x00401000",
|
||||
"bigpemu FUN_006d8be0 (BootROM read)", "nethersx2 v2.2n classes.dex icons/star-%d.png"):
|
||||
with self.subTest(ref=ref):
|
||||
self.assertEqual(self.errors(ref), [])
|
||||
|
||||
def test_each_build_mode_is_judged(self):
|
||||
self.assertEqual(len(self.errors({"standalone": "src/main.cpp:10", "libretro": "data/list.txt:3"})), 1)
|
||||
|
||||
def test_an_excepted_profile_is_not_judged(self):
|
||||
name = next(iter(validate_schemas.DATA_ONLY_REF_EXCEPTIONS))
|
||||
path = REPO_ROOT / "emulators" / f"{name}.yml"
|
||||
self.assertEqual(validate_schemas._data_only_source_refs(path, _profile("history.txt:38")), [])
|
||||
|
||||
def test_every_exception_names_a_profile_and_a_reason(self):
|
||||
for name, reason in validate_schemas.DATA_ONLY_REF_EXCEPTIONS.items():
|
||||
with self.subTest(profile=name):
|
||||
self.assertTrue((REPO_ROOT / "emulators" / f"{name}.yml").is_file())
|
||||
self.assertTrue(reason.strip())
|
||||
|
||||
def test_the_profiles_cite_their_readers(self):
|
||||
errors: list[str] = []
|
||||
for path in sorted((REPO_ROOT / "emulators").glob("*.yml")):
|
||||
errors.extend(validate_schemas._data_only_source_refs(
|
||||
path, validate_schemas._load_source(path)))
|
||||
self.assertEqual(errors, [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user