#!/usr/bin/env python3
import subprocess
import time
import unittest
import os
from multiprocessing import Queue
from cereal import messaging
from common . basedir import BASEDIR
from selfdrive . manager . helpers import unblock_stdout
from tools . sim import bridge
from tools . sim . bridge import CarlaBridge
CI = " CI " in os . environ
SIM_DIR = os . path . join ( BASEDIR , " tools/sim " )
class TestCarlaIntegration ( unittest . TestCase ) :
"""
Tests need Carla simulator to run
"""
processes = None
carla_process = None
def setUp ( self ) :
self . processes = [ ]
if not CI :
# We want to make sure that carla_sim docker isn't still running.
subprocess . run ( " docker rm -f carla_sim " , shell = True , stderr = subprocess . PIPE , check = False )
self . carla_process = subprocess . Popen ( " ./start_carla.sh " , cwd = SIM_DIR )
# Too many lagging messages in bridge.py can cause a crash. This prevents it.
unblock_stdout ( )
# Wait 10 seconds to startup carla
time . sleep ( 10 )
def test_engage ( self ) :
# Startup manager and bridge.py. Check processes are running, then engage and verify.
p_manager = subprocess . Popen ( " ./launch_openpilot.sh " , cwd = SIM_DIR )
self . processes . append ( p_manager )
sm = messaging . SubMaster ( [ ' controlsState ' , ' carEvents ' , ' managerState ' ] )
q = Queue ( )
carla_bridge = CarlaBridge ( bridge . parse_args ( [ ] ) )
p_bridge = carla_bridge . run ( q , retries = 10 )
self . processes . append ( p_bridge )
max_time_per_step = 60
# Wait for bridge to startup
start_waiting = time . monotonic ( )
while not carla_bridge . started and time . monotonic ( ) < start_waiting + max_time_per_step :
time . sleep ( 0.1 )
self . assertEqual ( p_bridge . exitcode , None , f " Bridge process should be running, but exited with code { p_bridge . exitcode } " )
start_time = time . monotonic ( )
no_car_events_issues_once = False
car_event_issues = [ ]
not_running = [ ]
while time . monotonic ( ) < start_time + max_time_per_step :
sm . update ( )
not_running = [ p . name for p in sm [ ' managerState ' ] . processes if not p . running and p . shouldBeRunning ]
car_event_issues = [ event . name for event in sm [ ' carEvents ' ] if any ( [ event . noEntry , event . softDisable , event . immediateDisable ] ) ]
if sm . all_alive ( ) and len ( car_event_issues ) == 0 and len ( not_running ) == 0 :
no_car_events_issues_once = True
break
self . assertTrue ( no_car_events_issues_once , f " Failed because no messages received, or CarEvents ' { car_event_issues } ' or processes not running ' { not_running } ' " )
start_time = time . monotonic ( )
min_counts_control_active = 100
control_active = 0
while time . monotonic ( ) < start_time + max_time_per_step :
sm . update ( )
q . put ( " cruise_down " ) # Try engaging
if sm . all_alive ( ) and sm [ ' controlsState ' ] . active :
control_active + = 1
if control_active == min_counts_control_active :
break
self . assertEqual ( min_counts_control_active , control_active , f " Simulator did not engage a minimal of { min_counts_control_active } steps was { control_active } " )
def tearDown ( self ) :
print ( " Test shutting down. CommIssues are acceptable " )
for p in reversed ( self . processes ) :
p . terminate ( )
for p in reversed ( self . processes ) :
if isinstance ( p , subprocess . Popen ) :
p . wait ( 15 )
else :
p . join ( 15 )
# Stop carla simulator by removing docker container
subprocess . run ( " docker rm -f carla_sim " , shell = True , stderr = subprocess . PIPE , check = False )
if self . carla_process is not None :
self . carla_process . wait ( )
if __name__ == " __main__ " :
unittest . main ( )