|  | // Copyright 2019 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 sdk | 
|  |  | 
|  | import ( | 
|  | "fmt" | 
|  | "io/ioutil" | 
|  | "os" | 
|  | "path/filepath" | 
|  | "reflect" | 
|  | "strings" | 
|  | "testing" | 
|  |  | 
|  | "android/soong/android" | 
|  | "android/soong/apex" | 
|  | "android/soong/cc" | 
|  | "android/soong/java" | 
|  | ) | 
|  |  | 
|  | func testSdkContext(bp string, fs map[string][]byte, extraOsTypes []android.OsType) (*android.TestContext, android.Config) { | 
|  | extraOsTypes = append(extraOsTypes, android.Android, android.Windows) | 
|  |  | 
|  | bp = bp + ` | 
|  | apex_key { | 
|  | name: "myapex.key", | 
|  | public_key: "myapex.avbpubkey", | 
|  | private_key: "myapex.pem", | 
|  | } | 
|  |  | 
|  | android_app_certificate { | 
|  | name: "myapex.cert", | 
|  | certificate: "myapex", | 
|  | } | 
|  | ` + cc.GatherRequiredDepsForTest(extraOsTypes...) | 
|  |  | 
|  | mockFS := map[string][]byte{ | 
|  | "build/make/target/product/security":           nil, | 
|  | "apex_manifest.json":                           nil, | 
|  | "system/sepolicy/apex/myapex-file_contexts":    nil, | 
|  | "system/sepolicy/apex/myapex2-file_contexts":   nil, | 
|  | "system/sepolicy/apex/mysdkapex-file_contexts": nil, | 
|  | "myapex.avbpubkey":                             nil, | 
|  | "myapex.pem":                                   nil, | 
|  | "myapex.x509.pem":                              nil, | 
|  | "myapex.pk8":                                   nil, | 
|  | } | 
|  |  | 
|  | cc.GatherRequiredFilesForTest(mockFS) | 
|  |  | 
|  | for k, v := range fs { | 
|  | mockFS[k] = v | 
|  | } | 
|  |  | 
|  | config := android.TestArchConfig(buildDir, nil, bp, mockFS) | 
|  |  | 
|  | // Add windows as a default disable OS to test behavior when some OS variants | 
|  | // are disabled. | 
|  | config.Targets[android.Windows] = []android.Target{ | 
|  | {android.Windows, android.Arch{ArchType: android.X86_64}, android.NativeBridgeDisabled, "", ""}, | 
|  | } | 
|  |  | 
|  | for _, extraOsType := range extraOsTypes { | 
|  | switch extraOsType { | 
|  | case android.LinuxBionic: | 
|  | config.Targets[android.LinuxBionic] = []android.Target{ | 
|  | {android.LinuxBionic, android.Arch{ArchType: android.X86_64}, android.NativeBridgeDisabled, "", ""}, | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | ctx := android.NewTestArchContext() | 
|  |  | 
|  | // Enable androidmk support. | 
|  | // * Register the singleton | 
|  | // * Configure that we are inside make | 
|  | // * Add CommonOS to ensure that androidmk processing works. | 
|  | android.RegisterAndroidMkBuildComponents(ctx) | 
|  | android.SetInMakeForTests(config) | 
|  | config.Targets[android.CommonOS] = []android.Target{ | 
|  | {android.CommonOS, android.Arch{ArchType: android.Common}, android.NativeBridgeDisabled, "", ""}, | 
|  | } | 
|  |  | 
|  | // from android package | 
|  | android.RegisterPackageBuildComponents(ctx) | 
|  | ctx.PreArchMutators(android.RegisterVisibilityRuleChecker) | 
|  | ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators) | 
|  | ctx.PreArchMutators(android.RegisterComponentsMutator) | 
|  |  | 
|  | android.RegisterPrebuiltMutators(ctx) | 
|  |  | 
|  | // Register these after the prebuilt mutators have been registered to match what | 
|  | // happens at runtime. | 
|  | ctx.PreArchMutators(android.RegisterVisibilityRuleGatherer) | 
|  | ctx.PostDepsMutators(android.RegisterVisibilityRuleEnforcer) | 
|  |  | 
|  | // from java package | 
|  | java.RegisterJavaBuildComponents(ctx) | 
|  | java.RegisterAppBuildComponents(ctx) | 
|  | java.RegisterSdkLibraryBuildComponents(ctx) | 
|  | java.RegisterStubsBuildComponents(ctx) | 
|  | java.RegisterSystemModulesBuildComponents(ctx) | 
|  |  | 
|  | // from cc package | 
|  | cc.RegisterRequiredBuildComponentsForTest(ctx) | 
|  |  | 
|  | // from apex package | 
|  | ctx.RegisterModuleType("apex", apex.BundleFactory) | 
|  | ctx.RegisterModuleType("apex_key", apex.ApexKeyFactory) | 
|  | ctx.PostDepsMutators(apex.RegisterPostDepsMutators) | 
|  |  | 
|  | // from this package | 
|  | ctx.RegisterModuleType("sdk", SdkModuleFactory) | 
|  | ctx.RegisterModuleType("sdk_snapshot", SnapshotModuleFactory) | 
|  | ctx.RegisterModuleType("module_exports", ModuleExportsFactory) | 
|  | ctx.RegisterModuleType("module_exports_snapshot", ModuleExportsSnapshotsFactory) | 
|  | ctx.PreDepsMutators(RegisterPreDepsMutators) | 
|  | ctx.PostDepsMutators(RegisterPostDepsMutators) | 
|  |  | 
|  | ctx.Register(config) | 
|  |  | 
|  | return ctx, config | 
|  | } | 
|  |  | 
|  | func runTests(t *testing.T, ctx *android.TestContext, config android.Config) *testSdkResult { | 
|  | t.Helper() | 
|  | _, errs := ctx.ParseBlueprintsFiles(".") | 
|  | android.FailIfErrored(t, errs) | 
|  | _, errs = ctx.PrepareBuildActions(config) | 
|  | android.FailIfErrored(t, errs) | 
|  | return &testSdkResult{ | 
|  | TestHelper: TestHelper{t: t}, | 
|  | ctx:        ctx, | 
|  | config:     config, | 
|  | } | 
|  | } | 
|  |  | 
|  | func testSdkWithFs(t *testing.T, bp string, fs map[string][]byte) *testSdkResult { | 
|  | t.Helper() | 
|  | ctx, config := testSdkContext(bp, fs, nil) | 
|  | return runTests(t, ctx, config) | 
|  | } | 
|  |  | 
|  | func testSdkError(t *testing.T, pattern, bp string) { | 
|  | t.Helper() | 
|  | ctx, config := testSdkContext(bp, nil, nil) | 
|  | _, errs := ctx.ParseFileList(".", []string{"Android.bp"}) | 
|  | if len(errs) > 0 { | 
|  | android.FailIfNoMatchingErrors(t, pattern, errs) | 
|  | return | 
|  | } | 
|  | _, errs = ctx.PrepareBuildActions(config) | 
|  | if len(errs) > 0 { | 
|  | android.FailIfNoMatchingErrors(t, pattern, errs) | 
|  | return | 
|  | } | 
|  |  | 
|  | t.Fatalf("missing expected error %q (0 errors are returned)", pattern) | 
|  | } | 
|  |  | 
|  | func ensureListContains(t *testing.T, result []string, expected string) { | 
|  | t.Helper() | 
|  | if !android.InList(expected, result) { | 
|  | t.Errorf("%q is not found in %v", expected, result) | 
|  | } | 
|  | } | 
|  |  | 
|  | func pathsToStrings(paths android.Paths) []string { | 
|  | var ret []string | 
|  | for _, p := range paths { | 
|  | ret = append(ret, p.String()) | 
|  | } | 
|  | return ret | 
|  | } | 
|  |  | 
|  | // Provides general test support. | 
|  | type TestHelper struct { | 
|  | t *testing.T | 
|  | } | 
|  |  | 
|  | func (h *TestHelper) AssertStringEquals(message string, expected string, actual string) { | 
|  | h.t.Helper() | 
|  | if actual != expected { | 
|  | h.t.Errorf("%s: expected %s, actual %s", message, expected, actual) | 
|  | } | 
|  | } | 
|  |  | 
|  | func (h *TestHelper) AssertErrorMessageEquals(message string, expected string, actual error) { | 
|  | h.t.Helper() | 
|  | if actual == nil { | 
|  | h.t.Errorf("Expected error but was nil") | 
|  | } else if actual.Error() != expected { | 
|  | h.t.Errorf("%s: expected %s, actual %s", message, expected, actual.Error()) | 
|  | } | 
|  | } | 
|  |  | 
|  | func (h *TestHelper) AssertTrimmedStringEquals(message string, expected string, actual string) { | 
|  | h.t.Helper() | 
|  | h.AssertStringEquals(message, strings.TrimSpace(expected), strings.TrimSpace(actual)) | 
|  | } | 
|  |  | 
|  | func (h *TestHelper) AssertDeepEquals(message string, expected interface{}, actual interface{}) { | 
|  | h.t.Helper() | 
|  | if !reflect.DeepEqual(actual, expected) { | 
|  | h.t.Errorf("%s: expected %#v, actual %#v", message, expected, actual) | 
|  | } | 
|  | } | 
|  |  | 
|  | // Encapsulates result of processing an SDK definition. Provides support for | 
|  | // checking the state of the build structures. | 
|  | type testSdkResult struct { | 
|  | TestHelper | 
|  | ctx    *android.TestContext | 
|  | config android.Config | 
|  | } | 
|  |  | 
|  | // Analyse the sdk build rules to extract information about what it is doing. | 
|  |  | 
|  | // e.g. find the src/dest pairs from each cp command, the various zip files | 
|  | // generated, etc. | 
|  | func (r *testSdkResult) getSdkSnapshotBuildInfo(sdk *sdk) *snapshotBuildInfo { | 
|  | androidBpContents := sdk.GetAndroidBpContentsForTests() | 
|  |  | 
|  | info := &snapshotBuildInfo{ | 
|  | r:                 r, | 
|  | androidBpContents: androidBpContents, | 
|  | } | 
|  |  | 
|  | buildParams := sdk.BuildParamsForTests() | 
|  | copyRules := &strings.Builder{} | 
|  | otherCopyRules := &strings.Builder{} | 
|  | snapshotDirPrefix := sdk.builderForTests.snapshotDir.String() + "/" | 
|  | for _, bp := range buildParams { | 
|  | switch bp.Rule.String() { | 
|  | case android.Cp.String(): | 
|  | output := bp.Output | 
|  | // Get destination relative to the snapshot root | 
|  | dest := output.Rel() | 
|  | src := android.NormalizePathForTesting(bp.Input) | 
|  | // We differentiate between copy rules for the snapshot, and copy rules for the install file. | 
|  | if strings.HasPrefix(output.String(), snapshotDirPrefix) { | 
|  | // Get source relative to build directory. | 
|  | _, _ = fmt.Fprintf(copyRules, "%s -> %s\n", src, dest) | 
|  | info.snapshotContents = append(info.snapshotContents, dest) | 
|  | } else { | 
|  | _, _ = fmt.Fprintf(otherCopyRules, "%s -> %s\n", src, dest) | 
|  | } | 
|  |  | 
|  | case repackageZip.String(): | 
|  | // Add the destdir to the snapshot contents as that is effectively where | 
|  | // the content of the repackaged zip is copied. | 
|  | dest := bp.Args["destdir"] | 
|  | info.snapshotContents = append(info.snapshotContents, dest) | 
|  |  | 
|  | case zipFiles.String(): | 
|  | // This could be an intermediate zip file and not the actual output zip. | 
|  | // In that case this will be overridden when the rule to merge the zips | 
|  | // is processed. | 
|  | info.outputZip = android.NormalizePathForTesting(bp.Output) | 
|  |  | 
|  | case mergeZips.String(): | 
|  | // Copy the current outputZip to the intermediateZip. | 
|  | info.intermediateZip = info.outputZip | 
|  | mergeInput := android.NormalizePathForTesting(bp.Input) | 
|  | if info.intermediateZip != mergeInput { | 
|  | r.t.Errorf("Expected intermediate zip %s to be an input to merge zips but found %s instead", | 
|  | info.intermediateZip, mergeInput) | 
|  | } | 
|  |  | 
|  | // Override output zip (which was actually the intermediate zip file) with the actual | 
|  | // output zip. | 
|  | info.outputZip = android.NormalizePathForTesting(bp.Output) | 
|  |  | 
|  | // Save the zips to be merged into the intermediate zip. | 
|  | info.mergeZips = android.NormalizePathsForTesting(bp.Inputs) | 
|  | } | 
|  | } | 
|  |  | 
|  | info.copyRules = copyRules.String() | 
|  | info.otherCopyRules = otherCopyRules.String() | 
|  |  | 
|  | return info | 
|  | } | 
|  |  | 
|  | func (r *testSdkResult) Module(name string, variant string) android.Module { | 
|  | return r.ctx.ModuleForTests(name, variant).Module() | 
|  | } | 
|  |  | 
|  | func (r *testSdkResult) ModuleForTests(name string, variant string) android.TestingModule { | 
|  | return r.ctx.ModuleForTests(name, variant) | 
|  | } | 
|  |  | 
|  | // Check the snapshot build rules. | 
|  | // | 
|  | // Takes a list of functions which check different facets of the snapshot build rules. | 
|  | // Allows each test to customize what is checked without duplicating lots of code | 
|  | // or proliferating check methods of different flavors. | 
|  | func (r *testSdkResult) CheckSnapshot(name string, dir string, checkers ...snapshotBuildInfoChecker) { | 
|  | r.t.Helper() | 
|  |  | 
|  | // The sdk CommonOS variant is always responsible for generating the snapshot. | 
|  | variant := android.CommonOS.Name | 
|  |  | 
|  | sdk := r.Module(name, variant).(*sdk) | 
|  |  | 
|  | snapshotBuildInfo := r.getSdkSnapshotBuildInfo(sdk) | 
|  |  | 
|  | // Check state of the snapshot build. | 
|  | for _, checker := range checkers { | 
|  | checker(snapshotBuildInfo) | 
|  | } | 
|  |  | 
|  | // Make sure that the generated zip file is in the correct place. | 
|  | actual := snapshotBuildInfo.outputZip | 
|  | if dir != "" { | 
|  | dir = filepath.Clean(dir) + "/" | 
|  | } | 
|  | r.AssertStringEquals("Snapshot zip file in wrong place", | 
|  | fmt.Sprintf(".intermediates/%s%s/%s/%s-current.zip", dir, name, variant, name), actual) | 
|  |  | 
|  | // Populate a mock filesystem with the files that would have been copied by | 
|  | // the rules. | 
|  | fs := make(map[string][]byte) | 
|  | for _, dest := range snapshotBuildInfo.snapshotContents { | 
|  | fs[dest] = nil | 
|  | } | 
|  |  | 
|  | // Process the generated bp file to make sure it is valid. | 
|  | testSdkWithFs(r.t, snapshotBuildInfo.androidBpContents, fs) | 
|  | } | 
|  |  | 
|  | type snapshotBuildInfoChecker func(info *snapshotBuildInfo) | 
|  |  | 
|  | // Check that the snapshot's generated Android.bp is correct. | 
|  | // | 
|  | // Both the expected and actual string are both trimmed before comparing. | 
|  | func checkAndroidBpContents(expected string) snapshotBuildInfoChecker { | 
|  | return func(info *snapshotBuildInfo) { | 
|  | info.r.t.Helper() | 
|  | info.r.AssertTrimmedStringEquals("Android.bp contents do not match", expected, info.androidBpContents) | 
|  | } | 
|  | } | 
|  |  | 
|  | // Check that the snapshot's copy rules are correct. | 
|  | // | 
|  | // The copy rules are formatted as <src> -> <dest>, one per line and then compared | 
|  | // to the supplied expected string. Both the expected and actual string are trimmed | 
|  | // before comparing. | 
|  | func checkAllCopyRules(expected string) snapshotBuildInfoChecker { | 
|  | return func(info *snapshotBuildInfo) { | 
|  | info.r.t.Helper() | 
|  | info.r.AssertTrimmedStringEquals("Incorrect copy rules", expected, info.copyRules) | 
|  | } | 
|  | } | 
|  |  | 
|  | func checkAllOtherCopyRules(expected string) snapshotBuildInfoChecker { | 
|  | return func(info *snapshotBuildInfo) { | 
|  | info.r.t.Helper() | 
|  | info.r.AssertTrimmedStringEquals("Incorrect copy rules", expected, info.otherCopyRules) | 
|  | } | 
|  | } | 
|  |  | 
|  | // Check that the specified paths match the list of zips to merge with the intermediate zip. | 
|  | func checkMergeZips(expected ...string) snapshotBuildInfoChecker { | 
|  | return func(info *snapshotBuildInfo) { | 
|  | info.r.t.Helper() | 
|  | if info.intermediateZip == "" { | 
|  | info.r.t.Errorf("No intermediate zip file was created") | 
|  | } | 
|  |  | 
|  | info.r.AssertDeepEquals("mismatching merge zip files", expected, info.mergeZips) | 
|  | } | 
|  | } | 
|  |  | 
|  | // Encapsulates information about the snapshot build structure in order to insulate tests from | 
|  | // knowing too much about internal structures. | 
|  | // | 
|  | // All source/input paths are relative either the build directory. All dest/output paths are | 
|  | // relative to the snapshot root directory. | 
|  | type snapshotBuildInfo struct { | 
|  | r *testSdkResult | 
|  |  | 
|  | // The contents of the generated Android.bp file | 
|  | androidBpContents string | 
|  |  | 
|  | // The paths, relative to the snapshot root, of all files and directories copied into the | 
|  | // snapshot. | 
|  | snapshotContents []string | 
|  |  | 
|  | // A formatted representation of the src/dest pairs for a snapshot, one pair per line, | 
|  | // of the format src -> dest | 
|  | copyRules string | 
|  |  | 
|  | // A formatted representation of the src/dest pairs for files not in a snapshot, one pair | 
|  | // per line, of the format src -> dest | 
|  | otherCopyRules string | 
|  |  | 
|  | // The path to the intermediate zip, which is a zip created from the source files copied | 
|  | // into the snapshot directory and which will be merged with other zips to form the final output. | 
|  | // Is am empty string if there is no intermediate zip because there are no zips to merge in. | 
|  | intermediateZip string | 
|  |  | 
|  | // The paths to the zips to merge into the output zip, does not include the intermediate | 
|  | // zip. | 
|  | mergeZips []string | 
|  |  | 
|  | // The final output zip. | 
|  | outputZip string | 
|  | } | 
|  |  | 
|  | var buildDir string | 
|  |  | 
|  | func setUp() { | 
|  | var err error | 
|  | buildDir, err = ioutil.TempDir("", "soong_sdk_test") | 
|  | if err != nil { | 
|  | panic(err) | 
|  | } | 
|  | } | 
|  |  | 
|  | func tearDown() { | 
|  | _ = os.RemoveAll(buildDir) | 
|  | } | 
|  |  | 
|  | func runTestWithBuildDir(m *testing.M) { | 
|  | run := func() int { | 
|  | setUp() | 
|  | defer tearDown() | 
|  |  | 
|  | return m.Run() | 
|  | } | 
|  |  | 
|  | os.Exit(run()) | 
|  | } |