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 "path/filepath" 20 "strings" 21 "testing" 22 23 "android/soong/android" 24 "android/soong/apex" 25 "android/soong/cc" 26 "android/soong/genrule" 27 "android/soong/java" 28 29 "github.com/google/blueprint/proptools" 30) 31 32// Prepare for running an sdk test with an apex. 33var prepareForSdkTestWithApex = android.GroupFixturePreparers( 34 apex.PrepareForTestWithApexBuildComponents, 35 android.FixtureAddTextFile("sdk/tests/Android.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 `), 47 48 android.FixtureMergeMockFs(map[string][]byte{ 49 "apex_manifest.json": nil, 50 "system/sepolicy/apex/myapex-file_contexts": nil, 51 "system/sepolicy/apex/myapex2-file_contexts": nil, 52 "system/sepolicy/apex/mysdkapex-file_contexts": nil, 53 "sdk/tests/myapex.avbpubkey": nil, 54 "sdk/tests/myapex.pem": nil, 55 "sdk/tests/myapex.x509.pem": nil, 56 "sdk/tests/myapex.pk8": nil, 57 }), 58) 59 60// Legacy preparer used for running tests within the sdk package. 61// 62// This includes everything that was needed to run any test in the sdk package prior to the 63// introduction of the test fixtures. Tests that are being converted to use fixtures directly 64// rather than through the testSdkError() and testSdkWithFs() methods should avoid using this and 65// instead should use the various preparers directly using android.GroupFixturePreparers(...) to 66// group them when necessary. 67// 68// deprecated 69var prepareForSdkTest = android.GroupFixturePreparers( 70 cc.PrepareForTestWithCcDefaultModules, 71 genrule.PrepareForTestWithGenRuleBuildComponents, 72 java.PrepareForTestWithJavaBuildComponents, 73 PrepareForTestWithSdkBuildComponents, 74 75 prepareForSdkTestWithApex, 76 77 cc.PrepareForTestOnWindows, 78 android.FixtureModifyConfig(func(config android.Config) { 79 // Add windows as a default disable OS to test behavior when some OS variants 80 // are disabled. 81 config.Targets[android.Windows] = []android.Target{ 82 {android.Windows, android.Arch{ArchType: android.X86_64}, android.NativeBridgeDisabled, "", "", true}, 83 } 84 }), 85 86 // Add a build number file. 87 android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { 88 variables.BuildNumberFile = proptools.StringPtr(BUILD_NUMBER_FILE) 89 }), 90 91 // Make sure that every test provides all the source files. 92 android.PrepareForTestDisallowNonExistentPaths, 93 android.MockFS{ 94 "Test.java": nil, 95 }.AddToFixture(), 96) 97 98var PrepareForTestWithSdkBuildComponents = android.GroupFixturePreparers( 99 android.FixtureRegisterWithContext(registerModuleExportsBuildComponents), 100 android.FixtureRegisterWithContext(registerSdkBuildComponents), 101) 102 103func testSdkWithFs(t *testing.T, bp string, fs android.MockFS) *android.TestResult { 104 t.Helper() 105 return android.GroupFixturePreparers( 106 prepareForSdkTest, 107 fs.AddToFixture(), 108 ).RunTestWithBp(t, bp) 109} 110 111func testSdkError(t *testing.T, pattern, bp string) { 112 t.Helper() 113 prepareForSdkTest. 114 ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(pattern)). 115 RunTestWithBp(t, bp) 116} 117 118func ensureListContains(t *testing.T, result []string, expected string) { 119 t.Helper() 120 if !android.InList(expected, result) { 121 t.Errorf("%q is not found in %v", expected, result) 122 } 123} 124 125// Analyse the sdk build rules to extract information about what it is doing. 126// 127// e.g. find the src/dest pairs from each cp command, the various zip files 128// generated, etc. 129func getSdkSnapshotBuildInfo(t *testing.T, result *android.TestResult, sdk *sdk) *snapshotBuildInfo { 130 info := &snapshotBuildInfo{ 131 t: t, 132 r: result, 133 androidBpContents: sdk.GetAndroidBpContentsForTests(), 134 infoContents: sdk.GetInfoContentsForTests(), 135 targetBuildRelease: sdk.builderForTests.targetBuildRelease, 136 } 137 138 buildParams := sdk.BuildParamsForTests() 139 copyRules := &strings.Builder{} 140 otherCopyRules := &strings.Builder{} 141 snapshotDirPrefix := sdk.builderForTests.snapshotDir.String() + "/" 142 143 seenBuildNumberFile := false 144 for _, bp := range buildParams { 145 switch bp.Rule.String() { 146 case android.Cp.String(), android.CpWithBash.String(): 147 output := bp.Output 148 // Get destination relative to the snapshot root 149 dest := output.Rel() 150 src := android.NormalizePathForTesting(bp.Input) 151 // We differentiate between copy rules for the snapshot, and copy rules for the install file. 152 if strings.HasPrefix(output.String(), snapshotDirPrefix) { 153 // Don't include the build-number.txt file in the copy rules as that would break lots of 154 // tests, just verify that it is copied here as it should appear in every snapshot. 155 if output.Base() == BUILD_NUMBER_FILE { 156 seenBuildNumberFile = true 157 } else { 158 // Get source relative to build directory. 159 _, _ = fmt.Fprintf(copyRules, "%s -> %s\n", src, dest) 160 } 161 info.snapshotContents = append(info.snapshotContents, dest) 162 } else { 163 _, _ = fmt.Fprintf(otherCopyRules, "%s -> %s\n", src, dest) 164 } 165 166 case repackageZip.String(): 167 // Add the destdir to the snapshot contents as that is effectively where 168 // the content of the repackaged zip is copied. 169 dest := bp.Args["destdir"] 170 info.snapshotContents = append(info.snapshotContents, dest) 171 172 case zipFiles.String(): 173 // This could be an intermediate zip file and not the actual output zip. 174 // In that case this will be overridden when the rule to merge the zips 175 // is processed. 176 info.outputZip = android.NormalizePathForTesting(bp.Output) 177 178 case mergeZips.String(): 179 // Copy the current outputZip to the intermediateZip. 180 info.intermediateZip = info.outputZip 181 mergeInput := android.NormalizePathForTesting(bp.Input) 182 if info.intermediateZip != mergeInput { 183 t.Errorf("Expected intermediate zip %s to be an input to merge zips but found %s instead", 184 info.intermediateZip, mergeInput) 185 } 186 187 // Override output zip (which was actually the intermediate zip file) with the actual 188 // output zip. 189 info.outputZip = android.NormalizePathForTesting(bp.Output) 190 191 // Save the zips to be merged into the intermediate zip. 192 info.mergeZips = android.NormalizePathsForTesting(bp.Inputs) 193 } 194 } 195 196 if !seenBuildNumberFile { 197 panic(fmt.Sprintf("Every snapshot must include the %s file", BUILD_NUMBER_FILE)) 198 } 199 200 info.copyRules = copyRules.String() 201 info.otherCopyRules = otherCopyRules.String() 202 203 return info 204} 205 206// The enum of different sdk snapshot tests performed by CheckSnapshot. 207type snapshotTest int 208 209const ( 210 // The enumeration of the different test configurations. 211 // A test with the snapshot/Android.bp file but without the original Android.bp file. 212 checkSnapshotWithoutSource snapshotTest = iota 213 214 // A test with both the original source and the snapshot, with the source preferred. 215 checkSnapshotWithSourcePreferred 216 217 // A test with both the original source and the snapshot, with the snapshot preferred. 218 checkSnapshotPreferredWithSource 219 220 // The directory into which the snapshot will be 'unpacked'. 221 snapshotSubDir = "snapshot" 222) 223 224// Check the snapshot build rules. 225// 226// Takes a list of functions which check different facets of the snapshot build rules. 227// Allows each test to customize what is checked without duplicating lots of code 228// or proliferating check methods of different flavors. 229func CheckSnapshot(t *testing.T, result *android.TestResult, name string, dir string, checkers ...snapshotBuildInfoChecker) { 230 t.Helper() 231 232 // The sdk CommonOS variant is always responsible for generating the snapshot. 233 variant := android.CommonOS.Name 234 235 sdk := result.Module(name, variant).(*sdk) 236 237 snapshotBuildInfo := getSdkSnapshotBuildInfo(t, result, sdk) 238 239 // Check state of the snapshot build. 240 for _, checker := range checkers { 241 checker(snapshotBuildInfo) 242 } 243 244 // Make sure that the generated zip file is in the correct place. 245 actual := snapshotBuildInfo.outputZip 246 if dir != "" { 247 dir = filepath.Clean(dir) + "/" 248 } 249 suffix := "-" + soongSdkSnapshotVersionCurrent 250 251 expectedZipPath := fmt.Sprintf(".intermediates/%s%s/%s/%s%s.zip", dir, name, variant, name, suffix) 252 android.AssertStringEquals(t, "Snapshot zip file in wrong place", expectedZipPath, actual) 253 254 // Populate a mock filesystem with the files that would have been copied by 255 // the rules. 256 fs := android.MockFS{} 257 for _, dest := range snapshotBuildInfo.snapshotContents { 258 fs[filepath.Join(snapshotSubDir, dest)] = nil 259 } 260 fs[filepath.Join(snapshotSubDir, "Android.bp")] = []byte(snapshotBuildInfo.androidBpContents) 261 262 // If the generated snapshot builders not for the current release then it cannot be loaded by 263 // the current release. 264 if snapshotBuildInfo.targetBuildRelease != buildReleaseCurrent { 265 return 266 } 267 268 // The preparers from the original source fixture. 269 sourcePreparers := result.Preparer() 270 271 // Preparer to combine the snapshot and the source. 272 snapshotPreparer := android.GroupFixturePreparers(sourcePreparers, fs.AddToFixture()) 273 274 var runSnapshotTestWithCheckers = func(t *testing.T, testConfig snapshotTest, extraPreparer android.FixturePreparer) { 275 t.Helper() 276 customization := snapshotBuildInfo.snapshotTestCustomization(testConfig) 277 customizedPreparers := android.GroupFixturePreparers(customization.preparers...) 278 279 // TODO(b/183184375): Set Config.TestAllowNonExistentPaths = false to verify that all the 280 // files the snapshot needs are actually copied into the snapshot. 281 282 // Run the snapshot with the snapshot preparer and the extra preparer, which must come after as 283 // it may need to modify parts of the MockFS populated by the snapshot preparer. 284 result := android.GroupFixturePreparers(snapshotPreparer, customizedPreparers, extraPreparer). 285 ExtendWithErrorHandler(customization.errorHandler). 286 RunTest(t) 287 288 // Perform any additional checks the test need on the result of processing the snapshot. 289 for _, checker := range customization.checkers { 290 checker(t, result) 291 } 292 } 293 294 t.Run("snapshot without source", func(t *testing.T) { 295 t.Parallel() 296 // Remove the source Android.bp file to make sure it works without. 297 removeSourceAndroidBp := android.FixtureModifyMockFS(func(fs android.MockFS) { 298 delete(fs, "Android.bp") 299 }) 300 301 runSnapshotTestWithCheckers(t, checkSnapshotWithoutSource, removeSourceAndroidBp) 302 }) 303 304 t.Run("snapshot with source preferred", func(t *testing.T) { 305 t.Parallel() 306 runSnapshotTestWithCheckers(t, checkSnapshotWithSourcePreferred, android.NullFixturePreparer) 307 }) 308 309 t.Run("snapshot preferred with source", func(t *testing.T) { 310 t.Parallel() 311 // Replace the snapshot/Android.bp file with one where "prefer: false," has been replaced with 312 // "prefer: true," 313 preferPrebuilts := android.FixtureModifyMockFS(func(fs android.MockFS) { 314 snapshotBpFile := filepath.Join(snapshotSubDir, "Android.bp") 315 unpreferred := string(fs[snapshotBpFile]) 316 fs[snapshotBpFile] = []byte(strings.ReplaceAll(unpreferred, "prefer: false,", "prefer: true,")) 317 318 prebuiltApexBpFile := "prebuilts/apex/Android.bp" 319 if prebuiltApexBp, ok := fs[prebuiltApexBpFile]; ok { 320 fs[prebuiltApexBpFile] = []byte(strings.ReplaceAll(string(prebuiltApexBp), "prefer: false,", "prefer: true,")) 321 } 322 }) 323 324 runSnapshotTestWithCheckers(t, checkSnapshotPreferredWithSource, preferPrebuilts) 325 }) 326} 327 328type snapshotBuildInfoChecker func(info *snapshotBuildInfo) 329 330// Check that the snapshot's generated Android.bp is correct. 331// 332// Both the expected and actual string are both trimmed before comparing. 333func checkAndroidBpContents(expected string) snapshotBuildInfoChecker { 334 return func(info *snapshotBuildInfo) { 335 info.t.Helper() 336 android.AssertTrimmedStringEquals(info.t, "Android.bp contents do not match", expected, info.androidBpContents) 337 } 338} 339 340// Check that the snapshot's copy rules are correct. 341// 342// The copy rules are formatted as <src> -> <dest>, one per line and then compared 343// to the supplied expected string. Both the expected and actual string are trimmed 344// before comparing. 345func checkAllCopyRules(expected string) snapshotBuildInfoChecker { 346 return func(info *snapshotBuildInfo) { 347 info.t.Helper() 348 android.AssertTrimmedStringEquals(info.t, "Incorrect copy rules", expected, info.copyRules) 349 } 350} 351 352func checkAllOtherCopyRules(expected string) snapshotBuildInfoChecker { 353 return func(info *snapshotBuildInfo) { 354 info.t.Helper() 355 android.AssertTrimmedStringEquals(info.t, "Incorrect copy rules", expected, info.otherCopyRules) 356 } 357} 358 359// Check that the specified paths match the list of zips to merge with the intermediate zip. 360func checkMergeZips(expected ...string) snapshotBuildInfoChecker { 361 return func(info *snapshotBuildInfo) { 362 info.t.Helper() 363 if info.intermediateZip == "" { 364 info.t.Errorf("No intermediate zip file was created") 365 } 366 367 android.AssertDeepEquals(info.t, "mismatching merge zip files", expected, info.mergeZips) 368 } 369} 370 371// Check that the snapshot's info contents are ciorrect. 372// 373// Both the expected and actual string are both trimmed before comparing. 374func checkInfoContents(config android.Config, expected string) snapshotBuildInfoChecker { 375 return func(info *snapshotBuildInfo) { 376 info.t.Helper() 377 android.AssertTrimmedStringEquals(info.t, "info contents do not match", 378 expected, android.StringRelativeToTop(config, info.infoContents)) 379 } 380} 381 382type resultChecker func(t *testing.T, result *android.TestResult) 383 384// snapshotTestPreparer registers a preparer that will be used to customize the specified 385// snapshotTest. 386func snapshotTestPreparer(snapshotTest snapshotTest, preparer android.FixturePreparer) snapshotBuildInfoChecker { 387 return func(info *snapshotBuildInfo) { 388 customization := info.snapshotTestCustomization(snapshotTest) 389 customization.preparers = append(customization.preparers, preparer) 390 } 391} 392 393// snapshotTestChecker registers a checker that will be run against the result of processing the 394// generated snapshot for the specified snapshotTest. 395func snapshotTestChecker(snapshotTest snapshotTest, checker resultChecker) snapshotBuildInfoChecker { 396 return func(info *snapshotBuildInfo) { 397 customization := info.snapshotTestCustomization(snapshotTest) 398 customization.checkers = append(customization.checkers, checker) 399 } 400} 401 402// snapshotTestErrorHandler registers an error handler to use when processing the snapshot 403// in the specific test case. 404// 405// Generally, the snapshot should work with all the test cases but some do not and just in case 406// there are a lot of issues to resolve, or it will take a lot of time this is a 407// get-out-of-jail-free card that allows progress to be made. 408// 409// deprecated: should only be used as a temporary workaround with an attached to do and bug. 410func snapshotTestErrorHandler(snapshotTest snapshotTest, handler android.FixtureErrorHandler) snapshotBuildInfoChecker { 411 return func(info *snapshotBuildInfo) { 412 customization := info.snapshotTestCustomization(snapshotTest) 413 customization.errorHandler = handler 414 } 415} 416 417// Encapsulates information provided by each test to customize a specific snapshotTest. 418type snapshotTestCustomization struct { 419 // Preparers that are used to customize the test fixture before running the test. 420 preparers []android.FixturePreparer 421 422 // Checkers that are run on the result of processing the preferred snapshot in a specific test 423 // case. 424 checkers []resultChecker 425 426 // Specify an error handler for when processing a specific test case. 427 // 428 // In some cases the generated snapshot cannot be used in a test configuration. Those cases are 429 // invariably bugs that need to be resolved but sometimes that can take a while. This provides a 430 // mechanism to temporarily ignore that error. 431 errorHandler android.FixtureErrorHandler 432} 433 434// Encapsulates information about the snapshot build structure in order to insulate tests from 435// knowing too much about internal structures. 436// 437// All source/input paths are relative either the build directory. All dest/output paths are 438// relative to the snapshot root directory. 439type snapshotBuildInfo struct { 440 t *testing.T 441 442 // The result from RunTest() 443 r *android.TestResult 444 445 // The contents of the generated Android.bp file 446 androidBpContents string 447 448 // The contents of the info file. 449 infoContents string 450 451 // The paths, relative to the snapshot root, of all files and directories copied into the 452 // snapshot. 453 snapshotContents []string 454 455 // A formatted representation of the src/dest pairs for a snapshot, one pair per line, 456 // of the format src -> dest 457 copyRules string 458 459 // A formatted representation of the src/dest pairs for files not in a snapshot, one pair 460 // per line, of the format src -> dest 461 otherCopyRules string 462 463 // The path to the intermediate zip, which is a zip created from the source files copied 464 // into the snapshot directory and which will be merged with other zips to form the final output. 465 // Is am empty string if there is no intermediate zip because there are no zips to merge in. 466 intermediateZip string 467 468 // The paths to the zips to merge into the output zip, does not include the intermediate 469 // zip. 470 mergeZips []string 471 472 // The final output zip. 473 outputZip string 474 475 // The target build release. 476 targetBuildRelease *buildRelease 477 478 // The test specific customizations for each snapshot test. 479 snapshotTestCustomizations snapshotTestCustomizationSet 480} 481 482type snapshotTestCustomizationSet struct { 483 snapshotWithoutSource *snapshotTestCustomization 484 snapshotWithSourcePreferred *snapshotTestCustomization 485 snapshotPreferredWithSource *snapshotTestCustomization 486} 487 488func (s *snapshotTestCustomizationSet) customization(snapshotTest snapshotTest) **snapshotTestCustomization { 489 var customization **snapshotTestCustomization 490 switch snapshotTest { 491 case checkSnapshotWithoutSource: 492 493 customization = &s.snapshotWithoutSource 494 case checkSnapshotWithSourcePreferred: 495 customization = &s.snapshotWithSourcePreferred 496 case checkSnapshotPreferredWithSource: 497 customization = &s.snapshotPreferredWithSource 498 default: 499 panic(fmt.Errorf("unsupported snapshotTest %v", snapshotTest)) 500 } 501 return customization 502} 503 504// snapshotTestCustomization gets the test specific customization for the specified snapshotTest. 505// 506// If no customization was created previously then it creates a default customization. 507func (i *snapshotBuildInfo) snapshotTestCustomization(snapshotTest snapshotTest) *snapshotTestCustomization { 508 customization := i.snapshotTestCustomizations.customization(snapshotTest) 509 if *customization == nil { 510 *customization = &snapshotTestCustomization{ 511 errorHandler: android.FixtureExpectsNoErrors, 512 } 513 } 514 return *customization 515} 516