import pathlib
import shutil
import jinja2
import matplotlib . pyplot as plt
import numpy as np
import os
import pyautogui
import pywinctl
import time
import unittest
from parameterized import parameterized
from cereal import messaging , car , log
from cereal . visionipc import VisionIpcServer , VisionStreamType
from cereal . messaging import SubMaster , PubMaster
from openpilot . common . params import Params
from openpilot . common . realtime import DT_MDL
from openpilot . common . transformations . camera import tici_f_frame_size
from openpilot . selfdrive . test . helpers import with_processes
from openpilot . selfdrive . test . process_replay . vision_meta import meta_from_camera_state
from openpilot . tools . webcam . camera import Camera
UI_DELAY = 0.5 # may be slower on CI?
NetworkType = log . DeviceState . NetworkType
NetworkStrength = log . DeviceState . NetworkStrength
EventName = car . CarEvent . EventName
EVENTS_BY_NAME = { v : k for k , v in EventName . schema . enumerants . items ( ) }
def setup_common ( click , pm : PubMaster ) :
Params ( ) . put ( " DongleId " , " 123456789012345 " )
dat = messaging . new_message ( ' deviceState ' )
dat . deviceState . started = True
dat . deviceState . networkType = NetworkType . cell4G
dat . deviceState . networkStrength = NetworkStrength . moderate
dat . deviceState . freeSpacePercent = 80
dat . deviceState . memoryUsagePercent = 2
dat . deviceState . cpuTempC = [ 2 , ] * 3
dat . deviceState . gpuTempC = [ 2 , ] * 3
dat . deviceState . cpuUsagePercent = [ 2 , ] * 8
pm . send ( " deviceState " , dat )
time . sleep ( UI_DELAY )
def setup_homescreen ( click , pm : PubMaster ) :
setup_common ( click , pm )
def setup_settings_device ( click , pm : PubMaster ) :
setup_common ( click , pm )
click ( 100 , 100 )
time . sleep ( UI_DELAY )
def setup_settings_network ( click , pm : PubMaster ) :
setup_common ( click , pm )
setup_settings_device ( click , pm )
click ( 300 , 600 )
time . sleep ( UI_DELAY )
def setup_onroad ( click , pm : PubMaster ) :
setup_common ( click , pm )
dat = messaging . new_message ( ' pandaStates ' , 1 )
dat . pandaStates [ 0 ] . ignitionLine = True
dat . pandaStates [ 0 ] . pandaType = log . PandaState . PandaType . uno
pm . send ( " pandaStates " , dat )
server = VisionIpcServer ( " camerad " )
server . create_buffers ( VisionStreamType . VISION_STREAM_ROAD , 40 , False , * tici_f_frame_size )
server . create_buffers ( VisionStreamType . VISION_STREAM_DRIVER , 40 , False , * tici_f_frame_size )
server . create_buffers ( VisionStreamType . VISION_STREAM_WIDE_ROAD , 40 , False , * tici_f_frame_size )
server . start_listener ( )
time . sleep ( UI_DELAY )
IMG = Camera . bgr2nv12 ( np . random . randint ( 0 , 255 , ( * tici_f_frame_size , 3 ) , dtype = np . uint8 ) )
IMG_BYTES = IMG . flatten ( ) . tobytes ( )
cams = ( ' roadCameraState ' , ' wideRoadCameraState ' )
frame_id = 0
for cam in cams :
msg = messaging . new_message ( cam )
cs = getattr ( msg , cam )
cs . frameId = frame_id
cs . timestampSof = int ( ( frame_id * DT_MDL ) * 1e9 )
cs . timestampEof = int ( ( frame_id * DT_MDL ) * 1e9 )
cam_meta = meta_from_camera_state ( cam )
pm . send ( msg . which ( ) , msg )
server . send ( cam_meta . stream , IMG_BYTES , cs . frameId , cs . timestampSof , cs . timestampEof )
time . sleep ( UI_DELAY )
def setup_onroad_map ( click , pm : PubMaster ) :
setup_onroad ( click , pm )
click ( 500 , 500 )
time . sleep ( UI_DELAY )
def setup_onroad_sidebar ( click , pm : PubMaster ) :
setup_onroad_map ( click , pm )
click ( 500 , 500 )
time . sleep ( UI_DELAY )
CASES = {
" homescreen " : setup_homescreen ,
" settings_device " : setup_settings_device ,
" settings_network " : setup_settings_network ,
" onroad " : setup_onroad ,
" onroad_map " : setup_onroad_map ,
" onroad_map_sidebar " : setup_onroad_sidebar
}
TEST_DIR = pathlib . Path ( __file__ ) . parent
TEST_OUTPUT_DIR = TEST_DIR / " report "
SCREENSHOTS_DIR = TEST_OUTPUT_DIR / " screenshots "
class TestUI ( unittest . TestCase ) :
@classmethod
def setUpClass ( cls ) :
os . environ [ " SCALE " ] = " 1 "
def setup ( self ) :
self . sm = SubMaster ( [ " uiDebug " ] )
self . pm = PubMaster ( [ " deviceState " , " pandaStates " , " controlsState " , ' roadCameraState ' , ' wideRoadCameraState ' ] )
while not self . sm . valid [ " uiDebug " ] :
self . sm . update ( 1 )
time . sleep ( UI_DELAY ) # wait a bit more for the UI to finish rendering
self . ui = pywinctl . getWindowsWithTitle ( " ui " ) [ 0 ]
def screenshot ( self ) :
im = pyautogui . screenshot ( region = ( self . ui . left , self . ui . top , self . ui . width , self . ui . height ) )
self . assertEqual ( im . width , 2160 )
self . assertEqual ( im . height , 1080 )
img = np . array ( im )
im . close ( )
return img
def click ( self , x , y , * args , * * kwargs ) :
pyautogui . click ( self . ui . left + x , self . ui . top + y , * args , * * kwargs )
@parameterized . expand ( CASES . items ( ) )
@with_processes ( [ " ui " ] )
def test_ui ( self , name , setup_case ) :
self . setup ( )
setup_case ( self . click , self . pm )
im = self . screenshot ( )
plt . imsave ( SCREENSHOTS_DIR / f " { name } .png " , im )
def create_html_report ( ) :
OUTPUT_FILE = TEST_OUTPUT_DIR / " index.html "
with open ( TEST_DIR / " template.html " ) as f :
template = jinja2 . Template ( f . read ( ) )
cases = { f . stem : ( str ( f . relative_to ( TEST_OUTPUT_DIR ) ) , " reference.png " ) for f in SCREENSHOTS_DIR . glob ( " *.png " ) }
cases = dict ( sorted ( cases . items ( ) ) )
with open ( OUTPUT_FILE , " w " ) as f :
f . write ( template . render ( cases = cases ) )
def create_screenshots ( ) :
if TEST_OUTPUT_DIR . exists ( ) :
shutil . rmtree ( TEST_OUTPUT_DIR )
SCREENSHOTS_DIR . mkdir ( parents = True )
unittest . main ( exit = False )
if __name__ == " __main__ " :
print ( " creating test screenshots " )
create_screenshots ( )
print ( " creating html report " )
create_html_report ( )