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
diff --git a/tools/Android.bp b/tools/Android.bp
index bea0602..b8ab162 100644
--- a/tools/Android.bp
+++ b/tools/Android.bp
@@ -82,3 +82,17 @@
}
}
}
+
+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 2dbb585..9ec0dce 100644
--- a/tools/BUILD.bazel
+++ b/tools/BUILD.bazel
@@ -26,3 +26,10 @@
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 ce64160..0bf47c6 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 @@
"""
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 @@
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 <apk> 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 51a8583..ce97723 100644
--- a/tools/auto_gen_test_config_test.py
+++ b/tools/auto_gen_test_config_test.py
@@ -30,6 +30,24 @@
</manifest>
"""
+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 = """<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.my.tests.x">
@@ -45,12 +63,12 @@
<instrumentation
android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.android.my.tests"
- android:label="My Tests" />
+ android:label="TestModule" />
</manifest>
"""
EXPECTED_JUNIT_TEST_CONFIG = """<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
+<!-- Copyright (C) 2023 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -66,19 +84,23 @@
-->
<!-- This test config file is auto-generated. -->
<configuration description="Runs TestModule.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
<option name="test-file-name" value="TestModule.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="com.android.my.tests.x" />
+ {EXTRA_TEST_RUNNER_CONFIGS}<option name="package" value="com.android.my.tests.x" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
</test>
</configuration>
"""
EXPECTED_INSTRUMENTATION_TEST_CONFIG = """<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
+<!-- Copyright (C) 2023 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -93,23 +115,74 @@
limitations under the License.
-->
<!-- This test config file is auto-generated. -->
-<configuration description="Runs My Tests.">
+<configuration description="Runs TestModule.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
<option name="test-file-name" value="TestModule.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.InstrumentationTest" >
- <option name="package" value="com.android.my.tests.x" />
+ {EXTRA_TEST_RUNNER_CONFIGS}<option name="package" value="com.android.my.tests.x" />
<option name="runner" value="android.test.InstrumentationTestRunner" />
</test>
</configuration>
"""
-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 = """<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!-- No AndroidTest.xml was provided and the manifest does not include
+ instrumentation, hence this apk is not instrumentable.
+-->
+<configuration description="Empty Configuration" />
+"""
+
+INSTRUMENTATION_TEST_CONFIG_TEMPLATE_CONTENT = """<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!-- This test config file is auto-generated. -->
+<configuration description="Runs {LABEL}.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+{EXTRA_CONFIGS}
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="{MODULE}.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.{TEST_TYPE}" >
+ {EXTRA_TEST_RUNNER_CONFIGS}<option name="package" value="{PACKAGE}" />
+ <option name="runner" value="{RUNNER}" />
+ </test>
+</configuration>
+"""
class AutoGenTestConfigUnittests(unittest.TestCase):
@@ -120,6 +193,16 @@
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 @@
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 @@
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 @@
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(