Yifan Hong | 0077a9d | 2021-03-15 15:01:01 -0700 | [diff] [blame] | 1 | #!/usr/bin/python3 |
| 2 | |
| 3 | """ |
| 4 | List VTS tests for each HAL by parsing module-info.json. |
| 5 | |
| 6 | Example usage: |
| 7 | |
| 8 | # First, build modules-info.json |
| 9 | m -j "${ANDROID_PRODUCT_OUT#$ANDROID_BUILD_TOP/}/module-info.json" |
| 10 | |
| 11 | # List with pretty-printed JSON. *IDL packages without a VTS module will show up |
| 12 | # as keys with empty lists. |
| 13 | ./list_hals_vts.py | python3 -m json.tool |
| 14 | |
| 15 | # List with CSV. *IDL packages without a VTS module will show up as a line with |
| 16 | # empty value in the VTS module column. |
| 17 | ./list_hals_vts.py --csv |
| 18 | """ |
| 19 | |
| 20 | import argparse |
| 21 | import collections |
| 22 | import csv |
| 23 | import io |
| 24 | import json |
| 25 | import os |
| 26 | import logging |
| 27 | import pathlib |
| 28 | import re |
| 29 | import sys |
| 30 | |
| 31 | PATH_PACKAGE_PATTERN = re.compile( |
| 32 | r'^hardware/interfaces/(?P<path>(?:\w+/)*?)(?:aidl|(?P<version>\d+\.\d+))/.*') |
| 33 | |
| 34 | |
| 35 | class CriticalHandler(logging.StreamHandler): |
| 36 | def emit(self, record): |
| 37 | super(CriticalHandler, self).emit(record) |
| 38 | if record.levelno >= logging.CRITICAL: |
| 39 | sys.exit(1) |
| 40 | |
| 41 | |
| 42 | logger = logging.getLogger(__name__) |
| 43 | logger.addHandler(CriticalHandler()) |
| 44 | |
| 45 | |
| 46 | def default_json(): |
| 47 | out = os.environ.get('ANDROID_PRODUCT_OUT') |
| 48 | if not out: return None |
| 49 | return os.path.join(out, 'module-info.json') |
| 50 | |
| 51 | |
| 52 | def infer_package(path): |
| 53 | """ |
| 54 | Infer package from a relative path from build top where a VTS module lives. |
| 55 | |
| 56 | :param path: a path like 'hardware/interfaces/vibrator/aidl/vts' |
| 57 | :return: The inferred *IDL package, e.g. 'android.hardware.vibrator' |
| 58 | |
| 59 | >>> infer_package('hardware/interfaces/automotive/sv/1.0/vts/functional') |
| 60 | 'android.hardware.automotive.sv@1.0' |
| 61 | >>> infer_package('hardware/interfaces/vibrator/aidl/vts') |
| 62 | 'android.hardware.vibrator' |
| 63 | """ |
| 64 | mo = re.match(PATH_PACKAGE_PATTERN, path) |
| 65 | if not mo: return None |
| 66 | package = 'android.hardware.' + ('.'.join(pathlib.Path(mo.group('path')).parts)) |
| 67 | if mo.group('version'): |
| 68 | package += '@' + mo.group('version') |
| 69 | return package |
| 70 | |
| 71 | |
| 72 | def load_modules_info(json_file): |
| 73 | """ |
| 74 | :param json_file: The path to modules-info.json |
| 75 | :return: a dictionary, where the keys are inferred *IDL package names, and |
| 76 | values are a list of VTS modules with that inferred package name. |
| 77 | """ |
| 78 | with open(json_file) as fp: |
| 79 | root = json.load(fp) |
| 80 | ret = collections.defaultdict(list) |
| 81 | for module_name, module_info in root.items(): |
| 82 | if 'vts' not in module_info.get('compatibility_suites', []): |
| 83 | continue |
| 84 | for path in module_info.get('path', []): |
| 85 | inferred_package = infer_package(path) |
| 86 | if not inferred_package: |
| 87 | continue |
| 88 | ret[inferred_package].append(module_name) |
| 89 | return ret |
| 90 | |
| 91 | |
| 92 | def add_missing_idl(vts_modules): |
| 93 | top = os.environ.get("ANDROID_BUILD_TOP") |
| 94 | interfaces = None |
| 95 | if top: |
| 96 | interfaces = os.path.join(top, "hardware", "interfaces") |
| 97 | else: |
| 98 | logger.warning("Missing ANDROID_BUILD_TOP") |
| 99 | interfaces = "hardware/interfaces" |
| 100 | if not os.path.isdir(interfaces): |
| 101 | logger.error("Not adding missing *IDL modules because missing hardware/interfaces dir") |
| 102 | return |
| 103 | assert not interfaces.endswith(os.path.sep) |
| 104 | for root, dirs, files in os.walk(interfaces): |
| 105 | for dir in dirs: |
| 106 | full_dir = os.path.join(root, dir) |
| 107 | assert full_dir.startswith(interfaces) |
| 108 | top_rel_dir = os.path.join('hardware', 'interfaces', full_dir[len(interfaces) + 1:]) |
| 109 | inferred_package = infer_package(top_rel_dir) |
| 110 | if inferred_package is None: |
| 111 | continue |
| 112 | if inferred_package not in vts_modules: |
| 113 | vts_modules[inferred_package] = [] |
| 114 | |
| 115 | |
| 116 | def main(): |
| 117 | parser = argparse.ArgumentParser(__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) |
| 118 | parser.add_argument('json', metavar='module-info.json', default=default_json(), nargs='?') |
| 119 | parser.add_argument('--csv', action='store_true', help='Print CSV. If not specified print JSON.') |
| 120 | args = parser.parse_args() |
| 121 | if not args.json: |
| 122 | logger.critical('No module-info.json is specified or found.') |
| 123 | vts_modules = load_modules_info(args.json) |
| 124 | add_missing_idl(vts_modules) |
| 125 | |
| 126 | if args.csv: |
| 127 | out = io.StringIO() |
| 128 | writer = csv.writer(out, ) |
| 129 | writer.writerow(["package", "vts_module"]) |
| 130 | for package, modules in vts_modules.items(): |
| 131 | if not modules: |
| 132 | writer.writerow([package, ""]) |
| 133 | for module in modules: |
| 134 | writer.writerow([package, module]) |
| 135 | result = out.getvalue() |
| 136 | else: |
| 137 | result = json.dumps(vts_modules) |
| 138 | |
| 139 | print(result) |
| 140 | |
| 141 | |
| 142 | if __name__ == '__main__': |
| 143 | main() |