1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# Copyright 2017 The Android Open Source Project
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10#   http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17#
18
19"""
20    This module tests the Vehicle HAL using adb socket.
21
22    Protocol Buffer:
23        This module relies on VehicleHalProto_pb2.py being in sync with the protobuf in the VHAL.
24        If the VehicleHalProto.proto file has changed, re-generate the python version using
25        a command of the form:
26            protoc -I=<proto_dir> --python_out=<out_dir> <proto_dir>/VehicleHalProto.proto
27        For example:
28            protoDir=$ANDROID_BUILD_TOP/hardware/interfaces/automotive/vehicle/2.0/default/impl/vhal_v2_0/proto
29            outDir=$ANDROID_BUILD_TOP/packages/services/Car/tools/emulator
30            protoc -I=$protoDir --python_out=$outDir $protoDir/VehicleHalProto.proto
31"""
32
33from __future__ import print_function
34
35# Suppress .pyc files
36import sys
37sys.dont_write_bytecode = True
38
39import VehicleHalProto_pb2
40import vhal_consts_2_0
41import vhal_emulator
42import logging
43
44class VhalTest:
45    # Global vars
46    _badProps = [0, 0x3FFFFFFF]     # List of bad properties to try for negative tests
47    _configs = 0                    # List of configs from DUT
48    _log = 0                        # Logger module
49    _vhal = 0                       # Handle to VHAL object that communicates over socket to DUT
50    # TODO: b/38203109 - Fix OBD2 values, implement handling for complex properties
51    _skipProps = [
52                    vhal_consts_2_0.VEHICLEPROPERTY_OBD2_LIVE_FRAME,
53                    vhal_consts_2_0.VEHICLEPROPERTY_OBD2_FREEZE_FRAME,
54                    vhal_consts_2_0.VEHICLEPROPERTY_OBD2_FREEZE_FRAME_INFO,
55                    vhal_consts_2_0.VEHICLEPROPERTY_OBD2_FREEZE_FRAME_CLEAR,
56                    vhal_consts_2_0.VEHICLEPROPERTY_VEHICLE_MAP_SERVICE,
57                    vhal_consts_2_0.VEHICLEPROPERTY_WHEEL_TICK,     # Need to support complex properties
58                    0x21E00666      # FakeDataControllingProperty - an internal test property
59                 ]
60
61    def _getMidpoint(self, minVal, maxVal):
62        retVal =  minVal + (maxVal - minVal)/2
63        return retVal
64
65    # Generates a test value based on the config
66    def _generateTestValue(self, cfg, idx, origValue):
67        valType = cfg.value_type
68        if valType in self._types.TYPE_STRING:
69            testValue = "test string"
70        elif valType in self._types.TYPE_BYTES:
71            # Generate array of integers counting from 0
72            testValue = list(range(len(origValue)))
73        elif valType == vhal_consts_2_0.VEHICLEPROPERTYTYPE_BOOLEAN:
74            testValue = origValue ^ 1
75        elif valType in self._types.TYPE_INT32:
76            try:
77                testValue = self._getMidpoint(cfg.area_configs[idx].min_int32_value,
78                                              cfg.area_configs[idx].max_int32_value)
79            except:
80                # min/max values aren't set.  Set a hard-coded value
81                testValue = 123
82        elif valType in self._types.TYPE_INT64:
83            try:
84                testValue = self._getMidpoint(cfg.area_configs[idx].min_int64_value,
85                                              cfg.area_configs[idx].max_int64_value)
86            except:
87                # min/max values aren't set.  Set a large hard-coded value
88                testValue = 1 << 50
89        elif valType in self._types.TYPE_FLOAT:
90            try:
91                testValue = self._getMidpoint(cfg.area_configs[idx].min_float_value,
92                                              cfg.area_configs[idx].max_float_value)
93            except:
94                # min/max values aren't set.  Set a hard-coded value
95                testValue = 123.456
96            # Truncate float to 5 decimal places
97            testValue = "%.5f" % testValue
98            testValue = float(testValue)
99        else:
100            self._log.error("generateTestValue:  valType=0x%X is not handled", valType)
101            testValue = None
102        return testValue
103
104    # Helper function to extract values array from rxMsg
105    def _getValueFromMsg(self, rxMsg):
106        # Check to see only one property value is returned
107        if len(rxMsg.value) != 1:
108            self._log.error("getValueFromMsg:  Received invalid value")
109            value = None
110        else:
111            valType = rxMsg.value[0].value_type
112            try:
113                if valType in self._types.TYPE_STRING:
114                    value = rxMsg.value[0].string_value
115                elif valType in self._types.TYPE_BYTES:
116                    value = rxMsg.value[0].bytes_value
117                elif valType == vhal_consts_2_0.VEHICLEPROPERTYTYPE_BOOLEAN:
118                    value = rxMsg.value[0].int32_values[0]
119                elif valType in self._types.TYPE_INT32:
120                    value = rxMsg.value[0].int32_values[0]
121                elif valType in self._types.TYPE_INT64:
122                    value = rxMsg.value[0].int64_values[0]
123                elif valType in self._types.TYPE_FLOAT:
124                    value = rxMsg.value[0].float_values[0]
125                    # Truncate float to 5 decimal places
126                    value = "%.5f" % value
127                    value = float(value)
128                elif valType in self._types.TYPE_MIXED:
129                    # Quick stub to unblock most tests
130                    # Todo: proper implement according to VehiclePropertyType in types.hal
131                    value = rxMsg.value[0].float_values[0]
132                    value = "%.5f" % value
133                    value = float(value)
134                else:
135                    self._log.error("getValueFromMsg:  valType=0x%X is not handled", valType)
136                    value = None
137            except IndexError:
138              self._log.error("getValueFromMsg:  Received malformed message: %s", str(rxMsg))
139              value = None
140        return value
141
142    def _validateVmsMessage(self, rxMsg):
143        return (len(rxMsg.value) == 1 and rxMsg.value[0].value_type in self._types.TYPE_MIXED and
144            len(rxMsg.value[0].int32_values) > 0 and
145            vhal_consts_2_0.VMSMESSAGETYPE_SUBSCRIBE <= rxMsg.value[0].int32_values[0]
146                <= vhal_consts_2_0.VMSMESSAGETYPE_LAST_VMS_MESSAGE_TYPE)
147
148    def _getVmsMessageTypeFromMsg(self, rxMsg):
149        if self._validateVmsMessage(rxMsg):
150            value = rxMsg.value[0].int32_values[
151                vhal_consts_2_0.VMSBASEMESSAGEINTEGERVALUESINDEX_MESSAGE_TYPE]
152        else:
153            self._log.error("getVmsMessageTypeFromMsg:  Received invalid message")
154            value = None
155        return value
156
157    # Helper function to receive a message and validate the type and status
158    #   retVal = 1 if no errors
159    #   retVal = 0 if errors detected
160    def _rxMsgAndValidate(self, expectedType, expectedStatus):
161        retVal = 1
162        rxMsg = self._vhal.rxMsg()
163        if rxMsg.msg_type != expectedType:
164            self._log.error("rxMsg Type expected: 0x%X, received: 0x%X", expectedType, rxMsg.msg_type)
165            retVal = 0
166        if rxMsg.status != expectedStatus:
167            self._log.error("rxMsg Status expected: 0x%X, received: 0x%X", expectedStatus, rxMsg.status)
168            retVal = 0
169        return rxMsg, retVal
170
171    # Calls getConfig() on each individual property ID and verifies it matches with the config
172    #   received in getConfigAll()
173    def testGetConfig(self):
174        self._log.info("Starting testGetConfig...")
175        for cfg in self._configs:
176            self._log.debug("  Getting config for propId=0x%X", cfg.prop)
177            self._vhal.getConfig(cfg.prop)
178            rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_CONFIG_RESP,
179                                                   VehicleHalProto_pb2.RESULT_OK)
180            if retVal:
181                if rxMsg.config[0] != cfg:
182                    self._log.error("testGetConfig failed.  prop=0x%X, expected:\n%s\nreceived:\n%s",
183                               cfg.prop, str(cfg), str(rxMsg.config))
184        self._log.info("  Finished testGetConfig!")
185
186    # Calls getConfig() on invalid property ID and verifies it generates an error
187    def testGetBadConfig(self):
188        self._log.info("Starting testGetBadConfig...")
189        for prop in self._badProps:
190            self._log.debug("  Testing bad propId=0x%X", prop)
191            self._vhal.getConfig(prop)
192            rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_CONFIG_RESP,
193                                                   VehicleHalProto_pb2.ERROR_INVALID_PROPERTY)
194            if retVal:
195                for cfg in rxMsg.config:
196                    self._log.error("testGetBadConfig  prop=0x%X, expected:None, received:\n%s",
197                                    cfg.prop, str(rxMsg.config))
198        self._log.info("  Finished testGetBadConfig!")
199
200    def testGetPropertyAll(self):
201        self._log.info("Starting testGetPropertyAll...")
202        self._vhal.getPropertyAll()
203        rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_PROPERTY_ALL_RESP,
204                                               VehicleHalProto_pb2.RESULT_OK)
205        if retVal == 0:
206            self._log.error("testGetPropertyAll:  Failed to receive proper rxMsg")
207
208        # TODO: Finish writing this test.  What should we be testing, anyway?
209
210        self._log.info("  Finished testGetPropertyAll!")
211
212    def testGetSet(self):
213        self._log.info("Starting testGetSet()...")
214        for cfg in self._configs:
215            if cfg.prop in self._skipProps:
216                # Skip properties that cannot be handled properly by this test.
217                self._log.warning("  Skipping propId=0x%X", cfg.prop)
218                continue
219
220            areas = cfg.supported_areas
221            idx = -1
222            while (idx == -1) | (areas != 0):
223                idx += 1
224                # Get the area to test
225                area = areas & (areas -1)
226                area ^= areas
227
228                # Remove the area from areas
229                areas ^= area
230
231                self._log.debug("  Testing propId=0x%X, area=0x%X", cfg.prop, area)
232
233                # Get the current value
234                self._vhal.getProperty(cfg.prop, area)
235                rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_PROPERTY_RESP,
236                                                       VehicleHalProto_pb2.RESULT_OK)
237
238                # Save the original value
239                origValue = self._getValueFromMsg(rxMsg)
240                if origValue == None:
241                    self._log.error("testGetSet:  Could not get value for prop=0x%X, area=0x%X",
242                                    cfg.prop, area)
243                    continue
244
245                # Generate the test value
246                testValue = self._generateTestValue(cfg, idx, origValue)
247                if testValue == None:
248                    self._log.error("testGetSet:  Cannot generate test value for prop=0x%X, area=0x%X",
249                                    cfg.prop, area)
250                    continue
251
252                # Send the new value
253                self._vhal.setProperty(cfg.prop, area, testValue)
254                rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.SET_PROPERTY_RESP,
255                                                        VehicleHalProto_pb2.RESULT_OK)
256
257                # Get the new value and verify it
258                self._vhal.getProperty(cfg.prop, area)
259                rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_PROPERTY_RESP,
260                                                       VehicleHalProto_pb2.RESULT_OK)
261                newValue = self._getValueFromMsg(rxMsg)
262                if newValue != testValue:
263                    self._log.error("testGetSet: set failed for propId=0x%X, area=0x%X", cfg.prop, area)
264                    print("testValue= ", testValue, "newValue= ", newValue)
265                    continue
266
267                # Reset the value to what it was before
268                self._vhal.setProperty(cfg.prop, area, origValue)
269                rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.SET_PROPERTY_RESP,
270                                                       VehicleHalProto_pb2.RESULT_OK)
271        self._log.info("  Finished testGetSet()!")
272
273    def testGetBadProperty(self):
274        self._log.info("Starting testGetBadProperty()...")
275        for prop in self._badProps:
276            self._log.debug("  Testing bad propId=0x%X", prop)
277            self._vhal.getProperty(prop, 0)
278            rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_PROPERTY_RESP,
279                                                   VehicleHalProto_pb2.ERROR_INVALID_PROPERTY)
280            if retVal:
281                for value in rxMsg.value:
282                    self._log.error("testGetBadProperty  prop=0x%X, expected:None, received:\n%s",
283                                    prop, str(rxMsg))
284        self._log.info("  Finished testGetBadProperty()!")
285
286    def testSetBadProperty(self):
287        self._log.info("Starting testSetBadProperty()...")
288        area = 1
289        value = 100
290        for prop in self._badProps:
291            self._log.debug("  Testing bad propId=0x%X", prop)
292            area = area + 1
293            value = value + 1
294            try:
295                self._vhal.setProperty(prop, area, value)
296                self._log.error("testGetBadProperty failed.  prop=0x%X, area=0x%X, value=%d",
297                                prop, area, value)
298            except ValueError as e:
299                # Received expected error
300                pass
301        self._log.info("  Finished testSetBadProperty()!")
302
303    def testGetVmsAvailability(self):
304        self._log.info("Starting testVms()...")
305
306        # Request the availability from the VmsCore.
307        value = {'int32_values' : [vhal_consts_2_0.VMSMESSAGETYPE_AVAILABILITY_REQUEST] }
308        self._vhal.setProperty(
309            vhal_consts_2_0.VEHICLEPROPERTY_VEHICLE_MAP_SERVICE, 0, value)
310
311        rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.SET_PROPERTY_RESP,
312                                               VehicleHalProto_pb2.RESULT_OK)
313
314        # The Vms Core should immediately respond
315        rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.SET_PROPERTY_ASYNC,
316                                               VehicleHalProto_pb2.RESULT_OK)
317
318        if self._getVmsMessageTypeFromMsg(rxMsg) != vhal_consts_2_0.VMSMESSAGETYPE_AVAILABILITY_RESPONSE:
319            self._log.error("testVms: VmsCore did not respond with AvailabilityResponse: %s", str(rxMsg))
320
321
322        # Test that we can get the property on command
323        self._vhal.getProperty(
324            vhal_consts_2_0.VEHICLEPROPERTY_VEHICLE_MAP_SERVICE, 0)
325        rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_PROPERTY_RESP,
326                                               VehicleHalProto_pb2.RESULT_OK)
327
328        if self._getVmsMessageTypeFromMsg(rxMsg) != vhal_consts_2_0.VMSMESSAGETYPE_AVAILABILITY_RESPONSE:
329            self._log.error("testVms: VmsCore did not respond with AvailabilityResponse: %s", str(rxMsg))
330        else:
331            # Parse Availability Response
332            layers = rxMsg.value[0].int32_values[
333                vhal_consts_2_0.VMSAVAILABILITYSTATEINTEGERVALUESINDEX_NUMBER_OF_ASSOCIATED_LAYERS]
334            index = vhal_consts_2_0.VMSAVAILABILITYSTATEINTEGERVALUESINDEX_LAYERS_START
335            numPublishersIndex = vhal_consts_2_0.VMSMESSAGEWITHLAYERINTEGERVALUESINDEX_LAYER_VERSION
336            self._log.info("testVms: %d available layers", layers)
337            for layer in xrange(layers):
338                self._log.info("testVms: Available layer: %s",
339                               rxMsg.value[0].int32_values[index:index+numPublishersIndex])
340                index += numPublishersIndex + 1 + rxMsg.value[0].int32_values[index+numPublishersIndex]
341
342            if len(rxMsg.value[0].int32_values) != index:
343              self._log.error("testVms: Malformed AvailabilityResponse: index: %d %s", index, str(rxMsg))
344
345    def runTests(self):
346        self.testGetConfig()
347        self.testGetBadConfig()
348        self.testGetPropertyAll()
349        self.testGetSet()
350        self.testGetBadProperty()
351        self.testSetBadProperty()
352        self.testGetVmsAvailability()
353        # Add new tests here to be run
354
355
356    # Valid logLevels:
357    #   CRITICAL    50
358    #   ERRROR      40
359    #   WARNING     30
360    #   INFO        20
361    #   DEBUG       10
362    #   NOTSET      0
363    def __init__(self, types, logLevel=20):
364        self._types = types
365        # Configure the logger
366        logging.basicConfig()
367        self._log = logging.getLogger('vhal_emulator_test')
368        self._log.setLevel(logLevel)
369        # Start the VHAL Emulator
370        self._vhal = vhal_emulator.Vhal(types)
371        # Get the list of configs
372        self._vhal.getConfigAll()
373        self._configs = self._vhal.rxMsg().config
374
375if __name__ == '__main__':
376    v = VhalTest(vhal_consts_2_0.vhal_types_2_0)
377    v.runTests()
378