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 125func pathsToStrings(paths android.Paths) []string { 126 var ret []string 127 for _, p := range paths { 128 ret = append(ret, p.String()) 129 } 130 return ret 131} 132 133// Analyse the sdk build rules to extract information about what it is doing. 134// 135// e.g. find the src/dest pairs from each cp command, the various zip files 136// generated, etc. 137func getSdkSnapshotBuildInfo(t *testing.T, result *android.TestResult, sdk *sdk) *snapshotBuildInfo { 138 info := &snapshotBuildInfo{ 139 t: t, 140 r: result, 141 version: sdk.builderForTests.version, 142 androidBpContents: sdk.GetAndroidBpContentsForTests(), 143 androidUnversionedBpContents: sdk.GetUnversionedAndroidBpContentsForTests(), 144 androidVersionedBpContents: sdk.GetVersionedAndroidBpContentsForTests(), 145 snapshotTestCustomizations: map[snapshotTest]*snapshotTestCustomization{}, 146 targetBuildRelease: sdk.builderForTests.targetBuildRelease, 147 } 148 149 buildParams := sdk.BuildParamsForTests() 150 copyRules := &strings.Builder{} 151 otherCopyRules := &strings.Builder{} 152 snapshotDirPrefix := sdk.builderForTests.snapshotDir.String() + "/" 153 154 seenBuildNumberFile := false 155 for _, bp := range buildParams { 156 switch bp.Rule.String() { 157 case android.Cp.String(): 158 output := bp.Output 159 // Get destination relative to the snapshot root 160 dest := output.Rel() 161 src := android.NormalizePathForTesting(bp.Input) 162 // We differentiate between copy rules for the snapshot, and copy rules for the install file. 163 if strings.HasPrefix(output.String(), snapshotDirPrefix) { 164 // Don't include the build-number.txt file in the copy rules as that would break lots of 165 // tests, just verify that it is copied here as it should appear in every snapshot. 166 if output.Base() == BUILD_NUMBER_FILE { 167 seenBuildNumberFile = true 168 } else { 169 // Get source relative to build directory. 170 _, _ = fmt.Fprintf(copyRules, "%s -> %s\n", src, dest) 171 } 172 info.snapshotContents = append(info.snapshotContents, dest) 173 } else { 174 _, _ = fmt.Fprintf(otherCopyRules, "%s -> %s\n", src, dest) 175 } 176 177 case repackageZip.String(): 178 // Add the destdir to the snapshot contents as that is effectively where 179 // the content of the repackaged zip is copied. 180 dest := bp.Args["destdir"] 181 info.snapshotContents = append(info.snapshotContents, dest) 182 183 case zipFiles.String(): 184 // This could be an intermediate zip file and not the actual output zip. 185 // In that case this will be overridden when the rule to merge the zips 186 // is processed. 187 info.outputZip = android.NormalizePathForTesting(bp.Output) 188 189 case mergeZips.String(): 190 // Copy the current outputZip to the intermediateZip. 191 info.intermediateZip = info.outputZip 192 mergeInput := android.NormalizePathForTesting(bp.Input) 193 if info.intermediateZip != mergeInput { 194 t.Errorf("Expected intermediate zip %s to be an input to merge zips but found %s instead", 195 info.intermediateZip, mergeInput) 196 } 197 198 // Override output zip (which was actually the intermediate zip file) with the actual 199 // output zip. 200 info.outputZip = android.NormalizePathForTesting(bp.Output) 201 202 // Save the zips to be merged into the intermediate zip. 203 info.mergeZips = android.NormalizePathsForTesting(bp.Inputs) 204 } 205 } 206 207 if !seenBuildNumberFile { 208 panic(fmt.Sprintf("Every snapshot must include the %s file", BUILD_NUMBER_FILE)) 209 } 210 211 info.copyRules = copyRules.String() 212 info.otherCopyRules = otherCopyRules.String() 213 214 return info 215} 216 217// The enum of different sdk snapshot tests performed by CheckSnapshot. 218type snapshotTest int 219 220const ( 221 // The enumeration of the different test configurations. 222 // A test with the snapshot/Android.bp file but without the original Android.bp file. 223 checkSnapshotWithoutSource snapshotTest = iota 224 225 // A test with both the original source and the snapshot, with the source preferred. 226 checkSnapshotWithSourcePreferred 227 228 // A test with both the original source and the snapshot, with the snapshot preferred. 229 checkSnapshotPreferredWithSource 230 231 // The directory into which the snapshot will be 'unpacked'. 232 snapshotSubDir = "snapshot" 233) 234 235// Check the snapshot build rules. 236// 237// Takes a list of functions which check different facets of the snapshot build rules. 238// Allows each test to customize what is checked without duplicating lots of code 239// or proliferating check methods of different flavors. 240func CheckSnapshot(t *testing.T, result *android.TestResult, name string, dir string, checkers ...snapshotBuildInfoChecker) { 241 t.Helper() 242 243 // The sdk CommonOS variant is always responsible for generating the snapshot. 244 variant := android.CommonOS.Name 245 246 sdk := result.Module(name, variant).(*sdk) 247 248 snapshotBuildInfo := getSdkSnapshotBuildInfo(t, result, sdk) 249 250 // Check state of the snapshot build. 251 for _, checker := range checkers { 252 checker(snapshotBuildInfo) 253 } 254 255 // Make sure that the generated zip file is in the correct place. 256 actual := snapshotBuildInfo.outputZip 257 if dir != "" { 258 dir = filepath.Clean(dir) + "/" 259 } 260 suffix := "" 261 if snapshotBuildInfo.version != soongSdkSnapshotVersionUnversioned { 262 suffix = "-" + snapshotBuildInfo.version 263 } 264 265 expectedZipPath := fmt.Sprintf(".intermediates/%s%s/%s/%s%s.zip", dir, name, variant, name, suffix) 266 android.AssertStringEquals(t, "Snapshot zip file in wrong place", expectedZipPath, actual) 267 268 // Populate a mock filesystem with the files that would have been copied by 269 // the rules. 270 fs := android.MockFS{} 271 for _, dest := range snapshotBuildInfo.snapshotContents { 272 fs[filepath.Join(snapshotSubDir, dest)] = nil 273 } 274 fs[filepath.Join(snapshotSubDir, "Android.bp")] = []byte(snapshotBuildInfo.androidBpContents) 275 276 // If the generated snapshot builders not for the current release then it cannot be loaded by 277 // the current release. 278 currentBuildRelease := latestBuildRelease() 279 if snapshotBuildInfo.targetBuildRelease != currentBuildRelease { 280 return 281 } 282 283 // The preparers from the original source fixture. 284 sourcePreparers := result.Preparer() 285 286 // Preparer to combine the snapshot and the source. 287 snapshotPreparer := android.GroupFixturePreparers(sourcePreparers, fs.AddToFixture()) 288 289 var runSnapshotTestWithCheckers = func(t *testing.T, testConfig snapshotTest, extraPreparer android.FixturePreparer) { 290 t.Helper() 291 customization := snapshotBuildInfo.snapshotTestCustomization(testConfig) 292 customizedPreparers := android.GroupFixturePreparers(customization.preparers...) 293 294 // TODO(b/183184375): Set Config.TestAllowNonExistentPaths = false to verify that all the 295 // files the snapshot needs are actually copied into the snapshot. 296 297 // Run the snapshot with the snapshot preparer and the extra preparer, which must come after as 298 // it may need to modify parts of the MockFS populated by the snapshot preparer. 299 result := android.GroupFixturePreparers(snapshotPreparer, extraPreparer, customizedPreparers). 300 ExtendWithErrorHandler(customization.errorHandler). 301 RunTest(t) 302 303 // Perform any additional checks the test need on the result of processing the snapshot. 304 for _, checker := range customization.checkers { 305 checker(t, result) 306 } 307 } 308 309 t.Run("snapshot without source", func(t *testing.T) { 310 // Remove the source Android.bp file to make sure it works without. 311 removeSourceAndroidBp := android.FixtureModifyMockFS(func(fs android.MockFS) { 312 delete(fs, "Android.bp") 313 }) 314 315 runSnapshotTestWithCheckers(t, checkSnapshotWithoutSource, removeSourceAndroidBp) 316 }) 317 318 t.Run("snapshot with source preferred", func(t *testing.T) { 319 runSnapshotTestWithCheckers(t, checkSnapshotWithSourcePreferred, android.NullFixturePreparer) 320 }) 321 322 t.Run("snapshot preferred with source", func(t *testing.T) { 323 // Replace the snapshot/Android.bp file with one where "prefer: false," has been replaced with 324 // "prefer: true," 325 preferPrebuilts := android.FixtureModifyMockFS(func(fs android.MockFS) { 326 snapshotBpFile := filepath.Join(snapshotSubDir, "Android.bp") 327 unpreferred := string(fs[snapshotBpFile]) 328 fs[snapshotBpFile] = []byte(strings.ReplaceAll(unpreferred, "prefer: false,", "prefer: true,")) 329 }) 330 331 runSnapshotTestWithCheckers(t, checkSnapshotPreferredWithSource, preferPrebuilts) 332 }) 333} 334 335type snapshotBuildInfoChecker func(info *snapshotBuildInfo) 336 337// Check that the snapshot's generated Android.bp is correct. 338// 339// Both the expected and actual string are both trimmed before comparing. 340func checkAndroidBpContents(expected string) snapshotBuildInfoChecker { 341 return func(info *snapshotBuildInfo) { 342 info.t.Helper() 343 android.AssertTrimmedStringEquals(info.t, "Android.bp contents do not match", expected, info.androidBpContents) 344 } 345} 346 347// Check that the snapshot's unversioned generated Android.bp is correct. 348// 349// This func should be used to check the general snapshot generation code. 350// 351// Both the expected and actual string are both trimmed before comparing. 352func checkUnversionedAndroidBpContents(expected string) snapshotBuildInfoChecker { 353 return func(info *snapshotBuildInfo) { 354 info.t.Helper() 355 android.AssertTrimmedStringEquals(info.t, "unversioned Android.bp contents do not match", expected, info.androidUnversionedBpContents) 356 } 357} 358 359// Check that the snapshot's versioned generated Android.bp is correct. 360// 361// This func should only be used to check the version specific snapshot generation code, 362// i.e. the encoding of version into module names and the generation of the _snapshot module. The 363// general snapshot generation code should be checked using the checkUnversionedAndroidBpContents() 364// func. 365// 366// Both the expected and actual string are both trimmed before comparing. 367func checkVersionedAndroidBpContents(expected string) snapshotBuildInfoChecker { 368 return func(info *snapshotBuildInfo) { 369 info.t.Helper() 370 android.AssertTrimmedStringEquals(info.t, "versioned Android.bp contents do not match", expected, info.androidVersionedBpContents) 371 } 372} 373 374// Check that the snapshot's copy rules are correct. 375// 376// The copy rules are formatted as <src> -> <dest>, one per line and then compared 377// to the supplied expected string. Both the expected and actual string are trimmed 378// before comparing. 379func checkAllCopyRules(expected string) snapshotBuildInfoChecker { 380 return func(info *snapshotBuildInfo) { 381 info.t.Helper() 382 android.AssertTrimmedStringEquals(info.t, "Incorrect copy rules", expected, info.copyRules) 383 } 384} 385 386func checkAllOtherCopyRules(expected string) snapshotBuildInfoChecker { 387 return func(info *snapshotBuildInfo) { 388 info.t.Helper() 389 android.AssertTrimmedStringEquals(info.t, "Incorrect copy rules", expected, info.otherCopyRules) 390 } 391} 392 393// Check that the specified paths match the list of zips to merge with the intermediate zip. 394func checkMergeZips(expected ...string) snapshotBuildInfoChecker { 395 return func(info *snapshotBuildInfo) { 396 info.t.Helper() 397 if info.intermediateZip == "" { 398 info.t.Errorf("No intermediate zip file was created") 399 } 400 401 android.AssertDeepEquals(info.t, "mismatching merge zip files", expected, info.mergeZips) 402 } 403} 404 405type resultChecker func(t *testing.T, result *android.TestResult) 406 407// snapshotTestPreparer registers a preparer that will be used to customize the specified 408// snapshotTest. 409func snapshotTestPreparer(snapshotTest snapshotTest, preparer android.FixturePreparer) snapshotBuildInfoChecker { 410 return func(info *snapshotBuildInfo) { 411 customization := info.snapshotTestCustomization(snapshotTest) 412 customization.preparers = append(customization.preparers, preparer) 413 } 414} 415 416// snapshotTestChecker registers a checker that will be run against the result of processing the 417// generated snapshot for the specified snapshotTest. 418func snapshotTestChecker(snapshotTest snapshotTest, checker resultChecker) snapshotBuildInfoChecker { 419 return func(info *snapshotBuildInfo) { 420 customization := info.snapshotTestCustomization(snapshotTest) 421 customization.checkers = append(customization.checkers, checker) 422 } 423} 424 425// snapshotTestErrorHandler registers an error handler to use when processing the snapshot 426// in the specific test case. 427// 428// Generally, the snapshot should work with all the test cases but some do not and just in case 429// there are a lot of issues to resolve, or it will take a lot of time this is a 430// get-out-of-jail-free card that allows progress to be made. 431// 432// deprecated: should only be used as a temporary workaround with an attached to do and bug. 433func snapshotTestErrorHandler(snapshotTest snapshotTest, handler android.FixtureErrorHandler) snapshotBuildInfoChecker { 434 return func(info *snapshotBuildInfo) { 435 customization := info.snapshotTestCustomization(snapshotTest) 436 customization.errorHandler = handler 437 } 438} 439 440// Encapsulates information provided by each test to customize a specific snapshotTest. 441type snapshotTestCustomization struct { 442 // Preparers that are used to customize the test fixture before running the test. 443 preparers []android.FixturePreparer 444 445 // Checkers that are run on the result of processing the preferred snapshot in a specific test 446 // case. 447 checkers []resultChecker 448 449 // Specify an error handler for when processing a specific test case. 450 // 451 // In some cases the generated snapshot cannot be used in a test configuration. Those cases are 452 // invariably bugs that need to be resolved but sometimes that can take a while. This provides a 453 // mechanism to temporarily ignore that error. 454 errorHandler android.FixtureErrorHandler 455} 456 457// Encapsulates information about the snapshot build structure in order to insulate tests from 458// knowing too much about internal structures. 459// 460// All source/input paths are relative either the build directory. All dest/output paths are 461// relative to the snapshot root directory. 462type snapshotBuildInfo struct { 463 t *testing.T 464 465 // The result from RunTest() 466 r *android.TestResult 467 468 // The version of the generated snapshot. 469 // 470 // See snapshotBuilder.version for more information about this field. 471 version string 472 473 // The contents of the generated Android.bp file 474 androidBpContents string 475 476 // The contents of the unversioned Android.bp file 477 androidUnversionedBpContents string 478 479 // The contents of the versioned Android.bp file 480 androidVersionedBpContents string 481 482 // The paths, relative to the snapshot root, of all files and directories copied into the 483 // snapshot. 484 snapshotContents []string 485 486 // A formatted representation of the src/dest pairs for a snapshot, one pair per line, 487 // of the format src -> dest 488 copyRules string 489 490 // A formatted representation of the src/dest pairs for files not in a snapshot, one pair 491 // per line, of the format src -> dest 492 otherCopyRules string 493 494 // The path to the intermediate zip, which is a zip created from the source files copied 495 // into the snapshot directory and which will be merged with other zips to form the final output. 496 // Is am empty string if there is no intermediate zip because there are no zips to merge in. 497 intermediateZip string 498 499 // The paths to the zips to merge into the output zip, does not include the intermediate 500 // zip. 501 mergeZips []string 502 503 // The final output zip. 504 outputZip string 505 506 // The target build release. 507 targetBuildRelease *buildRelease 508 509 // The test specific customizations for each snapshot test. 510 snapshotTestCustomizations map[snapshotTest]*snapshotTestCustomization 511} 512 513// snapshotTestCustomization gets the test specific customization for the specified snapshotTest. 514// 515// If no customization was created previously then it creates a default customization. 516func (i *snapshotBuildInfo) snapshotTestCustomization(snapshotTest snapshotTest) *snapshotTestCustomization { 517 customization := i.snapshotTestCustomizations[snapshotTest] 518 if customization == nil { 519 customization = &snapshotTestCustomization{ 520 errorHandler: android.FixtureExpectsNoErrors, 521 } 522 i.snapshotTestCustomizations[snapshotTest] = customization 523 } 524 return customization 525} 526