mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-08-16 03:03:44 +08:00
d02355b1a5
* rename tici hardware platform to comma * no /TICI * lil more * lil more * you had a good life larch64 * lil more * one more
167 lines
4.7 KiB
Python
167 lines
4.7 KiB
Python
import contextlib
|
|
import gc
|
|
import inspect
|
|
import os
|
|
import subprocess
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
from openpilot.common.hardware import HARDWARE, COMMA_HARDWARE
|
|
from openpilot.common.prefix import OpenpilotPrefix
|
|
from openpilot.system.manager import manager
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def clean_env():
|
|
starting_env = dict(os.environ)
|
|
try:
|
|
yield
|
|
finally:
|
|
os.environ.clear()
|
|
os.environ.update(starting_env)
|
|
|
|
|
|
class OpenpilotTestCase(unittest.TestCase):
|
|
"""TestCase with openpilot's per-test isolation."""
|
|
|
|
COMMA_HARDWARE_TEST = False
|
|
SHARED_DOWNLOAD_CACHE = False
|
|
|
|
def __init_subclass__(cls, **kwargs):
|
|
super().__init_subclass__(**kwargs)
|
|
# Preserve legacy xunit hooks and invoke them inside the prefix below.
|
|
for name in ("setup_method", "teardown_method"):
|
|
hook = cls.__dict__.get(name)
|
|
if hook is not None:
|
|
setattr(cls, f"openpilot_{name}", hook)
|
|
setattr(cls, name, None)
|
|
|
|
def _fixture(self, name):
|
|
if name == "mocker":
|
|
return Mocker(self.addCleanup)
|
|
if name == "monkeypatch":
|
|
return MonkeyPatch(self.addCleanup)
|
|
if name == "subtests":
|
|
return SubTests(self)
|
|
|
|
fixture = getattr(inspect.getmodule(type(self)), name)
|
|
kwargs = {p: self._fixture(p) for p in inspect.signature(fixture).parameters}
|
|
value = fixture(**kwargs)
|
|
if inspect.isgenerator(value):
|
|
generator = value
|
|
value = next(generator)
|
|
self.addCleanup(lambda: next(generator, None))
|
|
return value
|
|
|
|
def _callTestMethod(self, method):
|
|
params = [name for name, param in inspect.signature(method).parameters.items()
|
|
if param.default is inspect.Parameter.empty]
|
|
return method(**{name: self._fixture(name) for name in params})
|
|
|
|
def run(self, result=None):
|
|
# This boundary cannot live in setUp/tearDown: existing unittest classes
|
|
# are allowed to override those hooks without calling super().
|
|
if (self.COMMA_HARDWARE_TEST and not COMMA_HARDWARE) or getattr(type(self), "__unittest_skip__", False):
|
|
return super().run(result)
|
|
test_env = clean_env()
|
|
test_env.__enter__()
|
|
prefix = OpenpilotPrefix(shared_download_cache=self.SHARED_DOWNLOAD_CACHE)
|
|
prefix.__enter__()
|
|
try:
|
|
return super().run(result)
|
|
finally:
|
|
prefix.__exit__(None, None, None)
|
|
manager.manager_cleanup()
|
|
if not gc.isenabled():
|
|
gc.enable()
|
|
gc.collect()
|
|
test_env.__exit__(None, None, None)
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
if cls.COMMA_HARDWARE_TEST and not COMMA_HARDWARE:
|
|
raise unittest.SkipTest("Skipping comma hardware test on PC")
|
|
cls._class_env = clean_env()
|
|
cls._class_env.__enter__()
|
|
setup_class = getattr(cls, "setup_class", None)
|
|
if setup_class is not None:
|
|
setup_class()
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
try:
|
|
teardown_class = getattr(cls, "teardown_class", None)
|
|
if teardown_class is not None:
|
|
teardown_class()
|
|
finally:
|
|
cls._class_env.__exit__(None, None, None)
|
|
super().tearDownClass()
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
if self.COMMA_HARDWARE_TEST and not COMMA_HARDWARE:
|
|
self.skipTest("Skipping comma hardware test on PC")
|
|
|
|
if self.COMMA_HARDWARE_TEST:
|
|
HARDWARE.initialize_hardware()
|
|
HARDWARE.set_power_save(False)
|
|
subprocess.run(["pkill", "-9", "-f", "athena"], check=False)
|
|
|
|
setup_method = getattr(self, "openpilot_setup_method", None)
|
|
if setup_method is not None:
|
|
setup_method()
|
|
|
|
def tearDown(self):
|
|
try:
|
|
teardown_method = getattr(self, "openpilot_teardown_method", None)
|
|
if teardown_method is not None:
|
|
teardown_method()
|
|
finally:
|
|
super().tearDown()
|
|
|
|
|
|
class Mocker:
|
|
Mock = mock.Mock
|
|
MagicMock = mock.MagicMock
|
|
call = mock.call
|
|
ANY = mock.ANY
|
|
|
|
def __init__(self, add_cleanup):
|
|
self._add_cleanup = add_cleanup
|
|
self.patch = Patch(self._start)
|
|
|
|
def _start(self, patcher):
|
|
value = patcher.start()
|
|
self._add_cleanup(patcher.stop)
|
|
return value
|
|
|
|
|
|
class Patch:
|
|
def __init__(self, start):
|
|
self._start = start
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
return self._start(mock.patch(*args, **kwargs))
|
|
|
|
def object(self, *args, **kwargs):
|
|
return self._start(mock.patch.object(*args, **kwargs))
|
|
|
|
|
|
class MonkeyPatch:
|
|
def __init__(self, add_cleanup):
|
|
self._add_cleanup = add_cleanup
|
|
|
|
def setattr(self, target, name, value):
|
|
patcher = mock.patch.object(target, name, value)
|
|
patcher.start()
|
|
self._add_cleanup(patcher.stop)
|
|
|
|
|
|
class SubTests:
|
|
def __init__(self, test_case):
|
|
self._test_case = test_case
|
|
|
|
def test(self, label=None, **kwargs):
|
|
return self._test_case.subTest(**kwargs) if label is None else self._test_case.subTest(label, **kwargs)
|