• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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