Home | History | Annotate | Download | only in binary_test
      1 #
      2 # Copyright (C) 2016 The Android Open Source Project
      3 #
      4 # Licensed under the Apache License, Version 2.0 (the "License");
      5 # you may not use this file except in compliance with the License.
      6 # You may obtain a copy of the License at
      7 #
      8 #      http://www.apache.org/licenses/LICENSE-2.0
      9 #
     10 # Unless required by applicable law or agreed to in writing, software
     11 # distributed under the License is distributed on an "AS IS" BASIS,
     12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 # See the License for the specific language governing permissions and
     14 # limitations under the License.
     15 #
     16 
     17 import logging
     18 import os.path
     19 import posixpath as targetpath
     20 import time
     21 
     22 from vts.runners.host import asserts
     23 from vts.runners.host import base_test
     24 from vts.runners.host import const
     25 from vts.runners.host import keys
     26 from vts.runners.host import test_runner
     27 from vts.utils.python.common import list_utils
     28 from vts.utils.python.os import path_utils
     29 from vts.utils.python.precondition import precondition_utils
     30 from vts.utils.python.web import feature_utils
     31 
     32 from vts.testcases.template.binary_test import binary_test_case
     33 
     34 DATA_NATIVETEST = 'data/nativetest'
     35 DATA_NATIVETEST64 = '%s64' % DATA_NATIVETEST
     36 
     37 
     38 class BinaryTest(base_test.BaseTestClass):
     39     '''Base class to run binary tests on target.
     40 
     41     Attributes:
     42         _dut: AndroidDevice, the device under test as config
     43         shell: ShellMirrorObject, shell mirror
     44         testcases: list of BinaryTestCase objects, list of test cases to run
     45         tags: all the tags that appeared in binary list
     46         DEVICE_TMP_DIR: string, temp location for storing binary
     47         TAG_DELIMITER: string, separator used to separate tag and path
     48         SYSPROP_VTS_NATIVE_SERVER: string, the name of a system property which
     49                                    tells whether to stop properly configured
     50                                    native servers where properly configured
     51                                    means a server's init.rc is configured to
     52                                    stop when that property's value is 1.
     53     '''
     54     SYSPROP_VTS_NATIVE_SERVER = "vts.native_server.on"
     55 
     56     DEVICE_TMP_DIR = '/data/local/tmp'
     57     TAG_DELIMITER = '::'
     58     PUSH_DELIMITER = '->'
     59     DEFAULT_TAG_32 = '_%s' % const.SUFFIX_32BIT
     60     DEFAULT_TAG_64 = '_%s' % const.SUFFIX_64BIT
     61     DEFAULT_LD_LIBRARY_PATH_32 = '/data/local/tmp/32/'
     62     DEFAULT_LD_LIBRARY_PATH_64 = '/data/local/tmp/64/'
     63     DEFAULT_PROFILING_LIBRARY_PATH_32 = '/data/local/tmp/32/'
     64     DEFAULT_PROFILING_LIBRARY_PATH_64 = '/data/local/tmp/64/'
     65 
     66     def setUpClass(self):
     67         '''Prepare class, push binaries, set permission, create test cases.'''
     68         required_params = [
     69             keys.ConfigKeys.IKEY_DATA_FILE_PATH,
     70         ]
     71         opt_params = [
     72             keys.ConfigKeys.IKEY_BINARY_TEST_SOURCE,
     73             keys.ConfigKeys.IKEY_BINARY_TEST_WORKING_DIRECTORY,
     74             keys.ConfigKeys.IKEY_BINARY_TEST_ENVP,
     75             keys.ConfigKeys.IKEY_BINARY_TEST_ARGS,
     76             keys.ConfigKeys.IKEY_BINARY_TEST_LD_LIBRARY_PATH,
     77             keys.ConfigKeys.IKEY_BINARY_TEST_PROFILING_LIBRARY_PATH,
     78             keys.ConfigKeys.IKEY_BINARY_TEST_DISABLE_FRAMEWORK,
     79             keys.ConfigKeys.IKEY_BINARY_TEST_STOP_NATIVE_SERVERS,
     80             keys.ConfigKeys.IKEY_NATIVE_SERVER_PROCESS_NAME,
     81         ]
     82         self.getUserParams(
     83             req_param_names=required_params, opt_param_names=opt_params)
     84 
     85         # test-module-name is required in binary tests.
     86         self.getUserParam(
     87             keys.ConfigKeys.KEY_TESTBED_NAME, error_if_not_found=True)
     88 
     89         logging.info("%s: %s", keys.ConfigKeys.IKEY_DATA_FILE_PATH,
     90                      self.data_file_path)
     91 
     92         self.binary_test_source = self.getUserParam(
     93             keys.ConfigKeys.IKEY_BINARY_TEST_SOURCE, default_value=[])
     94 
     95         self.working_directory = {}
     96         if hasattr(self, keys.ConfigKeys.IKEY_BINARY_TEST_WORKING_DIRECTORY):
     97             self.binary_test_working_directory = map(
     98                 str, self.binary_test_working_directory)
     99             for token in self.binary_test_working_directory:
    100                 tag = ''
    101                 path = token
    102                 if self.TAG_DELIMITER in token:
    103                     tag, path = token.split(self.TAG_DELIMITER)
    104                 self.working_directory[tag] = path
    105 
    106         self.envp = {}
    107         if hasattr(self, keys.ConfigKeys.IKEY_BINARY_TEST_ENVP):
    108             self.binary_test_envp = map(str, self.binary_test_envp)
    109             for token in self.binary_test_envp:
    110                 tag = ''
    111                 path = token
    112                 split = token.find(self.TAG_DELIMITER)
    113                 if split >= 0:
    114                     tag, arg = token[:split], token[
    115                         split + len(self.TAG_DELIMITER):]
    116                 if tag in self.envp:
    117                     self.envp[tag] += ' %s' % path
    118                 else:
    119                     self.envp[tag] = path
    120 
    121         self.args = {}
    122         if hasattr(self, keys.ConfigKeys.IKEY_BINARY_TEST_ARGS):
    123             self.binary_test_args = map(str, self.binary_test_args)
    124             for token in self.binary_test_args:
    125                 tag = ''
    126                 arg = token
    127                 split = token.find(self.TAG_DELIMITER)
    128                 if split >= 0:
    129                     tag, arg = token[:split], token[
    130                         split + len(self.TAG_DELIMITER):]
    131                 if tag in self.args:
    132                     self.args[tag] += ' %s' % arg
    133                 else:
    134                     self.args[tag] = arg
    135 
    136         self.ld_library_path = {
    137             self.DEFAULT_TAG_32: self.DEFAULT_LD_LIBRARY_PATH_32,
    138             self.DEFAULT_TAG_64: self.DEFAULT_LD_LIBRARY_PATH_64,
    139         }
    140         if hasattr(self, keys.ConfigKeys.IKEY_BINARY_TEST_LD_LIBRARY_PATH):
    141             self.binary_test_ld_library_path = map(
    142                 str, self.binary_test_ld_library_path)
    143             for token in self.binary_test_ld_library_path:
    144                 tag = ''
    145                 path = token
    146                 if self.TAG_DELIMITER in token:
    147                     tag, path = token.split(self.TAG_DELIMITER)
    148                 if tag in self.ld_library_path:
    149                     self.ld_library_path[tag] = '{}:{}'.format(
    150                         path, self.ld_library_path[tag])
    151                 else:
    152                     self.ld_library_path[tag] = path
    153 
    154         self.profiling_library_path = {
    155             self.DEFAULT_TAG_32: self.DEFAULT_PROFILING_LIBRARY_PATH_32,
    156             self.DEFAULT_TAG_64: self.DEFAULT_PROFILING_LIBRARY_PATH_64,
    157         }
    158         if hasattr(self,
    159                    keys.ConfigKeys.IKEY_BINARY_TEST_PROFILING_LIBRARY_PATH):
    160             self.binary_test_profiling_library_path = map(
    161                 str, self.binary_test_profiling_library_path)
    162             for token in self.binary_test_profiling_library_path:
    163                 tag = ''
    164                 path = token
    165                 if self.TAG_DELIMITER in token:
    166                     tag, path = token.split(self.TAG_DELIMITER)
    167                 self.profiling_library_path[tag] = path
    168 
    169         self._dut = self.android_devices[0]
    170         self.shell = self._dut.shell
    171 
    172         if self.coverage.enabled and self.coverage.global_coverage:
    173             self.coverage.LoadArtifacts()
    174             self.coverage.InitializeDeviceCoverage(self._dut)
    175 
    176         # TODO: only set permissive mode for userdebug and eng build.
    177         self.shell.Execute("setenforce 0")  # SELinux permissive mode
    178 
    179         if not precondition_utils.CanRunHidlHalTest(self, self._dut,
    180                                                     self.shell):
    181             self._skip_all_testcases = True
    182 
    183         self.testcases = []
    184         self.tags = set()
    185         self.CreateTestCases()
    186         cmd = list(
    187             set('chmod 755 %s' % test_case.path
    188                 for test_case in self.testcases))
    189         cmd_results = self.shell.Execute(cmd)
    190         if any(cmd_results[const.EXIT_CODE]):
    191             logging.error('Failed to set permission to some of the binaries:\n'
    192                           '%s\n%s', cmd, cmd_results)
    193 
    194         stop_requested = False
    195 
    196         if getattr(self, keys.ConfigKeys.IKEY_BINARY_TEST_DISABLE_FRAMEWORK,
    197                    False):
    198             # Stop Android runtime to reduce interference.
    199             logging.debug("Stops the Android framework.")
    200             self._dut.stop()
    201             stop_requested = True
    202 
    203         if getattr(self, keys.ConfigKeys.IKEY_BINARY_TEST_STOP_NATIVE_SERVERS,
    204                    False):
    205             logging.debug("Stops all properly configured native servers.")
    206             results = self._dut.setProp(self.SYSPROP_VTS_NATIVE_SERVER, "1")
    207             stop_requested = True
    208 
    209         if stop_requested:
    210             native_server_process_names = getattr(
    211                 self, keys.ConfigKeys.IKEY_NATIVE_SERVER_PROCESS_NAME, [])
    212             if native_server_process_names:
    213                 for native_server_process_name in native_server_process_names:
    214                     while True:
    215                         cmd_result = self.shell.Execute("ps -A")
    216                         if cmd_result[const.EXIT_CODE][0] != 0:
    217                             logging.error("ps command failed (exit code: %s",
    218                                           cmd_result[const.EXIT_CODE][0])
    219                             break
    220                         if (native_server_process_name not in cmd_result[
    221                                 const.STDOUT][0]):
    222                             logging.info("Process %s not running",
    223                                          native_server_process_name)
    224                             break
    225                         logging.info("Checking process %s",
    226                                      native_server_process_name)
    227                         time.sleep(1)
    228 
    229     def CreateTestCases(self):
    230         '''Push files to device and create test case objects.'''
    231         source_list = list(map(self.ParseTestSource, self.binary_test_source))
    232         source_list = filter(bool, source_list)
    233         logging.info('Parsed test sources: %s', source_list)
    234 
    235         # Push source files first
    236         for src, dst, tag in source_list:
    237             if src:
    238                 if os.path.isdir(src):
    239                     src = os.path.join(src, '.')
    240                 logging.info('Pushing from %s to %s.', src, dst)
    241                 self._dut.adb.push('{src} {dst}'.format(src=src, dst=dst))
    242                 self.shell.Execute('ls %s' % dst)
    243 
    244         # Then create test cases
    245         for src, dst, tag in source_list:
    246             if tag is not None:
    247                 # tag not being None means to create a test case
    248                 self.tags.add(tag)
    249                 logging.info('Creating test case from %s with tag %s', dst,
    250                              tag)
    251                 testcase = self.CreateTestCase(dst, tag)
    252                 if not testcase:
    253                     continue
    254 
    255                 if type(testcase) is list:
    256                     self.testcases.extend(testcase)
    257                 else:
    258                     self.testcases.append(testcase)
    259 
    260         if type(self.testcases) is not list or len(self.testcases) == 0:
    261             asserts.fail("No test case is found or generated.")
    262 
    263     def PutTag(self, name, tag):
    264         '''Put tag on name and return the resulting string.
    265 
    266         Args:
    267             name: string, a test name
    268             tag: string
    269 
    270         Returns:
    271             String, the result string after putting tag on the name
    272         '''
    273         return '{}{}'.format(name, tag)
    274 
    275     def ExpandListItemTags(self, input_list):
    276         '''Expand list items with tags.
    277 
    278         Since binary test allows a tag to be added in front of the binary
    279         path, test names are generated with tags attached. This function is
    280         used to expand the filters correspondingly. If a filter contains
    281         a tag, only test name with that tag will be included in output.
    282         Otherwise, all known tags will be paired to the test name in output
    283         list.
    284 
    285         Args:
    286             input_list: list of string, the list to expand
    287 
    288         Returns:
    289             A list of string
    290         '''
    291         result = []
    292         for item in input_list:
    293             if self.TAG_DELIMITER in item:
    294                 tag, name = item.split(self.TAG_DELIMITER)
    295                 result.append(self.PutTag(name, tag))
    296             for tag in self.tags:
    297                 result.append(self.PutTag(item, tag))
    298         return result
    299 
    300     def tearDownClass(self):
    301         '''Perform clean-up tasks'''
    302         if getattr(self, keys.ConfigKeys.IKEY_BINARY_TEST_STOP_NATIVE_SERVERS,
    303                    False):
    304             logging.debug("Restarts all properly configured native servers.")
    305             results = self._dut.setProp(self.SYSPROP_VTS_NATIVE_SERVER, "0")
    306 
    307         # Restart Android runtime.
    308         if getattr(self, keys.ConfigKeys.IKEY_BINARY_TEST_DISABLE_FRAMEWORK,
    309                    False):
    310             logging.debug("Starts the Android framework.")
    311             self._dut.start()
    312 
    313         # Retrieve coverage if applicable
    314         if self.coverage.enabled and self.coverage.global_coverage:
    315             self.coverage.SetCoverageData(dut=self._dut, isGlobal=True)
    316 
    317         # Clean up the pushed binaries
    318         logging.info('Start class cleaning up jobs.')
    319         # Delete pushed files
    320 
    321         sources = [
    322             self.ParseTestSource(src) for src in self.binary_test_source
    323         ]
    324         sources = set(filter(bool, sources))
    325         paths = [dst for src, dst, tag in sources if src and dst]
    326         cmd = ['rm -rf %s' % dst for dst in paths]
    327         cmd_results = self.shell.Execute(cmd, no_except=True)
    328         if not cmd_results or any(cmd_results[const.EXIT_CODE]):
    329             logging.warning('Failed to clean up test class: %s', cmd_results)
    330 
    331         # Delete empty directories in working directories
    332         dir_set = set(path_utils.TargetDirName(dst) for dst in paths)
    333         dir_set.add(self.ParseTestSource('')[1])
    334         dirs = list(dir_set)
    335         dirs.sort(lambda x, y: cmp(len(y), len(x)))
    336         cmd = ['rmdir %s' % d for d in dirs]
    337         cmd_results = self.shell.Execute(cmd, no_except=True)
    338         if not cmd_results or any(cmd_results[const.EXIT_CODE]):
    339             logging.warning('Failed to remove: %s', cmd_results)
    340 
    341         if self.profiling.enabled:
    342             self.profiling.ProcessAndUploadTraceData()
    343 
    344         logging.info('Finished class cleaning up jobs.')
    345 
    346     def ParseTestSource(self, source):
    347         '''Convert host side binary path to device side path.
    348 
    349         Args:
    350             source: string, binary test source string
    351 
    352         Returns:
    353             A tuple of (string, string, string), representing (host side
    354             absolute path, device side absolute path, tag). Returned tag
    355             will be None if the test source is for pushing file to working
    356             directory only. If source file is specified for adb push but does not
    357             exist on host, None will be returned.
    358         '''
    359         tag = ''
    360         path = source
    361         if self.TAG_DELIMITER in source:
    362             tag, path = source.split(self.TAG_DELIMITER)
    363 
    364         src = path
    365         dst = None
    366         if self.PUSH_DELIMITER in path:
    367             src, dst = path.split(self.PUSH_DELIMITER)
    368 
    369         if src:
    370             src = os.path.join(self.data_file_path, src)
    371             if not os.path.exists(src):
    372                 logging.warning('binary source file is specified '
    373                                 'but does not exist on host: %s', src)
    374                 return None
    375 
    376         push_only = dst is not None and dst == ''
    377 
    378         if not dst:
    379             parent = self.working_directory[
    380                 tag] if tag in self.working_directory else self._GetDefaultBinaryPushDstPath(
    381                     src, tag)
    382             dst = path_utils.JoinTargetPath(parent, os.path.basename(src))
    383 
    384         if push_only:
    385             tag = None
    386 
    387         return str(src), str(dst), tag
    388 
    389     def _GetDefaultBinaryPushDstPath(self, src, tag):
    390         '''Get default binary push destination path.
    391 
    392         This method is called to get default push destination path when
    393         it is not specified.
    394 
    395         If binary source path contains 'data/nativetest[64]', then the binary
    396         will be pushed to /data/nativetest[64] instead of /data/local/tmp
    397 
    398         Args:
    399             src: string, source path of binary
    400             tag: string, tag of binary source
    401 
    402         Returns:
    403             string, default push path
    404         '''
    405         src_lower = src.lower()
    406         if DATA_NATIVETEST64 in src_lower:
    407             parent_path = targetpath.sep + DATA_NATIVETEST64
    408         elif DATA_NATIVETEST in src_lower:
    409             parent_path = targetpath.sep + DATA_NATIVETEST
    410         else:
    411             parent_path = self.DEVICE_TMP_DIR
    412 
    413         return targetpath.join(
    414             parent_path, 'vts_binary_test_%s' % self.__class__.__name__, tag)
    415 
    416     def CreateTestCase(self, path, tag=''):
    417         '''Create a list of TestCase objects from a binary path.
    418 
    419         Args:
    420             path: string, absolute path of a binary on device
    421             tag: string, a tag that will be appended to the end of test name
    422 
    423         Returns:
    424             A list of BinaryTestCase objects
    425         '''
    426         working_directory = self.working_directory[
    427             tag] if tag in self.working_directory else None
    428         envp = self.envp[tag] if tag in self.envp else ''
    429         args = self.args[tag] if tag in self.args else ''
    430         ld_library_path = self.ld_library_path[
    431             tag] if tag in self.ld_library_path else None
    432         profiling_library_path = self.profiling_library_path[
    433             tag] if tag in self.profiling_library_path else None
    434 
    435         return binary_test_case.BinaryTestCase(
    436             '',
    437             path_utils.TargetBaseName(path),
    438             path,
    439             tag,
    440             self.PutTag,
    441             working_directory,
    442             ld_library_path,
    443             profiling_library_path,
    444             envp=envp,
    445             args=args)
    446 
    447     def VerifyTestResult(self, test_case, command_results):
    448         '''Parse test case command result.
    449 
    450         Args:
    451             test_case: BinaryTestCase object, the test case whose command
    452             command_results: dict of lists, shell command result
    453         '''
    454         asserts.assertTrue(command_results, 'Empty command response.')
    455         asserts.assertFalse(
    456             any(command_results[const.EXIT_CODE]),
    457             'Test {} failed with the following results: {}'.format(
    458                 test_case, command_results))
    459 
    460     def RunTestCase(self, test_case):
    461         '''Runs a test_case.
    462 
    463         Args:
    464             test_case: BinaryTestCase object
    465         '''
    466         if self.profiling.enabled:
    467             self.profiling.EnableVTSProfiling(self.shell,
    468                                               test_case.profiling_library_path)
    469 
    470         cmd = test_case.GetRunCommand()
    471         logging.info("Executing binary test command: %s", cmd)
    472         command_results = self.shell.Execute(cmd)
    473 
    474         self.VerifyTestResult(test_case, command_results)
    475 
    476         if self.profiling.enabled:
    477             self.profiling.ProcessTraceDataForTestCase(self._dut)
    478             self.profiling.DisableVTSProfiling(self.shell)
    479 
    480     def generateAllTests(self):
    481         '''Runs all binary tests.'''
    482         self.runGeneratedTests(
    483             test_func=self.RunTestCase, settings=self.testcases, name_func=str)
    484 
    485 
    486 if __name__ == "__main__":
    487     test_runner.main()
    488