This commit is contained in:
@@ -0,0 +1,362 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Deterministic, networkless tests for the Phase-0.9C host protocol model."""
|
||||
|
||||
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(path: Path) -> ModuleType:
|
||||
spec = importlib.util.spec_from_file_location("phase09c_model", 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[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def require(condition: bool, message: str) -> None:
|
||||
if not condition:
|
||||
raise RuntimeError(message)
|
||||
|
||||
|
||||
def request(
|
||||
model: ModuleType,
|
||||
*,
|
||||
firmware_one: str | None = "9.60",
|
||||
firmware_two: str | None = "9.60",
|
||||
requested: int = 0b11,
|
||||
) -> object:
|
||||
return model.ResultRequest(
|
||||
execution_nonce=b"N" * 16,
|
||||
request_id=b"R" * 16,
|
||||
firmware_source_one=firmware_one,
|
||||
firmware_source_two=firmware_two,
|
||||
observer_version=1,
|
||||
requested_capabilities=requested,
|
||||
artifact_sha256=bytes.fromhex("11" * 32),
|
||||
deadline_monotonic_ns=10_000,
|
||||
)
|
||||
|
||||
|
||||
def mutate(record: bytes, offset: int, value: int) -> bytes:
|
||||
changed = bytearray(record)
|
||||
changed[offset] ^= value
|
||||
return bytes(changed)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
root = args.root.resolve()
|
||||
model = load_module(root / "tests/phase09c_feasibility_model.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
|
||||
|
||||
@case("startup callgraph")
|
||||
def _() -> None:
|
||||
require(model.validate_startup_model() == [], "startup model invalid")
|
||||
|
||||
@case("prohibited startup effects")
|
||||
def _() -> None:
|
||||
require(
|
||||
model.PROHIBITED_STARTUP_EFFECTS
|
||||
<= model.NORMAL_SDK_REACHABLE_PROHIBITED_EFFECTS,
|
||||
"normal SDK effects hidden",
|
||||
)
|
||||
|
||||
@case("freestanding dependency closure")
|
||||
def _() -> None:
|
||||
blockers = set(model.freestanding_blockers())
|
||||
require(
|
||||
{
|
||||
"stack_alignment",
|
||||
"callable_read_abi",
|
||||
"callable_monotonic_time_abi",
|
||||
"process_exit_abi",
|
||||
"return_cleanup",
|
||||
"bounded_result_copyout",
|
||||
}
|
||||
<= blockers,
|
||||
"freestanding blockers missing",
|
||||
)
|
||||
|
||||
@case("exit state machine")
|
||||
def _() -> None:
|
||||
for path in model.EXIT_PATHS:
|
||||
require(not model.exit_path_is_safe(path), f"{path} became safe")
|
||||
require("SIGKILL" in model.EXIT_PATHS["timeout"], "timeout kill hidden")
|
||||
|
||||
@case("output framing")
|
||||
def _() -> None:
|
||||
expected = request(model)
|
||||
record = model.build_result(
|
||||
expected, b"ok", observed_capabilities=0b11
|
||||
)
|
||||
require(len(record) == 4096, "record is not fixed-size")
|
||||
require(
|
||||
model.validate_result(record, expected, now_monotonic_ns=1)
|
||||
== "VALID_COMPLETE_RESULT",
|
||||
"valid result rejected",
|
||||
)
|
||||
|
||||
@case("maximum lengths")
|
||||
def _() -> None:
|
||||
expected = request(model, requested=0)
|
||||
record = model.build_result(expected, b"x" * model.MAX_BODY_SIZE)
|
||||
require(
|
||||
model.validate_result(record, expected, now_monotonic_ns=1)
|
||||
== "VALID_COMPLETE_RESULT",
|
||||
"maximum body rejected",
|
||||
)
|
||||
try:
|
||||
model.build_result(expected, b"x" * (model.MAX_BODY_SIZE + 1))
|
||||
except ValueError:
|
||||
return
|
||||
raise RuntimeError("oversized body accepted")
|
||||
|
||||
@case("truncation")
|
||||
def _() -> None:
|
||||
expected = request(model, requested=0)
|
||||
record = model.build_result(
|
||||
expected, b"x" * (model.MAX_BODY_SIZE + 1), truncate=True
|
||||
)
|
||||
require(
|
||||
model.validate_result(record, expected, now_monotonic_ns=1)
|
||||
== "BLOCKED_TRUNCATED",
|
||||
"truncation accepted",
|
||||
)
|
||||
|
||||
@case("stale nonce")
|
||||
def _() -> None:
|
||||
expected = request(model)
|
||||
record = model.build_result(
|
||||
expected, b"", observed_capabilities=0b11
|
||||
)
|
||||
stale = model.ResultRequest(
|
||||
execution_nonce=b"S" * 16,
|
||||
request_id=expected.request_id,
|
||||
firmware_source_one="9.60",
|
||||
firmware_source_two="9.60",
|
||||
observer_version=1,
|
||||
requested_capabilities=0b11,
|
||||
artifact_sha256=expected.artifact_sha256,
|
||||
deadline_monotonic_ns=10_000,
|
||||
)
|
||||
require(
|
||||
model.validate_result(record, stale, now_monotonic_ns=1)
|
||||
== "BLOCKED_STALE_NONCE",
|
||||
"stale nonce accepted",
|
||||
)
|
||||
|
||||
@case("duplicate result")
|
||||
def _() -> None:
|
||||
expected = request(model)
|
||||
record = model.build_result(
|
||||
expected, b"", observed_capabilities=0b11
|
||||
)
|
||||
consumer = model.ResultConsumer()
|
||||
require(
|
||||
consumer.consume(record, expected, now_monotonic_ns=1)
|
||||
== "VALID_COMPLETE_RESULT",
|
||||
"first result rejected",
|
||||
)
|
||||
require(
|
||||
consumer.consume(record, expected, now_monotonic_ns=1)
|
||||
== "BLOCKED_DUPLICATE_RESULT",
|
||||
"duplicate accepted",
|
||||
)
|
||||
|
||||
@case("result checksum failure")
|
||||
def _() -> None:
|
||||
expected = request(model)
|
||||
record = model.build_result(
|
||||
expected, b"body", observed_capabilities=0b11
|
||||
)
|
||||
require(
|
||||
model.validate_result(
|
||||
mutate(record, model.HEADER_SIZE, 1),
|
||||
expected,
|
||||
now_monotonic_ns=1,
|
||||
)
|
||||
== "BLOCKED_RESULT_CHECKSUM",
|
||||
"checksum corruption accepted",
|
||||
)
|
||||
|
||||
@case("timeout")
|
||||
def _() -> None:
|
||||
expected = request(model)
|
||||
record = model.build_result(
|
||||
expected, b"", observed_capabilities=0b11
|
||||
)
|
||||
require(
|
||||
model.validate_result(record, expected, now_monotonic_ns=10_001)
|
||||
== "BLOCKED_TIMEOUT",
|
||||
"expired result accepted",
|
||||
)
|
||||
|
||||
@case("incomplete completion marker")
|
||||
def _() -> None:
|
||||
expected = request(model)
|
||||
record = model.build_result(
|
||||
expected, b"", observed_capabilities=0b11, complete=False
|
||||
)
|
||||
require(
|
||||
model.validate_result(record, expected, now_monotonic_ns=1)
|
||||
== "BLOCKED_INCOMPLETE",
|
||||
"incomplete record accepted",
|
||||
)
|
||||
|
||||
@case("unsupported capability")
|
||||
def _() -> None:
|
||||
expected = request(model)
|
||||
record = model.build_result(
|
||||
expected,
|
||||
b"",
|
||||
observed_capabilities=0b01,
|
||||
unsupported_capabilities=0b10,
|
||||
)
|
||||
require(
|
||||
model.validate_result(record, expected, now_monotonic_ns=1)
|
||||
== "VALID_RECORD_WITH_UNSUPPORTED_CAPABILITIES",
|
||||
"explicit unsupported result lost",
|
||||
)
|
||||
|
||||
@case("error versus empty success")
|
||||
def _() -> None:
|
||||
expected = request(model, requested=0)
|
||||
error = model.build_result(
|
||||
expected, b"", status=model.STATUS_OBSERVER_ERROR
|
||||
)
|
||||
empty = model.build_result(expected, b"")
|
||||
require(
|
||||
model.validate_result(error, expected, now_monotonic_ns=1)
|
||||
== "BLOCKED_OBSERVER_FAILURE",
|
||||
"empty failure became success",
|
||||
)
|
||||
require(
|
||||
model.validate_result(empty, expected, now_monotonic_ns=1)
|
||||
== "VALID_COMPLETE_RESULT",
|
||||
"valid no-capability empty result rejected",
|
||||
)
|
||||
|
||||
@case("cleanup status")
|
||||
def _() -> None:
|
||||
expected = request(model)
|
||||
record = model.build_result(
|
||||
expected,
|
||||
b"",
|
||||
observed_capabilities=0b11,
|
||||
cleanup_status=model.CLEANUP_FAILED,
|
||||
)
|
||||
require(
|
||||
model.validate_result(record, expected, now_monotonic_ns=1)
|
||||
== "BLOCKED_CLEANUP_NOT_PROVEN",
|
||||
"failed cleanup accepted",
|
||||
)
|
||||
|
||||
@case("conflicting firmware sources")
|
||||
def _() -> None:
|
||||
expected = request(
|
||||
model, firmware_one="9.60", firmware_two="9.61"
|
||||
)
|
||||
record = model.build_result(
|
||||
expected, b"", observed_capabilities=0b11
|
||||
)
|
||||
require(
|
||||
model.validate_result(record, expected, now_monotonic_ns=1)
|
||||
== "BLOCKED_FIRMWARE_CONFLICT",
|
||||
"firmware conflict accepted",
|
||||
)
|
||||
|
||||
@case("absent firmware source two")
|
||||
def _() -> None:
|
||||
expected = request(model, firmware_two=None)
|
||||
record = model.build_result(
|
||||
expected, b"", observed_capabilities=0b11
|
||||
)
|
||||
require(
|
||||
model.validate_result(record, expected, now_monotonic_ns=1)
|
||||
== "BLOCKED_FIRMWARE_SOURCE_2_ABSENT",
|
||||
"missing firmware source accepted",
|
||||
)
|
||||
|
||||
@case("unknown protocol version")
|
||||
def _() -> None:
|
||||
expected = request(model)
|
||||
record = model.build_result(
|
||||
expected,
|
||||
b"",
|
||||
observed_capabilities=0b11,
|
||||
protocol_version=2,
|
||||
)
|
||||
require(
|
||||
model.validate_result(record, expected, now_monotonic_ns=1)
|
||||
== "BLOCKED_UNKNOWN_VERSION",
|
||||
"unknown version accepted",
|
||||
)
|
||||
|
||||
@case("capability completeness")
|
||||
def _() -> None:
|
||||
expected = request(model)
|
||||
record = model.build_result(
|
||||
expected, b"", observed_capabilities=0b01
|
||||
)
|
||||
require(
|
||||
model.validate_result(record, expected, now_monotonic_ns=1)
|
||||
== "BLOCKED_INCOMPLETE_CAPABILITY_RESULT",
|
||||
"partial capability bitmap accepted",
|
||||
)
|
||||
|
||||
@case("side-effect classification")
|
||||
def _() -> None:
|
||||
effects = model.classify_side_effects("filesystem_content_hash")
|
||||
require("semantic_readonly" in effects, "semantic read missing")
|
||||
require("atime_effect_possible" in effects, "atime risk hidden")
|
||||
require("audit_effect_possible" in effects, "audit risk hidden")
|
||||
require("cache_effect_possible" in effects, "cache risk hidden")
|
||||
require("object_race_possible" in effects, "race risk hidden")
|
||||
require(
|
||||
not model.is_proven_side_effect_free("filesystem_content_hash"),
|
||||
"read was promoted to side-effect-free",
|
||||
)
|
||||
|
||||
@case("output architecture ordering")
|
||||
def _() -> None:
|
||||
require(
|
||||
list(model.OUTPUT_ARCHITECTURES)
|
||||
== [
|
||||
"D1_CALLER_OWNED_BOUNDED_BUFFER",
|
||||
"D2_EXISTING_REQUEST_RESPONSE",
|
||||
"D3_LOADER_OWNED_STATUS_RECORD",
|
||||
"D4_PROCESS_EXIT_STATUS",
|
||||
],
|
||||
"output architectures reordered",
|
||||
)
|
||||
|
||||
for name, function in cases:
|
||||
try:
|
||||
function()
|
||||
except Exception as error:
|
||||
raise RuntimeError(f"Phase-0.9C protocol case failed: {name}: {error}") from error
|
||||
|
||||
print(f"Phase-0.9C protocol host tests: {len(cases)}/{len(cases)} PASS")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user