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