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