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