Add tests for ctx.InstallFile

Add tests that cover Soong-only installation as well as installation with
InstallBypassMake.

Bug: 204136549
Test: TestInstall
Test: TestInstallBypassMake
Change-Id: Iac22c9fdf99994e06b419623ee5fa399ef6957fb
diff --git a/android/module_test.go b/android/module_test.go
index 9e2b0ca..236cae7 100644
--- a/android/module_test.go
+++ b/android/module_test.go
@@ -15,7 +15,12 @@
 package android
 
 import (
+	"bytes"
+	"path/filepath"
+	"runtime"
 	"testing"
+
+	mkparser "android/soong/androidmk/parser"
 )
 
 func TestSrcIsModule(t *testing.T) {
@@ -199,17 +204,28 @@
 	}
 }
 
+func (m *depsModule) InstallBypassMake() bool {
+	return true
+}
+
 func (m *depsModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	outputFile := PathForModuleOut(ctx, ctx.ModuleName())
+	ctx.Build(pctx, BuildParams{
+		Rule:   Touch,
+		Output: outputFile,
+	})
+	installFile := ctx.InstallFile(PathForModuleInstall(ctx), ctx.ModuleName(), outputFile)
+	ctx.InstallSymlink(PathForModuleInstall(ctx, "symlinks"), ctx.ModuleName(), installFile)
 }
 
 func (m *depsModule) DepsMutator(ctx BottomUpMutatorContext) {
-	ctx.AddDependency(ctx.Module(), nil, m.props.Deps...)
+	ctx.AddDependency(ctx.Module(), installDepTag{}, m.props.Deps...)
 }
 
 func depsModuleFactory() Module {
 	m := &depsModule{}
 	m.AddProperties(&m.props)
-	InitAndroidModule(m)
+	InitAndroidArchModule(m, HostAndDeviceDefault, MultilibCommon)
 	return m
 }
 
@@ -320,3 +336,282 @@
 		ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern(expectedErrs)).
 		RunTestWithBp(t, bp)
 }
+
+func TestInstall(t *testing.T) {
+	if runtime.GOOS != "linux" {
+		t.Skip("requires linux")
+	}
+	bp := `
+		deps {
+			name: "foo",
+			deps: ["bar"],
+		}
+
+		deps {
+			name: "bar",
+			deps: ["baz", "qux"],
+		}
+
+		deps {
+			name: "baz",
+			deps: ["qux"],
+		}
+
+		deps {
+			name: "qux",
+		}
+	`
+
+	result := GroupFixturePreparers(
+		prepareForModuleTests,
+		PrepareForTestWithArchMutator,
+	).RunTestWithBp(t, bp)
+
+	module := func(name string, host bool) TestingModule {
+		variant := "android_common"
+		if host {
+			variant = result.Config.BuildOSCommonTarget.String()
+		}
+		return result.ModuleForTests(name, variant)
+	}
+
+	outputRule := func(name string) TestingBuildParams { return module(name, false).Output(name) }
+
+	installRule := func(name string) TestingBuildParams {
+		return module(name, false).Output(filepath.Join("out/soong/target/product/test_device/system", name))
+	}
+
+	symlinkRule := func(name string) TestingBuildParams {
+		return module(name, false).Output(filepath.Join("out/soong/target/product/test_device/system/symlinks", name))
+	}
+
+	hostOutputRule := func(name string) TestingBuildParams { return module(name, true).Output(name) }
+
+	hostInstallRule := func(name string) TestingBuildParams {
+		return module(name, true).Output(filepath.Join("out/soong/host/linux-x86", name))
+	}
+
+	hostSymlinkRule := func(name string) TestingBuildParams {
+		return module(name, true).Output(filepath.Join("out/soong/host/linux-x86/symlinks", name))
+	}
+
+	assertInputs := func(params TestingBuildParams, inputs ...Path) {
+		t.Helper()
+		AssertArrayString(t, "expected inputs", Paths(inputs).Strings(),
+			append(PathsIfNonNil(params.Input), params.Inputs...).Strings())
+	}
+
+	assertImplicits := func(params TestingBuildParams, implicits ...Path) {
+		t.Helper()
+		AssertArrayString(t, "expected implicit dependencies", Paths(implicits).Strings(),
+			append(PathsIfNonNil(params.Implicit), params.Implicits...).Strings())
+	}
+
+	assertOrderOnlys := func(params TestingBuildParams, orderonlys ...Path) {
+		t.Helper()
+		AssertArrayString(t, "expected orderonly dependencies", Paths(orderonlys).Strings(),
+			params.OrderOnly.Strings())
+	}
+
+	// Check host install rule dependencies
+	assertInputs(hostInstallRule("foo"), hostOutputRule("foo").Output)
+	assertImplicits(hostInstallRule("foo"),
+		hostInstallRule("bar").Output,
+		hostSymlinkRule("bar").Output,
+		hostInstallRule("baz").Output,
+		hostSymlinkRule("baz").Output,
+		hostInstallRule("qux").Output,
+		hostSymlinkRule("qux").Output,
+	)
+	assertOrderOnlys(hostInstallRule("foo"))
+
+	// Check host symlink rule dependencies
+	assertInputs(hostSymlinkRule("foo"), hostInstallRule("foo").Output)
+	assertImplicits(hostSymlinkRule("foo"))
+	assertOrderOnlys(hostSymlinkRule("foo"))
+
+	// Check device install rule dependencies
+	assertInputs(installRule("foo"), outputRule("foo").Output)
+	assertImplicits(installRule("foo"))
+	assertOrderOnlys(installRule("foo"),
+		installRule("bar").Output,
+		symlinkRule("bar").Output,
+		installRule("baz").Output,
+		symlinkRule("baz").Output,
+		installRule("qux").Output,
+		symlinkRule("qux").Output,
+	)
+
+	// Check device symlink rule dependencies
+	assertInputs(symlinkRule("foo"), installRule("foo").Output)
+	assertImplicits(symlinkRule("foo"))
+	assertOrderOnlys(symlinkRule("foo"))
+}
+
+func TestInstallBypassMake(t *testing.T) {
+	if runtime.GOOS != "linux" {
+		t.Skip("requires linux")
+	}
+	bp := `
+		deps {
+			name: "foo",
+			deps: ["bar"],
+		}
+
+		deps {
+			name: "bar",
+			deps: ["baz", "qux"],
+		}
+
+		deps {
+			name: "baz",
+			deps: ["qux"],
+		}
+
+		deps {
+			name: "qux",
+		}
+	`
+
+	result := GroupFixturePreparers(
+		prepareForModuleTests,
+		PrepareForTestWithArchMutator,
+		FixtureModifyConfig(SetKatiEnabledForTests),
+		FixtureRegisterWithContext(func(ctx RegistrationContext) {
+			ctx.RegisterSingletonType("makevars", makeVarsSingletonFunc)
+		}),
+	).RunTestWithBp(t, bp)
+
+	installs := result.SingletonForTests("makevars").Singleton().(*makeVarsSingleton).installsForTesting
+	buf := bytes.NewBuffer(append([]byte(nil), installs...))
+	parser := mkparser.NewParser("makevars", buf)
+
+	nodes, errs := parser.Parse()
+	if len(errs) > 0 {
+		t.Fatalf("error parsing install rules: %s", errs[0])
+	}
+
+	rules := parseMkRules(t, result.Config, nodes)
+
+	module := func(name string, host bool) TestingModule {
+		variant := "android_common"
+		if host {
+			variant = result.Config.BuildOSCommonTarget.String()
+		}
+		return result.ModuleForTests(name, variant)
+	}
+
+	outputRule := func(name string) TestingBuildParams { return module(name, false).Output(name) }
+
+	ruleForOutput := func(output string) installMakeRule {
+		for _, rule := range rules {
+			if rule.target == output {
+				return rule
+			}
+		}
+		t.Fatalf("no make install rule for %s", output)
+		return installMakeRule{}
+	}
+
+	installRule := func(name string) installMakeRule {
+		return ruleForOutput(filepath.Join("out/target/product/test_device/system", name))
+	}
+
+	symlinkRule := func(name string) installMakeRule {
+		return ruleForOutput(filepath.Join("out/target/product/test_device/system/symlinks", name))
+	}
+
+	hostOutputRule := func(name string) TestingBuildParams { return module(name, true).Output(name) }
+
+	hostInstallRule := func(name string) installMakeRule {
+		return ruleForOutput(filepath.Join("out/host/linux-x86", name))
+	}
+
+	hostSymlinkRule := func(name string) installMakeRule {
+		return ruleForOutput(filepath.Join("out/host/linux-x86/symlinks", name))
+	}
+
+	assertDeps := func(rule installMakeRule, deps ...string) {
+		t.Helper()
+		AssertArrayString(t, "expected inputs", deps, rule.deps)
+	}
+
+	assertOrderOnlys := func(rule installMakeRule, orderonlys ...string) {
+		t.Helper()
+		AssertArrayString(t, "expected orderonly dependencies", orderonlys, rule.orderOnlyDeps)
+	}
+
+	// Check host install rule dependencies
+	assertDeps(hostInstallRule("foo"),
+		hostOutputRule("foo").Output.String(),
+		hostInstallRule("bar").target,
+		hostSymlinkRule("bar").target,
+		hostInstallRule("baz").target,
+		hostSymlinkRule("baz").target,
+		hostInstallRule("qux").target,
+		hostSymlinkRule("qux").target,
+	)
+	assertOrderOnlys(hostInstallRule("foo"))
+
+	// Check host symlink rule dependencies
+	assertDeps(hostSymlinkRule("foo"))
+	assertOrderOnlys(hostSymlinkRule("foo"), hostInstallRule("foo").target)
+
+	// Check device install rule dependencies
+	assertDeps(installRule("foo"), outputRule("foo").Output.String())
+	assertOrderOnlys(installRule("foo"),
+		installRule("bar").target,
+		symlinkRule("bar").target,
+		installRule("baz").target,
+		symlinkRule("baz").target,
+		installRule("qux").target,
+		symlinkRule("qux").target,
+	)
+
+	// Check device symlink rule dependencies
+	assertDeps(symlinkRule("foo"))
+	assertOrderOnlys(symlinkRule("foo"), installRule("foo").target)
+}
+
+type installMakeRule struct {
+	target        string
+	deps          []string
+	orderOnlyDeps []string
+}
+
+func parseMkRules(t *testing.T, config Config, nodes []mkparser.Node) []installMakeRule {
+	var rules []installMakeRule
+	for _, node := range nodes {
+		if mkParserRule, ok := node.(*mkparser.Rule); ok {
+			var rule installMakeRule
+
+			if targets := mkParserRule.Target.Words(); len(targets) == 0 {
+				t.Fatalf("no targets for rule %s", mkParserRule.Dump())
+			} else if len(targets) > 1 {
+				t.Fatalf("unsupported multiple targets for rule %s", mkParserRule.Dump())
+			} else if !targets[0].Const() {
+				t.Fatalf("unsupported non-const target for rule %s", mkParserRule.Dump())
+			} else {
+				rule.target = normalizeStringRelativeToTop(config, targets[0].Value(nil))
+			}
+
+			prereqList := &rule.deps
+			for _, prereq := range mkParserRule.Prerequisites.Words() {
+				if !prereq.Const() {
+					t.Fatalf("unsupported non-const prerequisite for rule %s", mkParserRule.Dump())
+				}
+
+				if prereq.Value(nil) == "|" {
+					prereqList = &rule.orderOnlyDeps
+					continue
+				}
+
+				*prereqList = append(*prereqList, normalizeStringRelativeToTop(config, prereq.Value(nil)))
+			}
+
+			rules = append(rules, rule)
+		}
+	}
+
+	return rules
+}