1 #!/usr/bin/env python 2 # 3 # Copyright 2017 - The Android Open Source Project 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 # 17 18 # Create the configuration files for a hidl hal test. 19 # This script copy a template which contains Android.mk and AndroidTest.xml 20 # files under test/vts-testcases/hal/ based on the hal package name. 21 22 import datetime 23 import os 24 import sys 25 26 from build.vts_spec_parser import VtsSpecParser 27 from xml.dom import minidom 28 from xml.etree import cElementTree as ET 29 from xml.sax.saxutils import unescape 30 from utils.const import Constant 31 32 ANDROID_MK_FILE_NAME = 'Android.mk' 33 ANDROID_TEST_XML_FILE_NAME = 'AndroidTest.xml' 34 35 36 class TestCaseCreator(object): 37 """Init a test case directory with helloworld test case. 38 39 Attributes: 40 hal_package_name: string, package name of the testing hal. e.g. android.hardware.nfc (at] 1.0. 41 hal_name: name of the testing hal, derived from hal_package_name. e.g. nfc. 42 hal_version: version of the testing hal, derived from hal_package_name. 43 test_type: string, type of the test, currently support host and target. 44 package_root: String, prefix of the hal package, e.g. android.hardware. 45 path_root: String, root path that stores the hal definition, e.g. hardware/interfaces 46 test_binary_file: String, test binary name for target-side hal test. 47 test_script_file: String, test script name for host-side hal test. 48 test_config_dir: String, directory path to store the configure files. 49 test_name_prefix: prefix of generated test name. e.g. android.hardware.nfc (at] 1.0-test-target. 50 test_name: test name generated. e.g. android.hardware.nfc (at] 1.0-test-target-profiling. 51 test_plan: string, the plan that the test belongs to. 52 test_dir: string, test case absolute directory. 53 time_out: string, timeout of the test, default is 1m. 54 is_profiling: boolean, whether to create a profiling test case. 55 stop_runtime: boolean whether to stop framework before the test. 56 build_top: string, equal to environment variable ANDROID_BUILD_TOP. 57 vts_spec_parser: tools that generates and parses vts spec with hidl-gen. 58 current_year: current year. 59 """ 60 61 def __init__(self, vts_spec_parser, hal_package_name): 62 '''Initialize class attributes.''' 63 self._hal_package_name = hal_package_name 64 65 build_top = os.getenv('ANDROID_BUILD_TOP') 66 if not build_top: 67 print('Error: Missing ANDROID_BUILD_TOP env variable. Please run ' 68 '\'. build/envsetup.sh; lunch <build target>\' Exiting...') 69 sys.exit(1) 70 self._build_top = build_top 71 self._vts_spec_parser = vts_spec_parser 72 73 self._current_year = datetime.datetime.now().year 74 75 def LaunchTestCase(self, 76 test_type, 77 time_out='1m', 78 is_profiling=False, 79 is_replay=False, 80 stop_runtime=False, 81 update_only=False, 82 mapping_dir_path="", 83 test_binary_file=None, 84 test_script_file=None, 85 test_config_dir=Constant.VTS_HAL_TEST_CASE_PATH, 86 package_root=Constant.HAL_PACKAGE_PREFIX, 87 path_root=Constant.HAL_INTERFACE_PATH): 88 """Create the necessary configuration files to launch a test case. 89 90 Args: 91 test_type: type of the test. 92 time_out: timeout of the test. 93 is_profiling: whether to create a profiling test case. 94 stop_runtime: whether to stop framework before the test. 95 update_only: flag to only update existing test configure. 96 mapping_dir_path: directory that stores the cts_hal_mapping files. 97 Used for adapter test only. 98 99 Returns: 100 boolean, whether created/updated a test case successfully. 101 """ 102 self._test_type = test_type 103 self._time_out = time_out 104 self._is_profiling = is_profiling 105 self._is_replay = is_replay 106 self._stop_runtime = stop_runtime 107 self._mapping_dir_path = mapping_dir_path 108 self._test_binary_file = test_binary_file 109 self._test_script_file = test_script_file 110 self._test_config_dir = test_config_dir 111 self._package_root = package_root 112 self._path_root = path_root 113 114 [package, version] = self._hal_package_name.split('@') 115 self._hal_name = package[len(self._package_root) + 1:] 116 self._hal_version = version 117 118 self._test_module_name = self.GetVtsHalTestModuleName() 119 self._test_name = self._test_module_name 120 self._test_plan = 'vts-staging-default' 121 if is_replay: 122 self._test_name = self._test_module_name + 'Replay' 123 self._test_plan = 'vts-hal-replay' 124 if is_profiling: 125 self._test_name = self._test_module_name + 'Profiling' 126 self._test_plan = 'vts-hal-profiling' 127 if self._test_type == 'adapter': 128 self._test_plan = 'vts-hal-adapter' 129 130 self._test_dir = self.GetHalTestCasePath() 131 # Check whether the host side test script and target test binary is available. 132 if self._test_type == 'host': 133 if not self._test_script_file: 134 test_script_file = self.GetVtsHostTestScriptFileName() 135 if not os.path.exists(test_script_file): 136 print('Could not find the host side test script: %s.' % 137 test_script_file) 138 return False 139 self._test_script_file = os.path.basename(test_script_file) 140 elif self._test_type == 'target': 141 if not self._test_binary_file: 142 test_binary_file = self.GetVtsTargetTestSourceFileName() 143 if not os.path.exists(test_binary_file): 144 print('Could not find the target side test binary: %s.' % 145 test_binary_file) 146 return False 147 self._test_binary_file = os.path.basename(test_binary_file) 148 149 if os.path.exists(self._test_dir): 150 print 'WARNING: Test directory already exists. Continuing...' 151 elif not update_only: 152 try: 153 os.makedirs(self._test_dir) 154 except: 155 print('Error: Failed to create test directory at %s. ' 156 'Exiting...' % self._test_dir) 157 return False 158 else: 159 print('WARNING: Test directory does not exists, stop updating.') 160 return True 161 162 self.CreateAndroidMk() 163 self.CreateAndroidTestXml() 164 return True 165 166 def GetVtsTargetTestSourceFileName(self): 167 """Get the name of target side test source file .""" 168 test_binary_name = self._test_module_name + 'Test.cpp' 169 return os.path.join(self.GetHalInterfacePath(), 'vts/functional', 170 test_binary_name) 171 172 def GetVtsHostTestScriptFileName(self): 173 """Get the name of host side test script file .""" 174 test_script_name = self._test_module_name + 'Test.py' 175 return os.path.join( 176 self.GetHalTestCasePath(ignore_profiling=True), test_script_name) 177 178 def GetVtsHalTestModuleName(self): 179 """Get the test model name with format VtsHalHalNameVersionTestType.""" 180 sub_names = self._hal_name.split('.') 181 hal_name_upper_camel = ''.join(x.title() for x in sub_names) 182 return 'VtsHal' + hal_name_upper_camel + self.GetHalVersionToken( 183 ) + self._test_type.title() 184 185 def GetVtsHalReplayTraceFiles(self): 186 """Get the trace files for replay test.""" 187 trace_files = [] 188 for filename in os.listdir(self.GetHalTracePath()): 189 if filename.endswith(".trace"): 190 trace_files.append(filename) 191 return trace_files 192 193 def GetHalPath(self): 194 """Get the hal path based on hal name.""" 195 return self._hal_name.replace('.', '/') 196 197 def GetHalVersionToken(self): 198 """Get a string of the hal version.""" 199 return 'V' + self._hal_version.replace('.', '_') 200 201 def GetHalInterfacePath(self): 202 """Get the directory that stores the .hal files.""" 203 return os.path.join(self._build_top, self._path_root, 204 self.GetHalPath(), self._hal_version) 205 206 def GetHalTestCasePath(self, ignore_profiling=False): 207 """Get the directory that stores the test case.""" 208 test_dir = self._test_type 209 if self._is_replay: 210 test_dir = test_dir + '_replay' 211 if self._is_profiling and not ignore_profiling: 212 test_dir = test_dir + '_profiling' 213 return os.path.join(self._build_top, self._test_config_dir, 214 self.GetHalPath(), self.GetHalVersionToken(), 215 test_dir) 216 217 def GetHalTracePath(self): 218 """Get the directory that stores the hal trace files.""" 219 return os.path.join(self._build_top, Constant.HAL_TRACE_PATH, 220 self.GetHalPath(), self.GetHalVersionToken()) 221 222 def CreateAndroidMk(self): 223 """Create Android.mk.""" 224 target = os.path.join(self._test_dir, ANDROID_MK_FILE_NAME) 225 with open(target, 'w') as f: 226 print 'Creating %s' % target 227 f.write(LICENSE_STATEMENT_POUND.format(year=self._current_year)) 228 f.write('\n') 229 f.write(ANDROID_MK_TEMPLATE.format(test_name=self._test_name)) 230 231 def CreateAndroidTestXml(self): 232 """Create AndroidTest.xml.""" 233 VTS_FILE_PUSHER = 'com.android.compatibility.common.tradefed.targetprep.VtsFilePusher' 234 VTS_TEST_CLASS = 'com.android.tradefed.testtype.VtsMultiDeviceTest' 235 236 configuration = ET.Element('configuration', { 237 'description': 238 'Config for VTS ' + self._test_name + ' test cases' 239 }) 240 241 ET.SubElement( 242 configuration, 'option', { 243 'name': 'config-descriptor:metadata', 244 'key': 'plan', 245 'value': self._test_plan 246 }) 247 248 if self._test_type == 'adapter': 249 self.CreateAndroidTestXmlForAdapterTest(configuration) 250 else: 251 file_pusher = ET.SubElement(configuration, 'target_preparer', 252 {'class': VTS_FILE_PUSHER}) 253 254 self.GeneratePushFileConfigure(file_pusher) 255 test = ET.SubElement(configuration, 'test', 256 {'class': VTS_TEST_CLASS}) 257 258 self.GenerateTestOptionConfigure(test) 259 260 target = os.path.join(self._test_dir, ANDROID_TEST_XML_FILE_NAME) 261 with open(target, 'w') as f: 262 print 'Creating %s' % target 263 f.write(XML_HEADER) 264 f.write(LICENSE_STATEMENT_XML.format(year=self._current_year)) 265 f.write(self.Prettify(configuration)) 266 267 def CreateAndroidTestXmlForAdapterTest(self, configuration): 268 """Create the test configuration within AndroidTest.xml for adapter test. 269 270 Args: 271 configuration: parent xml element for test configure. 272 """ 273 274 # Configure VtsHalAdapterPreparer. 275 adapter_preparer = ET.SubElement(configuration, 'target_preparer', 276 {'class': VTA_HAL_ADAPTER_PREPARER}) 277 (major_version, minor_version) = self._hal_version.split('.') 278 adapter_version = major_version + '.' + str(int(minor_version) - 1) 279 ET.SubElement( 280 adapter_preparer, 'option', { 281 'name': 282 'adapter-binary-name', 283 'value': 284 Constant.HAL_PACKAGE_PREFIX + self._hal_name + '@' + 285 adapter_version + '-adapter' 286 }) 287 ET.SubElement(adapter_preparer, 'option', { 288 'name': 'hal-package-name', 289 'value': self._hal_package_name 290 }) 291 # Configure device health tests. 292 test = ET.SubElement(configuration, 'test', 293 {'class': ANDROID_JUNIT_TEST}) 294 ET.SubElement(test, 'option', { 295 'name': 'package', 296 'value': 'com.android.devicehealth.tests' 297 }) 298 ET.SubElement( 299 test, 'option', { 300 'name': 'runner', 301 'value': 'android.support.test.runner.AndroidJUnitRunner' 302 }) 303 304 # Configure CTS tests. 305 list_of_files = os.listdir(self._mapping_dir_path) 306 # Use the latest mapping file. 307 latest_file = max( 308 [ 309 os.path.join(self._mapping_dir_path, basename) 310 for basename in list_of_files 311 ], 312 key=os.path.getctime) 313 314 with open(latest_file, 'r') as cts_hal_map_file: 315 for line in cts_hal_map_file.readlines(): 316 if line.startswith(Constant.HAL_PACKAGE_PREFIX + 317 self._hal_name + '@' + adapter_version): 318 cts_tests = line.split(':')[1].split(',') 319 for cts_test in cts_tests: 320 test_config_name = cts_test[0:cts_test.find( 321 '(')] + '.config' 322 ET.SubElement(configuration, 'include', 323 {'name': test_config_name}) 324 325 def GeneratePushFileConfigure(self, file_pusher): 326 """Create the push file configuration within AndroidTest.xml 327 328 Args: 329 file_pusher: parent xml element for push file configure. 330 """ 331 ET.SubElement(file_pusher, 'option', { 332 'name': 'abort-on-push-failure', 333 'value': 'false' 334 }) 335 336 if self._test_type == 'target': 337 if self._is_replay: 338 ET.SubElement(file_pusher, 'option', { 339 'name': 'push-group', 340 'value': 'HalHidlHostTest.push' 341 }) 342 elif self._is_profiling: 343 ET.SubElement( 344 file_pusher, 'option', { 345 'name': 'push-group', 346 'value': 'HalHidlTargetProfilingTest.push' 347 }) 348 else: 349 ET.SubElement(file_pusher, 'option', { 350 'name': 'push-group', 351 'value': 'HalHidlTargetTest.push' 352 }) 353 else: 354 if self._is_profiling: 355 ET.SubElement(file_pusher, 'option', { 356 'name': 'push-group', 357 'value': 'HalHidlHostProfilingTest.push' 358 }) 359 else: 360 ET.SubElement(file_pusher, 'option', { 361 'name': 'push-group', 362 'value': 'HalHidlHostTest.push' 363 }) 364 365 imported_package_lists = self._vts_spec_parser.ImportedPackagesList( 366 self._hal_name, self._hal_version) 367 imported_package_lists.append(self._hal_package_name) 368 # Generate additional push files e.g driver/profiler/vts_spec 369 if self._test_type == 'host' or self._is_replay: 370 ET.SubElement(file_pusher, 'option', { 371 'name': 'cleanup', 372 'value': 'true' 373 }) 374 for imported_package in imported_package_lists: 375 imported_package_str, imported_package_version = imported_package.split( 376 '@') 377 imported_package_name = imported_package_str[ 378 len(self._package_root) + 1:] 379 imported_vts_spec_lists = self._vts_spec_parser.VtsSpecNames( 380 imported_package_name, imported_package_version) 381 for vts_spec in imported_vts_spec_lists: 382 push_spec = VTS_SPEC_PUSH_TEMPLATE.format( 383 hal_path=imported_package_name.replace('.', '/'), 384 hal_version=imported_package_version, 385 package_path=imported_package_str.replace('.', '/'), 386 vts_file=vts_spec) 387 ET.SubElement(file_pusher, 'option', { 388 'name': 'push', 389 'value': push_spec 390 }) 391 392 dirver_package_name = imported_package + '-vts.driver.so' 393 push_driver = VTS_LIB_PUSH_TEMPLATE_32.format( 394 lib_name=dirver_package_name) 395 ET.SubElement(file_pusher, 'option', { 396 'name': 'push', 397 'value': push_driver 398 }) 399 push_driver = VTS_LIB_PUSH_TEMPLATE_64.format( 400 lib_name=dirver_package_name) 401 ET.SubElement(file_pusher, 'option', { 402 'name': 'push', 403 'value': push_driver 404 }) 405 406 if self._is_profiling: 407 if self._test_type == 'target': 408 ET.SubElement(file_pusher, 'option', { 409 'name': 'cleanup', 410 'value': 'true' 411 }) 412 for imported_package in imported_package_lists: 413 profiler_package_name = imported_package + '-vts.profiler.so' 414 push_profiler = VTS_LIB_PUSH_TEMPLATE_32.format( 415 lib_name=profiler_package_name) 416 ET.SubElement(file_pusher, 'option', { 417 'name': 'push', 418 'value': push_profiler 419 }) 420 push_profiler = VTS_LIB_PUSH_TEMPLATE_64.format( 421 lib_name=profiler_package_name) 422 ET.SubElement(file_pusher, 'option', { 423 'name': 'push', 424 'value': push_profiler 425 }) 426 427 def GenerateTestOptionConfigure(self, test): 428 """Create the test option configuration within AndroidTest.xml 429 430 Args: 431 test: parent xml element for test option configure. 432 """ 433 ET.SubElement(test, 'option', { 434 'name': 'test-module-name', 435 'value': self._test_name 436 }) 437 438 if self._test_type == 'target': 439 if self._is_replay: 440 ET.SubElement(test, 'option', { 441 'name': 'binary-test-type', 442 'value': 'hal_hidl_replay_test' 443 }) 444 for trace in self.GetVtsHalReplayTraceFiles(): 445 ET.SubElement( 446 test, 'option', { 447 'name': 448 'hal-hidl-replay-test-trace-path', 449 'value': 450 TEST_TRACE_TEMPLATE.format( 451 hal_path=self.GetHalPath(), 452 hal_version=self.GetHalVersionToken(), 453 trace_file=trace) 454 }) 455 ET.SubElement( 456 test, 'option', { 457 'name': 'hal-hidl-package-name', 458 'value': self._hal_package_name 459 }) 460 else: 461 test_binary_file = TEST_BINEARY_TEMPLATE_32.format( 462 test_binary=self._test_binary_file[:-len('.cpp')]) 463 ET.SubElement(test, 'option', { 464 'name': 'binary-test-source', 465 'value': test_binary_file 466 }) 467 test_binary_file = TEST_BINEARY_TEMPLATE_64.format( 468 test_binary=self._test_binary_file[:-len('.cpp')]) 469 ET.SubElement(test, 'option', { 470 'name': 'binary-test-source', 471 'value': test_binary_file 472 }) 473 ET.SubElement(test, 'option', { 474 'name': 'binary-test-type', 475 'value': 'hal_hidl_gtest' 476 }) 477 if self._stop_runtime: 478 ET.SubElement(test, 'option', { 479 'name': 'binary-test-disable-framework', 480 'value': 'true' 481 }) 482 ET.SubElement(test, 'option', { 483 'name': 'binary-test-stop-native-servers', 484 'value': 'true' 485 }) 486 else: 487 test_script_file = TEST_SCRIPT_TEMPLATE.format( 488 hal_path=self.GetHalPath(), 489 hal_version=self.GetHalVersionToken(), 490 test_script=self._test_script_file[:-len('.py')]) 491 ET.SubElement(test, 'option', { 492 'name': 'test-case-path', 493 'value': test_script_file 494 }) 495 496 if self._is_profiling: 497 ET.SubElement(test, 'option', { 498 'name': 'enable-profiling', 499 'value': 'true' 500 }) 501 502 ET.SubElement(test, 'option', { 503 'name': 'test-timeout', 504 'value': self._time_out 505 }) 506 507 def Prettify(self, elem): 508 """Create a pretty-printed XML string for the Element. 509 510 Args: 511 elem: a xml element. 512 513 Regurns: 514 A pretty-printed XML string for the Element. 515 """ 516 if elem: 517 doc = minidom.Document() 518 declaration = doc.toxml() 519 rough_string = ET.tostring(elem, 'utf-8') 520 reparsed = minidom.parseString(rough_string) 521 return unescape( 522 reparsed.toprettyxml(indent=" ")[len(declaration) + 1:]) 523 524 525 LICENSE_STATEMENT_POUND = """# 526 # Copyright (C) {year} The Android Open Source Project 527 # 528 # Licensed under the Apache License, Version 2.0 (the "License"); 529 # you may not use this file except in compliance with the License. 530 # You may obtain a copy of the License at 531 # 532 # http://www.apache.org/licenses/LICENSE-2.0 533 # 534 # Unless required by applicable law or agreed to in writing, software 535 # distributed under the License is distributed on an "AS IS" BASIS, 536 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 537 # See the License for the specific language governing permissions and 538 # limitations under the License. 539 # 540 """ 541 542 LICENSE_STATEMENT_XML = """<!-- Copyright (C) {year} The Android Open Source Project 543 544 Licensed under the Apache License, Version 2.0 (the "License"); 545 you may not use this file except in compliance with the License. 546 You may obtain a copy of the License at 547 548 http://www.apache.org/licenses/LICENSE-2.0 549 550 Unless required by applicable law or agreed to in writing, software 551 distributed under the License is distributed on an "AS IS" BASIS, 552 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 553 See the License for the specific language governing permissions and 554 limitations under the License. 555 --> 556 """ 557 558 ANDROID_MK_TEMPLATE = """LOCAL_PATH := $(call my-dir) 559 560 include $(CLEAR_VARS) 561 562 LOCAL_MODULE := {test_name} 563 include test/vts/tools/build/Android.host_config.mk 564 """ 565 566 XML_HEADER = """<?xml version="1.0" encoding="utf-8"?> 567 """ 568 569 TEST_BINEARY_TEMPLATE_32 = '_32bit::DATA/nativetest/{test_binary}/{test_binary}' 570 TEST_BINEARY_TEMPLATE_64 = '_64bit::DATA/nativetest64/{test_binary}/{test_binary}' 571 572 TEST_SCRIPT_TEMPLATE = 'vts/testcases/hal/{hal_path}/{hal_version}/host/{test_script}' 573 TEST_TRACE_TEMPLATE = 'test/vts-testcase/hal-trace/{hal_path}/{hal_version}/{trace_file}' 574 VTS_SPEC_PUSH_TEMPLATE = ( 575 'spec/hardware/interfaces/{hal_path}/{hal_version}/vts/{vts_file}->' 576 '/data/local/tmp/spec/{package_path}/{hal_version}/{vts_file}') 577 VTS_LIB_PUSH_TEMPLATE_32 = 'DATA/lib/{lib_name}->/data/local/tmp/32/{lib_name}' 578 VTS_LIB_PUSH_TEMPLATE_64 = 'DATA/lib64/{lib_name}->/data/local/tmp/64/{lib_name}' 579 580 VTA_HAL_ADAPTER_PREPARER = 'com.android.tradefed.targetprep.VtsHalAdapterPreparer' 581 ANDROID_JUNIT_TEST = 'com.android.tradefed.testtype.AndroidJUnitTest' 582