1// Copyright 2021 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 bp2build 16 17/* 18For shareable/common bp2build testing functionality and dumping ground for 19specific-but-shared functionality among tests in package 20*/ 21 22import ( 23 "fmt" 24 "sort" 25 "strings" 26 "testing" 27 28 "github.com/google/blueprint/proptools" 29 30 "android/soong/android" 31 "android/soong/android/allowlists" 32 "android/soong/bazel" 33) 34 35var ( 36 buildDir string 37) 38 39func checkError(t *testing.T, errs []error, expectedErr error) bool { 40 t.Helper() 41 42 if len(errs) != 1 { 43 return false 44 } 45 if strings.Contains(errs[0].Error(), expectedErr.Error()) { 46 return true 47 } 48 49 return false 50} 51 52func errored(t *testing.T, tc Bp2buildTestCase, errs []error) bool { 53 t.Helper() 54 if tc.ExpectedErr != nil { 55 // Rely on checkErrors, as this test case is expected to have an error. 56 return false 57 } 58 59 if len(errs) > 0 { 60 for _, err := range errs { 61 t.Errorf("%s: %s", tc.Description, err) 62 } 63 return true 64 } 65 66 // All good, continue execution. 67 return false 68} 69 70func RunBp2BuildTestCaseSimple(t *testing.T, tc Bp2buildTestCase) { 71 t.Helper() 72 RunBp2BuildTestCase(t, func(ctx android.RegistrationContext) {}, tc) 73} 74 75type Bp2buildTestCase struct { 76 Description string 77 ModuleTypeUnderTest string 78 ModuleTypeUnderTestFactory android.ModuleFactory 79 Blueprint string 80 ExpectedBazelTargets []string 81 Filesystem map[string]string 82 Dir string 83 // An error with a string contained within the string of the expected error 84 ExpectedErr error 85 UnconvertedDepsMode unconvertedDepsMode 86 87 // For every directory listed here, the BUILD file for that directory will 88 // be merged with the generated BUILD file. This allows custom BUILD targets 89 // to be used in tests, or use BUILD files to draw package boundaries. 90 KeepBuildFileForDirs []string 91} 92 93func RunBp2BuildTestCase(t *testing.T, registerModuleTypes func(ctx android.RegistrationContext), tc Bp2buildTestCase) { 94 t.Helper() 95 bp2buildSetup := android.GroupFixturePreparers( 96 android.FixtureRegisterWithContext(registerModuleTypes), 97 SetBp2BuildTestRunner, 98 ) 99 runBp2BuildTestCaseWithSetup(t, bp2buildSetup, tc) 100} 101 102func RunApiBp2BuildTestCase(t *testing.T, registerModuleTypes func(ctx android.RegistrationContext), tc Bp2buildTestCase) { 103 t.Helper() 104 apiBp2BuildSetup := android.GroupFixturePreparers( 105 android.FixtureRegisterWithContext(registerModuleTypes), 106 SetApiBp2BuildTestRunner, 107 ) 108 runBp2BuildTestCaseWithSetup(t, apiBp2BuildSetup, tc) 109} 110 111func runBp2BuildTestCaseWithSetup(t *testing.T, extraPreparer android.FixturePreparer, tc Bp2buildTestCase) { 112 t.Helper() 113 dir := "." 114 filesystem := make(map[string][]byte) 115 for f, content := range tc.Filesystem { 116 filesystem[f] = []byte(content) 117 } 118 119 preparers := []android.FixturePreparer{ 120 extraPreparer, 121 android.FixtureMergeMockFs(filesystem), 122 android.FixtureWithRootAndroidBp(tc.Blueprint), 123 android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) { 124 ctx.RegisterModuleType(tc.ModuleTypeUnderTest, tc.ModuleTypeUnderTestFactory) 125 }), 126 android.FixtureModifyContext(func(ctx *android.TestContext) { 127 // A default configuration for tests to not have to specify bp2build_available on top level 128 // targets. 129 bp2buildConfig := android.NewBp2BuildAllowlist().SetDefaultConfig( 130 allowlists.Bp2BuildConfig{ 131 android.Bp2BuildTopLevel: allowlists.Bp2BuildDefaultTrueRecursively, 132 }, 133 ) 134 for _, f := range tc.KeepBuildFileForDirs { 135 bp2buildConfig.SetKeepExistingBuildFile(map[string]bool{ 136 f: /*recursive=*/ false, 137 }) 138 } 139 ctx.RegisterBp2BuildConfig(bp2buildConfig) 140 }), 141 android.FixtureModifyEnv(func(env map[string]string) { 142 if tc.UnconvertedDepsMode == errorModulesUnconvertedDeps { 143 env["BP2BUILD_ERROR_UNCONVERTED"] = "true" 144 } 145 }), 146 } 147 148 preparer := android.GroupFixturePreparers(preparers...) 149 if tc.ExpectedErr != nil { 150 pattern := "\\Q" + tc.ExpectedErr.Error() + "\\E" 151 preparer = preparer.ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(pattern)) 152 } 153 result := preparer.RunTestWithCustomResult(t).(*BazelTestResult) 154 if len(result.Errs) > 0 { 155 return 156 } 157 158 checkDir := dir 159 if tc.Dir != "" { 160 checkDir = tc.Dir 161 } 162 expectedTargets := map[string][]string{ 163 checkDir: tc.ExpectedBazelTargets, 164 } 165 166 result.CompareAllBazelTargets(t, tc.Description, expectedTargets, true) 167} 168 169// SetBp2BuildTestRunner customizes the test fixture mechanism to run tests in Bp2Build mode. 170var SetBp2BuildTestRunner = android.FixtureSetTestRunner(&bazelTestRunner{Bp2Build}) 171 172// SetApiBp2BuildTestRunner customizes the test fixture mechanism to run tests in ApiBp2build mode. 173var SetApiBp2BuildTestRunner = android.FixtureSetTestRunner(&bazelTestRunner{ApiBp2build}) 174 175// bazelTestRunner customizes the test fixture mechanism to run tests of the bp2build and 176// apiBp2build build modes. 177type bazelTestRunner struct { 178 mode CodegenMode 179} 180 181func (b *bazelTestRunner) FinalPreparer(result *android.TestResult) android.CustomTestResult { 182 ctx := result.TestContext 183 switch b.mode { 184 case Bp2Build: 185 ctx.RegisterForBazelConversion() 186 case ApiBp2build: 187 ctx.RegisterForApiBazelConversion() 188 default: 189 panic(fmt.Errorf("unknown build mode: %d", b.mode)) 190 } 191 192 return &BazelTestResult{TestResult: result} 193} 194 195func (b *bazelTestRunner) PostParseProcessor(result android.CustomTestResult) { 196 bazelResult := result.(*BazelTestResult) 197 ctx := bazelResult.TestContext 198 config := bazelResult.Config 199 _, errs := ctx.ResolveDependencies(config) 200 if bazelResult.CollateErrs(errs) { 201 return 202 } 203 204 codegenCtx := NewCodegenContext(config, ctx.Context, Bp2Build, "") 205 res, errs := GenerateBazelTargets(codegenCtx, false) 206 if bazelResult.CollateErrs(errs) { 207 return 208 } 209 210 // Store additional data for access by tests. 211 bazelResult.conversionResults = res 212} 213 214// BazelTestResult is a wrapper around android.TestResult to provide type safe access to the bazel 215// specific data stored by the bazelTestRunner. 216type BazelTestResult struct { 217 *android.TestResult 218 219 // The result returned by the GenerateBazelTargets function. 220 conversionResults 221} 222 223// CompareAllBazelTargets compares the BazelTargets produced by the test for all the directories 224// with the supplied set of expected targets. 225// 226// If ignoreUnexpected=false then this enforces an exact match where every BazelTarget produced must 227// have a corresponding expected BazelTarget. 228// 229// If ignoreUnexpected=true then it will ignore directories for which there are no expected targets. 230func (b BazelTestResult) CompareAllBazelTargets(t *testing.T, description string, expectedTargets map[string][]string, ignoreUnexpected bool) { 231 t.Helper() 232 actualTargets := b.buildFileToTargets 233 234 // Generate the sorted set of directories to check. 235 dirsToCheck := android.SortedKeys(expectedTargets) 236 if !ignoreUnexpected { 237 // This needs to perform an exact match so add the directories in which targets were 238 // produced to the list of directories to check. 239 dirsToCheck = append(dirsToCheck, android.SortedKeys(actualTargets)...) 240 dirsToCheck = android.SortedUniqueStrings(dirsToCheck) 241 } 242 243 for _, dir := range dirsToCheck { 244 expected := expectedTargets[dir] 245 actual := actualTargets[dir] 246 247 if expected == nil { 248 if actual != nil { 249 t.Errorf("did not expect any bazel modules in %q but found %d", dir, len(actual)) 250 } 251 } else if actual == nil { 252 expectedCount := len(expected) 253 if expectedCount > 0 { 254 t.Errorf("expected %d bazel modules in %q but did not find any", expectedCount, dir) 255 } 256 } else { 257 b.CompareBazelTargets(t, description, expected, actual) 258 } 259 } 260} 261 262func (b BazelTestResult) CompareBazelTargets(t *testing.T, description string, expectedContents []string, actualTargets BazelTargets) { 263 t.Helper() 264 if actualCount, expectedCount := len(actualTargets), len(expectedContents); actualCount != expectedCount { 265 t.Errorf("%s: Expected %d bazel target (%s), got %d (%s)", 266 description, expectedCount, expectedContents, actualCount, actualTargets) 267 } else { 268 sort.SliceStable(actualTargets, func(i, j int) bool { 269 return actualTargets[i].name < actualTargets[j].name 270 }) 271 sort.SliceStable(expectedContents, func(i, j int) bool { 272 return getTargetName(expectedContents[i]) < getTargetName(expectedContents[j]) 273 }) 274 for i, actualTarget := range actualTargets { 275 if w, g := expectedContents[i], actualTarget.content; w != g { 276 t.Errorf( 277 "%s[%d]: Expected generated Bazel target to be `%s`, got `%s`", 278 description, i, w, g) 279 } 280 } 281 } 282} 283 284type nestedProps struct { 285 Nested_prop *string 286} 287 288type EmbeddedProps struct { 289 Embedded_prop *string 290} 291 292type OtherEmbeddedProps struct { 293 Other_embedded_prop *string 294} 295 296type customProps struct { 297 EmbeddedProps 298 *OtherEmbeddedProps 299 300 Bool_prop bool 301 Bool_ptr_prop *bool 302 // Ensure that properties tagged `blueprint:mutated` are omitted 303 Int_prop int `blueprint:"mutated"` 304 Int64_ptr_prop *int64 305 String_prop string 306 String_literal_prop *string `android:"arch_variant"` 307 String_ptr_prop *string 308 String_list_prop []string 309 310 Nested_props nestedProps 311 Nested_props_ptr *nestedProps 312 313 Arch_paths []string `android:"path,arch_variant"` 314 Arch_paths_exclude []string `android:"path,arch_variant"` 315 316 // Prop used to indicate this conversion should be 1 module -> multiple targets 317 One_to_many_prop *bool 318 319 Api *string // File describing the APIs of this module 320} 321 322type customModule struct { 323 android.ModuleBase 324 android.BazelModuleBase 325 326 props customProps 327} 328 329// OutputFiles is needed because some instances of this module use dist with a 330// tag property which requires the module implements OutputFileProducer. 331func (m *customModule) OutputFiles(tag string) (android.Paths, error) { 332 return android.PathsForTesting("path" + tag), nil 333} 334 335func (m *customModule) GenerateAndroidBuildActions(ctx android.ModuleContext) { 336 // nothing for now. 337} 338 339func customModuleFactoryBase() android.Module { 340 module := &customModule{} 341 module.AddProperties(&module.props) 342 android.InitBazelModule(module) 343 return module 344} 345 346func customModuleFactoryHostAndDevice() android.Module { 347 m := customModuleFactoryBase() 348 android.InitAndroidArchModule(m, android.HostAndDeviceSupported, android.MultilibBoth) 349 return m 350} 351 352func customModuleFactoryDeviceSupported() android.Module { 353 m := customModuleFactoryBase() 354 android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibBoth) 355 return m 356} 357 358func customModuleFactoryHostSupported() android.Module { 359 m := customModuleFactoryBase() 360 android.InitAndroidArchModule(m, android.HostSupported, android.MultilibBoth) 361 return m 362} 363 364func customModuleFactoryHostAndDeviceDefault() android.Module { 365 m := customModuleFactoryBase() 366 android.InitAndroidArchModule(m, android.HostAndDeviceDefault, android.MultilibBoth) 367 return m 368} 369 370func customModuleFactoryNeitherHostNorDeviceSupported() android.Module { 371 m := customModuleFactoryBase() 372 android.InitAndroidArchModule(m, android.NeitherHostNorDeviceSupported, android.MultilibBoth) 373 return m 374} 375 376type testProps struct { 377 Test_prop struct { 378 Test_string_prop string 379 } 380} 381 382type customTestModule struct { 383 android.ModuleBase 384 385 props customProps 386 test_props testProps 387} 388 389func (m *customTestModule) GenerateAndroidBuildActions(ctx android.ModuleContext) { 390 // nothing for now. 391} 392 393func customTestModuleFactoryBase() android.Module { 394 m := &customTestModule{} 395 m.AddProperties(&m.props) 396 m.AddProperties(&m.test_props) 397 return m 398} 399 400func customTestModuleFactory() android.Module { 401 m := customTestModuleFactoryBase() 402 android.InitAndroidModule(m) 403 return m 404} 405 406type customDefaultsModule struct { 407 android.ModuleBase 408 android.DefaultsModuleBase 409} 410 411func customDefaultsModuleFactoryBase() android.DefaultsModule { 412 module := &customDefaultsModule{} 413 module.AddProperties(&customProps{}) 414 return module 415} 416 417func customDefaultsModuleFactoryBasic() android.Module { 418 return customDefaultsModuleFactoryBase() 419} 420 421func customDefaultsModuleFactory() android.Module { 422 m := customDefaultsModuleFactoryBase() 423 android.InitDefaultsModule(m) 424 return m 425} 426 427type EmbeddedAttr struct { 428 Embedded_attr *string 429} 430 431type OtherEmbeddedAttr struct { 432 Other_embedded_attr *string 433} 434 435type customBazelModuleAttributes struct { 436 EmbeddedAttr 437 *OtherEmbeddedAttr 438 String_literal_prop bazel.StringAttribute 439 String_ptr_prop *string 440 String_list_prop []string 441 Arch_paths bazel.LabelListAttribute 442 Api bazel.LabelAttribute 443} 444 445func (m *customModule) ConvertWithBp2build(ctx android.TopDownMutatorContext) { 446 if p := m.props.One_to_many_prop; p != nil && *p { 447 customBp2buildOneToMany(ctx, m) 448 return 449 } 450 451 paths := bazel.LabelListAttribute{} 452 strAttr := bazel.StringAttribute{} 453 for axis, configToProps := range m.GetArchVariantProperties(ctx, &customProps{}) { 454 for config, props := range configToProps { 455 if custProps, ok := props.(*customProps); ok { 456 if custProps.Arch_paths != nil { 457 paths.SetSelectValue(axis, config, android.BazelLabelForModuleSrcExcludes(ctx, custProps.Arch_paths, custProps.Arch_paths_exclude)) 458 } 459 if custProps.String_literal_prop != nil { 460 strAttr.SetSelectValue(axis, config, custProps.String_literal_prop) 461 } 462 } 463 } 464 } 465 productVariableProps := android.ProductVariableProperties(ctx, ctx.Module()) 466 if props, ok := productVariableProps["String_literal_prop"]; ok { 467 for c, p := range props { 468 if val, ok := p.(*string); ok { 469 strAttr.SetSelectValue(c.ConfigurationAxis(), c.SelectKey(), val) 470 } 471 } 472 } 473 474 paths.ResolveExcludes() 475 476 attrs := &customBazelModuleAttributes{ 477 String_literal_prop: strAttr, 478 String_ptr_prop: m.props.String_ptr_prop, 479 String_list_prop: m.props.String_list_prop, 480 Arch_paths: paths, 481 } 482 483 attrs.Embedded_attr = m.props.Embedded_prop 484 if m.props.OtherEmbeddedProps != nil { 485 attrs.OtherEmbeddedAttr = &OtherEmbeddedAttr{Other_embedded_attr: m.props.OtherEmbeddedProps.Other_embedded_prop} 486 } 487 488 props := bazel.BazelTargetModuleProperties{ 489 Rule_class: "custom", 490 } 491 492 ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs) 493} 494 495var _ android.ApiProvider = (*customModule)(nil) 496 497func (c *customModule) ConvertWithApiBp2build(ctx android.TopDownMutatorContext) { 498 props := bazel.BazelTargetModuleProperties{ 499 Rule_class: "custom_api_contribution", 500 } 501 apiAttribute := bazel.MakeLabelAttribute( 502 android.BazelLabelForModuleSrcSingle(ctx, proptools.String(c.props.Api)).Label, 503 ) 504 attrs := &customBazelModuleAttributes{ 505 Api: *apiAttribute, 506 } 507 ctx.CreateBazelTargetModule(props, 508 android.CommonAttributes{Name: c.Name()}, 509 attrs) 510} 511 512// A bp2build mutator that uses load statements and creates a 1:M mapping from 513// module to target. 514func customBp2buildOneToMany(ctx android.TopDownMutatorContext, m *customModule) { 515 516 baseName := m.Name() 517 attrs := &customBazelModuleAttributes{} 518 519 myLibraryProps := bazel.BazelTargetModuleProperties{ 520 Rule_class: "my_library", 521 Bzl_load_location: "//build/bazel/rules:rules.bzl", 522 } 523 ctx.CreateBazelTargetModule(myLibraryProps, android.CommonAttributes{Name: baseName}, attrs) 524 525 protoLibraryProps := bazel.BazelTargetModuleProperties{ 526 Rule_class: "proto_library", 527 Bzl_load_location: "//build/bazel/rules:proto.bzl", 528 } 529 ctx.CreateBazelTargetModule(protoLibraryProps, android.CommonAttributes{Name: baseName + "_proto_library_deps"}, attrs) 530 531 myProtoLibraryProps := bazel.BazelTargetModuleProperties{ 532 Rule_class: "my_proto_library", 533 Bzl_load_location: "//build/bazel/rules:proto.bzl", 534 } 535 ctx.CreateBazelTargetModule(myProtoLibraryProps, android.CommonAttributes{Name: baseName + "_my_proto_library_deps"}, attrs) 536} 537 538// Helper method for tests to easily access the targets in a dir. 539func generateBazelTargetsForDir(codegenCtx *CodegenContext, dir string) (BazelTargets, []error) { 540 // TODO: Set generateFilegroups to true and/or remove the generateFilegroups argument completely 541 res, err := GenerateBazelTargets(codegenCtx, false) 542 if err != nil { 543 return BazelTargets{}, err 544 } 545 return res.buildFileToTargets[dir], err 546} 547 548func registerCustomModuleForBp2buildConversion(ctx *android.TestContext) { 549 ctx.RegisterModuleType("custom", customModuleFactoryHostAndDevice) 550 ctx.RegisterForBazelConversion() 551} 552 553func simpleModuleDoNotConvertBp2build(typ, name string) string { 554 return fmt.Sprintf(` 555%s { 556 name: "%s", 557 bazel_module: { bp2build_available: false }, 558}`, typ, name) 559} 560 561type AttrNameToString map[string]string 562 563func (a AttrNameToString) clone() AttrNameToString { 564 newAttrs := make(AttrNameToString, len(a)) 565 for k, v := range a { 566 newAttrs[k] = v 567 } 568 return newAttrs 569} 570 571// makeBazelTargetNoRestrictions returns bazel target build file definition that can be host or 572// device specific, or independent of host/device. 573func makeBazelTargetHostOrDevice(typ, name string, attrs AttrNameToString, hod android.HostOrDeviceSupported) string { 574 if _, ok := attrs["target_compatible_with"]; !ok { 575 switch hod { 576 case android.HostSupported: 577 attrs["target_compatible_with"] = `select({ 578 "//build/bazel/platforms/os:android": ["@platforms//:incompatible"], 579 "//conditions:default": [], 580 })` 581 case android.DeviceSupported: 582 attrs["target_compatible_with"] = `["//build/bazel/platforms/os:android"]` 583 } 584 } 585 586 attrStrings := make([]string, 0, len(attrs)+1) 587 if name != "" { 588 attrStrings = append(attrStrings, fmt.Sprintf(` name = "%s",`, name)) 589 } 590 for _, k := range android.SortedKeys(attrs) { 591 attrStrings = append(attrStrings, fmt.Sprintf(" %s = %s,", k, attrs[k])) 592 } 593 return fmt.Sprintf(`%s( 594%s 595)`, typ, strings.Join(attrStrings, "\n")) 596} 597 598// MakeBazelTargetNoRestrictions returns bazel target build file definition that does not add a 599// target_compatible_with. This is useful for module types like filegroup and genrule that arch not 600// arch variant 601func MakeBazelTargetNoRestrictions(typ, name string, attrs AttrNameToString) string { 602 return makeBazelTargetHostOrDevice(typ, name, attrs, android.HostAndDeviceDefault) 603} 604 605// makeBazelTargetNoRestrictions returns bazel target build file definition that is device specific 606// as this is the most common default in Soong. 607func MakeBazelTarget(typ, name string, attrs AttrNameToString) string { 608 return makeBazelTargetHostOrDevice(typ, name, attrs, android.DeviceSupported) 609} 610 611type ExpectedRuleTarget struct { 612 Rule string 613 Name string 614 Attrs AttrNameToString 615 Hod android.HostOrDeviceSupported 616} 617 618func (ebr ExpectedRuleTarget) String() string { 619 return makeBazelTargetHostOrDevice(ebr.Rule, ebr.Name, ebr.Attrs, ebr.Hod) 620} 621 622func makeCcStubSuiteTargets(name string, attrs AttrNameToString) string { 623 if _, hasStubs := attrs["stubs_symbol_file"]; !hasStubs { 624 return "" 625 } 626 STUB_SUITE_ATTRS := map[string]string{ 627 "stubs_symbol_file": "symbol_file", 628 "stubs_versions": "versions", 629 "soname": "soname", 630 "source_library_label": "source_library_label", 631 } 632 633 stubSuiteAttrs := AttrNameToString{} 634 for key, _ := range attrs { 635 if _, stubSuiteAttr := STUB_SUITE_ATTRS[key]; stubSuiteAttr { 636 stubSuiteAttrs[STUB_SUITE_ATTRS[key]] = attrs[key] 637 } else { 638 panic(fmt.Sprintf("unused cc_stub_suite attr %q\n", key)) 639 } 640 } 641 return MakeBazelTarget("cc_stub_suite", name+"_stub_libs", stubSuiteAttrs) 642} 643 644func MakeNeverlinkDuplicateTarget(moduleType string, name string) string { 645 return MakeNeverlinkDuplicateTargetWithAttrs(moduleType, name, AttrNameToString{}) 646} 647 648func MakeNeverlinkDuplicateTargetWithAttrs(moduleType string, name string, extraAttrs AttrNameToString) string { 649 attrs := extraAttrs 650 attrs["neverlink"] = `True` 651 attrs["exports"] = `[":` + name + `"]` 652 return MakeBazelTarget(moduleType, name+"-neverlink", attrs) 653} 654 655func getTargetName(targetContent string) string { 656 data := strings.Split(targetContent, "name = \"") 657 if len(data) < 2 { 658 return "" 659 } else { 660 endIndex := strings.Index(data[1], "\"") 661 return data[1][:endIndex] 662 } 663} 664