378 lines
17 KiB
Python
378 lines
17 KiB
Python
|
#!/usr/bin/env python
|
||
|
# -*- coding: utf-8 -*-
|
||
|
#
|
||
|
# Copyright 2017 The Android Open Source Project
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
#
|
||
|
|
||
|
"""
|
||
|
This module tests the Vehicle HAL using adb socket.
|
||
|
|
||
|
Protocol Buffer:
|
||
|
This module relies on VehicleHalProto_pb2.py being in sync with the protobuf in the VHAL.
|
||
|
If the VehicleHalProto.proto file has changed, re-generate the python version using
|
||
|
a command of the form:
|
||
|
protoc -I=<proto_dir> --python_out=<out_dir> <proto_dir>/VehicleHalProto.proto
|
||
|
For example:
|
||
|
protoDir=$ANDROID_BUILD_TOP/hardware/interfaces/automotive/vehicle/2.0/default/impl/vhal_v2_0/proto
|
||
|
outDir=$ANDROID_BUILD_TOP/packages/services/Car/tools/emulator
|
||
|
protoc -I=$protoDir --python_out=$outDir $protoDir/VehicleHalProto.proto
|
||
|
"""
|
||
|
|
||
|
from __future__ import print_function
|
||
|
|
||
|
# Suppress .pyc files
|
||
|
import sys
|
||
|
sys.dont_write_bytecode = True
|
||
|
|
||
|
import VehicleHalProto_pb2
|
||
|
import vhal_consts_2_0
|
||
|
import vhal_emulator
|
||
|
import logging
|
||
|
|
||
|
class VhalTest:
|
||
|
# Global vars
|
||
|
_badProps = [0, 0x3FFFFFFF] # List of bad properties to try for negative tests
|
||
|
_configs = 0 # List of configs from DUT
|
||
|
_log = 0 # Logger module
|
||
|
_vhal = 0 # Handle to VHAL object that communicates over socket to DUT
|
||
|
# TODO: b/38203109 - Fix OBD2 values, implement handling for complex properties
|
||
|
_skipProps = [
|
||
|
vhal_consts_2_0.VEHICLEPROPERTY_OBD2_LIVE_FRAME,
|
||
|
vhal_consts_2_0.VEHICLEPROPERTY_OBD2_FREEZE_FRAME,
|
||
|
vhal_consts_2_0.VEHICLEPROPERTY_OBD2_FREEZE_FRAME_INFO,
|
||
|
vhal_consts_2_0.VEHICLEPROPERTY_OBD2_FREEZE_FRAME_CLEAR,
|
||
|
vhal_consts_2_0.VEHICLEPROPERTY_VEHICLE_MAP_SERVICE,
|
||
|
vhal_consts_2_0.VEHICLEPROPERTY_WHEEL_TICK, # Need to support complex properties
|
||
|
0x21E00666 # FakeDataControllingProperty - an internal test property
|
||
|
]
|
||
|
|
||
|
def _getMidpoint(self, minVal, maxVal):
|
||
|
retVal = minVal + (maxVal - minVal)/2
|
||
|
return retVal
|
||
|
|
||
|
# Generates a test value based on the config
|
||
|
def _generateTestValue(self, cfg, idx, origValue):
|
||
|
valType = cfg.value_type
|
||
|
if valType in self._types.TYPE_STRING:
|
||
|
testValue = "test string"
|
||
|
elif valType in self._types.TYPE_BYTES:
|
||
|
# Generate array of integers counting from 0
|
||
|
testValue = list(range(len(origValue)))
|
||
|
elif valType == vhal_consts_2_0.VEHICLEPROPERTYTYPE_BOOLEAN:
|
||
|
testValue = origValue ^ 1
|
||
|
elif valType in self._types.TYPE_INT32:
|
||
|
try:
|
||
|
testValue = self._getMidpoint(cfg.area_configs[idx].min_int32_value,
|
||
|
cfg.area_configs[idx].max_int32_value)
|
||
|
except:
|
||
|
# min/max values aren't set. Set a hard-coded value
|
||
|
testValue = 123
|
||
|
elif valType in self._types.TYPE_INT64:
|
||
|
try:
|
||
|
testValue = self._getMidpoint(cfg.area_configs[idx].min_int64_value,
|
||
|
cfg.area_configs[idx].max_int64_value)
|
||
|
except:
|
||
|
# min/max values aren't set. Set a large hard-coded value
|
||
|
testValue = 1 << 50
|
||
|
elif valType in self._types.TYPE_FLOAT:
|
||
|
try:
|
||
|
testValue = self._getMidpoint(cfg.area_configs[idx].min_float_value,
|
||
|
cfg.area_configs[idx].max_float_value)
|
||
|
except:
|
||
|
# min/max values aren't set. Set a hard-coded value
|
||
|
testValue = 123.456
|
||
|
# Truncate float to 5 decimal places
|
||
|
testValue = "%.5f" % testValue
|
||
|
testValue = float(testValue)
|
||
|
else:
|
||
|
self._log.error("generateTestValue: valType=0x%X is not handled", valType)
|
||
|
testValue = None
|
||
|
return testValue
|
||
|
|
||
|
# Helper function to extract values array from rxMsg
|
||
|
def _getValueFromMsg(self, rxMsg):
|
||
|
# Check to see only one property value is returned
|
||
|
if len(rxMsg.value) != 1:
|
||
|
self._log.error("getValueFromMsg: Received invalid value")
|
||
|
value = None
|
||
|
else:
|
||
|
valType = rxMsg.value[0].value_type
|
||
|
try:
|
||
|
if valType in self._types.TYPE_STRING:
|
||
|
value = rxMsg.value[0].string_value
|
||
|
elif valType in self._types.TYPE_BYTES:
|
||
|
value = rxMsg.value[0].bytes_value
|
||
|
elif valType == vhal_consts_2_0.VEHICLEPROPERTYTYPE_BOOLEAN:
|
||
|
value = rxMsg.value[0].int32_values[0]
|
||
|
elif valType in self._types.TYPE_INT32:
|
||
|
value = rxMsg.value[0].int32_values[0]
|
||
|
elif valType in self._types.TYPE_INT64:
|
||
|
value = rxMsg.value[0].int64_values[0]
|
||
|
elif valType in self._types.TYPE_FLOAT:
|
||
|
value = rxMsg.value[0].float_values[0]
|
||
|
# Truncate float to 5 decimal places
|
||
|
value = "%.5f" % value
|
||
|
value = float(value)
|
||
|
elif valType in self._types.TYPE_MIXED:
|
||
|
# Quick stub to unblock most tests
|
||
|
# Todo: proper implement according to VehiclePropertyType in types.hal
|
||
|
value = rxMsg.value[0].float_values[0]
|
||
|
value = "%.5f" % value
|
||
|
value = float(value)
|
||
|
else:
|
||
|
self._log.error("getValueFromMsg: valType=0x%X is not handled", valType)
|
||
|
value = None
|
||
|
except IndexError:
|
||
|
self._log.error("getValueFromMsg: Received malformed message: %s", str(rxMsg))
|
||
|
value = None
|
||
|
return value
|
||
|
|
||
|
def _validateVmsMessage(self, rxMsg):
|
||
|
return (len(rxMsg.value) == 1 and rxMsg.value[0].value_type in self._types.TYPE_MIXED and
|
||
|
len(rxMsg.value[0].int32_values) > 0 and
|
||
|
vhal_consts_2_0.VMSMESSAGETYPE_SUBSCRIBE <= rxMsg.value[0].int32_values[0]
|
||
|
<= vhal_consts_2_0.VMSMESSAGETYPE_LAST_VMS_MESSAGE_TYPE)
|
||
|
|
||
|
def _getVmsMessageTypeFromMsg(self, rxMsg):
|
||
|
if self._validateVmsMessage(rxMsg):
|
||
|
value = rxMsg.value[0].int32_values[
|
||
|
vhal_consts_2_0.VMSBASEMESSAGEINTEGERVALUESINDEX_MESSAGE_TYPE]
|
||
|
else:
|
||
|
self._log.error("getVmsMessageTypeFromMsg: Received invalid message")
|
||
|
value = None
|
||
|
return value
|
||
|
|
||
|
# Helper function to receive a message and validate the type and status
|
||
|
# retVal = 1 if no errors
|
||
|
# retVal = 0 if errors detected
|
||
|
def _rxMsgAndValidate(self, expectedType, expectedStatus):
|
||
|
retVal = 1
|
||
|
rxMsg = self._vhal.rxMsg()
|
||
|
if rxMsg.msg_type != expectedType:
|
||
|
self._log.error("rxMsg Type expected: 0x%X, received: 0x%X", expectedType, rxMsg.msg_type)
|
||
|
retVal = 0
|
||
|
if rxMsg.status != expectedStatus:
|
||
|
self._log.error("rxMsg Status expected: 0x%X, received: 0x%X", expectedStatus, rxMsg.status)
|
||
|
retVal = 0
|
||
|
return rxMsg, retVal
|
||
|
|
||
|
# Calls getConfig() on each individual property ID and verifies it matches with the config
|
||
|
# received in getConfigAll()
|
||
|
def testGetConfig(self):
|
||
|
self._log.info("Starting testGetConfig...")
|
||
|
for cfg in self._configs:
|
||
|
self._log.debug(" Getting config for propId=0x%X", cfg.prop)
|
||
|
self._vhal.getConfig(cfg.prop)
|
||
|
rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_CONFIG_RESP,
|
||
|
VehicleHalProto_pb2.RESULT_OK)
|
||
|
if retVal:
|
||
|
if rxMsg.config[0] != cfg:
|
||
|
self._log.error("testGetConfig failed. prop=0x%X, expected:\n%s\nreceived:\n%s",
|
||
|
cfg.prop, str(cfg), str(rxMsg.config))
|
||
|
self._log.info(" Finished testGetConfig!")
|
||
|
|
||
|
# Calls getConfig() on invalid property ID and verifies it generates an error
|
||
|
def testGetBadConfig(self):
|
||
|
self._log.info("Starting testGetBadConfig...")
|
||
|
for prop in self._badProps:
|
||
|
self._log.debug(" Testing bad propId=0x%X", prop)
|
||
|
self._vhal.getConfig(prop)
|
||
|
rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_CONFIG_RESP,
|
||
|
VehicleHalProto_pb2.ERROR_INVALID_PROPERTY)
|
||
|
if retVal:
|
||
|
for cfg in rxMsg.config:
|
||
|
self._log.error("testGetBadConfig prop=0x%X, expected:None, received:\n%s",
|
||
|
cfg.prop, str(rxMsg.config))
|
||
|
self._log.info(" Finished testGetBadConfig!")
|
||
|
|
||
|
def testGetPropertyAll(self):
|
||
|
self._log.info("Starting testGetPropertyAll...")
|
||
|
self._vhal.getPropertyAll()
|
||
|
rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_PROPERTY_ALL_RESP,
|
||
|
VehicleHalProto_pb2.RESULT_OK)
|
||
|
if retVal == 0:
|
||
|
self._log.error("testGetPropertyAll: Failed to receive proper rxMsg")
|
||
|
|
||
|
# TODO: Finish writing this test. What should we be testing, anyway?
|
||
|
|
||
|
self._log.info(" Finished testGetPropertyAll!")
|
||
|
|
||
|
def testGetSet(self):
|
||
|
self._log.info("Starting testGetSet()...")
|
||
|
for cfg in self._configs:
|
||
|
if cfg.prop in self._skipProps:
|
||
|
# Skip properties that cannot be handled properly by this test.
|
||
|
self._log.warning(" Skipping propId=0x%X", cfg.prop)
|
||
|
continue
|
||
|
|
||
|
areas = cfg.supported_areas
|
||
|
idx = -1
|
||
|
while (idx == -1) | (areas != 0):
|
||
|
idx += 1
|
||
|
# Get the area to test
|
||
|
area = areas & (areas -1)
|
||
|
area ^= areas
|
||
|
|
||
|
# Remove the area from areas
|
||
|
areas ^= area
|
||
|
|
||
|
self._log.debug(" Testing propId=0x%X, area=0x%X", cfg.prop, area)
|
||
|
|
||
|
# Get the current value
|
||
|
self._vhal.getProperty(cfg.prop, area)
|
||
|
rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_PROPERTY_RESP,
|
||
|
VehicleHalProto_pb2.RESULT_OK)
|
||
|
|
||
|
# Save the original value
|
||
|
origValue = self._getValueFromMsg(rxMsg)
|
||
|
if origValue == None:
|
||
|
self._log.error("testGetSet: Could not get value for prop=0x%X, area=0x%X",
|
||
|
cfg.prop, area)
|
||
|
continue
|
||
|
|
||
|
# Generate the test value
|
||
|
testValue = self._generateTestValue(cfg, idx, origValue)
|
||
|
if testValue == None:
|
||
|
self._log.error("testGetSet: Cannot generate test value for prop=0x%X, area=0x%X",
|
||
|
cfg.prop, area)
|
||
|
continue
|
||
|
|
||
|
# Send the new value
|
||
|
self._vhal.setProperty(cfg.prop, area, testValue)
|
||
|
rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.SET_PROPERTY_RESP,
|
||
|
VehicleHalProto_pb2.RESULT_OK)
|
||
|
|
||
|
# Get the new value and verify it
|
||
|
self._vhal.getProperty(cfg.prop, area)
|
||
|
rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_PROPERTY_RESP,
|
||
|
VehicleHalProto_pb2.RESULT_OK)
|
||
|
newValue = self._getValueFromMsg(rxMsg)
|
||
|
if newValue != testValue:
|
||
|
self._log.error("testGetSet: set failed for propId=0x%X, area=0x%X", cfg.prop, area)
|
||
|
print("testValue= ", testValue, "newValue= ", newValue)
|
||
|
continue
|
||
|
|
||
|
# Reset the value to what it was before
|
||
|
self._vhal.setProperty(cfg.prop, area, origValue)
|
||
|
rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.SET_PROPERTY_RESP,
|
||
|
VehicleHalProto_pb2.RESULT_OK)
|
||
|
self._log.info(" Finished testGetSet()!")
|
||
|
|
||
|
def testGetBadProperty(self):
|
||
|
self._log.info("Starting testGetBadProperty()...")
|
||
|
for prop in self._badProps:
|
||
|
self._log.debug(" Testing bad propId=0x%X", prop)
|
||
|
self._vhal.getProperty(prop, 0)
|
||
|
rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_PROPERTY_RESP,
|
||
|
VehicleHalProto_pb2.ERROR_INVALID_PROPERTY)
|
||
|
if retVal:
|
||
|
for value in rxMsg.value:
|
||
|
self._log.error("testGetBadProperty prop=0x%X, expected:None, received:\n%s",
|
||
|
prop, str(rxMsg))
|
||
|
self._log.info(" Finished testGetBadProperty()!")
|
||
|
|
||
|
def testSetBadProperty(self):
|
||
|
self._log.info("Starting testSetBadProperty()...")
|
||
|
area = 1
|
||
|
value = 100
|
||
|
for prop in self._badProps:
|
||
|
self._log.debug(" Testing bad propId=0x%X", prop)
|
||
|
area = area + 1
|
||
|
value = value + 1
|
||
|
try:
|
||
|
self._vhal.setProperty(prop, area, value)
|
||
|
self._log.error("testGetBadProperty failed. prop=0x%X, area=0x%X, value=%d",
|
||
|
prop, area, value)
|
||
|
except ValueError as e:
|
||
|
# Received expected error
|
||
|
pass
|
||
|
self._log.info(" Finished testSetBadProperty()!")
|
||
|
|
||
|
def testGetVmsAvailability(self):
|
||
|
self._log.info("Starting testVms()...")
|
||
|
|
||
|
# Request the availability from the VmsCore.
|
||
|
value = {'int32_values' : [vhal_consts_2_0.VMSMESSAGETYPE_AVAILABILITY_REQUEST] }
|
||
|
self._vhal.setProperty(
|
||
|
vhal_consts_2_0.VEHICLEPROPERTY_VEHICLE_MAP_SERVICE, 0, value)
|
||
|
|
||
|
rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.SET_PROPERTY_RESP,
|
||
|
VehicleHalProto_pb2.RESULT_OK)
|
||
|
|
||
|
# The Vms Core should immediately respond
|
||
|
rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.SET_PROPERTY_ASYNC,
|
||
|
VehicleHalProto_pb2.RESULT_OK)
|
||
|
|
||
|
if self._getVmsMessageTypeFromMsg(rxMsg) != vhal_consts_2_0.VMSMESSAGETYPE_AVAILABILITY_RESPONSE:
|
||
|
self._log.error("testVms: VmsCore did not respond with AvailabilityResponse: %s", str(rxMsg))
|
||
|
|
||
|
|
||
|
# Test that we can get the property on command
|
||
|
self._vhal.getProperty(
|
||
|
vhal_consts_2_0.VEHICLEPROPERTY_VEHICLE_MAP_SERVICE, 0)
|
||
|
rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_PROPERTY_RESP,
|
||
|
VehicleHalProto_pb2.RESULT_OK)
|
||
|
|
||
|
if self._getVmsMessageTypeFromMsg(rxMsg) != vhal_consts_2_0.VMSMESSAGETYPE_AVAILABILITY_RESPONSE:
|
||
|
self._log.error("testVms: VmsCore did not respond with AvailabilityResponse: %s", str(rxMsg))
|
||
|
else:
|
||
|
# Parse Availability Response
|
||
|
layers = rxMsg.value[0].int32_values[
|
||
|
vhal_consts_2_0.VMSAVAILABILITYSTATEINTEGERVALUESINDEX_NUMBER_OF_ASSOCIATED_LAYERS]
|
||
|
index = vhal_consts_2_0.VMSAVAILABILITYSTATEINTEGERVALUESINDEX_LAYERS_START
|
||
|
numPublishersIndex = vhal_consts_2_0.VMSMESSAGEWITHLAYERINTEGERVALUESINDEX_LAYER_VERSION
|
||
|
self._log.info("testVms: %d available layers", layers)
|
||
|
for layer in xrange(layers):
|
||
|
self._log.info("testVms: Available layer: %s",
|
||
|
rxMsg.value[0].int32_values[index:index+numPublishersIndex])
|
||
|
index += numPublishersIndex + 1 + rxMsg.value[0].int32_values[index+numPublishersIndex]
|
||
|
|
||
|
if len(rxMsg.value[0].int32_values) != index:
|
||
|
self._log.error("testVms: Malformed AvailabilityResponse: index: %d %s", index, str(rxMsg))
|
||
|
|
||
|
def runTests(self):
|
||
|
self.testGetConfig()
|
||
|
self.testGetBadConfig()
|
||
|
self.testGetPropertyAll()
|
||
|
self.testGetSet()
|
||
|
self.testGetBadProperty()
|
||
|
self.testSetBadProperty()
|
||
|
self.testGetVmsAvailability()
|
||
|
# Add new tests here to be run
|
||
|
|
||
|
|
||
|
# Valid logLevels:
|
||
|
# CRITICAL 50
|
||
|
# ERRROR 40
|
||
|
# WARNING 30
|
||
|
# INFO 20
|
||
|
# DEBUG 10
|
||
|
# NOTSET 0
|
||
|
def __init__(self, types, logLevel=20):
|
||
|
self._types = types
|
||
|
# Configure the logger
|
||
|
logging.basicConfig()
|
||
|
self._log = logging.getLogger('vhal_emulator_test')
|
||
|
self._log.setLevel(logLevel)
|
||
|
# Start the VHAL Emulator
|
||
|
self._vhal = vhal_emulator.Vhal(types)
|
||
|
# Get the list of configs
|
||
|
self._vhal.getConfigAll()
|
||
|
self._configs = self._vhal.rxMsg().config
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
v = VhalTest(vhal_consts_2_0.vhal_types_2_0)
|
||
|
v.runTests()
|