1// Copyright 2019 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package sdk 16 17import ( 18 "fmt" 19 "io/ioutil" 20 "os" 21 "path/filepath" 22 "reflect" 23 "strings" 24 "testing" 25 26 "android/soong/android" 27 "android/soong/apex" 28 "android/soong/cc" 29 "android/soong/java" 30) 31 32func testSdkContext(bp string, fs map[string][]byte, extraOsTypes []android.OsType) (*android.TestContext, android.Config) { 33 extraOsTypes = append(extraOsTypes, android.Android, android.Windows) 34 35 bp = bp + ` 36 apex_key { 37 name: "myapex.key", 38 public_key: "myapex.avbpubkey", 39 private_key: "myapex.pem", 40 } 41 42 android_app_certificate { 43 name: "myapex.cert", 44 certificate: "myapex", 45 } 46 ` + cc.GatherRequiredDepsForTest(extraOsTypes...) 47 48 mockFS := map[string][]byte{ 49 "build/make/target/product/security": nil, 50 "apex_manifest.json": nil, 51 "system/sepolicy/apex/myapex-file_contexts": nil, 52 "system/sepolicy/apex/myapex2-file_contexts": nil, 53 "system/sepolicy/apex/mysdkapex-file_contexts": nil, 54 "myapex.avbpubkey": nil, 55 "myapex.pem": nil, 56 "myapex.x509.pem": nil, 57 "myapex.pk8": nil, 58 } 59 60 cc.GatherRequiredFilesForTest(mockFS) 61 62 for k, v := range fs { 63 mockFS[k] = v 64 } 65 66 config := android.TestArchConfig(buildDir, nil, bp, mockFS) 67 68 // Add windows as a default disable OS to test behavior when some OS variants 69 // are disabled. 70 config.Targets[android.Windows] = []android.Target{ 71 {android.Windows, android.Arch{ArchType: android.X86_64}, android.NativeBridgeDisabled, "", ""}, 72 } 73 74 for _, extraOsType := range extraOsTypes { 75 switch extraOsType { 76 case android.LinuxBionic: 77 config.Targets[android.LinuxBionic] = []android.Target{ 78 {android.LinuxBionic, android.Arch{ArchType: android.X86_64}, android.NativeBridgeDisabled, "", ""}, 79 } 80 } 81 } 82 83 ctx := android.NewTestArchContext() 84 85 // Enable androidmk support. 86 // * Register the singleton 87 // * Configure that we are inside make 88 // * Add CommonOS to ensure that androidmk processing works. 89 android.RegisterAndroidMkBuildComponents(ctx) 90 android.SetInMakeForTests(config) 91 config.Targets[android.CommonOS] = []android.Target{ 92 {android.CommonOS, android.Arch{ArchType: android.Common}, android.NativeBridgeDisabled, "", ""}, 93 } 94 95 // from android package 96 android.RegisterPackageBuildComponents(ctx) 97 ctx.PreArchMutators(android.RegisterVisibilityRuleChecker) 98 ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators) 99 ctx.PreArchMutators(android.RegisterComponentsMutator) 100 101 android.RegisterPrebuiltMutators(ctx) 102 103 // Register these after the prebuilt mutators have been registered to match what 104 // happens at runtime. 105 ctx.PreArchMutators(android.RegisterVisibilityRuleGatherer) 106 ctx.PostDepsMutators(android.RegisterVisibilityRuleEnforcer) 107 108 // from java package 109 java.RegisterJavaBuildComponents(ctx) 110 java.RegisterAppBuildComponents(ctx) 111 java.RegisterSdkLibraryBuildComponents(ctx) 112 java.RegisterStubsBuildComponents(ctx) 113 java.RegisterSystemModulesBuildComponents(ctx) 114 115 // from cc package 116 cc.RegisterRequiredBuildComponentsForTest(ctx) 117 118 // from apex package 119 ctx.RegisterModuleType("apex", apex.BundleFactory) 120 ctx.RegisterModuleType("apex_key", apex.ApexKeyFactory) 121 ctx.PostDepsMutators(apex.RegisterPostDepsMutators) 122 123 // from this package 124 ctx.RegisterModuleType("sdk", SdkModuleFactory) 125 ctx.RegisterModuleType("sdk_snapshot", SnapshotModuleFactory) 126 ctx.RegisterModuleType("module_exports", ModuleExportsFactory) 127 ctx.RegisterModuleType("module_exports_snapshot", ModuleExportsSnapshotsFactory) 128 ctx.PreDepsMutators(RegisterPreDepsMutators) 129 ctx.PostDepsMutators(RegisterPostDepsMutators) 130 131 ctx.Register(config) 132 133 return ctx, config 134} 135 136func runTests(t *testing.T, ctx *android.TestContext, config android.Config) *testSdkResult { 137 t.Helper() 138 _, errs := ctx.ParseBlueprintsFiles(".") 139 android.FailIfErrored(t, errs) 140 _, errs = ctx.PrepareBuildActions(config) 141 android.FailIfErrored(t, errs) 142 return &testSdkResult{ 143 TestHelper: TestHelper{t: t}, 144 ctx: ctx, 145 config: config, 146 } 147} 148 149func testSdkWithFs(t *testing.T, bp string, fs map[string][]byte) *testSdkResult { 150 t.Helper() 151 ctx, config := testSdkContext(bp, fs, nil) 152 return runTests(t, ctx, config) 153} 154 155func testSdkError(t *testing.T, pattern, bp string) { 156 t.Helper() 157 ctx, config := testSdkContext(bp, nil, nil) 158 _, errs := ctx.ParseFileList(".", []string{"Android.bp"}) 159 if len(errs) > 0 { 160 android.FailIfNoMatchingErrors(t, pattern, errs) 161 return 162 } 163 _, errs = ctx.PrepareBuildActions(config) 164 if len(errs) > 0 { 165 android.FailIfNoMatchingErrors(t, pattern, errs) 166 return 167 } 168 169 t.Fatalf("missing expected error %q (0 errors are returned)", pattern) 170} 171 172func ensureListContains(t *testing.T, result []string, expected string) { 173 t.Helper() 174 if !android.InList(expected, result) { 175 t.Errorf("%q is not found in %v", expected, result) 176 } 177} 178 179func pathsToStrings(paths android.Paths) []string { 180 var ret []string 181 for _, p := range paths { 182 ret = append(ret, p.String()) 183 } 184 return ret 185} 186 187// Provides general test support. 188type TestHelper struct { 189 t *testing.T 190} 191 192func (h *TestHelper) AssertStringEquals(message string, expected string, actual string) { 193 h.t.Helper() 194 if actual != expected { 195 h.t.Errorf("%s: expected %s, actual %s", message, expected, actual) 196 } 197} 198 199func (h *TestHelper) AssertErrorMessageEquals(message string, expected string, actual error) { 200 h.t.Helper() 201 if actual == nil { 202 h.t.Errorf("Expected error but was nil") 203 } else if actual.Error() != expected { 204 h.t.Errorf("%s: expected %s, actual %s", message, expected, actual.Error()) 205 } 206} 207 208func (h *TestHelper) AssertTrimmedStringEquals(message string, expected string, actual string) { 209 h.t.Helper() 210 h.AssertStringEquals(message, strings.TrimSpace(expected), strings.TrimSpace(actual)) 211} 212 213func (h *TestHelper) AssertDeepEquals(message string, expected interface{}, actual interface{}) { 214 h.t.Helper() 215 if !reflect.DeepEqual(actual, expected) { 216 h.t.Errorf("%s: expected %#v, actual %#v", message, expected, actual) 217 } 218} 219 220// Encapsulates result of processing an SDK definition. Provides support for 221// checking the state of the build structures. 222type testSdkResult struct { 223 TestHelper 224 ctx *android.TestContext 225 config android.Config 226} 227 228// Analyse the sdk build rules to extract information about what it is doing. 229 230// e.g. find the src/dest pairs from each cp command, the various zip files 231// generated, etc. 232func (r *testSdkResult) getSdkSnapshotBuildInfo(sdk *sdk) *snapshotBuildInfo { 233 androidBpContents := sdk.GetAndroidBpContentsForTests() 234 235 info := &snapshotBuildInfo{ 236 r: r, 237 androidBpContents: androidBpContents, 238 } 239 240 buildParams := sdk.BuildParamsForTests() 241 copyRules := &strings.Builder{} 242 otherCopyRules := &strings.Builder{} 243 snapshotDirPrefix := sdk.builderForTests.snapshotDir.String() + "/" 244 for _, bp := range buildParams { 245 switch bp.Rule.String() { 246 case android.Cp.String(): 247 output := bp.Output 248 // Get destination relative to the snapshot root 249 dest := output.Rel() 250 src := android.NormalizePathForTesting(bp.Input) 251 // We differentiate between copy rules for the snapshot, and copy rules for the install file. 252 if strings.HasPrefix(output.String(), snapshotDirPrefix) { 253 // Get source relative to build directory. 254 _, _ = fmt.Fprintf(copyRules, "%s -> %s\n", src, dest) 255 info.snapshotContents = append(info.snapshotContents, dest) 256 } else { 257 _, _ = fmt.Fprintf(otherCopyRules, "%s -> %s\n", src, dest) 258 } 259 260 case repackageZip.String(): 261 // Add the destdir to the snapshot contents as that is effectively where 262 // the content of the repackaged zip is copied. 263 dest := bp.Args["destdir"] 264 info.snapshotContents = append(info.snapshotContents, dest) 265 266 case zipFiles.String(): 267 // This could be an intermediate zip file and not the actual output zip. 268 // In that case this will be overridden when the rule to merge the zips 269 // is processed. 270 info.outputZip = android.NormalizePathForTesting(bp.Output) 271 272 case mergeZips.String(): 273 // Copy the current outputZip to the intermediateZip. 274 info.intermediateZip = info.outputZip 275 mergeInput := android.NormalizePathForTesting(bp.Input) 276 if info.intermediateZip != mergeInput { 277 r.t.Errorf("Expected intermediate zip %s to be an input to merge zips but found %s instead", 278 info.intermediateZip, mergeInput) 279 } 280 281 // Override output zip (which was actually the intermediate zip file) with the actual 282 // output zip. 283 info.outputZip = android.NormalizePathForTesting(bp.Output) 284 285 // Save the zips to be merged into the intermediate zip. 286 info.mergeZips = android.NormalizePathsForTesting(bp.Inputs) 287 } 288 } 289 290 info.copyRules = copyRules.String() 291 info.otherCopyRules = otherCopyRules.String() 292 293 return info 294} 295 296func (r *testSdkResult) Module(name string, variant string) android.Module { 297 return r.ctx.ModuleForTests(name, variant).Module() 298} 299 300func (r *testSdkResult) ModuleForTests(name string, variant string) android.TestingModule { 301 return r.ctx.ModuleForTests(name, variant) 302} 303 304// Check the snapshot build rules. 305// 306// Takes a list of functions which check different facets of the snapshot build rules. 307// Allows each test to customize what is checked without duplicating lots of code 308// or proliferating check methods of different flavors. 309func (r *testSdkResult) CheckSnapshot(name string, dir string, checkers ...snapshotBuildInfoChecker) { 310 r.t.Helper() 311 312 // The sdk CommonOS variant is always responsible for generating the snapshot. 313 variant := android.CommonOS.Name 314 315 sdk := r.Module(name, variant).(*sdk) 316 317 snapshotBuildInfo := r.getSdkSnapshotBuildInfo(sdk) 318 319 // Check state of the snapshot build. 320 for _, checker := range checkers { 321 checker(snapshotBuildInfo) 322 } 323 324 // Make sure that the generated zip file is in the correct place. 325 actual := snapshotBuildInfo.outputZip 326 if dir != "" { 327 dir = filepath.Clean(dir) + "/" 328 } 329 r.AssertStringEquals("Snapshot zip file in wrong place", 330 fmt.Sprintf(".intermediates/%s%s/%s/%s-current.zip", dir, name, variant, name), actual) 331 332 // Populate a mock filesystem with the files that would have been copied by 333 // the rules. 334 fs := make(map[string][]byte) 335 for _, dest := range snapshotBuildInfo.snapshotContents { 336 fs[dest] = nil 337 } 338 339 // Process the generated bp file to make sure it is valid. 340 testSdkWithFs(r.t, snapshotBuildInfo.androidBpContents, fs) 341} 342 343type snapshotBuildInfoChecker func(info *snapshotBuildInfo) 344 345// Check that the snapshot's generated Android.bp is correct. 346// 347// Both the expected and actual string are both trimmed before comparing. 348func checkAndroidBpContents(expected string) snapshotBuildInfoChecker { 349 return func(info *snapshotBuildInfo) { 350 info.r.t.Helper() 351 info.r.AssertTrimmedStringEquals("Android.bp contents do not match", expected, info.androidBpContents) 352 } 353} 354 355// Check that the snapshot's copy rules are correct. 356// 357// The copy rules are formatted as <src> -> <dest>, one per line and then compared 358// to the supplied expected string. Both the expected and actual string are trimmed 359// before comparing. 360func checkAllCopyRules(expected string) snapshotBuildInfoChecker { 361 return func(info *snapshotBuildInfo) { 362 info.r.t.Helper() 363 info.r.AssertTrimmedStringEquals("Incorrect copy rules", expected, info.copyRules) 364 } 365} 366 367func checkAllOtherCopyRules(expected string) snapshotBuildInfoChecker { 368 return func(info *snapshotBuildInfo) { 369 info.r.t.Helper() 370 info.r.AssertTrimmedStringEquals("Incorrect copy rules", expected, info.otherCopyRules) 371 } 372} 373 374// Check that the specified paths match the list of zips to merge with the intermediate zip. 375func checkMergeZips(expected ...string) snapshotBuildInfoChecker { 376 return func(info *snapshotBuildInfo) { 377 info.r.t.Helper() 378 if info.intermediateZip == "" { 379 info.r.t.Errorf("No intermediate zip file was created") 380 } 381 382 info.r.AssertDeepEquals("mismatching merge zip files", expected, info.mergeZips) 383 } 384} 385 386// Encapsulates information about the snapshot build structure in order to insulate tests from 387// knowing too much about internal structures. 388// 389// All source/input paths are relative either the build directory. All dest/output paths are 390// relative to the snapshot root directory. 391type snapshotBuildInfo struct { 392 r *testSdkResult 393 394 // The contents of the generated Android.bp file 395 androidBpContents string 396 397 // The paths, relative to the snapshot root, of all files and directories copied into the 398 // snapshot. 399 snapshotContents []string 400 401 // A formatted representation of the src/dest pairs for a snapshot, one pair per line, 402 // of the format src -> dest 403 copyRules string 404 405 // A formatted representation of the src/dest pairs for files not in a snapshot, one pair 406 // per line, of the format src -> dest 407 otherCopyRules string 408 409 // The path to the intermediate zip, which is a zip created from the source files copied 410 // into the snapshot directory and which will be merged with other zips to form the final output. 411 // Is am empty string if there is no intermediate zip because there are no zips to merge in. 412 intermediateZip string 413 414 // The paths to the zips to merge into the output zip, does not include the intermediate 415 // zip. 416 mergeZips []string 417 418 // The final output zip. 419 outputZip string 420} 421 422var buildDir string 423 424func setUp() { 425 var err error 426 buildDir, err = ioutil.TempDir("", "soong_sdk_test") 427 if err != nil { 428 panic(err) 429 } 430} 431 432func tearDown() { 433 _ = os.RemoveAll(buildDir) 434} 435 436func runTestWithBuildDir(m *testing.M) { 437 run := func() int { 438 setUp() 439 defer tearDown() 440 441 return m.Run() 442 } 443 444 os.Exit(run()) 445} 446