diff --git a/openpilot/selfdrive/controls/tests/test_longcontrol.py b/openpilot/selfdrive/controls/tests/test_longcontrol.py index da916b156f..f69c54a3cd 100644 --- a/openpilot/selfdrive/controls/tests/test_longcontrol.py +++ b/openpilot/selfdrive/controls/tests/test_longcontrol.py @@ -27,30 +27,30 @@ class TestLongControlStateTransition: should_stop=False, brake_pressed=False, cruise_standstill=False) assert next_state == LongCtrlState.off -def test_engage(): - CP = car.CarParams.new_message() - active = True - current_state = LongCtrlState.off - next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1, - should_stop=True, brake_pressed=False, cruise_standstill=False) - assert next_state == LongCtrlState.stopping - next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1, - should_stop=False, brake_pressed=True, cruise_standstill=False) - assert next_state == LongCtrlState.stopping - next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1, - should_stop=False, brake_pressed=False, cruise_standstill=True) - assert next_state == LongCtrlState.stopping - next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1, - should_stop=False, brake_pressed=False, cruise_standstill=False) - assert next_state == LongCtrlState.pid + def test_engage(self): + CP = car.CarParams.new_message() + active = True + current_state = LongCtrlState.off + next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1, + should_stop=True, brake_pressed=False, cruise_standstill=False) + assert next_state == LongCtrlState.stopping + next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1, + should_stop=False, brake_pressed=True, cruise_standstill=False) + assert next_state == LongCtrlState.stopping + next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1, + should_stop=False, brake_pressed=False, cruise_standstill=True) + assert next_state == LongCtrlState.stopping + next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1, + should_stop=False, brake_pressed=False, cruise_standstill=False) + assert next_state == LongCtrlState.pid -def test_starting(): - CP = car.CarParams.new_message(startingState=True, vEgoStarting=0.5) - active = True - current_state = LongCtrlState.starting - next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1, - should_stop=False, brake_pressed=False, cruise_standstill=False) - assert next_state == LongCtrlState.starting - next_state = long_control_state_trans(CP, active, current_state, v_ego=1.0, - should_stop=False, brake_pressed=False, cruise_standstill=False) - assert next_state == LongCtrlState.pid + def test_starting(self): + CP = car.CarParams.new_message(startingState=True, vEgoStarting=0.5) + active = True + current_state = LongCtrlState.starting + next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1, + should_stop=False, brake_pressed=False, cruise_standstill=False) + assert next_state == LongCtrlState.starting + next_state = long_control_state_trans(CP, active, current_state, v_ego=1.0, + should_stop=False, brake_pressed=False, cruise_standstill=False) + assert next_state == LongCtrlState.pid diff --git a/openpilot/selfdrive/controls/tests/test_torqued_lat_accel_offset.py b/openpilot/selfdrive/controls/tests/test_torqued_lat_accel_offset.py index 7a63da23a5..7599eb1e7b 100644 --- a/openpilot/selfdrive/controls/tests/test_torqued_lat_accel_offset.py +++ b/openpilot/selfdrive/controls/tests/test_torqued_lat_accel_offset.py @@ -56,16 +56,17 @@ def simulate_straight_road_msgs(est): for which, msg in (('carControl', carControl), ('carOutput', carOutput), ('carState', carState), ('livePose', livePose)): est.handle_log(t, which, msg) -def test_estimated_offset(): - steer_torques, lat_accels = generate_inputs(TORQUE_TUNE_BIASED, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD) - est = get_warmed_up_estimator(steer_torques, lat_accels) - msg = est.get_msg() - # TODO add lataccelfactor and friction check when we have more accurate estimates - assert abs(msg.liveTorqueParameters.latAccelOffsetRaw - TORQUE_TUNE_BIASED.latAccelOffset) < 0.1 +class TestTorquedLatAccelOffset: + def test_estimated_offset(self): + steer_torques, lat_accels = generate_inputs(TORQUE_TUNE_BIASED, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD) + est = get_warmed_up_estimator(steer_torques, lat_accels) + msg = est.get_msg() + # TODO add lataccelfactor and friction check when we have more accurate estimates + assert abs(msg.liveTorqueParameters.latAccelOffsetRaw - TORQUE_TUNE_BIASED.latAccelOffset) < 0.1 -def test_straight_road_roll_bias(): - steer_torques, lat_accels = generate_inputs(TORQUE_TUNE, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD) - est = get_warmed_up_estimator(steer_torques, lat_accels) - simulate_straight_road_msgs(est) - msg = est.get_msg() - assert (msg.liveTorqueParameters.latAccelOffsetRaw < -0.05) and np.isfinite(msg.liveTorqueParameters.latAccelOffsetRaw) + def test_straight_road_roll_bias(self): + steer_torques, lat_accels = generate_inputs(TORQUE_TUNE, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD) + est = get_warmed_up_estimator(steer_torques, lat_accels) + simulate_straight_road_msgs(est) + msg = est.get_msg() + assert (msg.liveTorqueParameters.latAccelOffsetRaw < -0.05) and np.isfinite(msg.liveTorqueParameters.latAccelOffsetRaw) diff --git a/openpilot/selfdrive/locationd/test/test_torqued.py b/openpilot/selfdrive/locationd/test/test_torqued.py index ac8e40fc30..4d337178a0 100644 --- a/openpilot/selfdrive/locationd/test/test_torqued.py +++ b/openpilot/selfdrive/locationd/test/test_torqued.py @@ -2,24 +2,25 @@ from opendbc.car.structs import car from openpilot.selfdrive.locationd.torqued import TorqueEstimator -def test_cal_percent(): - est = TorqueEstimator(car.CarParams()) - msg = est.get_msg() - assert msg.liveTorqueParameters.calPerc == 0 +class TestTorqued: + def test_cal_percent(self): + est = TorqueEstimator(car.CarParams()) + msg = est.get_msg() + assert msg.liveTorqueParameters.calPerc == 0 - for (low, high), min_pts in zip(est.filtered_points.buckets.keys(), - est.filtered_points.buckets_min_points.values(), strict=True): - for _ in range(int(min_pts)): - est.filtered_points.add_point((low + high) / 2.0, 0.0) + for (low, high), min_pts in zip(est.filtered_points.buckets.keys(), + est.filtered_points.buckets_min_points.values(), strict=True): + for _ in range(int(min_pts)): + est.filtered_points.add_point((low + high) / 2.0, 0.0) - # enough bucket points, but not enough total points - msg = est.get_msg() - assert msg.liveTorqueParameters.calPerc == (len(est.filtered_points) / est.min_points_total * 100 + 100) / 2 + # enough bucket points, but not enough total points + msg = est.get_msg() + assert msg.liveTorqueParameters.calPerc == (len(est.filtered_points) / est.min_points_total * 100 + 100) / 2 - # add enough points to bucket with most capacity - key = list(est.filtered_points.buckets)[0] - for _ in range(est.min_points_total - len(est.filtered_points)): - est.filtered_points.add_point((key[0] + key[1]) / 2.0, 0.0) + # add enough points to bucket with most capacity + key = list(est.filtered_points.buckets)[0] + for _ in range(est.min_points_total - len(est.filtered_points)): + est.filtered_points.add_point((key[0] + key[1]) / 2.0, 0.0) - msg = est.get_msg() - assert msg.liveTorqueParameters.calPerc == 100 + msg = est.get_msg() + assert msg.liveTorqueParameters.calPerc == 100 diff --git a/openpilot/selfdrive/ui/mici/tests/test_widget_leaks.py b/openpilot/selfdrive/ui/mici/tests/test_widget_leaks.py index 09142a8e11..68c61f4ff3 100755 --- a/openpilot/selfdrive/ui/mici/tests/test_widget_leaks.py +++ b/openpilot/selfdrive/ui/mici/tests/test_widget_leaks.py @@ -41,80 +41,81 @@ def get_child_widgets(widget) -> list: return children -@pytest.mark.skip(reason="segfaults") -def test_dialogs_do_not_leak(): - import pyray as rl - rl.set_config_flags(rl.ConfigFlags.FLAG_WINDOW_HIDDEN) - from openpilot.system.ui.lib.application import gui_app +class TestWidgetLeaks: + @pytest.mark.skip(reason="segfaults") + def test_dialogs_do_not_leak(self): + import pyray as rl + rl.set_config_flags(rl.ConfigFlags.FLAG_WINDOW_HIDDEN) + from openpilot.system.ui.lib.application import gui_app - # mici dialogs - from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide as MiciTrainingGuide, OnboardingWindow as MiciOnboardingWindow - from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog as MiciDriverCameraDialog - from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog as MiciPairingDialog - from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialog, BigInputDialog - from openpilot.selfdrive.ui.mici.layouts.settings.device import MiciFccModal + # mici dialogs + from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide as MiciTrainingGuide, OnboardingWindow as MiciOnboardingWindow + from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog as MiciDriverCameraDialog + from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog as MiciPairingDialog + from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialog, BigInputDialog + from openpilot.selfdrive.ui.mici.layouts.settings.device import MiciFccModal - # tici dialogs - from openpilot.selfdrive.ui.onroad.driver_camera_dialog import DriverCameraDialog as TiciDriverCameraDialog - from openpilot.selfdrive.ui.layouts.onboarding import OnboardingWindow as TiciOnboardingWindow - from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog as TiciPairingDialog - from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog - from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog - from openpilot.system.ui.widgets.html_render import HtmlModal - from openpilot.system.ui.widgets.keyboard import Keyboard + # tici dialogs + from openpilot.selfdrive.ui.onroad.driver_camera_dialog import DriverCameraDialog as TiciDriverCameraDialog + from openpilot.selfdrive.ui.layouts.onboarding import OnboardingWindow as TiciOnboardingWindow + from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog as TiciPairingDialog + from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog + from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog + from openpilot.system.ui.widgets.html_render import HtmlModal + from openpilot.system.ui.widgets.keyboard import Keyboard - gui_app.init_window("ref-test") + gui_app.init_window("ref-test") - leaked_widgets = set() + leaked_widgets = set() - for ctor in ( - # mici - MiciDriverCameraDialog, MiciPairingDialog, - lambda: MiciTrainingGuide(lambda: None), - lambda: MiciOnboardingWindow(lambda: None), - lambda: BigDialog("test", "test"), - lambda: BigConfirmationDialog("test", gui_app.texture("icons_mici/settings/network/new/trash.png", 54, 64), lambda: None), - lambda: BigInputDialog("test"), - lambda: MiciFccModal(text="test"), - # tici - TiciDriverCameraDialog, TiciOnboardingWindow, TiciPairingDialog, Keyboard, - lambda: ConfirmDialog("test", "ok"), - lambda: MultiOptionDialog("test", ["a", "b"]), - lambda: HtmlModal(text="test"), - ): - widget = ctor() - all_refs = [weakref.ref(w) for w in get_child_widgets(widget) + [widget]] + for ctor in ( + # mici + MiciDriverCameraDialog, MiciPairingDialog, + lambda: MiciTrainingGuide(lambda: None), + lambda: MiciOnboardingWindow(lambda: None), + lambda: BigDialog("test", "test"), + lambda: BigConfirmationDialog("test", gui_app.texture("icons_mici/settings/network/new/trash.png", 54, 64), lambda: None), + lambda: BigInputDialog("test"), + lambda: MiciFccModal(text="test"), + # tici + TiciDriverCameraDialog, TiciOnboardingWindow, TiciPairingDialog, Keyboard, + lambda: ConfirmDialog("test", "ok"), + lambda: MultiOptionDialog("test", ["a", "b"]), + lambda: HtmlModal(text="test"), + ): + widget = ctor() + all_refs = [weakref.ref(w) for w in get_child_widgets(widget) + [widget]] - del widget + del widget - for ref in all_refs: - if ref() is not None: - obj = ref() - name = f"{type(obj).__module__}.{type(obj).__qualname__}" - leaked_widgets.add(name) + for ref in all_refs: + if ref() is not None: + obj = ref() + name = f"{type(obj).__module__}.{type(obj).__qualname__}" + leaked_widgets.add(name) - print(f"\n=== Widget {name} alive after del") - print(" Referrers:") - for r in gc.get_referrers(obj): - if r is obj: - continue + print(f"\n=== Widget {name} alive after del") + print(" Referrers:") + for r in gc.get_referrers(obj): + if r is obj: + continue - if hasattr(r, '__self__') and r.__self__ is not obj: - print(f" bound method: {type(r.__self__).__qualname__}.{r.__name__}") - elif hasattr(r, '__func__'): - print(f" method: {r.__name__}") - else: - print(f" {type(r).__module__}.{type(r).__qualname__}") - del obj + if hasattr(r, '__self__') and r.__self__ is not obj: + print(f" bound method: {type(r.__self__).__qualname__}.{r.__name__}") + elif hasattr(r, '__func__'): + print(f" method: {r.__name__}") + else: + print(f" {type(r).__module__}.{type(r).__qualname__}") + del obj - gui_app.close() + gui_app.close() - unexpected = leaked_widgets - KNOWN_LEAKS - assert not unexpected, f"New leaked widgets: {unexpected}" + unexpected = leaked_widgets - KNOWN_LEAKS + assert not unexpected, f"New leaked widgets: {unexpected}" - fixed = KNOWN_LEAKS - leaked_widgets - assert not fixed, f"These leaks are fixed, remove from KNOWN_LEAKS: {fixed}" + fixed = KNOWN_LEAKS - leaked_widgets + assert not fixed, f"These leaks are fixed, remove from KNOWN_LEAKS: {fixed}" if __name__ == "__main__": - test_dialogs_do_not_leak() + TestWidgetLeaks().test_dialogs_do_not_leak() diff --git a/openpilot/selfdrive/ui/tests/test_raylib_ui.py b/openpilot/selfdrive/ui/tests/test_raylib_ui.py index 69ba946dcd..d04c940cde 100644 --- a/openpilot/selfdrive/ui/tests/test_raylib_ui.py +++ b/openpilot/selfdrive/ui/tests/test_raylib_ui.py @@ -2,7 +2,8 @@ import time from openpilot.selfdrive.test.helpers import with_processes -@with_processes(["ui"]) -def test_raylib_ui(): - """Test initialization of the UI widgets is successful.""" - time.sleep(1) +class TestRaylibUi: + @with_processes(["ui"]) + def test_raylib_ui(self): + """Test initialization of the UI widgets is successful.""" + time.sleep(1) diff --git a/openpilot/selfdrive/ui/tests/test_translations.py b/openpilot/selfdrive/ui/tests/test_translations.py index fba595acad..9eae072f21 100644 --- a/openpilot/selfdrive/ui/tests/test_translations.py +++ b/openpilot/selfdrive/ui/tests/test_translations.py @@ -46,61 +46,59 @@ def load_po_text(po_path: Path) -> str: return po_path.read_text(encoding='utf-8') -@pytest.mark.parametrize("language_code", sorted(TRANSLATION_LANGUAGES.values())) -def test_translation_file_exists(language_code: str): - po_path = PO_DIR / f"app_{language_code}.po" - assert po_path.exists(), f"missing translation file: {po_path}" +class TestTranslations: + @pytest.mark.parametrize("language_code", sorted(TRANSLATION_LANGUAGES.values())) + def test_translation_file_exists(self, language_code: str): + po_path = PO_DIR / f"app_{language_code}.po" + assert po_path.exists(), f"missing translation file: {po_path}" + @pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name) + def test_translation_placeholders_are_preserved(self, po_path: Path): + _, entries = parse_po(po_path) + language = po_path.stem.removeprefix("app_") -@pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name) -def test_translation_placeholders_are_preserved(po_path: Path): - _, entries = parse_po(po_path) - language = po_path.stem.removeprefix("app_") + for entry in entries: + source_placeholders = extract_placeholders(entry.msgid) - for entry in entries: - source_placeholders = extract_placeholders(entry.msgid) + if entry.is_plural: + plural_placeholders = extract_placeholders(entry.msgid_plural) + message = ( + f"{language}: source plural placeholders do not match singular for " + + f"{entry.msgid!r}: {source_placeholders} vs {plural_placeholders}" + ) + assert plural_placeholders == source_placeholders, message - if entry.is_plural: - plural_placeholders = extract_placeholders(entry.msgid_plural) - message = ( - f"{language}: source plural placeholders do not match singular for " - + f"{entry.msgid!r}: {source_placeholders} vs {plural_placeholders}" - ) - assert plural_placeholders == source_placeholders, message + for idx, msgstr in sorted(entry.msgstr_plural.items()): + if not msgstr: + continue - for idx, msgstr in sorted(entry.msgstr_plural.items()): - if not msgstr: + translated_placeholders = extract_placeholders(msgstr) + message = ( + f"{language}: plural form {idx} changes placeholders for {entry.msgid!r}: " + + f"expected {source_placeholders}, got {translated_placeholders}" + ) + assert translated_placeholders == source_placeholders, message + else: + if not entry.msgstr: continue - translated_placeholders = extract_placeholders(msgstr) + translated_placeholders = extract_placeholders(entry.msgstr) message = ( - f"{language}: plural form {idx} changes placeholders for {entry.msgid!r}: " + f"{language}: translation changes placeholders for {entry.msgid!r}: " + f"expected {source_placeholders}, got {translated_placeholders}" ) assert translated_placeholders == source_placeholders, message - else: - if not entry.msgstr: - continue - translated_placeholders = extract_placeholders(entry.msgstr) - message = ( - f"{language}: translation changes placeholders for {entry.msgid!r}: " - + f"expected {source_placeholders}, got {translated_placeholders}" + @pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name) + def test_translation_refs_do_not_include_line_numbers(self, po_path: Path): + for line in load_po_text(po_path).splitlines(): + assert not LINE_NUMBER_REF_RE.match(line), ( + f"{po_path.name}: line-number source reference found: {line}" ) - assert translated_placeholders == source_placeholders, message - -@pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name) -def test_translation_refs_do_not_include_line_numbers(po_path: Path): - for line in load_po_text(po_path).splitlines(): - assert not LINE_NUMBER_REF_RE.match(line), ( - f"{po_path.name}: line-number source reference found: {line}" + @pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name) + def test_translation_entities_are_valid(self, po_path: Path): + matches = BAD_ENTITY_RE.findall(load_po_text(po_path)) + assert not matches, ( + f"{po_path.name}: found '@...;' entity typo(s): {', '.join(sorted(set(matches)))}" ) - - -@pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name) -def test_translation_entities_are_valid(po_path: Path): - matches = BAD_ENTITY_RE.findall(load_po_text(po_path)) - assert not matches, ( - f"{po_path.name}: found '@...;' entity typo(s): {', '.join(sorted(set(matches)))}" - ) diff --git a/openpilot/tools/jotpluggler/test_jotpluggler.py b/openpilot/tools/jotpluggler/test_jotpluggler.py index b9b751c4a5..0729e3b2e3 100644 --- a/openpilot/tools/jotpluggler/test_jotpluggler.py +++ b/openpilot/tools/jotpluggler/test_jotpluggler.py @@ -5,7 +5,8 @@ from pathlib import Path JOTPLUGGLER_DIR = Path(__file__).parent -def test_help(): - result = subprocess.run(["./jotpluggler", "-h"], cwd=JOTPLUGGLER_DIR, capture_output=True, text=True) - assert result.returncode == 0, result.stderr - assert "Usage:" in result.stderr +class TestJotpluggler: + def test_help(self): + result = subprocess.run(["./jotpluggler", "-h"], cwd=JOTPLUGGLER_DIR, capture_output=True, text=True) + assert result.returncode == 0, result.stderr + assert "Usage:" in result.stderr