1#!/usr/bin/env python3 2# 3# Copyright (C) 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"""A tool to generate TradeFed test config file. 18""" 19 20import argparse 21import re 22import os 23import shutil 24import sys 25from xml.dom.minidom import parse 26 27ATTRIBUTE_LABEL = 'android:label' 28ATTRIBUTE_RUNNER = 'android:name' 29ATTRIBUTE_PACKAGE = 'package' 30 31PLACEHOLDER_LABEL = '{LABEL}' 32PLACEHOLDER_EXTRA_CONFIGS = '{EXTRA_CONFIGS}' 33PLACEHOLDER_MODULE = '{MODULE}' 34PLACEHOLDER_PACKAGE = '{PACKAGE}' 35PLACEHOLDER_RUNNER = '{RUNNER}' 36PLACEHOLDER_TEST_TYPE = '{TEST_TYPE}' 37 38 39def main(argv): 40 """Entry point of auto_gen_test_config. 41 42 Args: 43 argv: A list of arguments. 44 Returns: 45 0 if no error, otherwise 1. 46 """ 47 48 parser = argparse.ArgumentParser() 49 parser.add_argument( 50 "target_config", 51 help="Path to the generated output config.") 52 parser.add_argument( 53 "android_manifest", 54 help="Path to AndroidManifest.xml or output of 'aapt2 dump xmltree' with .xmltree extension.") 55 parser.add_argument( 56 "empty_config", 57 help="Path to the empty config template.") 58 parser.add_argument( 59 "instrumentation_test_config_template", 60 help="Path to the instrumentation test config template.") 61 parser.add_argument("--extra-configs", default="") 62 args = parser.parse_args(argv) 63 64 target_config = args.target_config 65 android_manifest = args.android_manifest 66 empty_config = args.empty_config 67 instrumentation_test_config_template = args.instrumentation_test_config_template 68 extra_configs = '\n'.join(args.extra_configs.split('\\n')) 69 70 module = os.path.splitext(os.path.basename(target_config))[0] 71 72 # If the AndroidManifest.xml is not available, but the APK is, this tool also 73 # accepts the output of `aapt2 dump xmltree <apk> AndroidManifest.xml` written 74 # into a file. This is a custom structured aapt2 output - not raw XML! 75 if android_manifest.endswith(".xmltree"): 76 label = module 77 with open(android_manifest, encoding="utf-8") as manifest: 78 # e.g. A: package="android.test.example.helloworld" (Raw: "android.test.example.helloworld") 79 # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 80 pattern = re.compile(r"\(Raw:\s\"(.*)\"\)$") 81 curr_element = None 82 for line in manifest: 83 curr_line = line.strip() 84 if curr_line.startswith("E:"): 85 # e.g. "E: instrumentation (line=9)" 86 # ^^^^^^^^^^^^^^^ 87 curr_element = curr_line.split(" ")[1] 88 if curr_element == "instrumentation": 89 if ATTRIBUTE_RUNNER in curr_line: 90 runner = re.findall(pattern, curr_line)[0] 91 if ATTRIBUTE_LABEL in curr_line: 92 label = re.findall(pattern, curr_line)[0] 93 if curr_element == "manifest": 94 if ATTRIBUTE_PACKAGE in curr_line: 95 package = re.findall(pattern, curr_line)[0] 96 97 if not (runner and label and package): 98 # Failed to locate instrumentation or manifest element in AndroidManifest. 99 # file. Empty test config file will be created. 100 shutil.copyfile(empty_config, target_config) 101 return 0 102 103 else: 104 # If the AndroidManifest.xml file is directly available, read it as an XML file. 105 manifest = parse(android_manifest) 106 instrumentation_elements = manifest.getElementsByTagName('instrumentation') 107 manifest_elements = manifest.getElementsByTagName('manifest') 108 if len(instrumentation_elements) != 1 or len(manifest_elements) != 1: 109 # Failed to locate instrumentation or manifest element in AndroidManifest. 110 # file. Empty test config file will be created. 111 shutil.copyfile(empty_config, target_config) 112 return 0 113 114 instrumentation = instrumentation_elements[0] 115 manifest = manifest_elements[0] 116 if ATTRIBUTE_LABEL in instrumentation.attributes: 117 label = instrumentation.attributes[ATTRIBUTE_LABEL].value 118 else: 119 label = module 120 runner = instrumentation.attributes[ATTRIBUTE_RUNNER].value 121 package = manifest.attributes[ATTRIBUTE_PACKAGE].value 122 123 test_type = ('InstrumentationTest' 124 if runner.endswith('.InstrumentationTestRunner') 125 else 'AndroidJUnitTest') 126 127 with open(instrumentation_test_config_template) as template: 128 config = template.read() 129 config = config.replace(PLACEHOLDER_LABEL, label) 130 config = config.replace(PLACEHOLDER_MODULE, module) 131 config = config.replace(PLACEHOLDER_PACKAGE, package) 132 config = config.replace(PLACEHOLDER_TEST_TYPE, test_type) 133 config = config.replace(PLACEHOLDER_EXTRA_CONFIGS, extra_configs) 134 config = config.replace(PLACEHOLDER_RUNNER, runner) 135 with open(target_config, 'w') as config_file: 136 config_file.write(config) 137 return 0 138 139if __name__ == '__main__': 140 sys.exit(main(sys.argv[1:])) 141