Files
StarPilot/openpilot/starpilot/models/tests/test_persistent_runtime_cache.py
T
2026-11-09 15:45:03 +00:00

123 lines
4.8 KiB
Python

"""Persistent startup cache selection; not GPU or road qualification."""
import copy
import stat
from types import SimpleNamespace
from pathlib import Path, PurePosixPath
from unittest.mock import patch
import pytest
from openpilot.starpilot.models.runtime_bridge import runtime
@pytest.mark.parametrize("role", ["driving", "monitoring"])
def test_inference_cache_survives_reboot_without_changing_role_isolation(role):
selected = {"role": role}
with (patch("openpilot.common.hardware.AGNOS", True),
patch.object(runtime, "_selection", None),
patch.object(runtime, "_verify_policy") as verify,
patch.object(runtime, "_inference_cache_root", return_value=Path("/data/starpilot-model-runtime")),
patch.object(runtime, "activate", return_value=selected) as activate):
assert runtime.prepare(role) is selected
verify.assert_called_once()
activate.assert_called_once_with(runtime.BUNDLE, runtime.MANIFEST_SHA256,
runtime.LEGACY_REVISION, role, Path("/data/starpilot-model-runtime"))
assert runtime.prepare(role) is selected
assert activate.call_count == 1
def test_host_does_not_create_device_cache():
with (patch("openpilot.common.hardware.AGNOS", False),
patch.object(runtime, "_selection", None),
patch.object(runtime, "activate", side_effect=AssertionError("must not touch device cache"))):
assert runtime.prepare("driving") is None
class CachePath(PurePosixPath):
entries = {}
def lstat(self):
try:
uid, mode = self.entries[str(self)]
except KeyError:
raise FileNotFoundError(str(self)) from None
if isinstance(uid, BaseException):
raise uid
return SimpleNamespace(st_uid=uid, st_mode=mode)
@pytest.fixture
def cache_metadata():
root = CachePath("/data/starpilot-model-runtime")
revision = root / runtime.LEGACY_REVISION
CachePath.entries = {"/": (0, stat.S_IFDIR | 0o755), "/data": (1000, stat.S_IFDIR | 0o755)}
for role in ("driving", "monitoring"):
leaf = revision / role / runtime.MANIFEST_SHA256
for path in (root, revision, revision / role, leaf, leaf / "xdg"):
CachePath.entries[str(path)] = (1000, stat.S_IFDIR | 0o700)
def route(role="monitoring", uid=1000):
with patch.object(runtime, "Path", CachePath), patch.object(runtime.os, "geteuid", return_value=uid):
return runtime._inference_cache_root(role)
yield root, revision, route
CachePath.entries = {}
@pytest.mark.parametrize("role", ["driving", "monitoring"])
def test_owned_hot_cache_keeps_canonical_path_without_writes(cache_metadata, role):
root, _, route = cache_metadata
before = copy.deepcopy(CachePath.entries)
assert route(role) == root
assert CachePath.entries == before
def test_foreign_monitoring_cache_uses_only_fixed_uid_sibling(cache_metadata):
root, revision, route = cache_metadata
CachePath.entries[str(revision / "monitoring")] = (0, stat.S_IFDIR | 0o700)
before = copy.deepcopy(CachePath.entries)
assert route() == root.with_name(root.name + "-uid1000")
assert route("driving") == root
assert CachePath.entries == before
def test_root_mismatch_requires_explicit_private_cache(cache_metadata):
_, _, route = cache_metadata
before = copy.deepcopy(CachePath.entries)
with pytest.raises(ValueError, match="explicit private"):
route(uid=0)
assert CachePath.entries == before
@pytest.mark.parametrize("uid,mode", [(2000, 0o755), (0, 0o777), (1000, 0o775)])
def test_uid_fallback_rejects_untrusted_parent(cache_metadata, uid, mode):
_, revision, route = cache_metadata
CachePath.entries[str(revision / "monitoring")] = (0, stat.S_IFDIR | 0o700)
CachePath.entries["/data"] = (uid, stat.S_IFDIR | mode)
with pytest.raises(ValueError, match="Untrusted parent"):
route()
@pytest.mark.parametrize("mode", [stat.S_IFLNK | 0o777, stat.S_IFREG | 0o600])
def test_cache_route_rejects_symlink_or_nondirectory(cache_metadata, mode):
_, revision, route = cache_metadata
CachePath.entries[str(revision / "monitoring")] = (1000, mode)
with pytest.raises(ValueError, match="Invalid runtime cache directory"):
route()
@pytest.mark.parametrize("error", [PermissionError("denied"), OSError("unknown metadata")])
def test_cache_metadata_errors_propagate_without_fallback(cache_metadata, error):
root, _, route = cache_metadata
CachePath.entries[str(root)] = (error, 0)
with pytest.raises(type(error)):
route()
@pytest.mark.parametrize("uid,mode", [(0, stat.S_IFDIR | 0o700), (1000, stat.S_IFDIR | 0o777),
(1000, stat.S_IFLNK | 0o777)])
def test_existing_uid_sibling_must_be_private_and_owned(cache_metadata, uid, mode):
root, revision, route = cache_metadata
CachePath.entries[str(revision / "monitoring")] = (0, stat.S_IFDIR | 0o700)
CachePath.entries[str(root.with_name(root.name + "-uid1000"))] = (uid, mode)
with pytest.raises(ValueError, match="Invalid per-user"):
route()