Files
chimera-gfx-Public/tests/test_retroarch_phase10b.py
T
Chimera GFX release export a6037502d7
phase0-ci / build-and-audit (push) Successful in 2m14s
Publish Chimera GFX source
2026-09-03 03:27:14 +02:00

217 lines
7.3 KiB
Python

#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-3.0-or-later
"""Host-only policy tests for the Phase-1.0B smoke candidate."""
from __future__ import annotations
import argparse
import importlib.util
from pathlib import Path
import sys
from types import ModuleType
from typing import Callable
def load_module(name: str, path: Path) -> ModuleType:
spec = importlib.util.spec_from_file_location(name, path)
if spec is None or spec.loader is None:
raise RuntimeError(f"could not load {path}")
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
return module
def require(condition: bool, message: str) -> None:
if not condition:
raise RuntimeError(message)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--root", type=Path, required=True)
args = parser.parse_args()
root = args.root.resolve()
validator = load_module(
"phase10b_validator", root / "tools/validate_retroarch_phase10b.py"
)
cases: list[tuple[str, Callable[[], None]]] = []
def case(name: str) -> Callable[[Callable[[], None]], Callable[[], None]]:
def register(function: Callable[[], None]) -> Callable[[], None]:
cases.append((name, function))
return function
return register
def profile(**changes: object) -> dict[str, object]:
value: dict[str, object] = {
"disabled_features": sorted(validator.DISABLED_FEATURES),
"persistent_writes_allowed": False,
"content_required": False,
"config_required": False,
"networking": False,
"autoload": False,
"automatic_retry": False,
}
value.update(changes)
return value
def route(**changes: object) -> dict[str, object]:
value: dict[str, object] = {
"write": False,
"create": False,
"append": False,
"truncate": False,
"rename": False,
"unlink": False,
"mkdir": False,
"retry": False,
}
value.update(changes)
return value
@case("01 profile is closed")
def _() -> None:
require(validator.profile_is_closed(profile()), "closed profile rejected")
@case("02 networking is rejected")
def _() -> None:
require(not validator.profile_is_closed(profile(networking=True)), "network accepted")
@case("03 content is not required")
def _() -> None:
require(not validator.profile_is_closed(profile(content_required=True)), "content accepted")
@case("04 config is not required")
def _() -> None:
require(not validator.profile_is_closed(profile(config_required=True)), "config accepted")
@case("05 automatic retry is rejected")
def _() -> None:
require(not validator.profile_is_closed(profile(automatic_retry=True)), "retry accepted")
@case("06 autoload is rejected")
def _() -> None:
require(not validator.profile_is_closed(profile(autoload=True)), "autoload accepted")
@case("07 write is rejected")
def _() -> None:
require(not validator.route_is_read_only(route(write=True)), "write accepted")
@case("08 create is rejected")
def _() -> None:
require(not validator.route_is_read_only(route(create=True)), "create accepted")
@case("09 append is rejected")
def _() -> None:
require(not validator.route_is_read_only(route(append=True)), "append accepted")
@case("10 truncate is rejected")
def _() -> None:
require(not validator.route_is_read_only(route(truncate=True)), "truncate accepted")
@case("11 rename is rejected")
def _() -> None:
require(not validator.route_is_read_only(route(rename=True)), "rename accepted")
@case("12 unlink is rejected")
def _() -> None:
require(not validator.route_is_read_only(route(unlink=True)), "unlink accepted")
@case("13 mkdir is rejected")
def _() -> None:
require(not validator.route_is_read_only(route(mkdir=True)), "mkdir accepted")
@case("14 route retry is rejected")
def _() -> None:
require(not validator.route_is_read_only(route(retry=True)), "route retry accepted")
@case("15 RX/R/RW headers pass")
def _() -> None:
headers = [
{"type": "LOAD", "flags": "R E"},
{"type": "LOAD", "flags": "R"},
{"type": "LOAD", "flags": "RW"},
]
require(validator.program_headers_are_wx_closed(headers), "W^X headers rejected")
@case("16 RWE header fails")
def _() -> None:
headers = [
{"type": "LOAD", "flags": "RWE"},
{"type": "LOAD", "flags": "RW"},
{"type": "LOAD", "flags": "RW"},
]
require(not validator.program_headers_are_wx_closed(headers), "RWE accepted")
@case("17 SceNet import fails")
def _() -> None:
require(not validator.imports_are_closed(["sceNetSocket"]), "SceNet accepted")
@case("18 GNM import fails")
def _() -> None:
require(not validator.imports_are_closed(["sceGnmSubmitCommandBuffers"]), "GNM accepted")
@case("19 module loading fails")
def _() -> None:
require(not validator.imports_are_closed(["sceKernelLoadStartModule"]), "module loader accepted")
@case("20 rumble fails")
def _() -> None:
require(not validator.imports_are_closed(["scePadSetVibration"]), "rumble accepted")
@case("21 lightbar fails")
def _() -> None:
require(not validator.imports_are_closed(["scePadSetLightBar"]), "lightbar accepted")
@case("22 socket import fails")
def _() -> None:
require(not validator.imports_are_closed(["socket"]), "socket accepted")
@case("23 expected imports pass")
def _() -> None:
require(
validator.imports_are_closed(
["sceVideoOutOpen", "scePadOpen", "sceAudioOutOpen", "_Exit"]
),
"expected imports rejected",
)
@case("24 runtime contract pins both deadlines")
def _() -> None:
text = (root / "docs/retroarch/phase-1.0b-smoke-candidate-design.md").read_text()
require("60,000 ms" in text and "3,600" in text, "deadlines absent")
@case("25 exit risk remains explicit")
def _() -> None:
text = (root / "docs/retroarch/phase-1.0b-runtime-and-exit-contract.md").read_text()
require("unproven" in text.lower() and "_Exit" in text, "exit risk hidden")
@case("26 host evidence is not hardware proof")
def _() -> None:
text = (root / "docs/retroarch/phase-1.0b-smoke-candidate-design.md").read_text()
require(
"no host test or static audit" in text.lower()
and "is hardware evidence" in text.lower(),
"hardware disclaimer absent",
)
failures: list[str] = []
for name, function in cases:
try:
function()
except Exception as error: # noqa: BLE001 - isolated reporting
failures.append(f"{name}: {error}")
if len(cases) != 26:
failures.append(f"expected 26 cases, found {len(cases)}")
if failures:
print("\n".join(failures), file=sys.stderr)
return 1
print("26 Phase 1.0B guardrails passed")
return 0
if __name__ == "__main__":
raise SystemExit(main())