From f3406e64f778559b52cbb47a22a14799680b2b25 Mon Sep 17 00:00:00 2001 From: Jingwen Chen Date: Mon, 18 Sep 2023 07:19:11 +0000 Subject: [PATCH] Make auto_gen_test_config available to Bazel. In the Bazel rule, the actual AndroidManifest.xml file isn't available in the providers of the android_binary dependency. And for good reason, we should also rely on aapt2 to dump the manifest contents from the final packaged APK anyway, but this auto gen script doesn't support reading the output of aapt2 dump. This CL adds a simple reader of the output of `aapt2 dump xmltree`, and enabled if the xmltree content is passed to this tool instead of AndroidManifest.xml. Also fixup auto_gen_test_config_test.py. The test was completely failing. Test: atest auto_gen_test_config_test Test: presubmits Change-Id: I7fecd927d0ed7847b6463d964b3698f4164b0177 --- tools/Android.bp | 14 +++ tools/BUILD.bazel | 7 ++ tools/auto_gen_test_config.py | 78 +++++++++++---- tools/auto_gen_test_config_test.py | 148 +++++++++++++++++++++++++---- 4 files changed, 208 insertions(+), 39 deletions(-) diff --git a/tools/Android.bp b/tools/Android.bp index bea0602f59..b8ab162b76 100644 --- a/tools/Android.bp +++ b/tools/Android.bp @@ -82,3 +82,17 @@ python_binary_host { } } } + +python_test_host { + name: "auto_gen_test_config_test", + main: "auto_gen_test_config_test.py", + srcs: [ + "auto_gen_test_config.py", + "auto_gen_test_config_test.py", + ], + auto_gen_config: true, + test_suites: ["general-tests"], + test_options: { + unit_test: true, + }, +} diff --git a/tools/BUILD.bazel b/tools/BUILD.bazel index 2dbb585a27..9ec0dcef85 100644 --- a/tools/BUILD.bazel +++ b/tools/BUILD.bazel @@ -26,3 +26,10 @@ py_binary( python_version = "PY3", visibility = ["//visibility:public"], ) + +py_binary( + name = "auto_gen_test_config", + srcs = ["auto_gen_test_config.py"], + python_version = "PY3", + visibility = ["//visibility:public"], +) diff --git a/tools/auto_gen_test_config.py b/tools/auto_gen_test_config.py index ce6416000b..0bf47c6ec0 100755 --- a/tools/auto_gen_test_config.py +++ b/tools/auto_gen_test_config.py @@ -17,6 +17,7 @@ """A tool to generate TradeFed test config file. """ +import re import os import shutil import sys @@ -44,9 +45,9 @@ def main(argv): """ if len(argv) != 4 and len(argv) != 6: sys.stderr.write( - 'Invalid arguments. The script requires 4 arguments for file paths: ' - 'target_config android_manifest empty_config ' - 'instrumentation_test_config_template ' + f'Invalid arguments: {argv}. The script requires 4 arguments for file paths: ' + 'target_config, android_manifest (or the xmltree dump), empty_config, ' + 'instrumentation_test_config_template, ' 'and 2 optional arguments for extra configs: ' '--extra-configs \'EXTRA_CONFIGS\'.\n') return 1 @@ -57,27 +58,62 @@ def main(argv): instrumentation_test_config_template = argv[3] extra_configs = '\n'.join(argv[5].split('\\n')) if len(argv) == 6 else '' - manifest = parse(android_manifest) - instrumentation_elements = manifest.getElementsByTagName('instrumentation') - manifest_elements = manifest.getElementsByTagName('manifest') - if len(instrumentation_elements) != 1 or len(manifest_elements) != 1: - # Failed to locate instrumentation or manifest element in AndroidManifest. - # file. Empty test config file will be created. - shutil.copyfile(empty_config, target_config) - return 0 - module = os.path.splitext(os.path.basename(target_config))[0] - instrumentation = instrumentation_elements[0] - manifest = manifest_elements[0] - if ATTRIBUTE_LABEL in instrumentation.attributes: - label = instrumentation.attributes[ATTRIBUTE_LABEL].value - else: + + # If the AndroidManifest.xml is not available, but the APK is, this tool also + # accepts the output of `aapt2 dump xmltree AndroidManifest.xml` written + # into a file. This is a custom structured aapt2 output - not raw XML! + if android_manifest.endswith(".xmltree"): label = module - runner = instrumentation.attributes[ATTRIBUTE_RUNNER].value - package = manifest.attributes[ATTRIBUTE_PACKAGE].value + with open(android_manifest, encoding="utf-8") as manifest: + # e.g. A: package="android.test.example.helloworld" (Raw: "android.test.example.helloworld") + # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + pattern = re.compile(r"\(Raw:\s\"(.*)\"\)$") + curr_element = None + for line in manifest.readlines(): + curr_line = line.strip() + if curr_line.startswith("E:"): + # e.g. "E: instrumentation (line=9)" + # ^^^^^^^^^^^^^^^ + curr_element = curr_line.split(" ")[1] + if curr_element == "instrumentation": + if ATTRIBUTE_RUNNER in curr_line: + runner = re.findall(pattern, curr_line)[0] + if ATTRIBUTE_LABEL in curr_line: + label = re.findall(pattern, curr_line)[0] + if curr_element == "manifest": + if ATTRIBUTE_PACKAGE in curr_line: + package = re.findall(pattern, curr_line)[0] + + if not (runner and label and package): + # Failed to locate instrumentation or manifest element in AndroidManifest. + # file. Empty test config file will be created. + shutil.copyfile(empty_config, target_config) + return 0 + + else: + # If the AndroidManifest.xml file is directly available, read it as an XML file. + manifest = parse(android_manifest) + instrumentation_elements = manifest.getElementsByTagName('instrumentation') + manifest_elements = manifest.getElementsByTagName('manifest') + if len(instrumentation_elements) != 1 or len(manifest_elements) != 1: + # Failed to locate instrumentation or manifest element in AndroidManifest. + # file. Empty test config file will be created. + shutil.copyfile(empty_config, target_config) + return 0 + + instrumentation = instrumentation_elements[0] + manifest = manifest_elements[0] + if ATTRIBUTE_LABEL in instrumentation.attributes: + label = instrumentation.attributes[ATTRIBUTE_LABEL].value + else: + label = module + runner = instrumentation.attributes[ATTRIBUTE_RUNNER].value + package = manifest.attributes[ATTRIBUTE_PACKAGE].value + test_type = ('InstrumentationTest' - if runner.endswith('.InstrumentationTestRunner') - else 'AndroidJUnitTest') + if runner.endswith('.InstrumentationTestRunner') + else 'AndroidJUnitTest') with open(instrumentation_test_config_template) as template: config = template.read() diff --git a/tools/auto_gen_test_config_test.py b/tools/auto_gen_test_config_test.py index 51a8583f2e..ce97723d2c 100644 --- a/tools/auto_gen_test_config_test.py +++ b/tools/auto_gen_test_config_test.py @@ -30,6 +30,24 @@ MANIFEST_INVALID = """ """ +XMLTREE_JUNIT_TEST = """N: android=http://schemas.android.com/apk/res/android (line=2) + E: manifest (line=2) + A: package="com.android.my.tests.x" (Raw: "com.android.my.tests.x") + E: instrumentation (line=9) + A: http://schemas.android.com/apk/res/android:label(0x01010001)="TestModule" (Raw: "TestModule") + A: http://schemas.android.com/apk/res/android:name(0x01010003)="androidx.test.runner.AndroidJUnitRunner" (Raw: "androidx.test.runner.AndroidJUnitRunner") + A: http://schemas.android.com/apk/res/android:targetPackage(0x01010021)="com.android.my.tests" (Raw: "com.android.my.tests") +""" + +XMLTREE_INSTRUMENTATION_TEST = """N: android=http://schemas.android.com/apk/res/android (line=2) + E: manifest (line=2) + A: package="com.android.my.tests.x" (Raw: "com.android.my.tests.x") + E: instrumentation (line=9) + A: http://schemas.android.com/apk/res/android:label(0x01010001)="TestModule" (Raw: "TestModule") + A: http://schemas.android.com/apk/res/android:name(0x01010003)="android.test.InstrumentationTestRunner" (Raw: "android.test.InstrumentationTestRunner") + A: http://schemas.android.com/apk/res/android:targetPackage(0x01010021)="com.android.my.tests" (Raw: "com.android.my.tests") +""" + MANIFEST_JUNIT_TEST = """ @@ -45,12 +63,12 @@ MANIFEST_INSTRUMENTATION_TEST = """ + android:label="TestModule" /> """ EXPECTED_JUNIT_TEST_CONFIG = """ - + """ EXPECTED_INSTRUMENTATION_TEST_CONFIG = """ - - + + """ -TOOLS_DIR = os.path.dirname(os.path.dirname(__file__)) -EMPTY_TEST_CONFIG = os.path.join( - TOOLS_DIR, '..', 'core', 'empty_test_config.xml') -INSTRUMENTATION_TEST_CONFIG_TEMPLATE = os.path.join( - TOOLS_DIR, '..', 'core', 'instrumentation_test_config_template.xml') +EMPTY_TEST_CONFIG_CONTENT = """ + + + +""" + +INSTRUMENTATION_TEST_CONFIG_TEMPLATE_CONTENT = """ + + + + +""" class AutoGenTestConfigUnittests(unittest.TestCase): @@ -120,6 +193,16 @@ class AutoGenTestConfigUnittests(unittest.TestCase): self.test_dir = tempfile.mkdtemp() self.config_file = os.path.join(self.test_dir, TEST_MODULE + '.config') self.manifest_file = os.path.join(self.test_dir, 'AndroidManifest.xml') + self.xmltree_file = os.path.join(self.test_dir, TEST_MODULE + '.xmltree') + self.empty_test_config_file = os.path.join(self.test_dir, 'empty.config') + self.instrumentation_test_config_template_file = os.path.join( + self.test_dir, 'instrumentation.config') + + with open(self.empty_test_config_file, 'w') as f: + f.write(EMPTY_TEST_CONFIG_CONTENT) + + with open(self.instrumentation_test_config_template_file, 'w') as f: + f.write(INSTRUMENTATION_TEST_CONFIG_TEMPLATE_CONTENT) def tearDown(self): """Cleanup the test directory.""" @@ -133,11 +216,11 @@ class AutoGenTestConfigUnittests(unittest.TestCase): argv = [self.config_file, self.manifest_file, - EMPTY_TEST_CONFIG, - INSTRUMENTATION_TEST_CONFIG_TEMPLATE] + self.empty_test_config_file, + self.instrumentation_test_config_template_file] auto_gen_test_config.main(argv) with open(self.config_file) as config_file: - with open(EMPTY_TEST_CONFIG) as empty_config: + with open(self.empty_test_config_file) as empty_config: self.assertEqual(config_file.read(), empty_config.read()) def testCreateJUnitTestConfig(self): @@ -148,8 +231,8 @@ class AutoGenTestConfigUnittests(unittest.TestCase): argv = [self.config_file, self.manifest_file, - EMPTY_TEST_CONFIG, - INSTRUMENTATION_TEST_CONFIG_TEMPLATE] + self.empty_test_config_file, + self.instrumentation_test_config_template_file] auto_gen_test_config.main(argv) with open(self.config_file) as config_file: self.assertEqual(config_file.read(), EXPECTED_JUNIT_TEST_CONFIG) @@ -162,8 +245,37 @@ class AutoGenTestConfigUnittests(unittest.TestCase): argv = [self.config_file, self.manifest_file, - EMPTY_TEST_CONFIG, - INSTRUMENTATION_TEST_CONFIG_TEMPLATE] + self.empty_test_config_file, + self.instrumentation_test_config_template_file] + auto_gen_test_config.main(argv) + with open(self.config_file) as config_file: + self.assertEqual( + config_file.read(), EXPECTED_INSTRUMENTATION_TEST_CONFIG) + + def testCreateJUnitTestConfigWithXMLTree(self): + """Test creating test config for AndroidJUnitTest. + """ + with open(self.xmltree_file, 'w') as f: + f.write(XMLTREE_JUNIT_TEST) + + argv = [self.config_file, + self.xmltree_file, + self.empty_test_config_file, + self.instrumentation_test_config_template_file] + auto_gen_test_config.main(argv) + with open(self.config_file) as config_file: + self.assertEqual(config_file.read(), EXPECTED_JUNIT_TEST_CONFIG) + + def testCreateInstrumentationTestConfigWithXMLTree(self): + """Test creating test config for InstrumentationTest. + """ + with open(self.xmltree_file, 'w') as f: + f.write(XMLTREE_INSTRUMENTATION_TEST) + + argv = [self.config_file, + self.xmltree_file, + self.empty_test_config_file, + self.instrumentation_test_config_template_file] auto_gen_test_config.main(argv) with open(self.config_file) as config_file: self.assertEqual(