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