Auto generate test config files

This change allows one to run atest <module name>. The TradeFed test
config file will be automatically created in testcases directory based
on the settings.

Bug: 69929803, 69932044
Test: m -j NotificationStressTests libminijail_test
add following lines in notificationtests/Android.mk,
before "include $(BUILD_PACKAGE)"
LOCAL_COMPATIBILITY_SUITE := general-test

Change-Id: Ib5e9cd152d957b5d5beb6ed23a899a021a7e06d6
diff --git a/core/autogen_test_config.mk b/core/autogen_test_config.mk
new file mode 100644
index 0000000..9f3a2a6
--- /dev/null
+++ b/core/autogen_test_config.mk
@@ -0,0 +1,60 @@
+#
+# 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.
+#
+
+# This build rule allows TradeFed test config file to be created based on
+# following inputs:
+#   is_native: If the test is a native test.
+#   LOCAL_MANIFEST_FILE: Name of the AndroidManifest file for the test. If it's
+#       not set, default value `AndroidManifest.xml` will be used.
+# Output:
+#   autogen_test_config_file: Path to the test config file generated.
+
+autogen_test_config_file := $(dir $(LOCAL_BUILT_MODULE))$(LOCAL_MODULE).config
+ifeq (true,$(is_native))
+# Auto generating test config file for native test
+$(autogen_test_config_file) : $(NATIVE_TEST_CONFIG_TEMPLATE)
+	@echo "Auto generating test config $(notdir $@)"
+	$(hide) sed 's&{MODULE}&$(PRIVATE_MODULE)&g' $^ > $@
+my_auto_generate_config := true
+else
+# Auto generating test config file for instrumentation test
+ifeq ($(strip $(LOCAL_MANIFEST_FILE)),)
+  LOCAL_MANIFEST_FILE := AndroidManifest.xml
+endif
+ifdef LOCAL_FULL_MANIFEST_FILE
+  my_android_manifest := $(LOCAL_FULL_MANIFEST_FILE)
+else
+  my_android_manifest := $(LOCAL_PATH)/$(LOCAL_MANIFEST_FILE)
+endif
+ifneq (,$(wildcard $(my_android_manifest)))
+$(autogen_test_config_file) : $(my_android_manifest) $(EMPTY_TEST_CONFIG) $(INSTRUMENTATION_TEST_CONFIG_TEMPLATE)
+	@echo "Auto generating test config $(notdir $@)"
+	@rm -f $@
+	$(hide) $(AUTOGEN_TEST_CONFIG_SCRIPT) $@ $^
+my_auto_generate_config := true
+endif # ifeq (,$(wildcard $(my_android_manifest)))
+endif # ifneq (true,$(is_native))
+
+ifeq (true,$(my_auto_generate_config))
+  LOCAL_INTERMEDIATE_TARGETS += $(autogen_test_config_file)
+  $(LOCAL_BUILT_MODULE): $(autogen_test_config_file)
+  ALL_MODULES.$(my_register_name).auto_test_config := true
+else
+  autogen_test_config_file :=
+endif
+
+my_android_manifest :=
+my_auto_generate_config :=
diff --git a/core/base_rules.mk b/core/base_rules.mk
index dc3a78b..9234abe 100644
--- a/core/base_rules.mk
+++ b/core/base_rules.mk
@@ -510,7 +510,6 @@
 endif
 ifdef is_native
   arch_dir := /$($(my_prefix)$(LOCAL_2ND_ARCH_VAR_PREFIX)ARCH)
-  is_native :=
 endif
 
 # The module itself.
@@ -531,13 +530,35 @@
     $(foreach dir, $(call compatibility_suite_dirs,$(suite)), \
       $(s):$(dir)/$(n)))))
 
+test_config := $(wildcard $(LOCAL_PATH)/AndroidTest.xml)
+ifeq (,$(test_config))
+  ifneq (true,$(is_native))
+    is_instrumentation_test := true
+    ifeq (true, $(LOCAL_IS_HOST_MODULE))
+      is_instrumentation_test := false
+    endif
+  endif
+  # CTS modules can be used for test data, so test config files must be
+  # explicitly created using AndroidTest.xml
+  ifeq (,$(filter cts, $(LOCAL_COMPATIBILITY_SUITE)))
+    ifeq (true, $(filter true,$(is_native) $(is_instrumentation_test)))
+      include $(BUILD_SYSTEM)/autogen_test_config.mk
+      test_config := $(autogen_test_config_file)
+      autogen_test_config_file :=
+    endif
+  endif
+endif
 
-ifneq (,$(wildcard $(LOCAL_PATH)/AndroidTest.xml))
+is_instrumentation_test :=
+
+ifneq (,$(test_config))
 $(foreach suite, $(LOCAL_COMPATIBILITY_SUITE), \
   $(eval my_compat_dist_$(suite) += $(foreach dir, $(call compatibility_suite_dirs,$(suite)), \
-    $(LOCAL_PATH)/AndroidTest.xml:$(dir)/$(LOCAL_MODULE).config)))
+    $(test_config):$(dir)/$(LOCAL_MODULE).config)))
 endif
 
+test_config :=
+
 ifneq (,$(wildcard $(LOCAL_PATH)/DynamicConfig.xml))
 $(foreach suite, $(LOCAL_COMPATIBILITY_SUITE), \
   $(eval my_compat_dist_$(suite) += $(foreach dir, $(call compatibility_suite_dirs,$(suite)), \
@@ -552,6 +573,9 @@
 endif
 endif # $(my_prefix)$(LOCAL_MODULE_CLASS)_$(LOCAL_MODULE)_compat_files
 
+arch_dir :=
+is_native :=
+
 ifneq ($(my_test_data_file_pairs),)
 $(foreach pair, $(my_test_data_file_pairs), \
   $(eval parts := $(subst :,$(space),$(pair))) \
diff --git a/core/config.mk b/core/config.mk
index 3b1c157..ff68f56 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -156,8 +156,15 @@
 BUILD_HOST_DALVIK_JAVA_LIBRARY := $(BUILD_SYSTEM)/host_dalvik_java_library.mk
 BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY := $(BUILD_SYSTEM)/host_dalvik_static_java_library.mk
 
-BUILD_HOST_TEST_CONFIG:= $(BUILD_SYSTEM)/host_test_config.mk
-BUILD_TARGET_TEST_CONFIG:= $(BUILD_SYSTEM)/target_test_config.mk
+BUILD_HOST_TEST_CONFIG := $(BUILD_SYSTEM)/host_test_config.mk
+BUILD_TARGET_TEST_CONFIG := $(BUILD_SYSTEM)/target_test_config.mk
+
+INSTRUMENTATION_TEST_CONFIG_TEMPLATE := $(BUILD_SYSTEM)/instrumentation_test_config_template.xml
+NATIVE_TEST_CONFIG_TEMPLATE := $(BUILD_SYSTEM)/native_test_config_template.xml
+EMPTY_TEST_CONFIG := $(BUILD_SYSTEM)/empty_test_config.xml
+
+# Tool to generate TradeFed test config file automatically.
+AUTOGEN_TEST_CONFIG_SCRIPT := build/make/tools/auto_gen_test_config.py
 
 # ###############################################################
 # Parse out any modifier targets.
diff --git a/core/empty_test_config.xml b/core/empty_test_config.xml
new file mode 100644
index 0000000..7c9daff
--- /dev/null
+++ b/core/empty_test_config.xml
@@ -0,0 +1,19 @@
+<?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" />
diff --git a/core/instrumentation_test_config_template.xml b/core/instrumentation_test_config_template.xml
new file mode 100644
index 0000000..a0badab
--- /dev/null
+++ b/core/instrumentation_test_config_template.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+<!-- This test config file is auto-generated. -->
+<configuration description="Runs {LABEL}.">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="test-file-name" value="{MODULE}.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.{TEST_TYPE}" >
+        <option name="package" value="{PACKAGE}" />
+        <option name="runner" value="{RUNNER}" />
+    </test>
+</configuration>
diff --git a/core/native_test_config_template.xml b/core/native_test_config_template.xml
new file mode 100644
index 0000000..a960529
--- /dev/null
+++ b/core/native_test_config_template.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<!-- This test config file is auto-generated. -->
+<configuration description="Runs {MODULE}.">
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="{MODULE}" />
+    </test>
+</configuration>
diff --git a/core/tasks/module-info.mk b/core/tasks/module-info.mk
index f6d688c..b45526f 100644
--- a/core/tasks/module-info.mk
+++ b/core/tasks/module-info.mk
@@ -12,6 +12,7 @@
 			'"tags": [$(foreach w,$(sort $(ALL_MODULES.$(m).TAGS)),"$(w)", )], ' \
 			'"installed": [$(foreach w,$(sort $(ALL_MODULES.$(m).INSTALLED)),"$(w)", )], ' \
 			'"compatibility_suites": [$(foreach w,$(sort $(ALL_MODULES.$(m).COMPATIBILITY_SUITES)),"$(w)", )], ' \
+			'"auto_test_config": [$(ALL_MODULES.$(m).auto_test_config)], ' \
 			'},\n' \
 	 ) | sed -e 's/, *\]/]/g' -e 's/, *\}/ }/g' -e '$$s/,$$//' >> $@
 	$(hide) echo '}' >> $@
diff --git a/tools/auto_gen_test_config.py b/tools/auto_gen_test_config.py
new file mode 100755
index 0000000..fa018f4
--- /dev/null
+++ b/tools/auto_gen_test_config.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python
+#
+# 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.
+
+"""A tool to generate TradeFed test config file.
+"""
+
+import os
+import shutil
+import sys
+from xml.dom.minidom import parse
+
+ATTRIBUTE_LABEL = 'android:label'
+ATTRIBUTE_RUNNER = 'android:name'
+ATTRIBUTE_TARGET_PACKAGE = 'android:targetPackage'
+
+PLACEHOLDER_LABEL = '{LABEL}'
+PLACEHOLDER_MODULE = '{MODULE}'
+PLACEHOLDER_PACKAGE = '{PACKAGE}'
+PLACEHOLDER_RUNNER = '{RUNNER}'
+PLACEHOLDER_TEST_TYPE = '{TEST_TYPE}'
+
+
+def main(argv):
+  """Entry point of auto_gen_test_config.
+
+  Args:
+    argv: A list of arguments.
+  Returns:
+    0 if no error, otherwise 1.
+  """
+  if len(argv) != 4:
+    sys.stderr.write(
+        'Invalid arguements. The script requires 4 arguments for file paths: '
+        'target_config android_manifest empty_config '
+        'instrumentation_test_config_template.\n')
+    return 1
+  target_config = argv[0]
+  android_manifest = argv[1]
+  empty_config = argv[2]
+  instrumentation_test_config_template = argv[3]
+
+  manifest = parse(android_manifest)
+  instrumentation_elements = manifest.getElementsByTagName('instrumentation')
+  if len(instrumentation_elements) != 1:
+    # Failed to locate instrumentation 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]
+  if instrumentation.attributes.has_key(ATTRIBUTE_LABEL):
+    label = instrumentation.attributes[ATTRIBUTE_LABEL].value
+  else:
+    label = module
+  runner = instrumentation.attributes[ATTRIBUTE_RUNNER].value
+  package = instrumentation.attributes[ATTRIBUTE_TARGET_PACKAGE].value
+  test_type = ('AndroidJUnitTest' if runner.endswith('.AndroidJUnitRunner')
+               else 'InstrumentationTest')
+
+  with open(instrumentation_test_config_template) as template:
+    config = template.read()
+    config = config.replace(PLACEHOLDER_LABEL, label)
+    config = config.replace(PLACEHOLDER_MODULE, module)
+    config = config.replace(PLACEHOLDER_PACKAGE, package)
+    config = config.replace(PLACEHOLDER_TEST_TYPE, test_type)
+    config = config.replace(PLACEHOLDER_RUNNER, runner)
+    with open(target_config, 'w') as config_file:
+      config_file.write(config)
+  return 0
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/tools/auto_gen_test_config_test.py b/tools/auto_gen_test_config_test.py
new file mode 100644
index 0000000..a438b73
--- /dev/null
+++ b/tools/auto_gen_test_config_test.py
@@ -0,0 +1,170 @@
+#!/usr/bin/env python
+#
+# Copyright 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.
+
+"""Unittests for auto_gen_test_config."""
+
+import os
+import shutil
+import tempfile
+import unittest
+
+import auto_gen_test_config
+
+TEST_MODULE = 'TestModule'
+
+MANIFEST_INVALID = """<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+</manifest>
+"""
+
+MANIFEST_JUNIT_TEST = """<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.my.tests" />
+</manifest>
+"""
+
+MANIFEST_INSTRUMENTATION_TEST = """<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+    <instrumentation
+        android:name="android.test.InstrumentationTestRunner"
+        android:targetPackage="com.android.my.tests"
+        android:label="My Tests" />
+</manifest>
+"""
+
+EXPECTED_JUNIT_TEST_CONFIG = """<?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.
+-->
+<!-- This test config file is auto-generated. -->
+<configuration description="Runs TestModule.">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <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" />
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
+"""
+
+EXPECTED_INSTRUMENTATION_TEST_CONFIG = """<?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.
+-->
+<!-- This test config file is auto-generated. -->
+<configuration description="Runs My Tests.">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <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" />
+        <option name="runner" value="android.test.InstrumentationTestRunner" />
+    </test>
+</configuration>
+"""
+
+MAKE_ROOT = os.path.dirname(os.path.dirname(__file__))
+EMPTY_TEST_CONFIG = os.path.join(MAKE_ROOT, 'core', 'empty_test_config.xml')
+INSTRUMENTATION_TEST_CONFIG_TEMPLATE = os.path.join(
+    MAKE_ROOT, 'core', 'instrumentation_test_config_template.xml')
+
+
+class AutoGenTestConfigUnittests(unittest.TestCase):
+  """Unittests for auto_gen_test_config."""
+
+  def setUp(self):
+    """Setup directory for test."""
+    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')
+
+  def tearDown(self):
+    """Cleanup the test directory."""
+    shutil.rmtree(self.test_dir, ignore_errors=True)
+
+  def testInvalidManifest(self):
+    """An empty test config should be generated if AndroidManifest is invalid.
+    """
+    with open(self.manifest_file, 'w') as f:
+      f.write(MANIFEST_INVALID)
+
+    argv = [self.config_file,
+            self.manifest_file,
+            EMPTY_TEST_CONFIG,
+            INSTRUMENTATION_TEST_CONFIG_TEMPLATE]
+    auto_gen_test_config.main(argv)
+    with open(self.config_file) as config_file:
+      with open(EMPTY_TEST_CONFIG) as empty_config:
+        self.assertEqual(config_file.read(), empty_config.read())
+
+  def testCreateJUnitTestConfig(self):
+    """Test creating test config for AndroidJUnitTest.
+    """
+    with open(self.manifest_file, 'w') as f:
+      f.write(MANIFEST_JUNIT_TEST)
+
+    argv = [self.config_file,
+            self.manifest_file,
+            EMPTY_TEST_CONFIG,
+            INSTRUMENTATION_TEST_CONFIG_TEMPLATE]
+    auto_gen_test_config.main(argv)
+    with open(self.config_file) as config_file:
+      self.assertEqual(config_file.read(), EXPECTED_JUNIT_TEST_CONFIG)
+
+  def testCreateInstrumentationTestConfig(self):
+    """Test creating test config for AndroidJUnitTest.
+    """
+    with open(self.manifest_file, 'w') as f:
+      f.write(MANIFEST_INSTRUMENTATION_TEST)
+
+    argv = [self.config_file,
+            self.manifest_file,
+            EMPTY_TEST_CONFIG,
+            INSTRUMENTATION_TEST_CONFIG_TEMPLATE]
+    auto_gen_test_config.main(argv)
+    with open(self.config_file) as config_file:
+      self.assertEqual(
+          config_file.read(), EXPECTED_INSTRUMENTATION_TEST_CONFIG)
+
+if __name__ == '__main__':
+  unittest.main()