IQ.Pilot Prebuilt Release @ 27f668a
This commit is contained in:
1
iqpilot/common/tests/.gitignore
vendored
Normal file
1
iqpilot/common/tests/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
test_common
|
||||
0
iqpilot/common/tests/__init__.py
Normal file
0
iqpilot/common/tests/__init__.py
Normal file
19
iqpilot/common/tests/test_file_helpers.py
Normal file
19
iqpilot/common/tests/test_file_helpers.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import os
|
||||
from uuid import uuid4
|
||||
|
||||
from iqpilot.common.utils import atomic_write
|
||||
|
||||
|
||||
class TestFileHelpers:
|
||||
def run_atomic_write_func(self, atomic_write_func):
|
||||
path = f"/tmp/tmp{uuid4()}"
|
||||
with atomic_write_func(path) as f:
|
||||
f.write("test")
|
||||
assert not os.path.exists(path)
|
||||
|
||||
with open(path) as f:
|
||||
assert f.read() == "test"
|
||||
os.remove(path)
|
||||
|
||||
def test_atomic_write(self):
|
||||
self.run_atomic_write_func(atomic_write)
|
||||
15
iqpilot/common/tests/test_markdown.py
Normal file
15
iqpilot/common/tests/test_markdown.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import os
|
||||
|
||||
from iqpilot.common.basedir import BASEDIR
|
||||
from iqpilot.common.markdown import parse_markdown
|
||||
|
||||
|
||||
class TestMarkdown:
|
||||
def test_all_release_notes(self):
|
||||
with open(os.path.join(BASEDIR, "iqpilot", "docs", "CHANGELOG.md")) as f:
|
||||
release_notes = f.read().split("\n\n")
|
||||
assert len(release_notes) > 10
|
||||
|
||||
for rn in release_notes:
|
||||
md = parse_markdown(rn)
|
||||
assert len(md) > 0
|
||||
145
iqpilot/common/tests/test_params.py
Normal file
145
iqpilot/common/tests/test_params.py
Normal file
@@ -0,0 +1,145 @@
|
||||
import pytest
|
||||
import datetime
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from iqpilot.common.params import Params, ParamKeyFlag, UnknownKeyName
|
||||
|
||||
class TestParams:
|
||||
def setup_method(self):
|
||||
self.params = Params()
|
||||
|
||||
def test_params_put_and_get(self):
|
||||
self.params.put("DongleId", "cb38263377b873ee")
|
||||
assert self.params.get("DongleId") == "cb38263377b873ee"
|
||||
|
||||
def test_params_non_ascii(self):
|
||||
st = b"\xe1\x90\xff"
|
||||
self.params.put("CarParams", st)
|
||||
assert self.params.get("CarParams") == st
|
||||
|
||||
def test_params_get_cleared_manager_start(self):
|
||||
self.params.put("CarParams", b"test")
|
||||
self.params.put("DongleId", "cb38263377b873ee")
|
||||
assert self.params.get("CarParams") == b"test"
|
||||
|
||||
undefined_param = self.params.get_param_path(uuid.uuid4().hex)
|
||||
with open(undefined_param, "w") as f:
|
||||
f.write("test")
|
||||
assert os.path.isfile(undefined_param)
|
||||
|
||||
self.params.clear_all(ParamKeyFlag.CLEAR_ON_MANAGER_START)
|
||||
assert self.params.get("CarParams") is None
|
||||
assert self.params.get("DongleId") is not None
|
||||
assert not os.path.isfile(undefined_param)
|
||||
|
||||
def test_params_two_things(self):
|
||||
self.params.put("DongleId", "bob")
|
||||
self.params.put("AthenadPid", 123)
|
||||
assert self.params.get("DongleId") == "bob"
|
||||
assert self.params.get("AthenadPid") == 123
|
||||
|
||||
def test_params_get_block(self):
|
||||
def _delayed_writer():
|
||||
time.sleep(0.1)
|
||||
self.params.put("CarParams", b"test")
|
||||
threading.Thread(target=_delayed_writer).start()
|
||||
assert self.params.get("CarParams") is None
|
||||
assert self.params.get("CarParams", block=True) == b"test"
|
||||
|
||||
def test_params_unknown_key_fails(self):
|
||||
with pytest.raises(UnknownKeyName):
|
||||
self.params.get("swag")
|
||||
|
||||
with pytest.raises(UnknownKeyName):
|
||||
self.params.get_bool("swag")
|
||||
|
||||
with pytest.raises(UnknownKeyName):
|
||||
self.params.put("swag", "abc")
|
||||
|
||||
with pytest.raises(UnknownKeyName):
|
||||
self.params.put_bool("swag", True)
|
||||
|
||||
def test_remove_not_there(self):
|
||||
assert self.params.get("CarParams") is None
|
||||
self.params.remove("CarParams")
|
||||
assert self.params.get("CarParams") is None
|
||||
|
||||
def test_get_bool(self):
|
||||
self.params.remove("IsMetric")
|
||||
assert not self.params.get_bool("IsMetric")
|
||||
|
||||
self.params.put_bool("IsMetric", True)
|
||||
assert self.params.get_bool("IsMetric")
|
||||
|
||||
self.params.put_bool("IsMetric", False)
|
||||
assert not self.params.get_bool("IsMetric")
|
||||
|
||||
self.params.put("IsMetric", True)
|
||||
assert self.params.get_bool("IsMetric")
|
||||
|
||||
self.params.put("IsMetric", False)
|
||||
assert not self.params.get_bool("IsMetric")
|
||||
|
||||
def test_navigation_disabled_default(self):
|
||||
self.params.remove("NavigationEnabled")
|
||||
assert not self.params.get_bool("NavigationEnabled")
|
||||
|
||||
def test_put_non_blocking_with_get_block(self):
|
||||
q = Params()
|
||||
def _delayed_writer():
|
||||
time.sleep(0.1)
|
||||
Params().put_nonblocking("CarParams", b"test")
|
||||
threading.Thread(target=_delayed_writer).start()
|
||||
assert q.get("CarParams") is None
|
||||
assert q.get("CarParams", True) == b"test"
|
||||
|
||||
def test_put_bool_non_blocking_with_get_block(self):
|
||||
q = Params()
|
||||
def _delayed_writer():
|
||||
time.sleep(0.1)
|
||||
Params().put_bool_nonblocking("CarParams", True)
|
||||
threading.Thread(target=_delayed_writer).start()
|
||||
assert q.get("CarParams") is None
|
||||
assert q.get("CarParams", True) == b"1"
|
||||
|
||||
def test_params_all_keys(self):
|
||||
keys = Params().all_keys()
|
||||
|
||||
# sanity checks
|
||||
assert len(keys) > 20
|
||||
assert len(keys) == len(set(keys))
|
||||
assert b"CarParams" in keys
|
||||
|
||||
def test_params_default_value(self):
|
||||
self.params.remove("LanguageSetting")
|
||||
self.params.remove("LongitudinalPersonality")
|
||||
self.params.remove("LiveParameters")
|
||||
|
||||
assert self.params.get("LanguageSetting") is None
|
||||
assert self.params.get("LanguageSetting", return_default=False) is None
|
||||
assert isinstance(self.params.get("LanguageSetting", return_default=True), str)
|
||||
assert isinstance(self.params.get("LongitudinalPersonality", return_default=True), int)
|
||||
assert self.params.get("LiveParameters") is None
|
||||
assert self.params.get("LiveParameters", return_default=True) is None
|
||||
|
||||
def test_params_get_type(self):
|
||||
# json
|
||||
self.params.put("ApiCache_FirehoseStats", {"a": 0})
|
||||
assert self.params.get("ApiCache_FirehoseStats") == {"a": 0}
|
||||
|
||||
# int
|
||||
self.params.put("BootCount", 1441)
|
||||
assert self.params.get("BootCount") == 1441
|
||||
|
||||
# bool
|
||||
self.params.put("AdbEnabled", True)
|
||||
assert self.params.get("AdbEnabled")
|
||||
assert isinstance(self.params.get("AdbEnabled"), bool)
|
||||
|
||||
# time
|
||||
now = datetime.datetime.now(datetime.UTC)
|
||||
self.params.put("InstallDate", now)
|
||||
assert self.params.get("InstallDate") == now
|
||||
64
iqpilot/common/tests/test_realtime.py
Normal file
64
iqpilot/common/tests/test_realtime.py
Normal file
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
|
||||
from iqpilot.common.realtime import config_background_thread, Ratekeeper
|
||||
|
||||
|
||||
class MonotonicClock:
|
||||
def __init__(self) -> None:
|
||||
self.now = 0.
|
||||
|
||||
def advance(self, seconds: float) -> None:
|
||||
self.now += seconds
|
||||
|
||||
def __call__(self) -> float:
|
||||
return self.now
|
||||
|
||||
|
||||
def test_ratekeeper_reset_discards_accumulated_lag(monkeypatch):
|
||||
clock = MonotonicClock()
|
||||
monkeypatch.setattr("iqpilot.common.realtime.time.monotonic", clock)
|
||||
rk = Ratekeeper(100)
|
||||
|
||||
rk.monitor_time()
|
||||
clock.advance(0.075)
|
||||
rk.monitor_time()
|
||||
assert rk.remaining == pytest.approx(-0.055)
|
||||
assert rk.lag == pytest.approx(0.055)
|
||||
|
||||
rk.reset()
|
||||
assert rk.remaining == 0.
|
||||
assert rk.lag == 0.
|
||||
|
||||
rk.monitor_time()
|
||||
assert rk.remaining == pytest.approx(0.01)
|
||||
assert rk.lag == 0.
|
||||
|
||||
|
||||
def test_ratekeeper_reset_preserves_frame_count(monkeypatch):
|
||||
clock = MonotonicClock()
|
||||
monkeypatch.setattr("iqpilot.common.realtime.time.monotonic", clock)
|
||||
rk = Ratekeeper(100)
|
||||
|
||||
rk.monitor_time()
|
||||
clock.advance(0.01)
|
||||
rk.monitor_time()
|
||||
frame = rk.frame
|
||||
|
||||
rk.reset()
|
||||
assert rk.frame == frame
|
||||
|
||||
|
||||
def test_config_background_thread_restores_normal_scheduling(monkeypatch):
|
||||
calls = []
|
||||
monkeypatch.setattr("iqpilot.common.realtime.sys.platform", "linux")
|
||||
monkeypatch.setattr("iqpilot.common.realtime.PC", False)
|
||||
monkeypatch.setattr("iqpilot.common.realtime.os.cpu_count", lambda: 8)
|
||||
monkeypatch.setattr("iqpilot.common.realtime.os.SCHED_OTHER", 0, raising=False)
|
||||
monkeypatch.setattr("iqpilot.common.realtime.os.sched_param", lambda priority: priority, raising=False)
|
||||
monkeypatch.setattr("iqpilot.common.realtime.os.sched_setscheduler", lambda pid, policy, param: calls.append((pid, policy, param)), raising=False)
|
||||
monkeypatch.setattr("iqpilot.common.realtime.os.sched_setaffinity", lambda pid, cores: calls.append((pid, set(cores))), raising=False)
|
||||
|
||||
config_background_thread()
|
||||
|
||||
assert calls == [(0, 0, 0), (0, set(range(8)))]
|
||||
29
iqpilot/common/tests/test_simple_kalman.py
Normal file
29
iqpilot/common/tests/test_simple_kalman.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from iqpilot.common.simple_kalman import KF1D
|
||||
|
||||
|
||||
class TestSimpleKalman:
|
||||
def setup_method(self):
|
||||
dt = 0.01
|
||||
x0_0 = 0.0
|
||||
x1_0 = 0.0
|
||||
A0_0 = 1.0
|
||||
A0_1 = dt
|
||||
A1_0 = 0.0
|
||||
A1_1 = 1.0
|
||||
C0_0 = 1.0
|
||||
C0_1 = 0.0
|
||||
K0_0 = 0.12287673
|
||||
K1_0 = 0.29666309
|
||||
|
||||
self.kf = KF1D(x0=[[x0_0], [x1_0]],
|
||||
A=[[A0_0, A0_1], [A1_0, A1_1]],
|
||||
C=[C0_0, C0_1],
|
||||
K=[[K0_0], [K1_0]])
|
||||
|
||||
def test_getter_setter(self):
|
||||
self.kf.set_x([[1.0], [1.0]])
|
||||
assert self.kf.x == [[1.0], [1.0]]
|
||||
|
||||
def test_update_returns_state(self):
|
||||
x = self.kf.update(100)
|
||||
assert x == [i[0] for i in self.kf.x]
|
||||
91
iqpilot/common/tests/test_steer_delay.py
Normal file
91
iqpilot/common/tests/test_steer_delay.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
import iqpilot.cereal.messaging as messaging
|
||||
from iqpilot.cereal import car
|
||||
from iqpilot.common.params import Params
|
||||
from iqpilot.common.steer_delay import (
|
||||
SteerDelayPublisher,
|
||||
cached_steer_delay,
|
||||
fixed_steer_delay,
|
||||
lateral_action_delay,
|
||||
resolve_steer_delay,
|
||||
)
|
||||
|
||||
ANGLE = car.CarParams.SteerControlType.angle
|
||||
TORQUE = car.CarParams.SteerControlType.torque
|
||||
|
||||
LIVE_DELAY = 0.4387
|
||||
RACK_DELAY = 0.10
|
||||
OFFSET = 0.05
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def params(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("PARAMS_ROOT", str(tmp_path))
|
||||
p = Params()
|
||||
p.put("IQSteerDelayCache", LIVE_DELAY)
|
||||
p.put("IQSoftwareSteerDelay", OFFSET)
|
||||
return p
|
||||
|
||||
|
||||
def _car_params(steer_control_type):
|
||||
cp = car.CarParams.new_message()
|
||||
cp.steerControlType = steer_control_type
|
||||
cp.steerActuatorDelay = RACK_DELAY
|
||||
return cp
|
||||
|
||||
|
||||
def _lateral_delay_msg(value):
|
||||
msg = messaging.new_message("lateralDelay")
|
||||
msg.lateralDelay.lateralDelay = value
|
||||
return msg.as_reader()
|
||||
|
||||
|
||||
def test_params_fixture_is_isolated_from_the_real_device(params, tmp_path):
|
||||
assert str(tmp_path) in params.get_param_path("")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("live_enabled", [True, False])
|
||||
def test_torque_cars_always_use_live_delay(params, live_enabled):
|
||||
params.put_bool("IQLiveSteerDelay", live_enabled)
|
||||
assert lateral_action_delay(params, _car_params(TORQUE), LIVE_DELAY) == pytest.approx(LIVE_DELAY)
|
||||
|
||||
|
||||
def test_angle_cars_ignore_live_delay_when_self_tuning_is_off(params):
|
||||
params.put_bool("IQLiveSteerDelay", False)
|
||||
delay = lateral_action_delay(params, _car_params(ANGLE), LIVE_DELAY)
|
||||
assert delay == pytest.approx(RACK_DELAY + OFFSET)
|
||||
assert delay != pytest.approx(LIVE_DELAY)
|
||||
|
||||
|
||||
def test_angle_cars_use_cached_delay_when_self_tuning_is_on(params):
|
||||
params.put_bool("IQLiveSteerDelay", True)
|
||||
assert lateral_action_delay(params, _car_params(ANGLE), LIVE_DELAY) == pytest.approx(LIVE_DELAY)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("offset", [0.05, 0.20, 0.50])
|
||||
def test_manual_offset_reaches_the_path_and_matches_what_the_ui_reports(params, offset):
|
||||
params.put_bool("IQLiveSteerDelay", False)
|
||||
params.put("IQSoftwareSteerDelay", offset)
|
||||
ui_total = RACK_DELAY + offset
|
||||
assert fixed_steer_delay(params, RACK_DELAY) == pytest.approx(ui_total)
|
||||
assert lateral_action_delay(params, _car_params(ANGLE), LIVE_DELAY) == pytest.approx(ui_total)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("live_enabled", [False, True])
|
||||
def test_publisher_writes_the_value_the_resolver_reads(params, live_enabled):
|
||||
params.put_bool("IQLiveSteerDelay", live_enabled)
|
||||
params.put("IQSteerDelayCache", -1.0)
|
||||
SteerDelayPublisher(_car_params(ANGLE)).update(_lateral_delay_msg(LIVE_DELAY))
|
||||
|
||||
expected = LIVE_DELAY if live_enabled else RACK_DELAY + OFFSET
|
||||
deadline = time.monotonic() + 5.0
|
||||
while cached_steer_delay() != pytest.approx(expected) and time.monotonic() < deadline:
|
||||
time.sleep(0.01)
|
||||
assert cached_steer_delay() == pytest.approx(expected)
|
||||
assert resolve_steer_delay(params, RACK_DELAY) == pytest.approx(expected)
|
||||
Reference in New Issue
Block a user