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(