mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-23 20:29:06 -05:00
Guard semantic checks for invalid manifests
This commit is contained in:
1 parent
3b1ea6b5d2
commit
4bf53a0561
2 files changed
+123
-11
No files matched your search
+25
-11
@@ -8,6 +8,7 @@ import json
|
||||
import re
|
||||
import sys
|
||||
import zipfile
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path, PurePosixPath
|
||||
|
||||
import yaml
|
||||
@@ -123,7 +124,11 @@ def _checks_without_values(path: Path, document: object) -> list[str]:
|
||||
return out
|
||||
|
||||
|
||||
def _validate_json_files(paths: list[Path], schema_name: str) -> list[str]:
|
||||
def _validate_json_files(
|
||||
paths: list[Path],
|
||||
schema_name: str,
|
||||
semantic_check: Callable[[Path, object], list[str]] | None = None,
|
||||
) -> list[str]:
|
||||
validator = _validator(schema_name)
|
||||
out: list[str] = []
|
||||
for path in paths:
|
||||
@@ -132,7 +137,10 @@ def _validate_json_files(paths: list[Path], schema_name: str) -> list[str]:
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
out.append(f"{path.relative_to(ROOT)}: {exc}")
|
||||
continue
|
||||
out.extend(_errors(validator, data, str(path.relative_to(ROOT))))
|
||||
validation_errors = _errors(validator, data, str(path.relative_to(ROOT)))
|
||||
out.extend(validation_errors)
|
||||
if not validation_errors and semantic_check is not None:
|
||||
out.extend(semantic_check(path, data))
|
||||
return out
|
||||
|
||||
|
||||
@@ -407,16 +415,20 @@ def main() -> int:
|
||||
|
||||
if not args.source_only:
|
||||
database_path = ROOT / "database.json"
|
||||
errors.extend(_validate_json_files([database_path], "database.schema.json"))
|
||||
if database_path.exists():
|
||||
errors.extend(_semantic_database_checks(_load_json(database_path)))
|
||||
errors.extend(
|
||||
_validate_json_files(
|
||||
[database_path],
|
||||
"database.schema.json",
|
||||
lambda _path, database: _semantic_database_checks(database),
|
||||
)
|
||||
)
|
||||
|
||||
install_paths = sorted((ROOT / "install").glob("*.json"))
|
||||
errors.extend(
|
||||
_validate_json_files(install_paths, "install-manifest.schema.json")
|
||||
_validate_json_files(
|
||||
install_paths, "install-manifest.schema.json", _semantic_install_checks
|
||||
)
|
||||
)
|
||||
for path in install_paths:
|
||||
errors.extend(_semantic_install_checks(path, _load_json(path)))
|
||||
|
||||
target_paths = sorted((ROOT / "install" / "targets").glob("*.json"))
|
||||
errors.extend(
|
||||
@@ -448,10 +460,12 @@ def main() -> int:
|
||||
(api_dir / "systems").glob("*.json")
|
||||
) + sorted((api_dir / "lookup").glob("*.json"))
|
||||
errors.extend(
|
||||
_validate_json_files(envelope_paths, "site-api-envelope.schema.json")
|
||||
_validate_json_files(
|
||||
envelope_paths,
|
||||
"site-api-envelope.schema.json",
|
||||
_semantic_envelope_checks,
|
||||
)
|
||||
)
|
||||
for path in envelope_paths:
|
||||
errors.extend(_semantic_envelope_checks(path, _load_json(path)))
|
||||
|
||||
errors.extend(_validate_pack_manifests(ROOT / "dist"))
|
||||
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
"""Schema-invalid synthetic manifests must not crash semantic validation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import json
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from contextlib import redirect_stdout
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
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 = ""
|
||||
|
||||
|
||||
@unittest.skipIf(validate_schemas is None, f"validate_schemas unavailable: {IMPORT_ERROR}")
|
||||
class SemanticManifestValidation(unittest.TestCase):
|
||||
def test_invalid_synthetic_manifest_reports_schema_errors_without_crashing(self):
|
||||
with tempfile.TemporaryDirectory(dir=REPO_ROOT) as directory:
|
||||
root = Path(directory)
|
||||
(root / "emulators").mkdir()
|
||||
platforms = root / "platforms"
|
||||
platforms.mkdir()
|
||||
(platforms / "_data_dirs.yml").write_text(
|
||||
"data_directories: {}\n", encoding="utf-8"
|
||||
)
|
||||
(root / "database.json").write_text(
|
||||
json.dumps({"files": None}), encoding="utf-8"
|
||||
)
|
||||
install = root / "install"
|
||||
install.mkdir()
|
||||
(install / "synthetic.json").write_text(
|
||||
json.dumps({"files": None}), encoding="utf-8"
|
||||
)
|
||||
api = root / "docs" / "api" / "v1"
|
||||
api.mkdir(parents=True)
|
||||
(api / "catalog.json").write_text(
|
||||
json.dumps({"count": 1, "items": None}), encoding="utf-8"
|
||||
)
|
||||
|
||||
output = io.StringIO()
|
||||
with mock.patch.object(validate_schemas, "ROOT", root), mock.patch.object(
|
||||
sys, "argv", ["validate_schemas.py"]
|
||||
), redirect_stdout(output):
|
||||
result = validate_schemas.main()
|
||||
|
||||
self.assertEqual(result, 1)
|
||||
text = output.getvalue().replace("\\", "/")
|
||||
self.assertIn("database.json", text)
|
||||
self.assertIn("install/synthetic.json", text)
|
||||
self.assertIn("docs/api/v1/catalog.json", text)
|
||||
self.assertIn("files", text)
|
||||
self.assertIn("items", text)
|
||||
self.assertNotIn("Traceback", text)
|
||||
|
||||
def test_valid_synthetic_manifest_still_runs_semantic_checks(self):
|
||||
manifest = {
|
||||
"manifest_version": 2,
|
||||
"source": "full",
|
||||
"regions": [],
|
||||
"platform": "synthetic",
|
||||
"display_name": "Synthetic",
|
||||
"version": "test",
|
||||
"generated": "2026-10-21T00:00:00Z",
|
||||
"base_destination": "",
|
||||
"detect": [],
|
||||
"standalone_copies": [],
|
||||
"total_files": 1,
|
||||
"total_size": 0,
|
||||
"total_omitted": 0,
|
||||
"omitted_files": [],
|
||||
"files": [],
|
||||
}
|
||||
with tempfile.TemporaryDirectory(dir=REPO_ROOT) as directory:
|
||||
path = Path(directory) / "synthetic.json"
|
||||
path.write_text(json.dumps(manifest), encoding="utf-8")
|
||||
errors = validate_schemas._validate_json_files(
|
||||
[path],
|
||||
"install-manifest.schema.json",
|
||||
validate_schemas._semantic_install_checks,
|
||||
)
|
||||
|
||||
self.assertEqual(len(errors), 1)
|
||||
self.assertIn("total_files does not equal len(files)", errors[0])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user