// 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 ( "errors" "fmt" "reflect" "strconv" "strings" "testing" "github.com/google/blueprint/proptools" ) type strsTestCase struct { in []string out string err []error } var commonValidatePathTestCases = []strsTestCase{ { in: []string{""}, out: "", }, { in: []string{"a/b"}, out: "a/b", }, { in: []string{"a/b", "c"}, out: "a/b/c", }, { in: []string{"a/.."}, out: ".", }, { in: []string{"."}, out: ".", }, { in: []string{".."}, out: "", err: []error{errors.New("Path is outside directory: ..")}, }, { in: []string{"../a"}, out: "", err: []error{errors.New("Path is outside directory: ../a")}, }, { in: []string{"b/../../a"}, out: "", err: []error{errors.New("Path is outside directory: ../a")}, }, { in: []string{"/a"}, out: "", err: []error{errors.New("Path is outside directory: /a")}, }, { in: []string{"a", "../b"}, out: "", err: []error{errors.New("Path is outside directory: ../b")}, }, { in: []string{"a", "b/../../c"}, out: "", err: []error{errors.New("Path is outside directory: ../c")}, }, { in: []string{"a", "./.."}, out: "", err: []error{errors.New("Path is outside directory: ..")}, }, } var validateSafePathTestCases = append(commonValidatePathTestCases, []strsTestCase{ { in: []string{"$host/../$a"}, out: "$a", }, }...) var validatePathTestCases = append(commonValidatePathTestCases, []strsTestCase{ { in: []string{"$host/../$a"}, out: "", err: []error{errors.New("Path contains invalid character($): $host/../$a")}, }, { in: []string{"$host/.."}, out: "", err: []error{errors.New("Path contains invalid character($): $host/..")}, }, }...) func TestValidateSafePath(t *testing.T) { for _, testCase := range validateSafePathTestCases { t.Run(strings.Join(testCase.in, ","), func(t *testing.T) { ctx := &configErrorWrapper{} out, err := validateSafePath(testCase.in...) if err != nil { reportPathError(ctx, err) } check(t, "validateSafePath", p(testCase.in), out, ctx.errors, testCase.out, testCase.err) }) } } func TestValidatePath(t *testing.T) { for _, testCase := range validatePathTestCases { t.Run(strings.Join(testCase.in, ","), func(t *testing.T) { ctx := &configErrorWrapper{} out, err := validatePath(testCase.in...) if err != nil { reportPathError(ctx, err) } check(t, "validatePath", p(testCase.in), out, ctx.errors, testCase.out, testCase.err) }) } } func TestOptionalPath(t *testing.T) { var path OptionalPath checkInvalidOptionalPath(t, path) path = OptionalPathForPath(nil) checkInvalidOptionalPath(t, path) path = OptionalPathForPath(PathForTesting("path")) checkValidOptionalPath(t, path, "path") } func checkInvalidOptionalPath(t *testing.T, path OptionalPath) { t.Helper() if path.Valid() { t.Errorf("Uninitialized OptionalPath should not be valid") } if path.String() != "" { t.Errorf("Uninitialized OptionalPath String() should return \"\", not %q", path.String()) } paths := path.AsPaths() if len(paths) != 0 { t.Errorf("Uninitialized OptionalPath AsPaths() should return empty Paths, not %q", paths) } defer func() { if r := recover(); r == nil { t.Errorf("Expected a panic when calling Path() on an uninitialized OptionalPath") } }() path.Path() } func checkValidOptionalPath(t *testing.T, path OptionalPath, expectedString string) { t.Helper() if !path.Valid() { t.Errorf("Initialized OptionalPath should not be invalid") } if path.String() != expectedString { t.Errorf("Initialized OptionalPath String() should return %q, not %q", expectedString, path.String()) } paths := path.AsPaths() if len(paths) != 1 { t.Errorf("Initialized OptionalPath AsPaths() should return Paths with length 1, not %q", paths) } path.Path() } func check(t *testing.T, testType, testString string, got interface{}, err []error, expected interface{}, expectedErr []error) { t.Helper() printedTestCase := false e := func(s string, expected, got interface{}) { t.Helper() if !printedTestCase { t.Errorf("test case %s: %s", testType, testString) printedTestCase = true } t.Errorf("incorrect %s", s) t.Errorf(" expected: %s", p(expected)) t.Errorf(" got: %s", p(got)) } if !reflect.DeepEqual(expectedErr, err) { e("errors:", expectedErr, err) } if !reflect.DeepEqual(expected, got) { e("output:", expected, got) } } func p(in interface{}) string { if v, ok := in.([]interface{}); ok { s := make([]string, len(v)) for i := range v { s[i] = fmt.Sprintf("%#v", v[i]) } return "[" + strings.Join(s, ", ") + "]" } else { return fmt.Sprintf("%#v", in) } } func pathTestConfig(buildDir string) Config { return TestConfig(buildDir, nil, "", nil) } func TestPathForModuleInstall(t *testing.T) { testConfig := pathTestConfig("") hostTarget := Target{Os: Linux, Arch: Arch{ArchType: X86}} deviceTarget := Target{Os: Android, Arch: Arch{ArchType: Arm64}} testCases := []struct { name string ctx *testModuleInstallPathContext in []string out string partitionDir string }{ { name: "host binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: hostTarget.Os, target: hostTarget, }, }, in: []string{"bin", "my_test"}, out: "host/linux-x86/bin/my_test", partitionDir: "host/linux-x86", }, { name: "system binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, }, }, in: []string{"bin", "my_test"}, out: "target/product/test_device/system/bin/my_test", partitionDir: "target/product/test_device/system", }, { name: "vendor binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, earlyModuleContext: earlyModuleContext{ kind: socSpecificModule, }, }, }, in: []string{"bin", "my_test"}, out: "target/product/test_device/vendor/bin/my_test", partitionDir: "target/product/test_device/vendor", }, { name: "odm binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, earlyModuleContext: earlyModuleContext{ kind: deviceSpecificModule, }, }, }, in: []string{"bin", "my_test"}, out: "target/product/test_device/odm/bin/my_test", partitionDir: "target/product/test_device/odm", }, { name: "product binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, earlyModuleContext: earlyModuleContext{ kind: productSpecificModule, }, }, }, in: []string{"bin", "my_test"}, out: "target/product/test_device/product/bin/my_test", partitionDir: "target/product/test_device/product", }, { name: "system_ext binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, earlyModuleContext: earlyModuleContext{ kind: systemExtSpecificModule, }, }, }, in: []string{"bin", "my_test"}, out: "target/product/test_device/system_ext/bin/my_test", partitionDir: "target/product/test_device/system_ext", }, { name: "root binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, }, inRoot: true, }, in: []string{"my_test"}, out: "target/product/test_device/root/my_test", partitionDir: "target/product/test_device/root", }, { name: "recovery binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, }, inRecovery: true, }, in: []string{"bin/my_test"}, out: "target/product/test_device/recovery/root/system/bin/my_test", partitionDir: "target/product/test_device/recovery/root/system", }, { name: "recovery root binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, }, inRecovery: true, inRoot: true, }, in: []string{"my_test"}, out: "target/product/test_device/recovery/root/my_test", partitionDir: "target/product/test_device/recovery/root", }, { name: "ramdisk binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, }, inRamdisk: true, }, in: []string{"my_test"}, out: "target/product/test_device/ramdisk/system/my_test", partitionDir: "target/product/test_device/ramdisk/system", }, { name: "ramdisk root binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, }, inRamdisk: true, inRoot: true, }, in: []string{"my_test"}, out: "target/product/test_device/ramdisk/my_test", partitionDir: "target/product/test_device/ramdisk", }, { name: "vendor_ramdisk binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, }, inVendorRamdisk: true, }, in: []string{"my_test"}, out: "target/product/test_device/vendor_ramdisk/system/my_test", partitionDir: "target/product/test_device/vendor_ramdisk/system", }, { name: "vendor_ramdisk root binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, }, inVendorRamdisk: true, inRoot: true, }, in: []string{"my_test"}, out: "target/product/test_device/vendor_ramdisk/my_test", partitionDir: "target/product/test_device/vendor_ramdisk", }, { name: "debug_ramdisk binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, }, inDebugRamdisk: true, }, in: []string{"my_test"}, out: "target/product/test_device/debug_ramdisk/my_test", partitionDir: "target/product/test_device/debug_ramdisk", }, { name: "system native test binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, }, inData: true, }, in: []string{"nativetest", "my_test"}, out: "target/product/test_device/data/nativetest/my_test", partitionDir: "target/product/test_device/data", }, { name: "vendor native test binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, earlyModuleContext: earlyModuleContext{ kind: socSpecificModule, }, }, inData: true, }, in: []string{"nativetest", "my_test"}, out: "target/product/test_device/data/nativetest/my_test", partitionDir: "target/product/test_device/data", }, { name: "odm native test binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, earlyModuleContext: earlyModuleContext{ kind: deviceSpecificModule, }, }, inData: true, }, in: []string{"nativetest", "my_test"}, out: "target/product/test_device/data/nativetest/my_test", partitionDir: "target/product/test_device/data", }, { name: "product native test binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, earlyModuleContext: earlyModuleContext{ kind: productSpecificModule, }, }, inData: true, }, in: []string{"nativetest", "my_test"}, out: "target/product/test_device/data/nativetest/my_test", partitionDir: "target/product/test_device/data", }, { name: "system_ext native test binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, earlyModuleContext: earlyModuleContext{ kind: systemExtSpecificModule, }, }, inData: true, }, in: []string{"nativetest", "my_test"}, out: "target/product/test_device/data/nativetest/my_test", partitionDir: "target/product/test_device/data", }, { name: "sanitized system binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, }, inSanitizerDir: true, }, in: []string{"bin", "my_test"}, out: "target/product/test_device/data/asan/system/bin/my_test", partitionDir: "target/product/test_device/data/asan/system", }, { name: "sanitized vendor binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, earlyModuleContext: earlyModuleContext{ kind: socSpecificModule, }, }, inSanitizerDir: true, }, in: []string{"bin", "my_test"}, out: "target/product/test_device/data/asan/vendor/bin/my_test", partitionDir: "target/product/test_device/data/asan/vendor", }, { name: "sanitized odm binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, earlyModuleContext: earlyModuleContext{ kind: deviceSpecificModule, }, }, inSanitizerDir: true, }, in: []string{"bin", "my_test"}, out: "target/product/test_device/data/asan/odm/bin/my_test", partitionDir: "target/product/test_device/data/asan/odm", }, { name: "sanitized product binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, earlyModuleContext: earlyModuleContext{ kind: productSpecificModule, }, }, inSanitizerDir: true, }, in: []string{"bin", "my_test"}, out: "target/product/test_device/data/asan/product/bin/my_test", partitionDir: "target/product/test_device/data/asan/product", }, { name: "sanitized system_ext binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, earlyModuleContext: earlyModuleContext{ kind: systemExtSpecificModule, }, }, inSanitizerDir: true, }, in: []string{"bin", "my_test"}, out: "target/product/test_device/data/asan/system_ext/bin/my_test", partitionDir: "target/product/test_device/data/asan/system_ext", }, { name: "sanitized system native test binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, }, inData: true, inSanitizerDir: true, }, in: []string{"nativetest", "my_test"}, out: "target/product/test_device/data/asan/data/nativetest/my_test", partitionDir: "target/product/test_device/data/asan/data", }, { name: "sanitized vendor native test binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, earlyModuleContext: earlyModuleContext{ kind: socSpecificModule, }, }, inData: true, inSanitizerDir: true, }, in: []string{"nativetest", "my_test"}, out: "target/product/test_device/data/asan/data/nativetest/my_test", partitionDir: "target/product/test_device/data/asan/data", }, { name: "sanitized odm native test binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, earlyModuleContext: earlyModuleContext{ kind: deviceSpecificModule, }, }, inData: true, inSanitizerDir: true, }, in: []string{"nativetest", "my_test"}, out: "target/product/test_device/data/asan/data/nativetest/my_test", partitionDir: "target/product/test_device/data/asan/data", }, { name: "sanitized product native test binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, earlyModuleContext: earlyModuleContext{ kind: productSpecificModule, }, }, inData: true, inSanitizerDir: true, }, in: []string{"nativetest", "my_test"}, out: "target/product/test_device/data/asan/data/nativetest/my_test", partitionDir: "target/product/test_device/data/asan/data", }, { name: "sanitized system_ext native test binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, earlyModuleContext: earlyModuleContext{ kind: systemExtSpecificModule, }, }, inData: true, inSanitizerDir: true, }, in: []string{"nativetest", "my_test"}, out: "target/product/test_device/data/asan/data/nativetest/my_test", partitionDir: "target/product/test_device/data/asan/data", }, { name: "device testcases", ctx: &testModuleInstallPathContext{ 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", partitionDir: "target/product/test_device/testcases", }, { name: "host testcases", ctx: &testModuleInstallPathContext{ 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", partitionDir: "host/linux-x86/testcases", }, { name: "forced host testcases", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, }, inTestcases: true, forceOS: &Linux, forceArch: &X86, }, in: []string{"my_test", "my_test_bin"}, out: "host/linux-x86/testcases/my_test/my_test_bin", partitionDir: "host/linux-x86/testcases", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { tc.ctx.baseModuleContext.config = testConfig output := PathForModuleInstall(tc.ctx, tc.in...) if output.basePath.path != tc.out { t.Errorf("unexpected path:\n got: %q\nwant: %q\n", output.basePath.path, tc.out) } if output.partitionDir != tc.partitionDir { t.Errorf("unexpected partitionDir:\n got: %q\nwant: %q\n", output.partitionDir, tc.partitionDir) } }) } } func TestPathForModuleInstallRecoveryAsBoot(t *testing.T) { testConfig := pathTestConfig("") testConfig.TestProductVariables.BoardUsesRecoveryAsBoot = proptools.BoolPtr(true) testConfig.TestProductVariables.BoardMoveRecoveryResourcesToVendorBoot = proptools.BoolPtr(true) deviceTarget := Target{Os: Android, Arch: Arch{ArchType: Arm64}} testCases := []struct { name string ctx *testModuleInstallPathContext in []string out string partitionDir string }{ { name: "ramdisk binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, }, inRamdisk: true, inRoot: true, }, in: []string{"my_test"}, out: "target/product/test_device/recovery/root/first_stage_ramdisk/my_test", partitionDir: "target/product/test_device/recovery/root/first_stage_ramdisk", }, { name: "vendor_ramdisk binary", ctx: &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, }, inVendorRamdisk: true, inRoot: true, }, in: []string{"my_test"}, out: "target/product/test_device/vendor_ramdisk/first_stage_ramdisk/my_test", partitionDir: "target/product/test_device/vendor_ramdisk/first_stage_ramdisk", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { tc.ctx.baseModuleContext.config = testConfig output := PathForModuleInstall(tc.ctx, tc.in...) if output.basePath.path != tc.out { t.Errorf("unexpected path:\n got: %q\nwant: %q\n", output.basePath.path, tc.out) } if output.partitionDir != tc.partitionDir { t.Errorf("unexpected partitionDir:\n got: %q\nwant: %q\n", output.partitionDir, tc.partitionDir) } }) } } func TestBaseDirForInstallPath(t *testing.T) { testConfig := pathTestConfig("") deviceTarget := Target{Os: Android, Arch: Arch{ArchType: Arm64}} ctx := &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, }, } ctx.baseModuleContext.config = testConfig actual := PathForModuleInstall(ctx, "foo", "bar") expectedBaseDir := "target/product/test_device/system" if actual.partitionDir != expectedBaseDir { t.Errorf("unexpected partitionDir:\n got: %q\nwant: %q\n", actual.partitionDir, expectedBaseDir) } expectedRelPath := "foo/bar" if actual.Rel() != expectedRelPath { t.Errorf("unexpected Rel():\n got: %q\nwant: %q\n", actual.Rel(), expectedRelPath) } actualAfterJoin := actual.Join(ctx, "baz") // partitionDir is preserved even after joining if actualAfterJoin.partitionDir != expectedBaseDir { t.Errorf("unexpected partitionDir after joining:\n got: %q\nwant: %q\n", actualAfterJoin.partitionDir, expectedBaseDir) } // Rel() is updated though expectedRelAfterJoin := "baz" if actualAfterJoin.Rel() != expectedRelAfterJoin { t.Errorf("unexpected Rel() after joining:\n got: %q\nwant: %q\n", actualAfterJoin.Rel(), expectedRelAfterJoin) } } func TestDirectorySortedPaths(t *testing.T) { config := TestConfig("out", nil, "", map[string][]byte{ "Android.bp": nil, "a.txt": nil, "a/txt": nil, "a/b/c": nil, "a/b/d": nil, "b": nil, "b/b.txt": nil, "a/a.txt": nil, }) ctx := PathContextForTesting(config) makePaths := func() Paths { return Paths{ PathForSource(ctx, "a.txt"), PathForSource(ctx, "a/txt"), PathForSource(ctx, "a/b/c"), PathForSource(ctx, "a/b/d"), PathForSource(ctx, "b"), PathForSource(ctx, "b/b.txt"), PathForSource(ctx, "a/a.txt"), } } expected := []string{ "a.txt", "a/a.txt", "a/b/c", "a/b/d", "a/txt", "b", "b/b.txt", } paths := makePaths() reversePaths := ReversePaths(paths) sortedPaths := PathsToDirectorySortedPaths(paths) reverseSortedPaths := PathsToDirectorySortedPaths(reversePaths) if !reflect.DeepEqual(Paths(sortedPaths).Strings(), expected) { t.Fatalf("sorted paths:\n %#v\n != \n %#v", paths.Strings(), expected) } if !reflect.DeepEqual(Paths(reverseSortedPaths).Strings(), expected) { t.Fatalf("sorted reversed paths:\n %#v\n !=\n %#v", reversePaths.Strings(), expected) } expectedA := []string{ "a/a.txt", "a/b/c", "a/b/d", "a/txt", } inA := sortedPaths.PathsInDirectory("a") if !reflect.DeepEqual(inA.Strings(), expectedA) { t.Errorf("FilesInDirectory(a):\n %#v\n != \n %#v", inA.Strings(), expectedA) } expectedA_B := []string{ "a/b/c", "a/b/d", } inA_B := sortedPaths.PathsInDirectory("a/b") if !reflect.DeepEqual(inA_B.Strings(), expectedA_B) { t.Errorf("FilesInDirectory(a/b):\n %#v\n != \n %#v", inA_B.Strings(), expectedA_B) } expectedB := []string{ "b/b.txt", } inB := sortedPaths.PathsInDirectory("b") if !reflect.DeepEqual(inB.Strings(), expectedB) { t.Errorf("FilesInDirectory(b):\n %#v\n != \n %#v", inA.Strings(), expectedA) } } func TestMaybeRel(t *testing.T) { testCases := []struct { name string base string target string out string isRel bool }{ { name: "normal", base: "a/b/c", target: "a/b/c/d", out: "d", isRel: true, }, { name: "parent", base: "a/b/c/d", target: "a/b/c", isRel: false, }, { name: "not relative", base: "a/b", target: "c/d", isRel: false, }, { name: "abs1", base: "/a", target: "a", isRel: false, }, { name: "abs2", base: "a", target: "/a", isRel: false, }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { ctx := &configErrorWrapper{} out, isRel := MaybeRel(ctx, testCase.base, testCase.target) if len(ctx.errors) > 0 { t.Errorf("MaybeRel(..., %s, %s) reported unexpected errors %v", testCase.base, testCase.target, ctx.errors) } if isRel != testCase.isRel || out != testCase.out { t.Errorf("MaybeRel(..., %s, %s) want %v, %v got %v, %v", testCase.base, testCase.target, testCase.out, testCase.isRel, out, isRel) } }) } } func TestPathForSource(t *testing.T) { testCases := []struct { name string buildDir string src string err string }{ { name: "normal", buildDir: "out", src: "a/b/c", }, { name: "abs", buildDir: "out", src: "/a/b/c", err: "is outside directory", }, { name: "in out dir", buildDir: "out", src: "out/a/b/c", err: "is in output", }, } funcs := []struct { name string f func(ctx PathContext, pathComponents ...string) (SourcePath, error) }{ {"pathForSource", pathForSource}, {"safePathForSource", safePathForSource}, } for _, f := range funcs { t.Run(f.name, func(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { testConfig := pathTestConfig(test.buildDir) ctx := &configErrorWrapper{config: testConfig} _, err := f.f(ctx, test.src) if len(ctx.errors) > 0 { t.Fatalf("unexpected errors %v", ctx.errors) } if err != nil { if test.err == "" { t.Fatalf("unexpected error %q", err.Error()) } else if !strings.Contains(err.Error(), test.err) { t.Fatalf("incorrect error, want substring %q got %q", test.err, err.Error()) } } else { if test.err != "" { t.Fatalf("missing error %q", test.err) } } }) } }) } } type pathForModuleSrcTestModule struct { ModuleBase props struct { Srcs []string `android:"path"` Exclude_srcs []string `android:"path"` Src *string `android:"path"` Module_handles_missing_deps bool } src string rel string srcs []string rels []string missingDeps []string } func pathForModuleSrcTestModuleFactory() Module { module := &pathForModuleSrcTestModule{} module.AddProperties(&module.props) InitAndroidModule(module) return module } func (p *pathForModuleSrcTestModule) GenerateAndroidBuildActions(ctx ModuleContext) { var srcs Paths if p.props.Module_handles_missing_deps { srcs, p.missingDeps = PathsAndMissingDepsForModuleSrcExcludes(ctx, p.props.Srcs, p.props.Exclude_srcs) } else { srcs = PathsForModuleSrcExcludes(ctx, p.props.Srcs, p.props.Exclude_srcs) } p.srcs = srcs.Strings() for _, src := range srcs { p.rels = append(p.rels, src.Rel()) } if p.props.Src != nil { src := PathForModuleSrc(ctx, *p.props.Src) if src != nil { p.src = src.String() p.rel = src.Rel() } } if !p.props.Module_handles_missing_deps { p.missingDeps = ctx.GetMissingDependencies() } ctx.Build(pctx, BuildParams{ Rule: Touch, Output: PathForModuleOut(ctx, "output"), }) } type pathForModuleSrcOutputFileProviderModule struct { ModuleBase props struct { Outs []string Tagged []string } outs Paths tagged Paths } func pathForModuleSrcOutputFileProviderModuleFactory() Module { module := &pathForModuleSrcOutputFileProviderModule{} module.AddProperties(&module.props) InitAndroidModule(module) return module } func (p *pathForModuleSrcOutputFileProviderModule) GenerateAndroidBuildActions(ctx ModuleContext) { for _, out := range p.props.Outs { p.outs = append(p.outs, PathForModuleOut(ctx, out)) } for _, tagged := range p.props.Tagged { p.tagged = append(p.tagged, PathForModuleOut(ctx, tagged)) } } func (p *pathForModuleSrcOutputFileProviderModule) OutputFiles(tag string) (Paths, error) { switch tag { case "": return p.outs, nil case ".tagged": return p.tagged, nil default: return nil, fmt.Errorf("unsupported tag %q", tag) } } type pathForModuleSrcTestCase struct { name string bp string srcs []string rels []string src string rel string // Make test specific preparations to the test fixture. preparer FixturePreparer // A test specific error handler. errorHandler FixtureErrorHandler } func testPathForModuleSrc(t *testing.T, tests []pathForModuleSrcTestCase) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { fgBp := ` filegroup { name: "a", srcs: ["src/a"], } ` ofpBp := ` output_file_provider { name: "b", outs: ["gen/b"], tagged: ["gen/c"], } ` mockFS := MockFS{ "fg/Android.bp": []byte(fgBp), "foo/Android.bp": []byte(test.bp), "ofp/Android.bp": []byte(ofpBp), "fg/src/a": nil, "foo/src/b": nil, "foo/src/c": nil, "foo/src/d": nil, "foo/src/e/e": nil, "foo/src_special/$": nil, } errorHandler := test.errorHandler if errorHandler == nil { errorHandler = FixtureExpectsNoErrors } result := GroupFixturePreparers( FixtureRegisterWithContext(func(ctx RegistrationContext) { ctx.RegisterModuleType("test", pathForModuleSrcTestModuleFactory) ctx.RegisterModuleType("output_file_provider", pathForModuleSrcOutputFileProviderModuleFactory) }), PrepareForTestWithFilegroup, PrepareForTestWithNamespace, mockFS.AddToFixture(), OptionalFixturePreparer(test.preparer), ). ExtendWithErrorHandler(errorHandler). RunTest(t) m := result.ModuleForTests("foo", "").Module().(*pathForModuleSrcTestModule) AssertStringPathsRelativeToTopEquals(t, "srcs", result.Config, test.srcs, m.srcs) AssertStringPathsRelativeToTopEquals(t, "rels", result.Config, test.rels, m.rels) AssertStringPathRelativeToTopEquals(t, "src", result.Config, test.src, m.src) AssertStringPathRelativeToTopEquals(t, "rel", result.Config, test.rel, m.rel) }) } } func TestPathsForModuleSrc(t *testing.T) { tests := []pathForModuleSrcTestCase{ { name: "path", bp: ` test { name: "foo", srcs: ["src/b"], }`, srcs: []string{"foo/src/b"}, rels: []string{"src/b"}, }, { name: "glob", bp: ` test { name: "foo", srcs: [ "src/*", "src/e/*", ], }`, srcs: []string{"foo/src/b", "foo/src/c", "foo/src/d", "foo/src/e/e"}, rels: []string{"src/b", "src/c", "src/d", "src/e/e"}, }, { name: "recursive glob", bp: ` test { name: "foo", srcs: ["src/**/*"], }`, srcs: []string{"foo/src/b", "foo/src/c", "foo/src/d", "foo/src/e/e"}, rels: []string{"src/b", "src/c", "src/d", "src/e/e"}, }, { name: "filegroup", bp: ` test { name: "foo", srcs: [":a"], }`, srcs: []string{"fg/src/a"}, rels: []string{"src/a"}, }, { name: "output file provider", bp: ` test { name: "foo", srcs: [":b"], }`, srcs: []string{"out/soong/.intermediates/ofp/b/gen/b"}, rels: []string{"gen/b"}, }, { name: "output file provider tagged", bp: ` test { name: "foo", srcs: [":b{.tagged}"], }`, srcs: []string{"out/soong/.intermediates/ofp/b/gen/c"}, rels: []string{"gen/c"}, }, { name: "output file provider with exclude", bp: ` test { name: "foo", srcs: [":b", ":c"], exclude_srcs: [":c"] } output_file_provider { name: "c", outs: ["gen/c"], }`, srcs: []string{"out/soong/.intermediates/ofp/b/gen/b"}, rels: []string{"gen/b"}, }, { name: "special characters glob", bp: ` test { name: "foo", srcs: ["src_special/*"], }`, srcs: []string{"foo/src_special/$"}, rels: []string{"src_special/$"}, }, } testPathForModuleSrc(t, tests) } func TestPathForModuleSrc(t *testing.T) { tests := []pathForModuleSrcTestCase{ { name: "path", bp: ` test { name: "foo", src: "src/b", }`, src: "foo/src/b", rel: "src/b", }, { name: "glob", bp: ` test { name: "foo", src: "src/e/*", }`, src: "foo/src/e/e", rel: "src/e/e", }, { name: "filegroup", bp: ` test { name: "foo", src: ":a", }`, src: "fg/src/a", rel: "src/a", }, { name: "output file provider", bp: ` test { name: "foo", src: ":b", }`, src: "out/soong/.intermediates/ofp/b/gen/b", rel: "gen/b", }, { name: "output file provider tagged", bp: ` test { name: "foo", src: ":b{.tagged}", }`, src: "out/soong/.intermediates/ofp/b/gen/c", rel: "gen/c", }, { name: "special characters glob", bp: ` test { name: "foo", src: "src_special/*", }`, src: "foo/src_special/$", rel: "src_special/$", }, { // This test makes sure that an unqualified module name cannot contain characters that make // it appear as a qualified module name. name: "output file provider, invalid fully qualified name", bp: ` test { name: "foo", src: "://other:b", srcs: ["://other:c"], }`, preparer: FixtureAddTextFile("other/Android.bp", ` soong_namespace {} output_file_provider { name: "b", outs: ["gen/b"], } output_file_provider { name: "c", outs: ["gen/c"], } `), src: "foo/:/other:b", rel: ":/other:b", srcs: []string{"foo/:/other:c"}, rels: []string{":/other:c"}, }, { name: "output file provider, missing fully qualified name", bp: ` test { name: "foo", src: "//other:b", srcs: ["//other:c"], }`, errorHandler: FixtureExpectsAllErrorsToMatchAPattern([]string{ `"foo" depends on undefined module "//other:b"`, `"foo" depends on undefined module "//other:c"`, }), }, { name: "output file provider, fully qualified name", bp: ` test { name: "foo", src: "//other:b", srcs: ["//other:c"], }`, src: "out/soong/.intermediates/other/b/gen/b", rel: "gen/b", srcs: []string{"out/soong/.intermediates/other/c/gen/c"}, rels: []string{"gen/c"}, preparer: FixtureAddTextFile("other/Android.bp", ` soong_namespace {} output_file_provider { name: "b", outs: ["gen/b"], } output_file_provider { name: "c", outs: ["gen/c"], } `), }, } testPathForModuleSrc(t, tests) } func TestPathsForModuleSrc_AllowMissingDependencies(t *testing.T) { bp := ` test { name: "foo", srcs: [":a"], exclude_srcs: [":b"], src: ":c", } test { name: "bar", srcs: [":d"], exclude_srcs: [":e"], module_handles_missing_deps: true, } ` result := GroupFixturePreparers( PrepareForTestWithAllowMissingDependencies, FixtureRegisterWithContext(func(ctx RegistrationContext) { ctx.RegisterModuleType("test", pathForModuleSrcTestModuleFactory) }), FixtureWithRootAndroidBp(bp), ).RunTest(t) foo := result.ModuleForTests("foo", "").Module().(*pathForModuleSrcTestModule) AssertArrayString(t, "foo missing deps", []string{"a", "b", "c"}, foo.missingDeps) AssertArrayString(t, "foo srcs", []string{}, foo.srcs) AssertStringEquals(t, "foo src", "", foo.src) bar := result.ModuleForTests("bar", "").Module().(*pathForModuleSrcTestModule) AssertArrayString(t, "bar missing deps", []string{"d", "e"}, bar.missingDeps) AssertArrayString(t, "bar srcs", []string{}, bar.srcs) } func TestPathRelativeToTop(t *testing.T) { testConfig := pathTestConfig("/tmp/build/top") deviceTarget := Target{Os: Android, Arch: Arch{ArchType: Arm64}} ctx := &testModuleInstallPathContext{ baseModuleContext: baseModuleContext{ os: deviceTarget.Os, target: deviceTarget, }, } ctx.baseModuleContext.config = testConfig t.Run("install for soong", func(t *testing.T) { p := PathForModuleInstall(ctx, "install/path") AssertPathRelativeToTopEquals(t, "install path for soong", "out/soong/target/product/test_device/system/install/path", p) }) t.Run("install for make", func(t *testing.T) { p := PathForModuleInstall(ctx, "install/path").ToMakePath() AssertPathRelativeToTopEquals(t, "install path for make", "out/target/product/test_device/system/install/path", p) }) t.Run("output", func(t *testing.T) { p := PathForOutput(ctx, "output/path") AssertPathRelativeToTopEquals(t, "output path", "out/soong/output/path", p) }) t.Run("source", func(t *testing.T) { p := PathForSource(ctx, "source/path") AssertPathRelativeToTopEquals(t, "source path", "source/path", p) }) t.Run("mixture", func(t *testing.T) { paths := Paths{ PathForModuleInstall(ctx, "install/path"), PathForModuleInstall(ctx, "install/path").ToMakePath(), PathForOutput(ctx, "output/path"), PathForSource(ctx, "source/path"), } expected := []string{ "out/soong/target/product/test_device/system/install/path", "out/target/product/test_device/system/install/path", "out/soong/output/path", "source/path", } AssertPathsRelativeToTopEquals(t, "mixture", expected, paths) }) } func ExampleOutputPath_ReplaceExtension() { ctx := &configErrorWrapper{ config: TestConfig("out", nil, "", nil), } p := PathForOutput(ctx, "system/framework").Join(ctx, "boot.art") p2 := p.ReplaceExtension(ctx, "oat") fmt.Println(p, p2) fmt.Println(p.Rel(), p2.Rel()) // Output: // out/system/framework/boot.art out/system/framework/boot.oat // boot.art boot.oat } func ExampleOutputPath_InSameDir() { ctx := &configErrorWrapper{ config: TestConfig("out", nil, "", nil), } p := PathForOutput(ctx, "system/framework").Join(ctx, "boot.art") p2 := p.InSameDir(ctx, "oat", "arm", "boot.vdex") fmt.Println(p, p2) fmt.Println(p.Rel(), p2.Rel()) // Output: // out/system/framework/boot.art out/system/framework/oat/arm/boot.vdex // boot.art oat/arm/boot.vdex } func BenchmarkFirstUniquePaths(b *testing.B) { implementations := []struct { name string f func(Paths) Paths }{ { name: "list", f: firstUniquePathsList, }, { name: "map", f: firstUniquePathsMap, }, } const maxSize = 1024 uniquePaths := make(Paths, maxSize) for i := range uniquePaths { uniquePaths[i] = PathForTesting(strconv.Itoa(i)) } samePath := make(Paths, maxSize) for i := range samePath { samePath[i] = uniquePaths[0] } f := func(b *testing.B, imp func(Paths) Paths, paths Paths) { for i := 0; i < b.N; i++ { b.ReportAllocs() paths = append(Paths(nil), paths...) imp(paths) } } for n := 1; n <= maxSize; n <<= 1 { b.Run(strconv.Itoa(n), func(b *testing.B) { for _, implementation := range implementations { b.Run(implementation.name, func(b *testing.B) { b.Run("same", func(b *testing.B) { f(b, implementation.f, samePath[:n]) }) b.Run("unique", func(b *testing.B) { f(b, implementation.f, uniquePaths[:n]) }) }) } }) } }