Files
2026-10-23 20:48:09 -05:00

730 lines
35 KiB
Python

import hashlib
import io
import json
import multiprocessing
from pathlib import Path
import tempfile
import threading
import time
from types import SimpleNamespace
import unittest
from unittest.mock import patch
from openpilot.starpilot.models.catalog import ARTIFACT_ABI, BUNDLED_CURRENT, CATALOG_PATH, DEFAULT_SMALL
from openpilot.starpilot.models.manager import (
ModelError, ModelManager, atomic_json, catalog, preferences, randomize_next_start, resolve_runtime, validate_manifest,
)
def run_download_owner(root, manifest, data, entered, release, result):
class BlockedPart(io.BytesIO):
def read(self, size=-1):
entered.set()
if not release.wait(5):
raise OSError('fixture stalled')
return super().read(size)
def opener(url, timeout):
if '/manifests/' in url:
return io.BytesIO(json.dumps(manifest).encode())
if url.endswith('chunk01of02'):
return io.BytesIO(data[:10])
return BlockedPart(data[10:])
manager = ModelManager(root=Path(root), parked=lambda: True, gpu_present=lambda: False, opener=opener)
with patch('openpilot.starpilot.models.manager.shutil.disk_usage', return_value=SimpleNamespace(free=1024**3)):
try:
manager.action('download', {'model': 'gwm8223'})
manager.worker.join(6)
result.put(manager.snapshot())
finally:
manager.close()
class ModelManagerTest(unittest.TestCase):
def setUp(self):
disk_usage = patch('openpilot.starpilot.models.manager.shutil.disk_usage', return_value=SimpleNamespace(free=1024**3))
self.disk_usage = disk_usage.start()
self.addCleanup(disk_usage.stop)
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name)
shipped = patch('openpilot.starpilot.models.manager.SHIPPED_MODELS', self.root / 'shipped')
shipped.start()
self.addCleanup(shipped.stop)
self.parked = True
self.data = b'a verified compiled artifact'
from openpilot.common.file_chunker import package_file
source = self.root / 'shipped-source.pkl'
source.write_bytes(self.data)
package_file(source, self.root / 'shipped/rdf43_driving_tinygrad.pkl')
source.unlink()
for attribute, value in (('DEFAULT_SMALL_SHA256', hashlib.sha256(self.data).hexdigest()), ('DEFAULT_SMALL_SIZE', len(self.data))):
identity = patch('openpilot.starpilot.models.manager.' + attribute, value)
identity.start()
self.addCleanup(identity.stop)
self.manifest = json.loads(CATALOG_PATH.read_text())
self.row = next(x for x in self.manifest['models'] if x['id'] == 'gwm8223')
self.row.update(artifact_format=ARTIFACT_ABI, artifact_sha256=hashlib.sha256(self.data).hexdigest(),
artifact_size=len(self.data), artifact_chunk_count=2)
self.urls = []
self.bad = False
def request(url, timeout):
self.assertTrue(url.startswith('https://huggingface.co/buckets/StarPilot-Driving/StarPilot-Resources/resolve/'))
self.urls.append(url)
if '/manifests/' in url:
return io.BytesIO(json.dumps(self.manifest).encode())
if url.endswith('chunk01of02'):
return io.BytesIO(self.data[:10])
if url.endswith('chunk02of02'):
return io.BytesIO(b'corrupt' if self.bad else self.data[10:])
raise OSError('missing fixture')
self.manager = ModelManager(root=self.root, parked=lambda: self.parked, gpu_present=lambda: True, opener=request)
self.addCleanup(self.manager.close)
def download(self):
self.manager.action('download', {'model': 'gwm8223'})
self.manager.worker.join(3)
self.assertFalse(self.manager.worker.is_alive())
def model_status(self):
return next(x for x in self.manager.snapshot()['models'] if x['value'] == 'gwm8223')
def wait_for_check(self):
deadline = time.monotonic() + 3
while time.monotonic() < deadline:
row = self.model_status()
if not row['checking']:
return row
time.sleep(0.01)
self.fail('Artifact verification did not finish')
def test_selected_gpu_status_verifies_download_without_bundled_model_or_job_writes(self):
from openpilot.starpilot.models.manager import artifact_path
mid = 'cinquev3'
entry = {'artifact_sha256': hashlib.sha256(self.data).hexdigest(), 'artifact_size': len(self.data)}
path = artifact_path(mid, self.root)
path.parent.mkdir(parents=True)
path.write_bytes(self.data)
atomic_json(self.root / 'preferences.json', {'big': mid})
with patch('openpilot.starpilot.models.manager.catalog', return_value={mid: entry}), \
patch.object(self.manager, '_job_status', side_effect=AssertionError('Read-only status must not change jobs')):
assert self.manager.selected_gpu_status() == (False, True)
deadline = time.monotonic() + 3
while time.monotonic() < deadline:
state = self.manager.selected_gpu_status()
if state != (False, True):
break
time.sleep(.01)
assert state == (True, False)
path.write_bytes(b'x' * len(self.data))
assert self.manager.selected_gpu_status() == (False, True)
deadline = time.monotonic() + 3
while time.monotonic() < deadline:
state = self.manager.selected_gpu_status()
if state != (False, True):
break
time.sleep(.01)
assert state == (False, False)
atomic_json(self.root / 'preferences.json', {'big': ''})
assert self.manager.selected_gpu_status() == (False, False)
atomic_json(self.root / 'preferences.json', {'big': mid})
path.unlink()
assert self.manager.selected_gpu_status() == (False, False)
assert not (self.root / '.download-job.json').exists()
def test_chunk_download_verifies_and_selects_next_start(self):
self.download()
self.assertEqual(self.manager.progress, 'Downloaded!')
self.assertEqual(preferences(self.root)['small'], DEFAULT_SMALL)
self.manager.action('active', {'profile': 'small', 'model': 'gwm8223'})
selected = resolve_runtime(True, root=self.root)
self.assertEqual(selected.small_id, 'gwm8223')
self.assertEqual(selected.small_path.read_bytes(), self.data)
self.assertTrue(all('/v26/' in u or 'model_names_v26.json' in u for u in self.urls))
def test_corruption_never_publishes_or_selects(self):
self.bad = True
self.download()
self.assertIn('checksum', self.manager.progress)
self.assertFalse(list(self.root.rglob('*.pkl')))
with self.assertRaises(ModelError):
self.manager.action('active', {'profile': 'small', 'model': 'gwm8223'})
def test_insufficient_storage_preserves_installed_model_and_selection(self):
self.download()
self.manager.action('active', {'profile': 'small', 'model': 'gwm8223'})
other = next(row for row in self.manifest['models'] if row['id'] == 'pop223')
other.update({key: self.row[key] for key in ('artifact_format', 'artifact_sha256', 'artifact_size', 'artifact_chunk_count')})
self.urls.clear()
self.disk_usage.return_value = SimpleNamespace(free=256 * 1024 * 1024 + len(self.data) - 1)
self.manager.action('download', {'model': 'pop223'})
self.manager.worker.join(3)
self.assertFalse(self.manager.worker.is_alive())
self.assertEqual(self.manager.progress, 'Not enough storage for this model')
self.assertTrue(self.urls)
self.assertTrue(all('/manifests/' in url for url in self.urls))
self.assertEqual(preferences(self.root)['small'], 'gwm8223')
self.assertEqual(resolve_runtime(True, root=self.root).small_path.read_bytes(), self.data)
self.assertFalse(list((self.root / 'pop223').glob('.download-*')))
def test_changed_installed_bytes_fall_back_and_ui_rechecks(self):
self.download()
self.manager.action('active', {'profile': 'small', 'model': 'gwm8223'})
self.assertTrue(self.wait_for_check()['installed'])
(self.root/'gwm8223/gwm8223_driving_tinygrad.pkl').write_bytes(b'x' * len(self.data))
self.assertEqual(resolve_runtime(True, root=self.root).small_id, DEFAULT_SMALL)
self.assertFalse(self.model_status()['installed'])
self.assertFalse(self.wait_for_check()['installed'])
def test_snapshot_does_not_wait_for_artifact_hash(self):
self.download()
self.manager.checked.clear()
entered, release = threading.Event(), threading.Event()
from openpilot.starpilot.models import manager as manager_module
original = manager_module.verified_artifact
def slow_verify(*args, **kwargs):
entered.set()
self.assertTrue(release.wait(3))
return original(*args, **kwargs)
with patch.object(manager_module, 'verified_artifact', side_effect=slow_verify):
started = time.monotonic()
first = self.model_status()
self.assertLess(time.monotonic() - started, 0.5)
self.assertTrue(first['checking'])
self.assertFalse(first['installed'])
self.assertFalse(first['selectable'])
self.assertEqual(first['unavailableReason'], 'Checking artifact')
self.assertTrue(entered.wait(1))
started = time.monotonic()
second = self.model_status()
self.assertLess(time.monotonic() - started, 0.5)
self.assertTrue(second['checking'])
release.set()
self.assertTrue(self.wait_for_check()['installed'])
def test_replaced_artifact_invalidates_cached_validity(self):
self.download()
self.assertTrue(self.wait_for_check()['installed'])
path = self.root/'gwm8223/gwm8223_driving_tinygrad.pkl'
replacement = path.with_suffix('.replacement')
replacement.write_bytes(b'x' * len(self.data))
replacement.replace(path)
row = self.model_status()
self.assertFalse(row['installed'])
self.assertFalse(row['selectable'])
self.assertFalse(self.wait_for_check()['installed'])
replacement.write_bytes(self.data)
replacement.replace(path)
self.assertFalse(self.model_status()['installed'])
self.assertTrue(self.wait_for_check()['installed'])
def test_offroad_authority_rechecked_after_validation(self):
self.download()
samples = iter([True, False])
self.manager.parked = lambda: next(samples)
with self.assertRaises(ModelError):
self.manager.action('active', {'profile': 'small', 'model': 'gwm8223'})
self.assertEqual(preferences(self.root)['small'], DEFAULT_SMALL)
def test_hardware_profiles_and_selected_delete(self):
self.download()
with self.assertRaises(ModelError):
self.manager.action('active', {'profile': 'big', 'model': 'gwm8223'})
self.manager.action('active', {'profile': 'small', 'model': 'gwm8223'})
with self.assertRaises(ModelError):
self.manager.action('delete', {'model': 'gwm8223'})
self.manager.action('active', {'profile': 'big', 'model': ''})
self.assertFalse(resolve_runtime(True, root=self.root).allow_big)
def test_manifest_rejects_old_generation_and_hardware_change(self):
self.manifest['generation'] = 'v25'
with self.assertRaises(ModelError):
validate_manifest(self.manifest)
self.manifest['generation'] = 'v26'
self.row['uses_external_gpu'] = True
with self.assertRaises(ModelError):
validate_manifest(self.manifest)
def runtime_artifact(self, revision=2):
fields = ('artifact_format', 'artifact_sha256', 'artifact_size', 'artifact_chunk_count')
artifact = {name: self.row.pop(name) for name in fields}
self.row['runtime_artifacts'] = [dict(artifact, min_runner_revision=revision)]
return artifact
def test_runner_revision_gates_download_and_runtime_selection(self):
self.runtime_artifact()
self.download()
self.manager.action('active', {'profile': 'small', 'model': 'gwm8223'})
self.assertEqual(resolve_runtime(True, root=self.root).small_id, 'gwm8223')
with patch('openpilot.starpilot.models.manager.MODEL_RUNNER_REVISION', 1):
self.assertNotIn('artifact_sha256', validate_manifest(self.manifest)['gwm8223'])
self.assertEqual(resolve_runtime(True, root=self.root).small_id, DEFAULT_SMALL)
self.assertFalse(self.model_status()['downloadAvailable'])
self.assertEqual(resolve_runtime(True, root=self.root).small_id, 'gwm8223')
def test_future_runner_variant_preserves_existing_artifact(self):
previous = self.runtime_artifact(3)
self.assertNotIn('artifact_sha256', validate_manifest(self.manifest)['gwm8223'])
self.row.update(previous)
self.assertEqual(validate_manifest(self.manifest)['gwm8223']['artifact_sha256'], previous['artifact_sha256'])
self.download()
self.assertEqual(self.manager.progress, 'Downloaded!')
def test_runtime_variant_rejects_invalid_contract(self):
self.runtime_artifact()
valid = dict(self.row['runtime_artifacts'][0])
for field, value in [('min_runner_revision', True), ('min_runner_revision', 0),
('artifact_sha256', 'bad'), ('artifact_size', 0), ('unknown_feature', True)]:
self.row['runtime_artifacts'] = [dict(valid, **{field: value})]
with self.subTest(field=field, value=value), self.assertRaises(ModelError):
validate_manifest(self.manifest)
self.row['runtime_artifacts'] = [valid, valid]
with self.assertRaises(ModelError):
validate_manifest(self.manifest)
def test_runtime_variant_keeps_download_checksum_validation(self):
self.runtime_artifact()
self.bad = True
self.download()
self.assertIn('checksum', self.manager.progress)
self.assertFalse(list(self.root.rglob('*.pkl')))
def test_missing_big_download_uses_small_and_leaves_request(self):
atomic_json(self.root/'preferences.json', {'small': BUNDLED_CURRENT, 'big': 'cinquev3'})
self.assertFalse(resolve_runtime(True, root=self.root).allow_big)
self.assertEqual(preferences(self.root)['big'], 'cinquev3')
def test_preferences_and_unknown_payloads(self):
self.manager.action('preferences', {'userFavorites': ['gwm8223'], 'sortMode': 'series'})
self.assertEqual(preferences(self.root)['userFavorites'], ['gwm8223'])
for action, payload in [('active', {'profile': 'small', 'model': []}), ('download', {'model': '../bad'}),
('delete', {'model': BUNDLED_CURRENT}), ('preferences', {'userFavorites': ['unknown']})]:
with self.subTest(action=action), self.assertRaises(ModelError):
self.manager.action(action, payload)
def other_manager(self, opener=None):
manager = ModelManager(root=self.root, parked=lambda: self.parked, gpu_present=lambda: True,
opener=opener or self.manager.opener)
self.addCleanup(manager.close)
return manager
def test_another_manager_observes_progress_and_cancels_owner_job(self):
entered, release = threading.Event(), threading.Event()
original = self.manager.opener
class BlockedPart(io.BytesIO):
def read(inner, size=-1):
entered.set()
if not release.wait(3):
raise OSError('fixture stalled')
return super().read(size)
def opener(url, timeout):
return BlockedPart(self.data[10:]) if url.endswith('chunk02of02') else original(url, timeout)
self.manager.opener = opener
observer = self.other_manager()
self.manager.action('download', {'model': 'gwm8223'})
try:
self.assertTrue(entered.wait(1))
view = observer.snapshot()
self.assertTrue(view['downloading'])
self.assertEqual(view['modelToDownload'], 'gwm8223')
self.assertIn('%', view['progress'])
self.assertEqual(view['jobId'], self.manager.job_id)
for action, payload in [('download', {'model': 'gwm8223'}), ('refresh_manifest', {}),
('active', {'profile': 'small', 'model': BUNDLED_CURRENT}), ('delete', {'model': 'gwm8223'})]:
with self.subTest(action=action), self.assertRaises(ModelError):
observer.action(action, payload)
observer.action('cancel', {'jobId': view['jobId']})
self.assertTrue(observer.snapshot()['cancelRequested'])
finally:
release.set()
self.manager.worker.join(3)
self.assertFalse(self.manager.worker.is_alive())
self.assertEqual(observer.snapshot()['progress'], 'Download cancelled')
self.assertFalse(observer.snapshot()['downloading'])
self.assertFalse(list(self.root.rglob('*.pkl')))
self.assertFalse(list((self.root / 'gwm8223').glob('.download-*')))
observer.action('refresh_manifest', {})
observer.worker.join(3)
self.assertEqual(observer.snapshot()['progress'], 'Catalog updated')
self.assertNotEqual(observer.snapshot()['jobId'], view['jobId'])
with self.assertRaises(ModelError):
observer.action('cancel', {'jobId': view['jobId']})
def test_another_process_observes_and_cancels_download(self):
context = multiprocessing.get_context('spawn')
entered, release, result = context.Event(), context.Event(), context.Queue()
owner = context.Process(target=run_download_owner,
args=(str(self.root), self.manifest, self.data, entered, release, result))
owner.start()
try:
self.assertTrue(entered.wait(5))
view = self.manager.snapshot()
self.assertTrue(view['downloading'])
self.assertEqual(view['modelToDownload'], 'gwm8223')
self.assertIn('%', view['progress'])
with self.assertRaises(ModelError):
self.manager.action('refresh_manifest', {})
self.manager.action('cancel', {'jobId': view['jobId']})
release.set()
terminal = result.get(timeout=5)
owner.join(5)
self.assertEqual(owner.exitcode, 0)
self.assertFalse(terminal['downloading'])
self.assertEqual(terminal['progress'], 'Download cancelled')
self.assertFalse(list(self.root.rglob('*.pkl')))
self.assertFalse(self.manager.snapshot()['downloading'])
finally:
release.set()
owner.join(5)
if owner.is_alive():
owner.terminate()
owner.join(5)
result.close()
def test_simultaneous_managers_accept_exactly_one_job(self):
entered, release = threading.Event(), threading.Event()
original = self.manager.opener
def opener(url, timeout):
entered.set()
if not release.wait(3):
raise OSError('fixture stalled')
return original(url, timeout)
self.manager.opener = opener
other = self.other_manager(opener)
barrier = threading.Barrier(3)
accepted, rejected = [], []
def start(manager):
barrier.wait()
try:
manager.action('download', {'model': 'gwm8223'})
accepted.append(manager)
except ModelError as error:
rejected.append(str(error))
callers = [threading.Thread(target=start, args=(manager,)) for manager in (self.manager, other)]
for caller in callers:
caller.start()
barrier.wait()
try:
for caller in callers:
caller.join(1)
self.assertFalse(caller.is_alive())
self.assertTrue(entered.wait(1))
self.assertEqual(len(accepted), 1)
self.assertEqual(len(rejected), 1)
self.assertIn('Another model manager', rejected[0])
self.assertEqual(self.manager.snapshot()['jobId'], other.snapshot()['jobId'])
self.assertTrue(other.snapshot()['downloading'])
finally:
release.set()
for manager in (self.manager, other):
if manager.worker is not None:
manager.worker.join(3)
self.assertEqual(other.snapshot()['progress'], 'Downloaded!')
self.assertFalse(other.snapshot()['downloading'])
def test_closing_observer_does_not_cancel_other_manager_job(self):
entered, release = threading.Event(), threading.Event()
original = self.manager.opener
def opener(url, timeout):
if '/manifests/' in url:
entered.set()
if not release.wait(3):
raise OSError('fixture stalled')
return original(url, timeout)
self.manager.opener = opener
observer = self.other_manager()
self.manager.action('download', {'model': 'gwm8223'})
try:
self.assertTrue(entered.wait(1))
observer.close()
self.assertFalse(self.manager.snapshot()['cancelRequested'])
self.assertFalse(self.manager.cancelled.is_set())
finally:
release.set()
self.manager.worker.join(3)
self.assertEqual(self.manager.progress, 'Downloaded!')
self.assertTrue(self.wait_for_check()['installed'])
def test_stale_journal_and_completed_job_do_not_block_new_downloads(self):
observer = self.other_manager()
atomic_json(self.root / '.download-job.json', {
'schemaVersion': 1, 'jobId': 'a' * 32, 'state': 'running', 'model': 'gwm8223',
'downloadAll': True, 'progress': 'Old download', 'cancelRequested': True,
})
view = observer.snapshot()
self.assertFalse(view['downloading'])
self.assertEqual(view['progress'], 'Previous model download was interrupted')
self.assertEqual(observer.action('cancel', {})['message'], 'No model download is running')
observer.action('download', {'model': 'gwm8223'})
observer.worker.join(3)
self.assertEqual(observer.snapshot()['progress'], 'Downloaded!')
self.assertFalse(observer.snapshot()['downloading'])
observer.action('refresh_manifest', {})
observer.worker.join(3)
self.assertEqual(observer.snapshot()['progress'], 'Catalog updated')
self.assertFalse(observer.snapshot()['downloading'])
def install_fixture(self, mid):
row = next(x for x in self.manifest['models'] if x['id'] == mid)
row.update(artifact_format=ARTIFACT_ABI, artifact_sha256=hashlib.sha256(self.data).hexdigest(),
artifact_size=len(self.data), artifact_chunk_count=0)
path = self.root / mid / f'{mid}_driving_tinygrad.pkl'
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(self.data)
atomic_json(self.root / 'catalog.json', self.manifest)
return path
def test_randomizer_off_is_read_only_and_opt_in(self):
self.install_fixture('gwm8223')
self.assertEqual(resolve_runtime(False, root=self.root, randomize=True).small_id, DEFAULT_SMALL)
self.assertFalse((self.root / 'preferences.json').exists())
self.manager.action('active', {'profile': 'small', 'model': BUNDLED_CURRENT})
self.manager.action('preferences', {'randomizer': True})
self.assertEqual(resolve_runtime(False, root=self.root).small_id, DEFAULT_SMALL)
self.assertEqual(resolve_runtime(False, root=self.root, randomize=True).small_id, 'gwm8223')
self.assertEqual(preferences(self.root)['small'], 'gwm8223')
def test_randomizer_avoids_repeat_ignores_favorites_and_respects_exclusions(self):
self.install_fixture('gwm8223')
self.install_fixture('rdf43')
self.manager.action('preferences', {'randomizer': True, 'userFavorites': ['gwm8223'],
'blacklistedModels': ['gwm8223', BUNDLED_CURRENT]})
self.assertEqual(randomize_next_start(False, root=self.root)['small'], 'rdf43')
self.assertEqual(randomize_next_start(False, root=self.root)['small'], 'rdf43')
self.manager.action('preferences', {'blacklistedModels': []})
seen = []
def choose(options):
seen.extend(options)
return options[0]
randomize_next_start(False, root=self.root, chooser=choose)
self.assertNotIn('rdf43', seen)
self.assertIn('gwm8223', seen)
self.assertEqual(seen, ['gwm8223'])
def test_randomizer_no_chestnut_changes_only_small(self):
self.install_fixture('gwm8223')
self.install_fixture('cinquev3')
atomic_json(self.root / 'preferences.json', {'randomizer': True, 'big': 'cinquev3', 'small': BUNDLED_CURRENT})
selected = resolve_runtime(False, root=self.root, randomize=True)
self.assertEqual(selected.small_id, 'gwm8223')
self.assertFalse(selected.allow_big)
self.assertIsNone(selected.big_path)
self.assertEqual(preferences(self.root)['big'], 'cinquev3')
selected = resolve_runtime(True, root=self.root, randomize=True)
self.assertEqual(selected.big_id, 'cinquev3')
self.assertEqual(selected.big_path.read_bytes(), self.data)
self.assertTrue(selected.allow_big)
def test_randomizer_empty_corrupt_pool_has_safe_fallback(self):
path = self.install_fixture('gwm8223')
path.write_bytes(b'x' * len(self.data))
atomic_json(self.root / 'preferences.json', {'randomizer': True, 'small': 'gwm8223',
'blacklistedModels': [BUNDLED_CURRENT]})
self.assertEqual(resolve_runtime(False, root=self.root, randomize=True).small_id, DEFAULT_SMALL)
self.manager.action('preferences', {'blacklistedModels': []})
atomic_json(self.root / 'preferences.json', {'randomizer': True, 'big': 'cinquev3', 'small': 'gwm8223'})
selected = resolve_runtime(True, root=self.root, randomize=True)
self.assertFalse(selected.allow_big)
self.assertEqual(selected.small_id, DEFAULT_SMALL)
self.assertEqual(preferences(self.root)['big'], '')
def test_selection_preferences_require_parked_and_strict_values(self):
for payload in ({'randomizer': 1}, {'blacklistedModels': ['unknown']}, {'blacklistedModels': 'gwm8223'}):
with self.subTest(payload=payload), self.assertRaises(ModelError):
self.manager.action('preferences', payload)
self.parked = False
for payload in ({'randomizer': True}, {'blacklistedModels': ['gwm8223']}):
with self.subTest(payload=payload), self.assertRaises(ModelError):
self.manager.action('preferences', payload)
self.manager.action('preferences', {'userFavorites': ['gwm8223']})
self.parked = True
self.manager.action('preferences', {'randomizer': True})
with self.assertRaises(ModelError):
self.manager.action('active', {'profile': 'small', 'model': BUNDLED_CURRENT})
self.assertTrue(self.manager.snapshot()['randomizer'])
self.manager.parked = iter([True, False]).__next__
with self.assertRaises(ModelError):
self.manager.action('preferences', {'randomizer': False})
self.assertTrue(preferences(self.root)['randomizer'])
def test_remote_new_model_download_selection_restart_and_status(self):
from openpilot.starpilot.models.catalog import BY_ID, model_entries, resolve_selection
from openpilot.starpilot.models.receipt import read_receipt, write_receipt
from openpilot.starpilot.models.status import ModelHealth, ModelLoad, ModelOutput, ModelProcess, ModelVariant, project_status
from openpilot.starpilot.ui.models_state import home_model_label
row = dict(self.row, id="new_remote_model", name="Remote Model", version="v16", uses_external_gpu=True)
assert isinstance(row["id"], str) and isinstance(row["artifact_sha256"], str)
small_row = dict(row, id="new_remote_small", name="Remote Small", uses_external_gpu=False)
self.manifest["models"].extend((row, small_row))
self.manager.action("refresh_manifest", {})
self.manager.worker.join(3)
self.assertFalse(self.manager.worker.is_alive())
self.assertNotIn(row["id"], BY_ID)
self.manager.action("download", {"model": row["id"]})
self.manager.worker.join(3)
self.assertFalse(self.manager.worker.is_alive())
self.manager.action("download", {"model": small_row["id"]})
self.manager.worker.join(3)
self.assertFalse(self.manager.worker.is_alive())
self.manager.action("active", {"profile": "small", "model": small_row["id"]})
with self.assertRaises(ModelError):
self.manager.action("active", {"profile": "small", "model": row["id"]})
self.manager.action("active", {"profile": "big", "model": row["id"]})
self.manager.action("preferences", {"userFavorites": [row["id"]], "blacklistedModels": []})
restarted = self.other_manager()
self.assertEqual(preferences(self.root)["big"], row["id"])
self.assertEqual(preferences(self.root)["userFavorites"], [row["id"]])
selection = resolve_runtime(True, root=self.root)
self.assertTrue(selection.allow_big)
self.assertEqual((selection.small_id, selection.small_version), (small_row["id"], "v16"))
self.assertEqual((selection.big_id, selection.big_version, selection.big_sha256),
(row["id"], "v16", row["artifact_sha256"]))
self.assertTrue(selection.big_path.is_file())
with patch("openpilot.starpilot.models.manager.ROOT", self.root):
self.assertEqual(resolve_selection(row["id"]).name, row["name"])
load = ModelLoad(421, 5000, 1_100_000_000, row["id"], ModelVariant.CHESTNUT, row["artifact_sha256"])
receipt = self.root / "receipt" / "load.json"
write_receipt(load, receipt)
self.assertEqual(read_receipt(receipt), load)
wrong_hardware = ModelLoad(421, 5000, 1_100_000_000, row["id"], ModelVariant.SMALL, row["artifact_sha256"])
with self.assertRaises(ValueError):
write_receipt(wrong_hardware, receipt)
output = ModelOutput(1_180_000_000, True, 1_185_000_000, True, True, True)
status = project_status(row["id"], ModelProcess(421, 5000, True), load, output, 1_200_000_000)
self.assertEqual(status.health, ModelHealth.ACTIVE)
self.assertEqual(home_model_label(status), row["name"])
self.assertIn(row["id"], model_entries())
restarted.opener = lambda *args, **kwargs: (_ for _ in ()).throw(OSError("offline"))
with self.assertRaises(OSError):
restarted._refresh()
self.assertEqual(preferences(self.root)["big"], row["id"])
self.assertIn(row["id"], catalog(self.root))
self.assertNotIn(row["id"], BY_ID)
self.manager.action("preferences", {"randomizer": True})
chosen = randomize_next_start(True, root=self.root, chooser=lambda choices: row["id"])
self.assertEqual(chosen["big"], row["id"])
def test_remote_metadata_rejects_invalid_identity_runtime_and_hardware(self):
import copy
row = dict(self.row, id="new_remote", name="New Remote", version="v16")
good = {**self.manifest, "models": [row]}
self.assertIn(row["id"], validate_manifest(good))
for invalid_document in (None, [], "catalog"):
with self.subTest(document=invalid_document), self.assertRaises(ModelError):
validate_manifest(invalid_document)
for changes in ({"id": "../escape"}, {"id": "/escape"}, {"id": "Uppercase"}, {"name": ""},
{"name": "bad\nlabel"}, {"name": "x" * 129}, {"version": []}, {"version": "v17"}, {"uses_external_gpu": "false"},
{"artifact_format": "other"}, {"artifact_size": True}, {"artifact_sha256": "x" * 64},
{"artifact_chunk_count": -1}):
with self.subTest(changes=changes), self.assertRaises(ModelError):
validate_manifest({**good, "models": [row | changes]})
with self.assertRaises(ModelError):
validate_manifest({**good, "models": [row, row]})
unpublished = copy.deepcopy(row)
for key in ("artifact_format", "artifact_size", "artifact_sha256", "artifact_chunk_count"):
unpublished.pop(key)
with self.assertRaises(ModelError):
validate_manifest({**good, "models": [unpublished]})
future_only = unpublished | {"runtime_artifacts": [{
key: value for key, value in row.items() if key in {"artifact_format", "artifact_sha256", "artifact_size", "artifact_chunk_count"}
} | {"min_runner_revision": 3}]}
with self.assertRaises(ModelError):
validate_manifest({**good, "models": [future_only]})
for key in ("compiler_revision", "artifact_abi", "generation"):
with self.subTest(key=key), self.assertRaises(ModelError):
validate_manifest(good | {key: "other"})
known = dict(self.row, version="v16" if self.row["version"] != "v16" else "v15")
with self.assertRaises(ModelError):
validate_manifest({**good, "models": [known]})
def test_remote_metadata_cache_refresh_and_root_isolation(self):
from openpilot.starpilot.models.catalog import BY_ID, model_entries
row = dict(self.row, id="remote_cache", name="First name", version="v16")
atomic_json(self.root / "catalog.json", {**self.manifest, "models": [row]})
self.assertEqual(model_entries(self.root)[row["id"]].name, "First name")
with patch("openpilot.starpilot.models.manager.catalog", side_effect=AssertionError("cache reparsed")):
self.assertEqual(model_entries(self.root)[row["id"]].name, "First name")
atomic_json(self.root / "catalog.json", {**self.manifest, "models": [row | {"name": "Second name"}]})
self.assertEqual(model_entries(self.root)[row["id"]].name, "Second name")
with tempfile.TemporaryDirectory() as empty:
self.assertNotIn(row["id"], model_entries(Path(empty)))
self.assertEqual(model_entries(self.root)[DEFAULT_SMALL].source_sha256, BY_ID[DEFAULT_SMALL].source_sha256)
def test_catalog_page_refresh_is_async_bounded_and_parked_only(self):
entered, release = threading.Event(), threading.Event()
row = dict(self.row, id="page_remote", name="Page Remote", version="v16")
self.manifest["models"].append(row)
original = self.manager.opener
def blocked_manifest(url, timeout):
if "/manifests/" in url:
entered.set()
if not release.wait(3):
raise OSError("fixture stalled")
return original(url, timeout)
self.manager.opener = blocked_manifest
self.manager.refresh_catalog = True
self.parked = False
with patch("openpilot.starpilot.models.manager.time.monotonic", return_value=100):
self.manager.snapshot()
self.assertIsNone(self.manager.worker)
self.parked = True
self.manager.snapshot()
self.assertTrue(entered.wait(1))
self.assertTrue(self.manager.worker.is_alive())
self.assertFalse(any("/models/" in url for url in self.urls))
release.set()
self.manager.worker.join(3)
self.assertIn(row["id"], catalog(self.root))
previous = self.manager.worker
self.manager.snapshot()
self.assertIs(self.manager.worker, previous)
self.assertFalse(any("/models/" in url for url in self.urls))
def test_unpublished_catalog_is_visible_but_not_selectable(self):
manifest_ids = [row['id'] for row in json.loads(CATALOG_PATH.read_text())['models']]
self.assertEqual(len(manifest_ids), len(set(manifest_ids)))
self.assertCountEqual(catalog(self.root), manifest_ids)
self.manager.snapshot()
if self.manager.verify_worker is not None:
self.manager.verify_worker.join(2)
models = self.manager.snapshot()['models']
visible_ids = [row['value'] for row in models]
self.assertEqual(len(visible_ids), len(set(visible_ids)))
self.assertCountEqual(visible_ids, manifest_ids)
self.assertEqual(sum(row['selectable'] for row in models), 1)
self.assertTrue(all(row.get('unavailableReason') for row in models if not row['selectable']))
def test_fresh_install_knows_published_downloads_without_network_or_gpu(self):
self.manager.gpu_present = lambda: False
models = {row['value']: row for row in self.manager.snapshot()['models']}
self.assertTrue(models['gwm8223']['downloadAvailable'])
self.assertEqual(models['gwm8223']['unavailableReason'], 'Download required')
self.assertFalse(models['gwm8223']['selectable'])
self.assertFalse(models['gwm6223']['downloadAvailable'])
self.assertEqual(models['gwm6223']['unavailableReason'], 'v26 download not published')
big = next(row for row in models.values() if row.get('requiresGpu') and row.get('downloadAvailable'))
self.assertFalse(big['gpuAvailable'])
self.assertEqual(big['unavailableReason'], 'Download required')
self.assertEqual(self.urls, [])
self.assertFalse((self.root / 'catalog.json').exists())
if __name__ == '__main__':
unittest.main()