| // Copyright 2015 Google Inc. All rights reserved. | 
 | // | 
 | // 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. | 
 |  | 
 | package android | 
 |  | 
 | import ( | 
 | 	"path/filepath" | 
 | 	"runtime" | 
 | 	"testing" | 
 |  | 
 | 	"github.com/google/blueprint" | 
 | ) | 
 |  | 
 | func TestSrcIsModule(t *testing.T) { | 
 | 	type args struct { | 
 | 		s string | 
 | 	} | 
 | 	tests := []struct { | 
 | 		name       string | 
 | 		args       args | 
 | 		wantModule string | 
 | 	}{ | 
 | 		{ | 
 | 			name: "file", | 
 | 			args: args{ | 
 | 				s: "foo", | 
 | 			}, | 
 | 			wantModule: "", | 
 | 		}, | 
 | 		{ | 
 | 			name: "module", | 
 | 			args: args{ | 
 | 				s: ":foo", | 
 | 			}, | 
 | 			wantModule: "foo", | 
 | 		}, | 
 | 		{ | 
 | 			name: "tag", | 
 | 			args: args{ | 
 | 				s: ":foo{.bar}", | 
 | 			}, | 
 | 			wantModule: "foo{.bar}", | 
 | 		}, | 
 | 		{ | 
 | 			name: "extra colon", | 
 | 			args: args{ | 
 | 				s: ":foo:bar", | 
 | 			}, | 
 | 			wantModule: "foo:bar", | 
 | 		}, | 
 | 		{ | 
 | 			name: "fully qualified", | 
 | 			args: args{ | 
 | 				s: "//foo:bar", | 
 | 			}, | 
 | 			wantModule: "//foo:bar", | 
 | 		}, | 
 | 		{ | 
 | 			name: "fully qualified with tag", | 
 | 			args: args{ | 
 | 				s: "//foo:bar{.tag}", | 
 | 			}, | 
 | 			wantModule: "//foo:bar{.tag}", | 
 | 		}, | 
 | 		{ | 
 | 			name: "invalid unqualified name", | 
 | 			args: args{ | 
 | 				s: ":foo/bar", | 
 | 			}, | 
 | 			wantModule: "", | 
 | 		}, | 
 | 	} | 
 | 	for _, tt := range tests { | 
 | 		t.Run(tt.name, func(t *testing.T) { | 
 | 			if gotModule := SrcIsModule(tt.args.s); gotModule != tt.wantModule { | 
 | 				t.Errorf("SrcIsModule() = %v, want %v", gotModule, tt.wantModule) | 
 | 			} | 
 | 		}) | 
 | 	} | 
 | } | 
 |  | 
 | func TestSrcIsModuleWithTag(t *testing.T) { | 
 | 	type args struct { | 
 | 		s string | 
 | 	} | 
 | 	tests := []struct { | 
 | 		name       string | 
 | 		args       args | 
 | 		wantModule string | 
 | 		wantTag    string | 
 | 	}{ | 
 | 		{ | 
 | 			name: "file", | 
 | 			args: args{ | 
 | 				s: "foo", | 
 | 			}, | 
 | 			wantModule: "", | 
 | 			wantTag:    "", | 
 | 		}, | 
 | 		{ | 
 | 			name: "module", | 
 | 			args: args{ | 
 | 				s: ":foo", | 
 | 			}, | 
 | 			wantModule: "foo", | 
 | 			wantTag:    "", | 
 | 		}, | 
 | 		{ | 
 | 			name: "tag", | 
 | 			args: args{ | 
 | 				s: ":foo{.bar}", | 
 | 			}, | 
 | 			wantModule: "foo", | 
 | 			wantTag:    ".bar", | 
 | 		}, | 
 | 		{ | 
 | 			name: "empty tag", | 
 | 			args: args{ | 
 | 				s: ":foo{}", | 
 | 			}, | 
 | 			wantModule: "foo", | 
 | 			wantTag:    "", | 
 | 		}, | 
 | 		{ | 
 | 			name: "extra colon", | 
 | 			args: args{ | 
 | 				s: ":foo:bar", | 
 | 			}, | 
 | 			wantModule: "foo:bar", | 
 | 		}, | 
 | 		{ | 
 | 			name: "invalid tag", | 
 | 			args: args{ | 
 | 				s: ":foo{.bar", | 
 | 			}, | 
 | 			wantModule: "foo{.bar", | 
 | 		}, | 
 | 		{ | 
 | 			name: "invalid tag 2", | 
 | 			args: args{ | 
 | 				s: ":foo.bar}", | 
 | 			}, | 
 | 			wantModule: "foo.bar}", | 
 | 		}, | 
 | 		{ | 
 | 			name: "fully qualified", | 
 | 			args: args{ | 
 | 				s: "//foo:bar", | 
 | 			}, | 
 | 			wantModule: "//foo:bar", | 
 | 		}, | 
 | 		{ | 
 | 			name: "fully qualified with tag", | 
 | 			args: args{ | 
 | 				s: "//foo:bar{.tag}", | 
 | 			}, | 
 | 			wantModule: "//foo:bar", | 
 | 			wantTag:    ".tag", | 
 | 		}, | 
 | 		{ | 
 | 			name: "invalid unqualified name", | 
 | 			args: args{ | 
 | 				s: ":foo/bar", | 
 | 			}, | 
 | 			wantModule: "", | 
 | 		}, | 
 | 		{ | 
 | 			name: "invalid unqualified name with tag", | 
 | 			args: args{ | 
 | 				s: ":foo/bar{.tag}", | 
 | 			}, | 
 | 			wantModule: "", | 
 | 		}, | 
 | 	} | 
 | 	for _, tt := range tests { | 
 | 		t.Run(tt.name, func(t *testing.T) { | 
 | 			gotModule, gotTag := SrcIsModuleWithTag(tt.args.s) | 
 | 			if gotModule != tt.wantModule { | 
 | 				t.Errorf("SrcIsModuleWithTag() gotModule = %v, want %v", gotModule, tt.wantModule) | 
 | 			} | 
 | 			if gotTag != tt.wantTag { | 
 | 				t.Errorf("SrcIsModuleWithTag() gotTag = %v, want %v", gotTag, tt.wantTag) | 
 | 			} | 
 | 		}) | 
 | 	} | 
 | } | 
 |  | 
 | type depsModule struct { | 
 | 	ModuleBase | 
 | 	props struct { | 
 | 		Deps []string | 
 | 	} | 
 | } | 
 |  | 
 | 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(), installDepTag{}, m.props.Deps...) | 
 | } | 
 |  | 
 | func depsModuleFactory() Module { | 
 | 	m := &depsModule{} | 
 | 	m.AddProperties(&m.props) | 
 | 	InitAndroidArchModule(m, HostAndDeviceDefault, MultilibCommon) | 
 | 	return m | 
 | } | 
 |  | 
 | var prepareForModuleTests = FixtureRegisterWithContext(func(ctx RegistrationContext) { | 
 | 	ctx.RegisterModuleType("deps", depsModuleFactory) | 
 | }) | 
 |  | 
 | func TestErrorDependsOnDisabledModule(t *testing.T) { | 
 | 	bp := ` | 
 | 		deps { | 
 | 			name: "foo", | 
 | 			deps: ["bar"], | 
 | 		} | 
 | 		deps { | 
 | 			name: "bar", | 
 | 			enabled: false, | 
 | 		} | 
 | 	` | 
 |  | 
 | 	prepareForModuleTests. | 
 | 		ExtendWithErrorHandler(FixtureExpectsAtLeastOneErrorMatchingPattern(`module "foo": depends on disabled module "bar"`)). | 
 | 		RunTestWithBp(t, bp) | 
 | } | 
 |  | 
 | func TestDistErrorChecking(t *testing.T) { | 
 | 	bp := ` | 
 | 		deps { | 
 | 			name: "foo", | 
 |       dist: { | 
 |         dest: "../invalid-dest", | 
 |         dir: "../invalid-dir", | 
 |         suffix: "invalid/suffix", | 
 |       }, | 
 |       dists: [ | 
 |         { | 
 |           dest: "../invalid-dest0", | 
 |           dir: "../invalid-dir0", | 
 |           suffix: "invalid/suffix0", | 
 |         }, | 
 |         { | 
 |           dest: "../invalid-dest1", | 
 |           dir: "../invalid-dir1", | 
 |           suffix: "invalid/suffix1", | 
 |         }, | 
 |       ], | 
 |  		} | 
 | 	` | 
 |  | 
 | 	expectedErrs := []string{ | 
 | 		"\\QAndroid.bp:5:13: module \"foo\": dist.dest: Path is outside directory: ../invalid-dest\\E", | 
 | 		"\\QAndroid.bp:6:12: module \"foo\": dist.dir: Path is outside directory: ../invalid-dir\\E", | 
 | 		"\\QAndroid.bp:7:15: module \"foo\": dist.suffix: Suffix may not contain a '/' character.\\E", | 
 | 		"\\QAndroid.bp:11:15: module \"foo\": dists[0].dest: Path is outside directory: ../invalid-dest0\\E", | 
 | 		"\\QAndroid.bp:12:14: module \"foo\": dists[0].dir: Path is outside directory: ../invalid-dir0\\E", | 
 | 		"\\QAndroid.bp:13:17: module \"foo\": dists[0].suffix: Suffix may not contain a '/' character.\\E", | 
 | 		"\\QAndroid.bp:16:15: module \"foo\": dists[1].dest: Path is outside directory: ../invalid-dest1\\E", | 
 | 		"\\QAndroid.bp:17:14: module \"foo\": dists[1].dir: Path is outside directory: ../invalid-dir1\\E", | 
 | 		"\\QAndroid.bp:18:17: module \"foo\": dists[1].suffix: Suffix may not contain a '/' character.\\E", | 
 | 	} | 
 |  | 
 | 	prepareForModuleTests. | 
 | 		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.  Host symlinks must use a normal dependency, not an | 
 | 	// order-only dependency, so that the tool gets updated when the symlink is depended on. | 
 | 	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.  Device symlinks could use an order-only dependency, | 
 | 	// but the current implementation uses a normal dependency. | 
 | 	assertInputs(symlinkRule("foo"), installRule("foo").Output) | 
 | 	assertImplicits(symlinkRule("foo")) | 
 | 	assertOrderOnlys(symlinkRule("foo")) | 
 | } | 
 |  | 
 | func TestInstallKatiEnabled(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), | 
 | 		PrepareForTestWithMakevars, | 
 | 	).RunTestWithBp(t, bp) | 
 |  | 
 | 	rules := result.InstallMakeRulesForTesting(t) | 
 |  | 
 | 	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.  Host symlinks must use a normal dependency, not an | 
 | 	// order-only dependency, so that the tool gets updated when the symlink is depended on. | 
 | 	assertDeps(hostSymlinkRule("foo"), hostInstallRule("foo").Target) | 
 | 	assertOrderOnlys(hostSymlinkRule("foo")) | 
 |  | 
 | 	// 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.  Device symlinks could use an order-only dependency, | 
 | 	// but the current implementation uses a normal dependency. | 
 | 	assertDeps(symlinkRule("foo"), installRule("foo").Target) | 
 | 	assertOrderOnlys(symlinkRule("foo")) | 
 | } | 
 |  | 
 | type PropsTestModuleEmbedded struct { | 
 | 	Embedded_prop *string | 
 | } | 
 |  | 
 | type StructInSlice struct { | 
 | 	G string | 
 | 	H bool | 
 | 	I []string | 
 | } | 
 |  | 
 | type propsTestModule struct { | 
 | 	ModuleBase | 
 | 	DefaultableModuleBase | 
 | 	props struct { | 
 | 		A string `android:"arch_variant"` | 
 | 		B *bool | 
 | 		C []string | 
 | 	} | 
 | 	otherProps struct { | 
 | 		PropsTestModuleEmbedded | 
 |  | 
 | 		D      *int64 | 
 | 		Nested struct { | 
 | 			E *string | 
 | 		} | 
 | 		F *string `blueprint:"mutated"` | 
 |  | 
 | 		Slice_of_struct []StructInSlice | 
 | 	} | 
 | } | 
 |  | 
 | func propsTestModuleFactory() Module { | 
 | 	module := &propsTestModule{} | 
 | 	module.AddProperties(&module.props, &module.otherProps) | 
 | 	InitAndroidArchModule(module, HostAndDeviceSupported, MultilibBoth) | 
 | 	InitDefaultableModule(module) | 
 | 	return module | 
 | } | 
 |  | 
 | type propsTestModuleDefaults struct { | 
 | 	ModuleBase | 
 | 	DefaultsModuleBase | 
 | } | 
 |  | 
 | func propsTestModuleDefaultsFactory() Module { | 
 | 	defaults := &propsTestModuleDefaults{} | 
 | 	module := propsTestModule{} | 
 | 	defaults.AddProperties(&module.props, &module.otherProps) | 
 | 	InitDefaultsModule(defaults) | 
 | 	return defaults | 
 | } | 
 |  | 
 | func (p *propsTestModule) GenerateAndroidBuildActions(ctx ModuleContext) { | 
 | 	str := "abc" | 
 | 	p.otherProps.F = &str | 
 | } | 
 |  | 
 | func TestUsedProperties(t *testing.T) { | 
 | 	testCases := []struct { | 
 | 		desc          string | 
 | 		bp            string | 
 | 		expectedProps []propInfo | 
 | 	}{ | 
 | 		{ | 
 | 			desc: "only name", | 
 | 			bp: `test { | 
 | 			name: "foo", | 
 | 		} | 
 | 	`, | 
 | 			expectedProps: []propInfo{ | 
 | 				propInfo{Name: "Name", Type: "string", Value: "foo"}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			desc: "some props", | 
 | 			bp: `test { | 
 | 			name: "foo", | 
 | 			a: "abc", | 
 | 			b: true, | 
 | 			d: 123, | 
 | 		} | 
 | 	`, | 
 | 			expectedProps: []propInfo{ | 
 | 				propInfo{Name: "A", Type: "string", Value: "abc"}, | 
 | 				propInfo{Name: "B", Type: "bool", Value: "true"}, | 
 | 				propInfo{Name: "D", Type: "int64", Value: "123"}, | 
 | 				propInfo{Name: "Name", Type: "string", Value: "foo"}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			desc: "unused non-pointer prop", | 
 | 			bp: `test { | 
 | 			name: "foo", | 
 | 			b: true, | 
 | 			d: 123, | 
 | 		} | 
 | 	`, | 
 | 			expectedProps: []propInfo{ | 
 | 				// for non-pointer cannot distinguish between unused and intentionally set to empty | 
 | 				propInfo{Name: "A", Type: "string", Value: ""}, | 
 | 				propInfo{Name: "B", Type: "bool", Value: "true"}, | 
 | 				propInfo{Name: "D", Type: "int64", Value: "123"}, | 
 | 				propInfo{Name: "Name", Type: "string", Value: "foo"}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			desc: "nested props", | 
 | 			bp: `test { | 
 | 			name: "foo", | 
 | 			nested: { | 
 | 				e: "abc", | 
 | 			} | 
 | 		} | 
 | 	`, | 
 | 			expectedProps: []propInfo{ | 
 | 				propInfo{Name: "Name", Type: "string", Value: "foo"}, | 
 | 				propInfo{Name: "Nested.E", Type: "string", Value: "abc"}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			desc: "arch props", | 
 | 			bp: `test { | 
 | 			name: "foo", | 
 | 			arch: { | 
 | 				x86_64: { | 
 | 					a: "abc", | 
 | 				}, | 
 | 			} | 
 | 		} | 
 | 	`, | 
 | 			expectedProps: []propInfo{ | 
 | 				propInfo{Name: "Arch.X86_64.A", Type: "string", Value: "abc"}, | 
 | 				propInfo{Name: "Name", Type: "string", Value: "foo"}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			desc: "embedded props", | 
 | 			bp: `test { | 
 | 			name: "foo", | 
 | 			embedded_prop: "a", | 
 | 		} | 
 | 	`, | 
 | 			expectedProps: []propInfo{ | 
 | 				propInfo{Name: "Embedded_prop", Type: "string", Value: "a"}, | 
 | 				propInfo{Name: "Name", Type: "string", Value: "foo"}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			desc: "struct slice", | 
 | 			bp: `test { | 
 | 			name: "foo", | 
 | 			slice_of_struct: [ | 
 | 				{ | 
 | 					g: "abc", | 
 | 					h: false, | 
 | 					i: ["baz"], | 
 | 				}, | 
 | 				{ | 
 | 					g: "def", | 
 | 					h: true, | 
 | 					i: [], | 
 | 				}, | 
 | 			] | 
 | 		} | 
 | 	`, | 
 | 			expectedProps: []propInfo{ | 
 | 				propInfo{Name: "Name", Type: "string", Value: "foo"}, | 
 | 				propInfo{Name: "Slice_of_struct", Type: "struct slice", Values: []string{ | 
 | 					`android.StructInSlice{G: abc, H: false, I: [baz]}`, | 
 | 					`android.StructInSlice{G: def, H: true, I: []}`, | 
 | 				}}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			desc: "defaults", | 
 | 			bp: ` | 
 | test_defaults { | 
 | 	name: "foo_defaults", | 
 | 	a: "a", | 
 | 	b: true, | 
 | 	c: ["default_c"], | 
 | 	embedded_prop:"a", | 
 | 	arch: { | 
 | 		x86_64: { | 
 | 			a: "x86_64 a", | 
 | 		}, | 
 | 	}, | 
 | } | 
 | test { | 
 | 	name: "foo", | 
 | 	defaults: ["foo_defaults"], | 
 | 	c: ["c"], | 
 | 	nested: { | 
 | 		e: "nested e", | 
 | 	}, | 
 | 	target: { | 
 | 		linux: { | 
 | 			a: "a", | 
 | 		}, | 
 | 	}, | 
 | } | 
 | 	`, | 
 | 			expectedProps: []propInfo{ | 
 | 				propInfo{Name: "A", Type: "string", Value: "a"}, | 
 | 				propInfo{Name: "Arch.X86_64.A", Type: "string", Value: "x86_64 a"}, | 
 | 				propInfo{Name: "B", Type: "bool", Value: "true"}, | 
 | 				propInfo{Name: "C", Type: "string slice", Values: []string{"default_c", "c"}}, | 
 | 				propInfo{Name: "Defaults", Type: "string slice", Values: []string{"foo_defaults"}}, | 
 | 				propInfo{Name: "Embedded_prop", Type: "string", Value: "a"}, | 
 | 				propInfo{Name: "Name", Type: "string", Value: "foo"}, | 
 | 				propInfo{Name: "Nested.E", Type: "string", Value: "nested e"}, | 
 | 				propInfo{Name: "Target.Linux.A", Type: "string", Value: "a"}, | 
 | 			}, | 
 | 		}, | 
 | 	} | 
 |  | 
 | 	for _, tc := range testCases { | 
 | 		t.Run(tc.desc, func(t *testing.T) { | 
 | 			result := GroupFixturePreparers( | 
 | 				PrepareForTestWithAllowMissingDependencies, | 
 | 				PrepareForTestWithDefaults, | 
 | 				FixtureRegisterWithContext(func(ctx RegistrationContext) { | 
 | 					ctx.RegisterModuleType("test", propsTestModuleFactory) | 
 | 					ctx.RegisterModuleType("test_defaults", propsTestModuleDefaultsFactory) | 
 | 				}), | 
 | 				FixtureWithRootAndroidBp(tc.bp), | 
 | 			).RunTest(t) | 
 |  | 
 | 			foo := result.ModuleForTests("foo", "").Module().base() | 
 |  | 
 | 			AssertDeepEquals(t, "foo ", tc.expectedProps, foo.propertiesWithValues()) | 
 | 		}) | 
 | 	} | 
 | } | 
 |  | 
 | func TestSortedUniqueNamedPaths(t *testing.T) { | 
 | 	type np struct { | 
 | 		path, name string | 
 | 	} | 
 | 	makePaths := func(l []np) NamedPaths { | 
 | 		result := make(NamedPaths, 0, len(l)) | 
 | 		for _, p := range l { | 
 | 			result = append(result, NamedPath{PathForTesting(p.path), p.name}) | 
 | 		} | 
 | 		return result | 
 | 	} | 
 |  | 
 | 	tests := []struct { | 
 | 		name        string | 
 | 		in          []np | 
 | 		expectedOut []np | 
 | 	}{ | 
 | 		{ | 
 | 			name:        "empty", | 
 | 			in:          []np{}, | 
 | 			expectedOut: []np{}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "all_same", | 
 | 			in: []np{ | 
 | 				{"a.txt", "A"}, | 
 | 				{"a.txt", "A"}, | 
 | 				{"a.txt", "A"}, | 
 | 				{"a.txt", "A"}, | 
 | 				{"a.txt", "A"}, | 
 | 			}, | 
 | 			expectedOut: []np{ | 
 | 				{"a.txt", "A"}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "same_path_different_names", | 
 | 			in: []np{ | 
 | 				{"a.txt", "C"}, | 
 | 				{"a.txt", "A"}, | 
 | 				{"a.txt", "D"}, | 
 | 				{"a.txt", "B"}, | 
 | 				{"a.txt", "E"}, | 
 | 			}, | 
 | 			expectedOut: []np{ | 
 | 				{"a.txt", "A"}, | 
 | 				{"a.txt", "B"}, | 
 | 				{"a.txt", "C"}, | 
 | 				{"a.txt", "D"}, | 
 | 				{"a.txt", "E"}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "different_paths_same_name", | 
 | 			in: []np{ | 
 | 				{"b/b.txt", "A"}, | 
 | 				{"a/a.txt", "A"}, | 
 | 				{"a/txt", "A"}, | 
 | 				{"b", "A"}, | 
 | 				{"a/b/d", "A"}, | 
 | 			}, | 
 | 			expectedOut: []np{ | 
 | 				{"a/a.txt", "A"}, | 
 | 				{"a/b/d", "A"}, | 
 | 				{"a/txt", "A"}, | 
 | 				{"b/b.txt", "A"}, | 
 | 				{"b", "A"}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "all_different", | 
 | 			in: []np{ | 
 | 				{"b/b.txt", "A"}, | 
 | 				{"a/a.txt", "B"}, | 
 | 				{"a/txt", "D"}, | 
 | 				{"b", "C"}, | 
 | 				{"a/b/d", "E"}, | 
 | 			}, | 
 | 			expectedOut: []np{ | 
 | 				{"a/a.txt", "B"}, | 
 | 				{"a/b/d", "E"}, | 
 | 				{"a/txt", "D"}, | 
 | 				{"b/b.txt", "A"}, | 
 | 				{"b", "C"}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "some_different", | 
 | 			in: []np{ | 
 | 				{"b/b.txt", "A"}, | 
 | 				{"a/a.txt", "B"}, | 
 | 				{"a/txt", "D"}, | 
 | 				{"a/b/d", "E"}, | 
 | 				{"b", "C"}, | 
 | 				{"a/a.txt", "B"}, | 
 | 				{"a/b/d", "E"}, | 
 | 			}, | 
 | 			expectedOut: []np{ | 
 | 				{"a/a.txt", "B"}, | 
 | 				{"a/b/d", "E"}, | 
 | 				{"a/txt", "D"}, | 
 | 				{"b/b.txt", "A"}, | 
 | 				{"b", "C"}, | 
 | 			}, | 
 | 		}, | 
 | 	} | 
 | 	for _, tt := range tests { | 
 | 		t.Run(tt.name, func(t *testing.T) { | 
 | 			actual := SortedUniqueNamedPaths(makePaths(tt.in)) | 
 | 			expected := makePaths(tt.expectedOut) | 
 | 			t.Logf("actual: %v", actual) | 
 | 			t.Logf("expected: %v", expected) | 
 | 			AssertDeepEquals(t, "SortedUniqueNamedPaths ", expected, actual) | 
 | 		}) | 
 | 	} | 
 | } | 
 |  | 
 | func TestSetAndroidMkEntriesWithTestOptions(t *testing.T) { | 
 | 	tests := []struct { | 
 | 		name        string | 
 | 		testOptions CommonTestOptions | 
 | 		expected    map[string][]string | 
 | 	}{ | 
 | 		{ | 
 | 			name:        "empty", | 
 | 			testOptions: CommonTestOptions{}, | 
 | 			expected:    map[string][]string{}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "is unit test", | 
 | 			testOptions: CommonTestOptions{ | 
 | 				Unit_test: boolPtr(true), | 
 | 			}, | 
 | 			expected: map[string][]string{ | 
 | 				"LOCAL_IS_UNIT_TEST": []string{"true"}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "is not unit test", | 
 | 			testOptions: CommonTestOptions{ | 
 | 				Unit_test: boolPtr(false), | 
 | 			}, | 
 | 			expected: map[string][]string{}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "empty tag", | 
 | 			testOptions: CommonTestOptions{ | 
 | 				Tags: []string{}, | 
 | 			}, | 
 | 			expected: map[string][]string{}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "single tag", | 
 | 			testOptions: CommonTestOptions{ | 
 | 				Tags: []string{"tag1"}, | 
 | 			}, | 
 | 			expected: map[string][]string{ | 
 | 				"LOCAL_TEST_OPTIONS_TAGS": []string{"tag1"}, | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "multiple tag", | 
 | 			testOptions: CommonTestOptions{ | 
 | 				Tags: []string{"tag1", "tag2", "tag3"}, | 
 | 			}, | 
 | 			expected: map[string][]string{ | 
 | 				"LOCAL_TEST_OPTIONS_TAGS": []string{"tag1", "tag2", "tag3"}, | 
 | 			}, | 
 | 		}, | 
 | 	} | 
 | 	for _, tt := range tests { | 
 | 		t.Run(tt.name, func(t *testing.T) { | 
 | 			actualEntries := AndroidMkEntries{ | 
 | 				EntryMap: map[string][]string{}, | 
 | 			} | 
 | 			tt.testOptions.SetAndroidMkEntries(&actualEntries) | 
 | 			actual := actualEntries.EntryMap | 
 | 			t.Logf("actual: %v", actual) | 
 | 			t.Logf("expected: %v", tt.expected) | 
 | 			AssertDeepEquals(t, "TestProcessCommonTestOptions ", tt.expected, actual) | 
 | 		}) | 
 | 	} | 
 | } | 
 |  | 
 | type sourceProducerTestModule struct { | 
 | 	ModuleBase | 
 | 	props struct { | 
 | 		// A represents the source file | 
 | 		A string | 
 | 	} | 
 | } | 
 |  | 
 | func sourceProducerTestModuleFactory() Module { | 
 | 	module := &sourceProducerTestModule{} | 
 | 	module.AddProperties(&module.props) | 
 | 	InitAndroidModule(module) | 
 | 	return module | 
 | } | 
 |  | 
 | func (s sourceProducerTestModule) GenerateAndroidBuildActions(ModuleContext) {} | 
 |  | 
 | func (s sourceProducerTestModule) Srcs() Paths { return PathsForTesting(s.props.A) } | 
 |  | 
 | type outputFilesTestModule struct { | 
 | 	ModuleBase | 
 | 	props struct { | 
 | 		// A represents the tag | 
 | 		A string | 
 | 		// B represents the output file for tag A | 
 | 		B string | 
 | 	} | 
 | } | 
 |  | 
 | func outputFilesTestModuleFactory() Module { | 
 | 	module := &outputFilesTestModule{} | 
 | 	module.AddProperties(&module.props) | 
 | 	InitAndroidModule(module) | 
 | 	return module | 
 | } | 
 |  | 
 | func (o outputFilesTestModule) GenerateAndroidBuildActions(ctx ModuleContext) { | 
 | 	if o.props.A != "" || o.props.B != "" { | 
 | 		ctx.SetOutputFiles(PathsForTesting(o.props.B), o.props.A) | 
 | 	} | 
 | 	// This is to simulate the case that some module uses an object to set its | 
 | 	// OutputFilesProvider, but the object itself is empty. | 
 | 	ctx.SetOutputFiles(Paths{}, "missing") | 
 | } | 
 |  | 
 | type pathContextAddMissingDependenciesWrapper struct { | 
 | 	PathContext | 
 | 	OtherModuleProviderContext | 
 | 	missingDeps []string | 
 | } | 
 |  | 
 | func (p *pathContextAddMissingDependenciesWrapper) AddMissingDependencies(deps []string) { | 
 | 	p.missingDeps = append(p.missingDeps, deps...) | 
 | } | 
 | func (p *pathContextAddMissingDependenciesWrapper) OtherModuleName(module blueprint.Module) string { | 
 | 	return module.Name() | 
 | } | 
 |  | 
 | func (p *pathContextAddMissingDependenciesWrapper) Module() Module { return nil } | 
 |  | 
 | func (p *pathContextAddMissingDependenciesWrapper) GetOutputFiles() OutputFilesInfo { | 
 | 	return OutputFilesInfo{} | 
 | } | 
 |  | 
 | func (p *pathContextAddMissingDependenciesWrapper) EqualModules(m1, m2 Module) bool { | 
 | 	return m1 == m2 | 
 | } | 
 |  | 
 | func TestOutputFileForModule(t *testing.T) { | 
 | 	testcases := []struct { | 
 | 		name        string | 
 | 		bp          string | 
 | 		tag         string | 
 | 		expected    string | 
 | 		missingDeps []string | 
 | 		env         map[string]string | 
 | 		config      func(*config) | 
 | 	}{ | 
 | 		{ | 
 | 			name: "SourceFileProducer", | 
 | 			bp: `spt_module { | 
 | 					name: "test_module", | 
 | 					a: "spt.txt", | 
 | 				} | 
 | 			`, | 
 | 			tag:      "", | 
 | 			expected: "spt.txt", | 
 | 		}, | 
 | 		{ | 
 | 			name: "OutputFileProviderEmptyStringTag", | 
 | 			bp: `oft_module { | 
 | 					name: "test_module", | 
 | 					a: "", | 
 | 					b: "empty.txt", | 
 | 				} | 
 | 		`, | 
 | 			tag:      "", | 
 | 			expected: "empty.txt", | 
 | 		}, | 
 | 		{ | 
 | 			name: "OutputFileProviderTag", | 
 | 			bp: `oft_module { | 
 | 					name: "test_module", | 
 | 					a: "foo", | 
 | 					b: "foo.txt", | 
 | 				} | 
 | 			`, | 
 | 			tag:      "foo", | 
 | 			expected: "foo.txt", | 
 | 		}, | 
 | 		{ | 
 | 			name: "OutputFileAllowMissingDependencies", | 
 | 			bp: `oft_module { | 
 | 				name: "test_module", | 
 | 			} | 
 | 		`, | 
 | 			tag:         "missing", | 
 | 			expected:    "missing_output_file/test_module", | 
 | 			missingDeps: []string{"test_module"}, | 
 | 			config: func(config *config) { | 
 | 				config.TestProductVariables.Allow_missing_dependencies = boolPtr(true) | 
 | 			}, | 
 | 		}, | 
 | 	} | 
 |  | 
 | 	for _, tt := range testcases { | 
 | 		t.Run(tt.name, func(t *testing.T) { | 
 | 			result := GroupFixturePreparers( | 
 | 				PrepareForTestWithDefaults, | 
 | 				FixtureRegisterWithContext(func(ctx RegistrationContext) { | 
 | 					ctx.RegisterModuleType("spt_module", sourceProducerTestModuleFactory) | 
 | 					ctx.RegisterModuleType("oft_module", outputFilesTestModuleFactory) | 
 | 				}), | 
 | 				FixtureWithRootAndroidBp(tt.bp), | 
 | 			).RunTest(t) | 
 |  | 
 | 			config := TestConfig(buildDir, tt.env, tt.bp, nil) | 
 | 			if tt.config != nil { | 
 | 				tt.config(config.config) | 
 | 			} | 
 | 			ctx := &pathContextAddMissingDependenciesWrapper{ | 
 | 				PathContext:                PathContextForTesting(config), | 
 | 				OtherModuleProviderContext: result.TestContext.OtherModuleProviderAdaptor(), | 
 | 			} | 
 | 			got := OutputFileForModule(ctx, result.ModuleForTests("test_module", "").Module(), tt.tag) | 
 | 			AssertPathRelativeToTopEquals(t, "expected output path", tt.expected, got) | 
 | 			AssertArrayString(t, "expected missing deps", tt.missingDeps, ctx.missingDeps) | 
 | 		}) | 
 | 	} | 
 | } | 
 |  | 
 | func TestVintfFragmentModulesChecksPartition(t *testing.T) { | 
 | 	bp := ` | 
 | 	vintf_fragment { | 
 | 		name: "vintfModA", | 
 | 		src: "test_vintf_file", | 
 | 		vendor: true, | 
 | 	} | 
 | 	deps { | 
 | 		name: "modA", | 
 | 		vintf_fragment_modules: [ | 
 | 			"vintfModA", | 
 | 		] | 
 | 	} | 
 | 	` | 
 |  | 
 | 	testPreparer := GroupFixturePreparers( | 
 | 		PrepareForTestWithAndroidBuildComponents, | 
 | 		prepareForModuleTests, | 
 | 	) | 
 |  | 
 | 	testPreparer. | 
 | 		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern( | 
 | 			"Module .+ and Vintf_fragment .+ are installed to different partitions.")). | 
 | 		RunTestWithBp(t, bp) | 
 | } |