This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user