controlsd: add state machine tests (#24107)
* Handle NO_ENTRY in PRE_ENABLED
* add test
* add preEnabled NO_ENTRY test
* stash
* test soft disable
* tuples
* remove overriding until it's merged in
* use Events class
* fix tests and split out
* don't rely on controlsd's counter
old-commit-hash: d4f330447a
taco
parent
56bfed0c15
commit
8e11fbe2db
2 changed files with 128 additions and 16 deletions
@ -0,0 +1,105 @@ |
||||
#!/usr/bin/env python3 |
||||
import unittest |
||||
|
||||
from cereal import car, log |
||||
from common.realtime import DT_CTRL |
||||
from selfdrive.car.car_helpers import interfaces |
||||
from selfdrive.controls.controlsd import Controls, SOFT_DISABLE_TIME |
||||
from selfdrive.controls.lib.events import Events, ET, Alert, Priority, AlertSize, AlertStatus, VisualAlert, \ |
||||
AudibleAlert, EVENTS |
||||
|
||||
State = log.ControlsState.OpenpilotState |
||||
|
||||
# The event types that maintain the current state |
||||
MAINTAIN_STATES = {State.enabled: None, State.disabled: None, State.softDisabling: ET.SOFT_DISABLE, |
||||
State.preEnabled: ET.PRE_ENABLE} |
||||
ALL_STATES = tuple((state for state in State.schema.enumerants.values() if |
||||
state != State.overriding)) # TODO: remove overriding exception |
||||
# The event types checked in DISABLED section of state machine |
||||
ENABLE_EVENT_TYPES = (ET.ENABLE, ET.PRE_ENABLE) |
||||
|
||||
|
||||
def make_event(event_types): |
||||
event = {} |
||||
for ev in event_types: |
||||
event[ev] = Alert("", "", AlertStatus.normal, AlertSize.small, Priority.LOW, |
||||
VisualAlert.none, AudibleAlert.none, 1.) |
||||
EVENTS[0] = event |
||||
return 0 |
||||
|
||||
|
||||
class TestStateMachine(unittest.TestCase): |
||||
|
||||
def setUp(self): |
||||
CarInterface, CarController, CarState = interfaces["mock"] |
||||
CP = CarInterface.get_params("mock") |
||||
CI = CarInterface(CP, CarController, CarState) |
||||
|
||||
self.controlsd = Controls(CI=CI) |
||||
self.controlsd.events = Events() |
||||
self.controlsd.soft_disable_timer = int(SOFT_DISABLE_TIME / DT_CTRL) |
||||
self.CS = car.CarState() |
||||
|
||||
def test_immediate_disable(self): |
||||
for state in ALL_STATES: |
||||
self.controlsd.events.add(make_event([MAINTAIN_STATES[state], ET.IMMEDIATE_DISABLE])) |
||||
self.controlsd.state = state |
||||
self.controlsd.state_transition(self.CS) |
||||
self.assertEqual(State.disabled, self.controlsd.state) |
||||
self.controlsd.events.clear() |
||||
|
||||
def test_user_disable(self): |
||||
for state in ALL_STATES: |
||||
self.controlsd.events.add(make_event([MAINTAIN_STATES[state], ET.USER_DISABLE])) |
||||
self.controlsd.state = state |
||||
self.controlsd.state_transition(self.CS) |
||||
self.assertEqual(State.disabled, self.controlsd.state) |
||||
self.controlsd.events.clear() |
||||
|
||||
def test_soft_disable(self): |
||||
for state in ALL_STATES: |
||||
if state == State.preEnabled: # preEnabled considers NO_ENTRY instead |
||||
continue |
||||
self.controlsd.events.add(make_event([MAINTAIN_STATES[state], ET.SOFT_DISABLE])) |
||||
self.controlsd.state = state |
||||
self.controlsd.state_transition(self.CS) |
||||
self.assertEqual(self.controlsd.state, State.disabled if state == State.disabled else State.softDisabling) |
||||
self.controlsd.events.clear() |
||||
|
||||
def test_soft_disable_timer(self): |
||||
self.controlsd.state = State.enabled |
||||
self.controlsd.events.add(make_event([ET.SOFT_DISABLE])) |
||||
self.controlsd.state_transition(self.CS) |
||||
for _ in range(int(SOFT_DISABLE_TIME / DT_CTRL)): |
||||
self.assertEqual(self.controlsd.state, State.softDisabling) |
||||
self.controlsd.state_transition(self.CS) |
||||
|
||||
self.assertEqual(self.controlsd.state, State.disabled) |
||||
|
||||
def test_no_entry(self): |
||||
# disabled with enable events |
||||
for et in ENABLE_EVENT_TYPES: |
||||
self.controlsd.events.add(make_event([ET.NO_ENTRY, et])) |
||||
self.controlsd.state_transition(self.CS) |
||||
self.assertEqual(self.controlsd.state, State.disabled) |
||||
self.controlsd.events.clear() |
||||
|
||||
def test_no_entry_pre_enable(self): |
||||
# preEnabled with preEnabled event |
||||
self.controlsd.state = State.preEnabled |
||||
self.controlsd.events.add(make_event([ET.NO_ENTRY, ET.PRE_ENABLE])) |
||||
self.controlsd.state_transition(self.CS) |
||||
self.assertEqual(self.controlsd.state, State.disabled) |
||||
|
||||
def test_maintain_states(self): |
||||
# Given current state's event type, we should maintain state |
||||
for state in ALL_STATES: |
||||
self.controlsd.state = state |
||||
self.controlsd.events.add(make_event([MAINTAIN_STATES[state]])) |
||||
self.controlsd.state_transition(self.CS) |
||||
self.assertEqual(self.controlsd.state, state) |
||||
self.controlsd.events.clear() |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
unittest.main() |
Loading…
Reference in new issue