mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-10 21:43:23 -05:00
feat: arbitrate a contested slot by who the pack serves
This commit is contained in:
1 parent
1245bb6eed
commit
c3d9e96d61
2 files changed
+117
-4
No files matched your search
+77
-4
@@ -180,6 +180,67 @@ def find_conflicts(
|
||||
]
|
||||
|
||||
|
||||
# A frontend that never reads the bytes cannot be made unhappy by better ones.
|
||||
CONTENT_CHECKING_MODES = frozenset({"md5", "sha1"})
|
||||
|
||||
|
||||
@dataclass
|
||||
class Decision:
|
||||
"""Which claim a pack should honour at a contested destination, and why."""
|
||||
|
||||
conflict: Conflict
|
||||
winner: Claim
|
||||
reason: str
|
||||
|
||||
@property
|
||||
def serves_both(self) -> bool:
|
||||
"""Whether honouring the winner still satisfies the other layer."""
|
||||
return self.reason == "existence"
|
||||
|
||||
|
||||
def arbitrate(conflict: Conflict, mode: str, addressee: str = "platform") -> Decision:
|
||||
"""Decide a contested destination for the pack being built.
|
||||
|
||||
A pack answers to whoever asked for it. A platform pack must leave the
|
||||
frontend's own check green, because a user reading red concludes the pack
|
||||
is broken; a pack built for one emulator answers to that emulator. The
|
||||
other layer is served as well whenever the destination allows it.
|
||||
|
||||
In ``existence`` mode the frontend only looks for a path, so the emulator's
|
||||
file satisfies both sides at once and there is nothing to trade away. In a
|
||||
content-checking mode the two answers cannot share one path, and the pack's
|
||||
addressee decides; the loss is reported rather than absorbed, because the
|
||||
cause is an upstream declaration that needs fixing at its source.
|
||||
"""
|
||||
profile = conflict.profile_claims[0]
|
||||
if addressee == "emulator":
|
||||
return Decision(conflict, profile, "addressee")
|
||||
if mode not in CONTENT_CHECKING_MODES:
|
||||
return Decision(conflict, profile, "existence")
|
||||
return Decision(conflict, conflict.platform_claim, "frontend_checks_content")
|
||||
|
||||
|
||||
def format_decision(decision: Decision) -> str:
|
||||
"""One line naming the contested slot, the winner and the ground for it."""
|
||||
conflict = decision.conflict
|
||||
if decision.serves_both:
|
||||
return (
|
||||
f"{conflict.destination}: serve {decision.winner.local_path} "
|
||||
f"({', '.join(conflict.emulators) or 'profile'}); the frontend only "
|
||||
"checks the path, so both are satisfied"
|
||||
)
|
||||
if decision.reason == "addressee":
|
||||
return (
|
||||
f"{conflict.destination}: serve {decision.winner.local_path}, "
|
||||
"the pack answers to the emulator"
|
||||
)
|
||||
return (
|
||||
f"{conflict.destination}: keep {decision.winner.local_path}, the frontend "
|
||||
f"verifies content and would reject "
|
||||
f"{conflict.profile_claims[0].local_path}; upstream declaration is wrong"
|
||||
)
|
||||
|
||||
|
||||
def format_conflict(conflict: Conflict) -> str:
|
||||
"""One line per conflict, naming both answers and who gave them."""
|
||||
emus = ", ".join(conflict.emulators) or "profile"
|
||||
@@ -207,7 +268,11 @@ def main() -> int:
|
||||
import argparse
|
||||
import json
|
||||
|
||||
from common import list_registered_platforms, load_emulator_profiles
|
||||
from common import (
|
||||
list_registered_platforms,
|
||||
load_emulator_profiles,
|
||||
load_platform_config,
|
||||
)
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Report destinations where an emulator profile contradicts "
|
||||
@@ -256,12 +321,20 @@ def main() -> int:
|
||||
)
|
||||
)
|
||||
else:
|
||||
fixable = 0
|
||||
for platform, conflicts in found.items():
|
||||
print(f"{platform}: {len(conflicts)} contradicted")
|
||||
config = load_platform_config(platform, args.platforms_dir)
|
||||
mode = config.get("verification_mode", "existence")
|
||||
print(f"{platform}: {len(conflicts)} contradicted [{mode}]")
|
||||
for conflict in conflicts:
|
||||
print(f" {format_conflict(conflict)}")
|
||||
decision = arbitrate(conflict, mode)
|
||||
fixable += decision.serves_both
|
||||
print(f" {format_decision(decision)}")
|
||||
total = sum(len(c) for c in found.values())
|
||||
print(f"\n{total} destinations where a profile contradicts what ships.")
|
||||
print(
|
||||
f"\n{total} contested destinations. {fixable} the pack can settle on "
|
||||
f"its own, {total - fixable} rest on an upstream declaration."
|
||||
)
|
||||
|
||||
return 1 if found else 0
|
||||
|
||||
|
||||
@@ -198,6 +198,46 @@ class TestConflicts(unittest.TestCase):
|
||||
self.assertIn("dolphin", line)
|
||||
|
||||
|
||||
class TestArbitration(unittest.TestCase):
|
||||
"""A pack answers to whoever asked for it, and serves both when it can."""
|
||||
|
||||
def _conflict(self):
|
||||
platform = slots.Claim(
|
||||
"platform", "GC/JAP/IPL.bin", "IPL.bin",
|
||||
local_path="bios/Console/GC/USA/IPL.bin", status="md5_exact",
|
||||
)
|
||||
profile = slots.Claim(
|
||||
"profile", "GC/JAP/IPL.bin", "IPL.bin", emulator="dolphin",
|
||||
local_path="bios/Console/GC/JAP/IPL.bin", status="path_exact",
|
||||
)
|
||||
return slots.Conflict("GC/JAP/IPL.bin", platform, [profile])
|
||||
|
||||
def test_existence_mode_serves_both(self):
|
||||
decision = slots.arbitrate(self._conflict(), "existence")
|
||||
self.assertEqual(decision.winner.origin, "profile")
|
||||
self.assertTrue(decision.serves_both)
|
||||
|
||||
def test_md5_mode_keeps_the_frontend_green(self):
|
||||
decision = slots.arbitrate(self._conflict(), "md5")
|
||||
self.assertEqual(decision.winner.origin, "platform")
|
||||
self.assertFalse(decision.serves_both)
|
||||
|
||||
def test_sha1_mode_is_content_checking_too(self):
|
||||
decision = slots.arbitrate(self._conflict(), "sha1")
|
||||
self.assertEqual(decision.winner.origin, "platform")
|
||||
|
||||
def test_an_emulator_pack_answers_to_the_emulator(self):
|
||||
decision = slots.arbitrate(self._conflict(), "md5", addressee="emulator")
|
||||
self.assertEqual(decision.winner.origin, "profile")
|
||||
self.assertFalse(decision.serves_both)
|
||||
|
||||
def test_the_reported_line_gives_the_ground_for_the_decision(self):
|
||||
kept = slots.format_decision(slots.arbitrate(self._conflict(), "md5"))
|
||||
self.assertIn("verifies content", kept)
|
||||
served = slots.format_decision(slots.arbitrate(self._conflict(), "existence"))
|
||||
self.assertIn("both are satisfied", served)
|
||||
|
||||
|
||||
class TestProvenEvidence(unittest.TestCase):
|
||||
"""What counts as proof that a claim is about content, not about a name."""
|
||||
|
||||
|
||||
Reference in new issue
Block a user