mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-30 11:23:49 +08:00
180 lines
7.3 KiB
Python
180 lines
7.3 KiB
Python
"""Exercise physical event reads without a controller, native IPC, or vehicle actions."""
|
|
import ast
|
|
import os
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from openpilot.starpilot.system.wheel_controls import wheel_controlsd
|
|
from test_wheel_controlsd import FakeParams, source
|
|
|
|
|
|
PRESS_PARAM = "StandbyButtonPressTime"
|
|
|
|
|
|
@pytest.fixture
|
|
def input_pipe():
|
|
params = FakeParams({"ScreenManagement": True, "StandbyMode": True, "StandbyWakeButton": True})
|
|
memory = FakeParams()
|
|
daemon = wheel_controlsd.WheelControlsDaemon(params, memory)
|
|
read_fd, write_fd = os.pipe()
|
|
os.set_blocking(read_fd, False)
|
|
daemon.sources[read_fd] = source()
|
|
daemon.buffers[read_fd] = bytearray()
|
|
|
|
def send(event_type, code, value):
|
|
os.write(write_fd, wheel_controlsd.INPUT_EVENT.pack(0, 0, event_type, code, value))
|
|
daemon._read_events(read_fd)
|
|
|
|
yield daemon, params, memory, read_fd, send
|
|
os.close(write_fd)
|
|
daemon.close()
|
|
|
|
|
|
def test_unmapped_key_press_publishes_once_until_released(input_pipe, monkeypatch):
|
|
_daemon, _params, memory, _fd, send = input_pipe
|
|
now = [100]
|
|
monkeypatch.setattr(wheel_controlsd.time, "monotonic_ns", lambda: now[0])
|
|
|
|
send(wheel_controlsd.EV_KEY, 304, 1)
|
|
assert memory.get_int(PRESS_PARAM) == 100
|
|
now[0] = 200
|
|
for value in (2, 1, 2, 0):
|
|
send(wheel_controlsd.EV_KEY, 304, value)
|
|
assert memory.get_int(PRESS_PARAM) == 100
|
|
send(wheel_controlsd.EV_KEY, 304, 1)
|
|
assert memory.get_int(PRESS_PARAM) == 200
|
|
|
|
|
|
def test_selected_joystick_buttons_wake_without_executing_mappings(input_pipe, monkeypatch):
|
|
_daemon, params, memory, _fd, send = input_pipe
|
|
wheel_controlsd.upsert_mapping(source(), 304, 0, params)
|
|
wheel_controlsd.set_joystick_device(source().device_id, True, params)
|
|
actions = []
|
|
monkeypatch.setattr(wheel_controlsd, "execute_mapping_slot", lambda slot, *_args: actions.append(slot))
|
|
monkeypatch.setattr(wheel_controlsd.time, "monotonic_ns", lambda: 123456789)
|
|
|
|
send(wheel_controlsd.EV_KEY, 304, 1)
|
|
assert memory.get_int(PRESS_PARAM) == 123456789
|
|
assert actions == []
|
|
|
|
|
|
@pytest.mark.parametrize("disabled_key", ["ScreenManagement", "StandbyMode", "StandbyWakeButton"])
|
|
def test_enabling_standby_while_held_does_not_create_a_press(input_pipe, monkeypatch, disabled_key):
|
|
_daemon, params, memory, _fd, send = input_pipe
|
|
params.put_bool(disabled_key, False)
|
|
monkeypatch.setattr(wheel_controlsd.time, "monotonic_ns", lambda: 100)
|
|
send(wheel_controlsd.EV_KEY, 304, 1)
|
|
assert memory.get(PRESS_PARAM) is None
|
|
params.put_bool(disabled_key, True)
|
|
send(wheel_controlsd.EV_KEY, 304, 2)
|
|
send(wheel_controlsd.EV_KEY, 304, 1)
|
|
assert memory.get(PRESS_PARAM) is None
|
|
send(wheel_controlsd.EV_KEY, 304, 0)
|
|
send(wheel_controlsd.EV_KEY, 304, 1)
|
|
assert memory.get_int(PRESS_PARAM) == 100
|
|
|
|
|
|
def test_dpad_direction_edges_wake_but_neutral_repeats_and_analog_axes_do_not(input_pipe, monkeypatch):
|
|
_daemon, _params, memory, _fd, send = input_pipe
|
|
now = [100]
|
|
monkeypatch.setattr(wheel_controlsd.time, "monotonic_ns", lambda: now[0])
|
|
|
|
send(wheel_controlsd.EV_ABS, 0, 32767)
|
|
send(wheel_controlsd.EV_ABS, wheel_controlsd.ABS_HAT0X, 0)
|
|
assert memory.get(PRESS_PARAM) is None
|
|
send(wheel_controlsd.EV_ABS, wheel_controlsd.ABS_HAT0X, -1)
|
|
assert memory.get_int(PRESS_PARAM) == 100
|
|
now[0] = 200
|
|
send(wheel_controlsd.EV_ABS, wheel_controlsd.ABS_HAT0X, -1)
|
|
assert memory.get_int(PRESS_PARAM) == 100
|
|
send(wheel_controlsd.EV_ABS, wheel_controlsd.ABS_HAT0X, 1)
|
|
assert memory.get_int(PRESS_PARAM) == 200
|
|
now[0] = 300
|
|
send(wheel_controlsd.EV_ABS, wheel_controlsd.ABS_HAT0X, 0)
|
|
assert memory.get_int(PRESS_PARAM) == 200
|
|
send(wheel_controlsd.EV_ABS, wheel_controlsd.ABS_HAT0X, 1)
|
|
assert memory.get_int(PRESS_PARAM) == 300
|
|
|
|
|
|
def test_wake_only_listener_does_not_reactivate_disabled_mappings(input_pipe, monkeypatch):
|
|
_daemon, params, memory, _fd, send = input_pipe
|
|
wheel_controlsd.upsert_mapping(source(), 30, 2, params)
|
|
params.put_bool("WheelControlsEnabled", False)
|
|
actions = []
|
|
monkeypatch.setattr(wheel_controlsd, "execute_mapping_slot", lambda slot, *_args: actions.append(slot))
|
|
monkeypatch.setattr(wheel_controlsd.time, "monotonic_ns", lambda: 999)
|
|
|
|
send(wheel_controlsd.EV_KEY, 30, 1)
|
|
assert actions == []
|
|
assert memory.get_int(PRESS_PARAM) == 999
|
|
|
|
|
|
@pytest.mark.parametrize("wake_enabled", [False, True])
|
|
def test_enabled_mapping_still_executes_once_on_press_with_wake_timestamp(input_pipe, monkeypatch, wake_enabled):
|
|
_daemon, params, memory, _fd, send = input_pipe
|
|
wheel_controlsd.upsert_mapping(source(), 30, 2, params)
|
|
actions = []
|
|
monkeypatch.setattr(wheel_controlsd, "execute_mapping_slot", lambda slot, *_args: actions.append(slot))
|
|
monkeypatch.setattr(wheel_controlsd.time, "monotonic_ns", lambda: 777)
|
|
params.put_bool("StandbyWakeButton", wake_enabled)
|
|
|
|
for value in (1, 2, 0):
|
|
send(wheel_controlsd.EV_KEY, 30, value)
|
|
assert actions == [2]
|
|
assert memory.get(PRESS_PARAM) == (777 if wake_enabled else None)
|
|
|
|
|
|
def test_wake_timestamp_failure_does_not_interrupt_mapped_button_actions(input_pipe, monkeypatch):
|
|
_daemon, params, memory, _fd, send = input_pipe
|
|
wheel_controlsd.upsert_mapping(source(), 30, 2, params)
|
|
actions = []
|
|
monkeypatch.setattr(wheel_controlsd, "execute_mapping_slot", lambda slot, *_args: actions.append(slot))
|
|
|
|
def fail_write(key, value):
|
|
raise OSError("memory Params unavailable")
|
|
|
|
monkeypatch.setattr(memory, "put_int", fail_write)
|
|
send(wheel_controlsd.EV_KEY, 30, 1)
|
|
assert actions == [2]
|
|
|
|
|
|
def test_disconnected_device_does_not_suppress_next_press_on_reused_descriptor(input_pipe, monkeypatch):
|
|
daemon, _params, memory, fd, send = input_pipe
|
|
now = [100]
|
|
monkeypatch.setattr(wheel_controlsd.time, "monotonic_ns", lambda: now[0])
|
|
send(wheel_controlsd.EV_KEY, 304, 1)
|
|
assert memory.get_int(PRESS_PARAM) == 100
|
|
|
|
# Keep this test's pipe open while exercising the real per-device cleanup.
|
|
with monkeypatch.context() as patch:
|
|
patch.setattr(wheel_controlsd.os, "close", lambda _fd: None)
|
|
daemon._remove(fd)
|
|
daemon.sources[fd] = source()
|
|
daemon.buffers[fd] = bytearray()
|
|
now[0] = 200
|
|
send(wheel_controlsd.EV_KEY, 304, 1)
|
|
assert memory.get_int(PRESS_PARAM) == 200
|
|
|
|
|
|
@pytest.mark.parametrize("started", [False, True])
|
|
@pytest.mark.parametrize("mapping,management,standby,button,expected", [
|
|
(False, False, False, False, False),
|
|
(False, True, True, True, True),
|
|
(False, False, True, True, False),
|
|
(False, True, False, True, False),
|
|
(False, True, True, False, False),
|
|
(True, False, False, False, True),
|
|
])
|
|
def test_manager_runs_listener_for_enabled_mappings_or_standby(started, mapping, management, standby, button, expected):
|
|
# The manager module creates native processes at import, so load its real predicate only.
|
|
path = Path(__file__).resolve().parents[4] / "system/manager/process_config.py"
|
|
tree = ast.parse(path.read_text())
|
|
node = next(node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name == "wheel_controls_enabled")
|
|
namespace = {"Params": object, "car": SimpleNamespace(CarParams=object), "SimpleNamespace": SimpleNamespace}
|
|
exec(compile(ast.Module(body=[node], type_ignores=[]), str(path), "exec"), namespace)
|
|
params = FakeParams({"WheelControlsEnabled": mapping, "ScreenManagement": management,
|
|
"StandbyMode": standby, "StandbyWakeButton": button})
|
|
assert namespace["wheel_controls_enabled"](started, params, None, SimpleNamespace()) is expected
|