xref: /aosp_15_r20/build/make/tools/auto_gen_test_config.py (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
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