Add support for android_test modules

android_test module are APKs that can be run as tests, either
as standalone unit tests or as instrumentation tests for another
APK.

Test: m checkbuild
Change-Id: I16661701637e4048fd99442029c3e195ebf373a4
diff --git a/androidmk/cmd/androidmk/androidmk_test.go b/androidmk/cmd/androidmk/androidmk_test.go
index a54a4d2..ed5ae02 100644
--- a/androidmk/cmd/androidmk/androidmk_test.go
+++ b/androidmk/cmd/androidmk/androidmk_test.go
@@ -378,6 +378,33 @@
 }
 `,
 	},
+	{
+		desc: "Convert LOCAL_MODULE_TAGS tests to java_test",
+		in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+include $(BUILD_JAVA_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+include $(BUILD_PACKAGE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+include $(BUILD_HOST_JAVA_LIBRARY)
+`,
+
+		expected: `
+java_test {
+}
+
+android_test {
+}
+
+java_test_host {
+}
+`,
+	},
 
 	{
 		desc: "Input containing escaped quotes",
diff --git a/bpfix/bpfix/bpfix.go b/bpfix/bpfix/bpfix.go
index 1cd0409..05be3bd 100644
--- a/bpfix/bpfix/bpfix.go
+++ b/bpfix/bpfix/bpfix.go
@@ -67,6 +67,10 @@
 		fix:  rewriteIncorrectAndroidmkAndroidLibraries,
 	},
 	{
+		name: "rewriteTestModuleTypes",
+		fix:  rewriteTestModuleTypes,
+	},
+	{
 		name: "mergeMatchingModuleProperties",
 		fix:  runPatchListMod(mergeMatchingModuleProperties),
 	},
@@ -256,6 +260,46 @@
 	return nil
 }
 
+// rewriteTestModuleTypes looks for modules that are identifiable as tests but for which Make doesn't have a separate
+// module class, and moves them to the appropriate Soong module type.
+func rewriteTestModuleTypes(f *Fixer) error {
+	for _, def := range f.tree.Defs {
+		mod, ok := def.(*parser.Module)
+		if !ok {
+			continue
+		}
+
+		if !strings.HasPrefix(mod.Type, "java_") && !strings.HasPrefix(mod.Type, "android_") {
+			continue
+		}
+
+		hasInstrumentationFor := hasNonEmptyLiteralStringProperty(mod, "instrumentation_for")
+		tags, _ := getLiteralListPropertyValue(mod, "tags")
+
+		var hasTestsTag bool
+		for _, tag := range tags {
+			if tag == "tests" {
+				hasTestsTag = true
+			}
+		}
+
+		isTest := hasInstrumentationFor || hasTestsTag
+
+		if isTest {
+			switch mod.Type {
+			case "android_app":
+				mod.Type = "android_test"
+			case "java_library":
+				mod.Type = "java_test"
+			case "java_library_host":
+				mod.Type = "java_test_host"
+			}
+		}
+	}
+
+	return nil
+}
+
 func runPatchListMod(modFunc func(mod *parser.Module, buf []byte, patchlist *parser.PatchList) error) func(*Fixer) error {
 	return func(f *Fixer) error {
 		// Make sure all the offsets are accurate
@@ -383,26 +427,34 @@
 				// force installation for -eng builds.
 				`
 		case "tests":
-			if strings.Contains(mod.Type, "cc_test") || strings.Contains(mod.Type, "cc_library_static") {
+			switch {
+			case strings.Contains(mod.Type, "cc_test"),
+				strings.Contains(mod.Type, "cc_library_static"),
+				strings.Contains(mod.Type, "java_test"),
+				mod.Type == "android_test":
 				continue
-			} else if strings.Contains(mod.Type, "cc_lib") {
+			case strings.Contains(mod.Type, "cc_lib"):
 				replaceStr += `// WARNING: Module tags are not supported in Soong.
 					// To make a shared library only for tests, use the "cc_test_library" module
 					// type. If you don't use gtest, set "gtest: false".
 					`
-			} else if strings.Contains(mod.Type, "cc_bin") {
+			case strings.Contains(mod.Type, "cc_bin"):
 				replaceStr += `// WARNING: Module tags are not supported in Soong.
 					// For native test binaries, use the "cc_test" module type. Some differences:
 					//  - If you don't use gtest, set "gtest: false"
 					//  - Binaries will be installed into /data/nativetest[64]/<name>/<name>
 					//  - Both 32 & 64 bit versions will be built (as appropriate)
 					`
-			} else if strings.Contains(mod.Type, "java_lib") {
+			case strings.Contains(mod.Type, "java_lib"):
 				replaceStr += `// WARNING: Module tags are not supported in Soong.
 					// For JUnit or similar tests, use the "java_test" module type. A dependency on
 					// Junit will be added by default, if it is using some other runner, set "junit: false".
 					`
-			} else {
+			case mod.Type == "android_app":
+				replaceStr += `// WARNING: Module tags are not supported in Soong.
+					// For JUnit or instrumentataion app tests, use the "android_test" module type.
+					`
+			default:
 				replaceStr += `// WARNING: Module tags are not supported in Soong.
 					// In most cases, tests are now identified by their module type:
 					// cc_test, java_test, python_test
@@ -552,6 +604,11 @@
 	return found && len(list.Values) > 0
 }
 
+func hasNonEmptyLiteralStringProperty(mod *parser.Module, name string) bool {
+	s, found := getLiteralStringPropertyValue(mod, name)
+	return found && len(s) > 0
+}
+
 func getLiteralListProperty(mod *parser.Module, name string) (list *parser.List, found bool) {
 	prop, ok := mod.GetProperty(name)
 	if !ok {
@@ -561,6 +618,40 @@
 	return list, ok
 }
 
+func getLiteralListPropertyValue(mod *parser.Module, name string) (list []string, found bool) {
+	listValue, ok := getLiteralListProperty(mod, name)
+	if !ok {
+		return nil, false
+	}
+	for _, v := range listValue.Values {
+		stringValue, ok := v.(*parser.String)
+		if !ok {
+			return nil, false
+		}
+		list = append(list, stringValue.Value)
+	}
+
+	return list, true
+}
+
+func getLiteralStringProperty(mod *parser.Module, name string) (s *parser.String, found bool) {
+	prop, ok := mod.GetProperty(name)
+	if !ok {
+		return nil, false
+	}
+	s, ok = prop.Value.(*parser.String)
+	return s, ok
+}
+
+func getLiteralStringPropertyValue(mod *parser.Module, name string) (s string, found bool) {
+	stringValue, ok := getLiteralStringProperty(mod, name)
+	if !ok {
+		return "", false
+	}
+
+	return stringValue.Value, true
+}
+
 func propertyIndex(props []*parser.Property, propertyName string) int {
 	for i, prop := range props {
 		if prop.Name == propertyName {
diff --git a/java/androidmk.go b/java/androidmk.go
index 88dc6ef..bc9544a 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -228,6 +228,19 @@
 	}
 }
 
+func (a *AndroidTest) AndroidMk() android.AndroidMkData {
+	data := a.AndroidApp.AndroidMk()
+	data.Extra = append(data.Extra, func(w io.Writer, outputFile android.Path) {
+		fmt.Fprintln(w, "LOCAL_MODULE_TAGS := tests")
+		if len(a.testProperties.Test_suites) > 0 {
+			fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE :=",
+				strings.Join(a.testProperties.Test_suites, " "))
+		}
+	})
+
+	return data
+}
+
 func (a *AndroidLibrary) AndroidMk() android.AndroidMkData {
 	data := a.Library.AndroidMk()
 
diff --git a/java/app.go b/java/app.go
index ae0592a..37109b5 100644
--- a/java/app.go
+++ b/java/app.go
@@ -26,6 +26,7 @@
 
 func init() {
 	android.RegisterModuleType("android_app", AndroidAppFactory)
+	android.RegisterModuleType("android_test", AndroidTestFactory)
 }
 
 // AndroidManifest.xml merging
@@ -50,8 +51,6 @@
 
 	// list of resource labels to generate individual resource packages
 	Package_splits []string
-
-	Instrumentation_for *string
 }
 
 type AndroidApp struct {
@@ -61,6 +60,8 @@
 	certificate certificate
 
 	appProperties appProperties
+
+	extraLinkFlags []string
 }
 
 func (a *AndroidApp) ExportedProguardFlagFiles() android.Paths {
@@ -85,14 +86,11 @@
 }
 
 func (a *AndroidApp) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	var linkFlags []string
-	if String(a.appProperties.Instrumentation_for) != "" {
-		linkFlags = append(linkFlags,
-			"--rename-instrumentation-target-package",
-			String(a.appProperties.Instrumentation_for))
-	} else {
-		a.properties.Instrument = true
-	}
+	a.generateAndroidBuildActions(ctx)
+}
+
+func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) {
+	linkFlags := append([]string(nil), a.extraLinkFlags...)
 
 	hasProduct := false
 	for _, f := range a.aaptProperties.Aaptflags {
@@ -188,6 +186,8 @@
 	module.Module.deviceProperties.Optimize.Enabled = proptools.BoolPtr(true)
 	module.Module.deviceProperties.Optimize.Shrink = proptools.BoolPtr(true)
 
+	module.Module.properties.Instrument = true
+
 	module.AddProperties(
 		&module.Module.properties,
 		&module.Module.deviceProperties,
@@ -198,3 +198,43 @@
 	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	return module
 }
+
+type appTestProperties struct {
+	Instrumentation_for *string
+}
+
+type AndroidTest struct {
+	AndroidApp
+
+	appTestProperties appTestProperties
+
+	testProperties testProperties
+}
+
+func (a *AndroidTest) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	if String(a.appTestProperties.Instrumentation_for) != "" {
+		a.AndroidApp.extraLinkFlags = append(a.AndroidApp.extraLinkFlags,
+			"--rename-instrumentation-target-package",
+			String(a.appTestProperties.Instrumentation_for))
+	}
+
+	a.generateAndroidBuildActions(ctx)
+}
+
+func AndroidTestFactory() android.Module {
+	module := &AndroidTest{}
+
+	module.Module.deviceProperties.Optimize.Enabled = proptools.BoolPtr(true)
+
+	module.AddProperties(
+		&module.Module.properties,
+		&module.Module.deviceProperties,
+		&module.Module.protoProperties,
+		&module.aaptProperties,
+		&module.appProperties,
+		&module.appTestProperties)
+
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+
+	return module
+}
diff --git a/java/java.go b/java/java.go
index a4cb65b..02fdc00 100644
--- a/java/java.go
+++ b/java/java.go
@@ -212,8 +212,8 @@
 	}
 
 	Optimize struct {
-		// If false, disable all optimization.  Defaults to true for apps, false for
-		// libraries and tests.
+		// If false, disable all optimization.  Defaults to true for android_app and android_test
+		// modules, false for java_library and java_test modules.
 		Enabled *bool
 
 		// If true, optimize for size by removing unused code.  Defaults to true for apps,