mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-10 14:33:42 +08:00
123 lines
4.8 KiB
Python
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()
|