mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 13:33:42 +08:00
ca183df497
StarPilotAdapter subclasses jetlink's Stock: StarPilot's param names, the AOL engagement rule, the model root and a CatalogModelState history reset that keeps its JIT buffers. jetlinkd runs jetlink's owner as it is, modeld calls the hooks, and Galaxy and analytics read jetlink's runtime record in place of JetlinkRuntime. - no big-model slot or catalog keys: jetlink provisions its default model - jetlink restores the owner's tuning itself; the port draw stays cut with a host plugged in - the Jetson is asked to power off on DoShutdown only, capped at 30 s
138 lines
5.2 KiB
Python
138 lines
5.2 KiB
Python
"""Opt-in process ownership and remote shutdown before manager cleanup."""
|
|
from types import SimpleNamespace
|
|
import unittest
|
|
from unittest.mock import Mock, patch
|
|
|
|
from openpilot.starpilot.system.jetlink import lifecycle
|
|
|
|
|
|
def _cleanup_child(ready, cleaned, release):
|
|
import signal
|
|
import time
|
|
stopping = False
|
|
|
|
def stop(signum, frame):
|
|
nonlocal stopping
|
|
stopping = True
|
|
|
|
signal.signal(signal.SIGINT, stop)
|
|
ready.set()
|
|
while not stopping:
|
|
time.sleep(.01)
|
|
release.wait(2)
|
|
cleaned.set()
|
|
|
|
|
|
class TestJetlinkLifecycle(unittest.TestCase):
|
|
def jetlink(self, on):
|
|
link = Mock()
|
|
for name, value in (('hooks', lambda: link), ('link_on', lambda: on)):
|
|
patcher = patch(f'openpilot.starpilot.models.jetlink_adapter.{name}', value)
|
|
patcher.start()
|
|
self.addCleanup(patcher.stop)
|
|
return link
|
|
|
|
def test_with_the_link_off_jetlink_is_never_bound(self):
|
|
from jetlink.openpilot.hooks import Hooks
|
|
from openpilot.starpilot.models.jetlink_adapter import API
|
|
self.jetlink(False)
|
|
factory = Mock()
|
|
with patch('openpilot.starpilot.models.jetlink_adapter.hooks', lambda: Hooks(API, factory, link_on=lambda: False)):
|
|
self.assertFalse(lifecycle.enabled(None))
|
|
lifecycle.shutdown(SimpleNamespace(get_bool=lambda key: key == 'DoShutdown'))
|
|
factory.assert_not_called()
|
|
|
|
def test_the_owner_follows_jetlink(self):
|
|
link = self.jetlink(True)
|
|
for available in (True, False):
|
|
link.should_run.return_value = available
|
|
self.assertEqual(lifecycle.enabled(None), available)
|
|
|
|
def test_only_power_off_asks_the_host_and_is_capped(self):
|
|
link = self.jetlink(True)
|
|
for reason in (None, 'DoReboot', 'DoUninstall', 'DoShutdown'):
|
|
link.shutdown_step.reset_mock()
|
|
link.shutdown_step.return_value = True
|
|
params = SimpleNamespace(get_bool=lambda key, reason=reason: key == reason)
|
|
lifecycle.shutdown(params)
|
|
if reason == 'DoShutdown':
|
|
link.shutdown_step.assert_called_once_with(True, 'DoShutdown')
|
|
else:
|
|
link.shutdown_step.assert_not_called()
|
|
|
|
# a host that never answers holds the power-off for the cap, no longer
|
|
now, waits = [0.], []
|
|
|
|
def sleep(seconds):
|
|
waits.append(seconds)
|
|
if len(waits) > 1000:
|
|
raise AssertionError("the power-off wait has no cap")
|
|
now[0] += seconds
|
|
link.shutdown_step.return_value = False
|
|
lifecycle.shutdown(params, clock=lambda: now[0], sleep=sleep)
|
|
self.assertGreaterEqual(now[0], lifecycle.SHUTDOWN_CAP_S)
|
|
self.assertLess(now[0], lifecycle.SHUTDOWN_CAP_S + 1)
|
|
|
|
def test_registered_native_int_and_owner_file_reader_agree(self):
|
|
from pathlib import Path
|
|
from jetlink.openpilot.settings import FileParams, Settings
|
|
from openpilot.common.params import Params
|
|
from openpilot.common.prefix import OpenpilotPrefix
|
|
from openpilot.starpilot.models.jetlink_adapter import owner_config
|
|
|
|
with OpenpilotPrefix():
|
|
params = Params()
|
|
config = owner_config()
|
|
settings = Settings(FileParams(config.params_dir), config.keys)
|
|
from openpilot.starpilot.models import jetlink_adapter
|
|
jetlink_adapter.hooks.cache_clear()
|
|
self.addCleanup(jetlink_adapter.hooks.cache_clear)
|
|
with patch('jetlink.openpilot.parts.Parts.chestnut_fitted', return_value=False), \
|
|
patch('openpilot.common.hardware.HARDWARE.get_device_type', return_value='tizi'):
|
|
for value, mode in ((0, 'off'), (1, 'usb'), (2, 'ios'), (3, 'off'), (-1, 'off')):
|
|
with self.subTest(value=value):
|
|
params.put('JetlinkMode', value, block=True)
|
|
self.assertIs(type(params.get('JetlinkMode')), int)
|
|
self.assertEqual(params.get('JetlinkMode'), value)
|
|
self.assertEqual(FileParams(config.params_dir).get_int('JetlinkMode'), value)
|
|
self.assertEqual(settings.mode(), mode)
|
|
self.assertEqual(jetlink_adapter.link_on(), value in (1, 2))
|
|
self.assertEqual(lifecycle.enabled(params), value in (1, 2))
|
|
params.put('JetlinkMode', 1, block=True)
|
|
Path(params.get_param_path('JetlinkMode')).write_bytes(b'corrupt')
|
|
self.assertEqual(settings.mode(), 'off')
|
|
self.assertFalse(lifecycle.enabled(params))
|
|
|
|
def test_process_nonblocking_stop_and_blocking_signal_cleanup(self):
|
|
import multiprocessing
|
|
import time
|
|
from openpilot.starpilot.system.jetlink.process import JetlinkProcess
|
|
|
|
context = multiprocessing.get_context('spawn')
|
|
ready = context.Event()
|
|
cleaned = context.Event()
|
|
release = context.Event()
|
|
|
|
process = context.Process(target=_cleanup_child, args=(ready, cleaned, release))
|
|
process.start()
|
|
def cleanup():
|
|
if process.is_alive():
|
|
process.kill()
|
|
process.join(.5)
|
|
|
|
self.addCleanup(cleanup)
|
|
self.assertTrue(ready.wait(2))
|
|
owner = JetlinkProcess('jetlinkd', 'unused', lambda *args: True)
|
|
vars(owner)['proc'] = process
|
|
start = time.monotonic()
|
|
owner.stop(block=False)
|
|
owner.stop(block=False)
|
|
self.assertLess(time.monotonic() - start, .5)
|
|
self.assertTrue(owner.shutting_down)
|
|
release.set()
|
|
owner.stop(block=True)
|
|
self.assertTrue(cleaned.is_set())
|
|
self.assertEqual(process.exitcode, 0)
|
|
self.assertIsNone(owner.proc)
|
|
self.assertLess(time.monotonic() - start, 2)
|