Add InstallForceOS, fix testcases for host

Robolectric tests compile against device modules but are installed
and run as host modules.  Allow a module to override its install
OS.

Test: TestPathForModuleInstall
Change-Id: Icf37bb3d4cc1222a9b079602c6a5fdb8b51c86ed
diff --git a/android/module.go b/android/module.go
index 96c2e1e..2a2f5ad 100644
--- a/android/module.go
+++ b/android/module.go
@@ -172,6 +172,7 @@
 	InstallInRecovery() bool
 	InstallInRoot() bool
 	InstallBypassMake() bool
+	InstallForceOS() *OsType
 
 	RequiredModuleNames() []string
 	HostRequiredModuleNames() []string
@@ -213,6 +214,7 @@
 	InstallInRecovery() bool
 	InstallInRoot() bool
 	InstallBypassMake() bool
+	InstallForceOS() *OsType
 	SkipInstall()
 	ExportedToMake() bool
 	NoticeFile() OptionalPath
@@ -893,6 +895,10 @@
 	return false
 }
 
+func (m *ModuleBase) InstallForceOS() *OsType {
+	return nil
+}
+
 func (m *ModuleBase) Owner() string {
 	return String(m.commonProperties.Owner)
 }
@@ -1683,6 +1689,10 @@
 	return m.module.InstallBypassMake()
 }
 
+func (m *moduleContext) InstallForceOS() *OsType {
+	return m.module.InstallForceOS()
+}
+
 func (m *moduleContext) skipInstall(fullInstallPath InstallPath) bool {
 	if m.module.base().commonProperties.SkipInstall {
 		return true
diff --git a/android/paths.go b/android/paths.go
index 66725c6..f8562f6 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -53,6 +53,7 @@
 	InstallInRecovery() bool
 	InstallInRoot() bool
 	InstallBypassMake() bool
+	InstallForceOS() *OsType
 }
 
 var _ ModuleInstallPathContext = ModuleContext(nil)
@@ -1206,18 +1207,22 @@
 // module appended with paths...
 func PathForModuleInstall(ctx ModuleInstallPathContext, pathComponents ...string) InstallPath {
 	var outPaths []string
-	if ctx.Device() {
-		partition := modulePartition(ctx)
+	os := ctx.Os()
+	if forceOS := ctx.InstallForceOS(); forceOS != nil {
+		os = *forceOS
+	}
+	partition := modulePartition(ctx, os)
+	if os.Class == Device {
 		outPaths = []string{"target", "product", ctx.Config().DeviceName(), partition}
 	} else {
-		switch ctx.Os() {
+		switch os {
 		case Linux:
-			outPaths = []string{"host", "linux-x86"}
+			outPaths = []string{"host", "linux-x86", partition}
 		case LinuxBionic:
 			// TODO: should this be a separate top level, or shared with linux-x86?
-			outPaths = []string{"host", "linux_bionic-x86"}
+			outPaths = []string{"host", "linux_bionic-x86", partition}
 		default:
-			outPaths = []string{"host", ctx.Os().String() + "-x86"}
+			outPaths = []string{"host", os.String() + "-x86", partition}
 		}
 	}
 	if ctx.Debug() {
@@ -1253,43 +1258,46 @@
 	return "/" + rel
 }
 
-func modulePartition(ctx ModuleInstallPathContext) string {
+func modulePartition(ctx ModuleInstallPathContext, os OsType) string {
 	var partition string
-	if ctx.InstallInData() {
-		partition = "data"
-	} else if ctx.InstallInTestcases() {
+	if ctx.InstallInTestcases() {
+		// "testcases" install directory can be used for host or device modules.
 		partition = "testcases"
-	} else if ctx.InstallInRamdisk() {
-		if ctx.DeviceConfig().BoardUsesRecoveryAsBoot() {
-			partition = "recovery/root/first_stage_ramdisk"
+	} else if os.Class == Device {
+		if ctx.InstallInData() {
+			partition = "data"
+		} else if ctx.InstallInRamdisk() {
+			if ctx.DeviceConfig().BoardUsesRecoveryAsBoot() {
+				partition = "recovery/root/first_stage_ramdisk"
+			} else {
+				partition = "ramdisk"
+			}
+			if !ctx.InstallInRoot() {
+				partition += "/system"
+			}
+		} else if ctx.InstallInRecovery() {
+			if ctx.InstallInRoot() {
+				partition = "recovery/root"
+			} else {
+				// the layout of recovery partion is the same as that of system partition
+				partition = "recovery/root/system"
+			}
+		} else if ctx.SocSpecific() {
+			partition = ctx.DeviceConfig().VendorPath()
+		} else if ctx.DeviceSpecific() {
+			partition = ctx.DeviceConfig().OdmPath()
+		} else if ctx.ProductSpecific() {
+			partition = ctx.DeviceConfig().ProductPath()
+		} else if ctx.SystemExtSpecific() {
+			partition = ctx.DeviceConfig().SystemExtPath()
+		} else if ctx.InstallInRoot() {
+			partition = "root"
 		} else {
-			partition = "ramdisk"
+			partition = "system"
 		}
-		if !ctx.InstallInRoot() {
-			partition += "/system"
+		if ctx.InstallInSanitizerDir() {
+			partition = "data/asan/" + partition
 		}
-	} else if ctx.InstallInRecovery() {
-		if ctx.InstallInRoot() {
-			partition = "recovery/root"
-		} else {
-			// the layout of recovery partion is the same as that of system partition
-			partition = "recovery/root/system"
-		}
-	} else if ctx.SocSpecific() {
-		partition = ctx.DeviceConfig().VendorPath()
-	} else if ctx.DeviceSpecific() {
-		partition = ctx.DeviceConfig().OdmPath()
-	} else if ctx.ProductSpecific() {
-		partition = ctx.DeviceConfig().ProductPath()
-	} else if ctx.SystemExtSpecific() {
-		partition = ctx.DeviceConfig().SystemExtPath()
-	} else if ctx.InstallInRoot() {
-		partition = "root"
-	} else {
-		partition = "system"
-	}
-	if ctx.InstallInSanitizerDir() {
-		partition = "data/asan/" + partition
 	}
 	return partition
 }
diff --git a/android/paths_test.go b/android/paths_test.go
index 7a32026..f1908ac 100644
--- a/android/paths_test.go
+++ b/android/paths_test.go
@@ -205,6 +205,7 @@
 	inRamdisk      bool
 	inRecovery     bool
 	inRoot         bool
+	forceOS        *OsType
 }
 
 func (m moduleInstallPathContextImpl) Config() Config {
@@ -241,6 +242,10 @@
 	return false
 }
 
+func (m moduleInstallPathContextImpl) InstallForceOS() *OsType {
+	return m.forceOS
+}
+
 func pathTestConfig(buildDir string) Config {
 	return TestConfig(buildDir, nil, "", nil)
 }
@@ -598,6 +603,40 @@
 			},
 			in:  []string{"nativetest", "my_test"},
 			out: "target/product/test_device/data/asan/data/nativetest/my_test",
+		}, {
+			name: "device testcases",
+			ctx: &moduleInstallPathContextImpl{
+				baseModuleContext: baseModuleContext{
+					os:     deviceTarget.Os,
+					target: deviceTarget,
+				},
+				inTestcases: true,
+			},
+			in:  []string{"my_test", "my_test_bin"},
+			out: "target/product/test_device/testcases/my_test/my_test_bin",
+		}, {
+			name: "host testcases",
+			ctx: &moduleInstallPathContextImpl{
+				baseModuleContext: baseModuleContext{
+					os:     hostTarget.Os,
+					target: hostTarget,
+				},
+				inTestcases: true,
+			},
+			in:  []string{"my_test", "my_test_bin"},
+			out: "host/linux-x86/testcases/my_test/my_test_bin",
+		}, {
+			name: "forced host testcases",
+			ctx: &moduleInstallPathContextImpl{
+				baseModuleContext: baseModuleContext{
+					os:     deviceTarget.Os,
+					target: deviceTarget,
+				},
+				inTestcases: true,
+				forceOS:     &Linux,
+			},
+			in:  []string{"my_test", "my_test_bin"},
+			out: "host/linux-x86/testcases/my_test/my_test_bin",
 		},
 	}