1# Copyright 2018, The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15""" 16Utils for finder classes. 17""" 18 19from __future__ import print_function 20import logging 21import multiprocessing 22import os 23import re 24import subprocess 25import time 26import xml.etree.ElementTree as ET 27 28# pylint: disable=import-error 29import atest_error 30import atest_enum 31import constants 32 33# Helps find apk files listed in a test config (AndroidTest.xml) file. 34# Matches "filename.apk" in <option name="foo", value="filename.apk" /> 35# We want to make sure we don't grab apks with paths in their name since we 36# assume the apk name is the build target. 37_APK_RE = re.compile(r'^[^/]+\.apk$', re.I) 38# RE for check if TEST or TEST_F is in a cc file or not. 39_CC_CLASS_RE = re.compile(r'TEST(_F)?\(', re.I) 40# Parse package name from the package declaration line of a java or a kotlin file. 41# Group matches "foo.bar" of line "package foo.bar;" or "package foo.bar" 42_PACKAGE_RE = re.compile(r'\s*package\s+(?P<package>[^(;|\s)]+)\s*', re.I) 43# Matches install paths in module_info to install location(host or device). 44_HOST_PATH_RE = re.compile(r'.*\/host\/.*', re.I) 45_DEVICE_PATH_RE = re.compile(r'.*\/target\/.*', re.I) 46 47# Explanation of FIND_REFERENCE_TYPEs: 48# ---------------------------------- 49# 0. CLASS: Name of a java/kotlin class, usually file is named the same (HostTest lives 50# in HostTest.java or HostTest.kt) 51# 1. QUALIFIED_CLASS: Like CLASS but also contains the package in front like 52#. com.android.tradefed.testtype.HostTest. 53# 2. PACKAGE: Name of a java package. 54# 3. INTEGRATION: XML file name in one of the 4 integration config directories. 55# 4. CC_CLASS: Name of a cc class. 56 57FIND_REFERENCE_TYPE = atest_enum.AtestEnum(['CLASS', 'QUALIFIED_CLASS', 58 'PACKAGE', 'INTEGRATION', 'CC_CLASS']) 59# Get cpu count. 60_CPU_COUNT = 1 61try: 62 _CPU_COUNT = multiprocessing.cpu_count() 63except NotImplementedError: 64 pass 65# Unix find commands for searching for test files based on test type input. 66# Note: Find (unlike grep) exits with status 0 if nothing found. 67FIND_CMDS = { 68 FIND_REFERENCE_TYPE.CLASS: r"find {0} -type d {1} -prune -o -type f " 69 r"\( -name '*{2}.java' -o -name '*{2}.kt' \) -print", 70 FIND_REFERENCE_TYPE.QUALIFIED_CLASS: r"find {0} -type d {1} -prune -o " 71 r"\( -wholename '*{2}.java' " 72 r"-o -wholename '*{2}.kt' \) -print", 73 FIND_REFERENCE_TYPE.PACKAGE: r"find {0} -type d {1} -prune -o -wholename " 74 r"'*{2}' -type d -print", 75 FIND_REFERENCE_TYPE.INTEGRATION: r"find {0} -type d {1} -prune -o -wholename " 76 r"'*{2}.xml' -print", 77 FIND_REFERENCE_TYPE.CC_CLASS: r"find {0} -type d {1} -prune -o -type f " 78 r"\( -name '*.cpp' -o -name '*.cc' \)" 79 r" | xargs -P " + str(_CPU_COUNT) + 80 r" grep -s -H -E 'TEST(_F)?\({2},' {{}} + || true" 81} 82 83# XML parsing related constants. 84_COMPATIBILITY_PACKAGE_PREFIX = "com.android.compatibility" 85_CTS_JAR = "cts-tradefed" 86_XML_PUSH_DELIM = '->' 87_APK_SUFFIX = '.apk' 88# Setup script for device perf tests. 89_PERF_SETUP_LABEL = 'perf-setup.sh' 90 91# XML tags. 92_XML_NAME = 'name' 93_XML_VALUE = 'value' 94 95# VTS xml parsing constants. 96_VTS_TEST_MODULE = 'test-module-name' 97_VTS_MODULE = 'module-name' 98_VTS_BINARY_SRC = 'binary-test-source' 99_VTS_PUSH_GROUP = 'push-group' 100_VTS_PUSH = 'push' 101_VTS_BINARY_SRC_DELIM = '::' 102_VTS_PUSH_DIR = os.path.join(os.environ.get(constants.ANDROID_BUILD_TOP, ''), 103 'test', 'vts', 'tools', 'vts-tradefed', 'res', 104 'push_groups') 105_VTS_PUSH_SUFFIX = '.push' 106_VTS_BITNESS = 'append-bitness' 107_VTS_BITNESS_TRUE = 'true' 108_VTS_BITNESS_32 = '32' 109_VTS_BITNESS_64 = '64' 110_VTS_TEST_FILE = 'test-file-name' 111_VTS_APK = 'apk' 112# Matches 'DATA/target' in '_32bit::DATA/target' 113_VTS_BINARY_SRC_DELIM_RE = re.compile(r'.*::(?P<target>.*)$') 114_VTS_OUT_DATA_APP_PATH = 'DATA/app' 115 116# pylint: disable=inconsistent-return-statements 117def split_methods(user_input): 118 """Split user input string into test reference and list of methods. 119 120 Args: 121 user_input: A string of the user's input. 122 Examples: 123 class_name 124 class_name#method1,method2 125 path 126 path#method1,method2 127 Returns: 128 A tuple. First element is String of test ref and second element is 129 a set of method name strings or empty list if no methods included. 130 Exception: 131 atest_error.TooManyMethodsError raised when input string is trying to 132 specify too many methods in a single positional argument. 133 134 Examples of unsupported input strings: 135 module:class#method,class#method 136 class1#method,class2#method 137 path1#method,path2#method 138 """ 139 parts = user_input.split('#') 140 if len(parts) == 1: 141 return parts[0], frozenset() 142 elif len(parts) == 2: 143 return parts[0], frozenset(parts[1].split(',')) 144 raise atest_error.TooManyMethodsError( 145 'Too many methods specified with # character in user input: %s.' 146 '\n\nOnly one class#method combination supported per positional' 147 ' argument. Multiple classes should be separated by spaces: ' 148 'class#method class#method') 149 150 151# pylint: disable=inconsistent-return-statements 152def get_fully_qualified_class_name(test_path): 153 """Parse the fully qualified name from the class java file. 154 155 Args: 156 test_path: A string of absolute path to the java class file. 157 158 Returns: 159 A string of the fully qualified class name. 160 161 Raises: 162 atest_error.MissingPackageName if no class name can be found. 163 """ 164 with open(test_path) as class_file: 165 for line in class_file: 166 match = _PACKAGE_RE.match(line) 167 if match: 168 package = match.group('package') 169 cls = os.path.splitext(os.path.split(test_path)[1])[0] 170 return '%s.%s' % (package, cls) 171 raise atest_error.MissingPackageNameError('%s: Test class java file' 172 'does not contain a package' 173 'name.'% test_path) 174 175 176def has_cc_class(test_path): 177 """Find out if there is any test case in the cc file. 178 179 Args: 180 test_path: A string of absolute path to the cc file. 181 182 Returns: 183 Boolean: has cc class in test_path or not. 184 """ 185 with open(test_path) as class_file: 186 for line in class_file: 187 match = _CC_CLASS_RE.match(line) 188 if match: 189 return True 190 return False 191 192 193def get_package_name(file_name): 194 """Parse the package name from a java file. 195 196 Args: 197 file_name: A string of the absolute path to the java file. 198 199 Returns: 200 A string of the package name or None 201 """ 202 with open(file_name) as data: 203 for line in data: 204 match = _PACKAGE_RE.match(line) 205 if match: 206 return match.group('package') 207 208 209def extract_test_path(output, is_native_test=False): 210 """Extract the test path from the output of a unix 'find' command. 211 212 Example of find output for CLASS find cmd: 213 /<some_root>/cts/tests/jank/src/android/jank/cts/ui/CtsDeviceJankUi.java 214 215 Args: 216 output: A string output of a unix 'find' command. 217 is_native_test: A boolean variable of whether to search for a native 218 test or not. 219 220 Returns: 221 A string of the test path or None if output is '' or None. 222 """ 223 if not output: 224 return None 225 tests = output.strip('\n').split('\n') 226 if is_native_test: 227 tests = list(set([path.split(":")[0] for path in tests])) 228 return extract_test_from_tests(tests) 229 230 231def extract_test_from_tests(tests): 232 """Extract the test path from the tests. 233 234 Return the test to run from tests. If more than one option, prompt the user 235 to select one. 236 237 Args: 238 tests: A string list which contains multiple test paths. 239 240 Returns: 241 A string of the test path or None if tests is out-of-index or ''. 242 """ 243 count = len(tests) 244 test_index = 0 245 if count == 0: 246 return None 247 elif count > 1: 248 numbered_list = ['%s: %s' % (i, t) for i, t in enumerate(tests)] 249 print('Multiple tests found:\n{0}'.format('\n'.join(numbered_list))) 250 try: 251 test_index = int(raw_input('Please enter number of test to use ' 252 'or hit return to keep searching: ')) 253 if test_index not in range(count): 254 logging.warn('The input %s is out-of-range(%s).', 255 test_index, (count-1)) 256 return None 257 except ValueError: 258 return None 259 return tests[test_index] 260 261 262def static_var(varname, value): 263 """Decorator to cache static variable.""" 264 265 def fun_var_decorate(func): 266 """Set the static variable in a function.""" 267 setattr(func, varname, value) 268 return func 269 return fun_var_decorate 270 271 272@static_var("cached_ignore_dirs", []) 273def _get_ignored_dirs(): 274 """Get ignore dirs in find command. 275 276 Since we can't construct a single find cmd to find the target and 277 filter-out the dir with .out-dir, .find-ignore and $OUT-DIR. We have 278 to run the 1st find cmd to find these dirs. Then, we can use these 279 results to generate the real find cmd. 280 281 Return: 282 A list of the ignore dirs. 283 """ 284 out_dirs = _get_ignored_dirs.cached_ignore_dirs 285 if not out_dirs: 286 build_top = os.environ.get(constants.ANDROID_BUILD_TOP) 287 find_out_dir_cmd = (r'find %s -maxdepth 2 ' 288 r'-type f \( -name ".out-dir" -o -name ' 289 r'".find-ignore" \)') % build_top 290 out_files = subprocess.check_output(find_out_dir_cmd, shell=True) 291 # Get all dirs with .out-dir or .find-ignore 292 if out_files: 293 out_files = out_files.splitlines() 294 for out_file in out_files: 295 if out_file: 296 out_dirs.append(os.path.dirname(out_file.strip())) 297 # Get the out folder if user specified $OUT_DIR 298 custom_out_dir = os.environ.get(constants.ANDROID_OUT_DIR) 299 if custom_out_dir: 300 user_out_dir = None 301 if os.path.isabs(custom_out_dir): 302 user_out_dir = custom_out_dir 303 else: 304 user_out_dir = os.path.join(build_top, custom_out_dir) 305 # only ignore the out_dir when it under $ANDROID_BUILD_TOP 306 if build_top in user_out_dir: 307 if user_out_dir not in out_dirs: 308 out_dirs.append(user_out_dir) 309 _get_ignored_dirs.cached_ignore_dirs = out_dirs 310 return out_dirs 311 312 313def _get_prune_cond_of_ignored_dirs(): 314 """Get the prune condition of ignore dirs. 315 316 Generation a string of the prune condition in the find command. 317 It will filter-out the dir with .out-dir, .find-ignore and $OUT-DIR. 318 Because they are the out dirs, we don't have to find them. 319 320 Return: 321 A string of the prune condition of the ignore dirs. 322 """ 323 out_dirs = _get_ignored_dirs() 324 prune_cond = r'\( -name ".*"' 325 for out_dir in out_dirs: 326 prune_cond += r' -o -path %s' % out_dir 327 prune_cond += r' \)' 328 return prune_cond 329 330 331def run_find_cmd(ref_type, search_dir, target): 332 """Find a path to a target given a search dir and a target name. 333 334 Args: 335 ref_type: An AtestEnum of the reference type. 336 search_dir: A string of the dirpath to search in. 337 target: A string of what you're trying to find. 338 339 Return: 340 A string of the path to the target. 341 """ 342 prune_cond = _get_prune_cond_of_ignored_dirs() 343 find_cmd = FIND_CMDS[ref_type].format(search_dir, prune_cond, target) 344 start = time.time() 345 ref_name = FIND_REFERENCE_TYPE[ref_type] 346 logging.debug('Executing %s find cmd: %s', ref_name, find_cmd) 347 out = subprocess.check_output(find_cmd, shell=True) 348 logging.debug('%s find completed in %ss', ref_name, time.time() - start) 349 logging.debug('%s find cmd out: %s', ref_name, out) 350 if ref_type == FIND_REFERENCE_TYPE.CC_CLASS: 351 return extract_test_path(out, True) 352 return extract_test_path(out) 353 354 355def find_class_file(search_dir, class_name, is_native_test=False): 356 """Find a path to a class file given a search dir and a class name. 357 358 Args: 359 search_dir: A string of the dirpath to search in. 360 class_name: A string of the class to search for. 361 is_native_test: A boolean variable of whether to search for a native 362 test or not. 363 364 Return: 365 A string of the path to the java/cc file. 366 """ 367 if is_native_test: 368 find_target = class_name 369 ref_type = FIND_REFERENCE_TYPE.CC_CLASS 370 return run_find_cmd(ref_type, search_dir, find_target) 371 if '.' in class_name: 372 find_target = class_name.replace('.', '/') 373 ref_type = FIND_REFERENCE_TYPE.QUALIFIED_CLASS 374 else: 375 find_target = class_name 376 ref_type = FIND_REFERENCE_TYPE.CLASS 377 return run_find_cmd(ref_type, search_dir, find_target) 378 379 380def is_equal_or_sub_dir(sub_dir, parent_dir): 381 """Return True sub_dir is sub dir or equal to parent_dir. 382 383 Args: 384 sub_dir: A string of the sub directory path. 385 parent_dir: A string of the parent directory path. 386 387 Returns: 388 A boolean of whether both are dirs and sub_dir is sub of parent_dir 389 or is equal to parent_dir. 390 """ 391 # avoid symlink issues with real path 392 parent_dir = os.path.realpath(parent_dir) 393 sub_dir = os.path.realpath(sub_dir) 394 if not os.path.isdir(sub_dir) or not os.path.isdir(parent_dir): 395 return False 396 return os.path.commonprefix([sub_dir, parent_dir]) == parent_dir 397 398 399def find_parent_module_dir(root_dir, start_dir, module_info): 400 """From current dir search up file tree until root dir for module dir. 401 402 Args: 403 root_dir: A string of the dir that is the parent of the start dir. 404 start_dir: A string of the dir to start searching up from. 405 module_info: ModuleInfo object containing module information from the 406 build system. 407 408 Returns: 409 A string of the module dir relative to root, None if no Module Dir 410 found. There may be multiple testable modules at this level. 411 412 Exceptions: 413 ValueError: Raised if cur_dir not dir or not subdir of root dir. 414 """ 415 if not is_equal_or_sub_dir(start_dir, root_dir): 416 raise ValueError('%s not in repo %s' % (start_dir, root_dir)) 417 auto_gen_dir = None 418 current_dir = start_dir 419 while current_dir != root_dir: 420 # TODO (b/112904944) - migrate module_finder functions to here and 421 # reuse them. 422 rel_dir = os.path.relpath(current_dir, root_dir) 423 # Check if actual config file here 424 if os.path.isfile(os.path.join(current_dir, constants.MODULE_CONFIG)): 425 return rel_dir 426 # Check module_info if auto_gen config or robo (non-config) here 427 for mod in module_info.path_to_module_info.get(rel_dir, []): 428 if module_info.is_robolectric_module(mod): 429 return rel_dir 430 for test_config in mod.get(constants.MODULE_TEST_CONFIG, []): 431 if os.path.isfile(os.path.join(root_dir, test_config)): 432 return rel_dir 433 if mod.get('auto_test_config'): 434 auto_gen_dir = rel_dir 435 # Don't return for auto_gen, keep checking for real config, because 436 # common in cts for class in apk that's in hostside test setup. 437 current_dir = os.path.dirname(current_dir) 438 return auto_gen_dir 439 440 441def get_targets_from_xml(xml_file, module_info): 442 """Retrieve build targets from the given xml. 443 444 Just a helper func on top of get_targets_from_xml_root. 445 446 Args: 447 xml_file: abs path to xml file. 448 module_info: ModuleInfo class used to verify targets are valid modules. 449 450 Returns: 451 A set of build targets based on the signals found in the xml file. 452 """ 453 xml_root = ET.parse(xml_file).getroot() 454 return get_targets_from_xml_root(xml_root, module_info) 455 456 457def _get_apk_target(apk_target): 458 """Return the sanitized apk_target string from the xml. 459 460 The apk_target string can be of 2 forms: 461 - apk_target.apk 462 - apk_target.apk->/path/to/install/apk_target.apk 463 464 We want to return apk_target in both cases. 465 466 Args: 467 apk_target: String of target name to clean. 468 469 Returns: 470 String of apk_target to build. 471 """ 472 apk = apk_target.split(_XML_PUSH_DELIM, 1)[0].strip() 473 return apk[:-len(_APK_SUFFIX)] 474 475 476def _is_apk_target(name, value): 477 """Return True if XML option is an apk target. 478 479 We have some scenarios where an XML option can be an apk target: 480 - value is an apk file. 481 - name is a 'push' option where value holds the apk_file + other stuff. 482 483 Args: 484 name: String name of XML option. 485 value: String value of the XML option. 486 487 Returns: 488 True if it's an apk target we should build, False otherwise. 489 """ 490 if _APK_RE.match(value): 491 return True 492 if name == 'push' and value.endswith(_APK_SUFFIX): 493 return True 494 return False 495 496 497def get_targets_from_xml_root(xml_root, module_info): 498 """Retrieve build targets from the given xml root. 499 500 We're going to pull the following bits of info: 501 - Parse any .apk files listed in the config file. 502 - Parse option value for "test-module-name" (for vts tests). 503 - Look for the perf script. 504 505 Args: 506 module_info: ModuleInfo class used to verify targets are valid modules. 507 xml_root: ElementTree xml_root for us to look through. 508 509 Returns: 510 A set of build targets based on the signals found in the xml file. 511 """ 512 targets = set() 513 option_tags = xml_root.findall('.//option') 514 for tag in option_tags: 515 target_to_add = None 516 name = tag.attrib[_XML_NAME].strip() 517 value = tag.attrib[_XML_VALUE].strip() 518 if _is_apk_target(name, value): 519 target_to_add = _get_apk_target(value) 520 elif _PERF_SETUP_LABEL in value: 521 targets.add(_PERF_SETUP_LABEL) 522 continue 523 524 # Let's make sure we can actually build the target. 525 if target_to_add and module_info.is_module(target_to_add): 526 targets.add(target_to_add) 527 elif target_to_add: 528 logging.warning('Build target (%s) not present in module info, ' 529 'skipping build', target_to_add) 530 531 # TODO (b/70813166): Remove this lookup once all runtime dependencies 532 # can be listed as a build dependencies or are in the base test harness. 533 nodes_with_class = xml_root.findall(".//*[@class]") 534 for class_attr in nodes_with_class: 535 fqcn = class_attr.attrib['class'].strip() 536 if fqcn.startswith(_COMPATIBILITY_PACKAGE_PREFIX): 537 targets.add(_CTS_JAR) 538 logging.debug('Targets found in config file: %s', targets) 539 return targets 540 541 542def _get_vts_push_group_targets(push_file, rel_out_dir): 543 """Retrieve vts push group build targets. 544 545 A push group file is a file that list out test dependencies and other push 546 group files. Go through the push file and gather all the test deps we need. 547 548 Args: 549 push_file: Name of the push file in the VTS 550 rel_out_dir: Abs path to the out dir to help create vts build targets. 551 552 Returns: 553 Set of string which represent build targets. 554 """ 555 targets = set() 556 full_push_file_path = os.path.join(_VTS_PUSH_DIR, push_file) 557 # pylint: disable=invalid-name 558 with open(full_push_file_path) as f: 559 for line in f: 560 target = line.strip() 561 # Skip empty lines. 562 if not target: 563 continue 564 565 # This is a push file, get the targets from it. 566 if target.endswith(_VTS_PUSH_SUFFIX): 567 targets |= _get_vts_push_group_targets(line.strip(), 568 rel_out_dir) 569 continue 570 sanitized_target = target.split(_XML_PUSH_DELIM, 1)[0].strip() 571 targets.add(os.path.join(rel_out_dir, sanitized_target)) 572 return targets 573 574 575def _specified_bitness(xml_root): 576 """Check if the xml file contains the option append-bitness. 577 578 Args: 579 xml_root: abs path to xml file. 580 581 Returns: 582 True if xml specifies to append-bitness, False otherwise. 583 """ 584 option_tags = xml_root.findall('.//option') 585 for tag in option_tags: 586 value = tag.attrib[_XML_VALUE].strip() 587 name = tag.attrib[_XML_NAME].strip() 588 if name == _VTS_BITNESS and value == _VTS_BITNESS_TRUE: 589 return True 590 return False 591 592 593def _get_vts_binary_src_target(value, rel_out_dir): 594 """Parse out the vts binary src target. 595 596 The value can be in the following pattern: 597 - {_32bit,_64bit,_IPC32_32bit}::DATA/target (DATA/target) 598 - DATA/target->/data/target (DATA/target) 599 - out/host/linx-x86/bin/VtsSecuritySelinuxPolicyHostTest (the string as 600 is) 601 602 Args: 603 value: String of the XML option value to parse. 604 rel_out_dir: String path of out dir to prepend to target when required. 605 606 Returns: 607 String of the target to build. 608 """ 609 # We'll assume right off the bat we can use the value as is and modify it if 610 # necessary, e.g. out/host/linux-x86/bin... 611 target = value 612 # _32bit::DATA/target 613 match = _VTS_BINARY_SRC_DELIM_RE.match(value) 614 if match: 615 target = os.path.join(rel_out_dir, match.group('target')) 616 # DATA/target->/data/target 617 elif _XML_PUSH_DELIM in value: 618 target = value.split(_XML_PUSH_DELIM, 1)[0].strip() 619 target = os.path.join(rel_out_dir, target) 620 return target 621 622 623def get_plans_from_vts_xml(xml_file): 624 """Get configs which are included by xml_file. 625 626 We're looking for option(include) to get all dependency plan configs. 627 628 Args: 629 xml_file: Absolute path to xml file. 630 631 Returns: 632 A set of plan config paths which are depended by xml_file. 633 """ 634 if not os.path.exists(xml_file): 635 raise atest_error.XmlNotExistError('%s: The xml file does' 636 'not exist' % xml_file) 637 plans = set() 638 xml_root = ET.parse(xml_file).getroot() 639 plans.add(xml_file) 640 option_tags = xml_root.findall('.//include') 641 if not option_tags: 642 return plans 643 # Currently, all vts xmls live in the same dir : 644 # https://android.googlesource.com/platform/test/vts/+/master/tools/vts-tradefed/res/config/ 645 # If the vts plans start using folders to organize the plans, the logic here 646 # should be changed. 647 xml_dir = os.path.dirname(xml_file) 648 for tag in option_tags: 649 name = tag.attrib[_XML_NAME].strip() 650 plans |= get_plans_from_vts_xml(os.path.join(xml_dir, name + ".xml")) 651 return plans 652 653 654def get_targets_from_vts_xml(xml_file, rel_out_dir, module_info): 655 """Parse a vts xml for test dependencies we need to build. 656 657 We have a separate vts parsing function because we make a big assumption 658 on the targets (the way they're formatted and what they represent) and we 659 also create these build targets in a very special manner as well. 660 The 6 options we're looking for are: 661 - binary-test-source 662 - push-group 663 - push 664 - test-module-name 665 - test-file-name 666 - apk 667 668 Args: 669 module_info: ModuleInfo class used to verify targets are valid modules. 670 rel_out_dir: Abs path to the out dir to help create vts build targets. 671 xml_file: abs path to xml file. 672 673 Returns: 674 A set of build targets based on the signals found in the xml file. 675 """ 676 xml_root = ET.parse(xml_file).getroot() 677 targets = set() 678 option_tags = xml_root.findall('.//option') 679 for tag in option_tags: 680 value = tag.attrib[_XML_VALUE].strip() 681 name = tag.attrib[_XML_NAME].strip() 682 if name in [_VTS_TEST_MODULE, _VTS_MODULE]: 683 if module_info.is_module(value): 684 targets.add(value) 685 else: 686 logging.warning('vts test module (%s) not present in module ' 687 'info, skipping build', value) 688 elif name == _VTS_BINARY_SRC: 689 targets.add(_get_vts_binary_src_target(value, rel_out_dir)) 690 elif name == _VTS_PUSH_GROUP: 691 # Look up the push file and parse out build artifacts (as well as 692 # other push group files to parse). 693 targets |= _get_vts_push_group_targets(value, rel_out_dir) 694 elif name == _VTS_PUSH: 695 # Parse out the build artifact directly. 696 push_target = value.split(_XML_PUSH_DELIM, 1)[0].strip() 697 # If the config specified append-bitness, append the bits suffixes 698 # to the target. 699 if _specified_bitness(xml_root): 700 targets.add(os.path.join(rel_out_dir, push_target + _VTS_BITNESS_32)) 701 targets.add(os.path.join(rel_out_dir, push_target + _VTS_BITNESS_64)) 702 else: 703 targets.add(os.path.join(rel_out_dir, push_target)) 704 elif name == _VTS_TEST_FILE: 705 # The _VTS_TEST_FILE values can be set in 2 possible ways: 706 # 1. test_file.apk 707 # 2. DATA/app/test_file/test_file.apk 708 # We'll assume that test_file.apk (#1) is in an expected path (but 709 # that is not true, see b/76158619) and create the full path for it 710 # and then append the _VTS_TEST_FILE value to targets to build. 711 target = os.path.join(rel_out_dir, value) 712 # If value is just an APK, specify the path that we expect it to be in 713 # e.g. out/host/linux-x86/vts/android-vts/testcases/DATA/app/test_file/test_file.apk 714 head, _ = os.path.split(value) 715 if not head: 716 target = os.path.join(rel_out_dir, _VTS_OUT_DATA_APP_PATH, 717 _get_apk_target(value), value) 718 targets.add(target) 719 elif name == _VTS_APK: 720 targets.add(os.path.join(rel_out_dir, value)) 721 logging.debug('Targets found in config file: %s', targets) 722 return targets 723 724 725def get_dir_path_and_filename(path): 726 """Return tuple of dir and file name from given path. 727 728 Args: 729 path: String of path to break up. 730 731 Returns: 732 Tuple of (dir, file) paths. 733 """ 734 if os.path.isfile(path): 735 dir_path, file_path = os.path.split(path) 736 else: 737 dir_path, file_path = path, None 738 return dir_path, file_path 739 740 741def get_cc_filter(class_name, methods): 742 """Get the cc filter. 743 744 Args: 745 class_name: class name of the cc test. 746 methods: a list of method names. 747 748 Returns: 749 A formatted string for cc filter. 750 Ex: "class1.method1:class1.method2" or "class1.*" 751 """ 752 if methods: 753 return ":".join(["%s.%s" % (class_name, x) for x in methods]) 754 return "%s.*" % class_name 755 756 757def search_integration_dirs(name, int_dirs): 758 """Search integration dirs for name and return full path. 759 760 Args: 761 name: A string of plan name needed to be found. 762 int_dirs: A list of path needed to be searched. 763 764 Returns: 765 A string of the test path. 766 Ask user to select if multiple tests are found. 767 None if no matched test found. 768 """ 769 root_dir = os.environ.get(constants.ANDROID_BUILD_TOP) 770 test_files = [] 771 for integration_dir in int_dirs: 772 abs_path = os.path.join(root_dir, integration_dir) 773 test_file = run_find_cmd(FIND_REFERENCE_TYPE.INTEGRATION, abs_path, 774 name) 775 if test_file: 776 test_files.append(test_file) 777 return extract_test_from_tests(test_files) 778 779 780def get_int_dir_from_path(path, int_dirs): 781 """Search integration dirs for the given path and return path of dir. 782 783 Args: 784 path: A string of path needed to be found. 785 int_dirs: A list of path needed to be searched. 786 787 Returns: 788 A string of the test dir. None if no matched path found. 789 """ 790 root_dir = os.environ.get(constants.ANDROID_BUILD_TOP) 791 if not os.path.exists(path): 792 return None 793 dir_path, file_name = get_dir_path_and_filename(path) 794 int_dir = None 795 for possible_dir in int_dirs: 796 abs_int_dir = os.path.join(root_dir, possible_dir) 797 if is_equal_or_sub_dir(dir_path, abs_int_dir): 798 int_dir = abs_int_dir 799 break 800 if not file_name: 801 logging.warn('Found dir (%s) matching input (%s).' 802 ' Referencing an entire Integration/Suite dir' 803 ' is not supported. If you are trying to reference' 804 ' a test by its path, please input the path to' 805 ' the integration/suite config file itself.', 806 int_dir, path) 807 return None 808 return int_dir 809 810 811def get_install_locations(installed_paths): 812 """Get install locations from installed paths. 813 814 Args: 815 installed_paths: List of installed_paths from module_info. 816 817 Returns: 818 Set of install locations from module_info installed_paths. e.g. 819 set(['host', 'device']) 820 """ 821 install_locations = set() 822 for path in installed_paths: 823 if _HOST_PATH_RE.match(path): 824 install_locations.add(constants.DEVICELESS_TEST) 825 elif _DEVICE_PATH_RE.match(path): 826 install_locations.add(constants.DEVICE_TEST) 827 return install_locations 828 829 830def get_levenshtein_distance(test_name, module_name, dir_costs=constants.COST_TYPO): 831 """Return an edit distance between test_name and module_name. 832 833 Levenshtein Distance has 3 actions: delete, insert and replace. 834 dis_costs makes each action weigh differently. 835 836 Args: 837 test_name: A keyword from the users. 838 module_name: A testable module name. 839 dir_costs: A tuple which contains 3 integer, where dir represents 840 Deletion, Insertion and Replacement respectively. 841 For guessing typos: (1, 1, 1) gives the best result. 842 For searching keywords, (8, 1, 5) gives the best result. 843 844 Returns: 845 An edit distance integer between test_name and module_name. 846 """ 847 rows = len(test_name) + 1 848 cols = len(module_name) + 1 849 deletion, insertion, replacement = dir_costs 850 851 # Creating a Dynamic Programming Matrix and weighting accordingly. 852 dp_matrix = [[0 for _ in range(cols)] for _ in range(rows)] 853 # Weigh rows/deletion 854 for row in range(1, rows): 855 dp_matrix[row][0] = row * deletion 856 # Weigh cols/insertion 857 for col in range(1, cols): 858 dp_matrix[0][col] = col * insertion 859 # The core logic of LD 860 for col in range(1, cols): 861 for row in range(1, rows): 862 if test_name[row-1] == module_name[col-1]: 863 cost = 0 864 else: 865 cost = replacement 866 dp_matrix[row][col] = min(dp_matrix[row-1][col] + deletion, 867 dp_matrix[row][col-1] + insertion, 868 dp_matrix[row-1][col-1] + cost) 869 870 return dp_matrix[row][col] 871